Python3 - k8s之深入理解 Pod

Posted 韩俊强

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python3 - k8s之深入理解 Pod相关的知识,希望对你有一定的参考价值。

Python3 - k8s之深入理解 Pod

文章目录

一、 静态 Pod

在Kubernetes集群中除了我们经常使用到的普通的 Pod 外,还有一种特殊的 Pod,叫做Static Pod,就是我们说的静态 Pod,静态 Pod 有什么特殊的地方呢?

静态 Pod 直接由特定节点上的kubelet进程来管理,不通过 master 节点上的apiserver。无法与我们常用的控制器Deployment或者DaemonSet进行关联,它由kubelet进程自己来监控,当pod崩溃时重启该podkubelete也无法对他们进行健康检查。静态 pod 始终绑定在某一个kubelet,并且始终运行在同一个节点上。 kubelet会自动为每一个静态 pod 在 Kubernetes 的 apiserver 上创建一个镜像 Pod(Mirror Pod),因此我们可以在 apiserver 中查询到该 pod,但是不能通过 apiserver 进行控制(例如不能删除)。

创建静态 Pod 有两种方式:配置文件和 HTTP 两种方式

1.1 配置文件

配置文件就是放在特定目录下的标准的 JSON 或 YAML 格式的 pod 定义文件。用kubelet --pod-manifest-path=<the directory>来启动kubelet进程,kubelet 定期的去扫描这个目录,根据这个目录下出现或消失的 YAML/JSON 文件来创建或删除静态 pod。

比如我们在 node01 这个节点上用静态 pod 的方式来启动一个 nginx 的服务。我们登录到node01节点上面,可以通过下面命令找到kubelet对应的启动配置文件

[root@k8s-node01 ~]# systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
   Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; vendor preset: disabled)
  Drop-In: /usr/lib/systemd/system/kubelet.service.d
           └─10-kubeadm.conf
   Active: active (running) since Mon 2022-04-04 18:32:25 CST; 2 days ago
     Docs: https://kubernetes.io/docs/
 Main PID: 13026 (kubelet)
    Tasks: 43
   Memory: 74.3M
   CGroup: /system.slice/kubelet.service
           └─13026 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --co...

Apr 05 14:50:38 k8s-node01 kubelet[13026]: I0405 14:50:38.856208   13026 reconciler.go:301] Volume detached for volume "default-token-86...ePath ""
Apr 05 14:50:38 k8s-node01 kubelet[13026]: I0405 14:50:38.856215   13026 reconciler.go:301] Volume detached for volume "default-token-86...ePath ""
Apr 05 14:50:58 k8s-node01 kubelet[13026]: E0405 14:50:58.336985   13026 remote_runtime.go:295] ContainerStatus "d07a5775c1b727189585ff1...4ee747c0
Apr 05 14:50:58 k8s-node01 kubelet[13026]: I0405 14:50:58.389579   13026 reconciler.go:181] operationExecutor.UnmountVolume started for volume "...
Apr 05 14:50:58 k8s-node01 kubelet[13026]: I0405 14:50:58.396048   13026 operation_generator.go:831] UnmountVolume.TearDown succeeded for volume...
Apr 05 14:50:58 k8s-node01 kubelet[13026]: I0405 14:50:58.489791   13026 reconciler.go:301] Volume detached for volume "default-token-86...ePath ""
Apr 05 14:51:11 k8s-node01 kubelet[13026]: E0405 14:51:11.156490   13026 fsHandler.go:118] failed to collect filesystem stats - rootDisk...dd5b0f0b
Apr 05 14:51:12 k8s-node01 kubelet[13026]: E0405 14:51:12.094784   13026 fsHandler.go:118] failed to collect filesystem stats - rootDisk...da84c631
Apr 05 14:51:12 k8s-node01 kubelet[13026]: E0405 14:51:12.137411   13026 fsHandler.go:118] failed to collect filesystem stats - rootDisk...7698bddd
Apr 05 14:51:12 k8s-node01 kubelet[13026]: E0405 14:51:12.359154   13026 fsHandler.go:118] failed to collect filesystem stats - rootDisk...f845d50d
Hint: Some lines were ellipsized, use -l to show in full.

配置文件路径为:

[root@k8s-node01 ~]# cat /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf 
# Note: This dropin only works with kubeadm and kubelet v1.11+
[Service]
Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"
Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
# This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically
EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
# This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use
# the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file.
EnvironmentFile=-/etc/sysconfig/kubelet
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS

打开这个文件我们可以看到其中有一条如下的环境变量配置: Environment="KUBELET_SYSTEM_PODS_ARGS=--pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true"

所以如果我们通过kubeadm的方式来安装的集群环境,对应的kubelet已经配置了我们的静态 Pod 文件的路径,那就是/etc/kubernetes/manifests,所以我们只需要在该目录下面创建一个标准的 Pod 的 JSON 或者 YAML 文件即可:

如果你的 kubelet 启动参数中没有配置上面的--pod-manifest-path参数的话,那么添加上这个参数然后重启 kubelet 即可。

# 创建yaml文件
[root@k8s-node01 ~]# cd /etc/kubernetes/manifests/
[root@k8s-node01 manifests]# ls
[root@k8s-node01 manifests]# vim static-pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: static-pod1
  labels:
    app: static
spec:
  containers:
  - name: web
    image: nginx
    ports:
    - name: webport
      containerPort: 80
# 查看一下, 自动生成了容器
[root@k8s-node01 manifests]# docker ps | grep static
096f86b8540c   nginx                                               "/docker-entrypoint.…"   44 seconds ago   Up 44 seconds             k8s_web_static-pod1-k8s-node01_default_19ab912487f4709fa8ae7020401ef414_0
8ab8ee6de0fd   registry.aliyuncs.com/google_containers/pause:3.1   "/pause"                 47 seconds ago   Up 47 seconds             k8s_POD_static-pod1-k8s-node01_default_19ab912487f4709fa8ae7020401ef414_0

1.2 通过 HTTP 创建静态 Pods

kubelet 周期地从–manifest-url=参数指定的地址下载文件,并且把它翻译成 JSON/YAML 格式的 pod 定义。此后的操作方式与–pod-manifest-path=相同,kubelet 会不时地重新下载该文件,当文件变化时对应地终止或启动静态 pod。

1.3 静态pods的动态增加和删除

如何删除或重启呢? 直接移除/etc/kubernetes/manifests/文件夹下的yaml即可, 恢复则是重新放进来

运行中的kubelet周期扫描配置的目录(我们这个例子中就是/etc/kubernetes/manifests)下文件的变化,当这个目录中有文件出现或消失时创建或删除pods。

[root@k8s-node01 manifests]# mv static-pod.yaml /tmp/
[root@k8s-node01 manifests]# docker ps | grep static
[root@k8s-node01 manifests]#
[root@k8s-node01 manifests]# mv /tmp/static-pod.yaml ./
[root@k8s-node01 manifests]# docker ps | grep static
96ae6e7c78e2   nginx                                               "/docker-entrypoint.…"   1 second ago    Up 1 second              k8s_web_static-pod1-k8s-node01_default_19ab912487f4709fa8ae7020401ef414_0
14b6fcd7e257   registry.aliyuncs.com/google_containers/pause:3.1   "/pause"                 4 seconds ago   Up 4 seconds             k8s_POD_static-pod1-k8s-node01_default_19ab912487f4709fa8ae7020401ef414_0

其实我们用 kubeadm 安装的集群,master 节点上面的几个重要组件都是用静态 Pod 的方式运行的,我们登录到 master 节点上查看/etc/kubernetes/manifests目录:

[root@k8s-master01 ~]# cd /etc/kubernetes/manifests/
[root@k8s-master01 manifests]# lsshell
etcd.yaml  kube-apiserver.yaml  kube-controller-manager.yaml  kube-scheduler.yaml

那么在 k8s-master01 下创建会怎样呢?

