Kubernetes is an open-source container orchestration tool for managing containerized applications. In this post, we will build, deploy, and manage an end-to-end microservices application in Kubernetes. The sample web application you’ll use is a “todo list” application written in Node.js that uses MongoDB as a database.
You’ll build a container image for this app from a Dockerfile, push the image to Docker Hub, and then deploy it to your cluster. Then you’ll scale the app to meet increased demand.
Build Image with Dockerfile
We will begin by containerizing the web application by packaging it into a Docker image.
Start by changing to your home directory, then use Git to clone this post’s sample web application from its official repository on GitHub.
1 | cd ~ |
Build the container image from the Dockerfile. Use the -t switch to tag the image with the registry username, image name, and an optional tag.
1 | docker build -t merikanto/todo . |
Verify that the image is created by running the docker images command.
1 | docker images |
Next, push your image to the public registry on Docker Hub. To do this, log in to your Docker Hub account:
1 | docker login |
Once you provide your credentials, tag your image using your Docker Hub username:
1 | docker tag merikanto/todo-app |
Then push your image to Docker Hub:
1 | docker push |
You can verify that the new image is available by searching Docker Hub in your web browser.
With the Docker image pushed to the registry, let’s package the application for Kubernetes.
Deploy MongoDB Pod in K8S
The application uses MongoDB to store to-do lists created through the web application. To run MongoDB in Kubernetes, we need to package it as a Pod. When we launch this Pod, it will run a single instance of MongoDB.
Create a new YAML file called db-pod.yaml:
1 | vim db-pod.yaml |
Add the following code which defines a Pod with one container based on MongoDB. We expose port 27017, the standard port used by MongoDB. Notice that the definition contains the labels name and app. We’ll use those labels to identify and configure specific Pods.
1 | # db-pod.yaml |
The data is stored in the volume called mongo-storage which is mapped to the /data/db location of the node. For more information on Volumes, refer to the official Kubernetes volumes documentation.
Run the following command to create a Pod.
1 | kubectl create -f db-pod.yml |
You’ll see this output:
1 | # Output |
Now verify the creation of the Pod.
1 | kubectl get pods |
Let’s make this Pod accessible to the internal consumers of the cluster.
Create a new file called db-service.yaml
that contains this code which defines the Service for MongoDB:
The Service discovers all the Pods in the same Namespace that match the Label with name: db. The selector section of the YAML file explicitly defines this association.
We specify that the Service is visible within the cluster through the declaration type: ClusterIP
.
Save the file and exit the editor. Then use kubectl to submit it to the cluster.
1 | kubectl create -f db-service.yml |
You’ll see this output indicating the Service was created successfully:
1 | # Output |
Let’s get the port on which the Pod is available.
1 | kubectl get services |
You’ll see this output:
1 | Output |
From this output, you can see that the Service is available on port 27017. The web application can reach MongoDB through this service. When it uses the hostname db, the DNS service running within Kubernetes will resolve the address to the ClusterIP associated with the Service. This mechanism allows Pods to discover and communicate with each other.
With the database Pod and Service in place, let’s create a Pod for the web application.
Deploy Web App as a Pod
Let’s package the Docker image you created in the first step of this post as a Pod and deploy it to the cluster. This will act as the front-end web application layer accessible to end users.
Create a new YAML file called web-pod.yaml:
1 | vim web-pod.yaml |
Add the following code which defines a Pod with one container based on the merikanto/todo-app Docker image. It is exposed on port 3000 over the TCP protocol.
1 | # web-pod.yaml |
Notice that the definition contains the labels name and app. A Service will use these labels to route inbound traffic to the appropriate ports.
Run the following command to create the Pod:
1 | kubectl create -f web-pod.yaml |
Let’s verify the creation of the Pod:
1 | kubectl get pods |
Notice that we have both the MongoDB database and web app running as Pods.
Now we will make the web Pod accessible to the public Internet.
Services expose a set of Pods either internally or externally. Let’s define a Service that makes the web Pod publicly available. We’ll expose it through a NodePort, a scheme that makes the Pod accessible through an arbitrary port opened on each Node of the cluster.
Create a new file called web-service.yaml that contains this code which defines the Service for the app:
1 | apiVersion: v1 |
The Service discovers all the Pods in the same Namespace that match the Label with the name web. The selector section of the YAML file explicitly defines this association.
We specify that the Service is of type NodePort through the type: NodePort declaration.
Use kubectl to submit this to the cluster.
1 | kubectl create -f web-service.yml |
You’ll see this output indicating the Service was created successfully:
1 | # Output |
Let’s get the port on which the Pod is available.
1 | kubectl get services |
From this output, we see that the Service is available on port 30770. Let’s try to connect to one of the Worker Nodes.
Obtain the public IP address for one of the Worker Nodes associated with your Kubernetes Cluster by using the DigitalOcean console.
1 | DigitalOcean console showing worker nodes |
Once you’ve obtained the IP address, use the curl command to make an HTTP request to one of the nodes on port 30770:
1 | curl http://your_worker_ip_address:30770 |
You’ll see output similar to this:
1 |
|
You’ve defined the web Pod and a Service. Now let’s look at scaling it with Replica Sets.
Scale the Web Application
A Replica Set ensures that a minimum number of Pods are running in the cluster at all times. When a Pod is packaged as a Replica Set, Kubernetes will always run the minimum number of Pods defined in the specification.
Let’s delete the current Pod and recreate two Pods through the Replica Set. If we leave the Pod running it will not be a part of the Replica Set. Thus, it’s a good idea to launch Pods through a Replica Set, even when the count is just one.
First, delete the existing Pod.
1 | kubectl delete pod web |
Now create a new Replica Set declaration. The definition of the Replica Set is identical to a Pod. The key difference is that it contains the replica element which defines the number of Pods that need to run. Like a Pod, it also contains Labels as metadata that help in Service discovery.
Create the file web-rs.yaml and add this code to the file:
1 | apiVersion: extensions/v1beta1 |
Save and close the file.
Now create the Replica Set:
1 | kubectl create -f web-rs.yaml |
Then check the number of Pods:
1 | kubectl get pods |
When we access the Service through the NodePort, the request will be sent to one of the Pods managed by the Replica Set.
Let’s test the functionality of a Replica Set by deleting one of the Pods and seeing what happens:
1 | kubectl delete pod web-wh6nf |
Look at the Pods again:
1 | kubectl get pods |
As soon as the Pod is deleted, Kubernetes has created another one to ensure the desired count is maintained.
We can scale the Replica Set to run additional web Pods.
Run the following command to scale the web application to 10 Pods.
1 | kubectl scale rs/web --replicas=10 |
Check the Pod count:
1 | kubectl get pods |
You’ll see this output:
1 | Output |
Kubernetes has initiated the process of scaling the web Pod. When the request comes to the Service via the NodePort, it gets routed to one of the Pods in the Replica Set.
When the traffic and load subsides, we can revert to the original configuration of two Pods.
1 | kubectl scale rs/web --replicas=2 |
This command terminates all the Pods except two.
1 | kubectl get pods |
To verify the availability of the Replica Set, try deleting one of the Pods and check the count.
1 | kubectl delete pod web-ws59m |
As soon as the Pod count changes, Kubernetes adjusts it to match the count defined in the YAML file. When one of the web Pods in the Replica Set is deleted, another Pod is immediately created to maintain the desired count. This ensures high availability of the application by ensuring that the minimum number of Pods are running all the time.
You can delete all the objects created during this post with the following command:
1 | kubectl delete -f db-pod.yaml -f db-service.yaml -f web-rs.yaml -f web-service.yaml |