Containerization

Frits Jensen
AI+ Enterprise Engineering
15 min readApr 20, 2020

Introduction

Today most IT organizations are looking into containerizing their existing J2EE / Java EE applications and deploying to public / private cloud. There are good reasons for that; it really is the beginning of the journey to hybrid cloud, which provides the ability to deploy applications, in many cases with none or maybe just a few code changes, to a modern lightweight application server, such as Open Liberty, delivered as a portable container image. The container image can be deployed to any cloud runtime, private or public, that provides Red Hat OpenShift. That’s a lot of flexibility.

OpenShift enables easy access to open source technology as well as enterprise grade software like IBM’s Cloud Paks, which are IBMs delivery mechanism for certified containerized middleware. DevOps pipelines provided by the cloud infrastructure are integrated and ready, and it will speed up development cycles and lower maintenance costs quickly. The infrastructure provides many other desired Service Level Agreement features like automated horizontal scaling, advanced monitoring capabilities and automated health management. Importantly, it enables IT organizations to start using innovative cloud native technology to enhance the existing applications.

Of course, it is an investment to migrate to the cloud and it will impact the whole of the IT organization, but it provides a relatively quick ROI and creates a clear path to move the company’s IT strategy pointing forward. The experiences and reference cases on doing exactly this are plenty.

Different approaches for App Modernization

On the minds of people deciding upon this would be “should we instead refactor our applications into microservices?” Or repackage to improve scaling in the cloud, or enrich the applications with cloud native features? The difference is of course the amount of effort, implications and cost of doing that compared to just containerization.

A microservices architecture is not only about slicing the monolith in smaller parts, it’s also about modernization of the data. Think of containerization as a first low-cost step with minimal risk involved that quickly provides a positive ROI while the whole organization is getting early experiences and have time to adjust to the new world of hybrid cloud. Read more on that topic here: https://medium.com/cloud-engagement-hub/containerization-build-your-own-business-case-1340c2bc0ce9

Containerize using a proven migration process

All you need to do to get started is to migrate and containerize the applications, right? Well, there are some things to consider; the applications must adhere to a set of guidelines in order to be moved from traditional environments to containers and the cloud and there are other things to consider: Would you aim for a lift and shift migration approach where you change as little as possible or would you like to do some specific tailored modernization's as part of the migration now that you have to touch parts of the code anyway and you have to perform end to end tests anyway… There is no easy simple answer as it depends on the overall strategy, the time and budgets available, the nature of the applications being considered, but let’s dive into some of these questions.

Migration process and essential tools

Instead of just charging wildly ahead it is wise to first spend some time doing some high-level reconnaissance, looking over the whole application portfolio and all the environments, and to steer that journey we use a simple proven structured approach. Let’s use this simple three-phase migration process:

1. Discovery phase providing initial estimates and overview

2. Assessment phase refining estimates and doing select test migration projects

3. Performing a set of Proof of Concept (POC) migrations on a representative set of applications

Discovery phase

The Discovery step is about analyzing the complete application portfolio and scoring each of the individual applications adaptability to the proposed target cloud environment

The very first thing to do is to take the guesswork out of the equation; so, how many person hours will all this require you wonder? Provide your best guess or use IBM Cloud Transformation Advisor (TA). TA builds upon knowledge from years of migration projects which enables TA to estimate the effort required to perform containerization.

The TA will import and analyze all applications and configurations and based on hundreds of migration rules it will provide reliable estimates and categorize the complexity of the individual applications as being either simple, moderate or complex. TA will also produce a set of artifacts that helps getting projects going fast; Server configuration, docker file, Helm chart file, deployment file, Jenkins file, POM file etc. for the target platform. The TA can be launched from OpenShift w. IBM Cloud Pak for Applications or it can be run on a workstation PC/Mac with Docker using the free TA Local “beta” version, meaning 90 days trial edition.

Assessment phase

The Assessment phase iterates over the generated discovery estimates and further refines categories and estimates; E.g. if a certain set of applications are following the same design patterns or programming style, then the first application to be migrated would take a lot longer to migrate than the following similarly built applications, as the later ones would benefit from the troubleshooting and porting of common utilities used across such applications that was already done with the first application, and so the estimate can effectively be trimmed quite a bit down.

Also worth considering is if a home-built “legacy” framework is used by the applications then the code might need some additional changes to be able to run on the target platform (more on that later). During the assessment step sometimes some specific “test migrations” will be done focusing on select parts of the legacy code where e.g. investigations into if a standard piece of code/infrastructure could replace some home-built framework code and so on. This will be a big help in nailing down the reference architecture for the target platform, and feed yet more informed estimates into the estimations of total work effort.