[root@k8s-master01 ~]# cd /etc/kubernetes/manifests/
[root@k8s-master01 manifests]# vim static-pod2.yaml
[root@k8s-master01 manifests]# kubectl get pods
NAME                           READY   STATUS    RESTARTS   AGE
harry-nginx-6f9f8d4465-mr2ft   1/1     Running   0          2d1h
harry-nginx-6f9f8d4465-tfj78   1/1     Running   0          2d1h
my-nginx-576bb7cb54-k4gj6      1/1     Running   0          47h
static-pod1-k8s-node01         1/1     Running   0          4m50s
static-pod2-k8s-master01       1/1     Running   0          7s
testservice-754455d66-m64nw    1/1     Running   2          2d13h

现在明白了吧,这种方式也为我们将集群的一些组件容器化提供了可能,因为这些 Pod 都不会受到 apiserver 的控制,不然我们这里kube-apiserver怎么自己去控制自己呢?万一不小心把这个 Pod 删掉了呢?所以只能有kubelet自己来进行控制,这就是我们所说的静态 Pod。

二、 Pod Hook

我们知道PodKubernetes集群中的最小单元,而 Pod 是有容器组组成的,所以在讨论 Pod 的生命周期的时候我们可以先来讨论下容器的生命周期。

实际上 Kubernetes 为我们的容器提供了生命周期钩子的,就是我们说的Pod Hook,Pod Hook 是由 kubelet 发起的,当容器中的进程启动前或者容器中的进程终止之前运行,这是包含在容器的生命周期之中。我们可以同时为 Pod 中的所有容器都配置 hook。

Kubernetes 为我们提供了两种钩子函数:

  • PostStart:这个钩子在容器创建后立即执行。但是,并不能保证钩子将在容器ENTRYPOINT之前运行,因为没有参数传递给处理程序。主要用于资源部署、环境准备等。不过需要注意的是如果钩子花费太长时间以至于不能运行或者挂起, 容器将不能达到running状态。
  • PreStop:这个钩子在容器终止之前立即被调用。它是阻塞的,意味着它是同步的, 所以它必须在删除容器的调用发出之前完成。主要用于优雅关闭应用程序、通知其他系统等。如果钩子在执行期间挂起, Pod阶段将停留在running状态并且永不会达到failed状态。

如果PostStart或者PreStop钩子失败, 它会杀死容器。所以我们应该让钩子函数尽可能的轻量。当然有些情况下,长时间运行命令是合理的, 比如在停止容器之前预先保存状态。

另外我们有两种方式来实现上面的钩子函数:

  • Exec - 用于执行一段特定的命令,不过要注意的是该命令消耗的资源会被计入容器。
  • HTTP - 对容器上的特定的端点执行HTTP请求。

2.1 环境准备

以下示例中,定义了一个Nginx Pod,其中设置了PostStart钩子函数,即在容器创建成功后,写入一句话到/usr/share/message文件中。

---
apiVersion: v1
kind: Pod
metadata:
  name: hook-demo
  labels:
    app: hook
spec:
  containers:
  - name: hook-demo
    image: nginx
    ports:
    - name: webport
      containerPort: 80
    lifecycle:
      postStart:
        exec:
          command: ["/bin/sh", "-c", "echo hello from the postStart Handler > /usr/share/message"]
[root@k8s-master01 kubeadm]# kubectl apply -f pod-hook1.yaml 
pod/hook-demo created

[root@k8s-master01 kubeadm]# kubectl get pods
NAME                           READY   STATUS    RESTARTS   AGE
hook-demo                      1/1     Running   0          7s

2.2 优雅删除资源对象

当用户请求删除含有 pod 的资源对象时(如Deployment等),K8S 为了让应用程序优雅关闭(即让应用程序完成正在处理的请求后,再关闭软件),K8S提供两种信息通知:

  • 默认:K8S 通知 node 执行docker stop命令,docker 会先向容器中PID为1的进程发送系统信号SIGTERM,然后等待容器中的应用程序终止执行,如果等待时间达到设定的超时时间,或者默认超时时间(30s),会继续发送SIGKILL的系统信号强行 kill 掉进程。
  • 使用 pod 生命周期(利用PreStop回调函数),它执行在发送终止信号之前。

