官方文档详尽介绍了Pod的概念。

概念

Pods are the smallest deployable units of computing that you can create and manage in Kubernetes.

简单来讲,Pod 是一组(也可以为一个)container 的集合,这些 container 一起调度,视为一个基本单元。

可以通过示例test-pod.yml,简单了解Pod:

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox
command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']

创建:

1
kubectl create -f test-pod.yml 

查看日志:

1
2
[root@master-1 ~]# kubectl logs myapp-pod
Hello Kubernetes!

这是一个非常简单的 Pod 样例。它的主要字段解释如下:

  • apiVersion/kind:这是所有资源共有的基本字段,表示 api 版本号以及类型。
    • apiVersion:表示资源所属于的 group 以及 version。结构一般为 <group/<version>,其中 v1 是特例,其 group 为 “”, 省去了中间的 /。一般核心的资源都是 v1,比如 Namespace / Pod / ConfigMap 等。其它的资源各有各自的 group 以及 version。
    • kind:资源类型,开头大写。
  • metadata
    • name:该 pod 的名字。
    • labels:pod 的标签。
    • namespace:可选字段。Kubernetes 中的资源分为两类,一类属于 namespace,一类不属于。Pod 属于 namespace,如果 yaml 里没有写 namespace,表示属于 default namespace
  • spec:pod 的主要信息部分。
    • containers:一个列表,因为可以有包含多个 container。
    • name:这个 container 的名字,一个 pod 下面的多个 container 名字不能冲突。
    • image:这个 container 的镜像信息。
    • command:启动命令。是可选项,因为一般镜像都有默认值。

You can use workload resources to create and manage multiple Pods for you. A controller for the resource handles replication and rollout and automatic healing in case of Pod failure. For example, if a Node fails, a controller notices that Pods on that Node have stopped working and creates a replacement Pod. The scheduler places the replacement Pod onto a healthy Node.

当然,很少在 Kubernetes 中直接创建一个个的 Pod,甚至是单实例(Singleton)的 Pod。 这是因为 Pod 被设计成了相对临时性的、用后即抛的一次性实体。 当 Pod 由你或者间接地由 控制器 创建时,它被调度在集群中的节点上运行。 Pod 会保持在该节点上运行,直到 Pod 结束执行、Pod 对象被删除、Pod 因资源不足而被 驱逐 或者节点失效为止。

重启 Pod 中的容器不应与重启 Pod 混淆。 Pod 不是进程,而是容器运行的环境。 在被删除之前,Pod 会一直存在。

在实际的使用中,直接用 Pod 是不适合的,因为必然会产生单点故障。我们需要有一种方法来方便地创建、管理同一个服务的多个实例 Pod。Kubernetes 中引入了 Workload 的概念,它可以理解为 Pod 的父资源,主要的作用就是来管理多个 Pod 的生命周期。

Workload 主要分为以下几类:

  • Deployment 和 ReplicaSet:最常见的类型。广泛适用于多种业务场景。
  • DaemonSet:在集群的每个节点上部署一个 Pod,适用于各种 agent 业务的场景。
  • StatefulSet:适用于各种有状态服务的场景。
  • Job 和 CronJob: 用于一些自动化任务。

有状态服务:需要数据存储功能的服务,例如MySQL。

Pod基础结构

必选参数

1
2
3
4
5
6
7
8
9
10
# pod的最基础的yaml文件最少需要以下的几个参数
apiVersion: v1 # API版本号,注意:具有多个,不同的对象可能会使用不同API
kind: Pod # 对象类型,pod
metadata: # 元数据
name: string # POD名称
namespace: string # 所属的命名空间
spec: # specification of the resource content(资源内容的规范)
containers: # 容器列表
- name: string # 容器名称
image: string # 容器镜像

标签和注释

1
2
3
4
5
6
7
8
# 放在metadata中
metadata:
labels: # 自定义标签的名称和值
- key: value
- ...
annotations: # 自定义注释的名称和值
- key: value
- ...

容器

常用参数
1
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
30
31
32
33
34
spec:
containers:
- name: string # 容器名称
# 镜像
image: string
imagePullPolicy: [Always| Never | IfNotPresent]
# 镜像拉取策略。Always:总是拉取;Never:从不拉取;IfNotPresent:不存在就拉取
# 启动参数
command: [string]
# 容器启动命令列表,相当于Dockerfile中的ENDRYPOINT,是唯一的。如果不指定,就是使用容器本身的。示例:["/bin/sh","-c"]
args: [string]
# 容器启动参数列表,相当于Dockerfile中的CMD,示例:["-c"]
# 容器工作目录
workingDir: string
# 环境变量
env:
- name: string # 变量名称
value: * # 变量值
- name: string
valueFrome: # 指定值的来源
configMapkeyRef: # 从ConfigMap中获取
name: string # 指定ConfigMap
key: string # 指定configMap中的key,赋值给变量
# 端口
ports: # 需要暴露的端口列表
- name: string # 端口名称
containerPort: int # 容器端口
hostPort: int # 容器所在主机需要监听的端口号,默认为与容器IP相同,一般可以不设置
protocol: string # 端口协议,支持TCP和UDP,默认为TCP
# 挂载
volumeMounts: # 挂载定义的存储卷到容器,需要通过volumes定义
- name: string # 定义的volume的名称
mountPath: string # 容器内挂载的目录的绝对路径(少于512字符)
readOnly: boolean(布尔值) # 是否只读
资源配合

