本文已参与「新人创作礼」活动,一起开启掘金创作之路。
介绍
Init容器是一种特殊的容器,在Pod内的应用容器启动之前运行。在Pod内的应用容器启动之前运行。
Init容器可以包括一些应用镜像中不存在的使用工具和安装脚本。
可以在Pod的规约中与用来描述应用容器的containers数据平行的位置指定Init容器。
理解Init容器
每个 Pod中可以包含多个容器, 应用运行在这些容器里面,同时 Pod 也可以有一个或多个先于应用容器启动的 Init 容器。
Init 容器与普通的容器非常像,除了如下两点:
- 它们总是运行到完成。
- 每个都必须在下一个启动之前成功完成。
如果 Pod 的 Init 容器失败,kubelet 会不断地重启该 Init 容器直到该容器成功为止。 然而,如果 Pod 对应的 restartPolicy 值为 "Never",并且 Pod 的 Init 容器失败, 则 Kubernetes 会将整个 Pod 状态设置为失败。
为 Pod 设置 Init 容器需要在 Pod 规约中添加 initContainers 字段, 该字段以 Container 类型对象数组的形式组织,和应用的 containers 数组同级相邻。
Init 容器的状态在 status.initContainerStatuses 字段中以容器状态数组的格式返回 (类似 status.containerStatuses 字段)。
与普通容器的不同之处
Init 容器支持应用容器的全部字段和特性,包括资源限制、数据卷和安全设置。 然而,Init 容器对资源请求和限制的处理稍有不同。
- 所有Init容器上定义的任何特定资源的limit或request的最大值,最为Pod有效初始request/limit。如果任何资源都没有指定资源限制,这被视为最高限制。
- Pod对资源的有效limit/request是如下两者中较大者:
- 所有应用容器对某个资源的limit/request之和
- 对某个资源有效初始limit/request
- 基于有效 limit/request 完成调度,这意味着 Init 容器能够为初始化过程预留资源, 这些资源在 Pod 生命周期过程中并没有被使用。
- Pod 的 有效 QoS 层 ,与 Init 容器和应用容器的一样。
同时Init容器不支持lifecycle、livenessProbe、readinessPorbe和startupProbe,因为它们必须在Pod就绪之前完成的。
如果一个Pod中指定了多个Init容器,这些容器会按顺序逐个完成。每一个Init容器必须运行成功,下一个才能运行。当所有的Init容器运行完成时,Kubernetes才会为Pod初始化应用容器并像平常一样运行。
使用Init容器
因为 Init 容器具有与应用容器分离的单独镜像,其启动相关代码具有如下优势:
- Init 容器可以包含一些安装过程中应用容器中不存在的实用工具或个性化代码。 例如,没有必要仅为了在安装过程中使用类似
sed、awk、python或dig这样的工具而去FROM一个镜像来生成一个新的镜像。 - Init 容器可以安全地运行这些工具,避免这些工具导致应用镜像的安全性降低。
- 应用镜像的创建者和部署者可以各自独立工作,而没有必要联合构建一个单独的应用镜像。
- Init 容器能以不同于 Pod 内应用容器的文件系统视图运行。因此,Init 容器可以访问 应用容器不能访问的
Secret的权限。 - 由于 Init 容器必须在应用容器启动之前运行完成,因此 Init 容器提供了一种机制来阻塞或延迟应用容器的启动,直到满足了一组先决条件。 一旦前置条件满足,Pod 内的所有的应用容器会并行启动。
示例
下面的例子定义了一个具有2个Init容器的简单Pod。第一个等待myservice启动,第二个等待mydb启动。一旦这两个Init容器都启动完成,Pod将启动spec节中的应用容器。
# myapp.yaml
apiVersion: 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:
kubectl apply -f mysqll.yaml
输出类似于:
pod/myapp-pod created
使用下面命令检查其状态:
kubectl get -f myapp.yaml
输出类似于:
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:0/2 0 5m11s
或者查看更多详细信息:
kubectl describe -f myapp.yaml
如果需要查看Pod内Init容器的日志,则执行:
# 查看第一个 Init 容器
kubectl logs myapp-pod -c init-myservice
# 查看第二个 Init 容器
kubectl logs myapp-pod -c init-mydb
这时候,Init容器会等待至发现名称为myservice和mydb的Service。
如下创建这些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
创建mydb和myservice服务的命令:
kubectl create -f services.yaml
输出类似于:
service/myservice created
service/mydb created
这样就能看到这些Init容器执行完毕,随后my-app的Pod进入Running状态:
NAME READY STATUS RESTARTS AGE
myapp-pod 1/1 Running 0 14m
具体行为
在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 容器具有应用容器的所有字段。然而 Kubernetes 禁止使用 readinessProbe, 因为 Init 容器不能定义不同于完成态(Completion)的就绪态(Readiness)。 Kubernetes 会在校验时强制执行此检查。
在 Pod 上使用 activeDeadlineSeconds 和在容器上使用 livenessProbe 可以避免 Init 容器一直重复失败。 activeDeadlineSeconds 时间包含了 Init 容器启动的时间。 但建议仅在团队将其应用程序部署为 Job 时才使用 activeDeadlineSeconds, 因为 activeDeadlineSeconds 在 Init 容器结束后仍有效果。 如果你设置了 activeDeadlineSeconds,已经在正常运行的 Pod 会被杀死。
在 Pod 中的每个应用容器和 Init 容器的名称必须唯一; 与任何其它容器共享同一个名称,会在校验时抛出错误。
Pod 重启的原因
Pod 重启会导致Init容器重新执行,主要有如下几个原因:
- Pod的基础设施容器(如
pause容器)被重启,这种情况不多见,必须由具备root权限访问节点的人员来完成。 - 当
restartPolicy设置为Always,Pod中所有容器会终止而强制重启,由于垃圾收集机制的原因,Init容器的完成记录将会丢失。
创建包含Init容器的Pod
本例中将创建一个包含一个应用容器和一个Init容器的Pod。Init容器在应用容器启动前运行完成。
下面是Pod的配置文件:
# init-containers.yaml
apiVersion: v1
kind: Pod
metadata:
name: init-demo
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: workdir
mountPath: /usr/share/nginx/html
initContainers:
- name: install
image: busybox:1.28
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容器执行完下面的命令就终止:
wget -O /work-dir/index.html http://info.cern.ch
注意Init容器在nginx服务器的根目录写入Index.html。
创建Pod:
kubectl apply -f init-containers.yaml
检查Pod是否运行正常:
kubectl get -f init-containers.yaml
结果表明运行正常:
NAME READY STATUS RESTARTS AGE
init-demo 1/1 Running 0 3m59s
通过shell进入init-demo Pod中的nginx容器:
kubectl exec -it init-demo /bin/bash
在容器的shell中,发送GET请求到nginx服务器:
root@init-demo:/# curl localhost
<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>
结果表明nginx正在为Init容器编写的web页面提供服务。
理解Pod的状态
以Init:开头的Pod状态汇总了Init容器执行的状态。
| 状态 | 含义 |
|---|---|
| Init:N/M | Pod包含M个Init容器,其中N个已经运行完成 |
| Init:Error | Init容器已执行失败 |
| Init:CrashLoopBackOff | Init容器执行总是失败 |
| Pending | Pod还没有开始执行Init容器 |
| PodInitializing or Running | Pod已经完成执行Init容器 |