JavaScript - - By Jatin Shridhar

Introduction to Kubernetes: How to Deploy a Node.js Docker App

A team of workers with a crane loading containers onto the back of a whale. Kubernetes and Docker.

While container technology has existed for years, Docker really took it mainstream. A lot of companies and developers now use containers to ship their apps. Docker provides an easy to use interface to work with containers.

However, for any non-trivial application, you will not be deploying “one container”, but rather a group of containers on multiple hosts. In this article, we’ll take a look at Kubernetes, an open-source system for automating deployment, scaling, and management of containerized applications.

Prerequisites: This article assumes some familiarity with Docker. If you need a refresher, check out Understanding Docker, Containers and Safer Software Delivery.

What Problem Does Kubernetes Solve?

With Docker, you have simple commands like docker run or docker stop to start/stop a container respectively. Unlike these simple commands that let you do operations on a single container, there is no docker deploy command to push new images to a group of hosts.

Many tools have appeared in recent times to solve this problem of “container orchestration”; popular ones being Mesos, Docker Swarm (now part of the Docker engine), Nomad, and Kubernetes. All of them come with their pros and cons but, arguably, Kubernetes has the most mileage at this point.

Kubernetes (also referred to as ‘k8s’) provides powerful abstractions that completely decouple application operations such as deployments and scaling from underlying infrastructure operations. So, with Kubernetes, you do not work with individual hosts or virtual machines on which to run you code, but rather Kubernetes sees the underlying infrastructure as a sea of compute on which to put containers.

Kubernetes Concepts

Kubernetes has a client/server architecture. Kubernetes server runs on your cluster (a group of hosts) on which you will deploy your application. And you typically interact with the cluster using a client, such as the kubectl CLI.

Pods

A pod is the basic unit that Kubernetes deals with, a group of containers. If there are two or more containers that always need to work together, and should be on the same machine, make them a pod. A pod is a useful abstraction and there was even a proposal to make them a first class docker object.

Node

A node is a physical or virtual machine, running Kubernetes, onto which pods can be scheduled.

Label

A label is a key/value pair that is used to identify a resource. You could label all your pods serving production traffic with “role=production”, for example.

Selector

Selections let you search/filter resources by labels. Following on from the previous example, to get all production pods your selector would be “role=production”.

Service

A service defines a set of pods (typically selected by a “selector”) and a means by which to access them, such as single stable IP address and corresponding DNS name.

Deploy a Node.js App on GKE using Kubernetes

Now, that we are aware of basic Kubernetes concepts, let’s see it in action by deploying a Node.js application on Google Container Engine (referred to as GKE). You’ll need a Google Cloud Platform account for the same (Google provides a free trial with $300 credit).

1. Install Google Cloud SDK and Kubernetes Client

kubectl is the command line interface for running commands against Kubernetes clusters. You can install it as a part of Google Cloud SDK. After Google Cloud SDK installs, run the following command to install kubectl:

$ gcloud components install kubectl

or brew install kubectl if you are on Mac. To verify the installation run kubectl version.

You’ll also need to setup the Google cloud SDK with credentials for your Google cloud account. Just run gcloud init and follow the instructions.

2. Create a GCP project

All Google Cloud Platform resources are created under a project, so create one from the web UI.

Set the default project ID while working with CLI by running:

gcloud config set project {PROJECT_ID}

3. Create a Docker Image of your application

Here is the application that we’ll be working with: express-hello-world. You can see in the Dockerfile that we are using an existing Node.js image from dockerhub. Now, we’ll build our application image by running:

$ docker build -t hello-world-image . 

Run the app locally by running:

docker run --name hello-world -p 3000:3000 hello-world-image

If you visit localhost:3000 you should get the response.

4. Create a cluster

Now we’ll create a cluster with three instances (virtual machines), on which we’ll deploy our application. You can do it from the fairly intuitive web UI by going to container engine page or by running this command:

$ gcloud container clusters create {NAME} --zone {ZONE} 

Let’s create a cluster called hello-world-cluster in us-east1-b by running

$ gcloud container clusters create hello-world-cluster --zone us-east1-b --machine-type f1-micro 

