kubernetes基础概念-Pod
# kubernetes基础概念-Pod
# 1. Pod概念
Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。是一组(一个或多个) 容器 (opens new window); 这些容器共享存储、网络、以及怎样运行这些容器的声明。
简单来说,Pod就是Kubernetes里的“应用“;而一个应用可以由多个容器组成。而每一个Pod可以共享同一个命名空间,如:
Mount namespaces(挂载命名空间)
Network namespaces(网络命名空间)
UTS namespaces(主机名命名空间)
IPC namespaces(进程间通信命名空间)
PID namespaces(进程ID命名空间)
User namespaces(用户命名空间)
通过共享不同的命名空间,让每个Pod总是一起运行在同一个工作节点上。而每个Pod就像一个独立的逻辑机器,拥有自己的IP、主机名、进程等,并包含一个或多个容器,每个容器都运行一个应用进程。
# 2. 为什么需要Pod
首先需要了解一个概念,容器是一个“单进程”模型。一个正在运行的Docker容器,其实就是一个启用了多个Linux Namespace的应用进程,而这个进程能够使用的资源量,则受Cgroups配置的限制。
由于一个容器的本质就是一个进程,用户的应用进程实际上就是容器里PID=1的进程,也是其他后续创建的所有进程的父进程。这就意味着,在一个容器中,你没办法同时运行两个不同的应用,除非你能事先找到一个公共的PID=1的程序来充当两个不同应用的父进程。
容器本身设计之初就是希望容器和应用能够同生命周期,否则,一旦出现类似于“容器是正常运行的,但是里面的应用早已经挂了”的情况就很麻烦了。
而在真正的操作系统中,进程并不是单打独斗的,而是以进程组(线程组)的方式存在,可以登录到一台Linux服务器执行如下命令:
pstree -g
这条命令的作用,是展示当前系统中正在运行的进程的树状结构,返回结果如下:
systemd(1)-+-accounts-daemon(1984)-+-{gdbus}(1984)
| `-{gmain}(1984)
|-acpid(2044)
...
|-lxcfs(1936)-+-{lxcfs}(1936)
| `-{lxcfs}(1936)
|-mdadm(2135)
|-ntpd(2358)
|-polkitd(2128)-+-{gdbus}(2128)
| `-{gmain}(2128)
|-rsyslogd(1632)-+-{in:imklog}(1632)
| |-{in:imuxsock) S 1(1632)
| `-{rs:main Q:Reg}(1632)
|-snapd(1942)-+-{snapd}(1942)
| |-{snapd}(1942)
| |-{snapd}(1942)
| |-{snapd}(1942)
| |-{snapd}(1942)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
比如,这里有一个叫作rsyslogd的程序,它负责的是Linux操作系统里的日志处理。可以看到,rsyslogd的主程序main,和它要用到的内核日志模块imklog,imuxsock等,同属于1632进程组。这些进程相互协作,共同完成rsyslogd程序的职责。这三个进程一定要运行在同一台机器上,否则,它们之间基于Socket的通信和文件交换,都会出现问题。
所以,Google工程师提出了Pod的概念,通过以Pod为原子调度单位解决了两个问题:
- 进程组通信问题:进程互相之间会发生直接的文件交换、使用localhost或者Socket文件进行本地通信、会发生非常频繁的远程调用、需要共享某些Linux Namespace等等。
- 成组资源调度问题:如果以容器的“单进程”模型来看,为了保证进程的真实可用性,这三个进程就需要分为三个容器,同时三个容器必须要工作到同一个节点才可以正常使用。假设每个容器需要的资源是1GB,node01剩余内存有3GB,node02剩余内存有2.5GB,而最开始的时候有两个容器同时调度到了node02节点,但是进行imuxsock容器调度的时候发现,node02可用资源只有0.5 GB了,并不足以运行imuxsock容器,但是imuxsock容器又必须调度到node02节点上,这就是一个成组调度没有被妥善处理的例子。
而Pod内为了使用同一个namespace,就必须有一个中间容器起到“Hold”住namespace的作用,否则Pod中的多个容器就不是对等关系,而是拓扑关系了。
所以,在Kubernetes项目里,Pod的实现需要使用一个中间容器,这个容器叫作Infra容器。在这个Pod中,Infra容器永远都是第一个被创建的容器,而其他用户定义的容器,则通过Join Network Namespace的方式,与Infra容器关联在一起。
在Kubernetes项目里,Infra容器一定要占用极少的资源,所以它使用的是一个非常特殊的镜像,叫作:registry.k8s.io/pause
。这个镜像是一个用汇编语言编写的、永远处于“暂停”状态的容器,解压后的大小也只有100~200 KB左右。
在Infra容器“Hold住”Namespace后,用户容器就可以加入到Infra容器的Namespace当中了。
# 3. Pod使用
# 3.1. 创建Pod
在生产环境中,很少单独运行一个Pod,因为单独创建的Pod并不能实现一些高级的发布策略,所以在实际使用中经常会用Deployment、DaemonSet、StatefulSet等高级控制器调度并管理Pod。我们有时候也会单独启动一个Pod用于测试业务等,新建 nginx.yml
如下:
apiVersion: v1 # 必选,API的版本号
kind: Pod # 必选,类型Pod
metadata: # 必选,元数据
name: nginx # 必选,符合RFC 1035规范的Pod名称
namespace: web-testing # 可选,不指定默认为default,Pod所在的命名空间,命令行中可以使用 -n 指定命名空间
labels: # 可选,标签选择器,一般用于Selector
app: nginx
role: frontend # 可选,标签,可以写多个
annotations: # 可选,注释列表
app: nginx
spec: # 必选,用于定义容器的详细信息
restartPolicy: Always # 可选,容器重启策略,默认为Always
os:
name: linux # 可选,希望 Pod 运行在哪个操作系统之上
containers: # 必选,容器列表
- name: nginx # 必选,符合RFC 1035规范的容器名称
image: nginx:latest # 必选,容器所用的镜像的地址
imagePullPolicy: Always # 可选,镜像拉取策略
command:
- nginx # 可选,容器启动执行的命令
- -g
- "daemon off;"
resources: # 可选,资源限制和资源请求限制
limits: # 最大限制设置
cpu: 500m # 一个CPU等于1个物理CPU核或者1个虚拟核,0.1等价于100m
memory: 512Mi # 可以使用以数量为后缀,如G、M、k,也可使用2的幂数,如Gi、Mi、Ki
requests: # 可选,启动所需的资源
cpu: 100m
memory: 128Mi
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
使用kubectl apply命令从YAML文件创建Pod:
kubectl apply -f nginx.yml
查看pod的运行状态:
kubectl get pod nginx
# 3.2. Pod的状态
# 3.2.1. Phase
和裸容器部署一样,Pod在运行时也会有不同的状态,Pod的状态信息保存在PodStatus对象中,在PodStatus中有一个Phase字段,用于描述Pod在其生命周期中的不同状态。
通常情况下,在 Pod 的生命周期中,每个 Pod 会处于 5 个不同的 phase:pending,running,succeed,failed,unknown。
状态 | 说明 |
---|---|
Pending(挂起) | Pod 已被 Kubernetes 系统接收,但仍有一个或多个容器未被创建,可以通过 kubectl describe 查看处于 Pending 状态的原因 |
Running(运行中) | Pod 已经被绑定到一个节点上,并且所有的容器都已经被创建,而且至少有一个是运行状态,或者是正在启动或者重启,可以通过 kubectl logs 查看 Pod 的日志 |
Succeeded(成功) | 所有容器执行成功并终止,并且不会再次重启,可以通过 kubectl logs 查看 Pod日志 |
Failed(失败) | 所有容器都已终止,并且至少有一个容器以失败的方式终止,也就是说这个容器要么以非零状态退出,要么被系统终止,可以通过 logs 和 describe 查看 Pod 日志和状态 |
Unknown(未知) | 通常是由于通信问题造成的无法获得 Pod 的状态 |
可以通过命令查看:
kubectl get pods nginx -o yaml | grep phase
以下是一些常见的导致pod处于上述状态的原因:
状态 | 说明 |
---|---|
ImagePullBackOff ErrImagePull | 镜像拉取失败,一般是由于镜像不存在、网络不通或者需要登录认证引起的,可以使用 kubectl describe 命令查看具体原因 |
CrashLoopBackOff | 容器启动失败,可以通过 logs 命令查看具体原因,一般为启动命令不正确,健康检查不通过等 |
OOMKilled | 容器内存溢出,一般是容器的内存 Limit 设置的过小,或者程序本身有内存溢出,可以通过 kubectl logs 查看程序启动日志 |
Terminating | Pod 正在被删除,可以通过 kubectl describe 查看状态 |
SysctlForbidden | Pod 自定义了内核配置,但 kubelet 没有添加内核配置或配置的内核参数不支持,可以通过 kubectl describe 查看具体原因 |
Completed | 容器内部主进程退出,一般计划任务执行结束会显示该状态,此时可以通过 kubectl logs 查看容器日志 |
ContainerCreating | Pod 正在创建,一般为正在下载镜像,或者有配置不当的地方,可以通过 kubectl describe 查看具体原因 |
# 3.2.3. Conditions
conditions用于描述Pod当前处于哪个phase,以及处于该phase的原因。及作为一个辅助手段,详细的展示 pod 的状态信息,用于问题排查分析时提供更多依据。在同一时间内,1个Pod可能处于多个conditions。
状态 | 说明 |
---|---|
Initialized | 所有的 init containers 都已成功完成 |
Ready | 只有 pod 中的所有 container 就绪,且 pod 的 readiness probe 也完成了,意味着 pod 可以对外提供服务了 |
ContainersReady | Pod 中所有容器都已就绪,但这并不意味着 Pod 也 ready |
PodScheduled | Pod 已经被调度到某节点 |
可以通过命令查看:
kubectl describe pod nginx | grep Conditions: -A 5
# 3.3. Pod的探针(健康检查)
在生产环境下,进程正常启动并不代表应用能正常处理请求,所以合理地设计应用的健康检查尤为重要。在使用裸机或者裸容器部署时,一般很难对应用做很完善的健康检查,而Pod提供的探针可以很方便地用来检测容器内的应用是否正常。目前探针有3种检测方式,可以根据不同的场景选择合适的健康检查方式。
# 3.3.1. 探测类型
目前支持的探测器类型有3种,可以选择性地对容器进行检测。
种类 | 说明 |
---|---|
startupProbe | Kubernetes1.16 新加的探测方式,用于判断容器内的应用程序是否已经启动。如果配置了 startupProbe,就会先禁用其他探测,直到它成功为止。如果探测失败,Kubelet会杀死容器,之后根据重启策略进行处理,如果探测成功,或没有配置 startupProbe,则状态为成功,之后就不再探测。 |
livenessProbe | 用于探测容器是否在运行,如果探测失败,kubelet 会“杀死”容器并根据重启策略进行相应的处理。如果未指定该探针,将默认为 Success |
readinessProbe | 一般用于探测容器内的程序是否健康,即判断容器是否为就绪(Ready)状态。如果是,则可以处理请求,反之Endpoints Controller 将从所有的 Service 的Endpoints中删除此容器所在 Pod 的IP 地址。如果未指定,将默认为 Success |
startupProbe
startupProbe:
tcpSocket: # 端口检查方式
port: 80
initialDelaySeconds: 60 # 可选,容器启动后要等待多少秒后才启动探针检测
periodSeconds: 5 # 可选,执行探测的时间间隔,默认是 10 秒。最小值是 1
timeoutSeconds: 2 # 可选,探针超时后等待多少秒
successThreshold: 1 # 可选,检查成功为1次表示就绪
failureThreshold: 1 # 可选,检测失败1次表示未就绪
2
3
4
5
6
7
8
livenessProbe
livenessProbe:
exec:
command:
- cat
- /etc/hosts
initialDelaySeconds: 60 # 可选,容器启动后要等待多少秒后才启动探针检测
periodSeconds: 5 # 可选,执行探测的时间间隔,默认是 10 秒。最小值是 1
timeoutSeconds: 2 # 可选,探针超时后等待多少秒
successThreshold: 1 # 可选,检查成功为1次表示就绪
failureThreshold: 1 # 可选,检测失败1次表示未就绪
2
3
4
5
6
7
8
9
10
readinessProbe
readinessProbe:
httpGet: #
scheme: HTTP # 设置连接主机的协议,默认是HTTP
path: /_health
port: 80
httpHeaders: # 可选,自定义请求中的HTTP头
- name: end-user
value: jason
initialDelaySeconds: 60 # 可选,容器启动后要等待多少秒后才启动探针检测
periodSeconds: 5 # 可选,执行探测的时间间隔,默认是 10 秒。最小值是 1
timeoutSeconds: 2 # 可选,探针超时后等待多少秒
successThreshold: 1 # 可选,检查成功为1次表示就绪
failureThreshold: 1 # 可选,检测失败1次表示未就绪
2
3
4
5
6
7
8
9
10
11
12
13
# 3.3.2. 检查机制
实现方式 | 说明 |
---|---|
exec | 在容器内执行一个指定的命令,如果命令返回值为 0,则认为容器健康 |
tcpSocket | 通过 TCP 连接检查容器指定的端口,如果端口开放,则认为容器健康 |
httpGet | 对指定的URL 进行 Get 请求,如果状态码在 200~400 之间,则认为容器健康 |
grpc | 使用 gRPC (opens new window) 执行一个远程过程调用。如果响应的状态是 "SERVING",则认为容器健康 |
需要注意的是,同一种探测类型中只能实现一种检查机制。
前三种已经在探测类型中演示,这里不再赘述。
grpc
livenessProbe:
grpc: # grpc检查方式
port: 2379
initialDelaySeconds: 60 # 可选,容器启动后要等待多少秒后才启动探针检测
periodSeconds: 5 # 可选,执行探测的时间间隔,默认是 10 秒。最小值是 1
timeoutSeconds: 2 # 可选,探针超时后等待多少秒
successThreshold: 1 # 可选,检查成功为1次表示就绪
failureThreshold: 1 # 可选,检测失败1次表示未就绪
2
3
4
5
6
7
8
# 3.3.3. 探测结果
状态 | 说明 |
---|---|
Success(成功) | 容器通过检测 |
Failure(失败) | 容器检测失败 |
Unknown(未知) | 诊断失败,不采取任何措施 |
# 3.4. 镜像拉取策略和Pod恢复策略
# 3.4.1. 镜像拉取策略
可以使用 .spec.containers[].imagePullPolicy
指定镜像的拉取策略
操作方式 | 说明 |
---|---|
Always | 总是拉取,当tag为latest时,默认为Always |
Never | 无论是否存在都不拉取 |
IfNotPresent | 镜像不存在时拉取 |
# 3.4.2. Pod恢复策略
可以使用 .spec.restartPolicy
指定Pod的恢复策略
操作方式 | 说明 |
---|---|
Always | 默认策略。容器失效时,自动重启该容器 |
OnFailure | 容器以不为 0 的状态码终止,自动重启该容器 |
Never | 无论何种状态,都不会重启 |
这里我们新创建一个Pod进行演示:
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: test-liveness-exec
spec:
containers:
- name: liveness
image: nginx
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在这个Pod中,它在启动之后做的第一件事,就是在/tmp目录下创建了一个healthy文件,以此作为自己已经正常运行的标志。而30s过后,它会把这个文件删除掉。
与此同时,我们定义了一个这样的livenessProbe(健康检查)。它的类型是exec,这意味着,它会在容器启动后,在容器里面执行一条我们指定的命令,比如:cat /tmp/healthy
。这时,如果这个文件存在,这条命令的返回值就是0,Pod就会认为这个容器不仅已经启动,而且是健康的。这个健康检查,在容器启动5s后开始执行(initialDelaySeconds: 5),每5s执行一次(periodSeconds: 5)。
创建这个Pod,并查看Pod的状态:
kubectl create -f test-liveness-exec.yaml
sleep 20
kubectl get pod
2
3
等待30s后,再次查看Pod的状态:
30s后,我们删除了/tmp/healthy,健康检查探查到/tmp/healthy已经不存在了,但是Pod还是保持着Running的状态,而在这个过程中,Pod为什么保持Running状态不变呢?
我们看到RESTARTS字段变为了1,表示异常的容器已经被kubernetes重启了,而这个过程中,Pod保持Running状态不变。
而针对这种情况,只需要记住两个基本的设计原理:
- 只要Pod的restartPolicy指定的策略允许重启异常的容器(比如:Always),那么这个Pod就会保持Running状态,并进行容器重启。否则,Pod就会进入Failed状态 。
- 对于包含多个容器的Pod,只有它里面所有的容器都进入异常状态后,Pod才会进入Failed状态。在此之前,Pod都是Running状态。此时,Pod的READY字段会显示正常容器的个数。
# 3.5. 容器生命周期回调
容器生命周期回调,指的是在容器发生状态变化时,需要触发的一系列 "动作"。
参考 Attach Handlers to Container Lifecycle Events (opens new window)
种类 | 说明 |
---|---|
postStart | 容器创建后立即执行,注意由于是异步执行,它无法保证一定在 ENTRYPOINT 之前运行。如果失败,容器会被杀死,并根据 RestartPolicy 决定是 否重启 |
preStop | 容器终止前执行,常用于资源清理。执行完成之后容器将成功终止,如果失败,容器同样也会被杀死。在其完成之前 会阻塞删除容器的操作 |
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
preStop:
exec:
command: ["/bin/sh","-c","nginx -s quit; while killall -0 nginx; do sleep 1; done"]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
官方给出的文档中,使用了postStart和preStop命令,postStart在容器启动时输出了 echo Hello from the postStart handler 到message文件,而preStop实现了nginx的优雅退出。
需要注意的是,preStop会阻塞容器的结束流程,直到 "动作" 顺利完成后,才会允许容器结束。
参考文章及书籍: