A Journey from Dockerfile to Application Deployment on Kubernetes For Beginners

This blog shows examples to perform a basic application deployment on Kubernetes

Nishant Mohan
5 min readJun 18, 2021
Photo by Ian Taylor on Unsplash

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.

--

--

Nishant Mohan

A passionate developer who believes in learning & sharing.