2022年7月17日 星期日

Kubernetes in Practice 筆記 (in progress)

Kubernetes Architecture

Source: https://www.educative.io/courses/kubernetes-in-practice

  • Load Balance
  • Configuration
  • Secrets
  • Schedule Tasks

A Kubernetes Cluster

Introduction to Kubernetes cluster

  • Master node
  • Worker nodes


The workers

每個 worker node 上面可以有多個 container app

The master

Mater node 主要是負責管理這個 cluster 用的,負責執行 Kubernetes API 管理 cluster 的任務

當我們透過 kubectl 去 apply manifest file (yaml file),則 Master Node,就會將我們想要的狀態完成,它會去比對現在Cluster的狀態,然後作對應的調整。




Kubectl

kubectl 是 Kubernetes CLI 的工具,是用來溝通 Kubernetes cluster 的介面。這工具有兩種用法:
  • Declarative way 用 mainifest 來定義想要什麼,讓 master 來管理並達成。
  • Imperative way 則是我們直接指定應該用什麼順序來執行。

Pods

Introduction to pods

Pod 是 Kubernetes 中處理 schedule 時的最基本的 atomic 的單位。每一個 Pod 可以有多個 containers






Pods are the atomic unit of scheduling

Pod 是處理 schedule 的 atomic unit 的意思是指,當我們想要 scale 我們的 application 時,我們並不是在一個 pod  裡面生出很多的 container。而是建立多個 Pods 每個 pod 裡面都有一個 container 執行著一個 application。 

下面是一個 manifest 的 nginx.yaml範例,定義了一個 Pod 叫做 nginx,裡面有一個 container 叫做 nginx-container。

當我們執行 kubectl apply -f nginx.yaml 指令後,在執行 kubectl get pods 我們可以看到一個叫做 nginx 的 pod 跑起來了,狀態是 Running 情形。


Different between pods and containers

實際上,一個 pod 裡面可以有多個 containers,在大型系統裡面一個 pod 可能會是一個商業邏輯的應用服務,裡面可能同時包含了不同的containers來達到這個商業邏輯提供的服務。


Playing with Running Pods

  • Streaming logs
  • Executing commands
  • Killing pods

Streaming logs

執行 apply 以及 port-forward 後,已經啟動 nginx 並且 mapping 到 host 的 port 80
kubectl apply -f nginx.yaml

nohup kubectl port-forward --address 0.0.0.0 nginx 3001:80 > /dev/null 2>&1 &

如果要檢視 nginx 的 access logs 可以用以下的指令
kubectl logs --follow nginx

請參考以下範例

root@5c57b40d9e8f850e:/usercode# nohup kubectl port-forward --address 0.0.0.0 nginx 3001:80 > /dev/null 2>&1 &
[1] 6293
root@5c57b40d9e8f850e:/usercode# kubectl logs --follow nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2022/07/03 02:22:00 [notice] 1#1: using the "epoll" event method
2022/07/03 02:22:00 [notice] 1#1: nginx/1.23.0
2022/07/03 02:22:00 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6) 
2022/07/03 02:22:00 [notice] 1#1: OS: Linux 4.19.197
2022/07/03 02:22:00 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2022/07/03 02:22:00 [notice] 1#1: start worker processes
2022/07/03 02:22:00 [notice] 1#1: start worker process 32
2022/07/03 02:22:00 [notice] 1#1: start worker process 33
127.0.0.1 - - [03/Jul/2022:02:22:56 +0000] "GET / HTTP/1.1" 200 615 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36" "-"


Executing commands

如果想要在指定的 pod 上單純執行指令,請參考以下的範例,在指定的 pod nginx 上執行 ls 指令

kubectl exec nginx -- ls

如果想要在指定的 pod 上執行互動的指令,請參考以下的範例,在指定的 pod nginx 上執行 -it 的參數去bash 指令

kubectl exec -it nginx -- bash

Killing pods

以下兩種都是刪除 pods 的方式
kubectl delete pod nginx

kubectl delete -f nginx.yaml

Deployments

Defining Our Deployment Manifest

以下是一個 deployment.yaml 的簡單範例

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellok8s
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hellok8s
  template:
    metadata:
      labels:
        app: hellok8s
    spec:
      containers:
      - image: brianstorti/hellok8s:v1
        name: hellok8s-container

解釋說明

  • kind - 告訴 Kubernetes 這是一個 Deployment 的 object
  • metadata.name - 只需要定義一個有意義的名字來表示這個 Deployment
  • spec - 這個 section 主要是定義這個 Deployment 要做什麼事情
    • selector 裡面的 matchLabels 定義了這個 Deployment 將會管理那些 label為 app 的值為 hellok8s 的 pods。
    • template 則是用來定義,哪些 pods 會跑什麼 docker image。  

