As part of my project to move everything from VMs to kubernetes and to get rid of some really old VMs (talking OpenSuSE 11 from 2010 here), it is now time to migrate my old java applications to kubernetes. These apps are basic web apps but I am using some advanced stuff such as my own flexible JDBC realm database integration with Java EE and also using some tricky CDI things. The applications are stable and I even use one of them on almost a daily basis. However, I don’t want to spend time porting these apps over to a different environment. This is why a want to migrate this old (ancient) Glassfish V4 application server as is to Kubernetes.
The aim is to basically freeze this setup in time. I do not plan to work on these applications but I still use them. Instead of updating them at a later point in time, I would probably rewrite them from scratch and perhaps even in python instead of java using for instance django. Therefore, the aim is to freeze this setup in time as it were so I can keep on using it. No need to spend more time on it now. So this is going to get a little bit dirty.
Still here? So let’s start. As part of my previous setup, I already created my own RPM for Glassfish V4 that has a single domain and an empty password as a basis. The first step therefore, it to deploy it into a container. This starts of course with a Dockerfile:
FROM mydockerrepo.example.com/java8:1.8.0_152 COPY wamblee.repo /etc/yum.repos.d RUN yum makecache RUN yum install wamblee-glassfish appserv expect -y COPY entrypoint gf-changepassword gf-login /usr/java/ ENV PATH="/usr/java/glassfish/bin:${PATH}" # java user RUN mkdir /home/java RUN chown java:java /home/java ENTRYPOINT /usr/java/entrypoint USER java
The Dockerfile starts with extending a rocky linux 9 image that has an old java 8 version. I built this image previously and it is available in my own nexus repository (image name above was redacted). Using the most recent java 8 version resulted in several certificate errors when running it most likely because of more strict security configuration in the latest jdk.
Then the next steps are installing my glassfish RPM that I built years ago, together with an appserv package, which contains some useful scripts that I developed myself also years ago to more easily deploy applications on glassfish. The availability of these RPMs simplified the setup considerably and I am glad I built those all these years ago. The expect package is also installed since that is used by the initialization scripts.
Finally, the environment and entrypoint are setup. The entrypoint is a script that initializes the glassfish password when the container is starting up for the first time. The glassfish RPM contains a single domain named domain1, but renamed to domain1.empty. Then when the container starts up, it can determine whether glassfish has initialized by checking for the existence of a subdirectory in the domain1 directory. If it does not exist, then the container performs the one-time initialization.
The entrypoint script is as follows:
#!/bin/bash if [[ -z "$PASSWORD" ]] then echo "PASSWORD environment variable is not set, aborting" 1>&2 exit 1 fi if [[ -d "/usr/java/glassfish/glassfish/domains/domain1/config" ]] then echo "Previous installation exists, keeping domain1" rm -rf /usr/java/glassfish/glassfish/domains/domain1.empty else echo "Setting up domain1" mkdir -p /usr/java/glassfish/glassfish/domains/domain1/ rsync -av --delete /usr/java/glassfish/glassfish/domains/domain1.empty/ /usr/java/glassfish/glassfish/domains/domain1/ rm -rf /usr/java/glassfish/glassfish/domains/domain1.empty/ echo "Changing password" /usr/java/gf-changepassword "" "$PASSWORD" asadmin start-domain /usr/java/gf-login "$PASSWORD" echo "Enable secure admin" asadmin enable-secure-admin echo "Stopping" asadmin stop-domain fi echo "Starting glassfish" asadmin start-domain
The gf-changepassword script modifies the password from empty to the password given in the PASSWORD environment variable. This is an expect script based on the output of autoexpect asadmin change-admin-password:
#!/usr/bin/expect -f set old [lindex $argv 0] set new [lindex $argv 1] set force_conservative 0 ;# set to 1 to force conservative mode even if ;# script wasn't run conservatively originally if {$force_conservative} { set send_slow {1 .1} proc send {ignore arg} { sleep .1 exp_send -s -- $arg } } set timeout -1 spawn asadmin change-admin-password match_max 100000 expect -exact "Enter admin user name \[default: admin\]>" send -- "\r" expect -exact "\r Enter the admin password> " send -- "$old\r" expect -exact "\r Enter the new admin password> " send -- "$new\r" expect -exact "\r Enter the new admin password again> " send -- "$new\r" expect eof
The gf-login script similarly is based on the output of autoexpect asadmin login to allow subsequent commands to be run without having to login. Since the results of this are persisted in the domain1 subdirectory this allows asadmin start-domain to be used without entering the password. The gf-login script is as follows:
#!/usr/bin/expect -f set new [lindex $argv 0] set force_conservative 0 ;# set to 1 to force conservative mode even if ;# script wasn't run conservatively originally if {$force_conservative} { set send_slow {1 .1} proc send {ignore arg} { sleep .1 exp_send -s -- $arg } } set timeout -1 spawn asadmin login match_max 100000 expect -exact "Enter admin user name \[Enter to accept default\]> " send -- "\r" expect -exact "\r Enter admin password> " send -- "$new\r" expect eof
This setup assumes that subsequent changes to the password are done using asadmin change-admin-password and asadmin login from inside the container and not from the glassfish UI. If you change the admin password through the UI then glassfish will no longer startup since the login does not match anymore.
Finally, we are ready to deploy glassfish as a StatefulSet:
apiVersion: apps/v1 kind: StatefulSet metadata: name: glassfish namespace: wamblee-org spec: serviceName: glassfish replicas: 1 selector: matchLabels: app: glassfish-server template: metadata: labels: app: glassfish-server spec: containers: - name: glassfish image: cat.wamblee.org/glassfish:4-1 imagePullPolicy: Always ports: - containerPort: 4848 - containerPort: 8080 env: - name: PASSWORD # A valueFrom: secretKeyRef: name: glassfish-admin-password key: password volumeMounts: - name: glassfish-domain1 # B mountPath: /usr/java/glassfish/glassfish/domains/domain1 - name: glassfish-wiki # C mountPath: /usr/java/wiki volumeClaimTemplates: - metadata: name: glassfish-domain1 spec: volumeName: glassfish-domain1 accessModes: - ReadWriteOnce resources: requests: storage: 10Gi - metadata: name: glassfish-wiki spec: volumeName: glassfish-wiki accessModes: - ReadWriteOnce resources: requests: storage: 10Gi
- # A: Definition of the admin password
- # B: Mounting the domain1 directory to persist deployments and configuration across restarts of the pod
- # C: Another volume required by an application running on glassfish. In this case it is an old JSP wiki.
Here volumes are defined in the usual way, as is the service, and the exposure of the service. Applications can be deployed on glassfish from within the container using asadmin or from the admin UI.
Final thoughts
This post should have given an impression on how to package a legacy application such as glassfish in a container. Clearly such an old application is not designed for containerization and so requires a little bit of hacking to get everything working. Glassfish has served me well for the past decade, on to the next decade!