默认所有的优雅退出时间都在30秒内。kubectl delete 命令支持 --grace-period=<seconds>选项,这个选项允许用户用他们自己指定的值覆盖默认值。值’0’代表 强制删除 pod. 在 kubectl 1.5 及以上的版本里,执行强制删除时必须同时指定 --force --grace-period=0

强制删除一个 pod 是从集群状态还有 etcd 里立刻删除这个 pod。 当 Pod 被强制删除时, api 服务器不会等待来自 Pod 所在节点上的 kubelet 的确认信息:pod 已经被终止。在 API 里 pod 会被立刻删除,在节点上, pods 被设置成立刻终止后,在强行杀掉前还会有一个很小的宽限期。

以下示例中,定义了一个Nginx Pod,其中设置了PreStop钩子函数,即在容器退出之前,优雅的关闭 Nginx:

方法1 强制删除pod:

[root@k8s-master01 kubeadm]# kubectl delete pod hook-demo --grace-period=0 --force 
warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "hook-demo" force deleted

方式2 优雅的删除pod:

---
apiVersion: v1
kind: Pod
metadata:
  name: hook-demo2
  labels:
    app: hook
spec:
  containers:
  - name: hook-demo2
    image: nginx
    ports:
    - name: webport
      containerPort: 80
    volumeMounts:
    - name: message
      mountPath: /usr/share
    lifecycle:
      preStop:
        exec:
          command: ["/bin/sh", "-c", "echo hello from the postStart Handler > /usr/share/message"]
  volumes:
  - name: message
    hostPath:
      path: /tmp
[root@k8s-master01 kubeadm]# kubectl apply -f pod-hook2.yaml 
pod/hook-demo2 created
[root@k8s-master01 kubeadm]# kubectl get pods
NAME                           READY   STATUS    RESTARTS   AGE
hook-demo                      1/1     Running   0          17m
hook-demo2                     1/1     Running   0          7s

另外Hook调用的日志没有暴露个给 Pod 的 event,所以只能通过describe命令来获取,如果有错误将可以看到FailedPostStartHookFailedPreStopHook这样的 event。

2.3 Pod健康检查

liveness probe(存活探针)和readiness probe(可读性探针)

上面我们和大家一起学习了Pod中容器的生命周期的两个钩子函数,PostStartPreStop,其中PostStart是在容器创建后立即执行的,而PreStop这个钩子函数则是在容器终止之前执行的。除了上面这两个钩子函数以外,还有一项配置会影响到容器的生命周期的,那就是健康检查的探针。

Kubernetes集群当中,我们可以通过配置liveness probe(存活探针)和readiness probe(可读性探针)来影响容器的生存周期。

  • kubelet 通过使用 liveness probe 来确定你的应用程序是否正在运行,通俗点将就是是否还活着。一般来说,如果你的程序一旦崩溃了, Kubernetes 就会立刻知道这个程序已经终止了,然后就会重启这个程序。而我们的 liveness probe 的目的就是来捕获到当前应用程序还没有终止,还没有崩溃,如果出现了这些情况,那么就重启处于该状态下的容器,使应用程序在存在 bug 的情况下依然能够继续运行下去。
  • kubelet 使用 readiness probe 来确定容器是否已经就绪可以接收流量过来了。这个探针通俗点讲就是说是否准备好了,现在可以开始工作了。只有当 Pod 中的容器都处于就绪状态的时候 kubelet 才会认定该 Pod 处于就绪状态,因为一个 Pod 下面可能会有多个容器。当然 Pod 如果处于非就绪状态,那么我们就会将他从我们的工作队列(实际上就是我们后面需要重点学习的 Service)中移除出来,这样我们的流量就不会被路由到这个 Pod 里面来了。

和前面的钩子函数一样的,我们这两个探针的支持两种配置方式:

* exec:执行一段命令
* http:检测某个 http 请求
* tcpSocket:使用此配置, kubelet 将尝试在指定端口上打开容器的套接字。如果可以建立连接,容器被认为是健康的,如果不能就认为是失败的。实际上就是检查端口

