- Kubernetes Workshop
Kubernetes is an open source orchestrator for deploying and managing containerized applications and workloads.
- the resources of many servers nodes are federated into a resource pool
- workloads are dynamically scheduled depending on available resources and defined constraints
- Kubernetes provides several other vital mechanisms needed to make applications running on different nodes play together transparently
- ease of use, simple zero downtime deployments
- immutability, applications are replaced not mutated
- declarative configuration in yaml files
- health checks / self healing systems
- simple horizontal scaling
- kube-api-server receives commands from client apps (e.g.
kubectl
) - etcd keeps Control Pane Nodes in sync
- cloud-controller-manager communicates with Cloud APIs (e.g. AWS)
- kube-controller-manager coordinates nodes and application replication
- kube-scheduler distributes pods evenly on worker nodes
- kubelet turns object specifications into Kubernetes objects
- kube-proxy makes pods reachable via network
- coredns provides service discovery mechanisms via DNS
Kubernetes objects are persistent entities in the Kubernetes system. Kubernetes uses these entities to represent the state of your cluster. Objects are specified in yaml configuration files which can be turned for example into running applications, cloud resources, cluster behavior and much more. There are numerous Kubernetes objects. Some important examples are:
- Pods: A Pod (as in a pod of whales or pea pod) is a group of one or more containers (such as Docker containers), with shared storage/network,and a specification for how to run the containers
- ReplicaSet maintain a stable set of replica Pods running at any given time
- Deployments provide declarative updates for Pods and ReplicaSets such as rolling deployments or rollbacks
- Services are an abstract way to expose an application running on a set of Pods as a network service
In this section we're going to set up a Ghost blogging system consisting of multiple Kubernetes objects such as Deployments, Pods and Services.
The object specification in yaml are already prepared for you and are stripped down to the essentials so it will hopefully look less confusing.
The setup will consist of three components: the ghost blogging system itself, a MySQL database and an Nginx reverse proxy.
Before we start doing anything we need to make sure we are operating on the correct desired environment.
Kubectl contexts refer to a bunch of configurations and credentials which belong to a certain Kubernetes installation. Therefore switching to a certain context means you're switching the Kubernetes installation you are working on (presuming you have several installation configured).
What context are we operating? To look up the current context and the available context use these commands.
kubectl config current-context
kubectl config get-contexts
To set the desired context do
kubectl config use-context <mycontext>
Namespaces allow to logically separate Kubernetes objects into different concerns.
We create a namespace like this
kubectl create namespace ghost
Namespaces either can be be set temporarily with the -n
switch like
kubectl -n ghost ...
or permanently like so
kubectl config set-context --current --namespace ghost
To see the currently selected namespace do
kubectl config view --minify | grep namespace:
Kubernetes' units of execution are called Pods. A Pods is an object type that runs one or more containers (usually it should be just one).
Pods on their own are mostly used for debugging purpose or quick manual interventions. For reliably running applications you should use Deployments which will create the Pods for you from a template and will take care of keeping the pods running.
Have a look at the file resources/mysql-deployment.yaml
and read through it. Most of the statements will actually be even self-explanatory
if you recall what we're up to. Note for instance the env
section which provides
the pod the necessary configuration to make it run. Be aware that passwords and
other secrets are not supposed to be written in plain text. There is a Kube object
type called Secret which is not in scope of this tutorial though.
As the first step we want to run a mysql database. Do cd resources
and follow
along the commands. In order to send a yaml configuration to the Kubernetes API
we use the command kubectl apply -f <myconfig>.yaml
.
Apply the configuration for the mysql deployment
kubectl apply -f mysql-deployment.yaml
and see what it's doing
kubectl get pods --watch
Detailed information about the deployment can be obtained with the
kubectl describe ..
command. Try
kubectl describe deployment mysql
No worries if you do not understand every single detail yet. What you might find helpful for the moment is looking at the last few lines in the Events section which will display different kinds of errors if anything goes wrong. Right now you should see something like
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 7m38s deployment-controller Scaled up replica set mysql-58b5575c56 to 1
Looks good? Let's connect our database to the network.
In Kubernetes Pods are not supposed to be contacted directly. The whole topic of network connectivity is encapsulated into an object type called Service.
To be absolutely clear about it, every Pod which is supposed to be available via net work - may it privately / internally or publicly / externally - must have a Service object associated thus we need a service for all our Deployments.
Let's start off with MySQL.
kubectl apply -f mysql-service.yaml
and then check out it's internal
kubectl describe service mysql
Looks almost good except this Endpoints: <none>
. Apparently
it has no Pods listening to the service but how is it even supposed
to know to which Pods it should bind to? Here is the answer.
Every Service uses a label selector to find all the pods which belong
to a certain type of service. Have a look at the file
resources/mysql-service and
compare the selector
statement with the labels set in the file
resources/mysql-service.
After you corrected the error apply the configuration again.
kubectl apply -f mysql-service.yaml
kubectl describe service mysql
Now it should look similar to Endpoints: 172.17.0.4:3306
.
Let's move on to the ghost blog. Read through resources/ghost-deployment.yaml and apply it with
kubectl apply -f ghost-deployment.yaml
and see what it's doing
kubectl get pods --watch
Not great! As we see the Pod quits with an error until it stays in a CrashLoopBackOff state. We already learned a trick on obtaining more information. We describe the Pos selected by it's label.
kubectl describe pod -l=app=ghost
That doesn't look very helpful though. What we see here is telling us that Kubernetes basically has no Problems with our configuration which points us to a problem with the application configuration.
First thing we can do in such a case is to have a look inside the application logs.
kubectl logs -f deploy/ghost
It says something about a databse error. That is suspicious! Have a look at the two deployment configurations resources/ghost-deployment.yaml and resources/mysql-deployment.yaml and see if you can spot the error without immediately telling everybody so everyone gets his opportunity the get familiar with the syntax of the files.
Fix the error and apply the configuration again.
kubectl apply -f ghost-deployment.yaml
and see how we're doing this time.
kubectl get pods --watch
Next thing to do is to run Nginx.
kubectl apply -f nginx-deployment.yaml
kubectl get pods
That was almost to too easy. We have all our component running. Let's wire everything together.
Now you can set up the Services for Ghost and Nginx.
kubectl apply -f ghost-service.yaml
kubectl apply -f nginx-service.yaml
Maybe you should check if the services are configured correctly as we discussed above.
All Pods are now available via network but how do we actually connect to our blog? The way you would do it in a production grade setup is out of scope of this very tutorial but for now there is a neat trick which is also very handy for debugging purpose.
The command kubectl port-forward
let's you access all Pods via a
tunnel to your computer. This way are able to connect to our Nginx.
Try that
kubectl port-forward deploy/nginx 8080:80
and navigate to http://localhost:8080.
Warm greetings from Nginx! Nice, but not exactly what we wanted. What we see here is just a plain vanilla Nginx without any configuration. To get it configured you could either build a custom Nginx Docker image with your configuration included or you can just let Kubernetes inject the configuration file for you.
In Kubernetes files can be mounted like volumes after the file contents have been placed inside another Kubernetes object type called ConfigMap.
Read through the file resources/nginx-configmap.yaml and apply it
kubectl apply -f nginx-configmap.yaml
Bonus questions: Do you maybe have an idea on how to list ConfigMaps deployed in our Namespace?
Next we need to adjust the Nginx Deployment so the configuration is actually being mounted. Add the following lines to the end of resources/nginx-deployment.yaml.
volumeMounts:
- name: nginx
subPath: default.conf
mountPath: /etc/nginx/conf.d/default.conf
volumes:
- name: nginx
configMap:
name: nginx
and update the deployment
kubectl apply -f nginx-deployment.yaml
Finally have another look at http://localhost:8080.
If it's not working you might need to restart you kubectl port-forward
command. Also you might have to delete your browser cache in order to see
the updated content.
Let's set up the blog at http://localhost:8080/ghost. Fill in the form and enjoy the view of the admin panel for a moment and then prepare for the next 1000 concurrent users. The set up of the blog is necessary for the next steps to come.
If our blog gets overwhelmed by the high numbers of users we can user horizontal scaling to leverage our computation powers.
kubectl scale --replicas 4 deploy/ghost
kubectl get pods
Looks good? Scale down the Pods to 0
for our next experiment
kubectl scale --replicas 0 deploy/ghost
kubectl get pods
Let's make some trouble and delete the database Pod.
kubectl delete $(kubectl get pod -l=app=mysql -o name)
kubectl get pods --watch
Why is the Pod still there if we just deleted it? It's still there because the Deployment object which spawned the Pod for us is doing a good job - it replaces a Pod whenever it fails.
Now you learned to difference between scaling a Deployment and deleting a Pod. If you just fire up a pod yourself without a Deployment it will not be respawned automatically.
Let's bring back our Ghost Pod and see how our Blog is doing.
kubectl scale --replicas 1 deploy/ghost
kubectl get pods
Look again at http://localhost:8080/ghost
All is lost! When we killed the Database it sent it's data into oblivion. Containers are spawned from immutable images and all data saved inside a container is completely ephemeral. In order to make data persistent we have to mount a volume!
Add the following lines to resources/mysql-deployment.yaml
volumeMounts:
- mountPath: /var/lib/mysql
name: mysql
volumes:
- name: mysql
emptyDir: {}
That should look familiar to you from the Nginx configuration. The main difference is
the very last line which is the volume type. Before we declared a volume from a
ConfigMap and now it says emptyDir: {}
which means that we're just mounting a
folder from the host machine into the container. This is something you won't see very
often in production but it's good enough for our example.
Kubernetes supports all kinds of volumes from different persistency mechanisms and cloud providers. We can't go into detail here but we can get familiar with the basic syntax of mounting a volume into a Pod. A complete list of supported volume types can be found at https://kubernetes.io/docs/concepts/storage/volumes/.
Finally we have to apply our changes
kubectl apply -f mysql-deployment.yaml
kubectl get pods --watch
Guess what, a new version of Ghost was just released as we speak! Time to update.
Edit resources/ghost-deployment.yaml and change
the line - image: ghost
to - image: ghost:3.22
and redeploy. You should already
know what to do by now. if not review the section where we initially deployed Ghost.
Before we tear everything down feel free to do further experiments and check if this time the data is still there after you deleted the MySQL pod.
When you're done you can get rid of the whole setup with a single command.
kubectl delete -f .
And since our configuration is working now you can also easily bring everything back with a single command.
kubectl apply -f .
Stay tuned for more happy days on Kubernetes!
Part two of the introduction to Kubernetes can be found here.