Apply 並檢視 Deployments 和 Pods 的狀態

kubectl apply -f deployment.yaml
# deployment.apps/hellok8s created

kubectl get deployments

# NAME       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE
# hellok8s   1         1         1            1  

kubectl get pods

# NAME                        READY   STATUS    RESTARTS
# hellok8s-6678f66cb8-42jtr   1/1     Running   0

請注意這邊的 NAME 所顯示的 hellok8s-6678f66cb8-42jtr這個名稱是會改變的。

Restarting Failed Pods

Kubernetes is always trying to ensure our desired state matches our current state.

當我們試著透過指令 kubectl delete pod 強制將某個 pod 刪除,則 Kubernetes 會立刻重起一個 pod 來保持當時這個 Deployment.yaml 所要求的狀態 replicas: 1 。請參考以下範例:

kubectl apply -f deployment.yaml

kubectl get pods      
# NAME                        READY   STATUS    RESTARTS
# hellok8s-6678f66cb8-42jtr   1/1     Running   0      


# Replace the pod name with your local pod name
kubectl delete pod hellok8s-6678f66cb8-42jtr
# pod "hellok8s-6678f66cb8-42jtr" deleted

kubectl get pods
# NAME                        READY   STATUS    RESTARTS  
# hellok8s-6678f66cb8-8nqf2   1/1     Running   0  


Scaling Up/Down Our Application

Scaling Up Our Application

前面的 Deployment.yaml 中有一個定義在 spec 的 section 叫做 replicas 可以用來控制我們如何 Scaling Up / Down 我們的 pods 的數量。請參考下面這個範例 scaleup.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellok8s
spec:
  replicas: 10
  selector:
    matchLabels:
      app: hellok8s
  template:
    metadata:
      labels:
        app: hellok8s
    spec:
      containers:
      - image: brianstorti/hellok8s:v1
        name: hellok8s-container

當我們執行以下的指令時,它將會產生10個新的pods。

kubectl apply -f scaleup.yaml

這邊要特別說明,在真正的 production 環境時,實際上可能會有多個 worker nodes 在我們的 cluster 之中,而 Kubernetes 會將這些 pods 配置在多個不同的 nodes 上,所以即使其中一個 node 失敗,它仍就會在其他的 nodes 上把 pods 生出來確保 application 能夠正常的 Scaling Up。

Scaling Down Our Application

下面是一個 scaledown.yaml 的範例,將 replicas 改為 2。
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellok8s
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hellok8s
  template:
    metadata:
      labels:
        app: hellok8s
    spec:
      containers:
      - image: brianstorti/hellok8s:v1
        name: hellok8s-container

當我們再次執行以下指令時,Kubernetes,將會把其他的 pods 刪除,只留下2個 pods

kubectl apply -f scaledown.yaml

kubectl get pods
# NAME                        READY   STATUS    
# hellok8s-6678f66cb8-8nqf2   1/1     Running    
# hellok8s-6678f66cb8-cmb4j   1/1     Running    
# hellok8s-6678f66cb8-6r7fb   1/1     Terminating
# hellok8s-6678f66cb8-7bg4s   1/1     Terminating
# hellok8s-6678f66cb8-96xh5   1/1     Terminating
# hellok8s-6678f66cb8-h5tg4   1/1     Terminating
# hellok8s-6678f66cb8-j2b5n   1/1     Terminating
# hellok8s-6678f66cb8-l5hzw   1/1     Terminating
# hellok8s-6678f66cb8-r9bzd   1/1     Terminating
# hellok8s-6678f66cb8-wl4bb   1/1     Terminating


Rolling Out Releases

Releasing new versions

假設我們已經有一個新的版本的 docker image 叫做 hellok8s:v2 的版本。那我們想要更新現在的pods 變成新版的,只需要去修改原本的 Deployment.yaml 的 spec 區塊中 image 的值如下:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellok8s
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hellok8s
  template:
    metadata:
      labels:
        app: hellok8s
    spec:
      containers:
      - image: brianstorti/hellok8s:v2
        name: hellok8s-container

接著我們如下指令,重新 apply 即可。
kubectl apply -f deployment.yaml

我們可以透過兩種指令來觀察 pods 的實際上的變化,第二種要安裝 watch 工具
  • kubectl get pods --watch
  • watch kubectl get pods

Rolling Update

整個 rolling update 的過程如下圖所示,在沒有設定RollingUpdate的策略時,實際上是先產生新版本的 pod 然後,刪除一個舊版本的pod ,它會持續將指定的 pod 數量生出來為止。


Controlling the Rollout Rate

