Getting Started With Kubernetes
Kubernetes is a very 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 this post, we'll learn about Kubernetes by deploying a simple web application across a multi-node Kubernetes cluster. Before we can start deploying containers however, we first need to set up a cluster.
Setting Up a Kubernetes Cluster
GCE is a quick and easy method to get up and running. But for this article, we'll be deploying Kubernetes with an alternative provider. Specifically: 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 different platforms. The majority of these scripts use environmental variables to change how Kubernetes is installed.
For our 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 two node cluster.
We can do that like so:
If we wanted to deploy Kubernetes on AWS, for example, we could do so by changing the
KUBERNETES_PROVIDER environmental variable to
Using the get-kube Install Script
While there are many documented ways to install Kubernetes, I have found the easiest method is to use the get-kube install script.
This script wraps the installation scripts distributed with Kubernetes, and sets up a default cluster, which makes the process quite a bit easier. One of my favorite things about the get-kube script is that it also downloads Kubernetes for you.
To start using this script, you'll need to download it. You can do this with a quick
curl command. Like so:
Once we've downloaded the script, we can execute it by running the
bash command followed by the script name:
The result should look something like this:
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
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.
To download the
kubectl command, we once again use
After, change the permissions to allow execution:
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. You can do that like so:
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 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 telling the
kubectl to reference this configuration file.
Let's take a quick look at that file to get a better understanding of what is being set:
.kube/config file sets two main pieces of information:
- Location of the cluster
- Authentication data for communicating with that cluster
.kube/config file being read by
kubectl, let's attempt to execute a
kubectl command against our Kubernetes cluster to verify everything is working.
The output of the
./kubectl get nodes command shows that we were able to connect to our Kubernetes cluster and display the status of our two nodes:
With this, our installation is complete.
More About Kubernetes Nodes
In the previous command, we used
kubectl to show the status of the available Kubernetes nodes in our 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 (as 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. Kubernetes then determines which node the application container will run on.
This system also lets 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.
Since we'll be using a pre-built Docker container, we won't need to first build a Docker image. However, if you are using custom-built containers, you must first build the container and push it to a Docker repository such as Docker Hub.
You can start the Ghost container with the
kubectl run command:
Here, we created a deployment named
ghost, using the image
ghost, and specified that the
ghost container requires the port
Before going too far, let's first verify that the container is running. We can do this with the
kubectl get pods command:
get pods command lists all 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 on the same host.
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.
Because containers (each one running a single service or program) inside a pod can interact as if they were running on the same physical host, it is easy to deploy applications that are not specifically designed with containers in mind.
A deployment, or deployment object, is similar to the concept of a desired state. 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 always running.
Creating a service for Ghost
Containers within pods can connect to systems external to the cluster. However, external systems, and even other pods, cannot communicate with them. This is because, by default, the port we configured for Ghost is not exposed outside of the cluster.
This is where services come into play.
In order to make our Ghost application accessible outside of the cluster, the deployment we just created needs to be exposed as a Kubernetes service.
To do this, we use the
kubectl expose command:
In the above command, we set the
--type flag to
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 check this has worked with the
kubectl get services command:
At the moment, Kubernetes supports three service types:
If we wanted to only expose this service to other pods within the cluster, we could use the
ClusterIP service type. This is the default, and opens the port on each node for pod-to-pod communication.
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
kubectl describe service:
We can see the assigned port is
With this port, we can use the
curl command to make an HTTP call to any of our Kubernetes nodes:
From the output of the
curl command, we can see that the connection was successful with a
200 OK response.
What's interesting about this is that the request was made to a node that isn't running a Ghost container.
We can see this if we use the
describe the pod:
We can see that the Ghost pod is running on
kubernetes-node-2. However, the HTTP request we just made was to
How does that work?
Well, Kubernetes runs a service called
kube-proxy. Whenever traffic arrives on a service's port,
kube-proxy will transparently proxy that traffic to the node and port inside the pod that is running that service.
In our case, this means that even though the HTTP request was made to
kube-proxy service proxied 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 initially created, and whether or not it has moved. This is avoids a lot of potential headaches that normally come with scaling and self-healing.
Scaling a Deployment
Now that our Ghost service is running and accessible to the outside world, we need to perform our last task: scaling the application across multiple instances.
To do this, we can simply use the
kubectl scale command:
Here, we've specified there should be four
replicas of the
ghost deployment. If we execute
kubectl get pods again we should see three additional pods for the
ghost deployment. Like so:
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!
In this post, we saw how to deploy a multi-node Kubernetes cluster across several Vagrant-managed virtual machines.
We also saw 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 looked at all of this, we have only scratched the surface of what Kubernetes has to offer.