如果一个容器只指明limit而未设定request,则request的值等于limit值
requests申请范围是0到node节点的最大配置,而limits申请范围是requests到无限,即0 <= requests <=Node Allocatable, requests <= limits <= Infinity。

CPU超过limits时,pod不会被kill,只会被限制。但是内存超过时,就会被kernel视为内存溢出(OOM)kill掉。

1
2
3
4
5
6
7
8
9
10
11
12
# 资源和请求的设置
resource:
limits: # 资源限制
cpu: string # CPU限制。两种方式可以直接指定使用核数,也可以用单位:m来指定。
# 0.5 :相当于0.5颗
# 一台服务器的CPU总量等于核数乘以1000。设机器的核数为两核,则总量为2000m。此时设置CPU限制为100m,则相当于是使用了100/2000,也就是5%。此时0.5=500m
memory: string # 内存限制。
# 单位:直接使用正整数表示Byte;k;m;g;t;p
# 不区分大小写(Kilobyte,Megabyte,Gigabyte,Terabyte,Petabyte)
requests: # 资源请求设置,也就是容器启动时的初始资源请求,一般和limits相同可不设
cpu: string
memory: string
健康检查

 健康检查有三种方式分别是

  • 脚本或命令
  • httpGet
  • tcp检测
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  livenessProbe: # 如果探测失败会重启容器
exec: # 通过在容器内执行命令或脚本的方式,命令执行状态码为0,视为探测成功
command: [string]
httpGet: # 通过http get的方式访问容器IP地址,并指定端口和路径,如果响应码会2xx或3xx视为成功
path: string # 访问路径,也就是UPI示例:/index.html
port: number # 访问端口
host: string # 访问的主机名,默认为容器IP,可不设
scheme: string # 用于连接的协议,默认为http,可不设
httpHeaders: # 自定义请求头
- name: string # 名称
value: string # 值
tcpSocket: # 通过tcp协议对端口进行检测如果端口可以连通就视为检测成功
port: number
# 检测参数配置
initialDelaySeconds: number # 初始延迟秒数,也就容器启动多久后开始检测
timeoutSeconds: number # 响应超时时间
periodSeconds: number # 检测周期,也就检测时间间隔

存储卷

1
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
spec:
volumes: # 存储卷有多种类型,以下为一些常用类型
- name: string # 存储卷名称
emptyDir: {} # 该类存储卷是临时生成的一个目录,与pod生命周期同步
- name: string
hostPath: # 挂载宿主机的目录
path: string # 用于挂载的目录
- name: string
nfs:
server: string # 服务IP地址
path: string # 用于挂载的目录
- name: string
persistentVolumeClaim: # 调用已经创建的持久卷
claimName: string # 持久卷声明的名称
- name: string
configMap: # 挂载ConfigMap到容器内
name: string # ConfigMap的名称
items: # 要调用的键,值会被写入文件,可以设定多个,在被volumeMounts调用时,这些文件会被一起放在挂载目录下,也可以挂入到一个文件
- key: string
path: string # 文件名
- name: string
secret: # 挂载secret到容器内
secretname: string
items:
- key: string
path: string

重启调度

1
2
3
4
5
6
7
spec:
restartPolicy: [Always|Never|OnFailure] # 重启策略 # OnFailure:只有在pod为非0码退出时才重启
nodeSelector: # 根据标签调度到的指定node节点,使用前需要对节点打标签
key: value # 使用命令kubectl label nodes node-name key=value
imagePullSecrets: # 指定镜像拉取时使用账户密码。需要先保存到Secret中
- name: string
hostNetwork: false # 是否使用主机网络,默认为false

Pod生命周期

Pod 遵循一个预定义的生命周期,起始于 Pending 阶段,如果至少 其中有一个主要容器正常启动,则进入 Running,之后取决于 Pod 中是否有容器以 失败状态结束而进入 Succeeded 或者 Failed 阶段。

Pod 的 status 字段是一个 PodStatus对象,其中包含一个 phase 字段。

Pod 的阶段(Phase)是 Pod 在其生命周期中所处位置的简单宏观概述。 该阶段并不是对容器或 Pod 状态的综合汇总,也不是为了成为完整的状态机。

Pod 阶段的数量和含义是严格定义的。 除了本文档中列举的内容外,不应该再假定 Pod 有其他的 phase 值。