當我們有超過100個pods要進行更新時候,我們需要設定參數讓整個rollout速度加快。其中一種strategy 叫做 rollingUpdate 可以用來設定這種需求。

maxSurge and maxUnavailable

  • maxSurge 用來指定最多可以超過指定的 replicas 多少個 pods。
  • maxUnavailable 用來指定最多只能個低於指定的 replicas 多少個 pods。
以下面這個範例來看,假設 replicas 指定的值是3,而maxSurge和maxUnavailable 分別都指定1。這時候在 rollout 過程中會保證我們最少有2個pods存在( replicas - maxUnavailable ),最多有4個pods存在 (replicas + maxSurge)。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellok8s
spec:
  strategy:
     rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  replicas: 3
  selector:
    matchLabels:
      app: hellok8s
  template:
    metadata:
      labels:
        app: hellok8s
    spec:
      containers:
      - image: brianstorti/hellok8s:v2
        name: hellok8s-container

maxSurge 和 maxUnavailable 可以直接指定數字或是百分比% 來設定,在預設的情況下,這兩個參數的預設值是 25%。

以下這張圖就是整個 rollout 過程中v1和v2 版本整個 rollout 的過程,由左到右完成。



Using a Different Rollout Strategy 

Using Recreate

前面一種rollingUpdate的strategry是採用並行的方式進行更新v1到v2的版本,但如果我們有需求是要一次性的確保在production環境上只有一個版本運作時,我們可以採用另一種strategy叫做 Recreate,請參考以下的範例:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellok8s2
spec:
  strategy:
    type: Recreate
  replicas: 3
  selector:
    matchLabels:
      app: hellok8s
  template:
    metadata:
      labels:
        app: hellok8s
    spec:
      containers:
      - image: brianstorti/hellok8s:v2
        name: hellok8s-container

這種strategy會有 downtime的問題存在,它整個 deployment的過程如下所示:




Dealing with Bad Releases

在整個 release deployment 過程當中難免會遇到,新 release 版本不如預期,可能無法正確運行。
Kubetnets 提供了兩種能夠讓我們中止 rollout 的方式。一種是直接透過指令去中止並且重新將舊版本的 pods 建回來,一種是透過設定檢驗新的 pods是否如預期的行為,再決定是否繼續將整個 rollout deployment 完成。

Manually blocking the bad release

當我們執行以下的 deployment 指令,它在 deployment 過程中開始 rollout update。
kubectl apply -f deployment.yaml

一旦我們發現有問題,我們可以手動的執行以下的指令。(hellok8s是在deployment.yaml的metadata中定義的名稱)
kubectl rollout undo deployment hellok8s

Automatically blocking the bad release

另一種方式,是透過設定兩種探測方式(readinessProbelivenessProbe),來檢查新版本的 pod 是否如預期的行為,在決定是否繼續 rollout 。請參考以下使用readinessProbe的範例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellok8s
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hellok8s
  template:
    metadata:
      labels:
        app: hellok8s
    spec:
      containers:
      - image: brianstorti/hellok8s:v2 # Still using v2
        name: hellok8s-container
        readinessProbe: # New readiness probe
          periodSeconds: 1
          successThreshold: 5
          httpGet:
            path: /
            port: 4567

它定義了每一秒都會送一個 GET 到指定的port 4567 的URL path: / 的位置,並且成功5次。這個新的 pod 才會被認定是運作正常的,它才會開始繼續往下去 update 新的 pod 。整個過程如下所示:




Keeping Applications Healthy with Liveness Probes

livenessProbe readinessProbe

前面提到在整個 rollout deployment 過程當中,我們可以透過指定測試某些API是否運作正常來決定 pod 是否如預期的行為,再繼續完成整個 deployment 的過程。一些比較特殊的情況是 pod 可能運行一段時間後,就會無法處理 request,這時候需要讓 Kubernets 重啟這些 pod。我們就可以用另一種 probe 方式叫做 livenessProbe,請參考如下的範例:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellok8s
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hellok8s
  template:
    metadata:
      labels:
        app: hellok8s
    spec:
      containers:
      - image: brianstorti/hellok8s:v2
        name: hellok8s-container
        readinessProbe:
          periodSeconds: 1
          successThreshold: 5
          httpGet:
            path: /
            port: 4567
        livenessProbe:
          httpGet:
            path: /
            port: 4567

這個做法實際上,就是每隔一段時間 Kubernetes 會呼叫這個指定的位置,看是否能夠呼叫成功。預設的時間是每10秒一次,如果呼叫失敗它就會重啟這個 pod。