好,我们先来给大家演示下存活探针的使用方法,首先我们用exec执行命令的方式来检测容器的存活,如下:

[root@k8s-master01 kubeadm]# vim liveness-exec.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: liveness-exec
  labels:
    app: liveness
spec:
  containers:
  - name: liveness
    image: busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5

我们这里需要用到一个新的属性:livenessProbe,下面通过exec执行一段命令,其中periodSeconds属性表示让kubelet每隔5秒执行一次存活探针,也就是每5秒执行一次上面的cat /tmp/healthy命令,如果命令执行成功了,将返回0,那么kubelet就会认为当前这个容器是存活的并且很监控,如果返回的是非0值,那么kubelet就会把该容器杀掉然后重启它。另外一个属性initialDelaySeconds表示在第一次执行探针的时候要等待5秒,这样能够确保我们的容器能够有足够的时间启动起来。大家可以想象下,如果你的第一次执行探针等候的时间太短,是不是很有可能容器还没正常启动起来,所以存活探针很可能始终都是失败的,这样就会无休止的重启下去了,对吧?所以一个合理的initialDelaySeconds非常重要。

[root@k8s-master01 kubeadm]# kubectl apply -f liveness-exec.yaml 
pod/liveness-exec created

另外我们在容器启动的时候,执行了如下命令:

 ~ /bin/sh -c "touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600"

意思是说在容器最开始的30秒内有一个/tmp/healthy文件,在这30秒内执行cat /tmp/healthy命令都会返回一个成功的返回码。30秒后,我们删除这个文件,现在执行cat /tmp/healthy是不是就会失败了,这个时候就会重启容器了。

我们来创建下该Pod,在30秒内,查看PodEvent

~ kubectl describe pod liveness-exec

我们可以观察到容器是正常启动的,在隔一会儿,比如40s后,再查看下PodEvent,在最下面有一条信息显示 liveness probe失败了,容器被删掉并重新创建。

然后通过kubectl get pod liveness-exec可以看到RESTARTS值加1了。

同样的,我们还可以使用HTTP GET请求来配置我们的存活探针,我们这里使用一个liveness镜像来验证演示下,

---
apiVersion: v1
kind: Pod
metadata:
  name: liveness-http
  labels:
    name: liveness
spec:
  containers:
  - name: liveness
    image: cnych/liveness
    args:
    - /server
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 3
      periodSeconds: 3

同样的,根据periodSeconds属性我们可以知道kubelet需要每隔3秒执行一次liveness probe,该探针将向容器中的 server 的8080端口发送一个 HTTP GET 请求。如果 server 的 /healthz 路径的 handler 返回一个成功的返回码,kubelet就会认定该容器是活着的并且很健康,如果返回失败的返回码,kubelet将杀掉该容器并重启它。。initialDelaySeconds 指定kubelet在该执行第一次探测之前需要等待3秒钟。

通常来说,任何大于200小于400的返回码都会认定是成功的返回码。其他返回码都会被认为是失败的返回码。

我们可以来查看下上面的healthz的实现

http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) 
    duration := time.Now().Sub(started)
    if duration.Seconds() > 10 
        w.WriteHeader(500)
        w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
     else 
        w.WriteHeader(200)
        w.Write([]byte("ok"))
    
)

大概意思就是最开始前10s返回状态码200,10s过后就返回500的status_code了。所以当容器启动3秒后,kubelet 开始执行健康检查。第一次健康监测会成功,因为是在10s之内,但是10秒后,健康检查将失败,因为现在返回的是一个错误的状态码了,所以kubelet将会杀掉和重启容器。

同样的,我们来创建下该Pod测试下效果,10秒后,查看 Pod 的 event,确认liveness probe失败并重启了容器。

~ kubectl describe pod liveness-http

然后我们来通过端口的方式来配置存活探针,使用此配置,kubelet将尝试在指定端口上打开容器的套接字。 如果可以建立连接,容器被认为是健康的,如果不能就认为是失败的。

