Kubernetes 初始化容器InitContainer

Init Container


Pod中会有这几种类型的容器:
Infrastructure Container: 基础容器 ,维护整个Pod网络空间
InitContainers: 初始化容器,先于业务容器开始执行
Containers: 业务容器

Init 容器是一种特殊容器,在 Pod 内的应用容器启动之前运行。Init 容器可以包括一些应用镜像中不存在的实用工具和安装脚本。你可以在 Pod 的规约中与用来描述应用容器的 containers 数组平行的位置指定 Init 容器。

InitContainer 介绍


接下来看一下 InitContainer,首先介绍 InitContainer 和普通 container 的区别,有以下三点内容:

  1. InitContainer 首先会比普通 container 先启动,并且直到所有的 InitContainer 执行成功后,普通 container 才会被启动
  2. InitContainer 之间是按定义的次序去启动执行的,执行成功一个之后再执行第二个,而普通的 container 是并发启动的
  3. InitContainer 执行成功后就结束退出,而普通容器可能会一直在执行。它可能是一个 longtime 的,或者说失败了会重启,这个也是 InitContainer 和普通 container 不同的地方

根据上面三点内容,我们看一下 InitContainer 的一个用途。它其实主要为普通 container 服务,比如说它可以为普通 container 启动之前做一个初始化,或者为它准备一些配置文件, 配置文件可能是一些变化的东西。再比如做一些前置条件的校验,如网络是否联通。

Kubernetes 初始化容器InitContainer_第1张图片

理解 Init 容器


每个 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 容器不支持 lifecyclelivenessProbereadinessProbe 和 startupProbe, 因为它们必须在 Pod 就绪之前运行完成。

如果为一个 Pod 指定了多个 Init 容器,这些容器会按顺序逐个运行。 每个 Init 容器必须运行成功,下一个才能够运行。当所有的 Init 容器运行完成时, Kubernetes 才会为 Pod 初始化应用容器并像平常一样运行。

使用 Init 容器


因为 Init 容器具有与应用容器分离的单独镜像,其启动相关代码具有如下优势:

  • Init 容器可以包含一些安装过程中应用容器中不存在的实用工具或个性化代码。 例如,没有必要仅为了在安装过程中使用类似 sedawkpython 或 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 生成主应用配置文件。

使用 Init 容器的情况


示例:部署一个web网站,网站程序没有打到镜像中,而是希望从代码仓库中动态拉取放到应用容器中。
[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

Kubernetes 初始化容器InitContainer_第2张图片

下面是截取部署普罗米修斯的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

理解 Pod 的状态


以 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 容器执行顺序下,资源使用适用于如下规则:

  • 所有 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 的规约导致 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

Kubernetes 初始化容器InitContainer_第3张图片

 prometheus容器里面数据目录默认是nobody

你可能感兴趣的:(Kubernetes,Pod,kubernetes)