245 lines
12 KiB
Markdown
245 lines
12 KiB
Markdown
# Init 容器
|
||
|
||
该特性在自 Kubernetes 1.6 版本推出 beta 版本。Init 容器可以在 PodSpec 中同应用程序的 `containers` 数组一起来指定。此前 beta 注解的值仍将保留,并覆盖 PodSpec 字段值。
|
||
|
||
本文讲解 Init 容器的基本概念,这是一种专用的容器,在应用程序容器启动之前运行,用来包含一些应用镜像中不存在的实用工具或安装脚本。
|
||
|
||
## 理解 Init 容器
|
||
|
||
[Pod](https://kubernetes.io/docs/concepts/abstractions/pod/) 能够具有多个容器,应用运行在容器里面,但是它也可能有一个或多个先于应用容器启动的 Init 容器。
|
||
|
||
Init 容器与普通的容器非常像,除了如下两点:
|
||
|
||
- Init 容器总是运行到成功完成为止。
|
||
- 每个 Init 容器都必须在下一个 Init 容器启动之前成功完成。
|
||
|
||
如果 Pod 的 Init 容器失败,Kubernetes 会不断地重启该 Pod,直到 Init 容器成功为止。然而,如果 Pod 对应的 `restartPolicy` 为 Never,它不会重新启动。
|
||
|
||
指定容器为 Init 容器,在 PodSpec 中添加 `initContainers` 字段,以 v1.Container 类型对象的 JSON 数组的形式,还有 app 的 `containers` 数组。 Init 容器的状态在 `status.initContainerStatuses` 字段中以容器状态数组的格式返回(类似 `status.containerStatuses` 字段)。
|
||
|
||
### 与普通容器的不同之处
|
||
|
||
Init 容器支持应用容器的全部字段和特性,包括资源限制、数据卷和安全设置。 然而,Init 容器对资源请求和限制的处理稍有不同,在下面 [资源](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/#resources) 处有说明。 而且 Init 容器不支持 Readiness Probe,因为它们必须在 Pod 就绪之前运行完成。
|
||
|
||
如果为一个 Pod 指定了多个 Init 容器,那些容器会按顺序一次运行一个。只有当前面的 Init 容器必须运行成功后,才可以运行下一个 Init 容器。当所有的 Init 容器运行完成后,Kubernetes 才初始化 Pod 和运行应用容器。
|
||
|
||
## Init 容器能做什么?
|
||
|
||
因为 Init 容器具有与应用程序容器分离的单独镜像,所以它们的启动相关代码具有如下优势:
|
||
|
||
- 它们可以包含并运行实用工具,但是出于安全考虑,是不建议在应用程序容器镜像中包含这些实用工具的。
|
||
- 它们可以包含使用工具和定制化代码来安装,但是不能出现在应用程序镜像中。例如,创建镜像没必要 `FROM` 另一个镜像,只需要在安装过程中使用类似 `sed`、 `awk`、 `python` 或 `dig` 这样的工具。
|
||
- 应用程序镜像可以分离出创建和部署的角色,而没有必要联合它们构建一个单独的镜像。
|
||
- Init 容器使用 Linux Namespace,所以相对应用程序容器来说具有不同的文件系统视图。因此,它们能够具有访问 Secret 的权限,而应用程序容器则不能。
|
||
- 它们必须在应用程序容器启动之前运行完成,而应用程序容器是并行运行的,所以 Init 容器能够提供了一种简单的阻塞或延迟应用容器的启动的方法,直到满足了一组先决条件。
|
||
|
||
### 示例
|
||
|
||
下面列举了 Init 容器的一些用途:
|
||
|
||
- 等待一个 Service 创建完成,通过类似如下 shell 命令:
|
||
|
||
```bash
|
||
for i in {1..100}; do sleep 1; if dig myservice; then exit 0; fi; exit 1
|
||
```
|
||
|
||
- 将 Pod 注册到远程服务器,通过在命令中调用 API,类似如下:
|
||
|
||
```bash
|
||
curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
|
||
```
|
||
|
||
- 在启动应用容器之前等一段时间,使用类似 `sleep 60` 的命令。
|
||
|
||
- 克隆 Git 仓库到数据卷。
|
||
|
||
- 将配置值放到配置文件中,运行模板工具为主应用容器动态地生成配置文件。例如,在配置文件中存放 POD_IP 值,并使用 Jinja 生成主应用配置文件。
|
||
|
||
更多详细用法示例,可以在 [StatefulSet 文档](https://kubernetes.io/docs/concepts/abstractions/controllers/statefulsets/) 和 [生产环境 Pod 指南](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) 中找到。
|
||
|
||
### 使用 Init 容器
|
||
|
||
下面是 Kubernetes 1.5 版本 yaml 文件,展示了一个具有 2 个 Init 容器的简单 Pod。 第一个等待 `myservice` 启动,第二个等待 `mydb` 启动。 一旦这两个 Service 都启动完成,Pod 将开始启动。
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: Pod
|
||
metadata:
|
||
name: myapp-pod
|
||
labels:
|
||
app: myapp
|
||
annotations:
|
||
pod.beta.kubernetes.io/init-containers: '[
|
||
{
|
||
"name": "init-myservice",
|
||
"image": "busybox",
|
||
"command": ["sh", "-c", "until nslookup myservice; do echo waiting for myservice; sleep 2; done;"]
|
||
},
|
||
{
|
||
"name": "init-mydb",
|
||
"image": "busybox",
|
||
"command": ["sh", "-c", "until nslookup mydb; do echo waiting for mydb; sleep 2; done;"]
|
||
}
|
||
]'
|
||
spec:
|
||
containers:
|
||
- name: myapp-container
|
||
image: busybox
|
||
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
|
||
```
|
||
|
||
这是 Kubernetes 1.6 版本的新语法,尽管老的 annotation 语法仍然可以使用。我们已经把 Init 容器的声明移到 `spec` 中:
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: Pod
|
||
metadata:
|
||
name: myapp-pod
|
||
labels:
|
||
app: myapp
|
||
spec:
|
||
containers:
|
||
- name: myapp-container
|
||
image: busybox
|
||
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
|
||
initContainers:
|
||
- name: init-myservice
|
||
image: busybox
|
||
command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']
|
||
- name: init-mydb
|
||
image: busybox
|
||
command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']
|
||
```
|
||
|
||
> **注意:版本兼容性问题**
|
||
>
|
||
> 1.5 版本的语法在 1.6 和 1.7 版本中仍然可以使用,但是我们推荐使用 1.6 版本的新语法。Kubernetes 1.8 以后的版本只支持新语法。在 Kubernetes 1.6 版本中,Init 容器在 API 中新建了一个字段。 虽然期望使用 beta 版本的 annotation,但在未来发行版将会被废弃掉。
|
||
|
||
下面的 YAML 文件展示了 `mydb` 和 `myservice` 两个 Service:
|
||
|
||
```yaml
|
||
kind: Service
|
||
apiVersion: v1
|
||
metadata:
|
||
name: myservice
|
||
spec:
|
||
ports:
|
||
- protocol: TCP
|
||
port: 80
|
||
targetPort: 9376
|
||
---
|
||
kind: Service
|
||
apiVersion: v1
|
||
metadata:
|
||
name: mydb
|
||
spec:
|
||
ports:
|
||
- protocol: TCP
|
||
port: 80
|
||
targetPort: 9377
|
||
```
|
||
|
||
这个 Pod 可以使用下面的命令进行启动和调试:
|
||
|
||
```bash
|
||
$ kubectl create -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=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 with docker id 5ced34a04634; Security:[seccomp=unconfined]
|
||
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Started Started container with docker id 5ced34a04634
|
||
$ kubectl logs myapp-pod -c init-myservice # Inspect the first init container
|
||
$ kubectl logs myapp-pod -c init-mydb # Inspect the second init container
|
||
```
|
||
|
||
一旦我们启动了 `mydb` 和 `myservice` 这两个 Service,我们能够看到 Init 容器完成,并且 `myapp-pod` 被创建:
|
||
|
||
```bash
|
||
$ kubectl create -f services.yaml
|
||
service "myservice" created
|
||
service "mydb" created
|
||
$ kubectl get -f myapp.yaml
|
||
NAME READY STATUS RESTARTS AGE
|
||
myapp-pod 1/1 Running 0 9m
|
||
```
|
||
|
||
这个例子非常简单,但是应该能够为我们创建自己的 Init 容器提供一些启发。
|
||
|
||
## 具体行为
|
||
|
||
在 Pod 启动过程中,Init 容器会按顺序在网络和数据卷初始化之后启动。每个容器必须在下一个容器启动之前成功退出。如果由于运行时或失败退出,将导致容器启动失败,它会根据 Pod 的 `restartPolicy` 指定的策略进行重试。然而,如果 Pod 的 `restartPolicy` 设置为 Always,Init 容器失败时会使用 `RestartPolicy` 策略。
|
||
|
||
在所有的 Init 容器没有成功之前,Pod 将不会变成 `Ready` 状态。Init 容器的端口将不会在 Service 中进行聚集。 正在初始化中的 Pod 处于 `Pending` 状态,但应该会将 `Initializing` 状态设置为 true。
|
||
|
||
如果 Pod [重启](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/#pod-restart-reasons),所有 Init 容器必须重新执行。
|
||
|
||
对 Init 容器 spec 的修改被限制在容器 image 字段,修改其他字段都不会生效。更改 Init 容器的 image 字段,等价于重启该 Pod。
|
||
|
||
因为 Init 容器可能会被重启、重试或者重新执行,所以 Init 容器的代码应该是幂等的。特别地当写到 `EmptyDirs` 文件中的代码,应该对输出文件可能已经存在做好准备。
|
||
|
||
Init 容器具有应用容器的所有字段。除了 `readinessProbe`,因为 Init 容器无法定义不同于完成(completion)的就绪(readiness)之外的其他状态。这会在验证过程中强制执行。
|
||
|
||
在 Pod 上使用 `activeDeadlineSeconds`,在容器上使用 `livenessProbe`,这样能够避免 Init 容器一直失败。 这就为 Init 容器活跃设置了一个期限。
|
||
|
||
在 Pod 中的每个 app 和 Init 容器的名称必须唯一;与任何其它容器共享同一个名称,会在验证时抛出错误。
|
||
|
||
### 资源
|
||
|
||
为 Init 容器指定顺序和执行逻辑,下面对资源使用的规则将被应用:
|
||
|
||
- 在所有 Init 容器上定义的,任何特殊资源请求或限制的最大值,是 *有效初始请求/限制*
|
||
- Pod 对资源的有效请求/限制要高于:
|
||
|
||
- 所有应用容器对某个资源的请求/限制之和
|
||
- 对某个资源的有效初始请求/限制
|
||
|
||
- 基于有效请求/限制完成调度,这意味着 Init 容器能够为初始化预留资源,这些资源在 Pod 生命周期过程中并没有被使用。
|
||
|
||
- Pod 的 *有效 QoS 层*,是 Init 容器和应用容器相同的 QoS 层。
|
||
|
||
基于有效 Pod 请求和限制来应用配额和限制。Pod 级别的 cgroups 是基于有效 Pod 请求和限制,和调度器相同。
|
||
|
||
### Pod 重启的原因
|
||
|
||
Pod 重启,会导致 Init 容器重新执行,主要有如下几个原因:
|
||
|
||
- 用户更新 PodSpec 导致 Init 容器镜像发生改变。应用容器镜像的变更只会重启应用容器。
|
||
- Pod 基础设施容器被重启。这不多见,但某些具有 root 权限可访问 Node 的人可能会这样做。
|
||
- 当 `restartPolicy` 设置为 Always,Pod 中所有容器会终止,强制重启,由于垃圾收集导致 Init 容器完整的记录丢失。
|
||
|
||
## 支持与兼容性
|
||
|
||
API Server 版本为 1.6 或更高版本的集群,通过使用 `spec.initContainers` 字段来支持 Init 容器。之前的版本可以使用 alpha 和 beta 注解支持 Init 容器。`spec.initContainers` 字段也被加入到 alpha 和 beta 注解中,所以 Kubernetes 1.3.0 版本或更高版本可以执行 Init 容器,并且 1.6 版本的 API Server 能够安全地回退到 1.5.x 版本,而不会使已创建的 Pod 失去 Init 容器的功能。
|