下面是 phase 可能的值:

取值 描述
Pending(悬决) Pod 已被 Kubernetes 系统接受,但有一个或者多个容器尚未创建亦未运行。此阶段包括等待 Pod 被调度的时间和通过网络下载镜像的时间。若一直处于pending状态,可参考之前的文章排查。
Running(运行中) Pod 已经绑定到了某个节点,Pod 中所有的容器都已被创建。至少有一个容器仍在运行,或者正处于启动或重启状态。
Succeeded(成功) Pod 中的所有容器都已成功终止,并且不会再重启。
Failed(失败) Pod 中的所有容器都已终止,并且至少有一个容器是因为失败终止。也就是说,容器以非 0 状态退出或者被系统终止。
Unknown(未知) 因为某些原因无法取得 Pod 的状态。这种情况通常是因为与 Pod 所在主机通信失败。

如果某节点死掉或者与集群中其他节点失联,Kubernetes 会实施一种策略,将失去的节点上运行的所有 Pod 的 phase 设置为 Failed

一旦调度器将 Pod 分派给某个节点,kubelet 就通过 容器运行时开始为 Pod 创建容器。 容器的状态有三种:Waiting(等待)、Running(运行中)和 Terminated(已终止)。

Pod 拓扑分布约束

在 v1.18 之前的 Kubernetes 版本中,如果要使用 Pod 拓扑扩展约束,必须在 API 服务器 和调度器 中启用 EvenPodsSpread 特性门控。EvenPodsSpread:使 Pod 能够在拓扑域之间平衡调度。

通过拓扑分布约束(Topology Spread Constraints),可控制在集群内故障域之间的分布,例如区域(Region)、可用区(Zone)、节点和其他用户自定义拓扑域,以实现高可用并提升资源利用率。

示例

查看并配置节点标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@master-1 tmp]# kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
master-1 Ready control-plane,master 128d v1.20.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master-1,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=
node-1 Ready <none> 128d v1.20.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node-1,kubernetes.io/os=linux
node-2 Ready <none> 128d v1.20.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node-2,kubernetes.io/os=linux
[root@master-1 tmp]# kubectl label nodes node-1 address=Hangzhou
node/node-1 labeled
[root@master-1 tmp]# kubectl label nodes node-2 address=Chengdu
node/node-2 labeled
[root@master-1 tmp]# kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
master-1 Ready control-plane,master 128d v1.20.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master-1,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=
node-1 Ready <none> 128d v1.20.11 address=Hangzhou,beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node-1,kubernetes.io/os=linux
node-2 Ready <none> 128d v1.20.11 address=Chengdu,beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node-2,kubernetes.io/os=linux

新建Deployment:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:latest
name: nginx

创建:

1
2
3
4
5
6
[root@master-1 test]# kubectl create -f nginx.yml 
deployment.apps/nginx-test created
[root@master-1 test]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-55649fd747-h4c8z 1/1 Running 0 20s 10.16.84.136 node-1 <none> <none>
nginx-55649fd747-ncj5l 1/1 Running 0 20s 10.16.84.135 node-1 <none> <none>

可见两个Pod分布到同一个节点。

配置分布约束:

1
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
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:latest
name: nginx
topologySpreadConstraints:
- topologyKey: address
maxSkew: 1
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: nginx

说明:

  • maxSkew 描述 Pod 分布不均的程度。这是给定拓扑类型中任意两个拓扑域中 匹配的 pod 之间的最大允许差值。它必须大于零。取决于 whenUnsatisfiable 的 取值,其语义会有不同。
    • 当 whenUnsatisfiable 等于 “DoNotSchedule” 时,maxSkew 是目标拓扑域 中匹配的 Pod 数与全局最小值之间可存在的差异。
    • 当 whenUnsatisfiable 等于 “ScheduleAnyway” 时,调度器会更为偏向能够降低 偏差值的拓扑域。
  • topologyKey 是节点标签的键。如果两个节点使用此键标记并且具有相同的标签值, 则调度器会将这两个节点视为处于同一拓扑域中。调度器试图在每个拓扑域中放置数量 均衡的 Pod。
  • whenUnsatisfiable 指示如果 Pod 不满足分布约束时如何处理:
    • DoNotSchedule(默认)告诉调度器不要调度。
    • ScheduleAnyway 告诉调度器仍然继续调度,只是根据如何能将偏差最小化来对 节点进行排序。
  • labelSelector 用于查找匹配的 pod。匹配此标签的 Pod 将被统计,以确定相应 拓扑域中 Pod 的数量。

查看pod已分别调度到不同的node:

1
2
3
4
[root@master-1 test]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-58c948c775-h69xr 1/1 Running 0 39s 10.16.247.18 node-2 <none> <none>
nginx-58c948c775-nlcn2 1/1 Running 0 39s 10.16.84.137 node-1 <none> <none>