另一種,我們也可以自訂 script 來驗證這個 pod 是否運行正常,請參考以下使用exec.command的範例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellok8s
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hellok8s
  template:
    metadata:
      labels:
        app: hellok8s
    spec:
      containers:
      - image: brianstorti/hellok8s:v2
        name: hellok8s-container
        readinessProbe:
          periodSeconds: 1
          successThreshold: 5
          httpGet:
            path: /
            port: 4567
        livenessProbe:
          exec:
            command:
              - check_health.sh

這個 check_health.sh 執行結束回傳 0 表示成功。

Services

Service 是 Kubernetes 的另一種 resource,用來提供一個存取 pods 的可信賴的端點。它的概念如下圖所示:

A Quick Example

下面是一個 service.yaml 簡單的範例:
apiVersion: v1
kind: Service
metadata:
  name: hellok8s-svc
spec:
  type: NodePort
  selector:
    app: hellok8s
  ports:
  - port: 4567
    nodePort: 30001

當我們執行以下的指令:
kubectl apply -f service.yaml
Kubernetes 將會建立如下圖的關係,使用者可以透過存取Port 30001,而這個會自動做 port-foward 到 match 的 label 為 app 值是 hellok8s 的 application。



我們可以利用下面的指令來檢視 service 的狀態
kubectl get service hellok8s-svc

# NAME           TYPE       CLUSTER-IP        PORT(S)      
# hellok8s-svc   NodePort   10.102.141.32     4567:30001

Service Types and ClusterIP

Service Types

前面用過 NodePort 的 Service, 它是直接開一個 Port  30001 在全部的 worker nodes 上,然後將這個 30001 的 request 導向指定 pods 的 port 4567去處理這個 request。當我們在撰寫 manifest 使用 service 時,如果沒有特別指定 spec.type 為 NodePort 的話,那預設就是 ClusterIP。 

ClusterIP

當我們需要在 cluster 內部,讓另一個 pod-b,來存取 pod-a的時候,我們會建立一個 service-a 的 spec.type指定為  ClusterIP 的 type,如以下的 nginx.yaml 範例:

apiVersion: v1
kind: Service
metadata:
  name: clusterip-svc
spec:
  type: ClusterIP # optional
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80

---

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
  - name: nginx-container
    image: nginx

當我們 Apply 這個 nginx.yaml 之後,它將會建立一個
# Apply these files
kubectl apply -f deployment.yaml
kubectl apply -f nginx.yaml


kubectl get service
# NAME            TYPE        CLUSTER-IP
# clusterip-svc   ClusterIP   10.105.223.250

這時候,這個 clusterip-svc的服務所提供的 IP 10.105.223.250 只能夠在這個 cluster 內部被存取。
所以當我們進入到原本的 hellok8s 其中一個 pod 時,我們可以直接去存取這個 IP 10.105.223.250。請參考以下範例:
# Replace the pod name to what you have running locally
kubectl exec -it hellok8s-6696859cbd-gmhcp -- sh

# Replace the service ip to what you have running locally
# We are now inside the hellok8s container
curl http://10.105.223.250:80
# nginx welcome page

NodePort and LoadBalancer

NodePort

NodePort 的運作方式,實際上是在每個 Worker Node上都開啟一個Port 30001。這個Port 30001是外部可以直接存取的,以下面這張圖為例子。
外部是可以透過 http://node1-ip:30001 或 http://node2-ip:30001 直接存取的


LoadBalancer

LoadBalancer 實際上是 NodePort 和 ClusterIP 所擴展的一種 Service Type。它直接提供了外部一個存取的端點,透過它將會自動地把 request 送到對應的 pods 去。如下面這張圖所示:


ExternalName

另一種 Service Type 叫做 ExternalName,這是一種用來定義給Cluster內部的 pods 去存取外部資源的端點位置,請參考下面這個 database.yaml 範例。

apiVersion: v1
kind: Service
metadata:
  name: db
spec:
  type: ExternalName
  externalName: my-db.company.com


這些 pods 只需要透過名稱 my-db.company.com 去存取外部的 database。而 dateabase.yaml 實際上就是建立一個 db service,提供了一個外部的名稱讓內部的 pods 能夠存取,而不需要知道它實際上的位置在哪裡。

Service Discovery

前面實際上,我們提過用 ClusterIP 這種 service type 來提供給 app-a 去存取 app-b 是透過 service-b 這種方式來存取。我們可以不用 hard-code 這個 app-b 的 IP。因此,無論 app-b 是否重新被建立,這個 app-a 都能夠用這個 ClusterIP來存取。但是,ClusterIP 實際上也是會改變的,當我們刪除這個 service-b ,再重新建立 service-b 時,它所綁定的 ClusterIP 也會改變。因此我們需要 Service Discovery 的功能。

Kubernetes 提供兩種不同的 Service Discovery 的方式:
  • DNS
  • Injected environment variables

DNS



Environment Variables


Ingress



