The Complete Kubernetes Course - Workshop

This workshop is based on the course "Learn DevOps: The Complete Kubernetes Course" on Udemy

Setup & Running First App

Setup on Azure

Please refer to this guide.

Running First App

Create a Pod

Create a file pod-helloworld.yml with the pod definition:

apiVersion: v1
kind: Pod
metadata:
  name: nodehelloworld.example.com
  labels:
    app: helloworld
spec:
  containers:
    - name: k8s-demo
      image: wardviaene/k8s-demo
      ports:
        - containerPort: 3000

Use kubectl to create the pod on the kubernetes cluster:

$ kubectl create -f pod-helloworld.yml
pod "nodehelloworld.example.com" created

See pod status

$ kubectl get pod
NAME                         READY     STATUS    RESTARTS   AGE
nodehelloworld.example.com   1/1       Running   0          2m

See pod config

$ kubectl describe pod nodehelloworld.example.com
Name:		nodehelloworld.example.com
Namespace:	default
Node:		k8s-agent-7d111633-0/10.240.0.4
Start Time:	Sun, 29 Apr 2018 04:36:37 +0000
Labels:		app=helloworld
Annotations:	<none>
Status:		Running
IP:		10.244.1.7
Containers:
  k8s-demo:
    Container ID:	docker://dddf1e925faf9c3fee56b373244bb7227d7f4c139c76816dbafb8237d6d00515
    Image:		wardviaene/k8s-demo
    Image ID:		docker-pullable://wardviaene/k8s-demo@sha256:2c050f462f5d0b3a6430e7869bcdfe6ac48a447a89da79a56d0ef61460c7ab9e
    Port:		3000/TCP
    State:		Running
      Started:		Sun, 29 Apr 2018 04:37:46 +0000
    Ready:		True
    Restart Count:	0
    Environment:	<none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-lr576 (ro)
Conditions:
  Type		Status
  Initialized 	True
  Ready 	True
  PodScheduled 	True
Volumes:
  default-token-lr576:
    Type:	Secret (a volume populated by a Secret)
    SecretName:	default-token-lr576
    Optional:	false
QoS Class:	BestEffort
Node-Selectors:	<none>
Tolerations:	<none>
Events:
  FirstSeen	LastSeen	Count	From				SubObjectPath			Type		Reason		      	Message
  ---------	--------	-----	----				-------------			--------	------		      	-------
  4m		4m		1	default-scheduler						Normal		Scheduled	      	Successfully assigned nodehelloworld.example.com to k8s-agent-7d111633-0
  4m		4m		1	kubelet, k8s-agent-7d111633-0					Normal		SuccessfulMountVolume  	MountVolume.SetUp succeeded for volume "default-token-lr576"
  4m		4m		1	kubelet, k8s-agent-7d111633-0	spec.containers{k8s-demo}	Normal		Pulling		      	pulling image "wardviaene/k8s-demo"
  2m		2m		1	kubelet, k8s-agent-7d111633-0	spec.containers{k8s-demo}	Normal		Pulled		      	Successfully pulled image "wardviaene/k8s-demo"
  2m		2m		1	kubelet, k8s-agent-7d111633-0	spec.containers{k8s-demo}	Normal		Created		      	Created container
  2m		2m		1	kubelet, k8s-agent-7d111633-0	spec.containers{k8s-demo}	Normal		Started		      	Started container

Forward port

$ kubectl port-forward nodehelloworld.example.com 8081:3000
Forwarding from 127.0.0.1:8081 -> 3000
Forwarding from [::1]:8081 -> 3000

Create Service

Expose service

$ kubectl expose pod nodehelloworld.example.com --type=NodePort --name nodehelloworld-service
service "nodehelloworld-service" exposed

See list of service

$ kubectl get service
NAME                     CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE
kubernetes               10.0.0.1      <none>        443/TCP          7d
nodehelloworld-service   10.0.241.45   <nodes>       3000:30706/TCP   25s

Get service url

$ kubectl describe service nodehelloworld-service
Name:				nodehelloworld-service
Namespace:			default
Labels:				app=helloworld
Annotations:		<none>
Selector:			app=helloworld
Type:				NodePort
IP:			    	10.0.241.45
Port:				<unset>	3000/TCP
NodePort:			<unset>	30706/TCP
Endpoints:			10.244.1.7:3000
Session Affinity:	None
Events:				<none>

Try accessing app locally

$ curl 10.244.1.7:3000
Hello World!

Access From the Internet

Edit service with VI

$ kubectl edit service/nodehelloworld-service
service "nodehelloworld-service" edited

Change externalTrafficPolicy to Local and change type to LoadBalancer.

# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: 2018-04-29T04:44:16Z
  labels:
    app: helloworld
  name: nodehelloworld-service
  namespace: default
  resourceVersion: "109236"
  selfLink: /api/v1/namespaces/default/services/nodehelloworld-service
  uid: f8e985ab-4b67-11e8-9b4e-000d3aa08982
spec:
  clusterIP: 10.0.241.45
  externalTrafficPolicy: Local
  healthCheckNodePort: 31970
  ports:
  - nodePort: 30706
    port: 3000
    protocol: TCP
    targetPort: 3000
  selector:
    app: helloworld
  sessionAffinity: None
  type: LoadBalancer
status:
  loadBalancer:
    ingress:
    - ip: 52.163.120.220

VI Usage: Press i to enter edit mode. Once done, press ESC and type :wq to save and quite.

Check service using kubectl get service

$ kubectl get service
NAME                     CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE
kubernetes               10.0.0.1      <none>        443/TCP          7d
nodehelloworld-service   10.0.241.45   <pending>     3000:30706/TCP   56m

Wait for a few minutes until <pending> changing to an IP address

$ kubectl get service
NAME                     CLUSTER-IP    EXTERNAL-IP      PORT(S)          AGE
kubernetes               10.0.0.1      <none>           443/TCP          7d
nodehelloworld-service   10.0.241.45   52.163.120.220   3000:30706/TCP   59m

Try to access the app at the displayed IP:port e.g. http://52.163.120.220:3000

References

Useful Commands

Command

Description

kubectl get pod

Get information about all running pods

kubectl describe pod <pod>

Describe one pod

kubectl expose pod <pod> --port=444 --name=frontend

Expose the pod of a pod (creates a new service)

kubectl port-forward <pod> 8080

Pod forward the exposed pod port to your local machine

kubectl attach <pod> -i

Attach to the pod

kubectl exec <pod> -- <command>

Execute a command on the pod

kubectl label pod <pod> mylabel=awesome

Add a new label to a pod

kubectl run -i --tty busybox --image=busybox --restart=never -- sh

Run a shell in a pod, very useful for debugging

Replication and Deployment

$ kubectl scale --replicas=3 -f helloworld-replica.yml
replicationcontroller "helloworld-controller" scaled

$ kubectl get pod
NAME                          READY     STATUS    RESTARTS   AGE
helloworld-controller-2v2x1   1/1       Running   0          13m
helloworld-controller-hgn9s   1/1       Running   0          3m
helloworld-controller-r2vkh   1/1       Running   0          5s

Replication Controller

Scaling

Our First App

To replicate our example app 2 times:

apiVersion: v1
kind: ReplicationController
metadata:
  name: helloworld-controller
spec:
  replicas: 2
  selector:
    app: helloworld
  template:
    metadata:
      labels:
        app: helloworld
    spec:
      containers:
      - name: k8s-demo
        image: wardviaene/k8s-demo
        ports:
        - name: nodejs-port
          containerPort: 3000

Create pods

$ kubectl create -f helloworld-replica.yml
replicationcontroller "helloworld-controller" created

$ kubectl get pod
NAME                          READY     STATUS    RESTARTS   AGE
helloworld-controller-2v2x1   1/1       Running   0          9s
helloworld-controller-8bdf3   1/1       Running   0          9s

If one pod is failed, replication controller will automatically restore.

$ kubectl delete pod helloworld-controller-8bdf3
pod "helloworld-controller-8bdf3" deleted

$ kubectl get pod
NAME                          READY     STATUS              RESTARTS   AGE
helloworld-controller-2v2x1   1/1       Running             0          10m
helloworld-controller-8bdf3   1/1       Terminating         0          10m
helloworld-controller-hgn9s   0/1       ContainerCreating   0          3s

You can also specify number of replicas.

$ kubectl scale --replicas=3 -f helloworld-replica.yml
replicationcontroller "helloworld-controller" scaled

$ kubectl get pod
NAME                          READY     STATUS    RESTARTS   AGE
helloworld-controller-2v2x1   1/1       Running   0          13m
helloworld-controller-hgn9s   1/1       Running   0          3m
helloworld-controller-r2vkh   1/1       Running   0          5s

You can see status of the Replication Controller using kubectl get rc

$ kubectl get rc
NAME                    DESIRED   CURRENT   READY     AGE
helloworld-controller   3         3         3         14m

Finally, delete the app.

$ kubectl delete rc/helloworld-controller
replicationcontroller "helloworld-controller" delete

Deployments

Replication Set

Deployment Object

Example Deployment

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: helloworld-deployment
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: helloworld
    spec:
      containers:
      - name: k8s-demo
        image: wardviaene/k8s-demo
        ports:
        - name: nodejs-port
          containerPort: 3000

