A Journey from Dockerfile to Application Deployment on Kubernetes For Beginners
This blog shows examples to perform a basic application deployment on Kubernetes
In today’s world, Docker and Kubernetes are fast growing and widely used technologies to containerize your application, deploy and manage them using a container orchestration platform.
For a beginner, these technologies, their concepts, and implementation might be difficult to understand.
This blog will help to show you the deployment of a java-based demoapp application to understand right from the Docker image creation to the deployment on a Kubernetes setup.
A basic understanding of Docker Containers and Kubernetes is required to understand the examples given here.
The blog deals with concepts and examples which would be useful post your application development.
So, Let’s start!
Want to read this story later? Save it in Journal.
Assuming, you have already created a java-based servlet application and packaged it as demoapp.war. Now, you can create a Docker image out of it. To create a Docker image, we need to bundle the application related libraries and a web server which will host the application. We shall use Tomcat for this purpose.
Below Dockerfile creates an image using Tomcat 9.0 as a base image and copy the demoapp.war in it.
FROM tomcat:9.0USER rootRUN rm -rf webappsRUN mv webapps.dist webappsCOPY demoapp.war /usr/local/tomcat/webapps/demoapp.war
We need to place the demoapp.war in the same location where Dockerfile is present.
Run below command to create the image.
docker build -t sampleapp:latest .
The above command will create an image which you can check using
docker image ls
Now that, we have docker image ready, let’s proceed for its deployment using Kubernetes.
You can any use any Kubernetes cluster to try out this example. I am using one node cluster for this tutorial which have sampleapp:latest image locally present. We can also push it to any private/public repository as well and pull it from Kubernetes resources like Pod, StatefulSet, Deployments etc.
To deploy any application in Kubernetes cluster, there are many resources we may need to create based on our requirements. We may have a Persistent Volume to store application logs or to provide any configurations from outside. We may have secrets to store our confidential data, configmaps to store the application configurations parameters, a Kubernetes Service to expose the application to outside world.
For our demoapp application, we will be creating all these resources.
Let us see the resources definitions.
namespace.yaml
kind: NamespaceapiVersion: v1metadata: name: sample-ns
We create a separate namespace “sample-ns” and keep all our resources into this.
pv.yaml
kind: PersistentVolumeapiVersion: v1metadata: name: demoapp-logs labels: intent: logsspec: storageClassName: “standard” capacity: storage: 500Mi accessModes: - ReadWriteMany hostPath: path: “/localPath/to/logs”
For this demo, to keep things simple, we have used hostPath to store logs on host machine. NFS and cloud storage providers (If supported) are also other options.
pvc.yaml
apiVersion: v1kind: PersistentVolumeClaimmetadata: name: demoapp-pv-claim namespace: sample-nsspec: storageClassName: “standard” selector: matchExpressions: - {key: intent, operator: In, values: [logs]} accessModes: - ReadWriteMany resources: requests: storage: 500Mi
Look out for the key and values under matchExpressions. These will make sure that correct PV gets attached to this PVC.
secret.yaml
apiVersion: v1kind: Secretmetadata: name: demoapp-secret namespace: sample-nstype: Opaquedata: DB_USER: YWRtaW4= DB_PASS: cGFzc3dvcmQ=
We have created a secret object which stores the Database credentials, needed for application to connect with underlying Database. You can put any confidential information in secret.
secrets can be mounted inside the Pod either as an environment variable or as a volume.
configmap.yaml
apiVersion: v1kind: ConfigMapmetadata: name: demoapp-cmap namespace: sample-nsdata: sample.properties: |- key1=value1 key2=value2 key3=value3
We have created a configmap object which holds some configuration parameters in the form of key-value pair.
Like secret, configmap can be mounted inside the Pod either as an environment variable or as a volume.
service.yaml
apiVersion: v1kind: Servicemetadata: name: demoapp-service namespace: sample-ns labels: intent: service-for-demoappspec: type: NodePort ports: - port: 8080 protocol: TCP targetPort: 8080 selector: app: demoapp— -apiVersion: v1kind: Servicemetadata: name: demoapp-headless-service namespace: sample-ns labels: intent: headless-service-for-demoappspec: clusterIP: None ports: - name: http port: 8080 protocol: TCP targetPort: 8080 selector: app: demoapp
Above are the service object definitions, where we create two services, one NodePort and other headless service. NodePort service will open up a port on host machine and map it to port 8080. headless service is required for statefulset applications.
Headless service: It is a service with a service IP but instead of load-balancing it returns the IPs of our associated Pods. This allows us to interact directly with the Pods instead of a proxy.
Refer here for more details on Headless service.
sts.yaml
apiVersion: apps/v1kind: StatefulSetmetadata: name: demoapp-statefulset namespace: sample-ns labels: app: demoappspec: replicas: 2 selector: matchLabels: app: demoapp serviceName: “demoapp-headless-service” template: metadata: labels: app: demoapp spec: volumes: - name: tomcatlogs persistentVolumeClaim: claimName: “demoapp-pv-claim” - name: configmap-volume configMap: name: “demoapp-cmap” containers: - name: demoapp image: sampleapp:latest imagePullPolicy: IfNotPresent ports: - name: http containerPort: 8080 protocol: TCP envFrom: - secretRef: name: “demoapp-secret” volumeMounts: - name: tomcatlogs mountPath: /usr/local/tomcat/logs - name: configmap-volume mountPath: /opt/cmap
The above resource corresponds to a statefulset (sts) definition, which will spawn 2 application pods as mentioned by replicas: 2. We have provided the image name that we built earlier, in the sts definition. The configmap can be seen mounted as a volume and secret can be seen mounted as an environment variable in the sts definition. The configmap will create a file “sample.properties” at /opt/cmap location inside the pod and secret data will be exposed as environment variables. The application code can easily access them using correct available APIs.
Also, the tomcat logs are mounted outside using “demoapp-pv-claim” PVC which in turn is bounded with a PV created earlier.
Let’s create these resources in below order.
kubectl create -f namespace.yamlkubectl create -f configmap.yamlkubectl create -f pv.yamlkubectl create -f pvc.yamlkubectl create -f secret.yamlkubectl create -f sts.yamlkubectl create -f service.yaml
Once all resource is created, get the NodePort and access the application using below URL
http://<Node_IP>:<Node_Port>/demoapp
Use below command to get the NodePort
kubectl get svc | grep demoapp-service
Once done with the exercise, the resources can be deleted in the below order
kubectl delete -f sts.yamlkubectl delete -f configmap.yamlkubectl delete -f pvc.yamlkubectl delete -f pv.yamlkubectl delete -f secret.yamlkubectl delete -f service.yamlkubectl delete -f namespace.yaml
So, that’s all for the demo. Hope you find it useful. Thanks for reading!
Enjoyed this post? Subscribe to the Machine Learnings newsletter for an easy understanding of AI advances shaping our world.