Configuring Applications with Configmaps


Using Secrets for Sensitive Configs


Running Jobs


Organizing Resources with Namespaces


Managing Containers Resource Usage


Understanding the Kubeconfig File














Docker 筆記

Docker 架構







About Docker client

Docker client 實際上就是我們安裝好 Docker 後,提供給我們使用的 CLI command。他主要是負責透過Restful API 去呼叫 Docker daemon 來完成所需要的操作。

About Docker daemon

實際上是一個 dockerd 的服務程序,提供 Restful API (over gRPC) 給 Docker client進行操作,用來管理 Docker objects,包含 images, containers, networks 和 volumes。它也能夠用來和別的 Docker daemon 進行溝通來管理其他的 Docker services。

About containerd

containerd (docker-containerd)是一個 high-level 的 runtime 用來管理 container的整個生命週期。此外,還有一些額外的功能包含 下載(pulling) images, 建立 network interface,以及管理 low-level 的 runc的 instances。在啟動一個新的 container 的過程當中,containerd 並不是直接自己啟動一個新的 container。實際上,它是將需要的 Docker image 轉換成 OCI bundle,並且透過 runc 來使用這個 OCI bundle 來建立一個新的 container。當 container 這個 sub-process 啟動後,runc 就會離開。而 shim 會變成這個 container sub-process 的 parent process。

About shim

shim 是 container 的 parent process 用來負責維持 STDIN 和 STDOUT 的pipes是open的狀態, 因此當 Docker daemon 重啟時並不會造成 container 因為 pipes 被 close 後而被終止的情形。另外,shim 會負責回報目前 container的狀態給 Docker daemon。

About runc

TBD

Implementation on Linux


  • dockerd (the Docker daemon)
  • docker-containerd (containerd)
  • docker-containerd-shim (shim)
  • docker-runc (runc)

Containerd - https://github.com/containerd/containerd/releases

Docker images


Image Registries

用來存放 docker image 的位置,最常見的 registry 在 https://hub.docker.com/

可以用 docker info 指令來檢視  Registry:  所指定的位置
shell> docker info


Image Registries 可以有 1 個或個 多個 image repositories,而每個 repositories 可以有多個 images

Official repositories

Image Naming and Tagging

docker image pull <repository>:<tag>

以下指令是 從 top-level 的 repository 上面下載 tag 為 latest的 image

docker image pull alpine:latest
docker image pull redis:latest

請注意 tag 為 latest 並不代表一定就是最新的版本,具體要看那個 image 官方網站的版本

以下指令是 從 top-level 的 repository 的 namespace=tu-deom上面下載 tag 為 v2 的 image

docker image pull nigelpulton/tu-deom:v2

以下指令是透過 -a 參數,去指定下載全部的 tags 版本

docker image pull -a <repository>:<tag>

實際上 tag 只是一個任意的字串,具體的版號比對還是要看 IMAGE-ID


Images and Layers

在執行 docker image pull 時,會從 Layer-1 開始下載




利用下面這個指令可以檢視 image 的 layers

shell> docker image inspect ubuntu:latest

