Migrate Custom Java Application deployed in Tomcat to containerised environment on Oracle Cloud
In this article, I will explain how to migrate custom Java applications deployed on Tomcat application server on-premises (or any Cloud Service Provider — CSP) to a containerised environment (deploy in Kubernetes). Also, I will use Oracle Kubernetes Engine (OKE) as a managed Kubernetes environment.
Deployment Architecture
Here is high level deployment architecture on Oracle Cloud Infrastructure (OCI).
You may have several Java applications deployed in one Tomcat server, but we recommend using different pods to run each one of these applications in a Kubernetes environment.
In this architecture, I have a sample application (HelloWorld application) deployed in Tomcat 9.
Next I will describe how to migrate this custom application in OKE.
Migration Steps
Here are steps to migrate the application to OKE.
Step 1: Understand Source Application.
Before starting the migration, ensure you have a clear understanding of your custom Java application’s architecture, dependencies, configurations, and data storage requirements.
For example, if you have any custom configurations in Tomcat configuration and discover all custom application artifacts (ear, war, and jar files).
In this migration, we don’t have any custom Tomcat configurations and we only have helloapp.war file to deploy in the target environment.
Step 2: Choose Containerization Platform.
Decide which containerization platform you want to use. For this article I will use Oracle Container Engine for Kubernetes (OKE) and Oracle Cloud Infrastructure Registry (OCIR) to manage containerized applications.
Step 3: Provision Containerize Platform.
In this step I will provision a simple OKE cluster to migrate a Tomcat application. You can follow OCI documentation to provision the OKE cluster (Quick create or Custom create). Also, you can use Terraform to automate provisioning of the cluster, but that is out of scope for my article.
For simplification, I will use serverless Kubernetes using OCI virtual nodes to provision the cluster.
Before provisioning the cluster, you should create the following policy in root compartment.
define tenancy ske as ocid1.tenancy.oc1..aaaaaaaacrvwsphodcje6wfbc3xsixhzcan5zihki6bvc7xkwqds4tqhzbaq
define compartment ske_compartment as ocid1.compartment.oc1..aaaaaaaa2bou6r766wmrh5zt3vhu2rwdya7ahn4dfdtwzowb662cmtdc5fea
endorse any-user to associate compute-container-instances in compartment ske_compartment of tenancy ske with subnets in tenancy where ALL {request.principal.type='virtualnode',request.operation='CreateContainerInstance',request.principal.subnet=2.subnet.id}
endorse any-user to associate compute-container-instances in compartment ske_compartment of tenancy ske with vnics in tenancy where ALL {request.principal.type='virtualnode',request.operation='CreateContainerInstance',request.principal.subnet=2.subnet.id}
endorse any-user to associate compute-container-instances in compartment ske_compartment of tenancy ske with network-security-group in tenancy where ALL {request.principal.type='virtualnode',request.operation='CreateContainerInstance'}
Also, for security reasons, I provisioned the cluster using a private API endpoint (we can manage the cluster from Bastion host).
After provisioning the cluster, we can use the following commands to access the cluster from Bastion host (we need to install and configure OCI CLI inside Bastion host).
mkdir -p $HOME/.kube
oci ce cluster create-kubeconfig --cluster-id <Cluster OCID>--file $HOME/.kube/config --region <Cluster OCI Region>--token-version 2.0.0 --kube-endpoint PRIVATE_ENDPOINT
export KUBECONFIG=$HOME/.kube/config
[opc@bastion ~]$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
<Node IP> Ready <none> 67m v1.26.2
Step 4: Create source environment docker image.
I am using the following Dockerfile that uses a base Tomcat image (such as tomcat:9.0
) and incorporates your customizations.
# Use a Tomcat base image
FROM tomcat:9.0
# Copy your custom configuration files to the Tomcat conf directory
# COPY custom-configs/ /usr/local/tomcat/conf/
# Copy your application WAR file to the webapps directory
COPY helloapp.war /usr/local/tomcat/webapps/
In this example, the custom-configs
directory contains the custom configuration files you want to include in the Tomcat image (I have commented them out as I don’t have any custom configuration) and helloapp.war is a war file that I want to deploy in Tomcat.
Step 5: Build and Push the Docker Image in OCIR.
In this step, I am going to build the image, tag it and push it to Oracle Container Repository (OICR).
First, you need to login to docker registry using your credentials:
[opc@bastion tomcat-oke]$ docker login registry-1.docker.io
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
Username: <Doker Registry user>
Password:
Login Succeeded!
Build the Docker image using the docker build
command.
[opc@bastion tomcat-oke]$ docker build -t tomact-helloworld:1.0 .
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
STEP 1/2: FROM tomcat:9.0
✔ docker.io/library/tomcat:9.0
Trying to pull docker.io/library/tomcat:9.0...
Getting image source signatures
Copying blob e77c55930573 done
Copying blob f2b566cb887b done
Copying blob 9d19ee268e0d done
Copying blob 38ab1613eab3 done
Copying blob 2699dbfb6757 done
Copying blob 1d4c657a9280 done
Copying blob fc246adea39f done
Copying config ee772f2dcb done
Writing manifest to image destination
Storing signatures
STEP 2/2: COPY helloapp.war /usr/local/tomcat/webapps/
COMMIT tomact-helloworld:1.0
--> 6470d2c1fbb
Successfully tagged localhost/tomact-helloworld:1.0
6470d2c1fbb7da02ad4a2ae7cfb4d34192742c48670b21ec641117eebaf22c9b
Before pushing the image and deploying it to the Kubernetes cluster, we can test the image locally by creating a container from the created image and test it using the curl command.
[opc@bastion tomcat-oke]$ docker run -it --rm -d -p 8080:8080 localhost/tomact-helloworld:1.0
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
5e077eb7f424e0c51af0bd54941894636383d00fd96873f76a755ccede2defce
[opc@bastion tomcat-oke]$ curl http://localhost:8080/helloapp/hello
Hello, World!
Next, I will create a repository in OCIR to push the image.
Then, login to OCIR using following commands (OCIR password is user auth token):
[opc@bastion tomcat-oke]$ docker login mel.ocir.io -u <tenancy>/<username>
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
Password:
Login Succeeded!
Tag the image and push it to OCIR using following commands:
[opc@bastion ~]$ docker tag localhost/tomact-helloworld:1.0 <Region Identifier>.ocir.io/<Tenancy>/app-repo/tomcat-helloworld:1.0
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
[opc@bastion ~]$ docker push <Region Identifier>.ocir.io/<Tenancy>/app-repo/tomcat-helloworld:1.0
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
Getting image source signatures
Copying blob 535126407d49 done
Copying blob ad7bc490a727 done
Copying blob 4b3f0e3a7158 done
Copying blob ed32c2a47a0a done
Copying blob 3bccb20dc942 done
Copying blob 59c56aee1fb4 done
Copying blob 258eaea2c691 done
Copying blob 0640d7cf935d done
Copying config 6470d2c1fb done
Writing manifest to image destination
Storing signatures
Step 6: Create Kubernetes Artifacts.
In this step, I will create Kubernetes artifacts to prepare deployment of the Tomcat application.
- Create namespace.
[opc@bastion ~]$ kubectl create namespace tomcat-helloworld
namespace/tomcat-helloworld created
- Create OCIR secret.
[opc@bastion ~]$ kubectl create secret docker-registry ocirsecret /
-n tomcat-helloworld --docker-server=<Region>.ocir.io /
--docker-username='<OCIR Username>' --docker-password='<User Auth Token>' /
--docker-email='<User Email Address>'
secret/ocirsecret created
- Create Deployment and Service.
Create the following deployment YAML file for Tomcat application.
apiVersion: apps/v1
kind: Deployment
metadata:
name: tomcat-helloworld
namespace: tomcat-helloworld
spec:
replicas: 2
selector:
matchLabels:
app: tomcat-helloworld
template:
metadata:
labels:
app: tomcat-helloworld
spec:
containers:
- name: tomcat-helloworld-container
image: <region>.oicr.io/<tenancy>/app-repo/tomcat-helloworld:1.0
ports:
- containerPort: 8080
imagePullSecrets:
- name: ocirsecret
Create the deployment and check that pod is up and running:
kubectl apply -f tomcat-helloworld-deployment.yaml
[opc@bastion tomcat-oke]$ kubectl get pods -n tomcat-helloworld
NAME READY STATUS RESTARTS AGE
tomcat-helloworld-77985c97f8-wkvqn 1/1 Running 0 5m36s
Finally, create a service (load balancer type) to access the Tomcat application externally.
apiVersion: v1
kind: Service
metadata:
name: tomcat-helloworld-svc
namespace: tomcat-helloworld
spec:
selector:
app: tomcat-helloworld
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer
Create and find load balancer IP address:
kubectl apply -f tomcat-helloworld-service.yaml
[opc@bastion tomcat-oke]$ kubectl get svc -n tomcat-helloworld
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
tomcat-helloworld-svc LoadBalancer 10.96.59.122 <LB IP> 80:31771/TCP 27s
Step 7: Test Application.
You can test the application deployed on OKE using following command (or from a browser).
[opc@bastion tomcat-oke]$ curl http://<LB Public IP>/helloapp/hello
Hello, World!
Step 8: Scale Application.
You can simply scale the Tomcat application using the following command:
kubectl scale deployment tomcat-helloworld -n tomcat-helloworld --replicas=2
[opc@bastion ~]$ kubectl get pods -n tomcat-helloworld
NAME READY STATUS RESTARTS AGE
tomcat-helloworld-77985c97f8-85xbl 1/1 Running 0 2m26s
tomcat-helloworld-77985c97f8-qqxsn 1/1 Running 1 9m1s
Conclusion
In this blog post, I have explained how to migrate a simple Java application deployed in a Tomcat application server (in bare metal or virtual machine) to a containerised environment (specifically OKE in this article). You can expand this to deploy other applications to containerised environments too!
References
Apache Tomcat: https://tomcat.apache.org/
Oracle Container Engine: https://docs.oracle.com/en-us/iaas/Content/ContEng/Concepts/contengoverview.htm