INFRASTRUCTURE

Getting started with Kubernetes

So far I’ve been deploying all my applications on VMs directly using provisioning tools (Ansible) or for simpler applications deployment scripts (like Python’s Fabric). In case of applications running on AWS, I was leveraging a potential of using AMI images for EC2 instances to speed up new instance creation within AutoScaling groups. Although that way of deploying apps proved to be very reliable, running for years in production with no issues, I was looking for some ways to make deployments even more unified across all projects in the organization. Docker turns out to be a good candidate to fulfill such requirements, it defines a deployment unit - container, which is built and always launched in the same way regardless of the app inside, whether it’s Python, Node.js or Java application, the process of running container doesn’t change.

Orchestration

Docker itself wouldn’t be sufficient to provide fully automated environment with safe blue/green deployments, monitoring, replacing containers that are failing, load balancing and more. There are a few tools in the market that offer such capabilities: Kubernetes, Docker Swarm, Mesos + Marathon, AWS ECS. In this post I will focus on explaining how Kubernetes works and what value it brings in managing the infrastructure through Docker containers.

Kubernetes basic concepts

Before diving into managing Kubernetes cluster with a command-line tool kubectl, I would like to explain some basic objects.

Pod - simplest Kubernetes object, defines a process running in the cluster, it wraps one or more (if they are co-located and need to share the same resources) Docker containers

Service - defines access policy to the Pods, so how to make a logical group of Pods available to other pods and possibly external network

Deployment - declarative definition for the application, mainly informs the cluster how many replicas of a given Pod should be running, which version of the Docker image should be used, how much resources should be assigned. When executing a deployment definition, Kubernetes handles the whole process of creating, updating and deleting Pods automatically.

There are many more objects in Kubernetes, but I purposefully skipped more advanced parts to show the minimal setup to deploy an application inside the cluster.

Our application for deployment

To keep it simple, I will deploy the “Hello World” Python application (the code and all configuration files are available on GitHub)

app.py


from flask import Flask, jsonify
app = Flask(__name__)

@app.route('/')
def hello_world():
    return jsonify({'message': 'Hello Kubernetes!'})

Dockerfile


FROM python:3.6.2
COPY . /srv/app
WORKDIR /srv/app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
ENV FLASK_APP app.py
CMD [ "flask", "run", "--host=0.0.0.0", "--port=5000" ]
EXPOSE 5000

We can now build the image:

docker build -t docker-kubernetes:0.0.1 .

and then run our container to see if it works as expected

docker run -p 5000:5000 -it docker-kubernetes:0.0.1

We expect to see:

$ curl localhost:5000
{
  "message": "Hello Kubernetes!"
}

Installing Kubernetes locally

Install kubectl first, which is a command-line tool for managing Kubernetes cluster - scheduling deployments, creating services, listing running services, etc.

For installing the cluster we will use minikube which allows us to create a local single-node cluster using select VM driver. I used Virtualbox, but there are also other drivers.

$ minikube start --vm-driver=virtualbox

To verify that minikube has created a cluster correctly, run

$ minikube ip

$ kubectl

they both should show the same ip address, meaning kubectl is correctly configured to send requests to Kubernetes API.

First Pod and Service

Kubernetes cluster can be configured with yaml configuration files, that specify all the details about deployments. To see our application in action we will need to create 2 Kubernetes objects: Pod and Service. Let’s start with Pod.

We define metadata.name to be our deployment’s name, we will need to provide it every time we want to check or change something in our configuration.

spec.replicas defines how many pods should be running in the cluster, in our case it translates directly to 2 containers with codeidea/docker-kubernetes:0.0.1 image.

Deployment configuration example:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: docker-kubernetes
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: docker-kubernetes
    spec:
      containers:
      - name: docker-kubernetes
        image: codeidea/docker-kubernetes:0.0.1
        ports:
        - containerPort: 5000

Deploying selected image on containers is as simple as executing one command with the configuration file passed as an argument.

$ kubectl apply -f pod.yml

You should see the following output after executing kubectl get pods:

$ kubectl get pods
NAME                                READY     STATUS    RESTARTS   AGE
docker-kubernetes-923010726-k9gx1   1/1       Running   0          4s
docker-kubernetes-923010726-xn08q   1/1       Running   0          4s

The last step is to expose our Pods as a Service, we need to prepare a similar yaml configuration:

