Python3 - k8s之深入理解 Pod
Posted 韩俊强
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python3 - k8s之深入理解 Pod相关的知识,希望对你有一定的参考价值。
Python3 - k8s之深入理解 Pod
文章目录
- Python3 - k8s之深入理解 Pod
一、 静态 Pod
在Kubernetes集群中除了我们经常使用到的普通的 Pod 外,还有一种特殊的 Pod,叫做Static Pod
,就是我们说的静态 Pod,静态 Pod 有什么特殊的地方呢?
静态 Pod 直接由特定节点上的kubelet
进程来管理,不通过 master 节点上的apiserver
。无法与我们常用的控制器Deployment
或者DaemonSet
进行关联,它由kubelet
进程自己来监控,当pod
崩溃时重启该pod
,kubelete
也无法对他们进行健康检查。静态 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
我们知道Pod
是Kubernetes
集群中的最小单元,而 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
命令来获取,如果有错误将可以看到FailedPostStartHook
或FailedPreStopHook
这样的 event。
2.3 Pod健康检查
liveness probe
(存活探针)和readiness probe
(可读性探针)
上面我们和大家一起学习了Pod
中容器的生命周期的两个钩子函数,PostStart
与PreStop
,其中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秒内,查看Pod
的Event
:
~ kubectl describe pod liveness-exec
我们可以观察到容器是正常启动的,在隔一会儿,比如40s后,再查看下Pod
的Event
,在最下面有一条信息显示 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 probe
和liveness 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
。两者如果同时使用的话就可以确保流量不会到达还未准备好的容器,准备好过后,如果应用程序出现了错误,则会重新启动容器。
另外除了上面的initialDelaySeconds
和periodSeconds
属性外,探针还可以配置如下几个参数:
* timeoutSeconds:探测超时时间,默认1秒,最小1秒。
* successThreshold:探测失败后,最少连续探测成功多少次才被认定为成功。默认是 1,但是如果是`liveness`则必须是 1。最小值是 1。
* failureThreshold:探测成功后,最少连续探测失败多少次才被认定为失败。默认是 3,最小值是 1。
这就是liveness probe
(存活探针)和readiness probe
(可读性探针)的使用方法。在Pod
的生命周期当中,我们已经学习了容器生命周期中的钩子函数和探针检测,接下来讲解Pod
层面生命周期的一个阶段:初始化容器。
三、初始化容器
上面我们学习了容器的健康检查的两个探针:liveness probe
(存活探针)和readiness probe
(可读性探针)的使用方法,我们说在这两个探针是可以影响容器的生命周期的,包括我们之前提到的容器的两个钩子函数PostStart
和PreStop
。我们今天要给大家介绍的是Init Container
(初始化容器)。
Init Container
就是用来做初始化工作的容器,可以是一个或者多个,如果有多个的话,这些容器会按定义的顺序依次执行,只有所有的Init Container
执行完后,主容器才会被启动。我们知道一个Pod
里面的所有容器是共享数据卷和网络命名空间的,所以Init Container
里面产生的数据可以被主容器使用到的。
是不是感觉Init Container
和之前的钩子函数有点类似啊,只是是在容器执行前来做一些工作,是吧?从直观的角度看上去的话,初始化容器的确有点像PreStart
,但是钩子函数和我们的Init Container
是处在不同的阶段的,我们可以通过下面的图来了解下:
从上面这张图我们可以直观的看到PostStart
和PreStop
包括liveness
和readiness
是属于主容器的生命周期范围内的,而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
启动过程中,初始化容器会按顺序在网络和数据卷初始化之后启动。每个容器必须在下一个容器启动之前成功退出。如果由于运行时或失败退出,导致容器启动失败,它会根据Pod
的restartPolicy
指定的策略进行重试。 然而,如果 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对象