[root@k8s-master01 kubeadm]# kubectl get pods
NAME                           READY   STATUS             RESTARTS   AGE
hook-demo                      1/1     Running            0          148m
liveness-exec                  0/1     CrashLoopBackOff   7          14m
liveness-http                  0/1     CrashLoopBackOff   6          7m7s
---
apiVersion: v1
kind: Pod
metadata:
  name: liveness-readiness
  labels:
    name: liveness-readiness
spec:
  containers:
  - name: liveness-readiness
    image: cnych/liveness
    args:
    - /server
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 5
    readinessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 3
      periodSeconds: 3

我们可以看到,TCP 检查的配置与 HTTP 检查非常相似,只是将httpGet替换成了tcpSocket。 而且我们同时使用了readiness probeliveness probe两种探针。 容器启动后5秒后,kubelet将发送第一个readiness probe(可读性探针)。 该探针会去连接容器的8080端,如果连接成功,则该 Pod 将被标记为就绪状态。然后Kubelet将每隔10秒钟执行一次该检查。

除了readiness probe之外,该配置还包括liveness probe。 容器启动15秒后,kubelet将运行第一个 liveness probe。 就像readiness probe一样,这将尝试去连接到容器的8080端口。如果liveness probe失败,容器将重新启动。

有的时候,应用程序可能暂时无法对外提供服务,例如,应用程序可能需要在启动期间加载大量数据或配置文件。 在这种情况下,您不想杀死应用程序,也不想对外提供服务。 那么这个时候我们就可以使用readiness probe来检测和减轻这些情况。 Pod中的容器可以报告自己还没有准备,不能处理Kubernetes服务发送过来的流量。

从上面的YAML文件我们可以看出readiness probe的配置跟liveness probe很像,基本上一致的。唯一的不同是使用readinessProbe而不是livenessProbe。两者如果同时使用的话就可以确保流量不会到达还未准备好的容器,准备好过后,如果应用程序出现了错误,则会重新启动容器。

另外除了上面的initialDelaySecondsperiodSeconds属性外,探针还可以配置如下几个参数:

* timeoutSeconds:探测超时时间,默认1秒,最小1秒。
* successThreshold:探测失败后,最少连续探测成功多少次才被认定为成功。默认是 1,但是如果是`liveness`则必须是 1。最小值是 1。
* failureThreshold:探测成功后,最少连续探测失败多少次才被认定为失败。默认是 3,最小值是 1。

这就是liveness probe(存活探针)和readiness probe(可读性探针)的使用方法。在Pod的生命周期当中,我们已经学习了容器生命周期中的钩子函数和探针检测,接下来讲解Pod层面生命周期的一个阶段:初始化容器。

三、初始化容器

上面我们学习了容器的健康检查的两个探针:liveness probe(存活探针)和readiness probe(可读性探针)的使用方法,我们说在这两个探针是可以影响容器的生命周期的,包括我们之前提到的容器的两个钩子函数PostStartPreStop。我们今天要给大家介绍的是Init Container(初始化容器)。

Init Container就是用来做初始化工作的容器,可以是一个或者多个,如果有多个的话,这些容器会按定义的顺序依次执行,只有所有的Init Container执行完后,主容器才会被启动。我们知道一个Pod里面的所有容器是共享数据卷和网络命名空间的,所以Init Container里面产生的数据可以被主容器使用到的。

是不是感觉Init Container和之前的钩子函数有点类似啊,只是是在容器执行前来做一些工作,是吧?从直观的角度看上去的话,初始化容器的确有点像PreStart,但是钩子函数和我们的Init Container是处在不同的阶段的,我们可以通过下面的图来了解下:

从上面这张图我们可以直观的看到PostStartPreStop包括livenessreadiness是属于主容器的生命周期范围内的,而Init Container是独立于主容器之外的,当然他们都属于Pod的生命周期范畴之内的,现在我们应该明白Init Container和钩子函数之类的区别了吧。

