KIA CH02 First Steps with Docker and Kubernetes

Hi there.

Today, let us read the Chapter 02: First Steps with Docker and Kubernetes (Part I: Overview) of Kubernetes in Action

  1. Creating, running, and sharing a container image with Docker
  2. Running a single-node Kubernetes cluster locally, with Minikube
  3. Setting up and using the kubectl command-line client
  4. Deploying an app on Kubernetes and scaling it horizontally

2.1 Creating, running, and sharing a container image

2.1.1 Running a Hello World container

busybox is a single executable that combines many of the standard UNIX command-line tools, such as echo, ls, gzip, etc.

docker run command will download and run the container.

1# docker run <image>:<tag>
2docker run busybox echo "Hello world"
  1. Docker checks the presence of image busybox:latest in local machine. (image = busybox, tag is latest by default)
    • If yes, continue to step 2
    • If no, Docker pulls it from Docker Hub registry (locally stored layer will NOT be pulled)
  2. Docker creates a container from that image
  3. Docker runs the echo "Hello world" command inside container

Then the app was executed inside a container, completely isolated from all the other processes running on your machine.

2.1.2-7 Running a Node.js container

  1. write the app Use the following code to start an HTTP server on port 8080, and then to reply with "You've hit " + ${actual hostname of a server} and status code 200 OK:

     1// app.js - a demo Node.js app
     2const http = require('http');
     3const os = require('os');
     4
     5console.log("kubia server starting...");
     6
     7var handler = function(request, response) {
     8console.log("Received request from " + request.connection.remoteAddress);
     9response.writeHead(200);
    10response.end("You've hit " + os.hostname() + "\n");
    11};
    12
    13var www = http.createServer(handler);
    14www.listen(8080);
    
  2. write Dockerfile

    A Dockerfile is a text file that contains all the commands to build a given image. And in the Dockerfile, each individual command creates a new layer. The topmost layer a Dockerfile created will be tagged as latest.

    Use the following Dockerfile to package the app into an image:

    1FROM node:7
    2ADD app.js /app.js
    3ENTRYPOINT ["node", "app.js"]
    
    • the FROM to define the base image(image as a starting point)
    • the ADD to add your app.js file from your local directory into the root directory in the image, creating a new layer on top of the base image
    • the ENTRYPOINT command denotes that command node app.js will be executed upon starting the image, thereby creating an additional layer on top of the newly created layer, which will then be tagged as latest.
  3. build the container image

    use "docker build" command to build the container image out of Dockerfile by pulling/creating layers:

    1docker build -t kubia .
    
    • image name: kubia
    • where is the Dockerfile located: '.' (the current working directory)
    @startuml
    actor developer
    usecase "Docker client" as client
    usecase "Docker daemon" as daemon
    database "Docker Hub" as hub
    node "output image" as output
    
    developer-->client:    1. runs `docker build kubia .`
    client-->daemon:       2. uploads 'Dockerfile' and 'app.js'
    daemon<--hub:          3. pulls layers of base image if not stored locally
    daemon-->output:       4. builds new image
    @enduml
    

    Use "docker images" to list the newly built Docker image:

    REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
    kubia latest d30ecc7419e7 1 minute ago 637.1 MB
  4. run the container

    Use "docker run" to run the image

    1docker run --name kubia-container -p 80:8080 -d kubia
    
    • name of container is kubia-container, started from image named kubia
    • -d: container is detached from console
    • -p 80:8080: the container's internal port 8080 is mapped to port 80 on the host
    • the 'kubia' app can thus be accessed by http://localhost:80

    Use "docker ps" to list running containers

    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    44d76963e8e1 kubia:latest "/bin/sh -c 'node app.js' 6 minutes ago Up 6 minutes 0.0.0.0:80->8080/tcp kubia-container

    Use "docker inspect" to see detailed info about the docker container in JSON

    Use "docker exec" to run the shell inside the container

    1docker exec -it kubia-container /bin/bash
    
    • -i: makes sure STDIN is kept open
    • -t: allocates a pseudo terminal (TTY)

    Now that the app is running inside the container 44d76963e8e1, we can find it by running ps aux.

    • isolated process tree
      • PID Linux namespace
      • processes running in the container are running in the host OS, but with different process ID (PID)
    • isolated filesystem
  5. stops and removes a container Use "docker stop" to stop a docker

    1docker stop kubia-container
    

    Use "docker rm" to remove the container that still exists in "docker ps -a" command

    1docker rm kubia-container
    

