Give Codeship a try

Want to learn more?

Kubernetes is a highly popular open-source container management system. The goal of the Kubernetes project is to make management of containers across multiple nodes as simple as managing containers on a single system. To accomplish this, it offers quite a few unique features such as Traffic Load Balancing, Self-Healing (automatic restarts), Scheduling, Scaling, and Rolling Updates.

In today’s article, we’ll learn about Kubernetes by deploying a simple web application across a multinode Kubernetes cluster. Before we can start deploying containers however, we first need to set up a cluster.

Standing Up a Kubernetes Cluster

The official Getting Started guide walks you through deploying a Kubernetes cluster on Google’s Container Engine platform. While this is a quick and easy method to get up and running, for this article, we’ll be deploying Kubernetes with an alternative provider, specifically via Vagrant. We’re using Vagrant for a few reasons, but primarily because it shows how to deploy Kubernetes on a generic platform rather than GCE.

Kubernetes comes with several install scripts for various platforms. The majority of these scripts use Bash environmental variables to change what and how Kubernetes is installed.

For this installation, we’ll define two variables:

NUM_NODES – controls the number of nodes to deploy

KUBERNETES_PROVIDER – specifies the platform on which to install Kubernetes

Let’s define that the installation scripts should use the vagrant platform and to provision a 2 node cluster.

$ export NUM_NODES=2
$ export KUBERNETES_PROVIDER=vagrant

If we wanted to deploy Kubernetes on AWS, for example, we could do so by changing the KUBERNETES_PROVIDER environmental variable to aws.

Using the Kubernetes install script