另外我们可以看到上面我们的Pod右边还有一个infra的容器,这是一个什么容器呢?我们可以在集群环境中去查看下人任意一个Pod对应的运行的Docker容器,我们可以发现每一个Pod下面都包含了一个pause-amd64的镜像,这个就是我们的infra镜像,我们知道Pod下面的所有容器是共享同一个网络命名空间的,这个镜像就是来做这个事情的,所以每一个Pod当中都会包含一个这个镜像。

最开始 Pod 启动不起来就是因为这个 infra 镜像没有被拉下来,因为默认该镜像是需要到谷歌服务器上拉取的,所以需要提前拉取到节点上面。

我们说Init Container主要是来做初始化容器工作的,那么他有哪些应用场景呢?

  • 等待其他模块Ready:这个可以用来解决服务之间的依赖问题,比如我们有一个 Web 服务,该服务又依赖于另外一个数据库服务,但是在我们启动这个 Web 服务的时候我们并不能保证依赖的这个数据库服务就已经启动起来了,所以可能会出现一段时间内 Web 服务连接数据库异常。要解决这个问题的话我们就可以在 Web 服务的 Pod 中使用一个 InitContainer,在这个初始化容器中去检查数据库是否已经准备好了,准备好了过后初始化容器就结束退出,然后我们的主容器 Web 服务被启动起来,这个时候去连接数据库就不会有问题了。
  • 做初始化配置:比如集群里检测所有已经存在的成员节点,为主容器准备好集群的配置信息,这样主容器起来后就能用这个配置信息加入集群。
  • 其它场景:如将 pod 注册到一个中央数据库、配置中心等。

3.1 init-pod

我们先来给大家演示下服务依赖的场景下初始化容器的使用方法,如下Pod的定义方法

---
apiVersion: v1
kind: Pod
metadata:
  name: init-pod
  labels:
    app: init
spec:
  initContainers:
  - name: init-myservice
    image: busybox
    command: ['sh', '-c', 'until nslookpup myservice; do echo waiting for myservice; sleep 2; done;']
  - name: init-mydb
    image: busybox
    command: ['sh', '-c', 'until nslookpup mydb; do echo waiting for mydb; sleep 2; done;']
  containers:
  - name: main-container
    image: busybox
    command: ["sh", "-c", "echo The app is running! && sleep 3600"]

Service的对应YAML内容:

---
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 6379

---
apiVersion: v1
kind: Service
metadata:
  name: mydb
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 6378
[root@k8s-master01 kubeadm]# kubectl get pods
NAME                           READY   STATUS             RESTARTS   AGE
hook-demo                      1/1     Running            0          3h23m
init-pod                       1/1     Running            0          15m

我们可以先创建上面的Pod,然后查看下Pod的状态,然后再创建下面的Service,对比下前后状态。

我们在Pod启动过程中,初始化容器会按顺序在网络和数据卷初始化之后启动。每个容器必须在下一个容器启动之前成功退出。如果由于运行时或失败退出,导致容器启动失败,它会根据PodrestartPolicy指定的策略进行重试。 然而,如果 Pod 的 restartPolicy 设置为 Always,Init 容器失败时会使用 RestartPolicy 策略。

在所有的初始化容器没有成功之前,Pod将不会变成 Ready状态。正在初始化中的Pod处于Pending状态,但应该会将条件Initializing设置为 true。

3.2 初始化配置pod

接下来我们再来尝试创建一个做初始化配置工作的Pod

---
apiVersion: v1
kind: Pod
metadata:
  name: init-demo
  labels:
    app: init
spec:
  initContainers:
  - name: install
    image: busybox
    command:
    - wget
    - "-O"
    - "/work-dir/index.html"
    - https://www.baidu.com
    volumeMounts:
    - name: workdir
      mountPath: /work-dir
  containers:
  - name12, k8s 之深入理解Pod对象

深入理解K8S——Pod Preemption资源抢占

深入剖析k8s中的存储

深入玩转K8S之存储资源管理

kubernetes概述之深入理解pod对象

混沌工程之 ChaosToolkit K8S 使用之删除 POD 实验