Kubes for Noobs: An Introduction to Kubernetes
Overview
This tutorial offers a conceptual introduction to Kubernetes, and then guides the user through a hands-on, beginner-level exercise using Kubernetes and Google Cloud to create a compute cluster and deploy a simple web application.
Learning Objectives
- Understand the advantages of using Kubernetes to manage distributed computing systems.
- Set up the tools for using Kubernetes, on your machine and in Google Cloud.
- Create your first Kubernetes cluster.
- Deploy a sample app and load balancer.
- Route traffic through the load balancer to the application using a Kubernetes service.
Outline
- The Kubernetes philosophy: a conceptual introduction
- Getting a handle on Kubernetes: setting up your tools
- Set up your Google Cloud
- Set up kubectl
- Cloud Operations with Kubernetes
- Create a cluster in the cloud
- Deploy a sample app
- Route traffic to the application
- Test your system
- Clean up by destroying your cluster
The Kubernetes philosophy: a conceptual introduction
Kubernetes is a tool for orchestrating distributed computing systems. In recent years it has been widely adopted for applications both small and large due to its ease of use and its power as a force multiplier in operations, allowing a small number of engineers to deploy, reliably maintain, and seamlessly upgrade distributed computing systems, work that previously would have occupied a much larger team.
In large part the power of Kubernetes to ease operations work lies in the philosophy of declarative configuration which it embodies. Essentially this means that as a user, you command Kubernetes to bring about a particular state of your computing system by describing or “declaring” the desired end state, rather than by issuing a sequence of commands to perform specific operations (what is known as imperative configuration, by contrast). As an analogy, consider helping your friend navigate to a cafe to meet you for lunch. An imperative way to “configure” their location to the coordinates of the cafe would be to issue a sequence of concrete instructions, such as “walk to the corner of Blah Street and turn left”, “continue down Whatever Avenue for 2 miles”, or “take the next right after the little red school house”. A declarative approach would be to simply declare the address or GPS coordinates of the cafe, and have them use an automated system capable of continuously nudging their position toward the destination from wherever they currently are.
The declarative approach has obvious advantages over the imperative approach. Firstly, the imperative approach is more fragile in that it depends on knowing the person’s precise location at the start. If they are not where you think they are when you start issuing commands, they will be rather confused. A recorded sequence of instructions (for example, a printed out sheet of navigational instructions) contains no provisions for any unexpected surprises that might cause your friend to have to alter their route. Unless your friend is a capable navigator already familiar with the area, they will be lost if everything doesn’t go according to plan. Even a missing street sign could cause them to end up on the other side of town, while you sit at the cafe by yourself.
The declarative approach clearly requires sophisticated technology, in this case a GPS navigation system. But, given that one does have access to such a system, the advantages are great indeed. Kubernetes offers comparable advantages in the domain of distributed computing systems operations. Rather than having to follow a series of specific and independently fragile procedural steps such as “download the binary from https://whatever.url”, “copy the file into /some/specific/directory”, etc., one simply provides Kubernetes with a manifest, a human-readable description of the desired end state of the system. The powerful technology Kubernetes encapsulates then guides the system to the correct state. Similar to an automated GPS navigator, Kubernetes can bring a system to a desired state from a wide range of starting positions, and can correct course along the way if things go wrong.
Declarative configuration has the further benefit that system states are easily reproducible. By writing down the simple address of the cafe, your friend can later remember where the two of you had lunch or even recommend the place to their other friends. Similarly, the manifest that one uses to declare a desired state of a system to Kubernetes can later serve as a record of what that state was, given that Kubernetes was able to successfully achieve it. Therefore, by saving these declarations in a version control system such as Git, one maintains the ability to return the system to a prior state by commanding Kubernetes to apply the old manifest. In contrast, even if one records the exact steps involved in imperative configuration, it can be extremely difficult to reverse them and return the system to a previous state. Most operations one can perform on a distributed computing system lack anything like a simple ‘undo’ function.
Another great advantage of Kubernetes is its ability to “self-heal” if the components of your system fail for any reason. Kubernetes does this particularly effectively by using the concept of a pod, which is a collection of containerized applications that need to be located together in a single compute environment in order to function (for example, if they need to share a file system). If your manifest says there should be 147 replicas of a pod, hence 147 independently well-functioning instances of application, and someone in a data center accidentally unplugs the machine running 20 of them, Kubernetes will quickly detect this disparity and bring the system back to the state described in the manifest by allocating 20 replicas to other available machines. Another great advantage of pods is that they allow for smart, efficient scaling of distributed applications by separating the vertical scaling factor (the compute resources available) for specific containers, from the horizontal scaling factor (the number of instances) for a collection of colocated containers.
In summary, Kubernetes allows a small team of engineers to easily do what otherwise might be difficult for a much larger team: to precisely guide a distributed system into an understandable and reproducible state, and to reliably maintain that state until they intend to change it.
Getting a Handle on Kubernetes: setting up our tools
In this section we will prepare the infrastructure and tools to use Kubernetes. We will use Google cloud, the cloud computing environment hosted by Google. It is an easy way to start, although it is also powerful enough to handle enterprise scale industrial computing applications.
Set up your Google Cloud
- Create a Google Cloud account for yourself, if you do not already have one, at https://cloud.google.com. Google offers a free trial account with plenty of credits. You will need to enter a credit card to verify your identity, but you will not be charged.
- Create a project in Google Cloud at https://console.cloud.google.com/projectcreate. Record the project name or keep the browser window open so you can find it easily.
- Enable Kubernetes Engine API. Find it by searching at https://console.cloud.google.com/apis/ and then clicking ‘enable’.
- Install ‘gcloud’, the Google Cloud command line interface (CLI) tool https://cloud.google.com/sdk/docs/downloads-interactive.
- Open iterm (on a mac) or your terminal of choice, and type
gcloud version
to ensure that gcloud is correctly installed. -
Configure the gcloud CLI to point to your new project by running
gcloud config set project YOUR_PROJECT_NAME
Note: You can always check which project gcloud is targetting by running
gcloud config get-value project
.
Set up kubectl
Kubectl is the command line interface (CLI) for Kubernetes. On a mac computer, the easiest way to manage kubectl is using homebrew.
- In iterm or your terminal of choice, run
brew update
to make sure your local homebrew is up to date and knows about the latest version of kubectl. - Run
kubectl
to check whether kubectl is installed.- If it is installed, you should see a summary of the manual page, including a list of top level commands. In this case, run
brew upgrade kubectl
to have homebrew bring your kubectl up to date. - If kubectl is not installed, your shell will tell you that the kubectl command cannot be found. In this case, run
brew install kubectl
. Now running thekubectl
command should give you a summary of the manual page.
- If it is installed, you should see a summary of the manual page, including a list of top level commands. In this case, run
Cloud operations with Kubernetes
Part 1: Create a cluster in the cloud
- (Optional) If you prefer to create your cluster in a specific zone, you can target the zone by running
gcloud config set compute/zone PREFERRED_ZONE
, for example, you could use ‘us-west1-a’ as the value ofPREFERRED_ZONE
. -
Create a cluster in your Google Cloud by running:
gcloud container clusters create YOUR_CLUSTER_NAME
The output should confirm that your cluster is up and running (the output below is for creation of a cluster called ‘noobcluster’ in a Google Cloud project called ‘kubes4noobs’). This can take a few minutes.
Creating cluster noobcluster in us-west1-a... Cluster is being health-checked (master is
healthy)...done.
Created [https://container.googleapis.com/v1/projects/kubes4noobs/zones/us-west1-a/clusters/noobcluster].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-west1-a/noobcluster?project=kubes4noobs
kubeconfig entry generated for noobcluster.
NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS
noobcluster us-west1-a 1.11.7-gke.12 35.197.16.123 n1-standard-1 1.11.7-gke.12 3 RUNNING
Part 2: Deploy a sample app
Now we will deploy a very simple app with a load balancer in front of a microservice that responds to http requests by saying “hello”.
We’ll do this by running the kubectl apply
command, pointing to a manifest that describes a deployment with the pod containing our app. The manifest will look like this:
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
spec:
selector:
matchLabels:
app: hello
tier: backend
track: stable
replicas: 7
template:
metadata:
labels:
app: hello
tier: backend
track: stable
spec:
containers:
- name: hello
image: "gcr.io/google-samples/hello-go-gke:1.0"
ports:
- name: http
containerPort: 80
The manifest includes metadata and labels that the kubectl CLI uses to find the app in order to execute commands. It also includes the template metadata labels that define the labels for the pod that the app will exist in in our deployment, as well as the selector matchLabels that Kubernetes will use to identify the pod in which to place the app. This is a bit complicated, but for now just be aware that these must match for Kubernetes to find the correct pod which will contain the app. If you are curious, try changing these labels so they don’t match and see what happens. Don’t worry, nothing terrible will happen.
The containers
section at the bottom defines a single container that will run your app. We give it a name and tell Kubernetes where to find the container image–in this case we will borrow it from the sample apps provided by Google on its public registry, gcr.io. We also tell it on which port to listen for http requests, in this case the standard default port 80.
Create the deployment by telling Kubernetes to apply the manifest at the given file path, with the command: kubectl apply -f PATH_TO_MANIFEST
. To fill in “PATH_TO_MANIFEST” with the correct path, you can either a) copy the manifest above into a file on your local file system, and use the path to that file on your local file system, or b) use the following path where the manifest is hosted by k8s.io as an example: https://k8s.io/examples/service/access/hello.yaml.
You should see confirmation that the deployment has been created:
deployment.apps/hello created
If you ask Kubernetes to list your deployments, you should see it listed:
:> kubectl get deployments
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
hello 7 7 7 7 1m
You can get detailed information about the deployment by asking Kubernetes to describe it, using the name we defined in the manifest, “hello”:
kubectl describe deployments hello
The manifest we used to create the deployment asks for seven replicas of the pod containing our application. This is more than we need for this simple example, so let’s scale it back by editing the manifest.
Run kubectl edit deployments hello
to edit the manifest in your default text editor. Under the spec
key, edit the value of the replicas
key to be 3, rather than seven. Save and close the file, and Kubernetes will apply your changes.
Alternately, if you have copied the manifest to a local file, you can edit the value of the replicas
key in this local file, and tell Kubernetes to redeploy by using same command again: kubectl apply -f PATH_TO_MANIFEST
.
If you run kubectl describe deployments hello
again, you should see that there are now only 3 replicas.
:> kubectl describe deployments hello | grep Replicas:
Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable
You can also see that the Events
section will show when the pod containing your app scaled to seven replicas as originally deployed, and then scaled down to three after you redeployed with the edited manifest:
:> kubectl describe deployments hello | grep -A4 Events
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 14m deployment-controller Scaled up replica set hello-7d7d777c6b to 7
Normal ScalingReplicaSet 4m36s deployment-controller Scaled down replica set hello-7d7d777c6b to 3
Part 3: Route traffic to the application
Currently, the application is not exposed to the internet. Do this, we will have Kubernetes create a load balancer with a public internet IP address to which we can make requests using a web browser or the curl
command. However, first, we need to define a Kubernetes ‘service’ object to make our app backend discoverable to the load balancer. We will do this by applying the following manifest:
kind: Service
apiVersion: v1
metadata:
name: hello
spec:
selector:
app: hello
tier: backend
ports:
- protocol: TCP
port: 80
targetPort: http
Note that the manifest uses the ‘app: hello’ and ‘tier: backend’ selectors that we defined in the manifest we used to deploy the app, in order to locate the app.
As before we will command Kubernetes to make the desired changes using kubernetes apply -f PATH_TO_MANIFEST
. Either a) copy the manifest above into a file on your local file system, and use the path to the manifest on your local file system, or b) use the following path where it is hosted by k8s.io as an example: https://k8s.io/examples/service/access/hello-service.yaml.
Terminal output should confirm that the service has been created.
Finally, we will deploy a simple nginx load balancer and create a corresponding service. We will do this in one swoop by applying the following manifest, which contains one section for the service and one for the deployment of the load balancer itself:
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
selector:
app: hello
tier: frontend
ports:
- protocol: "TCP"
port: 80
targetPort: 80
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
selector:
matchLabels:
app: hello
tier: frontend
track: stable
replicas: 1
template:
metadata:
labels:
app: hello
tier: frontend
track: stable
spec:
containers:
- name: nginx
image: "gcr.io/google-samples/hello-frontend:1.0"
lifecycle:
preStop:
exec:
command: ["/usr/sbin/nginx","-s","quit"]
Once more, command Kubernetes to make the desired changes using kubernetes apply -f PATH_TO_MANIFEST
. Either a) copy the manifest above into a file on your local file system, and use the path to the manifest on your local file system, or b) use the following path where it is hosted by k8s.io as an example: https://k8s.io/examples/service/access/frontend.yaml.
Terminal output should confirm that both the deployment and the service have been created.
Part 4: Test your system
To test the system you have created, make a request through the internet to the public IP address of the load balancer. The load balancer will then route the request to the app container in one of the replicas of your ‘hello’ pods.
To find this public IP address, run kubectl get services
, to display basic information about your running services.
You should see both your ‘frontend’ and ‘hello’ services listed, as will as a service called ‘kubernetes’, which is created automatically and used to send commands to Kubernetes from inside the cluster.
Optional: if you want to watch Kubernetes self-heal in a rather minor way, run kubectl delete service kubernetes
. This will indeed delete the kubernetes
service, as you can confirm by running kubectl get services
immediately (it will be gone). However, in a matter of seconds, Kubernetes will recreate this important service object.
Only the ‘frontend’ service will have an “external-ip” that can be reached through the internet. When you first check, it may be listed as “pending”. Simply take a break to stretch or dance around for a minute or so to get your blood pumping for the excitement to come, then check again.
Once kubectl get services
, or the more specific command kubectl get service frontend
, displays an external IP for the frontend service, we are ready to make a request to our application. In the terminal, type: curl http://YOUR_EXTERNAL_IP
, using the displayed external IP. You should see a simple yet friendly message in return:
{"message":"Hello"}
You can also confirm that the app is running by navigating to http://YOUR_EXTERNAL_IP
in your web browser of choice. Your browser should display the same simple ‘hello’ message.
Part 5: Clean up by destroying your cluster
When you are ready to clean everything up, tell Google Cloud to delete your cluster. If you don’t remember the name of your cluster, ask Google Cloud by running gcloud container clusters list
.
When you are ready, run gcloud container clusters delete YOUR_CLUSTER_NAME
. This will destroy the deployments and services and associated Kubernetes objects.
To confirm that everything has been deleted, you can return to the Google Cloud console at https://console.cloud.google.com/. The console may several minutes (perhaps up to half an hour) to update, but eventually it will show that you have no active resources listed under the ‘resources’ tab. If you click on the ‘Billing’ tab in the left hand navigation menu, you should see that you have plenty of credits left over from your free trial to experiment with Google Cloud and kubernetes. There are lots of good tutorials to explore at https://kubernetes.io/docs/tutorials/.