This starts a cluster with three nodes. We are using f1-micro as machine type because it is the smallest available, to ensure minimal costs.

Connect your kubectl client to your cluster by running:

gcloud container clusters get-credentials hello-world-cluster --zone us-east1-b

So, now we have a docker image and a cluster. We want to deploy that image to our cluster and start the containers, which will serve the requests.

5. Upload Docker Image to Google Container Image Registry

The Google container image registry is a cloud registry where you can push your images and these images automatically become available to your container engine cluster. To push an image, you have to build it with a proper name.

To build the container image of this application and tag it for uploading, run the following command:

$ docker build -t gcr.io/{PROJECT_ID}/hello-world-image:v1 .

v1 is the tag of the image.

Next step is to upload the image we just built:

$ gcloud docker -- push gcr.io/{PROJECT_ID}/hello-world-image:v1

6. First Deployment

Now we have a cluster and an image in the cloud. Let’s deploy that image on our cluster with Kubernetes. We’ll do that by creating a deployment spec file. Deployments are a kubernetes resource and all kubernetes resource can be declaratively defined by a spec file. This spec file dictates the desired state of that resource and Kubernetes figures out how to go from the current state to the desired state.

Let’s create one for our first deployment:

deployment.yml

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: hello-world-deployment
spec:
  replicas: 2
  template:
    metadata:
      labels: # labels to select/identify the deployment
        app: hello-world  
    spec:     # pod spec                  
      containers: 
      - name: hello-world 
        image: hello-world-image:v1 # image we pushed
        ports:
        - containerPort: 3000

This spec file says: start two pods where each pod is defined by the given pod spec. Each pod should have one container containing the hello-world-image:v1 we pushed.

Now, run:

$ kubectl create -f deployment.yml --save-config

You can see your deployment status by running kubectl get deployments. To view the pod created by the deployment, run this command: kubectl get pods. You should see the running pods:

$ kubectl get pods
NAME                                     READY     STATUS    RESTARTS   AGE
hello-world-deployment-629197995-ndmrf   1/1       Running   0          27s
hello-world-deployment-629197995-tlx41   1/1       Running   0          27s

Note that we have two pods running because we set the replicas to 2 in the deployment.yml file.

To make sure that the server started, check logs by running:

$ kubectl logs {pod-name}  # kubectl logs hello-world-deployment-629197995-ndmrf

7. Expose the Service to Internet

To expose the service to Internet, you have to put your VMs behind a load balancer. To do that we create a Kubernetes Service.

$ kubectl expose deployment hello-world-deployment --type="LoadBalancer"

Behind the scenes, it creates a service object (a service is a Kubernetes resource, like a Deployment) and also creates a Google Cloud load balancer.

Run kubectl get services to see the public IP of your service. The console output should look like this:

NAME                     CLUSTER-IP       EXTERNAL-IP      PORT(S)          AGE
hello-world-deployment   10.103.254.137   35.185.127.224   3000:30877/TCP   9m
kubernetes               10.103.240.1     <none>           443/TCP          17d

Visit http://<EXTERNAL-IP>:<PORT> to access the service. You can also buy a custom domain name and make it point to this IP.

8. Scaling Your Service

Let’s say your service starts getting more traffic and you need to spin up more instances of your application. To scale up in such a case, just edit your deployment.yml file and change the number of replicas to, say, 3 and then run kubectl apply -f deployment.yml and you will have three pods running in no time. It’s also possible to set up autoscaling, but that’s beyond the scope of this tutorial.

9. Clean Up

Don’t forget to clean up the resources once you are done, otherwise, they’ll keep on eating away your Google credits!

$ kubectl delete service/hello-world-deployment
$ kubectl delete deployment/hello-world-deployment
$ gcloud container clusters delete hello-world-cluster --zone us-east1-b 

Wrapping Up

We’ve covered a lot of ground in this tutorial but as far as Kubernetes is concerned, this is barely scratching the surface. There’s a lot more you can do, like scaling your services to more pods with one command, or mounting secret on pods for things like AWS credentials etc. However, this should be enough to get you started. Head over to kubernetes.io to learn more!

This article was peer reviewed by Graham Cox. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

Sponsors