上一节我们成功创建了自己的第一个pod,但是这背后到底发生了什么,又有什么可以被我们自定义的还不知道。这一节我们一起来看看一个容器从被创建到被销毁经历的生命周期,并详细学习下其中的初始化过程:Init阶段。
我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。
容器的生命周期
下面这个图很好的说明了在敲完命令kubectl apply -f pod.yaml
之后经历的过程
- 首先kubectl和api server交互,然后到etcd,再到对应node的kubelet
- 然后kubelet和node上的CRI,也就是docker交互,开始图中得初始化流程
- 首先创建好pod内容器公用得pause网络栈,然后进行一系列得Init C流程
- 初始化工作完成后每个容器都会有自己的start初始化脚本要跑
- 可以设定一个探针,在初始化脚本一段时间后检测容器是否可提供服务,如果可以提供服务显示为running状态。也就是上面得readiness状态
- 同时还有另一个探针,不停检测容器是否有异常,并根据restartPolicy决定是否要重启。也就是上面得Liveness状态
- 最后在销毁容器之前,还可以运行一段结尾的stop脚本
下面我们就用实例对这些流程一个个的深入了解一下。这一节先看看第一个步骤Init C。
Init C
Init C是专门用作初始化的容器,其具有如下两个特点:
- Init容器总是运行到成功退出为止,如果Init容器失败,k8s会不停重启该Pod,直到成功为止
- 每个Init容器必须在上一个Init容器成功完成后才能运行
注意,如果restartPolicy是never的话,Init容器失败不会重新启动。
因为Init是区别于业务容器的单独容器,所以其可以完成如下工作:
- 可以安装某些可以被业务容器使用的工具,这些工具如果封装在业务镜像中会增加业务镜像的冗余
- 对业务容器的代码进行分离为创建和部署两阶段,将创建阶段放入Init容器中
- Init容器属于Linux命名空间,相对业务容器具有更高的文件系统权限,例如Secret权限。敏感文件处理在Init阶段完成使得业务容器安全性更高
- 针对某些有严重顺序依赖的两个Pod来说,可以再需要后启动的Pod的Init中去做判断,一直等到先启动的Pod成功完成,再退出Init,启动后一个Pod
说了这么多,下面来实际上手操作看看。
Init容器实际操作
下面所有操作的源码都被托管在github:
https://github.com/Victor2Code/centos-k8s-init/tree/master/test%20yaml/Pod%20Lifecycle%20-%20Init
下面创建一个带Init容器的yaml配置文件test-init-main.yaml
如下
apiVersion: v1
kind: Pod
metadata:
name: test-init-main
labels:
app: myapp
version: v1
spec:
containers:
- name: my-busybox
image: busybox
command: ['sh','-c','echo Main 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;']
busybox是用在嵌入式中的轻量级linux系统,这里用busybox做为容器的镜像。containers
下配置了一个主容器,功能只是启动后会打印一条记录,因为是从字符串获取shell命令,所以这里要用-c
选项。initContainers
下配置了两个Init容器,分别是等待myservice和mydb这两个服务起来后退出Init。service起来后,coreDNS里面就会有对应的解析IP,其余pod进行域名解析的话会自动获取到
shell命令的语法这里就不细说了,until do done的结构是直到until条件满足跳出循环
同时选择image的时候最好加上一个tag,而不要用默认的latest,因为latest的镜像每次都会去重新pull。即使是用的latest的image,也最好在本地打上自己的tag避免重复下载
运行kubectl apply -f test-init-main.yaml
可以看到容器停在了Init阶段,并且两个Init容器只完成了0个
[root@k8s-master k8s-test]# kubectl apply -f test-init-main.yaml
pod/test-init-main created
[root@k8s-master k8s-test]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
curl-6bf6db5c4f-kljp4 1/1 Running 1 40h 10.244.1.2 k8s-node1 <none> <none>
hellok8s 2/2 Running 0 21h 10.244.1.6 k8s-node1 <none> <none>
test-init-main 0/1 Init:0/2 0 12s 10.244.1.8 k8s-node1 <none> <none>
去看一下pod的详细信息
[root@k8s-master k8s-test]# kubectl describe pod test-init-main
Name: test-init-main
Namespace: default
Priority: 0
Node: k8s-node1/172.29.56.176
Start Time: Thu, 30 Apr 2020 09:44:01 +0800
Labels: app=myapp
version=v1
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"labels":{"app":"myapp","version":"v1"},"name":"test-init-main","namespace":"...
Status: Pending
IP: 10.244.1.8
Init Containers:
init-myservice:
Container ID: docker://58d01f2b5edd15b8f62c83386c2efa20bb1e95c9295b9807c258fb03a14c9486
Image: busybox
Image ID: docker-pullable://busybox@sha256:a8cf7ff6367c2afa2a90acd081b484cbded349a7076e7bdf37a05279f276bc12
Port: <none>
Host Port: <none>
Command:
sh
-c
until nslookup myservice; do echo waiting for myservice; sleep 2; done;
State: Running
Started: Thu, 30 Apr 2020 09:44:06 +0800
Ready: False
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-hln8x (ro)
init-mydb:
Container ID:
Image: busybox
Image ID:
Port: <none>
Host Port: <none>
Command:
sh
-c
until nslookup mydb; do echo waiting for mydb; sleep 2; done;
State: Waiting
Reason: PodInitializing
Ready: False
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-hln8x (ro)
Containers:
my-busybox:
Container ID:
Image: busybox
Image ID:
Port: <none>
Host Port: <none>
Command:
sh
-c
echo Main app is running && sleep 3600
State: Waiting
Reason: PodInitializing
Ready: False
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-hln8x (ro)
Conditions:
Type Status
Initialized False
Ready False
ContainersReady False
PodScheduled True
Volumes:
default-token-hln8x:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-hln8x
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events: <none>
可以看到init-myservice
这个Init容器还处于running状态,所以无法向下继续。
这时候我们通过yaml文件test-init-myservice.yaml
去创建一个叫myservice的服务
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
ports:
- protocol: TCP
port: 80
targetPort: 6666
这里是服务IP的80端口对应着容器的6666端口,这里先暂时不用管。确保服务正常起来
[root@k8s-master k8s-test]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 43h
myservice ClusterIP 10.107.244.119 <none> 80/TCP 6s
再去看pod的状态,发现第一个Init容器已经退出了
[root@k8s-master k8s-test]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
curl-6bf6db5c4f-kljp4 1/1 Running 1 42h 10.244.1.2 k8s-node1 <none> <none>
hellok8s 2/2 Running 0 23h 10.244.1.6 k8s-node1 <none> <none>
test-init-main 0/1 Init:1/2 0 115m 10.244.1.8 k8s-node1 <none> <none>
同样再通过yaml文件test-init-mydb.yaml
去创建mydb服务
apiVersion: v1
kind: Service
metadata:
name: mydb
spec:
ports:
- protocol: TCP
port: 80
targetPort: 7777
同样确保服务已经起来
[root@k8s-master k8s-test]# kubectl apply -f test-init-mydb.yaml
service/mydb created
[root@k8s-master k8s-test]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 44h
mydb ClusterIP 10.106.146.185 <none> 80/TCP 27s
myservice ClusterIP 10.107.244.119 <none> 80/TCP 56m
再看pod的状态,所有Init容器都已经退出,主容器处于running状态
[root@k8s-master k8s-test]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
curl-6bf6db5c4f-kljp4 1/1 Running 1 43h 10.244.1.2 k8s-node1 <none> <none>
hellok8s 2/2 Running 0 24h 10.244.1.6 k8s-node1 <none> <none>
test-init-main 1/1 Running 0 155m 10.244.1.8 k8s-node1 <none> <none>
查看一下日志,正好是我们想要打印的内容
[root@k8s-master k8s-test]# kubectl logs test-init-main
Main app is running
Init容器注意事项
下面是Init容器的几个注意事项:
- Init容器是在网络和数据卷初始化之后启动,所以Init容器并不是Pod的第一个容器。但是我们对网络和数据卷的初始化不能做任何操作
- Init容器只能一个接一个串联执行,而不能并行
- 在所有的Init容器成功之前,Pod将不会变为ready状态,也就意味着不会在service中进行聚集
- 如果pod重启,所有的Init容器都会被重新执行一遍。这也就意味着Init容器多次执行的结果不能有差异
- Pod已经在running的情况下可以用
kubectl edit pod xxx
去修改pod的一些属性。对Init容器只有修改image字段才会生效,之后会重启该pod - 在yaml文件中配置containers和initContainers的字段几乎一样,除了Init容器没有readinessProbe
- 在yaml文件中配置的主容器和Init容器的名字必须要唯一不能重复
总结
这一节我们用实例学习了容器创建的第一个阶段,Init C。下一节我们来详细学习下探针。