CKAD Exam Preparation 4/4 - Deployments, Services and Networking

▶️ Introduction

This part outlines the key deployment and networking aspects of the CKAD Exam Curriculum. To learn more about the CKAD Exam please read this overview.

About this Series

During this blog series I summarize the main “study hooks” in order to be successful with your exam, as I was. The series is composed by the following articles:

All the examples have been developed using minikube on macOS Catalina with VirtualBox.

🖥️ Deployments

📘   https://kubernetes.io/docs/concepts/workloads/controllers/deployment/

Create a new Deployment boilerplate via dry-run:

kubectl create deployment ex1 --image=nginx  --dry-run=client -o=yaml > dep1.yaml

Then such deployment can be tuned, for instance setting the number of desired replicas:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: ex1
  name: ex1
  namespace: jmcf
spec:
  replicas: 3
  selector:
    matchLabels:
      app: ex1
  strategy: {}
  template:
    metadata:
      labels:
        app: ex1
    spec:
      containers:
      - image: nginx
        name: nginx

Check Deployment status:

kubectl get deployments/ex1 -n jmcf -o=wide -w
NAME   READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES   SELECTOR
ex1    3/3     3            3           2m18s   nginx        nginx    app=ex1
kubectl get rs -n jmcf -o=wide --selector='app=ex1'
NAME             DESIRED   CURRENT   READY   AGE     CONTAINERS   IMAGES   SELECTOR
ex1-678d4cb9c5   3         3         3       5m23s   nginx        nginx    app=ex1,

📌  A Replica Set is created as a subsidiary of a Deployment.

List the Pods associated to a Deployment:

kubectl get pods -n jmcf --selector='app=ex1' -o=wide
NAME                   READY   STATUS    RESTARTS   AGE     IP            
ex1-678d4cb9c5-lx7sx   1/1     Running   0          7m30s   172.17.0.32
ex1-678d4cb9c5-lzlkk   1/1     Running   0          7m30s   172.17.0.47
ex1-678d4cb9c5-zmknk   1/1     Running   0          7m30s   172.17.0.48

📌  A Deployment is based on a templated Pod. There should be as many Pods as desired replicas.

📌  A delete of a Deployment is on cascade, i.e. all subsidiary Pods will be deleted.

A Deployment can be updated just through kubectl edit:

kubectl edit -n jmcf -f dep1.yaml

📌  When you edit a Deployment a new revision might be created, allowing you to rollback later to a previous configuration.

Update Deployment image:

kubectl set image deployment/ex1 nginx=nginx:1.9.1 -n jmcf
kubectl get rs -n jmcf -o=wide --selector='app=ex1'
NAME             DESIRED   CURRENT   READY   AGE     CONTAINERS   IMAGES        SELECTOR
ex1-678d4cb9c5   0         0         0       3h30m   nginx        nginx         app=ex1
ex1-79c777cf98   3         3         3       171m    nginx        nginx:1.9.1   app=ex1

📌  Changing the image of a Deployment implies the creation of a new Replica Set.

Rollout

To check the rollout status of a Deployment:

kubectl rollout status deploy ex1 -n jmcf -w

📌  maxSurge and maxUnavailable (spec.strategy) are two important parameters that govern how the rollout process is conducted.

Display rollout history of a Deployment:

kubectl rollout history deployments/ex1 -n jmcf

Rolling back to a previous revision:

kubectl rollout undo deployments/ex1 --to-revision=1 -n jmcf

📌  A rollback generates a new revision.

Scaling out a Deployment:

kubectl scale deployments/ex1 --replicas=5 -n jmcf

📌  Scaling out a Deployment does not generate a new revision.

Auto scale an existing Deployment:

kubectl autoscale deployments/ex1 --min=5 --max=10 --cpu-percent=80 -n jmcf

📌  Requesting autoscaling implies the creation of an Horizontal Pod Autoscaler.

📌  An Horizontal Pod Autoscaler takes precedence over a Replica Set.

List the existing Horizontal Pod Autoscalers:

kubectl get hpa -n jmcf
NAME   REFERENCE        TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
ex1    Deployment/ex1   <unknown>/80%   5         10        5          2m57s

Remove Horizontal Autoscaling:

kubectl delete hpa/ex1 -n jmcf

🧱 Services

📘   https://kubernetes.io/docs/concepts/services-networking/service

Headless Service

Create a headless (without Cluster IP) Service:

kubectl create service clusterip my-service --tcp=80:80 --clusterip="None" --dry-run=client -o=yaml > service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: ex1
  name: my-service
  namespace: jmcf
spec:
  clusterIP: None
  ports:
  - name: 80-80
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: ex1
  type: ClusterIP

📌  The name of a Service object must be a valid DNS label name.

📌  The ports exposed under a headless Service are just Pod’s container(s) ports

📌  A Service is assigned a DNS name, usually in the form <service-name>.<namespace>.svc.cluster.local

📌  A Service groups Pods based on matching labels.

kubectl describe service my-service -n jmcf
Name:              my-service
Namespace:         jmcf
Labels:            app=ex1
Annotations:       Selector:  app=ex1
Type:              ClusterIP
IP:                None
Port:              80-80  80/TCP
TargetPort:        80/TCP
Endpoints:         172.17.0.47:80,172.17.0.48:80,172.17.0.49:80
Session Affinity:  None
Events:            <none>

Test that my-service has been configured properly:

kubectl run test1 -it --rm=true --image=busybox --restart=Never -n jmcf -- wget -O - http://my-service
kubectl run test1 -it --rm=true --image=busybox --restart=Never -n jmcf -- wget -O - http://my-service.jmcf
kubectl run test1 -it --rm=true --image=busybox --restart=Never -n jmcf -- wget -O - http://my-service.jmcf.svc.cluster.local

