This is a hands-on introduction to Kubernetes. Browse the examples:
Kubernetes pods by example
A pod is a collection of containers sharing a network and mount namespace and is the basic unit of deployment in Kubernetes. All containers in a pod are scheduled on the same node.
To launch a pod using the container image mhausenblas/simpleservice:0.5.0
and exposing a HTTP API on port 9876
, execute:
$ kubectl run sise --image=mhausenblas/simpleservice:0.5.0 --port=9876
We can now see that the pod is running:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sise-3210265840-k705b 1/1 Running 0 1m
$ kubectl describe pod sise-3210265840-k705b | grep IP:
IP: 172.17.0.3
From within the cluster (e.g. via minishift ssh
) this pod is accessible via the pod IP 172.17.0.3
, which we’ve learned from the kubectl describe
command above:
[cluster] $ curl 172.17.0.3:9876/info
{"host": "172.17.0.3:9876", "version": "0.5.0", "from": "172.17.0.1"}
Note that kubectl run
creates a deployment, so in order to get rid of the pod you have to execute kubectl delete deployment sise
.
Using configuration file
You can also create a pod from a configuration file. In this case the pod is running the already known simpleservice
image from above along with a generic CentOS
container:
$ kubectl apply -f https://raw.githubusercontent.com/openshift-evangelists/kbe/master/specs/pods/pod.yaml
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
twocontainers 2/2 Running 0 7s
Now we can exec into the CentOS
container and access the simpleservice
on localhost:
$ kubectl exec twocontainers -c shell -i -t -- bash
[root@twocontainers /]# curl -s localhost:9876/info
{"host": "localhost:9876", "version": "0.5.0", "from": "127.0.0.1"}
Specify the resources
field in the pod to influence how much CPU and/or RAM a container in a pod can use (here: 64MB
of RAM and 0.5
CPUs):
$ kubectl create -f https://raw.githubusercontent.com/openshift-evangelists/kbe/master/specs/pods/constraint-pod.yaml
$ kubectl describe pod constraintpod
...
Containers:
sise:
...
Limits:
cpu: 500m
memory: 64Mi
Requests:
cpu: 500m
memory: 64Mi
...
Learn more about resource constraints in Kubernetes via the docs here and here.
To remove all the pods created, just run:
$ kubectl delete pod twocontainers
$ kubectl delete pod constraintpod
To sum up, launching one or more containers (together) in Kubernetes is simple, however doing it directly as shown above comes with a serious limitation: you have to manually take care of keeping them running in case of a failure. A better way to supervise pods is to use deployments, giving you much more control over the life cycle, including rolling out a new version.
Kubernetes labels by example
Labels are the mechanism you use to organize Kubernetes objects. A label is a key-value pair with certain restrictions concerning length and allowed values but without any pre-defined meaning. So you’re free to choose labels as you see fit, for example, to express environments such as ‘this pod is running in production’ or ownership, like ‘department X owns that pod’.
Let’s create a pod that initially has one label (env=development
):
$ kubectl apply -f https://raw.githubusercontent.com/openshift-evangelists/kbe/master/specs/labels/pod.yaml
$ kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
labelex 1/1 Running 0 10m env=development
In above get pods
command note the --show-labels
option that output the labels of an object in an additional column.
You can add a label to the pod as:
$ kubectl label pods labelex owner=michael
$ kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
labelex 1/1 Running 0 16m env=development,owner=michael
To use a label for filtering, for example to list only pods that have an owner
that equals michael
, use the --selector
option:
$ kubectl get pods --selector owner=michael
NAME READY STATUS RESTARTS AGE
labelex 1/1 Running 0 27m
The --selector
option can be abbreviated to -l
, so to select pods that are labelled with env=development
, do:
$ kubectl get pods -l env=development
NAME READY STATUS RESTARTS AGE
labelex 1/1 Running 0 27m
Oftentimes, Kubernetes objects also support set-based selectors. Let’s launch another pod that has two labels (env=production
and owner=michael
):
$ kubectl apply -f https://raw.githubusercontent.com/openshift-evangelists/kbe/master/specs/labels/anotherpod.yaml
Now, let’s list all pods that are either labelled with env=development
or withenv=production
:
$ kubectl get pods -l 'env in (production, development)'
NAME READY STATUS RESTARTS AGE
labelex 1/1 Running 0 43m
labelexother 1/1 Running 0 3m
Other verbs also support label selection, for example, you could remove both of these pods with:
$ kubectl delete pods -l 'env in (production, development)'
Beware that this will destroy any pods with those labels.
You can also delete them directly, via their names, with:
$ kubectl delete pods labelex
$ kubectl delete pods labelexother
Note that labels are not restricted to pods. In fact you can apply them to all sorts of objects, such as nodes or services.
Kubernetes deployments by example
A deployment is a supervisor for pods, giving you fine-grained control over how and when a new pod version is rolled out as well as rolled back to a previous state.
Let’s create a deployment called sise-deploy
that supervises two replicas of a pod as well as a replica set:
$ kubectl apply -f https://raw.githubusercontent.com/openshift-evangelists/kbe/master/specs/deployments/d09.yaml
You can have a look at the deployment, as well as the the replica set and the pods the deployment looks after like so:
$ kubectl get deploy
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
sise-deploy 2 2 2 2 10s
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
sise-deploy-3513442901 2 2 2 19s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sise-deploy-3513442901-cndsx 1/1 Running 0 25s
sise-deploy-3513442901-sn74v 1/1 Running 0 25s
Note the naming of the pods and replica set, derived from the deployment name.
At this point in time the sise
containers running in the pods are configured to return the version 0.9
. Let’s verify that from within the cluster (using kubectl describe
first to get the IP of one of the pods):
[cluster] $ curl 172.17.0.3:9876/info
{"host": "172.17.0.3:9876", "version": "0.9", "from": "172.17.0.1"}
Let’s now see what happens if we change that version to 1.0
in an updated deployment:
$ kubectl apply -f https://raw.githubusercontent.com/openshift-evangelists/kbe/master/specs/deployments/d10.yaml
deployment "sise-deploy" configured
Note that you could have used kubectl edit deploy/sise-deploy
alternatively to achieve the same by manually editing the deployment.
What we now see is the rollout of two new pods with the updated version 1.0
as well as the two old pods with version 0.9
being terminated:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sise-deploy-2958877261-nfv28 1/1 Running 0 25s
sise-deploy-2958877261-w024b 1/1 Running 0 25s
sise-deploy-3513442901-cndsx 1/1 Terminating 0 16m
sise-deploy-3513442901-sn74v 1/1 Terminating 0 16m
Also, a new replica set has been created by the deployment:
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
sise-deploy-2958877261 2 2 2 4s
sise-deploy-3513442901 0 0 0 24m
Note that during the deployment you can check the progress using kubectl rollout status deploy/sise-deploy
.
To verify that if the new 1.0
version is really available, we execute from within the cluster (again using kubectl describe
get the IP of one of the pods):
[cluster] $ curl 172.17.0.5:9876/info
{"host": "172.17.0.5:9876", "version": "1.0", "from": "172.17.0.1"}
A history of all deployments is available via:
$ kubectl rollout history deploy/sise-deploy
deployments "sise-deploy"
REVISION CHANGE-CAUSE
1 <none>
2 <none>
If there are problems in the deployment Kubernetes will automatically roll back to the previous version, however you can also explicitly roll back to a specific revision, as in our case to revision 1 (the original pod version):
$ kubectl rollout undo deploy/sise-deploy --to-revision=1
deployment "sise-deploy" rolled back
$ kubectl rollout history deploy/sise-deploy
deployments "sise-deploy"
REVISION CHANGE-CAUSE
2 <none>
3 <none>
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sise-deploy-3513442901-ng8fz 1/1 Running 0 1m
sise-deploy-3513442901-s8q4s 1/1 Running 0 1m
At this point in time we’re back at where we started, with two new pods serving again version 0.9
.
Finally, to clean up, we remove the deployment and with it the replica sets and pods it supervises:
$ kubectl delete deploy sise-deploy
deployment "sise-deploy" deleted
See also the docs for more options on deployments and when they are triggered.
Kubernetes services by example
A service is an abstraction for pods, providing a stable, so called virtual IP (VIP) address. While pods may come and go and with it their IP addresses, a service allows clients to reliably connect to the containers running in the pod using the VIP. The virtual
in VIP means it is not an actual IP address connected to a network interface, but its purpose is purely to forward traffic to one or more pods. Keeping the mapping between the VIP and the pods up-to-date is the job of kube-proxy, a process that runs on every node, which queries the API server to learn about new services in the cluster.
Let’s create a pod supervised by an RC and a service along with it:
$ kubectl apply -f https://raw.githubusercontent.com/openshift-evangelists/kbe/master/specs/services/rc.yaml
$ kubectl apply -f https://raw.githubusercontent.com/openshift-evangelists/kbe/master/specs/services/svc.yaml
Now we have the supervised pod running:
$ kubectl get pods -l app=sise
NAME READY STATUS RESTARTS AGE
rcsise-6nq3k 1/1 Running 0 57s
$ kubectl describe pod rcsise-6nq3k
Name: rcsise-6nq3k
Namespace: default
Security Policy: restricted
Node: localhost/192.168.99.100
Start Time: Tue, 25 Apr 2017 14:47:45 +0100
Labels: app=sise
Status: Running
IP: 172.17.0.3
Controllers: ReplicationController/rcsise
Containers:
...
You can, from within the cluster, access the pod directly via its assigned IP 172.17.0.3
:
[cluster] $ curl 172.17.0.3:9876/info
{"host": "172.17.0.3:9876", "version": "0.5.0", "from": "172.17.0.1"}
This is however, as mentioned above, not advisable since the IPs assigned to pods may change. Hence, enter the simpleservice
we’ve created:
$ kubectl get svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
simpleservice 172.30.228.255 <none> 80/TCP 5m
$ kubectl describe svc simpleservice
Name: simpleservice
Namespace: default
Labels: <none>
Selector: app=sise
Type: ClusterIP
IP: 172.30.228.255
Port: <unset> 80/TCP
Endpoints: 172.17.0.3:9876
Session Affinity: None
No events.
The service keeps track of the pods it forwards traffic to through the label, in our case app=sise
.
From within the cluster we can now access simpleservice
like so:
[cluster] $ curl 172.30.228.255:80/info
{"host": "172.30.228.255", "version": "0.5.0", "from": "10.0.2.15"}
What makes the VIP 172.30.228.255
forward the traffic to the pod? The answer is: IPtables, which is essentially a long list of rules that tells the Linux kernel what to do with a certain IP package.
Looking at the rules that concern our service (executed on a cluster node) yields:
[cluster] $ sudo iptables-save | grep simpleservice
-A KUBE-SEP-4SQFZS32ZVMTQEZV -s 172.17.0.3/32 -m comment --comment "default/simpleservice:" -j KUBE-MARK-MASQ
-A KUBE-SEP-4SQFZS32ZVMTQEZV -p tcp -m comment --comment "default/simpleservice:" -m tcp -j DNAT --to-destination 172.17.0.3:9876
-A KUBE-SERVICES -d 172.30.228.255/32 -p tcp -m comment --comment "default/simpleservice: cluster IP" -m tcp --dport 80 -j KUBE-SVC-EZC6WLOVQADP4IAW
-A KUBE-SVC-EZC6WLOVQADP4IAW -m comment --comment "default/simpleservice:" -j KUBE-SEP-4SQFZS32ZVMTQEZV
Above you can see the four rules that kube-proxy
has thankfully added to the routing table, essentially stating that TCP traffic to 172.30.228.255:80
should be forwarded to 172.17.0.3:9876
, which is our pod.
Let’s now add a second pod by scaling up the RC supervising it:
$ kubectl scale --replicas=2 rc/rcsise
replicationcontroller "rcsise" scaled
$ kubectl get pods -l app=sise
NAME READY STATUS RESTARTS AGE
rcsise-6nq3k 1/1 Running 0 15m
rcsise-nv8zm 1/1 Running 0 5s
When we now check the relevant parts of the routing table again we notice the addition of a bunch of IPtables rules:
[cluster] $ sudo iptables-save | grep simpleservice
-A KUBE-SEP-4SQFZS32ZVMTQEZV -s 172.17.0.3/32 -m comment --comment "default/simpleservice:" -j KUBE-MARK-MASQ
-A KUBE-SEP-4SQFZS32ZVMTQEZV -p tcp -m comment --comment "default/simpleservice:" -m tcp -j DNAT --to-destination 172.17.0.3:9876
-A KUBE-SEP-PXYYII6AHMUWKLYX -s 172.17.0.4/32 -m comment --comment "default/simpleservice:" -j KUBE-MARK-MASQ
-A KUBE-SEP-PXYYII6AHMUWKLYX -p tcp -m comment --comment "default/simpleservice:" -m tcp -j DNAT --to-destination 172.17.0.4:9876
-A KUBE-SERVICES -d 172.30.228.255/32 -p tcp -m comment --comment "default/simpleservice: cluster IP" -m tcp --dport 80 -j KUBE-SVC-EZC6WLOVQADP4IAW
-A KUBE-SVC-EZC6WLOVQADP4IAW -m comment --comment "default/simpleservice:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-4SQFZS32ZVMTQEZV
-A KUBE-SVC-EZC6WLOVQADP4IAW -m comment --comment "default/simpleservice:" -j KUBE-SEP-PXYYII6AHMUWKLYX
In above routing table listing we see rules for the newly created pod serving at172.17.0.4:9876
as well as an additional rule:
-A KUBE-SVC-EZC6WLOVQADP4IAW -m comment --comment "default/simpleservice:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-4SQFZS32ZVMTQEZV
This causes the traffic to the service being equally split between our two pods by invoking the statistics
module of IPtables.
You can remove all the resources created by doing:
$ kubectl delete svc simpleservice
$ kubectl delete rc rcsise