POC migration projects

The TA, discovery and assessment phases helped categorize the applications into a set of categories, simple, moderate or complex. Now a set of POC containerization projects will kick-start the migration to the target environment. The projects will also be providing info on actual time spent on the work that was necessary to migrate each of the selected category representative applications, once again refining the estimates, now making them final.

An additional task of the POC projects will be to produce a “cookbook” documenting the steps required to perform the migration. The cookbook will document the code changes done, the configurations done, and serve as vital documentation as part of the overall reference architecture documentation “this is how we do it”. Developers can install IBM’s WebSphere Application Migration Toolkit plugin for Eclipse (WAMT) which is a free toolkit.

Eclipse WAMT —Rules and quick fixes

It provides context aware help (F1) and many automated code fixes for known issues and depending on the nature and age of the application it can reduce migration effort sometimes up to 50%. Even if developers are using other IDEs / editors, Eclipse with WAMT can be installed for this sole purpose and be used to initially work on the code applying the migration rules, then leave the following iteration steps to the preferred IDE / editor.

WAMT contains many rules supporting migrations from different application servers to Open Liberty, including Java version migration and migrating from on-prem to the cloud. Some rules shown in parentheses can change the code automatically in Eclipse. So e.g. for a WebLogic migration to containerized Open Liberty on a later JVM in the cloud we have a total of 456 potential rules that would activate and of those 79 rules contain quick fixes for fixing the code in Eclipse.

WAMT rules — currently available rules and fixes

Must change vs want to change

Depending on a variety of different reasons, it is sometimes desired to change as little code as possible and just aim to containerize the application. One reason for this could be that a decision was made to in-source an application and that speed of migration project delivery is of the essence. The quickest route would be to migrate an application that runs on traditional WebSphere Application Server (tWAS) as is to OpenShift. This approach would require the least amount of changes done to the application and configuration, while still achieving the goal to containerize it. It will require least effort to test the migrated application end to end. The downside is of course is that the application code might be using older maybe even deprecated API’s still, and from a runtime perspective application servers like tWAS have significantly larger disk footprint and are not ideally suited for DevOps pipelines or runtime scaling activities in OpenShift.

Migrating to Open Liberty is preferred and for many more reasons than just disk footprint; e.g. the smaller memory footprint helps achieving higher deployment density by maximizing efficiency of server real estate and bandwidth. However, migration to Open Liberty would likely result in more code and configuration changes. Read this blog entry for a recent viewpoint on this: https://medium.com/cloud-engagement-hub/experiences-using-the-twas-docker-container-557a9b044370. The migration to Open Liberty can require that the application gets upgraded to use newer Java EE APIs, however that could be a welcome opportunity to perform some long-wanted “Version Hygiene “and also finally go and untangle the existing “deployment hair-ball” of dependencies and old scripting frameworks.

Generic migration considerations

J2EE applications might have been built many years ago, maybe even decades ago and those applications are still running. The applications often use a variety of design patterns, APIs that were the latest and greatest back then but now are deprecated. Back then in some cases those APIs were deemed insufficient by the developers, when it came to features and function, and so sometimes developers created a home-built framework to add the wanted function to the applications / infrastructure, and the framework became an integral part of those applications. Those applications are running on premises, on bare metal or running virtualized and managed by the company’s own infrastructure team.

Let’s look at concrete examples of code changes, and of those some code changes are required, and some are optional.

Required changes

· Possible Java Virtual Machine (JVM) upgrade is required: application should use a Java 10 or greater (preferably Java 11 as this is a long-term support edition) as those have greatly improved integration with the container runtime

· Some code changes are mandatory: E.g. applications often use flat files for configuration purposes. Container images should be as generic as possible, so typical configuration key-value pairs should be defined as config maps / secrets and in general it is preferable not being tied to files on external volumes

· Applications using complex home-built frameworks may require changes to the framework or even require redeveloping the applications to use similar standard functions in order to be able to run containerized in the cloud. This might be a significant effort depending on the function of that framework, but such effort might be justified. If not, then option is to leave application as is on-premise / on the current virtual image and simply re-host it

· Fixing other inconsistencies not necessarily flagged as code errors as we would want to make sure the application is as good as can be; e.g. libraries that are incompatible or unsupported with other libraries, out-of-support libraries, JVM version dependencies etc. Applications might not have been incrementally modernized for a long time and could be using old API versions, so maybe it is time…

