如何在Kubernetes中使用Init Container
Pod 可以包含多個容器,應(yīng)用運行在這些容器里面,同時 Pod 也可以有一個或多個先于應(yīng)用容器啟動的 Init 容器。
Init Container 是什么
Init Container 是一種特殊容器,顧名思義是用來做初始化工作的容器,可以是一個或者多個,如果有多個的話,這些容器會按定義的順序依次執(zhí)行,只有所有的Init Container執(zhí)行完后,主容器才會被啟動。
我們知道一個Pod里面的所有容器是共享數(shù)據(jù)卷和網(wǎng)絡(luò)命名空間的,所以Init Container里面產(chǎn)生的數(shù)據(jù)可以被主容器使用到的。Init Container與應(yīng)用容器本質(zhì)上是一樣的,除了以下兩點:
- Init Container 不支持 lifecycle、livenessProbe、readinessProbe 和 startupProbe, 因為它們必須在 Pod 就緒之前運行完成,所以他們是僅運行一次就結(jié)束的任務(wù)
- 必須在成功執(zhí)行完后,系統(tǒng)才能繼續(xù)執(zhí)行下一個容器。
如果 Pod 的 Init 容器失敗,Kubernetes 會不斷地重啟該 Pod,直到 Init 容器成功為止。如果 Pod 對應(yīng)的 restartPolicy 為 Never,它不會重新啟動。
Pod 的生命周期:
從上面這張圖我們可以直觀的看到 Init Container 是獨立于主容器之外的,但他們都屬于Pod的生命周期。
應(yīng)用場景
- 等待其他關(guān)聯(lián)服務(wù)正確運行(例如數(shù)據(jù)庫或某個后臺服務(wù))
- 基于環(huán)境變量或配置模板生成服務(wù)所需配置文件
- 從遠(yuǎn)程數(shù)據(jù)庫獲取本地所需配置,或者將自身注冊到某個中央數(shù)據(jù)庫中
- 下載相關(guān)依賴包,或者對統(tǒng)進(jìn)行一些預(yù)配置操作
簡單示例
應(yīng)用容器定義在 Pod.Spec.Containers,是必填字段,而 init 是定義在 Pod.Spec.initContainers 中,是可選字段。
下面的例子定義了一個具有 2 個 Init 容器的簡單 Pod。第一個等待 myservice 啟動, 第二個等待 mydb 啟動。一旦這兩個 Init 容器都啟動完成,Pod 將啟動 spec 節(jié)中的應(yīng)用容器。
myapp.yaml:
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app.kubernetes.io/name: MyApp
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.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]
- name: init-mydb
image: busybox:1.28
command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mydb; sleep 2; done"]
創(chuàng)建:
[root@localhost ~]# kubectl apply -f myapp.yaml
pod/myapp-pod created
查看狀態(tài):
[root@localhost ~]# kubectl get -f myapp.yaml
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:0/2 0 8s
輸出詳細(xì)信息:
[root@localhost ~]# kubectl describe -f myapp.yaml
Name: myapp-pod
Namespace: default
[...]
Labels: app.kubernetes.io/name=MyApp
Annotations: <none>
Status: Pending
[...]
Init Containers:
init-myservice:
[...]
State: Running
[...]
init-mydb:
[...]
State: Waiting
Reason: PodInitializing
Ready: False
[...]
Containers:
myapp-container:
[...]
State: Waiting
Reason: PodInitializing
Ready: False
[...]
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 20s default-scheduler Successfully assigned default/myapp-pod to localhost.localdomain
Normal Pulling 17s kubelet Pulling image "busybox:1.28"
Normal Pulled 8s kubelet Successfully pulled image "busybox:1.28" in 9.30472043s
Normal Created 7s kubelet Created container init-myservice
Normal Started 6s kubelet Started container init-myservice
查看 Pod 內(nèi) Init 容器的日志:
[root@localhost ~]# kubectl logs myapp-pod -c init-myservice # 查看第一個 Init 容器
nslookup: can't resolve 'myservice.default.svc.cluster.local'
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
waiting for myservice
[root@localhost ~]# kubectl logs myapp-pod -c init-mydb # 查看第二個 Init 容器
Error from server (BadRequest): container "init-mydb" in pod "myapp-pod" is waiting to start: PodInitializing
此時,init-mydb容器會等待 init-myservice 執(zhí)行完成后再執(zhí)行。如下為創(chuàng)建這些 Service 的配置文件:services.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
創(chuàng)建:
[root@localhost ~]# kubectl apply -f services.yaml
service/myservice created
service/mydb created
再次查看狀態(tài):變成 了 Running:
[root@localhost ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
myapp-pod 1/1 Running 0 2m35s
此時再次查看詳細(xì)信息,發(fā)現(xiàn)兩個 init-myservice 和 init-mydb 已經(jīng) Terminated 了:
Init Containers:
init-myservice:
[...]
State: Terminated
Reason: Completed
Exit Code: 0
[...]
init-mydb:
[...]
State: Terminated
Reason: Completed
Exit Code: 0
Sidecar 新特性
隨著Kubernetes發(fā)布了1.28,支持了不少重磅特性,其中最令人感慨的莫過于新的Sidecar,目前是alpha版本。之前Sidecar的稱謂只是一種多容器的設(shè)計模式,在K8s看來和普通容器沒什么不一樣。但由于其生命周期與業(yè)務(wù)容器并不一致,對于Sidecar的生命周期管理一直是個問題。
新版本的Sidecar是放置在initContainers中,指定restartPolicy為Always便開啟Sidecar,其生命周期以及重啟管理與普通容器也是一樣的,此特性也可用于運行 Job 。
下面是一個帶有Sidecar的Deployment示例,log Sidecar容器用來輸出日志到終端,main容器模擬寫入日志: sidecar.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
app: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: alpine:latest
command: ['sh', '-c', 'while true; do echo "logging" >> /opt/logs.txt; sleep 1; done']
volumeMounts:
- name: data
mountPath: /opt
initContainers:
- name: logshipper # sidecar 容器
image: alpine:latest
restartPolicy: Always # 必須指定restartPolicy為Always才能開啟sidecar
command: ['sh', '-c', 'tail -f /opt/logs.txt']
volumeMounts:
- name: data
mountPath: /opt
volumes:
- name: data
emptyDir: {}
部署到K8s集群中,可以看到initContainers[*].restartPolicy字段:
[root@localhost ~]# kubectl create -f sidecar.yaml
deployment.apps/myapp created
[root@localhost ~]# kubectl get po -l app=myapp -ojsonpath='{.items[0].spec.initContainers[0].restartPolicy}'
Always
[root@localhost ~]# kubectl get po -l app=myapp
NAME READY STATUS RESTARTS AGE
myapp-215h3248d-p4z6 2/2 Running 0 1m5s
myapp Pod中兩個容器都是Ready(2/2),查看日志可以看到log Sidecar一直在輸出日志。
[root@localhost ~]# kubectl logs -l app=myapp -c logshipper -f
logging
logging