Previously, Ive talked about how to get a NodeJS app running in a container, and today we're going to deploy that app to Kubernetes.

What is Kubernetes?

For those that haven't ventured into container orchestration, you're probably wondering what Kubernetes is.

Kubernetes is an open-source system for automating deployment, scaling, and management of containerized applications.

Kubernetes ("k8s" for short), was a project originally started at, and designed by Google, and is heavily influenced by Google's large scale cluster management system, Borg. More simply, k8s gives you a platform for managing and running your applications at scale across multiple physicaly (or virtual) machines.

Installing minikube and kubectl

To make things easy, we're going to use minikube on our local machine to run a single-node kubernetes cluster. Minikube is a handy tool that starts a virtual machine and bootstraps the cluster for you.

Firstly, if you dont have VirtualBox, go download and install it. While minikube works with other virtualization platforms, Ive found VirtualBox to be the most reliable.

Next, we need to install not only minikube, but also kubectl which will be used to interact with our k8s cluster. To do so, run the script below:

#!/bin/bash

ARCH=$(uname | awk '{print tolower($0)}')
TARGET_VERSION="v0.15.0"
MINIKUBE_URL="https://storage.googleapis.com/minikube/releases/${TARGET_VERSION}/minikube-${ARCH}-amd64"

KUBECTL_VER="v1.5.1"
KUBECTL_URL="http://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VER}/bin/${ARCH}/amd64/kubectl"

echo "installing latest kubectl..."
curl -Lo kubectl $KUBECTL_URL && chmod +x kubectl && sudo mv kubectl /usr/local/bin/

echo "installing latest minikube..."
curl -Lo minikube $MINIKUBE_URL && chmod +x minikube && sudo mv minikube /usr/local/bin/

ISO_URL="https://storage.googleapis.com/minikube/iso/minikube-v1.0.1.iso"
minikube start \
    --vm-driver=virtualbox \
    --iso-url=$ISO_URL

echo "starting minikube dashboard..."
minikube dashboard

If everything has worked correctly, the kubernetes dashboard should open in your browser.

Using kubectl

When minikube starts, it will automatically set the context for kubectl. If you run kubectl get nodes you should see something like this:

kubectl get nodes
NAME       STATUS    AGE
minikube   Ready     2m

Same with if you run kubectl get pods --all-namespaces:

kubectl get pods --all-namespaces
NAMESPACE     NAME                          READY     STATUS    RESTARTS   AGE
kube-system   kube-addon-manager-minikube   1/1       Running   0          3m
kube-system   kube-dns-v20-qkzgg            3/3       Running   0          3m
kube-system   kubernetes-dashboard-1hs02    1/1       Running   0          3m

While the dashboard is useful for visualizing pods and deployments, we'll primarily be using kubectl to interact with our cluster.

The demo NodeJS app

Back in the article I wrote on deploying Docker containers with CoreOS Fleet, we wrote a little NodeJS server called "stupid-server" (for being stupidly simple). Stupid-server can be found over at github.com/seanmcgary/stupid-server and we'll be using it for this example as well. In the repository, you should find a server that looks something like this:

var http = require('http');

var server = http.createServer(function(req, res){
    res.end(new Date().toISOString());
});

server.listen(8000);

And a Dockerfile that looks like this:

FROM quay.io/seanmcgary/nodejs-raw-base
MAINTAINER Sean McGary <sean@seanmcgary.com>


EXPOSE 8000

ADD start.sh start.sh

RUN chmod +x start.sh

CMD ./start.sh

By default, the Dockerfile will on runtime, clone the repo and run the server. Feel free to edit the Dockerfile to add the repo you've already cloned to the container rather than pulling every time.

Build the container

To build the container, run:

CONTAINER_NAME="<container name>"
docker build -t $CONTAINER_NAME:latest .
docker push $CONTAINER_NAME:latest

Note - since k8s is running in it's own virtual machine, it doesn't have access to Docker images that you build. In order to proceed with this tutorial, you'll need to push your image to some place accessible by k8s. Dockerhub is available and free, but I would highly suggest Google's Container Registry which is extremely low cost and supports private images. You can find the gcr getting started guide over here.

Creating a deployment

To deploy our app, we're going to use the "Deployment" pod type. A deployment wraps the functionality of Pods and ReplicaSets to allow you to declaratively update your application. This is the magic that allows you to leverage zero-downtime deploys via Kubernetes' RollingUpdate functionality.

deployment.yaml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: stupid-server-deployment
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: stupid-server
    spec:
      containers:
      - name: stupid-server
        image: <container image>
        imagePullPolicy: Always
        ports:
        - containerPort: 8000
# vim: set ts=2 expandtab!:

To deploy your deployment, run:

kubectl create -f deployment.yaml

To get your deployment with kubectl, run:

kubectl get deployments
NAME                       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
stupid-server-deployment   1         1         1            1           7m

This metadata will update as your deployment is created and pulls down containers.

Creating a service

Now that our application is deployed, we need a way to expose it to traffic from outside the cluster. To to this, we're going to create a Service. Since we're not covering IngressControllers and advanced load balancing in this tutorial, we're going to open up a NodePort directly to our application on port 30061.

service.yaml

apiVersion: v1
kind: Service
metadata:
  name: stupid-server
  labels:
    app: stupid-server
spec:
  selector:
    app: stupid-server
  ports:
  - port: 8000
    protocol: TCP
    nodePort: 30061
  type: LoadBalancer

Now we can create the service within Kubernetes:

kubectl create -f service.yaml

And we can get the details by running:

kubectl get services
NAME            CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE
kubernetes      10.0.0.1     <none>        443/TCP          1h
stupid-server   10.0.0.121   <pending>     8000:30061/TCP   12m

Now, if we look at the ReplicaSet for our deployment, we should see something like this:

Accessing the stupid-server

In the Service we defined a NodePort; this exposes a port directly to the IP address that minikube is running on so that your app is accessible outside of the cluster.

By default, minikube binds to port 192.168.99.100. To double check this, you can run minikube ip which will return the current IP address.

To access your service, simply curl the IP on port 30061:

curl http://192.168.99.100:30061
2017-01-17T16:10:55.153Z

If everything is successful, you'll see a timestamp returned from your application.

Wrap up

This tutorial was meant as a very quick overview of how to get a NodeJS application up and running on Kubernetes with the least amount of configuration possible. Kubernetes is an incredibly powerful platform that has many more features than we used today. Stay tuned for more tutorials and articles on how to work with Kubernetes!