Desired changes

· Migrating to a lightweight application server runtime will benefit the DevOps toolchain and allows for increased deployment density.

· Do you really need “all” those jar files? I recommend using this opportunity to clean up unused / unnecessary /redundant jar files as they can cause “classpath pollution” and they of course increase the size of the application. For those that (really) are needed, make sure every single individual requirement and dependencies to JVM and other jar files for each included jar file are met, e.g. if upgrading JVM version level then some libraries used might need to be upgraded as well, and some libraries might already be supplied by the underlying runtime and could therefore be removed.

· If the application code is residing in an Eclipse workspace as Eclipse projects, maybe you want to move it to a Maven based project structure that plays well with DevOps

Nice to have changes (here for completeness)

· Java version upgrades do not require you to use newer language constructs of Java as Java is typically backward compatible. However later Java versions contains language upgrades like e.g. Java 8 adds Lambdas, Streams, Java 9 adds Reactive Streams and Java 10 then adds (var) type inference. These constructs can make the code much more readable, less verbose and compact and more robust

· Later Java EE API versions are much less verbose e.g. they use Annotations and injection mechanisms and do not require any boilerplate code or XML configuration, so an upgrade can reduce the amount of code significantly making it easier to understand and maintain the code going forward

JVM migration

A classic Java EE application and its JVM will try to grab all resources available — working as designed. There are plenty of posts on forums telling the story of sudden crashes running in container environments as the infrastructure is trying to manage resources and Kubernetes runtimes can/will be evicting Pods that are too greedy.

These environments use control groups (cgroups) and definitions from OpenShift “yaml” deployment files to limit resources. It’s definitely a good idea to limit memory and CPU while running applications in containers — it prevents an application from using the whole available memory and/or CPU, which makes other containers running on the same system unresponsive. Limiting resources improves reliability and stability of applications. It also enables hardware capacity planning.

Until Java 9 the JVM did not recognize memory nor CPU limits set by the container using flags. And as of Java 10, memory limits are automatically recognized and enforced. Therefore, we can state that JVM’s before Java 10 did not play well with the container and OpenShift runtimes. Some of the changes from Java 9 where back ported to Java 8 update 212, but consider Java 10 (even better Java 11 with long term support) as the minimum Java version to be based on.

Java 10 changes

· Adhering to memory limits set in the container

· Setting available CPU’s in the container

· Setting CPU constraints in the container

Note that OpenJDK’s default settings are not optimal for container environments and some parameters must be changed. In any case before deploying any production workload on OpenShift, the JVM settings that is currently being used by the application being migrated, should be removed and then be used as a reference providing ideas on how to configure the JVM optimally in the container environment. The following guide should be followed: https://docs.openshift.com/container-platform/4.3/nodes/clusters/nodes-cluster-resource-configure.html#nodes-cluster-resource-configure-jdk_nodes-cluster-resource-configure

Migrating old J2EE applications

There are old J2EE applications out there, some maybe > 20 years old, and they are still running and serving customers well. They might not have been modernized incrementally over the years and that could be for many reasons: it’s stuck on deprecated APIs, the developers who made it are not around anymore and nobody knows much about it, the application is mission critical and nobody want to touch it too much, and budget constraints could also have been involved. The options are: containerize as-is, migrate and containerize or just not doing it — leaving it where it is:

1) Least effort: simply containerize the application as is with as few changes as possible and keep using the same traditional application server possibly the latest version. The “version to version” upgrade of both the application server and JVM will possibly lead to a few code changes and mostly in the Java area

2) Some additional effort: Migrate/slightly modernize & containerize to a cloud friendly application server possibly involving some additional code changes by migrating code to later versions of the APIs used or migrating away from deprecated APIs to the current APIs, E.g. migrating JAX-RPC web services to JAX-WS with JAXB or alternatively to JAX-RS RESTful Web Services. Note that an EAR file at a certain version level can embed and contain lower version level Java EE artifacts like WAR files and in some situations that will work just fine. However, mixing certain API’s at different version levels can if you are not careful cause conflicts in between or with the application server classloaders. So, it depends on the given situation.

3) No effort: Leave the applications as is, on-premise or on the current image until its end of life. This can be relevant for applications that have been selected to be redeveloped in near future

Some older applications might require older J2EE APIs from the underlying application server and the following table depicts some of these relationships on a higher level.

· Tomcat is mostly used to run simple Web or Spring framework-based applications where applications must bring their own API implementation libraries