2.1.8 Pushing the image to an image registry

To make the local image (e.g., the image built in section 2.1.4) available to others, we need to push the image to an external image registry.

There are 3 steps to push image to registry

  1. Tag an image

    Use "docker tag" to create an additional tag for the same image

    1docker tag kubia ${yourDockerRegistryID}/kubia
    
  2. Log into Docker Registry

    Use "docker login" to log into the Docker Hub Registry, or other Registries by:

    1docker login -u username ${DockerRegistryURL}
    
  3. Push the image Use "docker push" to push image to Registry

    1docker push ${yourDockerRegistryID}/kubia
    

2.2 Setting up a Kubernetes cluster

2.2.1 Running a local single-node Kubernetes cluster with Minikube

Minikube is a tool that sets up a single-node cluster for both testing Kubernetes and developing apps locally.

The minikube documentation provides detailed installation guide. For example, I will use Debian package in x64-based Ubuntu Linux:

1curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube_latest_amd64.deb
2sudo dpkg -i minikube_latest_amd64.deb
  1. start a Kubernetes cluster with Minikube

    1minikube start
    
  2. display cluster info

    1minikube kubectl -- cluster-info
    

    to explore inside cluster, use minikube ssh to log into the Minikube VM with SSH

2.2.2 Using a hosted Kubernetes cluster of Cloud Provider

kubectl is a command line tool for communicating with control plane of a Kubernetes cluster, using the Kubernetes API.

Use kubectl to list all nodes in the cluster

  • "kubectl cluster-info": to show basic information about a Kubernetes cluster
  • "kubectl get nodes": to show the most basic information for the listed objects
  • "kubectl describe node ${nameOfNode}": to show the status of a specific node, including CPU and memory data, system information, containers running on the node, etc.

Kubernetes uses Pod to deal with a group of containers (rather than dealing with individual containers directly)

  • same pod: containers running on the same logical machine
  • different pod: containers running on the different logical machines, even these containers are running on the same worker node

Each pod behaves like a separate independent machine with its own IP address and hostname.

@startuml
caption "Figure 2.1   The relationship among containers, pods, and physical worker nodes"
node "Worker node 1" {

    rectangle "Pod 1 (IP: 10.1.0.1)" {
        rectangle "Container" as c11
    }
    rectangle "Pod 2 (IP: 10.1.0.2)" {
        rectangle "Container 1" as c21
        rectangle "Container 2" as c22
    }
    rectangle "Pod 3 (IP: 10.1.0.3)" {
        rectangle "Container" as c31
    }
}
node "Worker node 2" {
    rectangle "Pod 4 (IP: 10.1.1.1)" {
        rectangle "Container 1" as c41
        rectangle "Container 2" as c42
    }
    rectangle "Pod 5 (IP: 10.1.1.2)" {
        rectangle "Container" as c51
    }
}
@enduml

Use "kubectl get pods" to show basic info about pods, and use "kubectl describe pod".

2.3 Running your first app on Kubernetes

This section require experiments on cloud platform, and the minikube may not fit this section, because the minikube does not support "LoadBalancer" service.

2.3.1 Deploying your Node.js app

Use "kubectl create deployment" to create a deployment

1kubectl create deployment kubia --image=${YourDockerRegistryId}/kubia --port=8080
2# (deprecated): kubectl run kubia --image=${YourDockerRegistryId}/kubia --port=8080 --generator=run/v1

The diagram shown below is about how a container runs in Kubernetes:

