# 多個 Container 在同一個 Pod 通常一個 Pod 內只會有一個應用程式在運行,這也比較有利管理及規模的控制。不過有的應用程式較為複雜,例如可能需要先做些前置作業,或是有輔助的程式,這時能在 Pod 中使用多個 Container,以 Container 為單位來建構出這個應用,但又都放在同一個 Pod 中。Kubernetes 提供了 init Container 及 multi Container 的功能讓一個 Pod 中能運行多個 Container。 ## Multi-Container Pod 中可運行多個 Container,在 **`spec.containers`** 這邊設置。 ```yaml apiVersion: v1 kind: Pod metadata: name: hello-world-pod spec: containers: - name: container1 image: chihhuiw/nodejs-helloworld:1.0.0 imagePullPolicy: Always # 預設是 IfNotPresented command: ["node", "/app/index.js"] args: ["3001"] ports: - containerPort: 3001 volumeMounts: - mountPath: /demo1 name: demo-volume - name: container2 image: chihhuiw/nodejs-helloworld:1.0.0 imagePullPolicy: Always command: ["node", "/app/index.js"] args: ["3002"] ports: - containerPort: 3002 volumeMounts: - mountPath: /demo2 name: demo-volume volumes: - name: demo-volume emptyDir: {} ``` 這邊我設置了 arguments,可以在啟動 node js 程式時指定要跑的 port。所以這邊我跑了兩個 Container 但使用不同的 port,因同一個 port 只能被一個 Container 使用。 ### Pod 內的 Container 該如何溝通呢? 同一個 Pod 中的兩個 Containers 可直接用 **`localhost: port`** 連接。來測試看看: ```bash kubectl exec -it hello-world-pod -c container1 -- /bin/sh ``` **`-c`** : 指定 Container 名稱,如果一個 Pod 裡面只有一個 Container 則可忽略。 #### Container 1 ```bash wget -O - http://localhost:3001 # Connecting to localhost:3001 ([::1]:3001) # writing to stdout # - 100% |*************************************************************************************************************************| 15 0:00:00 ETA # written to stdout wget -O - http://localhost:3002 # Connecting to localhost:3002 ([::1]:3002) # writing to stdout # - 100% |*************************************************************************************************************************| 15 0:00:00 ETA # written to stdout ``` #### Container 2 ```bash wget -O - http://localhost:3001 # Connecting to localhost:3001 ([::1]:3001) # writing to stdout # - 100% |*************************************************************************************************************************| 15 0:00:00 ETA # written to stdout wget -O - http://localhost:3002 # Connecting to localhost:3002 ([::1]:3002) # writing to stdout # - 100% |*************************************************************************************************************************| 15 0:00:00 ETA # written to stdout ``` ### 如果一個 Pod 裡面兩個 Containers 使用了同樣的 Port 因 Pod 建立時會被 assign 一組 IP,同一個 Pod 內的 Container,不能監聽同樣的 port。這種情況下只會有一個 Container 綁定到那個 port,第二個 Container 則無法成功啟動。 ```yaml apiVersion: v1 kind: Pod metadata: name: nginx-pod labels: name: nginx-pod spec: containers: - name: nginx-1 image: nginx ports: - containerPort: 80 - name: nginx-2 image: nginx ports: - containerPort: 80 ``` ```bash kubectl get pod # NAME READY STATUS RESTARTS AGE # nginx-pod 1/2 Error 2 (24s ago) 35s ``` ```bash kubectl describe pod nginx-pod # Events: # Type Reason Age From Message # ---- ------ ---- ---- ------- # Normal Scheduled 8m11s default-scheduler Successfully assigned default/nginx-pod to k3d-mycluster-agent-1 # Normal Pulling 8m11s kubelet Pulling image "nginx" # Normal Pulled 8m9s kubelet Successfully pulled image "nginx" in 1.489500015s (1.489515279s including waiting) # Normal Created 8m9s kubelet Created container nginx-1 # Normal Started 8m9s kubelet Started container nginx-1 # Normal Pulled 8m7s kubelet Successfully pulled image "nginx" in 1.4456404s (1.44565471s including waiting) # Normal Pulled 8m3s kubelet Successfully pulled image "nginx" in 1.465707457s (1.465717735s including waiting) # Normal Pulled 7m47s kubelet Successfully pulled image "nginx" in 1.529490129s (1.529505566s including waiting) # Normal Pulling 7m18s (x4 over 8m9s) kubelet Pulling image "nginx" # Normal Pulled 7m16s kubelet Successfully pulled image "nginx" in 1.474763148s (1.474774467s including waiting) # Normal Created 7m16s (x4 over 8m7s) kubelet Created container nginx-2 # Normal Started 7m16s (x4 over 8m7s) kubelet Started container nginx-2 # Warning BackOff 3m7s (x22 over 7m59s) kubelet Back-off restarting failed container nginx-2 in pod nginx-pod_default(bde750a9-a93d-4404-9e18-9693696da363) ``` 可以看到最後一行出現 Warning,是關於第二個 Container 建立失敗的訊息。 ### 共用 Volume 另外上面的 yaml 我們還有定義了 **`volume`** 這個 field,這是用來設置 Pod 存取檔案的資訊。一個 Pod 可以包含一個或多個 volumes,這些 volumes 可以被該 Pod 中的一個或多個 Containers 所使用。在這邊我們使用 **`emptyDir`** 的方式掛載 volume。 當 Pod 被分配到 Node 上,一個 emptyDir volume 就會被建立起來,並持續到 Pod 死掉。而這個 directory 一開始會是空的,可以被掛到多個 Container 中。應用場景會是一些暫時性的使用,例如快取。 而在每個 Container 中可設定這個 volume 要掛載進 Container 中的哪個位置。這兩個 Container 能互相對同一個 volume 底下的檔案做操作。 ```bash kubectl exec -it pods/hello-world-pod -c container1 -- /bin/sh /app $ echo test >> /demo1/test.txt /app $ ls /demo1/ # test.txt /app $ cat /demo1/test.txt # test kubectl exec -it pods/hello-world-pod -c container2 -- /bin/sh /app $ cd /demo2/ /demo2 $ ls # test.txt /demo2 $ cat test.txt # test /demo2 $ rm test.txt kubectl exec -it pods/hello-world-pod -c container1 -- /bin/sh /app $ ls /demo1/ /app $ ``` 先進去 Container 1,在 **`demo1`** 資料夾底下建立 **`test.txt`** 檔案,其內容包含 **`test`**,接著進入 Container 2,在 **`demo2`** 資料夾底下查看是否有 **`test.txt`** 檔案並確定其內容之後,刪除 **`test.txt`**,最後再進入 Container 1 發現原本的 **`/demo1/test.txt`** 不見了。 ## Init container 在啟動 Pod 的時候,其中 Containers 的啟動是不分順序的。但如果我們有些工作希望能在主要 Container 啟動之前執行,就可以使用 initial Container,例如先從 repo 拉下最新的程式碼。 如同 **`containers`** 的 section 一樣,一個 Pod 可以有多個 init Container,會照順序執行。要注意的是,要等前一個 init Container 運行成功,才會跑下一個。init Container 如果運行失敗,kubelet 會重啟這個 Pod 直到 init Container 啟動成功。但如果 **`restartPolicy`**(Pod level)設置為 **`Never`**,這個 Pod 就會被視為啟動失敗。 ```yaml apiVersion: v1 kind: Pod metadata: name: init-containers-pod spec: containers: - name: myapp-container image: busybox:1.28 command: ["sh", "-c", "echo The app is running! && sleep 3600"] initContainers: - name: init-myservice image: busybox:1.28 command: [ "sh", "-c", "until nslookup myservice; do echo waiting for myservice; sleep 2; done;", ] - name: init-mydb image: busybox:1.28 command: ["sh", "-c", "until nslookup mydb; do echo waiting for mydb; sleep 2; done;"] ``` 上面的程式碼有主要的 Container 一個,init Containers 兩個。 成功啟動 **`init-containers-pod`** 這個 Pod 的步驟如下: + **`init-myservice`** 執行的指令是,執行 **`nslookup myservice`**,如果沒有找到就印出 **`waiting for ...`**,找到後等 2 秒結束。 + 而 **`init-mydb`** 則會等 **`init-myservice`** 執行成功後,才執行。一樣會執行 **`nslookup mydb`** 後,如果沒找到就印出字樣,找到後等 2 秒後結束。 + **`init Containers`** 執行完畢後,才會開始跑 **`myapp-container`** 。 但第一階段我們還沒有建立 **`myservice`** & **`mydb`**,所以 init Containers 應該是無法完成它的任務。 ```bash kubectl get pod # NAME READY STATUS RESTARTS AGE # init-containers-pod 0/1 Init:0/2 0 2m32s ``` 利用 **`kubectl describe pod init-containers-pod`** 觀察 Pod 資訊。我們可以看到整個 Pod 會是 **`Pending`** 的狀態,而 **`init-myservice`** 的狀態為 **`Running`**,因為還沒有找到 **`myservice`**。 ```bash kubectl describe pod init-containers-pod # Name: init-containers-pod # ... # Status: Pending # ... # Init Containers: # init-myservice: # ... # State: Running # Started: Fri, 05 Jan 2024 10:48:00 +0800 # Ready: False # ... ``` 後續的 Containers 狀態都還是 **`Waiting`**。 ```bash # init-mydb: # ... # State: Waiting # Reason: PodInitializing # Ready: False # ... # Containers: # myapp-container: # ... # State: Waiting # Reason: PodInitializing # Ready: False # ... ``` 利用 **`kubectl logs init-containers-pod -c init-myservice`** 檢查特定 Container log 會發現 **`init-myservice`** 很努力的在找 **`myservice`** 不過找不到。 ```bash kubectl logs init-containers-pod -c init-myservice # nslookup: can't resolve 'myservice' # Server: 10.43.0.10 # Address 1: 10.43.0.10 kube-dns.kube-system.svc.cluster.local # waiting for myservice # Server: 10.43.0.10 # Address 1: 10.43.0.10 kube-dns.kube-system.svc.cluster.local ``` 接著我們再看 **`init-mydb`** 的 log 訊息,顯示在等待。 ```bash kubectl logs init-containers-pod -c init-mydb # Error from server (BadRequest): container "init-mydb" in pod "init-containers-pod"is # waiting to start: PodInitializing ``` 再來我們建立 **`myservice`** & **`mydb`** service 給這兩個 Container 連。 ```yaml apiVersion: v1 kind: Service metadata: name: myservice spec: ports: - protocol: TCP port: 80 targetPort: 9376 --- apiVersion: v1 kind: Service metadata: name: mydb spec: ports: - protocol: TCP port: 80 targetPort: 9377 ``` 這時再檢查 Pod status,變成 **`Running`** 了。 ```bash kubectl apply -f multi-init-container/services.yaml # service/myservice created # service/mydb created kubectl get all # NAME READY STATUS RESTARTS AGE # pod/init-containers-pod 1/1 Running 0 11m # NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE # service/kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 5d14h # service/myservice ClusterIP 10.43.5.90 <none> 80/TCP 6s # service/mydb ClusterIP 10.43.158.84 <none> 80/TCP 6s ``` 利用 **`kubectl describe pod init-containers-pod`** 觀察 Pod 資訊。兩個 init Containers 狀態都為 **`Terminated`**,而主要的 Container 狀態為 **`Running`**。 ```bash kubectl describe pod init-containers-pod # Name: init-containers-pod # ... # Status: Running # ... # Init Containers: # init-myservice: # ... # State: Terminated # Reason: Completed # Exit Code: 0 # Started: Fri, 05 Jan 2024 11:20:39 +0800 # Finished: Fri, 05 Jan 2024 11:32:20 +0800 # Ready: True # ... # init-mydb: # ... # State: Terminated # Reason: Completed # Exit Code: 0 # Started: Fri, 05 Jan 2024 11:32:21 +0800 # Finished: Fri, 05 Jan 2024 11:32:21 +0800 # Ready: True # ... # Containers: # myapp-container: # ... # State: Running # Started: Fri, 05 Jan 2024 11:32:22 +0800 # Ready: True # ... ``` 一樣用 **`kubectl logs <pod_name> -c <container_name>`** 看 log。可以看出有印出 **`waiting for myservice`** 的句子,最後找到 **`myservice`** 後則停止。 ```bash kubectl logs init-containers-pod -c init-myservice # waiting for myservice # Server: 10.43.0.10 # Address 1: 10.43.0.10 kube-dns.kube-system.svc.cluster.local # Name: myservice # Address 1: 10.43.5.90 myservice.default.svc.cluster.local ``` 因我們同時建立了兩個 service,而 **`init-mydb`** 這個 Container 又是接在 **`init-service`** 後面啟動,這時 **`nslookup`** 直接找到 **`mydb`** 這個 service 了,就沒有印出 **`waiting for mydb`**。 ```bash kubectl logs init-containers-pod -c init-mydb # Server: 10.43.0.10 # Address 1: 10.43.0.10 kube-dns.kube-system.svc.cluster.local # Name: mydb # Address 1: 10.43.158.84 mydb.default.svc.cluster.local ``` 最後看一下主要 Container 的 log。等 init Containers 執行成功後,主要的 Container 也順利啟動了。 ```bash kubectl logs init-containers-pod -c myapp-container # The app is running! ```