03 Fundamentals.v1
03 Fundamentals.v1
Table of contents
1. Managing containers at scale 6
Managing containers allocations 10
Managing placements 12
Containers, networking and service discovery 14
5. Prerequisites 51
3
Windows 10 52
macOS 54
Ubuntu 57
Installing VirtualBox 57
Installing kubectl 58
minikube 58
6. First deployment 61
Starting the local cluster 62
Your first deployment 64
Inspecting YAML resources 68
Exposing the application 76
Recap 82
Ingress 145
Namespace 145
Version 3.0.0 146
Managing
containers at
scale
7
1 2
3 4
5 6
1 2
3 4
manually?
12
Managing placements
If you have a diverse set of servers, you want your containers
for data-intensive processing — such as machine learning
jobs — always to be scheduled on servers with dedicated
GPUs.
13
1 2
3 4
1 2
Running
containers at
scale
18
Datacentre as a computer
19
1 2
3 4
node.
The rest of the servers join the master node and
Fig. 3
form a cluster.
Fig. 4 The cluster behaves as if the three machines were
one.
1 2
3 4
1 2
3 4
You can have any servers joining the cluster. The size
Fig. 1
Fig. 3The cluster has the CPU and memory of all the
over servers combined.
Fig. 4The cluster has the CPU and memory of all the
over servers combined.
Fig. 5The cluster has the CPU and memory of all the
over servers combined.
Kubernetes as a scheduler
When you deploy an application, Kubernetes identifies the
memory requirements for your container and finds the best
place to deploy your application.
You don't decide which server the application is deployed.
Kubernetes has a scheduler that decides it for you.
The scheduler is optimised to pack containers efficiently:
initially, it spreads containers over the nodes to maximise
resiliency
25
1 2
3 4
5 6
compute resources.
When the cluster receives a request to deploy a
Fig. 2
container, it scans the worker Nodes and find the best fit.
27
Kubernetes as an API
When you wish to deploy a container in Kubernetes, you
end up calling a REST API.
Kubernetes ingests the request and, as soon as the scheduler
identifies the right place, a container is deployed.
If you wish to provision a load balancer to route traffic to
28
1 2
3 4
machine.
Fig. 3However, Amazon has its secret sauce for its virtual
machines. As an example, virtual machines that run in
Elastic Compute Cloud use Amazon Machine Images
(AMI) as base images.
Fig. 4If you wish to export the images to use in another
cloud provider such as Azure, you can't. Azure uses a
different standard. There's limited portability between
clouds even if they offer similar services.
1 2
3 4
5
32
Amazon Web Services for your tasks, you can swap the
underlying layer and still deploy applications as if
nothing happened.
Deploying
applications
on
Kubernetes
34
1 2
1 2
3 4
5 6
1 2
3 4
5 6
Fig. 1In the picture, there is one Ingress, one Service and
five Pods. There's another object that is not visible: the
Deployment.
41
Creating
resources in
Kubernetes
43
example.json
{
44
"contributors": [
{
"name": "John Doe",
"email": "[email protected]"
},
{
"name": "Ivy Lane",
"url": "https://example.com/ivylane"
}
],
"dependencies": {
"dep1": "^1.0.0",
"dep2": "3.40",
"dep3": "6.71"
}
}
example.yaml
contributors:
- name: John Doe
email: [email protected]
- name: Ivy Lane
url: https://example.com/ivylane
dependencies:
dep1: "^1.0.0"
dep2: '3.40'
dep3: '6.71'
YAML maps
Let's start by looking at YAML maps.
Maps let you associate key-value pairs.
For example, you might have a config file that starts like this:
46
pod.yaml
apiVersion: v1
kind: Pod
pod.json
{
"apiVersion": "v1",
"kind": "Pod"
}
pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: example-pod
labels:
app: app
In this case, you have a key, metadata , that has as its value a
map with two more keys, name and labels .
The labels key itself has a map as its value.
You can nest these as far as you want to.
The YAML processor knows how all of these pieces relate to
each other because you indented the lines.
47
pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: example-pod
labels:
app: app
pod.json
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "example-pod",
"labels": {
"app": "app"
}
}
}
YAML lists
YAML lists are a sequence of objects.
For example:
args.yaml
args:
- sleep
- "1000"
args.json
{
"args": ["sleep", "1000"]
}
pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: example-pod
labels:
app: web
spec:
containers:
- name: app
image: nginx
ports:
- containerPort: 88
Prerequisites
52
Windows 10
You can download minikube with:
PowerShell — □ 𝖷
PowerShell — □ 𝖷
53
PowerShell — □ 𝖷
PowerShell — □ 𝖷
The --v=7 flag increases the logging level, and you should
be able to spot the error in the terminal output.
The extra verbose logging should help you get to the issue.
In a particular case, minikube used to fail with:
PowerShell — □ 𝖷
Hyper-V\Start-VM minikube
~~~~~~~~~~~~~~~~~~~~~~~~~
CategoryInfo : FromStdErr: (:) [Start-VM], VirtualizationException
FullyQualifiedErrorId : OutOfMemory,Microsoft.HyperV.PowerShell.Commands.StartVM
PowerShell — □ 𝖷
macOS
You can download minikube with:
bash
bash
$ minikube start
👍 Starting control plane node minikube in cluster minikube
🐳 Preparing Kubernetes v1.23.0 ...
🔎 Verifying Kubernetes components...
🌟 Enabled addons: default-storageclass, storage-provisioner
🏄 Done! kubectl is now configured to use "minikube" by default
If for any reason minikube fails to start up, you can debug it
with:
bash
56
$ minikube delete
🔥 Deleting "minikube" ...
💀 Removed all traces of the "minikube" cluster.
The extra verbose logging should help you get to the issue.
It's time to test if your installation is successful.
Before you do so, in the command prompt type:
bash
bash
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"19", GitVersion:"v1.18.3"}
Server Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.3"}
Ubuntu
Before you install minikube, you should install VirtualBox.
Installing VirtualBox
bash
bash
$ virtualbox
58
Installing kubectl
bash
$ curl -s \
https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
OK
bash
$ sudo apt-get -qq update && sudo apt-get -qqy install kubectl
$ Unpacking kubectl ...
Setting up kubectl ...
minikube
bash
bash
bash
If for any reason minikube fails to start up, you can debug it
with:
PowerShell — □ 𝖷
PowerShell — □ 𝖷
The --v=7 flag increases the logging level, and you should
be able to spot the error in the terminal output.
It's time to test if your installation was successful. In the
command prompt type:
bash
Great!
Chapter 6
First
deployment
62
PowerShell — □ 𝖷
63
bash
$ kubectl cluster-info
Kubernetes master is running at https://192.168.64.20:8443
KubeDNS is running at <long url>
bash
$ minikube delete
🔥 Deleting "minikube" ...
💀 Removed all traces of the "minikube" cluster.
to the Kubernetes API, it's worth checking that you are not
using an older version of kubectl.
bash
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"19", GitVersion:"v1.18.3"}
Server Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.3"}
bash
66
PowerShell — □ 𝖷
bash
Great!
But how can you reach that Pod and visit it in a browser?
You should expose it.
You can expose the podinfo Pod with a Service:
bash
bash
bash
bash
However:
bash
name: podinfo
namespace: default
# truncated output
spec:
containers:
- image: stefanprodan/podinfo:6.0.3
imagePullPolicy: Always
name: podinfo
ports:
- containerPort: 9898
protocol: TCP
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: default-token-lvsst
readOnly: true
# truncated output
status:
# truncated output
With time, you will learn all of those, but for now, let's focus
on the basics.
The long output can be shortened to this:
pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: podinfo
spec:
containers:
- name: podinfo
image: stefanprodan/podinfo:6.0.3
ports:
- containerPort: 9898
The name of the Pod, the image and the container port are
the same values that you provided to kubectl run .
The command generated the above YAML and submitted
to the cluster for you.
As you can imagine, kubectl expose works in a similar
way.
As you type the command, kubectl generates a Service.
Let's explore the YAML stored in the cluster with:
bash
run: podinfo
name: podinfo
namespace: default
# truncated output
spec:
clusterIP: 10.111.188.196
externalTrafficPolicy: Cluster
ports:
- nodePort: 32036
port: 9898
protocol: TCP
targetPort: 9898
selector:
run: podinfo
sessionAffinity: None
type: NodePort
status:
loadBalancer: {}
The output is verbose, and there are a few fields that are
automatically set to their defaults.
The above YAML shortened could be rewritten as:
service.yaml
apiVersion: v1
kind: Service
metadata:
name: podinfo
spec:
selector:
run: podinfo
ports:
- port: 9898
targetPort: 9898
nodePort: 32036
1 2
3 4
74
bash
bash
bash
DESCRIPTION:
List of containers belonging to the pod. Containers cannot currently be
added or removed. There must be at least one container in a Pod. Cannot be
updated.
You might have used a tool such as Nginx to route the traffic
to several apps.
You define rules, such as the domain or the path, and the
backend that should receive the traffic.
Nginx inspects every incoming request against the rules and
forwards it to the right backend.
Routing traffic in such manner is popular even in
78
Kubernetes.
Hence there's a specific object called Ingress where you can
define routing rules.
ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: podinfo
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: podinfo
port:
number: 9898
ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: podinfo
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: podinfo
port:
number: 9898
bash
bash
bash
bash
Does it work?
82
bash
$ minikube ip
192.168.64.18
Recap
A quick recap of what you've done so far:
Self-healing
and scaling
85
bash
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: first-deployment
spec:
selector:
matchLabels:
run: podinfo
template:
metadata:
labels:
run: podinfo
spec:
containers:
- name: podinfo
image: stefanprodan/podinfo:6.0.3
ports:
- containerPort: 9898
of a Pod.
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: first-deployment
spec:
selector:
matchLabels:
run: podinfo
template:
metadata:
labels:
run: podinfo
spec:
containers:
- name: podinfo
image: stefanprodan/podinfo:6.0.3
ports:
- containerPort: 9898
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: first-deployment
spec:
selector:
matchLabels:
run: podinfo
template:
metadata:
labels:
run: podinfo
spec:
containers:
- name: podinfo
image: stefanprodan/podinfo:6.0.3
ports:
- containerPort: 9898
You can copy the content of the above file and save it as
deployment.yaml .
You can create the Deployment in Kubernetes with:
bash
The Deployment should create a Pod that has the same label
as the one that was deleted earlier.
If you visit http://[replace with minikube ip]/ , can you
see the app?
Yes, it worked.
89
bash
bash
bash
bash
bash
You should see four more Pods in the terminal running the
command kubectl get pods --watch :
bash
bash
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: first-deployment
spec:
replicas: 10
selector:
matchLabels:
run: podinfo
template:
metadata:
labels:
run: podinfo
spec:
containers:
- name: podinfo
image: stefanprodan/podinfo:6.0.3
ports:
- containerPort: 9898
bash
1 2
3
95
Recap
Your deployment in Kubernetes is rock solid.
When a Pod is deleted, it is automatically respawned.
You can change how many instances of your app are
running with a line change in a YAML file.
You have a solid framework that can be copy-pasted and
adapted to deployment.
You can reuse the same YAML file, and template the same
values with different names.
In your Kubernetes journey, you will
Not create Pods manually, but use Deployments instead.
Often create pairs of Deployment and Service.
Occasionally create Ingress resources to expose a few
96
Deploying an
app on
Kubernetes
end-to-end
98
bash
$ mkdir -p first-k8s-app
$ cd first-k8s-app
<html style="background-color:#FF003C">
<head>
<title>v1.0.0</title>
</head>
<body style="display:flex;
align-items:center;
justify-content:center;
color:#FFFFFF;
font-family:sans-serif;
font-size:6rem;margin:0;
letter-spacing:-0.1em">
<h1>v1.0.0</h1>
</body>
</html>
You can open the file in your browser of choice and see what
it looks like.
It's a boring HTML page!
100
bash
Dockerfile
FROM nginx:1.19.5
EXPOSE 80
COPY index.html /usr/share/nginx/html
bash
Dockerfile.
bash
$ docker images
104
bash
bash
$ curl localhost:8080
<html style="background-color:#FF003C">
<head>
<title>v1.0.0</title>
</head>
<body style="display:flex;
align-items:center;
justify-content:center;
color:#FFFFFF;
font-family:sans-serif;
font-size:6rem;margin:0;
letter-spacing:-0.1em">
<h1>v1.0.0</h1>
</body>
</html>
bash
$ docker ps
CONTAINER ID IMAGE COMMAND PORTS NAMES
41b50740a920 first-k8s-app "nginx -g 'daemon of…" 0.0.0.0:8080->80/tcp busy_allen
Great!
It's time to test your application!
You can visit http://localhost:8080 with your browser to
inspect the containerised app.
When you're done experimenting, stop the container with
Ctrl+C.
1 2
3 4
5
108
bash
PowerShell — □ 𝖷
PowerShell — □ 𝖷
bash
$ docker ps
CONTAINER ID IMAGE COMMAND
c0a09c8dcb24 ba04bb24b957 "/storage-provisioner"
2c808ad27e8e db91994f4ee8 "/coredns -conf /etc…"
d47bbd039ab0 788e63d07298 "/usr/local/bin/kube…"
87a4be9b8bbf k8s.gcr.io/pause:3.2 "/pause"
14a19b073040 k8s.gcr.io/pause:3.2 "/pause"
3fb274f7889d k8s.gcr.io/pause:3.2 "/pause"
ee124aac2c1c 60d957e44ec8 "kube-scheduler --au…"
72080d7884b1 95d99817fc33 "kube-apiserver --ad…"
21ef0b1bc69a 3a1a2b528610 "kube-controller-man…"
a18906334302 05b738aa1bc6 "etcd --advertise-cl…"
b75a0a16831e k8s.gcr.io/pause:3.2 "/pause"
7b060488c2da k8s.gcr.io/pause:3.2 "/pause"
00b7de9b36c0 k8s.gcr.io/pause:3.2 "/pause"
6a729d4b70f1 k8s.gcr.io/pause:3.2 "/pause"
110
bash
$ docker ps
CONTAINER ID IMAGE COMMAND
bash
bash
bash
And you can verify that the image was built correctly with:
bash
app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
113
replicas: 1
selector:
matchLabels:
name: app
template:
metadata:
labels:
name: app
spec:
containers:
- name: app
image: first-k8s-app
ports:
- containerPort: 80
imagePullPolicy: IfNotPresent
bash
114
DESCRIPTION:
Deployment enables declarative updates for Pods and ReplicaSets.
# truncated output
bash
DESCRIPTION:
Number of desired pods. This is a pointer to distinguish between explicit
zero and not specified. Defaults to 1.
kube/app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
115
spec:
replicas: 1
selector:
matchLabels:
name: app
template:
metadata:
labels:
name: app
spec:
containers:
- name: app
image: first-k8s-app
ports:
- containerPort: 80
imagePullPolicy: IfNotPresent
kube/app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 1
selector:
matchLabels:
name: app
template:
metadata:
labels:
name: app
spec:
containers:
- name: app
image: first-k8s-app
ports:
- containerPort: 80
imagePullPolicy: IfNotPresent
kube/app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 1
selector:
matchLabels:
name: app
template:
metadata:
labels:
name: app
spec:
containers:
- name: app
image: first-k8s-app
ports:
- containerPort: 80
imagePullPolicy: IfNotPresent
Defining a Service
A Service resource makes Pods accessible to other Pods or
users inside and outside the cluster.
119
app.yaml
apiVersion: v1
kind: Service
metadata:
120
name: app
spec:
selector:
name: app
ports:
- port: 8080
targetPort: 80
kube/app.yaml
kube/app.yaml
apiVersion: v1
kind: Service
metadata:
name: app
spec:
selector:
name: app
ports:
- port: 8080
targetPort: 80
kube/app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
# ...
template:
metadata:
labels:
name: app
# ...
kube/app.yaml
apiVersion: v1
kind: Service
metadata:
name: app
spec:
selector:
name: app
ports:
- port: 8080
targetPort: 80
In this case, the Service listens for requests on port 8080 and
forwards them to port 80 of the target Pods:
123
Defining an Ingress
The Ingress is the component that exposes your Pods to the
public internet.
The definition for the resource is similar to what you used in
the previous chapter:
app.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /second
pathType: Prefix
backend:
service:
name: app
port:
number: 8080
125
You can paste the above content at the end of your existing
app.yaml file, after Deployment and Service like this:
kube/app.yaml
kube/app.yaml
apiVersion: networking.k8s.io/v1
126
kind: Ingress
metadata:
name: app
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /second
pathType: Prefix
backend:
service:
name: app
port:
number: 8080
bash
bash
bash
$ minikube ip
192.168.64.18
When you're done testing the app, you can remove it from
the cluster with the following command:
bash
applications in Kubernetes.
Recap
Deploying an application in Kubernetes requires you to:
Organising
resources
132
You might have noticed that the Pod that you created isn't
the only Pod in the cluster.
You can list all the Pods in the cluster with:
bash
bash
bash
111s
kube-system Active 111s
kube/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: first-k8s-app
bash
bash
pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
bash
bash
bash
bash
Recap
140
Practicing
deployments
142
Version 2.0.0
You should deploy in Minikube an app that uses Nginx and
serves a static HTML file.
The content of the HTML file is:
index.html
<html style="background-color:#FABE28">
<head>
<title>v2.0.0</title>
</head>
143
<body style="display:flex;
align-items:center;
justify-content:center;
color:#000000;
font-family:sans-serif;
font-size:6rem;
margin:0;
letter-spacing:-0.1em">
<h1>v2.0.0</h1>
</body>
</html>
You should:
Namespace all resources in Kubernetes.
Package the application as a Docker container.
Deploy it in Minikube.
Scale the Deployment to two replicas.
Expose the app with a Service.
Expose the app with an Ingress with path /v2 .
Use YAML resource definitions (please don't use
kubectl run , kubectl expose or kubectl scale ).
Deployment
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-name
labels:
key: pod-label
spec:
selector:
matchLabels:
key: pod-label
template:
metadata:
labels:
key: pod-label
spec:
containers:
- name: container-name
image: container-image
ports:
- containerPort: 3000
Service
service.yaml
apiVersion: v1
145
kind: Service
metadata:
name: service-name
spec:
selector:
key: value
ports:
- port: 3000
targetPort: 9376
Ingress
You can find the official documentation for the Ingress here
An Ingress manifest looks like this:
ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-name
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-name
port:
number: 8888
Namespace
here
A Namespace looks like this:
namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: namespace-name
Version 3.0.0
How does the deployment change when using an app
written in Python?
Let's assume you created a python directory and saved the
following HTML file as index.html :
index.html
<html style="background-color:#00C176">
<head>
<title>API</title>
</head>
<body style="display:flex;
align-items:center;
justify-content:center;
color:#FFFFFF;
font-family:sans-serif;
font-size:6rem;
margin:0;
letter-spacing:-0.1em">
147
<h1>API</h1>
</body>
</html>
bash
$ python3 -m http.server
k8s-is-great.com .
Use YAML resource definitions.
Group the resources.
Please note that you might need to use curl to test the
domain-based routing of the Ingress.
You can use the following command to send a request with a
specific domain name:
bash
Making
sense of
Deployment,
Service and
Ingress
150
1 2
3
151
hello-world.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-hello-world
labels:
track: canary
spec:
selector:
matchLabels:
run: pod-hello-world
template:
metadata:
labels:
run: pod-hello-world
spec:
containers:
- name: cont1
image: learnk8s/app:1.0.0
ports:
- containerPort: 8080
---
152
apiVersion: v1
kind: Service
metadata:
name: service-hello-world
spec:
ports:
- port: 80
targetPort: 8080
selector:
run: pod-hello-world
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-hello-world
spec:
rules:
- http:
paths:
- path: /
backend:
service:
name: service-hello-world
port:
number: 80
pathType: Prefix
bash
1 2
3 4
5
155
hello-world.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-hello-world
labels:
track: canary
spec:
selector:
matchLabels:
run: pod-hello-world
template:
metadata:
labels:
156
run: pod-hello-world
spec:
containers:
- name: cont1
image: learnk8s/app:1.0.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: service-hello-world
spec:
ports:
- port: 80
targetPort: 8080
selector:
run: pod-hello-world
bash
bash
bash
Where:
service/service-hello-world is the name of the
service
3000 is the port that you wish to open on your
computer
80 is the port exposed by the Service in the port field
158
1 2
3 4
hello-world.yaml
apiVersion: v1
kind: Service
metadata:
name: service-hello-world
spec:
ports:
- port: 80
targetPort: 8080
selector:
run: pod-hello-world
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-hello-world
spec:
rules:
- http:
paths:
- backend:
service:
name: service-hello-world
port:
number: 80
path: /
pathType: Prefix
bash
STATUS
kube-system coredns-5644d7b6d9-jn7cq 1/1 Running
kube-system etcd-minikube 1/1 Running
kube-system kube-apiserver-minikube 1/1 Running
kube-system kube-controller-manager-minikube 1/1 Running
kube-system kube-proxy-zvf2h 1/1 Running
kube-system kube-scheduler-minikube 1/1 Running
kube-system nginx-ingress-controller-6fc5bcc 1/1 Running
bash
bash
Recap
Here's a quick recap on what ports and labels should match:
Why Pods?
164
1 2
3 4
multiple containers.
The Pod above is a combination of Node.js and
Fig. 2
Fluentd.
166