@startuml
caption "Figure 2.2  Running container in Kubernetes"

actor "Developer" as dev

node "Local dev machine" as local {
    rectangle "Docker" as localDocker
    rectangle "kubectl" as localKubectl #BAC3E2
}
rectangle "Kubernetes Cluster" as cluster {
    node "Control Plane(Master) Node" as master {
        rectangle "Scheduler" as masterScheduler
        rectangle "RESTful API Server" as apiServer #BAC3E2
    }
    rectangle "Worker Nodes" as workers {
        node "worker-1" {
            rectangle "Docker" as Docker1 #BAC3E2
            rectangle {
                rectangle "Kubelet" as Kubelet1 #BAC3E2
                rectangle "Kube-proxy" as proxy1
            }
        }
        node "worker-2" {
            rectangle " " as Docker2 #BAC3E2
            rectangle {
                rectangle " " as Kubelet2 #BAC3E2
                rectangle " " as proxy2
            }
        }
        node "worker-3" {
            rectangle " " as Docker3 #BAC3E2
            rectangle {
                rectangle " " as Kubelet3 #BAC3E2
                rectangle " " as proxy3
            }
        }
    }
}
database "Docker Hub" as hub

dev-->localDocker:        1. <u>docker push</u>
localDocker-->hub:        2. <u>push image</u>
dev-->localKubectl:       3. <u>kubectl run</u>
localKubectl-->apiServer: 4. <u>issues REST call</u>
note top of master:       5. <u>creates Pod and schedules to Worker node</u>
master-->Kubelet1:        6. <u>notifies Kubelet</u>
Kubelet1-->Docker1:       7. <u>instructs Docker to run image</u>
Docker1<--hub:            8. <u>pulls and runs image</u>
@enduml
  • what happens behind the scene:
    1. kubectl will create a new ReplicationController object in the cluster, by sending a REST HTTP request to the Kubernetes API server
    2. ReplicationController then creates a new pod
    3. Scheduler schedules the new pod to one of the worker nodes
    4. kubelet of the scheduled worker node instructs Docker to pull image from registry if the specified image is not available locally
    5. Docker creates and runs the container

2.3.2 Accessing your web application

There are 4 types of services:

  • ClusterIP: A regular service, just like the pod, is only accessible from inside the cluster.
  • NodePort
  • LoadBalancer: creating an external load balancer, and allowing external access to the pod via the public IP
    • Note: Minikube does NOT support LoadBalance service
  • ExternalName

And this section mainly covers how to use LoadBalancer service to allow external access of the Node.js app:

  1. Use "kubectl create" to create a LoadBalancer service
1kubectl expose deployment kubia --type=LoadBalancer --name kubia-http
2# (deprecated): kubectl expose rc kubia --type=LoadBalancer --name kubia-http
  • minikube does NOT support LoadBalance service, so the "EXTERNAL-IP" of command "kubectl get services" will always be pending.
  • "Replication Controller" has been deprecated in newer version of kubectl
  1. Use "kubectl get services" command to see the newly created Service object

    • cluster IP
    • external IP
    • port
    • age
  2. Access the service via external IP

2.3.3 The logical parts of your system

There are 2 views of the system:

  • Physical view
    • Worker nodes (or nodes)
    • Control Plane (Master) node(s)
  • Logical View
    • Pods
      • each pod has its unique private IP address
    • ReplicationControllers
      • replicate pods to the exactly number of replicas(by default: 1 replica).
      • monitor and keep pods running
        • creating a new pod to replace the missing one
    • Services
      • expose multiple pods at a single constant <IP, port> pair
      • offer a static IP to forward user requests to pods, regardless of IP addresses of pods
@startuml
left to right direction
caption "Figure 2.3  System consists of 1 ReplicationController, 1 Pod, and 1 Service"
actor User
interface Service #BAC3E2
note top of Service
    Service: kubia-http-service
    Internal IP: 10.3.246.185
    External IP: 104.155.74.57