kind: Service
apiVersion: v1
metadata:
  name: docker-kubernetes
spec:
  selector:
    app: docker-kubernetes
  ports:
  - protocol: TCP
    port: 5000
    targetPort: 5000
  externalIPs:
  - 192.168.99.100 # ip of our cluster, execute `minikube ip` to get it

Remember about executing that config with:

kubectl apply -f service.yml

In this case we expose port 5000 from our VM running Kubernetes cluster, there are other options as well, I will cover them in other posts.

That’s it, we have our first application running on Kubernetes.

$ curl 192.168.99.100:5000
{
  "message": "Hello Kubernetes!"
}

Updating code with no downtime

When we’ve got our application running on Kubernetes cluster, now it’s essential to keep it running without any disruptions, even while updating the containers. Unfortunately the basic configuration that was provided earlier is not sufficient to support zero-downtime deployments. It can be easily checked by running ab (Apache Benchmark) against our endpoint and updating the container’s image while ab is running.

We have 2 possible ways of updating container’s image:

1) Updating image in yaml file with the configuration of our Pod and executing

kubectl apply -f pod.yml

2) kubectl set image deployment/docker-kubernetes docker-kubernetes=codeidea/docker-kubernetes:0.0.2

If we do it while performing a benchmark test, at the time when containers are being replaced we will see an error:

$ ab -t 30 -c 25 -l http://192.168.99.100:5000/
This is ApacheBench, Version 2.3 <$Revision: 1757674 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.99.100 (be patient)
Completed 5000 requests
apr_socket_recv: Connection refused (61)
Total of 5617 requests completed

Requests are failing, because for a brief moment application is not running on any of the containers, but Service is still accepting requests. It takes some time to initialize the application, so we need to check if the container is running the application first, before we can accept any traffic. We will need to set up readinessProbe which is used to check, when Container is ready for accepting traffic.

initialDelaySeconds - configures how much time should pass before performing the first check periodSeconds - defines interval at which the probe should be performed

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: docker-kubernetes
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: docker-kubernetes
    spec:
      containers:
      - name: docker-kubernetes
        image: codeidea/docker-kubernetes:0.0.1
        ports:
        - containerPort: 5000
        readinessProbe:
          httpGet:
            path: /
            port: 5000
          initialDelaySeconds: 3
          periodSeconds: 3

After updating deployment configuration with kubectl apply -f pod.yml, we can perform our test again (run ab and replace the image in the meanwhile), this time there is no downtime during deployment as expected:

$ ab -t 30 -c 25 -l http://192.168.99.100:5000/
\This is ApacheBench, Version 2.3 <$Revision: 1757674 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.99.100 (be patient)
Completed 5000 requests
Completed 10000 requests
Completed 15000 requests
Completed 20000 requests
Finished 21081 requests


Server Software:        Werkzeug/0.12.2
Server Hostname:        192.168.99.100
Server Port:            5000

Document Path:          /
Document Length:        Variable

Concurrency Level:      25
Time taken for tests:   30.001 seconds
Complete requests:      21081
Failed requests:        0
Total transferred:      3893248 bytes
HTML transferred:       815422 bytes
Requests per second:    702.67 [#/sec] (mean)
Time per request:       35.579 [ms] (mean)
Time per request:       1.423 [ms] (mean, across all concurrent requests)
Transfer rate:          126.73 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.3      0      14
Processing:     1   35  31.5     35     270
Waiting:        1   34  31.2     35     268
Total:          1   35  31.5     36     270

Percentage of the requests served within a certain time (ms)
  50%     36
  66%     54
  75%     59
  80%     62
  90%     71
  95%     81
  98%     98
  99%    114
 100%    270 (longest request)

Summary

It’s fairly easy to start deploying application to Kubernetes. We define our deployments as yaml configuration files, Kubernetes handles container creation for us and with the right config offers zero-downtime deployments. Of course it was just an example of local single-node cluster and there is much more work involved in managing production cluster, but those first step already show that Kubernetes is a very promising solution for managing container-based infrastructure. In the next articles, I will try to cover other important aspects of running Kubernetes cluster: monitoring, alerting, logging, adding more nodes, load balancing and finally deploying and managing production cluster in automated way on AWS.

Tomasz Chojna

Software Developer @ Grand Parade (William Hill)