Useful Commands

Command Description
kubectl get deployments Get information on current deployments
kubectl get rs Get information about the replica sets
kubectl get pods --show-labels Get pods, and also show labels attached to those pods
kubectl rollout status deployment/helloworld-deployment Get deployment status
kubectl set image deployment/helloworld-deployment k8s-demo=k8s-demo:2 Run k8s-demo with the image label version 2
kubectl rollout status deployment/helloworld-deployment Get the status of the rollout
kubectl rollout history deployment/helloworld-deployment Get the rollout history
kubectl rollout undo deployment/helloworld-deployment Rollback to previous version
kubectl rollout undo deployment/helloworld-deployment --to-revision=n Rollback to any version

Demo

Deploy Version 1

$ kubectl create -f helloworld-deploy.yml
deployment "helloworld-deployment" created

$ kubectl get deployment
NAME                    DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
helloworld-deployment   3         3         3            3           2m

$ kubectl get rs
NAME                               DESIRED   CURRENT   READY     AGE
helloworld-deployment-4153696333   3         3         3         2m

$ kubectl get pod --show-labels
NAME                                     READY     STATUS    RESTARTS   AGE       LABELS
helloworld-deployment-4153696333-17vqp   1/1       Running   0          3m        app=helloworld,pod-template-hash=4153696333
helloworld-deployment-4153696333-ltxvt   1/1       Running   0          3m        app=helloworld,pod-template-hash=4153696333
helloworld-deployment-4153696333-zbtn7   1/1       Running   0          3m        app=helloworld,pod-template-hash=4153696333

$ kubectl rollout status deployment/helloworld-deployment
deployment "helloworld-deployment" successfully rolled out

Expose and Test Version 1

$ kubectl expose deployment helloworld-deployment --type=NodePort
service "helloworld-deployment" exposed

$ kubectl get service
NAME                    CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
helloworld-deployment   10.0.214.139   <nodes>       3000:31616/TCP   37s
kubernetes              10.0.0.1       <none>        443/TCP          9d

$ kubectl describe service helloworld-deployment
Name:				helloworld-deployment
Namespace:			default
Labels:				app=helloworld
Annotations:		<none>
Selector:			app=helloworld
Type:				NodePort
IP:					10.0.214.139
Port:				<unset>	3000/TCP
NodePort:			<unset>	31616/TCP
Endpoints:			10.244.1.11:3000,10.244.1.12:3000,10.244.1.13:3000
Session Affinity:	None
Events:				<none>

$ curl http://10.0.214.139:3000
Hello World!

Upgrade to Version 2

$ kubectl set image deployment/helloworld-deployment k8s-demo=wardviaene/k8s-demo:2
deployment "helloworld-deployment" image updated

$ kubectl rollout status deployment/helloworld-deployment
deployment "helloworld-deployment" successfully rolled out

$ curl http://10.0.214.139:3000
Hello World v2!

$ kubectl get pod
NAME                                     READY     STATUS        RESTARTS   AGE
helloworld-deployment-4153696333-zbtn7   0/1       Terminating   0          15m
helloworld-deployment-521624165-3jftr    1/1       Running       0          36s
helloworld-deployment-521624165-469mg    1/1       Running       0          46s
helloworld-deployment-521624165-pt5f7    1/1       Running       0          46s

$ kubectl rollout history deployment/helloworld-deployment
deployments "helloworld-deployment"
REVISION	CHANGE-CAUSE
1		<none>
2		<none>

Rollback to Version 1

$ kubectl rollout undo deployment/helloworld-deployment
deployment "helloworld-deployment" rolled back

$ kubectl rollout status deployment/helloworld-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for rollout to finish: 1 old replicas are pending termination...
Waiting for rollout to finish: 1 old replicas are pending termination...
Waiting for rollout to finish: 1 old replicas are pending termination...
Waiting for rollout to finish: 2 of 3 updated replicas are available...
deployment "helloworld-deployment" successfully rolled out

$ kubectl get pod
NAME                                     READY     STATUS        RESTARTS   AGE
helloworld-deployment-4153696333-d0jv7   1/1       Running       0          10s
helloworld-deployment-4153696333-w5dhs   1/1       Running       0          15s
helloworld-deployment-4153696333-x62bk   1/1       Running       0          15s
helloworld-deployment-521624165-3jftr    1/1       Terminating   0          1m
helloworld-deployment-521624165-469mg    1/1       Terminating   0          1m
helloworld-deployment-521624165-pt5f7    1/1       Terminating   0          1m