end note
component "Pod" as Pod{
    rectangle Container
}
note top of Pod
    name: kubia-pod
    IP: 10.1.0.1
end note
rectangle ReplicationController
note top of ReplicationController
    name: kubia
    Replicas: 1
end note
User-->Service: requests
Service-->Container: Port 8080
Pod<--ReplicationController
@enduml

2.3.4 Horizontally scaling the application

To scale up the number of pods:

  1. use "kubectl get rc" or "kubectl get replicationcontrollers" to get the number of replicas

    NAME DESIRED CURRENT AGE
    kubia 1 1 17m
    • DESIRED: the number of pod replicas you want the ReplicationController to keep
    • CURRENT: the actual number of pods currently running
  2. use "kubectl scale rc kubia --replicas=3" command to increase the DESIRED replica count of kubia to 3

    • inspect with "kubectl get rc"
      NAME DESIRED CURRENT READY AGE
      kubia 1 1 2 17m
    • inspect with "kubectl get pods"
      NAME READY STATUS RESTARTS AGE
      kubia-hczji 1/1 Running 0 7s
      kubia-iq9y6 0/1 Pending 0 7s
      kubia-4jfyf 1/1 Running 0 18m
@startuml
left to right direction
caption "Figure 2.4  System consists of 1 ReplicationController, 3 Pods, and 1 Service"
actor User
interface Service #BAC3E2
note top of Service
    Service: kubia-http-service
    Internal IP: 10.3.246.185
    External IP: 104.155.74.57
end note
component "Pod" as Pod1
component "Pod" as Pod2
component "Pod" as Pod3
note top of Pod1
    name: kubia-hczji
    IP: 10.1.0.1
end note
note top of Pod2
    name: kubia-iq9y6
    IP: 10.1.0.2
end note
note bottom of Pod3
    name: kubia-4jfyf
    IP: 10.1.0.3
end note
rectangle ReplicationController
note top of ReplicationController
    name: kubia
    Replicas: 3
end note
User-->Service: requests
Service-->Pod1: Port 8080
Service-->Pod2: Port 8080
Service-->Pod3: Port 8080
Pod1<--ReplicationController
Pod2<--ReplicationController
Pod3<--ReplicationController
@enduml

2.3.5 Examining what nodes your app is running on

In general, there is no need to care about which worker node a pod is scheduled to, yet there is still a way to find out:

  1. use "kubectl get pods -o wide" command to show list pods with IP and worker node

    • -o wide option is used to display additional columns based on "kubectl get pods" command
  2. use "kubectl describe pod kubia-hczji" command to check which worker node the pod kubia-hczji is scheduled to.

2.3.6 Kubernetes dashboard

Kubernetes dashboard allows you to list all the Pods, ReplicationControllers, Services, and other objects deployed in your cluster, as well as to create, modify, and delete them.

Use "minikube dashboard" to open Kubernetes Dashboard in browser.

1# 1) start Minikube Dashboard
2minikube dashboard
3
4# 2) add proxy to allow external access (outside VM where Minikube is installed)
5kubectl proxy --port=8001 --address='0.0.0.0' --accept-hosts='^.*' &

Then dashboard can be accessed by:

1http://${YourVmAddress}:8001/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/

2.4 Summary

In this chapter, there are mainly two topics:

  • how to run a app in Docker, and then package it into a Docker Image
  • how to use Kubernetes with Minikube and kubectl
    • single node Kubernetes cluster
    • scaling the Kubernetes cluster
      • one control plane node
      • three worker nodes

2.5 Appendix: best practice

Original Abbreviation
pods po
replication controller(deprecated) rc
services svc

best practice about alias in Linux terminal:

1# use `k` instead of `kubectl`
2alias k=kubectl

Note: if you use k instead of kubectl, you should execute the following commands to enable command autocompletion:

1# 1) allow command completion for `kubectl`
2source <(kubectl completion bash)
3
4# 2) allow command completion for `k`
5source <(kubectl completion bash | sed s/kubectl/k/g)

* This blog was last updated on 2024-05-11 20:16