官方文档: Init 容器
Init 容器
Init 容器是一种特殊容器,在 Pod 内的应用容器启动之前运行。Init 容器可以包括一些应用镜像中不存在的实用工具和安装脚本。
可以在 Pod 的 yaml 文件中与用来描述应用容器的 containers
数组平行的位置指定 Init 容器。
理解 Init 容器
每个 Pod 中可以包含多个容器,应用运行在这些容器里面,同时 Pod 也可以有一个或多个先于应用容器启动的 Init 容器。Init 容器与普通的容器非常像,除了以下两点:
- 它们总是运行到完成;
- 每个都必须在下一个启动之前成功完成;
如果 Pod 的 Init 容器失败,kubelet 会不断地重启该 Init 容器,直到该容器成功为止。然而,如果 Pod 对应的 restartPolicy 值为 Never,并且 Pod 的 Init 容器失败,则 Kubernetes 会将整个 Pod 状态设置为失败。
为 Pod 设置 Init 容器,需要再 Pod 的 spec 中添加 initContainers
字段,该字段以 Container 类型对象数组的形式组成,和应用的 containers 数组同级相邻。Init 容器的状态在 status.initContainerStatuses 字段中以容器状态数组的格式返回(类似 status.containerStatuses 字段)。
与普通容器的不同之处
Init 容器支持应用容器的全部字段和特性,包括资源限制,数据卷和安全设置。然而,Init 容器对资源请求和限制的处理稍有不同,在下面资源段有说明。
同时 Init 容器不支持 lifecycle, livenessProbe, readinessProbe 和 startupProbe,因为它们必须在 Pod 就绪之前运行完成。
如果为一个 Pod 指定了多个 Init 容器,这些容器会按顺序逐个运行。每个 Init 容器都必须运行成功,下一个才能够运行。当所有的 Init 容器运行完成,Kubernetes 才会为 Pod 初始化应用容器并像平常一样运行。
使用 Init 容器
因为 Init 容器具有与应用容器分离的单独镜像,其启动相关代码具有如下优势:
- Init 容器可以包含一些安装过程中应用容器中不存在的实用工具或个性化代码。例如,没有必要仅为了在安装过程中使用类似 sed,awk,python 或 dig 这样的工具而去 FROM 一个镜像来生成一个新的镜像;
- Init 容器可以安全地运行这些工具,避免这些工具导致应用镜像的安全性降低;
- 应用镜像的创建者和部署者可以各自独立工作,而没有必要联合构建一个单独的应用镜像;
- Init 容器能以不同于 Pod 内应用容器的文件系统视图运行。因此,Init 容器可以访问应用容器不能访问的 Secret 的权限;
- 由于 Init 容器必须在应用容器启动之前运行完成,因此 Init 容器提供了一种机制来阻塞或延迟应用容器的启动,直到满足了一组先决条件。一旦前置条件满足,Pod 内的所有应用程序会并行启动。
示例
下面是一些如何使用 Init 容器的想法
等待一个 Service 完成创建,通过类似如下 shell 命令
1
for i in {1..100}; do sleep 1; if dig myservice; then exit 0; fi; exit 1
注册这个 Pod 到远程服务器,通过在命令中调用 API,类似如下
1
2curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register \
-d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'在启动应用容器之前先等待一段时间,使用类似如下命令
1
sleep 60
克隆仓库到卷中
将配置值放到配置文件中,运行模板工具为主应用容器动态地生成配置文件。例如,在配置文件中存放 POP_IP 值,并使用 Jinja 生成主应用配置文件。
使用 Init 容器的示例
下面的例子定义了一个具有2个 Init 容器的简单 Pod,第一个等待 myservice 启动,第二个等待 mydb 启动。一旦这两个 Init 容器都启动完成,Pod 将启动 spec 节中的应用容器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: 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"]通过以下命令启动 Pod
1
kubectl create -f myapp.yaml
使用以下命令检查其状态
1
kubectl get -f myapp.yaml
输出类似于
1
2NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:0/2 0 6m使用以下命令查看更详细的信息
1
kubectl describe -f myapp.yaml
输出如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88Name: myapp-pod
Namespace: default
Priority: 0
Node: k8s-master-02/192.168.200.19
Start Time: Tue, 29 Jun 2021 20:52:36 +0800
Labels: app=myapp
Annotations: cni.projectcalico.org/podIP: 172.21.176.200/32
cni.projectcalico.org/podIPs: 172.21.176.200/32
Status: Pending
IP: 172.21.176.200
IPs:
IP: 172.21.176.200
Init Containers:
init-myservice:
Container ID: docker://e942d4a9da7c4dae9ad40b189ce3ca10d497562b33a75e3622ab43e9573b0eab
Image: busybox:1.28
Image ID: docker-pullable://busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47
Port: <none>
Host Port: <none>
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
State: Running
Started: Tue, 29 Jun 2021 20:53:18 +0800
Ready: False
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-5776z (ro)
init-mydb:
Container ID:
Image: busybox:1.28
Image ID:
Port: <none>
Host Port: <none>
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
State: Waiting
Reason: PodInitializing
Ready: False
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-5776z (ro)
Containers:
myapp-container:
Container ID:
Image: busybox:1.28
Image ID:
Port: <none>
Host Port: <none>
Command:
sh
-c
echo The app is running! && sleep 3600
State: Waiting
Reason: PodInitializing
Ready: False
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-5776z (ro)
Conditions:
Type Status
Initialized False
Ready False
ContainersReady False
PodScheduled True
Volumes:
default-token-5776z:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-5776z
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m1s default-scheduler Successfully assigned default/myapp-pod to k8s-master-02
Normal Pulling 2m1s kubelet Pulling image "busybox:1.28"
Normal Pulled 79s kubelet Successfully pulled image "busybox:1.28" in 41.784614207s
Normal Created 79s kubelet Created container init-myservice
Normal Started 79s kubelet Started container init-myservice可以输入以下命令查看 Pod 内 Init 容器的日志
1
2kubectl logs -f myapp-pod -c init-myservice # 查看第一个 Init 容器
kubectl logs -f myapp-pod -c init-mydb # 查看第二个 Init 容器这时候,Init 容器将会等待至发现名为 mydb 和 myservice 的 Service,如下为创建这些 Service 的配置清单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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使用以下命令创建 Service
1
kubectl create -f services.yaml
查看 Init 容器是否执行完毕,随后 my-app 的 Pod 进入 Running 状态
1
kubectl get -f myapp.yaml
输出类似如下
1
2NAME READY STATUS RESTARTS AGE
myapp-pod 1/1 Running 0 11m
以上只是一个简单的 Init 容器使用示例,接下来的章节提供了更详细例子的链接。
具体行为
在 Pod 启动过程中,每个 Init 容器会在网络和数据卷初始化之后按顺序启动。kubelet 依据 Init 容器在 Pod 规约中的出现顺序依次运行之。
每个 Init 容器成功退出后才会启动下一个 Init 容器。如果某容器因为容器运行时的原因无法启动,或以错误状态退出,kubelet 会根据 Pod 的 restartPolicy 策略进行重试。然而,如果 Pod 的 restartPolicy 设置为 “Always”,Init 容器失败时会使用 restartPolicy 的 “OnFailure” 策略。
在所有的 Init 容器没有成功之前,Pod 将不会变成 Ready 状态。Init 容器的端口将不会在 Service 中进行聚集。正在初始化中的 Pod 处于 Pending 状态,但会将状况 Initializing 设置为 false。
如果 Pod 重启,所有 Init 容器必须重新执行。
对 Init 容器规约的修改仅限于容器的 image 字段,更改 Init 容器的 image 字段,等同于重启该 Pod。
因为 Init 容器可能会被重启,重试或者重新执行,所以 Init 容器的代码应该是幂等的。特别的,基于 emptyDirs 写文件的代码,应该对输出的文件可能已经存在做好准备。
Init 容器具有应用容器的所有字段。然而 Kubernetes 禁止使用 readinessProbe, 因为 Init 容器不能定义不同于完成态(Completion)的就绪态(Readiness)。 Kubernetes 会在校验时强制执行此检查。
在 Pod 上使用 activeDeadlineSeconds 和在容器上使用 livenessProbe 可以避免 Init 容器一直重复失败。activeDeadlineSeconds 时间包含了 Init 容器启动的时间。
在 Pod 中的每个应用容器和 Init 容器的名称必须唯一; 与任何其它容器共享同一个名称,会在校验时抛出错误。
资源
在给定的 Init 容器执行顺序下,资源使用适用于如下规则:
- 所有 Init 容器上定义的任何特定资源的 limit 或 request 的最大值,作为 Pod 有效初始 request/limit;
- Pod 对资源的 有效 limit/request 是如下两者的较大者:
- 所有应用容器对某个资源的 limit/request 之和
- 对某个资源的有效初始 limit/request
- 基于有效 limit/request 完成调度,这意味着 Init 容器能够为初始化过程预留资源,这些资源在 Pod 生命周期过程中并没有被使用。
- Pod 的有效 QoS 层 ,与 Init 容器和应用容器的一样。
配额和限制适用于有效 Pod 的请求和限制值。 Pod 级别的 cgroups 是基于有效 Pod 的请求和限制值,和调度器相同。
Pod 重启的原因
Pod 重启会导致 Init 容器重新执行,主要有如下几个原因:
- Pod 的基础设施容器 (译者注:如 pause 容器) 被重启。这种情况不多见, 必须由具备 root 权限访问节点的人员来完成。
- 当 restartPolicy 设置为 “Always”,Pod 中所有容器会终止而强制重启。由于垃圾收集机制的原因,Init 容器的完成记录将会丢失。
当 Init 容器的镜像发生改变或者 Init 容器的完成记录因为垃圾收集等原因被丢失时,Pod 不会被重启。这一行为适用于 Kubernetes v1.20 及更新版本。如果你在使用较早版本的 Kubernetes,可查阅你所使用的版本对应的文档。
创建一个包含 Init 容器的 Pod 示例
本例将创建一个包含一个应用程序和一个 Init 容器的 Pod,Init 容器在应用容器启动前运行完成。下面是 Pod 的配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29apiVersion: v1
kind: Pod
metadata:
name: init-demo
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: workdir
mountPath: /usr/share/nginx/html
# These containers are run during pod initialization
initContainers:
- name: install
image: busybox
command:
- wget
- "-O"
- "/work-dir/index.html"
- http://info.cern.ch
volumeMounts:
- name: workdir
mountPath: "/work-dir"
dnsPolicy: Default
volumes:
- name: workdir
emptyDir: {}配置文件中,可以看到应用容器和 Init 容器共享了一个卷。Init 容器将共享卷挂载到了
/work-dir
目录,应用容器将共享卷挂载到了/usr/share/nginx/html
目录。Init 容器执行完下面的命令就终止:1
wget -O /work-dir/index.html http://info.cern.ch
请注意 Init 容器在 nginx 服务器的根目录写入 index.html。
创建 Pod
1
kubectl create -f init-containers.yaml
检查 nginx 容器运行是否正常
1
2
3kubectl get pods init-demo
NAME READY STATUS RESTARTS AGE
init-demo 1/1 Running 0 81s结果表明 nginx 容器运行正常。
通过 shell 进入 init-demo Pod 中的 nginx 容器:
1
kubectl exec -ti init-demo -- /bin/bash
在 shell 中发送个 GET 请求到 nginx 服务器
1
2
3root@init-demo:/# apt-get update
root@init-demo:/# apt-get install curl
root@init-demo:/# curl localhost结果表明 nginx 的首页正是 Init 容器下载的
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13<html><head></head><body><header>
<title>http://info.cern.ch</title>
</header>
<h1>http://info.cern.ch - home of the first website</h1>
<p>From here you can:</p>
<ul>
<li><a href="http://info.cern.ch/hypertext/WWW/TheProject.html">Browse the first website</a></li>
<li><a href="http://line-mode.cern.ch/www/hypertext/WWW/TheProject.html">Browse the first website using the line-mode browser simulator</a></li>
<li><a href="http://home.web.cern.ch/topics/birth-web">Learn about the birth of the web</a></li>
<li><a href="http://home.web.cern.ch/about">Learn about CERN, the physics laboratory where the web was born</a></li>
</ul>
</body></html>