3-4-4-init容器
Init 容器是一种特殊容器 , 在Pod 内的应用容器启动之前运行。Init 容器可以包括一些应用镜像中不存在的实用工具和安装脚本。
你可以在 Pod 的规约中与用来描述应用容器的 containers 数组平行的位置指定 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 容器的想法 :
等待一个
Service完成创建 , 通过类似如下Shell命令 :for i in {1..100}; do sleep 1; if nslookup myservice; then exit 0; fi; done; exit 1
注册这个
Pod到远程服务器 , 通过在命令中调用API, 类似如下 :curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'在启动应用容器之前等一段时间 , 使用类似命令 :
sleep 60克隆
Git仓库到卷中。将配置值放到配置文件中 , 运行模板工具为主应用容器动态地生成配置文件。 例如 , 在配置文件中存放
POD_IP值 , 并使用Jinja生成主应用配置文件。
使用 Init 容器的情况
下面的例子定义了一个具有 2 个 Init 容器的简单 Pod。 第一个等待 myservice 启动 , 第二个等待 mydb 启动。 一旦这两个 Init 容器都启动完成 , Pod 将启动 spec 节中的应用容器。
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"]你通过运行下面的命令启动 Pod :
kubectl apply -f myapp.yaml输出类似于:
pod/myapp-pod created使用下面的命令检查其状态 :
kubectl get -f myapp.yaml输出类似于 :
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:0/2 0 6m或者查看更多详细信息 :
kubectl describe -f myapp.yaml输出类似于 :
Name: myapp-pod
Namespace: default
[...]
Labels: app.kubernetes.io/name=MyApp
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:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
16s 16s 1 {default-scheduler } Normal Scheduled Successfully assigned myapp-pod to 172.17.4.201
16s 16s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Pulling pulling image "busybox"
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Pulled Successfully pulled image "busybox"
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Created Created container init-myservice
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Started Started container init-myservice如需查看 Pod 内 Init 容器的日志 , 请执行:
kubectl logs myapp-pod -c init-myservice # 查看第一个 Init 容器
kubectl logs myapp-pod -c init-mydb # 查看第二个 Init 容器在这一刻 , Init 容器将会等待至发现名称为 mydb 和 myservice 的服务。
如下为创建这些 Service 的配置文件 :
---
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 apply -f services.yaml输出类似于 :
service/myservice created
service/mydb created这样你将能看到这些 Init 容器执行完毕 , 随后 my-app 的 Pod 进入 Running 状态 :
kubectl get -f myapp.yaml输出类似于 :
NAME READY STATUS RESTARTS AGE
myapp-pod 1/1 Running 0 9m这个简单例子应该能为你创建自己的 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 容器启动的时间。 但建议仅在团队将其应用程序部署为 Job 时才使用 activeDeadlineSeconds , 因为 activeDeadlineSeconds 在 Init 容器结束后仍有效果。 如果你设置了 activeDeadlineSeconds , 已经在正常运行的 Pod 会被杀死。
在 Pod 中的每个应用容器和 Init 容器的名称必须唯一。 与任何其它容器共享同一个名称 , 会在校验时抛出错误。
边车容器 API
特性状态 : Kubernetes v1.28 [alpha]
Kubernetes 自 1.28 版本起引入了一个名为 SidecarContainers 的 Alpha 特性门控 , 允许你为 Init 容器指定独立于 Pod 和其他 Init 容器的 restartPolicy。 你还可以添加容器探针来控制 Init 容器的生命周期。
如果 Init 容器被创建时 restartPolicy 设置为 Always , 则 Init 容器将启动并在整个 Pod 的生命期内保持运行 , 这对于运行与主应用容器分离的支持服务非常有用。
如果为该 Init 容器指定了 readinessProbe , 则其结果将用于确定 Pod 的 ready 状态。
由于这些容器以 Init 容器的形式定义 , 所以它们具有与其他 Init 容器相同的按序执行和顺序保证优势 , 从而允许使用这些容器与其他 Init 容器混合在一起构造复杂的 Pod 初始化流程。
与常规的 Init 容器相比 , 只要 kubelet 将边车风格的 Init 容器的 started 容器状态设置为 true , 边车风格的 Init 容器会继续运行 , 下一个 Init 容器可以开始启动。 到达该状态的前提是 , 要么需要容器中有进程正在运行且未定义启动探针 , 要么其 startupProbe 的结果是成功的。
此特性可用于以更稳健的方式实现边车容器模式 , 这是因为如果某个边车容器失败 , kubelet 总会重新启动它。
以下是一个具有两个容器的 Deployment 示例 , 其中一个是边车:
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', 'echo "logging" > /opt/logs.txt']
volumeMounts:
- name: data
mountPath: /opt
initContainers:
- name: logshipper
image: alpine:latest
restartPolicy: Always
command: ['sh', '-c', 'tail /opt/logs.txt']
volumeMounts:
- name: data
mountPath: /opt
volumes:
- name: data
emptyDir: {}此特性也可用于运行带有边车的 Job , 因为在主容器完成后 , 边车容器不会阻止 Job 完成。
以下是一个具有两个容器的 Job 示例 , 其中一个是边车 :
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
template:
spec:
containers:
- name: myjob
image: alpine:latest
command: ['sh', '-c', 'echo "logging" > /opt/logs.txt']
volumeMounts:
- name: data
mountPath: /opt
initContainers:
- name: logshipper
image: alpine:latest
restartPolicy: Always
command: ['sh', '-c', 'tail /opt/logs.txt']
volumeMounts:
- name: data
mountPath: /opt
restartPolicy: Never
volumes:
- name: data
emptyDir: {}容器内的资源共享
在给定的 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 , 可查阅你所使用的版本对应的文档。
参考链接
Pod - Init 容器 - 《Kubernetes v1.28 中文文档》 - 书栈网 · BookStack