Init 容器是一种特殊容器,在 Pod 内的应用容器启动之前运行。Init 容器可以包括一些应用镜像中不存在的实用工具和安装脚本。你可以在 Pod 的规约中与用来描述应用容器的 containers
数组平行的位置指定 Init 容器。
接下来看一下 InitContainer,首先介绍 InitContainer 和普通 container 的区别,有以下三点内容:
根据上面三点内容,我们看一下 InitContainer 的一个用途。它其实主要为普通 container 服务,比如说它可以为普通 container 启动之前做一个初始化,或者为它准备一些配置文件, 配置文件可能是一些变化的东西。再比如做一些前置条件的校验,如网络是否联通。
每个 Pod 中可以包含多个容器, 应用运行在这些容器里面,同时 Pod 也可以有一个或多个先于应用容器启动的 Init 容器。
Init 容器与普通的容器非常像,除了如下两点:
如果 Pod 的 Init 容器失败,kubelet 会不断地重启该 Init 容器直到该容器成功为止。 然而,如果 Pod 对应的 restartPolicy
值为 "Never",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 容器可以包含一些安装过程中应用容器中不存在的实用工具或个性化代码。 例如,没有必要仅为了在安装过程中使用类似 sed
、awk
、python
或 dig
这样的工具而去 FROM
一个镜像来生成一个新的镜像。
Init 容器可以安全地运行这些工具,避免这些工具导致应用镜像的安全性降低。
应用镜像的创建者和部署者可以各自独立工作,而没有必要联合构建一个单独的应用镜像。
Init 容器能以不同于 Pod 内应用容器的文件系统视图运行。因此,Init 容器可以访问 应用容器不能访问的 Secret 的权限。
由于 Init 容器必须在应用容器启动之前运行完成,因此 Init 容器 提供了一种机制来阻塞或延迟应用容器的启动,直到满足了一组先决条件。 一旦前置条件满足,Pod 内的所有的应用容器会并行启动。
下面是一些如何使用 Init 容器的想法:
等待一个 Service 完成创建,通过类似如下 shell 命令:
for i in {1..100}; do sleep 1; if dig myservice; then exit 0; fi; exit 1
注册这个 Pod 到远程服务器,通过在命令中调用 API,类似如下:
curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register \
-d 'instance=$()&ip=$()'
在启动应用容器之前等一段时间,使用类似命令:
sleep 60
克隆 Git 仓库到卷中。
将配置值放到配置文件中,运行模板工具为主应用容器动态地生成配置文件。 例如,在配置文件中存放 POD_IP
值,并使用 Jinja 生成主应用配置文件。
[root@k8s-master ~]# cat init-container.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: init-demo
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: init
template:
metadata:
labels:
app: init
spec:
initContainers:
- name: download
image: busybox
command:
- wget
- "-O"
- "/opt/index.html"
- http://www.ctnrs.com
volumeMounts:
- name: wwwroot
mountPath: "/opt"
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: wwwroot
mountPath: "/usr/share/nginx/html"
volumes:
- name: wwwroot
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: initweb
namespace: default
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: init
type: NodePort
配置文件中,你可以看到应用容器和 Init 容器共享了一个卷。
Init 容器将共享卷挂载到了 /opt
目录,应用容器将共享卷挂载到了/usr/share/nginx/html
目录。 Init 容器执行完下面的命令就终止:
- wget
- "-O"
- "/opt/index.html"
- http://www.ctnrs.com
wget -O /opt/index.html http://www.ctnrs.com
请注意 Init 容器在 nginx 服务器的根目录写入 index.html
。
[root@k8s-master ~]# kubectl apply -f init-container.yml
deployment.apps/init-demo created
service/initweb created
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
init-demo-5ff7995557-2slpk 0/1 Init:0/1 0 18s
init-demo-5ff7995557-hwp26 0/1 Init:0/1 0 18s
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
init-demo-5ff7995557-2slpk 0/1 PodInitializing 0 21s
init-demo-5ff7995557-hwp26 0/1 Init:0/1 0 21s
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
init-demo-5ff7995557-2slpk 1/1 Running 0 45s
init-demo-5ff7995557-hwp26 0/1 PodInitializing 0 45s
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
init-demo-5ff7995557-2slpk 1/1 Running 0 107s
init-demo-5ff7995557-hwp26 1/1 Running 0 107s
[root@k8s-master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
initweb NodePort 10.99.15.60 80:30287/TCP 16m
下面是截取部署普罗米修斯的deployment里面的片段
[root@k8s-master prometheus]# cat prometheus-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus
namespace: ops
labels:
k8s-app: prometheus
spec:
replicas: 1
selector:
matchLabels:
k8s-app: prometheus
template:
metadata:
labels:
k8s-app: prometheus
spec:
serviceAccountName: prometheus
initContainers: #这里部署了一个初始化容器用来改变目录权限
- name: "init-chown-data"
image: "busybox:latest"
imagePullPolicy: "IfNotPresent"
command: ["chown", "-R", "65534:65534", "/data"]
volumeMounts:
- name: prometheus-data
mountPath: /data
subPath: ""
containers:
- name: prometheus-server-configmap-reload
image: "jimmidyson/configmap-reload:v0.1"
imagePullPolicy: "IfNotPresent"
args:
- --volume-dir=/etc/config
- --webhook-url=http://localhost:9090/-/reload
volumeMounts:
- name: config-volume
mountPath: /etc/config
readOnly: true
resources:
limits:
cpu: 10m
memory: 10Mi
requests:
cpu: 10m
memory: 10Mi
- name: prometheus-server
image: "prom/prometheus:v2.20.0"
imagePullPolicy: "IfNotPresent"
args:
- --config.file=/etc/config/prometheus.yml
- --storage.tsdb.path=/data
- --web.console.libraries=/etc/prometheus/console_libraries
- --web.console.templates=/etc/prometheus/consoles
- --web.enable-lifecycle
ports:
- containerPort: 9090
readinessProbe:
httpGet:
path: /-/ready
port: 9090
initialDelaySeconds: 30
timeoutSeconds: 30
livenessProbe:
httpGet:
path: /-/healthy
port: 9090
initialDelaySeconds: 30
timeoutSeconds: 30
resources:
limits:
cpu: 500m
memory: 1500Mi
requests:
cpu: 200m
memory: 600Mi
volumeMounts:
- name: config-volume
mountPath: /etc/config
- name: prometheus-data
mountPath: /data
subPath: ""
- name: prometheus-rules
mountPath: /etc/config/rules
volumes:
- name: config-volume
configMap:
name: prometheus-config
- name: prometheus-rules
configMap:
name: prometheus-rules
- name: prometheus-data
persistentVolumeClaim:
claimName: prometheus
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prometheus
namespace: ops
spec:
storageClassName: "managed-nfs-storage"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
以 Init:
开头的 Pod 状态汇总了 Init 容器执行的状态。 下表介绍调试 Init 容器时可能看到的一些状态值示例。
状态 | 含义 |
---|---|
Init:N/M |
Pod 包含 M 个 Init 容器,其中 N 个已经运行完成。 |
Init:Error |
Init 容器已执行失败。 |
Init:CrashLoopBackOff |
Init 容器执行总是失败。 |
Pending |
Pod 还没有开始执行 Init 容器。 |
PodInitializing or Running |
Pod 已经完成执行 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
设置为 true。
如果 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 容器执行顺序下,资源使用适用于如下规则:
配额和限制适用于有效 Pod 的请求和限制值。 Pod 级别的 cgroups 是基于有效 Pod 的请求和限制值,和调度器相同。
Pod 重启会导致 Init 容器重新执行,主要有如下几个原因:
用户更新 Pod 的规约导致 Init 容器镜像发生改变。Init 容器镜像的变更会引起 Pod 重启。 应用容器镜像的变更仅会重启应用容器。
Pod 的基础设施容器 (译者注:如 pause
容器) 被重启。这种情况不多见, 必须由具备 root 权限访问节点的人员来完成。
当 restartPolicy
设置为 "Always
",Pod 中所有容器会终止而强制重启。 由于垃圾收集机制的原因,Init 容器的完成记录将会丢失。
这个初始化容器是设置初始化目录的一个权限
spec:
serviceAccountName: prometheus
initContainers:
- name: "init-chown-data"
image: "busybox:latest"
imagePullPolicy: "IfNotPresent"
command: ["chown", "-R", "65534:65534", "/data"]
volumeMounts:
- name: prometheus-data
mountPath: /data
subPath: ""
volumes:
- name: config-volume
configMap:
name: prometheus-config
- name: prometheus-rules
configMap:
name: prometheus-rules
- name: prometheus-data
persistentVolumeClaim:
claimName: prometheus
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prometheus
namespace: ops
spec:
storageClassName: "managed-nfs-storage"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
prometheus容器里面数据目录默认是nobody