In February I wrote a high level overview of the primary Kubernetes features. In this blog post, we’ll actively use all of these features to deploy a simple 2-tier application inside of a Kubernetes cluster. I highly recommend reading the intro blog before getting started.
The easiest way to deploy a cluster is to use the Google Container Engine, which is available on your Google Compute Engine account. If you don’t have an account, you may use one of the available Getting Started guides in the official Github repository. One of the great things about Kubernetes is that it will function almost identically regardless of where it’s deployed with the exception of some cloud provider integrations.
I’ve created a small test cluster on GCE, which resulted in three instances being created. I’ve also added my public SSH key to master node so that I may log in via SSH and use the kubectl command locally. kubectl is the CLI for Kubernetes and you can also install it locally on your workstation if you prefer.
My demo application is a small python based app that leverages redis as a backend. The source is available here. It expects Docker style environment variables for to point to the redis server and will purposely throw a 5XX status code if there are issues reaching the database.
First we’re going to change the Kubernetes configuration to allow privileged containers. This is only being done for demo purposes and shouldn’t be used in a production environment if you can avoid it. This is for the logging container we’ll be deploying with the application.
sudo sed -i 's/false/true/' /srv/pillar/privilege.sls
sudo salt '*' saltutil.refresh_pillar
sudo salt-minion
id: redis-master
kind: Pod
apiVersion: v1beta1
labels:
name: redis-master
desiredState:
manifest:
version: v1beta1
id: redis-master
containers:
- name: redis-master
image: dockerfile/redis
ports:
- containerPort: 6379
I’m using a Pod as opposed to a replicationController since this is a stateful service and it would not be appropriate to run multiple redis nodes in this scenario.
kubectl create -f redis-master.yaml
kubectl get pods
kind: Service
apiVersion: v1beta1
id: redis
port: 6379
selector:
name: redis-master
containerPort: 6379
kubectl create –f redis-service.yaml
kubectl get services
Notice that I’m hard coding the service port to match the standard redis port of 6379. Making these match isn’t required as so long as the containerPort is correct.
Under the hood, creating a service causes a new iptables entry to be created on each node. The entries will automatically redirect traffic to a port locally where kube-proxy is listening. Kube-proxy is in turn aware of where my redis-master container is running and will proxy connections for me.
To prove this works, I’ll connect to redis via my local address (127.0.0.1:60863) which does not have redis running and I’ll get a proper connection to my database which is on another machine:
Seeing as that works, let’s get back to the point at hand and deploy our application.
id: frontend-controller
apiVersion: v1beta1
kind: ReplicationController
labels:
name: frontend-controller
desiredState:
replicas: 2
replicaSelector:
name: demoapp
podTemplate:
labels:
name: demoapp
desiredState:
manifest:
id: demoapp
version: v1beta3
containers:
- name: frontend
image: doublerr/redis-demo
ports:
- containerPort: 8888
hostPort: 80
- name: logentries
privileged: true
command:
- "--no-stats"
- "-l"
- "<log token>"
- "-j"
- "-t"
- "<account token>"
- "-a app=demoapp"
image: logentries/docker-logentries
volumeMounts:
- mountPath: /var/run/docker.sock
name: dockersock
readOnly: true
volumes:
- name: dockersock
source:
hostDir:
path: /var/run/docker.sock
In the above description, I’m grouping 2 containers based on my redis-demo image and the logentries image respectively. I wanted to show the idea of sidecar containers, which are containers deployed alongside of the primary container and whose job is to support the primary container. In the above case, the sidecar forwards logs to my logentries.com account tagged with name of my app.
If you’re following along you can sign up for a free logentries account to test this out. You’ll need to create a new log, retrieve the log token and account token first. You can then replace the <log token> and <account token> in the yaml file with your values.
kubectl create -f demoapp.yaml
kubectl get pods
Co-locating containers is a powerful concept worth spending some time talking about. Since Kubernetes guarantees co-located containers be run together, my primary container doesn’t need to be aware of anything beyond running the application. In this case logging is dealt with separately. If I want to switch logging services, I just need to redeploy the app with a new sidecar container that is able to send the logs elsewhere. Imagine doing this for monitoring, application content updates, etc . You can really see the power of co-locating containers together.
On a side note the logentries image isn’t perfectly suited for this methodology. It’s designed such that you should run 1 of these containers per docker host and it will forward all container logs upstream. It also requires access to the docker socket on the host. A better design for Kubernetes paradigm would be a container that only collects STDOUT and STDERR for the container it’s attached to. The logentries image works for this proof of concept though and I can see errors in my account:
In closing, Kubernetes is fun to deploy applications into especially if you start thinking of how best to leverage group containers. Most stateless applications will want to leverage the ReplicationController instead of a single pod and services help tie everything together.
For more Docker tutorials, insight and analysis, visit our dedicated Docker page.
Ryan Richard is a systems architect at Rackspace with a background in automation and OpenStack. His primary role revolves around research and development of new technologies. He added the initial support for the Rackspace Cloud into the Kubernetes codebase. He can be reached at: @rackninja on Twitter.