{"id":2412,"date":"2022-10-18T18:46:39","date_gmt":"2022-10-18T18:46:39","guid":{"rendered":"https:\/\/brakkee.org\/site\/?p=2412"},"modified":"2022-10-30T22:21:56","modified_gmt":"2022-10-30T22:21:56","slug":"migrating-an-old-and-rusty-application-server-to-k8s","status":"publish","type":"post","link":"https:\/\/brakkee.org\/site\/2022\/10\/18\/migrating-an-old-and-rusty-application-server-to-k8s\/","title":{"rendered":"Migrating an old and (t)rusty application server to k8s"},"content":{"rendered":"<p>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 <a href=\"https:\/\/flexiblejdbcrealm.wamblee.org\/site\/\">flexible JDBC realm<\/a> database integration with Java EE and also using some tricky <a href=\"https:\/\/docs.oracle.com\/javaee\/6\/tutorial\/doc\/giwhl.html\">CDI<\/a> things. The applications are stable and I even use one of them on almost a daily basis. However, I don&#8217;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.<\/p>\n<p>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.<\/p>\n<p><!--more--><\/p>\n<p>Still here? So let&#8217;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 <em>Dockerfile<\/em>:<\/p>\n<pre>FROM mydockerrepo.example.com\/java8:1.8.0_152\r\n\r\nCOPY wamblee.repo \/etc\/yum.repos.d\r\n\r\nRUN yum makecache\r\nRUN yum install wamblee-glassfish appserv expect -y \r\nCOPY entrypoint gf-changepassword gf-login \/usr\/java\/\r\nENV PATH=\"\/usr\/java\/glassfish\/bin:${PATH}\"\r\n\r\n# java user\r\nRUN mkdir \/home\/java\r\nRUN chown java:java \/home\/java\r\n\r\nENTRYPOINT \/usr\/java\/entrypoint\r\nUSER java\r\n<\/pre>\n<p>The <em>Dockerfile<\/em> 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.<\/p>\n<p>Then the next steps are installing my <em>glassfish<\/em> RPM that I built years ago, together with an <em>appserv<\/em> 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.<\/p>\n<p>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 <em>domain1<\/em>, but renamed to <em>domain1.empty<\/em>. Then when the container starts up, it can determine whether glassfish has initialized by checking for the existence of a subdirectory in the <em>domain1<\/em> directory. If it does not exist, then the container performs the one-time initialization.<\/p>\n<p>The <em>entrypoint<\/em> script is as follows:<\/p>\n<pre>#!\/bin\/bash\r\n\r\nif [[ -z \"$PASSWORD\" ]]\r\nthen\r\n  echo \"PASSWORD environment variable is not set, aborting\" 1&gt;&amp;2\r\n  exit 1\r\nfi\r\n\r\nif [[ -d \"\/usr\/java\/glassfish\/glassfish\/domains\/domain1\/config\" ]]\r\nthen\r\n  echo \"Previous installation exists, keeping domain1\"\r\n  rm -rf \/usr\/java\/glassfish\/glassfish\/domains\/domain1.empty\r\nelse\r\n  echo \"Setting up domain1\"\r\n  mkdir -p \/usr\/java\/glassfish\/glassfish\/domains\/domain1\/\r\n  rsync -av --delete \/usr\/java\/glassfish\/glassfish\/domains\/domain1.empty\/ \/usr\/java\/glassfish\/glassfish\/domains\/domain1\/\r\n  rm -rf \/usr\/java\/glassfish\/glassfish\/domains\/domain1.empty\/\r\n  echo \"Changing password\"\r\n  \/usr\/java\/gf-changepassword \"\" \"$PASSWORD\"\r\n  asadmin start-domain \r\n  \/usr\/java\/gf-login \"$PASSWORD\"\r\n  echo \"Enable secure admin\"\r\n  asadmin enable-secure-admin\r\n  echo \"Stopping\"\r\n  asadmin stop-domain\r\nfi\r\n\r\necho \"Starting glassfish\"\r\nasadmin start-domain\r\n<\/pre>\n<p>The <em>gf-changepassword<\/em> script modifies the password from empty to the password given in the PASSWORD environment variable. This is an <a href=\"https:\/\/man7.org\/linux\/man-pages\/man1\/expect.1.html\">expect<\/a> script based on the output of <em>autoexpect<\/em> <em>asadmin change-admin-password<\/em>:<\/p>\n<pre>#!\/usr\/bin\/expect -f\r\n\r\nset old [lindex $argv 0]\r\nset new [lindex $argv 1]\r\n\r\nset force_conservative 0  ;# set to 1 to force conservative mode even if\r\n                          ;# script wasn't run conservatively originally\r\nif {$force_conservative} {\r\n        set send_slow {1 .1}\r\n        proc send {ignore arg} {\r\n                sleep .1\r\n                exp_send -s -- $arg\r\n        }\r\n}\r\n\r\n\r\nset timeout -1\r\nspawn asadmin change-admin-password\r\nmatch_max 100000\r\nexpect -exact \"Enter admin user name \\[default: admin\\]&gt;\"\r\nsend -- \"\\r\"\r\nexpect -exact \"\\r\r\nEnter the admin password&gt; \"\r\nsend -- \"$old\\r\"\r\nexpect -exact \"\\r\r\nEnter the new admin password&gt; \"\r\nsend -- \"$new\\r\"\r\nexpect -exact \"\\r\r\nEnter the new admin password again&gt; \"\r\nsend -- \"$new\\r\"\r\nexpect eof\r\n<\/pre>\n<p>The <em>gf-login<\/em> script similarly is based on the output of <em>autoexpect asadmin login<\/em> to allow subsequent commands to be run without having to login. Since the results of this are persisted in the <em>domain1<\/em> subdirectory this allows <em>asadmin start-domain<\/em> to be used without entering the password. The <em>gf-login<\/em> script is as follows:<\/p>\n<pre>#!\/usr\/bin\/expect -f\r\n\r\nset new [lindex $argv 0]\r\n\r\nset force_conservative 0  ;# set to 1 to force conservative mode even if\r\n                          ;# script wasn't run conservatively originally\r\nif {$force_conservative} {\r\n        set send_slow {1 .1}\r\n        proc send {ignore arg} {\r\n                sleep .1\r\n                exp_send -s -- $arg\r\n        }\r\n}\r\n\r\nset timeout -1\r\nspawn asadmin login\r\nmatch_max 100000\r\nexpect -exact \"Enter admin user name \\[Enter to accept default\\]&gt; \"\r\nsend -- \"\\r\"\r\nexpect -exact \"\\r\r\nEnter admin password&gt; \"\r\nsend -- \"$new\\r\"\r\nexpect eof\r\n<\/pre>\n<p>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.<\/p>\n<p>Finally, we are ready to deploy glassfish as a <em>StatefulSet<\/em>:<\/p>\n<pre>apiVersion: apps\/v1\r\nkind: StatefulSet\r\nmetadata:\r\n  name: glassfish\r\n  namespace: wamblee-org\r\nspec:\r\n  serviceName: glassfish\r\n  replicas: 1\r\n  selector:\r\n    matchLabels:\r\n      app: glassfish-server\r\n  template:\r\n    metadata:\r\n      labels:\r\n        app: glassfish-server\r\n    spec:\r\n      containers:\r\n        - name: glassfish\r\n          image: cat.wamblee.org\/glassfish:4-1\r\n          imagePullPolicy: Always\r\n          ports:\r\n            - containerPort: 4848\r\n            - containerPort: 8080\r\n          env:\r\n            - name: PASSWORD                                            # A\r\n              valueFrom:\r\n                secretKeyRef:\r\n                  name: glassfish-admin-password\r\n                  key: password\r\n          volumeMounts:\r\n            - name: glassfish-domain1                                   # B\r\n              mountPath: \/usr\/java\/glassfish\/glassfish\/domains\/domain1\r\n            - name: glassfish-wiki                                      # C\r\n              mountPath: \/usr\/java\/wiki\r\n  volumeClaimTemplates:\r\n    - metadata:\r\n        name: glassfish-domain1\r\n      spec:\r\n        volumeName: glassfish-domain1\r\n        accessModes:\r\n          - ReadWriteOnce\r\n        resources:\r\n          requests:\r\n            storage: 10Gi\r\n    - metadata:\r\n        name: glassfish-wiki\r\n      spec:\r\n        volumeName: glassfish-wiki\r\n        accessModes:\r\n          - ReadWriteOnce\r\n        resources:\r\n          requests:\r\n            storage: 10Gi\r\n<\/pre>\n<ul>\n<li><em># A<\/em>: Definition of the admin password<\/li>\n<li><em># B<\/em>: Mounting the <em>domain1<\/em> directory to persist deployments and configuration across restarts of the pod<\/li>\n<li><em># C<\/em>: Another volume required by an application running on glassfish. In this case it is an old JSP wiki.<\/li>\n<\/ul>\n<p>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 <em>asadmin<\/em> or from the admin UI.<\/p>\n<h2>Final thoughts<\/h2>\n<p>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!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 &hellip; <a href=\"https:\/\/brakkee.org\/site\/2022\/10\/18\/migrating-an-old-and-rusty-application-server-to-k8s\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[10],"tags":[],"_links":{"self":[{"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/posts\/2412"}],"collection":[{"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/comments?post=2412"}],"version-history":[{"count":19,"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/posts\/2412\/revisions"}],"predecessor-version":[{"id":2477,"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/posts\/2412\/revisions\/2477"}],"wp:attachment":[{"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/media?parent=2412"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/categories?post=2412"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/tags?post=2412"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}