All the services need to be in a running state. With Kubernetes, our ultimate aim is to deploy our application in the form of containers on a set of machines that are configured as worker nodes in a cluster. Kubernetes does not deploy containers directly on the worker nodes. The containers are encapsulated into a Kubernetes object known as pods. A pod is a single instance of an application. A pod is the smallest object that you can create in Kubernetes.
What is a Pod?
- A Pod is an abstraction of a server
- It can run multiple containers within a single NameSpace, exposed by a single IP address
- The Pod is the minimal entity that can be managed by Kubernetes
- From a container perspective, a Pod is an entity that runs typically one or more containers by using container images
- Typically, Pods are only started through a Deployment, because “naked” Pods are not rescheduled in case of a node failure
Naked Pod Disadvantages
- Naked Pods are not rescheduled in case of failure
- Rolling updates don’t apply to naked Pods; you can only bring it down and bring it up again with the new settings
- Naked Pods cannot be scaled
- Naked Pods cannot be replaced automatically
Using Deployments
- The Deployment is the standard way for running containers in Kubernetes
- Deployments are responsible for starting Pods in a scalable way
- The Deployment resource uses a ReplicaSet to manage scalability
- Also, the Deployment offers the RollingUpdate feature to allow for zero-downtime application updates
- To start a Deployment the imperative way, use
kubectl create deploy
…
Kubernetes Tools
- Before starting the installation, you’ll have to install the Kubernetes tools
- These include the following:
kubeadm
: used to install and manage a Kubernetes clusterkubelet
: the core Kubernetes service that starts all Podskubectl
: the interface that allows you to run and manage applications in Kubernetes
kubectl
1 |
$ kubectl run nginx --image nginx |
This command deploys a Docker container by creating a pod, so it first creates a pod automatically and deploys an instance of the NGINX Docker image, but where does it get the application image from? For that, you need to specify the image name using the dash dash image parameter. The application image, in this case, the NGINX image, is downloaded from the Docker Hub repository. You could configure Kubernetes to pull the image from the public Docker Hub or a private repository within the organization.
Now that we have a pod created, how do we see the list of pods available?
1 |
$ kubectl get pods |
The kubectl get pods
command helps us see the list of pods in our cluster. In this case,we see the pod is in a container creating state and soon changes to a running statewhen it is actually running.
To see detailed information about the pod, run:
1 |
$ kubectl describe pod myapp-pod |
This will tell you information about the pod when it was created, what labels are assigned to it, what docker containers are part of it and the events associated with that pod.
Examples
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
[root@k8s ~]# kubectl run testpod --image=nginx pod/testpod created [root@k8s ~]# kubectl get pods NAME READY STATUS RESTARTS AGE testpod 0/1 Pending 0 8s [root@k8s ~]# kubectl create deploy -h [root@k8s ~]# kubectl create deployment firstnginx --image=nginx --replicas=3 deployment.apps/firstnginx created [root@k8s ~]# kubectl get all NAME READY STATUS RESTARTS AGE pod/firstnginx-d8679d567-249g9 0/1 Pending 0 20s pod/firstnginx-d8679d567-66c4s 0/1 Pending 0 20s pod/firstnginx-d8679d567-72qbd 0/1 Pending 0 20s pod/testpod 0/1 Pending 0 7m47s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 5h NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/firstnginx 0/3 3 0 20s NAME DESIRED CURRENT READY AGE replicaset.apps/firstnginx-d8679d567 3 3 0 20s |
Understanding DaemonSets
- A DaemonSet is a resource that starts one application instance on each cluster node
- It is commonly used to start agents like the kube-proxy that need to be running on all cluster nodes
- It can also be used for user workloads
- lf the DaemonSet needs to run on control-plane nodes, a toleration must be configured to allow the node to run regardless of the control-plane taints
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
[root@k8s ~]# kubectl get ds No resources found in default namespace. [root@k8s ~]# kubectl get ds -A NAMESPACE NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE kube-system kube-proxy 1 1 1 1 1 kubernetes.io/os=linux 5h4m [root@k8s ~]# kubectl get ds -n kube-system kube-proxy NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE kube-proxy 1 1 1 1 1 kubernetes.io/os=linux 5h9m [root@k8s ~]# kubectl get ds -n kube-system kube-proxy -o yaml apiVersion: apps/v1 kind: DaemonSet metadata: annotations: deprecated.daemonset.template.generation: "1" creationTimestamp: "2024-01-31T15:03:27Z" generation: 1 labels: k8s-app: kube-proxy name: kube-proxy namespace: kube-system resourceVersion: "4832" uid: 2c4a7965-c3a4-42f9-8ce0-caa49b6d41c5 spec: revisionHistoryLimit: 10 selector: matchLabels: k8s-app: kube-proxy template: metadata: creationTimestamp: null labels: k8s-app: kube-proxy spec: containers: - command: - /usr/local/bin/kube-proxy - --config=/var/lib/kube-proxy/config.conf - --hostname-override=$(NODE_NAME) env: - name: NODE_NAME valueFrom: fieldRef: apiVersion: v1 fieldPath: spec.nodeName image: registry.k8s.io/kube-proxy:v1.28.3 imagePullPolicy: IfNotPresent name: kube-proxy resources: {} securityContext: privileged: true terminationMessagePath: /dev/termination-log terminationMessagePolicy: File volumeMounts: - mountPath: /var/lib/kube-proxy name: kube-proxy - mountPath: /run/xtables.lock name: xtables-lock - mountPath: /lib/modules name: lib-modules readOnly: true dnsPolicy: ClusterFirst hostNetwork: true nodeSelector: kubernetes.io/os: linux priorityClassName: system-node-critical restartPolicy: Always schedulerName: default-scheduler securityContext: {} serviceAccount: kube-proxy serviceAccountName: kube-proxy terminationGracePeriodSeconds: 30 tolerations: - operator: Exists volumes: - configMap: defaultMode: 420 name: kube-proxy name: kube-proxy - hostPath: path: /run/xtables.lock type: FileOrCreate name: xtables-lock - hostPath: path: /lib/modules type: "" name: lib-modules updateStrategy: rollingUpdate: maxSurge: 0 maxUnavailable: 1 type: RollingUpdate status: currentNumberScheduled: 1 desiredNumberScheduled: 1 numberAvailable: 1 numberMisscheduled: 0 numberReady: 1 observedGeneration: 1 updatedNumberScheduled: 1 |
Lets create a DaemmonSet as it is written at
https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/
copy yaml from
https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/controllers/daemonset.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
[root@k8s ~]# vi daemondemo.yaml [root@k8s ~]# cat daemondemo.yaml apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd-elasticsearch namespace: kube-system labels: k8s-app: fluentd-logging spec: selector: matchLabels: name: fluentd-elasticsearch template: metadata: labels: name: fluentd-elasticsearch spec: tolerations: # these tolerations are to have the daemonset runnable on control plane nodes # remove them if your control plane nodes should not run pods - key: node-role.kubernetes.io/control-plane operator: Exists effect: NoSchedule - key: node-role.kubernetes.io/master operator: Exists effect: NoSchedule containers: - name: fluentd-elasticsearch image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2 resources: limits: memory: 200Mi requests: cpu: 100m memory: 200Mi volumeMounts: - name: varlog mountPath: /var/log # it may be desirable to set a high priority class to ensure that a DaemonSet Pod # preempts running Pods # priorityClassName: important terminationGracePeriodSeconds: 30 volumes: - name: varlog hostPath: path: /var/log [root@k8s ~]# kubectl create deploy mydaemon --image=nginx --dry-run=client -o yaml > mydaemon.yaml [root@k8s ~]# vim mydaemon.yaml |
After edit the kind and remove replicas and sttrategy spec mydaemon.yaml file looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
[root@k8s ~]# cat mydaemon.yaml apiVersion: apps/v1 kind: Daemonset metadata: creationTimestamp: null labels: app: mydaemon name: mydaemon spec: selector: matchLabels: app: mydaemon template: metadata: creationTimestamp: null labels: app: mydaemon spec: containers: - image: nginx name: nginx resources: {} status: {} |
Now lets create daemonset:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[root@k8s ~]# kubectl apply -f mydaemon.yaml daemonset.apps/mydaemon created [root@k8s ~]# kubectl get ds NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE mydaemon 0 0 0 0 0 <none> 62s [root@k8s ~]# kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES firstnginx-d8679d567-249g9 0/1 Pending 0 35m <none> <none> <none> <none> firstnginx-d8679d567-66c4s 0/1 Pending 0 35m <none> <none> <none> <none> firstnginx-d8679d567-72qbd 0/1 Pending 0 35m <none> <none> <none> <none> testpod 0/1 Pending 0 43m <none> <none> <none> <none> |
We have no daemonset running because we use minikube which has no worker node.
Stateful and Stateless Applications
- A stateless application is an application that doesn’t store any session data
- Redirecting traffic in a stateless application is easy, the traffic can just be directed to another Pod instance
- A stateful application saves session data to persistent storage
- Databases are an example of stateful applications
- Even if stateful applications can be started by a Deployment, it’s better to start it in a StatefulSet
StatefulSet
A StatefulSet offers features that are needed by stateful applications
-
- It provides guarantees about ordering and uniqueness of Pods
- It maintains a sticky identifier for each of the Pods it creates
- Pods in a StatefulSet are not interchangeable: each Pod has a persistent identifier that it maintains while being rescheduled
- The unique Pod identifiers make it easier to match existing volumes to replaced Pods
StatefulSet Considerations
- Storage must be automatically provisioned by a persistent volume provisioner. Pre-provisioning is challenging, as volumes need to be dynamically added when new Pods are scheduled
- When a StatefulSet is deleted, associated volumes will not be deleted
- A headless Service resource must be created in order to manage the network identity of Pods
- Pods are not guaranteed to be stopped while deleting a StatefulSet, and it is recommended to scale down to zero Pods before deleting the StatefulSet
Let’s look at the below yaml file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
[root@k8s cka]# cat statefuldemo.yaml apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx --- apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: selector: matchLabels: app: nginx serviceName: "nginx" replicas: 3 template: metadata: labels: app: nginx spec: terminationGracePeriodSeconds: 10 containers: - name: nginx image: k8s.gcr.io/nginx-slim:0.8 ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: [ "ReadWriteMany" ] resources: requests: storage: 1Gi |
StatefullSets requires headless service so clusterIP
is set to none
in the above yaml file. Let’s run it and see what it is doing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[root@k8s cka]# kubectl apply -f statefuldemo.yaml service/nginx created statefulset.apps/web created [root@k8s cka]# kubectl get statefulset NAME READY AGE web 0/3 16s [root@k8s cka]# kubectl get pods NAME READY STATUS RESTARTS AGE firstnginx-d8679d567-249g9 0/1 Pending 0 12h firstnginx-d8679d567-66c4s 0/1 Pending 0 12h firstnginx-d8679d567-72qbd 0/1 Pending 0 12h testpod 0/1 Pending 0 12h web-0 0/1 Pending 0 25s [root@k8s cka]# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE www-web-0 Pending standard 40s |
The name of pods (web-0
) which has been generated doesn’t contain the random id but there are nice names. Because of the availability here of the storage class the StatefulSets has been able to automatically allocate storage for very single instance in the StatefulSet.
Running Individual Pods
- Running individual Pods has disadvantages:
- No workload protection
- No load balancing
- No zero-downtime application update
- Use individual Pods only for testing, troubleshooting, and analyzing
- In all other cases, use Deployment, DaemonSet, or StatefulSet
Here is how we can run an idividual pod:
1 2 3 4 5 6 7 8 9 10 11 12 |
[root@k8s cka]# kubectl run -h [root@k8s cka]# kubectl run sleepy --image=busybox -- sleep 3600 pod/sleepy created [root@k8s cka]# kubectl get pods NAME READY STATUS RESTARTS AGE firstnginx-d8679d567-249g9 0/1 Pending 0 14h firstnginx-d8679d567-66c4s 0/1 Pending 0 14h firstnginx-d8679d567-72qbd 0/1 Pending 0 14h sleepy 0/1 Pending 0 3m14s testpod 0/1 Pending 0 14h web-0 0/1 Pending 0 133m |
--
[dash dash space] is the easy way to pass a command (sleep 3600
) that sholud be started by a pod.
What can you do if you want to initialize something before the main application is started ? You can run an init container.
Using !nit Containers
- If preparation is required before running the main container, use an init container
- Init containers run to completion, and once completed the main container can be started
- Use init containers in any case where preliminary setup is required
Consider such an init container temmplate:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
[root@k8s cka]# cat initme.yaml apiVersion: v1 kind: Pod metadata: name: init-demo spec: containers: - name: nginx image: nginx ports: - containerPort: 80 # These containers are run during pod initialization initContainers: - name: install image: busybox command: - sleep - "30" [root@k8s cka]# kubectl apply -f initme.yaml pod/init-demo created [root@k8s cka]# kubectl get pods NAME READY STATUS RESTARTS AGE firstnginx-d8679d567-249g9 0/1 Pending 0 15h firstnginx-d8679d567-66c4s 0/1 Pending 0 15h firstnginx-d8679d567-72qbd 0/1 Pending 0 15h init-demo 0/1 Pending 0 20s sleepy 0/1 Pending 0 47m testpod 0/1 Pending 0 15h web-0 0/1 Pending 0 177m |
Scaling Applications
- kubectl scale is used to manually scale Deployment, ReplicaSet, or StatefulSet
kubectl scale deployment myapp --replicas=3
- Alternatively, HorizontalPodAutoscaler can be used
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
[root@k8s cka]# kubectl scale -h [root@k8s cka]# kubectl scale --replicas=4 deployment/firstnginx deployment.apps/firstnginx scaled [root@k8s cka]# kubectl get deploy NAME READY UP-TO-DATE AVAILABLE AGE firstnginx 0/4 4 0 17h [root@k8s cni]# kubectl get all --selector app=firstnginx NAME READY STATUS RESTARTS AGE pod/firstnginx-d8679d567-249g9 1/1 Running 0 24h pod/firstnginx-d8679d567-66c4s 1/1 Running 0 24h pod/firstnginx-d8679d567-72qbd 1/1 Running 0 24h pod/firstnginx-d8679d567-rhhlz 1/1 Running 0 7h54m NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/firstnginx 4/4 4 4 24h NAME DESIRED CURRENT READY AGE replicaset.apps/firstnginx-d8679d567 4 4 4 24h |
Multi-container Pods
- As a Pod should be created for each specific task, running single-container Pods is the standard
- In some cases, an additional container is needed to modify or present data generated by the main container
- Specific use cases are defined:
- Sidecar: provides additional functionality to the main container
- Ambassador: is used as a proxy to connect containers externally
- Adapter: is used to standardize or normalize main container output
Multi-container Storage
- In a multi-container Pod, Pod Volumes are often used as shared storage
- The Pod Volume may use PersistentVolumeClaim (PVC) to refer to a PersistentVolume, but may also directly refer to the required storage
- By using shared storage, the main container can write to it, and the helper Pod will pick up information written to the shared storage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
[root@k8s cka]# cat sidecarlog.yaml apiVersion: v1 kind: Pod metadata: name: two-containers spec: volumes: - name: shared-data emptyDir: {} containers: - name: nginx-container image: nginx volumeMounts: - name: shared-data mountPath: /usr/share/nginx/html - name: busybox-container image: busybox volumeMounts: - name: shared-data mountPath: /messages command: ["/bin/sh"] args: ["-c", "echo hello from the cluster > /messages/index.html && sleep 600" ] [root@k8s cka]# kubectl apply -f sidecarlog.yaml pod/two-containers created [root@k8s cni]# kubectl exec -it two-containers -c nginx-container -- cat /usr/share/nginx/html/index.html hello from the cluster |
Lab: Running a DaemonSet
- Create a DaemonSet with the name nginxdaemon.
- Ensure it runs an Nginx Pod on every worker node.
1 2 |
[root@k8s cni]# kubectl create deployment deploydaemon --image=nginx --dry-run=client -o yaml > deploydaemon.yaml [root@k8s cni]# vim deploydaemon.yaml |
Edit deploydaemon.yaml
. Change kind and delete replicas and strategy lines.
1 2 3 4 5 6 7 8 9 |
[root@k8s cni]# kubectl apply -f deploydaemon.yaml daemonset.apps/deploydaemon created [root@k8s cni]# kubectl get all --selector app=deploydaemon NAME READY STATUS RESTARTS AGE pod/deploydaemon-zzllp 1/1 Running 0 28s NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE daemonset.apps/deploydaemon 1 1 1 1 1 <none> 28s |