· Open Liberty is a cloud efficient “new age” application server

· JBoss does have a relatively low disk footprint compared to tWAS and WebLogic

API back-level compatibility compared

1) If an application uses older J2EE API’s (e.g. J2EE 1.4, Java EE 5) e.g. EJB Entity beans, then that will have to be migrated to JPA; it can be complex, but many articles supports this effort

2) Tomcat based applications must include many of the required APIs as jar files, but those must all support the required Java version

3) While some older versions of JBoss (v.4 or v.5) could be used to provide a more backward compatible version of the application server, they do not support the required version of Java: (https://access.redhat.com/articles/113083)

Examples of major issues typically found with older J2EE applications

Considerations on home-built application frameworks

Many old J2EE applications have been “enhanced” by home-built frameworks for a variety of historical reasons. Historically J2EE was initially a slowly moving and in some areas an under-specified standard and therefore some function was lacking, and to remediate that developers would create frameworks with the missing function. These frameworks became rigid and maintenance-heavy years later, especially regarding upgrading to newer APIs.

These home-built frameworks can be simple, or they can be complex. They can provide a variety of functions and features such as simply wrapping a logging function, implementing desired code patterns thus enforcing a specific programming model, or it could be as complex as keeping track of application versioning, performing audit trails, injecting home-built security authorization functions by extending standard APIs, or by relying on local machine resources, and even consist of supporting applications that integrate with applications using the framework. Some frameworks must then either be migrated to become portable or they should optimally be replaced by standard provided functions that provide the same kind of service. This will require additional effort but that can sometimes be justified and seen as a welcome opportunity to replace proprietary maintenance heavy code with some standard function provided by the middleware.

Migrating modern Java EE applications

If an application is based on a more recent version of Java EE it could relatively easily be migrated to Java EE 7 or Java EE 8 on Open Liberty and this is highly recommended.

Migrating Spring applications

In most cases an application using the Spring framework can be containerized as is, though it is required to use a Spring version that supports the required Java version. If the application is using 2PC / XA transactions, we recommended to ensure that the application is running on top of a lightweight application server that has a reliable transaction monitor/coordinator like Open Liberty, and that the Spring configuration is such that the Spring framework delegates the transactional work to the application server.

Spring can easily be configured to use the transaction manager of the application server. For both Open Liberty and JBoss, when recent versions of Spring is configured correctly like below, then Spring will use JNDI to lookup the transaction manager interface of the application server:

<bean id=”transactionManager” class=”org.springframework.transaction.jta.JtaTransactionManager”>

For Tomcat based Spring applications that use transactions, a transaction manager like Narayana or other must then be embedded with the application and the relevant Spring configuration to integrate with that must then also provided.

Runtime performance

The performance of the middleware matters both on the developer workstation and in the cloud environments. Quick startup time will help OpenShift scale workload seamlessly (depending on the efficiency of the application itself of course) and low memory consumption will enable higher deployment density as previously outlined. For in depth information on the topic of Open Liberty and performance: https://community.ibm.com/HigherLogic/System/DownloadDocumentFile.ashx?DocumentFileKey=b37a0776-076c-cb9e-ad42-4bd524992846&forceDialog=0

Startup time, memory footprint and performance throughput — competitive comparison
With Spring Boot — using Open Liberty results in less memory usage
With Spring Boot — Using Open Liberty results in higher throughput

Conclusion

This blog has outlined a proven migration process for migration and containerization of existing Java applications. The blog has touched upon a variety of important questions that would be considered, once it has been decided to go ahead with the move to cloud. By utilizing a unique set of tools that provide deep and complete insight, we take the guesswork out of the equation and then also aid in transforming and containerizing the existing workload, so that deployment to the cloud is easier than ever.

Links:

IBM Cloud Transformation Advisor (TA)

https://www.ibm.com/garage/method/practices/learn/ibm-transformation-advisor

WebSphere Migration Knowledge Collection: Downloads (ibm.com)

WebSphere Application Migration Toolkit (WAMT)

https://developer.ibm.com/wasdev/docs/migration/

https://developer.ibm.com/wasdev/downloads/#asset/tools-WebSphere_Application_Server_Migration_Toolkit

Open Liberty

Open Liberty Technical update and POT Materials

https://community.ibm.com/community/user/imwuc/viewdocument/liberty-2000123-technical-over-1?CommunityKey=5c4ba155-561a-4794-9883-bb0c6164e14e&tab=librarydocuments

--

--