$ kubectl rollout history deployment/helloworld-deployment
deployments "helloworld-deployment"
REVISION	CHANGE-CAUSE
2		<none>
3		<none>

Services

Services

Example Service

apiVersion: v1
kind: Service
metadata:
  name: helloworld-service
spec:
  ports:
  - port: 31001
    nodePort: 31001
    targetPort: nodejs-port
    protocol: TCP
  selector:
    app: helloworld
  type: NodePort

Demo

Create Pod

$ kubectl create -f pod-helloworld.yml
pod "nodehelloworld.example.com" created

$ kubectl get pod
NAME                         READY     STATUS    RESTARTS   AGE
nodehelloworld.example.com   1/1       Running   0          9s

$ kubectl describe pod nodehelloworld.example.com
Name:		nodehelloworld.example.com
Namespace:	default
Node:		k8s-agent-7d111633-0/10.240.0.4
Start Time:	Mon, 07 May 2018 13:55:55 +0000
Labels:		app=helloworld
Annotations:	<none>
Status:		Running
IP:		10.244.1.10
Containers:
  k8s-demo:
    Container ID:	docker://88b44009636f515c1af43c940fa7b351ba7e772af7d8fe71e572a8cdba0d6505
    Image:		wardviaene/k8s-demo
    Image ID:		docker-pullable://wardviaene/k8s-demo@sha256:2c050f462f5d0b3a6430e7869bcdfe6ac48a447a89da79a56d0ef61460c7ab9e
    Port:		3000/TCP
    State:		Running
      Started:		Mon, 07 May 2018 13:55:59 +0000
    Ready:		True
    Restart Count:	0
    Environment:	<none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-lr576 (ro)
Conditions:
  Type		Status
  Initialized 	True
  Ready 	True
  PodScheduled 	True
Volumes:
  default-token-lr576:
    Type:	Secret (a volume populated by a Secret)
    SecretName:	default-token-lr576
    Optional:	false
QoS Class:	BestEffort
Node-Selectors:	<none>
Tolerations:	<none>
Events:
  FirstSeen	LastSeen	Count	From				SubObjectPath			Type		Reason			Message
  ---------	--------	-----	----				-------------			--------	------			-------
  37s		37s		1	default-scheduler						Normal		Scheduled		Successfully assigned nodehelloworld.example.com to k8s-agent-7d111633-0
  37s		37s		1	kubelet, k8s-agent-7d111633-0					Normal		SuccessfulMountVolume	MountVolume.SetUp succeeded for volume "default-token-lr576"
  36s		36s		1	kubelet, k8s-agent-7d111633-0	spec.containers{k8s-demo}	Normal		Pulling			pulling image "wardviaene/k8s-demo"
  33s		33s		1	kubelet, k8s-agent-7d111633-0	spec.containers{k8s-demo}	Normal		Pulled			Successfully pulled image "wardviaene/k8s-demo"
  33s		33s		1	kubelet, k8s-agent-7d111633-0	spec.containers{k8s-demo}	Normal		Created			Created container
  32s		32s		1	kubelet, k8s-agent-7d111633-0	spec.containers{k8s-demo}	Normal		Started			Started container

Create Service

$ cat helloworld-service.yml
apiVersion: v1
kind: Service
metadata:
  name: helloworld-service
spec:
  ports:
  - port: 31001
    nodePort: 31001
    targetPort: nodejs-port
    protocol: TCP
  selector:
    app: helloworld
  type: NodePort

$ kubectl create -f helloworld-service.yml
service "helloworld-service" created

$ kubectl get svc
NAME                 CLUSTER-IP     EXTERNAL-IP   PORT(S)           AGE
helloworld-service   10.0.240.190   <nodes>       31001:31001/TCP   12s
kubernetes           10.0.0.1       <none>        443/TCP           15d

$ kubectl describe svc helloworld-service
Name:			helloworld-service
Namespace:		default
Labels:			<none>
Annotations:		<none>
Selector:		app=helloworld
Type:			NodePort
IP:			10.0.240.190
Port:			<unset>	31001/TCP
NodePort:		<unset>	31001/TCP
Endpoints:		<none>
Session Affinity:	None

Re-create Service

$ kubectl delete svc helloworld-service
service "helloworld-service" deleted

$ kubectl create -f helloworld-service.yml
service "helloworld-service" created

$ kubectl describe svc helloworld-service
Name:			helloworld-service
Namespace:		default
Labels:			<none>
Annotations:		<none>
Selector:		app=helloworld
Type:			NodePort
IP:			10.0.166.250
Port:			<unset>	31001/TCP
NodePort:		<unset>	31001/TCP
Endpoints:		<none>
Session Affinity:	None

Please note that the virtual IP address is changed.