Cluster IP Service

Create a Cluster IP Service

kubectl create service clusterip my-service --tcp=8080:80 --dry-run=client -o=yaml > service-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: ex1
  name: my-service-cip
  namespace: jmcf
spec:
  ports:
  - name: 8080-80
    port: 8080
    protocol: TCP
    targetPort: 80
  selector:
    app: ex1
  type: ClusterIP

📌  targetPort (the second element in the --tcp parameter) is the Pod’s container port.

kubectl describe service my-service-cip -n jmcf
Name:              my-service-cip
Namespace:         jmcf
Labels:            app=ex1
Annotations:       Selector:  app=ex1
Type:              ClusterIP
IP:                10.96.72.170
Port:              8080-80  8080/TCP
TargetPort:        80/TCP
Endpoints:         172.17.0.47:80,172.17.0.48:80,172.17.0.49:80
Session Affinity:  None
Events:            <none>

Test that the Cluster IP has been assigned properly:

kubectl run test1 -it --rm=true --image=busybox --restart=Never -n jmcf -- wget -O - http://10.96.72.170:8080
kubectl run test1 -it --rm=true --image=busybox --restart=Never -n jmcf -- wget -O - http://my-service-cip:8080
kubectl run test1 -it --rm=true --image=busybox --restart=Never -n jmcf -- wget -O - http://my-service-cip.jmcf:8080
kubectl run test1 -it --rm=true --image=busybox --restart=Never -n jmcf -- wget -O - http://my-service-cip.jmcf.svc.cluster.local:8080

Node Port Service

Create Node Port Service

kubectl create service nodeport my-service-np --tcp=8080:80 --dry-run=client -o=yaml > service-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: ex1
  name: my-service-np
  namespace: jmcf
spec:
  ports:
  - name: 8080-80
    port: 8080
    protocol: TCP
    targetPort: 80
  selector:
    app: ex1
  type: NodePort
kubectl describe service my-service-np -n jmcf
Name:                     my-service-np
Namespace:                jmcf
Labels:                   app=ex1
Annotations:              Selector:  app=ex1
Type:                     NodePort
IP:                       10.106.215.220
Port:                     8080-80  8080/TCP
TargetPort:               80/TCP
NodePort:                 8080-80  31460/TCP
Endpoints:                172.17.0.47:80,172.17.0.48:80,172.17.0.49:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>

Test that the Node Port has been assigned properly:

wget -O - http://192.168.99.101:31460
kubectl run test1 -it --rm=true --image=busybox --restart=Never -n jmcf -- wget -O - http://10.106.215.220:8080
kubectl run test1 -it --rm=true --image=busybox --restart=Never -n jmcf -- wget -O - http://my-service-np:8080
kubectl run test1 -it --rm=true --image=busybox --restart=Never -n jmcf -- wget -O - http://my-service-np.jmcf:8080
kubectl run test1 -it --rm=true --image=busybox --restart=Never -n jmcf -- wget -O - http://my-service-np.jmcf.svc.cluster.local:8080
kubectl run test1 -it --rm=true --image=busybox --restart=Never -n jmcf -- wget -O - http://172.17.0.47

🌐 Networking

Ingress

📘   https://kubernetes.io/docs/concepts/services-networking/ingress/

📌  Ingress exposes HTTP and HTTPS routes from outside the cluster to services within the cluster.

📌  You must have an ingress controller to satisfy an Ingress. Only creating an Ingress resource has no effect.

An Ingress which enables reverse proxying to your Service from a canonical address:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: test-ingress
  namespace: jmcf
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: ckad.example.org
    http:
      paths:
      - path: /ex1
        backend:
          serviceName: my-service-cip
          servicePort: 8080

Then you can get access to your Service through (provided external DNS entry or etc/hosts has been set up):

curl http://ckad.example.org/ex1

📌  The annotation nginx.ingress.kubernetes.io/rewrite-target: / enables reverse proxying.

Network Policies

📘   https://kubernetes.io/docs/concepts/services-networking/network-policies/

📌  To use Network Policies, you must be using a networking solution (such as Calico, Cilium or Flannel) which supports NetworkPolicy. Creating a NetworkPolicy resource without a controller that implements it will have no effect.

Define a Network Policy that allows to talk to, our previously defined, ex1 Pods only from containers belonging to the jmcf Namespace which are labeled as role=test.

kubectl label namespace jmcf 'project=ckad'

📌  Namespace selector needs matching labels.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: jmcf
spec:
  podSelector:
    matchLabels:
      app: ex1
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          project: ckad
    - podSelector:
        matchLabels:
          role: test
    ports:
    - protocol: TCP
      port: 80
kubectl describe NetworkPolicy test-network-policy -n jmcf
Name:         test-network-policy
Namespace:    jmcf
Created on:   2020-06-25 14:03:55 +0200 CEST
Labels:       <none>
Annotations:  Spec:
  PodSelector:     app=ex1
  Allowing ingress traffic:
    To Port: 80/TCP
    From:
      NamespaceSelector: project=ckad
    From:
      PodSelector: role=test
  Not affecting egress traffic
  Policy Types: Ingress

📌  A Network Policy defines white lists for ingress traffic, egress traffic or both.

📌  A Network Policy applies to certain Pods (those matching labels) in a Namespace.

📌  A Network Policy ingress or egress rules determines from or to which Pods and/or Namespaces traffic is allowed.

📌  If you declare Ingress or Egress policy types (under policyTypes), and no rule is provided under that category, then no traffic of such category will be allowed.

🗒️ Feedback