# 多個 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!
```