While there are many walk-throughs on how to install Kubernetes, I have found that the easiest method is to use the Kubernetes install script available at (https://get.k8s.io).

This script is essentially a wrapper to the installation scripts distributed with Kubernetes, which makes the process quite a bit easier. One of my favorite things about this script is that it will also download Kubernetes for you.

To start using this script, we’ll need to download it; we can do this with a quick curl command. Once we’ve downloaded the script, we can execute it by running the bash command followed by the script name.

After the script completes execution, we have a running Kubernetes cluster. However, we still have one more step before we can start to interact with this Kubernetes cluster; we need the kubectl command to be installed.

Setting up kubectl

The kubectl command exists for both Linux and Mac OS X. Since I’m running this installation from my MacBook, I’ll be installing the Mac OS X version of kubectl. This means I’ll be running the cluster via Vagrant but interacting with that cluster from my MacBook.

After the kubectl binary is downloaded and permissions are changed to allow execution, the kubectl command is almost ready. One more step is required before we can start interacting with our Kubernetes cluster. That step is to configure the kubectl command.

$ export KUBECONFIG=~/.kube/config

As with most Kubernetes scripts, the kubectl command’s configuration is driven by environmental variables. When we executed the cluster installation script above, that script created a .kube configuration directory in my users home directory. Within that directory, it also created a file named config. This file is used to store information about the Kubernetes cluster that was created.

By setting the KUBECONFIG environmental variable to ~/.kube/config, we are defining that the kubectl command should reference this configuration file. Let’s take a quick look at that file to get a better understanding of what is being set.

The output of the ./kubectl get nodes command shows us that we were able to connect to our Kubernetes cluster and display the status of our two nodes kubernetes-node-1 and kubernetes-node-2. With this, we can move on as our installation is complete.

More About Kubernetes Nodes

In the command above, we used kubectl to show the status of the available Kubernetes Nodes on this cluster. However, we really didn’t explore what a node is or what role it plays within a cluster.

A Kubernetes Node is a physical or virtual (in our case, virtual) machine used to host application containers. In a traditional container-based environment, you would typically define that specific containers run on specified physical or virtual hosts. In a Kubernetes cluster, however, you simply define what application containers you wish to run. The Kubernetes master determines which node the application container will run on.

This methodology also enables the Kubernetes cluster to perform tasks such as automated restarts when containers or nodes die.

Deploying Our Application

With our Kubernetes cluster ready, we can now start deploying application containers. The application container we will be deploying today will be an instance of Ghost. Ghost is a popular JavaScript-based blogging platform, and with its official Docker image, it’s pretty simple to deploy.

Since we’ll be using a prebuilt Docker container, we won’t need to first build a Docker image. However, it is important to call out that in order to use custom-built containers on a Kubernetes cluster. You must first build the container and push it to a Docker repository such as Docker Hub.

To start our Ghost container, we will use the ./kubectl command with the run option.

In the command above, we created a deployment named ghost, using the image ghost and specified that the ghost container requires the port 2368. Before going too far, let’s first verify that the container is running. We can verify this by executing the kubectl command with the get pods options.

The get pods option will tell the kubectl command to list all of the Kubernetes Pods currently deployed to the cluster.

What Are Pods and Deployments?

A Pod is a group of containers that can communicate with each other as though they are running within the same system. For those familiar with Docker, this may sound like linking containers, but there’s actually a bit more to it than that. Containers within Pods are not only able to connect to each other through a localhost connection, the processes running within the containers are also able to share memory segments with other containers.

The goal of a Pod is to allow applications running within the Pod to interact in the same way they would as though they were not running in containers but simply running on the same physical host. This ability makes it easy to deploy applications that are not specifically designed to run within containers.

A Deployment, or Deployment Object, is similar to the concept of a Desired State. Essentially the Deployment is a high-level configuration around a desired function. For example, earlier when we started the Ghost container, we didn’t just launch a Ghost container. We actually configured Kubernetes to ensure that at least one copy of a Ghost container is running.

Creating a service for Ghost

While containers within Pods can connect to systems external to the cluster, external systems and even other Pods cannot communicate with them. This is because, by default, the port defined for the Ghost service is not exposed beyond this cluster. This is where Services come into play.

In order to make our Ghost application accessible outside the cluster, the deployment we just created needs to be exposed as a Kubernetes Service. To set our Ghost deployment as a service, we will use the kubectl command once again, this time using the expose option.

In the above command, we used the flag --type with the argument of NodePort. This flag defines the service type to expose for this service, in this case a NodePort service type. The NodePort service type will set all nodes to listen on the specified port. We can see our change take effect if we use the kubectl command again, but this time with the get services option.

Service types

If we wanted to only expose this service to other Pods within this cluster, we can use the ClusterIP service type, which is the default. This opens the port on each node for Pod to Pod communication.

The LoadBalancer service type is designed to provision an external IP to act as a Load Balancer for the service. Since our deployment is leveraging Vagrant on a local laptop, this option does not work in our environment. It does work with Kubernetes clusters deployed in cloud environments like GCE or AWS.

Testing our Ghost instance

Since we did not specify a port to use when defining our NodePort service, Kubernetes randomly assigned a port. To see what port it assigned, we can use the kubectl command, with the describe service option.

We can see that the port assigned is 32738. With this port, we can use the curl command to make an HTTP call to any of our Kubernetes Nodes and get redirected to port 2368 within our applications container.

From the output of the curl command, we can see that the connection was successful with a 200 OK response. What is interesting about this is that the request was to a node that wasn’t running the Ghost container. We can see this if we use the kubectl to describe the Pod.

In the description above, we can see that the Ghost Pod is running on kubernetes-node-2. However, the HTTP request we just made was to kubernetes-node-1. This is made possible by a Kubernetes service called kube-proxy. With kube-proxy, whenever traffic arrives on a service’s port, the Kubernetes node will check if the service is running local to that node. If not, it will redirect the traffic to a node that is running that service.

In the case above, this means that even though the HTTP request was made to kubernetes-node-1, the kube-proxy service redirected that traffic to kubernetes-node-2 where the container is running.

This feature allows users to run services without having to worry about where the service is and whether or not it has moved from node to node. A very useful feature that reduces quite a bit of maintenance and headache.

Scaling a Deployment

Now that our Ghost service is running and accessible to the outside world, we need to perform our last task, scaling out our Ghost application across multiple instances. To do this, we can simply call the kubectl command again, this time however, with the scale option.

With this last step, we now have our Ghost service running across multiple nodes and multiple pods. As requests are made to our Ghost service, those requests will be load balanced to our various Ghost instances.

Summary

In this article, we learned how to deploy a multinode Kubernetes cluster across several Vagrant-managed virtual machines. We also learned how to deploy an application to the cluster, make that application accessible to external systems, and finally scale out the application to four instances. Even though we performed all of these tasks, we have only scratched the surface of Kubernetes.

Want to explore more of Kubernetes features? Let us know in the comments.

Subscribe via Email

Over 60,000 people from companies like Netflix, Apple, Spotify and O'Reilly are reading our articles. Subscribe to receive a weekly newsletter with articles around Continuous Integration, Docker, and software development best practices.

We promise that we won't spam you. You can unsubscribe any time.

Join the Discussion

Leave us some comments on what you think about this topic or if you like to add something.

Yahav Biran

Great post Ben! I have a very basic question: will the cluster run on my Mac using vagrant? Or my cluster can deployed on my VM (also through vagrant) that runs somewhere but controlled through the kubectl that installed on my Mac? I tried the GCE tutorials and it worked great for me but I could not make it work on generic environment and it looks like the vagrant platform can help me with that.

Thanks

madflojo

In the article the cluster was made up of VM’s (vagrant) running on my Mac. So only one level of vagrant basically.

Yahav Biran

Make sense…. Do you know about an easy way to deploy Kubernetes in a generic cloud environment? More specifically, in a cross cloud environment, so the control is done through a separate VM, Machine, Mac, PC etc with no dependency in the cloud provider?

Ideally your Pod example could show Ghost connecting to a postgres backend etc (two dependent services). Scaling Ghost in its default state doesn’t make sense the way you have it configured here (a .db file is kept on the disk representing the users/articles etc) so round-robin would give you N different sets of content.

Manikanta Gorapalli

thanks Ben thats a great post….. when i an trying to access the application i am getting HTTP/1.1 504 Gateway Timeout can you please help me out on this issue…..