[ { "Id": "sha256:bd3d4369ae.......fa2645f5699037d7d8c6b415a10", "RepoTags": [ "ubuntu:latest" <Snip> "RootFS": { "Type": "layers", "Layers": [ "sha256:c8a75145fc...894129005e461a43875a094b93412", "sha256:c6f2b330b6...7214ed6aac305dd03f70b95cdc610", "sha256:055757a193...3a9565d78962c7f368d5ac5984998", "sha256:4837348061...12695f548406ea77feb5074e195e3", "sha256:0cad5e07ba...4bae4cfc66b376265e16c32a0aae9" ] } } ]

Pulling Images by Digest

由於 git tag 是可以修改的,或是說可以被 overwrite。因此,為了確保在做 pull image 時候確保下載的 docker image 是原本所預期的版本,我們可以透過以下指令來顯示該 image 的 DIGEST

shell> docker image ls --digests alpine
REPOSITORY   TAG       DIGEST                                                                    IMAGE ID       CREATED       SIZE
alpine       latest    sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f271ab1acc53015037c   e66264b98777   4 weeks ago   5.53MB


然後,利用以下命令來指定digest 做 docker image pull 
shell> docker image pull alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f271ab1acc53015037c

Multi-Architecture Images

Docker image 透過以下的結構來支援在單一個 docker image 同時支援不同平台架構(Linux on x64, Linux on PowerPC, Windows x64, Linux on different versions of ARM)

docker container run 時候,會先查找 Manifest List 中是否有對應支援的平台的 Manifest,如果有會先取得這個對應的 Manifest 然後才


檢視 manifest list 內容

shell> docker manifest inspect golang

{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests": [
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1796,
         "digest": "sha256:ea66badd7cf7b734e2484a1905b6545bd944ef3bdeea18be833db3e2219f1153",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1796,
         "digest": "sha256:d1faaf4c45064097d13d604c93724fa2b57f76223e31dc20091ee6aec5df65c6",
         "platform": {
            "architecture": "arm",
            "os": "linux",
            "variant": "v5"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1796,
         "digest": "sha256:2f6a38bfc8e43016a5f79d4d376e09bfd829566b20a40898798ba6a10b143269",
         "platform": {
            "architecture": "arm",
            "os": "linux",
            "variant": "v7"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1796,
         "digest": "sha256:47b9c8aa3f8b415aeca87a1e13353a611df4cf64a07e38f14b1bc9679d56a3b8",
         "platform": {
            "architecture": "arm64",
            "os": "linux",
            "variant": "v8"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1796,
         "digest": "sha256:08c3c6149e409134d1773f215c8f207866605c1bec4bf6763175b5cc22eafb53",
         "platform": {
            "architecture": "386",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1796,
         "digest": "sha256:9b853afe0ce8062bb0a1ca8babb72c0d5773bd14788f4471e2c9a7654745f01e",
         "platform": {
            "architecture": "mips64le",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1796,
         "digest": "sha256:726e75aa04bb7ed012e7bf9984b0a1fe25e1e0316e0f9245cc68b2bd0af5112c",
         "platform": {
            "architecture": "ppc64le",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1796,
         "digest": "sha256:9b1f50237df09f109696a52b6206d2d00692295bf675b649f8ed4fd6a9bcf31c",
         "platform": {
            "architecture": "s390x",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 3401,
         "digest": "sha256:cf9ffc87c29e1f1e72a74e714e58f00e55eed4c0acecf7fa5ea148cd46d8a5b2",
         "platform": {
            "architecture": "amd64",
            "os": "windows",
            "os.version": "10.0.20348.768"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 3401,
         "digest": "sha256:cbe8f5856e85ad134eec9e778abb3f4b50926e4a935afae9cace36adc3e6b58b",
         "platform": {
            "architecture": "amd64",
            "os": "windows",
            "os.version": "10.0.17763.3046"
         }
      }
   ]
}

How to build image support multi-arch 

export DOCKER_CLI_EXPERIMENTAL=enabled

docker buildx build --push --platform linux/arm/v7,linux/arm64/v8,linux/amd64 --tag your-username/multiarch-example:buildx-latest .

另一種方式去啟用 buildx 工具是去修改 config.json 如下
{ "experimental": true }

Reference:


Containers

一個 Image 可以用來建立多個 Containers 來執行。Containers 就是 Image 的一個 instance。

Inspecting docker container run

當執行這樣的指令時
shell> docker container run -it ubuntu:latest /bin/bash

實際上會做下面的事情:
  1. docker client 會將 command 的參數轉換後,呼叫 docker daemon 上面所提供的 API server (over gRPC)
  2. docker daemon 會接收到 command 的內容,並且查找 Docker host 的 local image repository是否有指定的 image
  3. 如果沒有,docker daemon 會去找 Docker Hub,如果有找到就會下載這個 image 存到 local image repository
  4. docker daemon 透過 containerd 和 runc 來用這個指定的 image 建立 container 並啟動

Stopping Containers Gracefully

應該要使用 docker container stop 來關閉 container,因為它會送出一個 SIGTERM 的訊號給正在執行的 main process。如果 main process 在10秒內沒有自己關閉,它將會收到一個 SIGKILL的訊號。

Self-Healing Containers with Restart Policies

  • always
  • unless-stopped
  • on-failed

shell> docker container run --name neversaydie -it --restart always alpine sh

shell> docker container run --name neversaydie -it --restart unless-stopped alpine sh

shell> docker container run --name neversaydie -it --restart on-failed alpine sh

unless-stopped

如果該 container 的狀態是 stopped 時,當重啟 docker daemon 時候,並不會自動啟動該 container

on-failure

這個 container 如果是因為收到 non-zero exit code 而離開時,它會自動被重啟


Containerizing an Application



  1. Start with your application code and dependencies.
  2. Create a Dockerfile that describes your app, its dependencies, and how to run it.
  3. Feed the Dockerfile into the docker image build command.
  4. Push the new image to a registry (optional).
    1. Docker login

    2. Docker tag <image_name> <your_docker_hub_username>/<image>:<version>

    3. Docker push <your_docker_hub_username>/<image>:<version>

  5. Run the container from the image.


Dockerfile

Dockerfile 檔名要完全大小寫一樣,以大寫D開頭,指令不分大小寫,但通常都是用大寫。
此外以下的指令都會建立一個新的 Layer
  • FROM
  • RUN
  • COPY




Containerize the Application

Building images

在提供 Dockerfile 的目錄執行以下指令來build一個新的 docker image 並且設定 tag 叫做 web:latest

shell> docker image build -t web:latest .

Tag the image

以下指令將 image 指定的tag,新增一個 tag

docker image tag <current-tag> <new-tag>

shell> docker image tag web:latest nigelpoulton/web:latest

Pushing images

shell> docker login --username {{username}} --password {{password}}
shell> docker image push nigelpoulton/web:latest

以上的指令,會是 push 到 Repository 為 nigelpoulton/web 的位置,而 image tag 為 latest

Build from Cache

docker image build 指令的處理過程會使用到 cache 的機制。它在處理 Dockerfile 時候,是根據每一個 layer 去檢查是否已經有存在的 layer 已經在 local cache 可以參考。因此,在撰寫 Dockerfile 時候,要盡量讓變動性大的 layer 放在越後面越好。如果不想要使用 cache 機制,在build docker image 時可以指定 --no-cache=true的參數。

Image history

檢視 image build 的歷史紀錄
shell> docker image history web:latest

Squash the Image

docker image build 指令加上參數 --squash 可以build出一個壓縮所有的 layers 變成只有一層 layer,因此這種方式是無法共享 layer的。


Docker 常用指令

檢查版本
shell> docker version

啟動docker daemon (MacOSX)
shell> open -a Docker

關閉 docker daemon (MacOSX)
shell> pkill -SIGUP -f  /Applications/Docker.app 'docker serve'

啟動docker daemon (Linux without systemd) 並檢查狀態
shell> sudo service docker start
shell> service docker status

啟動 docker daemon (Linux with systemd) 並檢查狀態
shell> sudo systemctl start docker
shell> systemctl  is-active docker


docker image 

Usage:  docker image COMMAND

Manage images


列出 images
shell> docker image list

列出 images 只有 tags 是 latest 的
shell> docker image list  --filter=reference="*:latest"

列出 images 用指定的格式輸出
shell> docker image list --format "{{.size}}" 
shell> docker image list --format "{{.Repository}}: {{.Tag}}: {{.Size}}"

下載 images
shell> docker image pull ubuntu:latest

Build image (在有Dockerfile的位置執行)
shell> docker image build -t image-name:latest . 

Build image 不使用 cache (在有Dockerfile的位置執行)
shell> docker image build --no-cache=true -t image-name:latest . 

Build image 不安裝 recommends 的 packages (如果Dockerfile有用到 apt-get install這種方式時)
shell> docker image build --no-install-recommends -t image-name:latest . 

從 Docker Hub 搜尋 repository name 指定 official 為 true 的限制 5 筆資料
shell> docker search  {repository-name} --filter "is-official=true" --limit 5 

從 Docker Hub 搜尋 repository name 指定 automated builds 為 true 的,
shell> docker search  {repository-name} --filter "is-automated=true"

刪除特定的 images
shell> docker image rm 02674b9cb179 02675b9cb180

刪除全部的 images
shell> docker image rm $(docker image ls -q) -f

docker container run (等同 docker service create)

Usage:  docker container run [OPTIONS]  IMAGE  [COMMAND]  [ARG...]

Run a command in a new container


啟動 container 互動模式, 執行指定的指令直到 /bin/bash 終止
shell> docker container run -it ubuntu:latest /bin/bash 

啟動 container 互動模式, 執行 sleep 10 秒, 然後關閉
shell> docker container run -it ubuntu:latest sleep 10 

啟動 container 為 daemon模式, 印出 container-id
shell> docker container run -d ubuntu:latest 


啟動 container 為 daemon模式, 並且 mount local 的 /Users/username/Desktop/ 目錄為 /desktop 的目錄,在 container 啟動後可以讀寫 /desktop的目錄
shell> docker run -it -v /Users/username/Desktop/:/desktop ubuntu:latest

啟動 container 為 daemon模式, 並且 mount local 的 /Users/username/Desktop/ 目錄為 /desktop 的目錄,在 container 啟動後只能唯讀 /desktop的目錄
shell> docker run -it -v /Users/username/Desktop/:/desktop:ro ubuntu:latest


啟動 container 為 daemon模式, 指定 container name 並且 publish container 8080 port 到 docker host 80 port
shell> docker container run -d  --name container-name --publish 8080:80  ubuntu:latest 

檢視正在執行的 containers
shell> docker container list  

檢視全部的 containers
shell> docker container list -a  

進入到 執行的container環境 
docker container exec <options> <container-name or container-id> <command/app>
shell> docker container exec -it ubuntu:latest /bin/bash 

Attach 到 container
shell> docker attach {container-id

停止 container
shell> docker stop {container-id or container-name

刪除 container
shell> docker rm {container-id or container-name} 

停止並刪除 container
shell> docker kill {container-id or container-name} 

docker logs

用來查找 指定的 application log
shell> docker logs -f {container-id} 

docker volume

建立 volume 名稱叫做 app_share_data
shell> docker volume app_share_data

列出 volume
shell> docker volume ls 

檢視特定的 volume 資訊
shell> docker volume inspect share_data 

docker system

檢視docker 已使用的空間大小
shell> docker system df 
TYPE                TOTAL               ACTIVE              SIZE                RECLAIMABLE
Images              36                  6                   12.19GB             11.48GB (94%)
Containers          18                  1                   28.31MB             28.06MB (99%)
Local Volumes       33                  0                   1.818GB             1.818GB (100%)
Build Cache         0                   0                   0B                  0B
而 total reclaimable 顯示有多少空間是可以回收的

回收 docker 所占用的空間
shell> docker system prune 

如果要回收特定的空間可以參考
  • docker container prune
  • docker images prune
  • docker network prune

docker network

列出 docker network interface 清單
shell> docker network list 

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
915fe26ffbc8        bridge              bridge              local
3ac2c0505d62        host                host                local
753c94184c7b        none                null                local
docker 內建支援三種 DRIVER:bridge, host, null,只有 bridge 是能夠設定的。

檢視 docker network interface
shell> docker inspect {network-id}


假設我們看到下面這兩個 container mysql webapp,我們想讓 webapp能夠存取 mysql。這時候,我們需要透過 docker run --link 的指令(--link name:alias)
shell> docker run --link "mysql:backenddb" webapp:latest 

當它執行成功後,我們可以在 webapp這個 container的 /etc/hosts 檔案中看到一筆 backenddb 的紀錄,它會有一個 docker 配置給 backenddb 的 IP address。

"Containers"{
            "029acceda0674c23a09c7f37b7c3af733cc295f429573e8ef266882d6fc3912a"{
                "Name""mysql",
                "EndpointID""f8184cbe809537ec80902323655c6f7ee9fd31940c601252f34fdede1d7201fd",
                "MacAddress""02:42:ac:11:00:03",
                "IPv4Address""172.17.0.3/16",
                "IPv6Address"""
            },
            "57c5fdd375b5e9e5f923fbc289623c3a12995559af501d9cab8be11e788893dc"{
                "Name""webapp",
                "EndpointID""af7da2a39e84006db2ecace93602acddaaa16b2e9603a6edf8c4d1a007c80e17",
                "MacAddress""02:42:ac:11:00:02",
                "IPv4Address""172.17.0.2/16",
                "IPv6Address"""
            }
        },


Configure Docker for TLS

會需要 以下的檔案

ca-key.pem << CA private key
ca.pem << CA public key (cert)
client-cert.pem << client public key (Cert)
client-key.pem << client private key
daemon-cert.pem << daemon public key (cert)
daemon-key.pem << daemon private key

Configuring the Docker daemon for TLS

修改 daemon.json (詳細規格)
MacOSX的 daemon.json 位置 /Users/user_name/.docker/daemon.json
{ "hosts": ["tcp://node3:2376"], "tls": true, "tlsverify": true, "tlscacert": "/home/ubuntu/.docker/ca.pem", "tlscert": "/home/ubuntu/.docker/cert.pem", "tlskey": "/home/ubuntu/.docker/key.pem" }

  • tlsverify enables TLS verification. 預設false,使用TLS並做後臺程序與客戶端通訊的驗證
  • tlscacert tells the daemon which CA to trust. 預設 ~/.docker/ca.pem,通過CA認證過的的certificate檔案路徑
  • tlscert tells Docker where the daemon’s certificate is located. 預設 ~/.docker/cert.pem ,TLS的certificate檔案路徑 
  • tlskey tells Docker where the daemon’s private key is located. 預設~/.docker/key.pem,TLS的key檔案路徑
  • hosts tells Docker which sockets to bind the daemon on.

Warning! Linux systems running systemd don’t allow you to use the “hosts” option in daemon.json. Instead, you have to specify it in a systemd override file. You may be able to do this with the sudo systemctl edit docker command. This will open a new file called /etc/systemd/system/docker.service.d/override.conf in an editor. Add the following three lines and save the file.

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://node3:2376


檢視 指定的 host 的 docker daemon 版本 
shell> docker -H tcp://node3:2376 version


Configuring the Docker client for TLS

export DOCKER_HOST=tcp://node3:2376
export DOCKER_TLS_VERIFY=1
export DOCKER_CERT_PATH=位置指向 ca.pem, cert,pem, 和 key.pem的目錄
docker version