What are the minimum changes I should make to my WebSphere application before deploying to Kubernetes?
This document describes the minimum changes that should be made to an existing WebSphere application during modernization to a Kubernetes based runtime such as IBM Cloud Private. These changes include mandatory changes due to differences in WebSphere runtime, Java and Java EE as well as a set of recommended changes based on the 12-factors methodology that is used for Cloud Native applications.
In order to take advantage of the portability provided by a platform such as Kubernetes and the speed of DevOps it is necessary to deploy applications in immutable containers using automation.
In this context, immutable means that a container will not be modified during its life: no updates, no patches, no configuration changes. If you need to update the application code or apply a patch, you build a new image and deploy it. In order to achieve this, it is necessary to inject environment specific configuration in to the container in each environment (Database UserIDs and Passwords for example) instead of hard-coding the values in properties files in the container.
Automation is required because in a typical Application Server world if a patch or upgrade is required to the runtime it only has to be applied once, but in a container based world there may be hundreds of containers that all need to be rebuilt and deployed in order for the patch to be applied. Automation also allows the speed of the build, test and deploy process to be greatly increased over whatever manual process existed for the traditional Application Server world.
The sections below discuss the required and recommended changes that are necessary to modernize traditional Application Server applications to run in a container but also become portable, scalable and integrated in to logging and monitoring framework of the new platform.
IBM Cloud Transformation Advisor
IBM Cloud Transformation Advisor should be used to analyze the current application EAR/WAR file to identify the mandatory application code changes required during modernization. These may include WebSphere, Java and/or Java EE changes between the current Application Server runtime and the targeted WebSphere version. This video describes the steps required to use IBM Cloud Transformation Advisor
One application per Application Server
In order to maximize the scalability, agility and flexibility of the new platform, a single “application” should be deployed to each WebSphere container. In most cases this will be a single EAR/WAR file but in some cases it may be more than one EAR/WAR file.
It is acceptable to deploy more than one EAR/WAR file to a WebSphere container when they are developed together, stored in the same SCM and deployed using the same CI/CD pipeline but otherwise each WebSphere container should only contain a single EAR/WAR file.
This allows us to develop, test, deploy and scale applications independently without worrying about what effects changes in one application will have on another which is a challenge in the current traditional WebSphere ND world.
The diagram below shows a traditional WebSphere ND environment with four applications deployed and how those applications are modernized to separate WebSphere Liberty containers that can be built, tested, deployed and scaled independently.
Java Version and Build Path
In general, the application code should be compiled with the version of Java that the runtime is using. This means the following changes are required in Eclipse:
- set the default installed JRE to the same version as the runtime will be using
- set the compiler compliance level to the same version as the runtime will be using
- update the project facet for Java to the same version as the runtime will be using
It is also important to compile the application with the same versions of the Java EE features that the runtime will use. In order to achieve this, the Build Path should be updated. First, the Targeted Runtime in Eclipse should be set to the version of the runtime you will be using which will update the Build Path to use the Runtime Libraries provided by the application server.
If this was an existing Eclipse project, it may also be necessary to manually remove old Runtime Libraries from the Build Path.
The result of these changes should be an application that is compiled at the correct level using the Java EE libraries provided by the application server which will minimize runtime execution and Classpath issues.
WebSphere Application Server Migration Toolkit plugin
Once you are ready to make the code changes, the WebSphere Application Server Migration Toolkit plugin to Eclipse should be used to locate the sections of code that require modification and assist in making the changes. This video describes the basic steps required to use the WebSphere Application Server Migration Toolkit plugin
After these changes your application should now be updated to run on the new target version of WebSphere Application Server.
Shared Libraries
Shared Libraries were used in traditional WebSphere to add classes to an application Classpath without adding them to the application EAR or WAR file. These libraries were often used by many applications that were running on the application server and by managing them centrally it reduced the number of duplicate libraries on the system.When deploying a single application per container (application server) there is no need to be concerned with duplicate libraries and Shared Libraries are no longer required.
During the modernization process the libraries that were in the shared library should be included in the application EAR/WAR file (if they are still required) as part of the automated build process (described later). This helps to minimize the number of deployment dependencies that need to be managed.
Logging
In order to have log and trace messages generated in a Docker container collected by the logging framework in ICP they need to be streamed to stdout/stderr (console) instead of written to one or more files. The IBM provided Docker images for WebSphere Liberty and traditional WebSphere stream the application server logging messages to stdout/stderr by default, however, many existing Java applications have been configured to use frameworks such as LOG4J for logging and tracing. LOG4J is a powerful and easy to use logging and tracing framework which can still be used in IBM Cloud Private but should be reconfigured. LOG4J is often configured to write to one or more files on the file system outside of the typical Application Server log files and directories.
This can be achieved by creating a console appender
as shown below that has a target
of System.Out
and the using that appender in the root category
as well as any other categories
that already exist in the log4j.xml
file
<appender name=”console” class=”org.apache.log4j.ConsoleAppender”>
<param name=”Target” value=”System.Out”/>
<layout class=”org.apache.log4j.PatternLayout”>
<param name=”ConversionPattern” value=”%d %-5p [%t] %C{2} (%F:%L) — %m\n”/>
</layout>
</appender><root>
<priority value=”warn”/>
<appender-ref ref=”console”/>
</root>
Source Code Project
The source code repository should contain the application source code as well as the artifacts used to build, test and deploy it. This is covered in the Adding Maven to Existing Projects article in detail, but I’ve included the outline of the structure below for reference.
The project should now contain everything required to build, test and deploy the application in an automated fashion (see below for more on automation).
Environment Specific Configuration
Applications deployed to an Application Server typically require access to backend services such as Databases, Message Queues, LDAP servers and other applications. Connection information for the backend services might include URL, Hostname, Port, UserID and Password and this information is often different depending on the environment (e.g. Dev, Test or Production) that the application is deployed to.
The goal of the modernization is to deploy the application in a portable, immutable container and in order to achieve that any environment specific configuration should be externalized from the container image and injected by the platform. In the Kubernetes world this is achieved using Environment Variables, ConfigMaps and Secrets.
Environment Variables are defined as part of the Kubernetes Deployment and allow individual literal values to be injected in to the container as System Environment Variables. The values can be hardcoded as part of the Deployment/Pod definition or can be extracted from a ConfigMap or Secret. The deployment
snippet below shows DB2USER
and DB2PASS
being extracted from a Secret
named demo-secret
and injected in to the container.
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: “demo-liberty”
spec:
replicas: 3
template:
metadata:
labels:
app: “demo-liberty”
spec:
containers:
— name: demo-liberty
image: mycluster.icp:8500/demo/liberty:1.0
env:
— name: DB2USER
valueFrom:
secretKeyRef:
name: demo-secret
key: username
— name: DB2PASS
valueFrom:
secretKeyRef:
name: demo-secret
key: password
Using the env.
prefix in the WebSphere Liberty server.xml
forces the runtime to use the value of the environment variable when the server starts. See the documentation for more details.
<dataSource id=”jdbc/demo” jndiName=”jdbc/demo” transactional=”true” type=”javax.sql.XADataSource”>
<jdbcDriver libraryRef=”DB2JCCLib”/>
<properties.db2.jcc databaseName=”DEMO” password=”${env.DB2PASS}” portNumber=”50000" serverName=”localhost” user=”${env.DB2USER}”/>
</dataSource>
ConfigMaps can be used to store literal values as well as entire files . Some examples of injecting an entire file might be using a Liberty Configuration snippet to switch LDAP from one provider to another or to turn off tracing or logging for the application. This allows the flexibility to dynamically change configuration files in a container.
The combination of Environment Variables, ConfigMaps and Secrets allow you to decouple configuration artifacts from image content to keep containerized applications portable.
Automation
Basic automation for every modernized application should include build and deployment automation.
Jenkins is a great tool to use to automate the build and deployment steps for a WebSphere application to IBM Cloud Private. A typical Jenkins job will have the following steps:
- Check out the project from SCM
- Build the application EAR/WAR file using Maven
- Build a Docker image containing the application EAR/WAR and any WebSphere specific configuration files (such as server.xml)
- Push the Docker image to a Docker registry (such as the ICP Docker registry)
- Deploy the application to ICP using kubectl (yaml) or a Helm Chart
For build, Maven should be used to compile the source code and create an EAR/WAR file as IBM provides Maven plugins to help building applications using the WebSphere Java EE libraries. This is covered in the Adding Maven to Existing Projects article in detail.
For deploy, there are many options available based on the Kubernetes runtime you are deploying in to. For IBM Cloud Private you can deploy the Docker images directly using kubectl to create the required Deployments, Services and Ingress, or you can use helm and deploy the application using the IBM CloudPak Helm Charts.
Summary
The sections above discuss the required and recommended changes that are necessary to modernize traditional Application Server applications to run in a container but also become portable, scalable and integrated in to logging and monitoring framework of the new platform.