05 Architecture.v2
05 Architecture.v2
Table of contents
1. Kubernetes architecture 4
2. Bootstrapping a cluster 7
Bootstrapping the primary node 9
Installing a network plugin 14
Connecting worker Nodes 17
Installing the Ingress controller 21
Exposing the kubeconfig 23
Recap 26
3. Deploying an application 27
Hello Pod 28
Verifying the deployment 32
4. Exploring etcd 34
Connecting to etcd 35
Reading, writing and watching keys 37
Summary 40
3
7. Testing resiliency 59
Making the nodes unavailable 60
Making the control plane unavailable 64
Scheduling workloads with no worker node 67
Summary 71
Chapter 1
Kubernetes
architecture
5
bash
$ local command
PowerShell — □ 𝖷
6
bash@node1
Bootstrapping
a cluster
8
bash
😄
$ minikube start --no-kubernetes --container-runtime=containerd --driver=docker --nodes 3
✨
minikube v1.29.0
👍
🚜
Using the docker driver based on user configuration
Starting minikube without Kubernetes in cluster minikube
🔥
Pulling base image ...
🏄
Preparing containerd 1.6.15
Done! minikube is ready without Kubernetes!
bash
It's worth noting that those nodes are not vanilla Ubuntu
images.
Containerd (the container runtime) is preinstalled.
9
bash
bash@minikube
bash
bash@minikube-m02
bash
bash@minikube-m03
Those scripts:
Downloads kubeadm , kubectl and the kubelet .
Installs the shared certificates necessary to trust other
entities.
Creates the Systemd unit necessary to launch the
kubelet .
Creates the kubeadm config necessary to bootstrap the
cluster.
Once completed, you can finally switch back to the terminal
session for the primary node and bootstrap the cluster with:
bash@minikube
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Then you can join any number of worker nodes by running the following on each as root:
kubeadm
bash@minikube
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
bash@minikube
$ kubectl cluster-info
Kubernetes control plane is running at https://<master-node-ip>:8443
CoreDNS is running at https://<master-node-ip>:8443/api/v1/namespaces/kube…
bash@minikube
bash@minikube
In other words, Flannel can route the traffic from any Pod to
any Pod — just what we need.
Let's install it in the cluster.
The master.sh script you executed earlier also created a
flannel.yaml in the local directory.
bash@minikube
$ ls -1
config.yaml
flannel.yaml
master.sh
traefik.yaml
bash@minikube
created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds created
bash@minikube
Once all the Pods are "Ready", the control plane Node
should transition to a "Ready" state too:
bash@minikube
Excellent!
The control plane is successfully configured to run
Kubernetes.
This time, CoreDNS should be running as well:
bash@minikube
17
bash@minikube
bash
Then, you should SSH into the first worker node with:
bash
bash@minikube-m02
bash@minikube-m02
bash
bash
bash@minikube-m03
bash@minikube-m03
bash@minikube
$ ls -1
config.yaml
flannel.yaml
master.sh
traefik.yaml
bash@minikube
bash
bash
From this container, you can reach any node of the cluster
— let's retrieve and repeat the experiment:
bash@netshoot
bash
bash
$ docker ps
CONTAINER ID IMAGE PORTS NAMES
5717b8d142ac gcr.io/k8s-minikube/kicbase:v0.0.37 127.0.0.1:53517->8443/tcp minikube-m03
d5e1dbe9611c gcr.io/k8s-minikube/kicbase:v0.0.37 127.0.0.1:53503->8443/tcp minikube-m02
648efe712022 gcr.io/k8s-minikube/kicbase:v0.0.37 127.0.0.1:53486->8443/tcp minikube
Find the port that forwards to 8443 for the control plane (in
the above example is 53486 ).
And finally, replace <master-node-ip>:8443 in your
kubeconfig:
kubeconfig
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CR…
server: https://127.0.0.1:<insert-port-here>
name: mk
contexts:
bash
$ export KUBECONFIG="${PWD}/kubeconfig"
bash
$ export KUBECONFIG="${PWD}/kubeconfig"
PowerShell — □ 𝖷
PS> $Env:KUBECONFIG="${PWD}\kubeconfig"
You can verify that you are connected to the cluster with:
bash
$ kubectl cluster-info
Kubernetes control plane is running at https://127.0.0.1:53486
CoreDNS is running at https://127.0.0.1:53486/api/v1/namespaces/kube…
Congratulations!
You just configured a fully functional Kubernetes cluster!
Recap
You created three virtual machines using minikube.
You bootstrapped the Kubernetes control plane node
using kubeadm .
You installed Flannel as the network plugin.
You installed an Ingress controller.
You configured kubectl to work from outside the
control plane.
The cluster is fully functional, and it's time to deploy an
application.
Chapter 3
Deploying an
application
28
Hello Pod
You will create a Pod that has a single container with the
ghcr.io/learnk8s/podinfo:2023.03 image.
If you wish to test the application locally before you deploy
it in the cluster, you can do so with:
bash
bash
$ curl http://localhost:8080
{
"hostname": "podinfo-787d4945fb-fs9cc",
"version": "6.3.4",
"revision": "1abc44f0d8dd6cd9df76090ea4ad694b70e03ee4",
"color": "#34577c",
"logo": "https://raw.githubusercontent.com/stefanprodan/podinfo/gh-pages/cuddle_clap.gif",
"message": "greetings from podinfo v6.2.2",
"goos": "linux",
"goarch": "arm64",
29
"runtime": "go1.20.1",
"num_goroutine": "7",
"num_cpu": "4"
}%
Creating a Deployment
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
30
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.23
ports:
- containerPort: 80
Creating a Service
service.yaml
apiVersion: v1
kind: Service
31
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress
spec:
rules:
- http:
32
paths:
- path: /testpath
pathType: Prefix
backend:
service:
name: test
port:
number: 80
bash
Exploring
etcd
35
bash
$ minikube ssh
ssh@minikube
$ etcdctl version
etcdctl version: 3.5.7
API version: 3.5
Connecting to etcd
kubeadm deploys etcd with mutual TLS authentication, so
you must provide TLS certificates and keys when you issue
36
requests.
This is a bit tedious, but a Bash alias can help speed things
along:
ssh@minikube
ssh@minikube
As you can see, the cluster has a single etcd node running.
How does the Kubernetes API store data in etcd?
Let's list all the data:
ssh@minikube
37
ssh@minikube
ssh@minikube
Let's investigate:
ssh@minikube
v1Pod�
�
kube-scheduler-master�
kube-system"*$f8e4441d-fb03-4c98-b48b-61a42643763a2��نZ
ssh@minikube
ssh@minikube
output.json
{
"Header": {
"cluster_id": 18038207397139143000,
"member_id": 12593026477526643000,
"revision": 935,
"raft_term": 2
},
"Events": [
{
"kv": {
"key": "L3JlZ2lzdHJ5L3BvZHMvZGVmYXVsdC9uZ2lueA==",
"create_revision": 935,
"mod_revision": 935,
"version": 1,
"value": "azh...ACIA"
40
}
}
],
"CompactRevision": 0,
"Canceled": false,
"Created": false
}
ssh@minikube
Summary
Peeking under the covers of Kubernetes is always interesting,
41
Exploring the
API server
43
Creating a proxy
The API server comprises several components, including
authentication, authorization, admission controllers, etc.
To avoid authenticating and authorizing every single
request, you can create a proxy with:
bash
bash
$ curl localhost:8888
{
"paths": [
"/api",
"/api/v1",
"/apis",
"/apis/",
"/apis/admissionregistration.k8s.io",
"/apis/admissionregistration.k8s.io/v1",
// more APIs ...
]
}
Kubernetes API
Every request to the API server follows a RESTful API
pattern:
/api/ (the core APIs) or
/apis/ (APIs grouped by API group).
bash
$ curl http://localhost:8888/api/v1/pods
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "5155"
},
"items": [
{
"metadata": {
"name": "hello-world-56686f7466-db9g5",
# truncated output
bash
$ curl http://localhost:8888/api/v1/namespaces/default/pods/hello-world-56686f7466-jbwvt
{
"kind": "Pod",
"apiVersion": "v1",
"metadata": {
"name": "hello-world-56686f7466-jbwvt",
"generateName": "hello-world-56686f7466-",
"namespace": "default",
"uid": "6f0c094a-1d4a-4e3a-9050-001104e61f5d",
# truncated output
/api/v1/namespaces/[namespace-name]/[resource-type-name]/[resource-name]
bash
$ curl http://localhost:8888/api/v1/nodes
{
"kind": "NodeList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "5765"
},
# truncated output
bash
$ curl http://localhost:8888/api/v1/nodes/minikube
{
"kind": "Node",
"apiVersion": "v1",
"metadata": {
"name": "minikube",
"uid": "b6b5aa96-3bf6-43a6-aa69-183e16a4397b",
# truncated output
/api/v1/[resource-type-name]/[resource-name]
47
OpenAPI spec
It's improbable that you will have to issue HTTP requests
directly to the API server.
However, there are times when it's convenient to do so.
For example:
You might want to create your scripts to orchestrate
Kubernetes.
You might want to build a Kubernetes operator.
You enjoy the freedom of issuing curl requests and
mangling the responses with jq .
In all of those cases, you should not memorize the API
endpoints.
Instead, you should learn how to navigate the OpenAPI
spec for the Kubernetes API.
The OpenAPI lists all the resources and endpoints available
in the cluster.
If you open the page and search for "Create Connect Proxy",
you might notice that there's a section on how to create a
proxy and the HTTP path to issue the command.
Let's see if you can complete a quick challenge.
Can you find the API endpoint to display the logs for one of
the running Pods?
48
bash
$ killall kubectl
Summary
In this section, you learned how to connect to the
Kubernetes API and issue requests.
You also learned:
The structure of the HTTP requests in Kubernetes.
How namespaced resources are handled in the API
server.
How to issue commands to the API server with curl
and kubectl proxy .
How to read the OpenAPI spec for all the APIs available
in the cluster.
Chapter 6
Exploring the
kubelet
50
Exploring containerd
Containerd can run every container image that you build
with Docker because there is a standard for building and
running containers (the Open Container Initiative).
51
bash
$ minikube ssh
bash@minikube
bash@minikube
$ sudo crictl ps
CONTAINER IMAGE STATE NAME POD ID
58cfd54d948ef b6cb6292a4954 Running traefik 0c995ff1892c0
38efab4272865 b19406328e70d Running coredns 16eec5b244e46
a1e73f7fca20a b19406328e70d Running coredns 8e5f4bbe36d72
714baac1290ad 02de1966ebfd3 Running kube-flannel a13cddbee7230
c331fef9d40d8 3bb41bb94b1dd Running kube-proxy cf1e9267a3b8b
20b4870309227 c07d98db81fde Running kube-controller-manager c52f9c34ee021
298d6af09b5d1 21fe7c4e54aea Running kube-apiserver e0e13cecb0c8e
f4b549dfd51a7 93331be9505c4 Running kube-scheduler aec2ca6d4025f
6f67c6c43096d ef24580282403 Running etcd 13ff9aec1e011
bash@minikube
Simulating failures
The kubelet always keeps the local state synced with the
control plane.
Let's check what happens when you forcefully remove a Pod
from the running node.
Connect to the control plane node with:
bash
$ minikube ssh
bash@minikube
bash
bash@minikube-m02
$ sudo crictl ps
CONTAINER IMAGE STATE NAME POD ID
331bc172b5342 da23b04751258 Running app 10301445cf6cd
277b37dc8a3a5 b6cb6292a4954 Running traefik 29c2cbf8e5478
8747eb5c41638 02de1966ebfd3 Running kube-flannel 03b7501b43535
60c2595281d2d 3bb41bb94b1dd Running kube-proxy c4685de0da2e4
bash@minikube-m02
bash@minikube
node.
Instead of crictl to list the container, let's use ctr — a
command-line client shipped as part of the containerd
project.
While crictl provides a consistent interface to access all
container runtimes, ctr is specifically designed to interact
with containerd.
Let's list all containers:
bash@minikube-m02
bash@minikube-m02
bash
bash
Name: hello-world-5d6cfd9db8-jjrn5
Namespace: default
Priority: 0
# truncated output
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SandboxChanged 29s kubelet Pod sandbox changed, it will be killed and re-created.
Normal Killing 29s kubelet Stopping container app
Normal Started 28s kubelet Started container app
There was an error with the Pod sandbox (this is when you
forcefully deleted the Pod).
You can inspect the kubelet logs to investigate the issue
further:
bash@minikube-m02
The kubelet keeps the local state of the node up to date, but
it can't find the container.
So it removes all the containers in the Pod and recreates
them.
Summary
In this chapter, you learned:
The kubelet is an agent that lives in every cluster node.
58
Testing
resiliency
60
bash
bash
✋
$ minikube node stop minikube-m03
🛑
Stopping node "minikube-m03" ...
Successfully stopped node minikube-m03
Please note the current time and set the alarm for 5 minutes
— (you will understand why soon).
Observe the node almost immediately transitioning to a
"Not Ready" state:
bash
bash@netshoot
Hello, hello-world-5d6cfd9db8-nn256
bash
bash
minikube-m03
pod/hello-world-5d6cfd9db8-rjd54 1/1 Running minikube-m02
Why 5 minutes?
The kubelet reports the status of the Pods at regular
intervals.
If the primary node doesn't receive updates from the kubelet
during those 5 minutes, it assumes that the Pods are gone.
If there's an update, it just resets the timeout.
This is convenient if there are network glitches and the
kubelet cannot update the control plane on time.
As long as there's an update within 5 minutes, the cluster
works as usual.
Are you happy to wait for 5 minutes before detecting lost
Pods?
It depends.
This is not ideal if your applications are optimised for
latency and throughput.
If your cluster runs mostly batch jobs, 5 minutes is fine.
You can tweak the timeout with the --pod-eviction-
timeout flag in the kube-controller-manager component.
64
bash
bash@netshoot
bash
bash
66
kubeconfig
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CR…
server: https://127.0.0.1:<insert-new-port-here>
name: mk
contexts:
bash
✋
$ minikube node stop minikube-m02
🛑
Stopping node "minikube-m02" ...
Successfully stopped node minikube-m02
bash
INTERNAL-IP
node/minikube Ready control-plane 192.168.105.18
node/minikube-m02 NotReady <none> 192.168.105.19
node/minikube-m03 NotReady <none> 192.168.105.20
bash
bash
68
bash
bash@netshoot
cluster?
Let's pay attention to the Pod distribution in the cluster:
bash
bash
Summary
There was a lot to take away from this chapter, so let's do a
recap of what you learned:
You can take down a Node in Kubernetes. The
application still works as expected if you have more than
a single Pod.
You can take down the control plane. The cluster is still
functioning, albeit there could be some disruptions to
the API server.
The kubelet reports to the control plane:
1. The state of the Nodes at regular intervals.
2. The node heartbeat.
The controller manager reschedules unavailable pods
only after 5 minutes. You can change that with the --
pod-eviction-timeout flag.
The controller manager detects a node is unavailable in
less than 10 seconds.
72