Migrating an old and (t)rusty application server to k8s

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!

This entry was posted in Devops/Linux. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *