在k8s上Pod是运行容器应用及调度的最小单元,同一个Pod可以有一到多个容器,这些容器共享UTS、IPC和Network名称空间,并且能够访问同一组存储卷。封装了应用容器的Pod代表运行在k8s系统上的进程或进程组,它可由单个容器或少量具有强耦合关系的容器组成,用于抽象、组织和管理集群之上的应用程序。
Pod创建和删除过程
Pod的创建过程如上图所示:
- 用户通过kubectl或其它客户端发送创建Pod的请求给API Server
- API Server将Pod的相关信息写入etcd,等待写入成功后,API Server就会返回确认信息给客户端
- Scheduler监测到有新的Pod创建,就会为其选择一个合适的运行节点,并将调度结果更新至API Server
- 调度结果信息由API Server更新至etcd,并确认信息同步给Scheduler
- 相应工作节点上的kubelet监测到有新的Pod绑定到本节点后会读取Pod配置信息,调用本地容器运行时创建相应的容器启动Pod对象,然后将结果更新至API Server
- API Server将kubelet发来的Pod状态信息存至etcd,并将确认信息发至相应的kebelet
Pod删除过程如上图所示:
- 用户发送删除Pod的请求到API Server
- API服务器中的Pod对象会随着时间的推移而更新,在宽限期内(默认30s),Pod被视为dead
- 将Pod标记为Terminating状态
- (与步骤3同时进行) kubelet监测到Pod转为Terminating状态的同时启动Pod的关闭过程
- (与步骤3同时进行) 端点控制器监测到Pod对象的关闭行为时,将其从所有匹配到此端点的Service资源的端点列表中移除
- 如果Pod对象定义了preStop钩子处理器,在其被标记为Terminating状态后会立即以同步方式启动执行;如果宽限期结束后,preStop仍未执行完成,则第二步会被重启执行并额外获取一个时长为2秒的小宽限期
- Pod对象中的容器收到TERM信号
- 宽限期结束后,若Pod内还存在运行的进程,Pod对象会立即收到SIGKILL信号
- kubelet请求API Server将此Pod的宽限期设置为0从而完成删除操作,它变得对用户不可见
Pod常见状态
Pod对象被创建后可能存在很多状态,下面列出一些常见状态及含义:
- Unschedulable:Pod不能被调度,kube-scheduler没有匹配到合适的node节点
- PodScheduled:Pod正处于调度中
- Pending:正在创建Pod,但Pod中的容器还没有全部创建完成,如果Pod一直处于此状态应该检查Pod依赖的存储资源是否具备
- Failed:Pod中有容器启动失而导致Pod工作异常
- Unknow:由于某种原因无法获取Pod的当前状态,通常是由于与Pod所在的节点通信失败导致
- Initialized:Pod中的所有初始化容器已经完成
- ImagePullBackOff:Pod所在的节点下载镜像失败
- Running:Pod内部的容器已被创建并且正常运行
- Ready:表示Pod中的容器可以正常提供服务
- Error:Pod启动过程中发生错误
- NodeLost:Pod所在节点失联
- Waiting:Pod等待启动
- Terminating:Pod正在被删除
- CrashLoopBackOff:Pod运行出现错误,但kubelet正在将它重启
- InvalidImageName:node节点无法解析镜像名称导致镜像无法下载
- ImageInspectError:无法校验镜像,镜像不完整导致
- ErrImageNeverPull:策略禁止拉取镜像,镜像中心权限是私有等
- RegistryUnavailable:镜像服务器不可用,网络原因或镜像服务器故障
- ErrImagePull:镜像拉取出错,超时或下载被强制终止
- CreateContainerConfigError:不能创建kubelet使用的容器配置
- CreateContainerError:创建容器失败
- RunContainerError:Pod运行失败,容器中没有初始化PID为1的守护进程
- ContainersNotInitialized:Pod没有初始化完毕
- ContainersNotReady:Pod没有准备完毕
- ContainerCreating:Pod正在创建中
- PodInitialzing:Pod正在初始化中
- DockerDaemonNotReady:节点上的docker服务没有启动
- NetworkPluginNotReady:网络插件没有启动
pause容器
每一个Pod都有一个特殊的Pause容器,又叫Infra容器,是Pod的基础容器,镜像体积只有几百k左右,主要的功能是为Pod中的多个容器提供网络通信。
Pause容器被创建后会初始化Network Namespace,之后Pod内的其它容器就可以加入到Pause容器中共享Pause容器的Network Namespace了,因此如果一个容器中有两个容器A和B,那么关系如下:
- A和B可以直接使用localhost通信
- A和B可以看到相同的网卡、IP与端口监听信息
- Pod只有一个IP地址,也就是该Pod的Network Namespace对应的IP地址,被Pod内的容器共享使用
- Pod删除后Pause容器也会被删除,其IP被回收
Pause容器共享的Namespace有:
- Network Namespace:Pod中的多个容器共享同一个网络名称空间,即使用相同的IP和端口信息
- IPC Namespace:Pod中的多个容器可以使用System V IPC或POSIX消息队列进行通信
- UTS Namespace:Pod中的多个容器共享一个主机名
init容器
init容器官方文档:https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/init-containers/ Pod中的init容器主要可以用来为业务容器做一些初始化设置,例如下面这些场景:
- 可以为业务容器提前准备好需要的运行环境,,比如将业务容器需要的配置文件提前准备好并放在指定位置、检查数据权限或完整性、软件版本等基础运行环境
- 可以在运行业务容器之前准备好需要的业务数据,比如从OSS下载或者从其他位置copy
- 检查依赖的服务是否能够访问
init容器的特性:
- 一个Pod可以有多个init容器,但是每个init容器和业务容器的运行环境是隔离的
- init容器会比业务容器先启动
- 所有init容器运行成功之后才能启动业务容器,如果init容器运行失败会导致Pod被重启(Pod重启策略为Never时除外)
- 如果Pod定义有多个init容器,会以init容器定义顺序串行运行,直到所有init容器成功运行结束
- init容器不支持探针检测,因为执行完成退出后就不再运行了
init容器定义在Pod资源的.spec.initContainers字段中,其可用字段和.spec.containers字段类似,具体可以参考官方文档。下面是一个示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-with-initcontainers
spec:
replicas: 1
selector:
matchLabels:
app: nginx-with-initcontainers
template:
metadata:
labels:
app: nginx-with-initcontainers
spec:
initContainers:
- name: init-web-data #初始化容器1,负责为nginx生成html页面
image: centos:7.8.2003
command: ["/bin/bash", "-c", "for i in {1..10};do echo '<h1>'$i web page at $(date +%Y%m%d%H%M%S)'<h1/>' >>/data/nginx/html/myserver/index.html;sleep 2;done"]
volumeMounts:
- name: html-data
mountPath: /data/nginx/html/myserver
- name: change-data-owner #初始容器2,负责修改html文件的权限
image: busybox:1.28.3
command: ["/bin/sh", "-c", "/bin/chmod -R 644 /data/nginx/html/myserver/*"]
volumeMounts:
- name: html-data
mountPath: /data/nginx/html/myserver
containers:
- name: nginx
image: nginx
volumeMounts:
- name: html-data
mountPath: /usr/share/nginx/html/myserver
volumes:
- name: html-data
hostPath:
path: /tmp/nginx/html
访问Pod测试
查看Pod中文件权限
Pod重启策略
Pod对象因应用容器崩溃、启动状态检测失败、存活状态检测失败或者使用资源超出限制都会被终止,此时重启与否取决于Pod的restartPolicy字段的定义,该字段支持以下值:
- Always:无论因何原因、因何种方式终止,kubelet都将重启该Pod,此为默认设置
- OnFailure,仅在Pod中进程以非0方式退出时将其重启
- Never:不论容器运行状态如何都不会重启该Pod
容器探针检测
探针介绍
容器探针官方文档:https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/pod-lifecycle/#container-probes 探针检测是由kubelet定期对容器进行的诊断,以确保Pod的运行状态符合预期,通过探针检测可以减少运维问题并提高服务质量。
Pod内容器的探针检测主要分为3类:
- startupProbe:启动状态检测,判断容器内的进程是否已经启动完成,用来判断容器内的进程是否已经启动成功,如果配置了startupProbe会先禁用所有其它检测,直到startupProbe检测成功为止;如果startupProbe检测失败,kubelet会杀死容器,并按照重启策略进行下一步操作;如果容器没有定义startupProbe,则默认为Success
- LivenessProbe:存活状态检测,检测容器是否正在运行,如果存活检测失败,kubelet会杀死容器,并按照重启策略执行下一步操作;如果容器没有定义存活状态检测,则默认为Success
- ReadinessProbe:就绪状态检测,检测容器是否就绪可以对外提供服务,如果就绪检测失败,端点控制器将从与此Pod匹配的所有Service对象的端点中删除该Pod的IP地址;如果容器没有定义就绪状态检测,则默认为Success
使用探针检查容器主要有三种不同的方式实现,每个探针检测都必须定义为这三种方式中的一种:
- ExecAction:在容器内执行一条命令,如果命令返回码为0则表示成功
- TCPSocketAction:通过与容器上的TCP端口建立连接进行诊断,如果建立连接成功则表示成功
- HTTPGetAction:向容器的ip:port/path发送HTTP Get请求进行诊断,如果响应码为2xx或3xx则表示成功
每次探测都将获得以下3种结果之一:
- Success:成功,容器通过检测
- Failed:失败,容器检测失败
- Unknow:未知,诊断失败,因此不会采取任何行动
存活状态检测
容器探针有很多配置字段,具体可以参考https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ Pod配置中,.spec.containers.livenessProbe用于定义存活状态检测,其可嵌套使用的字段如下:
spec:
containers:
- name: ...
image: ...
livenessProbe:
exec: #命令式探针配置
command: <[]string> #要执行的命令
httpGet: #http探针配置
host: <string> #连接使用的主机名,默认是Pod IP,也可以在HTTP Header中设置HOST替代
httpHeaders: <[]Object> #请求中自定义的 HTTP 头。HTTP 头字段允许重复
path: <string> #http url路径
port: <int> #访问容器的端口号
scheme: <string> #请求协议,默认http,也可以选择https
tcpSocket: #tcp探针配置
host: <string> #连接的主机名,默认是Pod IP
port: <string> #连接的端口
initialDelaySeconds: <int> #容器成功启动后等待多少秒开始执行存活检测
periodSeconds: <int> #每隔多少秒执行一次存活检测
successThreshold: <int> #成功阈值,默认为1,对于存活检测来说只能为1
failureThreshold: <int> #失败阈值,默认为3
timeoutSeconds: <int> #检测超时时间
httpGet探针
下面是基于httpGet的存活状态检测探针示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: liveness-http-demo
spec:
replicas: 1
selector:
matchLabels:
app: liveness-http-demo
template:
metadata:
labels:
app: liveness-http-demo
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
livenessProbe:
httpGet: #向nginx的80端口发起http请求以验证存活状态
path: index.html
port: 80
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
failureThreshold: 3
timeoutSeconds: 1
查看pod状态,因为存活状态检测正常,不会被重启
然后修改请求路径为错误的路径重新重建Deployment,来验证存活状态检测失败的效果
重建之后查看Pod状态,通过kubectl describe可以看到Pod的存活检测失败
3次检测失败后,Pod会被重启,大约启动后20s左右
Pod多次重启之后存活检测都未成功,会被至于CrushLoopBackOff状态
tcpSocket探针
下面是基于tcpSocket的存活状态检测探针示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: liveness-tcpsocket-demo
spec:
replicas: 1
selector:
matchLabels:
app: liveness-tcpsocket-demo
template:
metadata:
labels:
app: liveness-tcpsocket-demo
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
livenessProbe:
tcpSocket: #通过与容器的80端口建立TCP连接来进行存活状态检测
port: http
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
failureThreshold: 3
timeoutSeconds: 2
exec探针
下面是基于exec的存活状态检测探针示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: liveness-exec-demo
spec:
replicas: 1
selector:
matchLabels:
app: liveness-exec-demo
template:
metadata:
labels:
app: liveness-exec-demo
spec:
containers:
- name: redis
image: redis
imagePullPolicy: IfNotPresent
ports:
- name: redis
containerPort: 6379
livenessProbe:
exec:
command:
- /usr/local/bin/redis-cli
- quit
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
failureThreshold: 3
timeoutSeconds: 2
就绪状态检测
容器的就绪状态检测定义在Pod的.spec.containers.readinessProbe字段中,同样支持httpGet、tcpSocket和exec3种方式,且它们各自的定义方式和存活探针检测一致。因此将容器的livenessProbe改为readinessProbe,并略作适应性修改就可以使用。下面以httpGet为例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: readiness-httpget-demo
spec:
replicas: 1
selector:
matchLabels:
app: readiness-httpget-demo
template:
metadata:
labels:
app: readiness-httpget-demo
spec:
containers:
- name: nginx
image: nginx
ports:
- name: http
containerPort: 80
readinessProbe:
httpGet:
port: 80
path: index.html
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
failureThreshold: 3
timeoutSeconds: 2
---
apiVersion: v1
kind: Service
metadata:
name: readinessprobe-svc
spec:
type: NodePort
selector:
app: readiness-httpget-demo
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
nodePort: 30180
创建后查看Pod状态,正常运行,也可以通过Service对象访问Pod
然后进入Pod将index.html移除,来模拟就绪状态检测失败的情况
通过kubectl desccribe命令查看,可以看就绪状态探测失败的信息
3次检测失败后,Pod中Ready的容器数量会变为0;同时Service会把Pod IP从对应的Endpoint中移除,此时就不能通过Service访问Pod了
启动状态检测
容器的启动状态检测定义在Pod的.spec.containers.startupProbe字段中,定义方式和其余两种探针一致。下面还是以httpGet为例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: startup-httpget-demo
spec:
replicas: 1
selector:
matchLabels:
app: startup-httpget-demo
template:
metadata:
labels:
app: startup-httpget-demo
spec:
containers:
- name: nginx
image: nginx
ports:
- name: http
containerPort: 80
startupProbe:
httpGet:
port: 80
path: index.html
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
failureThreshold: 3
timeoutSeconds: 2
创建后查看Pod状态,正常运行。如果启动状态检测失败,kubelet也会杀死容器,并按照重启策略进行处理,和存活状态检测处理方式一致。
综合示例
postStart和preStop钩子函数
postStart和preStop官方文档:https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/ k8s为容器提供了postStart和preStop钩子,它们的作用如下:
- postStart:在容器创建完成后立即执行的钩子,该钩子定义的的操作完成后容器才能真正的完成启动过程,如果执行失败不会继续创建Pod;k8s不能保证postStart钩子在容器的主应用程序之前运行
- preStop:在容器终止操作运行之前立即执行的钩子,它以同步方式调用,因此在它完成之前会阻塞删除容器的操作,这意味者preStop钩子的事件成功执行并退出,容器终止操作才能真正完成
钩子函数的实现方式类似于容器探针,也支持exec、tcpSocket和httpGet三种实现方式,配置格式和工作逻辑与容器探针都一致。
postStart和preStop定义在Pod内容器的lifecycle字段,下面是一个示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: lifecycle-demo
spec:
replicas: 1
selector:
matchExpressions:
- {key: app, operator: In, values: ["lifecycle-demo"]}
template:
metadata:
labels:
app: lifecycle-demo
spec:
containers:
- name: image
image: nginx
lifecycle:
postStart: #postStart配置
exec: #使用exec方式执行命令,生成默认页面
command: ["/bin/sh", "-c", "echo poststart web page >/usr/share/nginx/html/index.html"]
preStop: #preStop配置
exec: #使用exec方式执行命令,优雅终止nginx
command: ["/bin/sh", "-c", "nginx -s quit; while killall -0 nginx; do sleep 1; done"]
访问容器测试,可以看到index.html以被替换
Pod生命周期
如上图所示,如果一个Pod配置了上述所有行为定义,则一个Pod的生命周期运行步骤如下:
- 先创建pause容器,它初始化Pod基础环境,并为后续加入的容器提供共享名称空间
- 按顺序以串行方式运行各个初始化容器,任何一个初始化容器运行失败都会导致Pod创建失败,并按照重启策略处理,默认是重启
- 所有初始化容器运行完成后,启动应用程序容器。多应用容器Pod环境中,会并行启动所有应用容器,它们各自按其定义展开生命周期。应用容器创建之后会立即执行postStart钩子函数,该步骤失败会导致相关容器被重启
- 运行容器启动状态检测,判断容器是否启动成功,如果检测失败,同样按照重启策略进行处理;未定义时默认状态为Success
- 容器成功启动后,定期运行存活状态检测和就绪状态检测。存活状态检测失败会导致容器被重启;就绪状态检测失败会导致Pod从其所属的Service对象的可用端点列表中移除。
- 终止Pod对象时会先运行preStop钩子函数,并在宽限期结束后终止容器