Pod 是 K8s 集群中创建和管理的、最小的可部署的计算单元。K8s 不会直接操作容器,而是通过 Pod 封装了容器,集群通过管控 Pod ,便可控制容器的存储、网络等资源,实现资源隔离或共享。
K8s Pod 中的所有容器共享一个相同的 Linux 命名空间(network、UTS、IPC),而不是每个容器一个命名空间,这一点与 Docker 不同,因此 Pod 中的多个容器使用相同的网络、主机名称,看到的是同一个 IP 地址。
不过进程还是按照容器进行隔离。由于 Mount、User 命名空间不共享,因此在容器中,文件系统和用户是隔离的。
启动流程
在 Kubernetes 中,当创建 Pod 时,会先启动一个 pause 容器,pause 容器创建了网络,然后应用容器以 Container 模式连接到该 pause 容器的网络。
生命周期
当 Pod 被分配到某个节点时, Pod 会一直在该节点运行,直到停止或被终止,Pod 在整个生命周期中只会被调度一次。
Pod 的整个生命周期可能有四种状态:
- Pending,尝试启动容器,如果容器正常启动,则进入下一个阶段;
- Running,处于运行状态;
- Succeeded、Failed,正常结束或故障等导致容器结束;
- Unknown,因为某些原因无法取得 Pod 的状态。
部署方式
Deployment
Deployment 管理部署 Pod,维持 Pod 的副本数量以及 Pod 监控和维护。
1kubectl apply -f nginx.yaml
kubectl apply 命令将会把推送的版本与以前的版本进行比较,并应用你所做的更改,并且不会自动覆盖任何你没有指定更改的属性。根据 Kubernetes 官方的文档,应始终使用
kubectl apply
或kubectl create --save-config
创建资源。
1kubectl create deployment testnginx --image=nginx:latest --dry-run=client -o yaml
--dry-run
取值必须为none、server或client。如果客户端策略,只打印将要发送的对象,而不发送它。如果是服务器策略,提交服务器端请求而不持久化资源。
ReplicaSet
Deployment 的 YAML 文件中有 replicas 字段,表示维持多少个 Pod 实例。
1spec:
2 progressDeadlineSeconds: 600
3 replicas: 1
创建一个 Deployment 时,Deployment 会自动创建一个 ReplicaSet ,ReplicaSet 管理的副本数量跟 YAML 文件中 replicas 字段值一致。
DaemonSet
DaemonSet 确保一个节点只运行一个 Pod。
DaemonSet 无视节点的排斥性,即节点可以排斥调度器在此节点上部署 Pod,DaemonSet 则会绕过调度器,强行部署。
DaemonSet 的一些典型用法:
- 在每个节点上运行集群守护进程
- 在每个节点上运行日志收集守护进程
- 在每个节点上运行监控守护进程
StatefulSet todo
扩容、缩容
scale
使用 kubectl scale
命令直接设置:
1kubectl scale deployment nginx --replicas=10
autoscale
Pod 水平自动扩缩可以基于 CPU 利用率自动扩缩 ReplicationController、Deployment、ReplicaSet 和 StatefulSet 中的 Pod 数量。Pod 自动扩缩不适用于无法扩缩的对象,比如 DaemonSet。
除了 CPU 利用率,也可以基于其他应程序提供的自定义度量指标 来执行自动扩缩。
参考资料: https://kubernetes.io/zh/docs/tasks/run-application/horizontal-pod-autoscale/
1kubectl autoscale deployment nginx --min=10 --max=15 --cpu-percent=80
表示目标 CPU 使用率为 80%
(期望指标),副本数量配置应该为 10 到 15 之间,CPU 是动态缩放 pod 的指标,会根据具体的 CPU 使用率计算副本数量,其计算公式如下。
1期望副本数 = ceil[当前副本数 * (当前指标 / 期望指标)]
按照算法计算,若当前副本数量为 12,且 CPU 使用率达到 90%,则期望副本数为 12*(90%/80%)
= 13.5,那么理论上会部署 14 个 Pod,但是 CPU 再继续增加的话,最多 15 个副本数量。如果在机器管够的情况下,可以去掉 min
和 max
参数。
比例缩放
比例缩放指的是在上线 Deployment 时,临时运行着应用程序的多个版本(共存),比例缩放是控制上线时多个 Pod 服务可用数量的方式。
水平缩放只关心最终的期望 Pod 数量,直接修改副本数和水平缩放,决定最终 Pod 数量有多少个。
而比例缩放是控制对象上线过程中,新 Pod 的创建速度和旧 Pod 的销毁速度、 Pod 的可用程度,跟上线过程中新旧版本的 Pod 替换数量有关。
查看上一章中创建的 Deployment 的部分 YAML 如下:
1spec:
2 progressDeadlineSeconds: 600
3 replicas: 1
4 revisionHistoryLimit: 10
5 selector:
6 matchLabels:
7 app: nginx
8 strategy:
9 rollingUpdate:
10 maxSurge: 25%
11 maxUnavailable: 25%
12 type: RollingUpdate
- maxUnavailable:最大不可用数量或比例,旧的 Pod 会以这个数量或比例逐渐减少。
- maxSurge:最大峰值,新的 Pod 会按照这个数量或比例逐渐创建。
如果想新版本的 Pod 上线速度更快,则可以把
maxSurge
数量或比例设置大一些;为了保证上线过程稳定、服务可用程度高,可以把maxUnavailable
设置小一些。
strategy
可以配置 Pod 是怎么更新的。当我们设置.spec.strategy.type==RollingUpdate
时,便会采取滚动更新的方式更新 Pods,此时可以指定 maxUnavailable
和 maxSurge
来控制滚动更新 过程。这个我们之前提到过,就是 Deployment 默认会保证一直有 75% 的 pod处于可用状态,在完成更新前可能有多个版本的 pod 共存。
标签与选择
label
Label 是附加到 Kubernetes 对象上的键值对,例如 Pod 可通过 kubectl describe pods
查询,可以看到每个 Pod 都带有 Labels: app=...
。
1... ...
2Labels: app=nginx
3 pod-template-hash=85b45874d9
4... ...
创建 Deployment 时,如果没有指定 Pod 的 app 标签值,那么一般跟 Deployment 名称一致。
在 Deployment 中,selector 字段设置了如何查找属于此对象的 Pod。
1 selector:
2 matchLabels:
3 app: nginx
命令式 label 选择
查询 Pod 所有的 Label:
1kubectl get pods --show-labels
查找符合条件的 pod
1kubectl get pods -l app=nginx
2kubectl get pods -l app!=nginx
列出不包含某个标签的 Pod:
1kubectl get pods -l '!env'
2kubectl get pods -l '!app'
获取同时包含两个标签的 Pod:
1kubectl get pods -l app,env
标签选择有等值和集合两种,其中等值选择有 =
、==
、!=
三种符号,=
和 ==
无任何区别,所以实际只有 等于
、不等于
两种选择情况。
在多个需求(多个label)的情况下,可以使用 &&
运算符,表示需要同时符合多个条件,但是选择器不存在 ||
这种逻辑或运算符。
查看符合两个条件的节点:
1# 多个条件使用 逗号","" 隔开,而不是 "&&"。
2kubectl get nodes -l disktype=ssd,disksize!=big
selector
1spec:
2 selector:
3 matchLabels:
4 app: nginx
5 template:
6 metadata:
7 labels:
8 app: nginx
-
spec.template
是定义所有 Pod 的模板,template.metadata
可以为所有 Pod 设置一些元数据,例如labels
。 -
spec.selector
定义 Deployment 如何查找要管理的 Pods。matchLabels
中的标签表示集合运算的in
,Pod 只需要包含这些标签,就会被此 Deployment 管理。
若 matchLabels
还可进一步使用 matchExpressions
注解
Kubernetes 注解为对象附加任意的非标识的元数据,注解使用 annotations 标识。客户端程序(例如工具和库)能够获取这些元数据信息。
annotations 由 key/value 组成,类似 label,但是 annotations 支持一些特殊字符,可以用作构建发布镜像时的信息、日志记录等。
1Annotations: meta.helm.sh/release-name: kubernetes-dashboard
2 meta.helm.sh/release-namespace: kubernetes-dashboard
亲和性、反亲和性
节点亲和性类似于 nodeSelector ,根据节点上的标签约束 Pod 可以调度到哪些节点。
Pod 亲和性有两种别为:
- requiredDuringSchedulingIgnoredDuringExecution:必须满足
- preferredDuringSchedulingIgnoredDuringExecution:尽力满足
1apiVersion: v1
2kind: Pod
3metadata:
4 name: with-node-affinity
5spec:
6 affinity:
7 nodeAffinity:
8 requiredDuringSchedulingIgnoredDuringExecution:
9 nodeSelectorTerms:
10 - matchExpressions:
11 - key: kubernetes.io/e2e-az-name
12 operator: In
13 values:
14 - e2e-az1
15 - e2e-az2
16 preferredDuringSchedulingIgnoredDuringExecution:
17 - weight: 1
18 preference:
19 matchExpressions:
20 - key: another-node-label-key
21 operator: In
22 values:
23 - another-node-label-value
24 containers:
25 - name: with-node-affinity
26 image: k8s.gcr.io/pause:2.0
如果我们设置了多个 nodeSelectorTerms :
1requiredDuringSchedulingIgnoredDuringExecution:
2 nodeSelectorTerms:
3 ...
4 nodeSelectorTerms:
则只需要满足其中一种表达式即可调度 Pod 到 节点上。
我们再回忆一下,节点选择器叫 nodeSelector
,而节点亲和性叫 nodeAffinity
,它们都可以让 Deployment 等对象部署 Pod 时选择合适的节点,它们都是使用标签(Label)来完成选择工作。
如果你同时指定了 nodeSelector
和 nodeAffinity
,则两者必须同时满足条件, 才能将 Pod 调度到候选节点上。
亲和性和反亲和性的 YAML 很复杂,需要使用时查看文档。
https://kubernetes.io/zh/docs/concepts/scheduling-eviction/assign-pod-node
污点、容忍度
当节点添加一个污点后,除非 Pod 声明能够容忍这个污点,否则 Pod 不会被调度到这个 节点上。
如果节点存在污点,那么 Pod 可能不会被分配到此节点上;如果节点一开始没有设置污点,然后部署了 Pod,后面节点设置了污点,节点可能会删除已部署的 Pod,这种行为称为驱逐。
节点污点(taint) 可以排斥一类特定的 Pod,而 容忍度(Toleration)则表示能够容忍这个对象的污点。
节点的污点可以设置为以下三种效果:
NoSchedule
:不能容忍此污点的 Pod 不会被调度到节点上;不会影响已存在的 pod。PreferNoSchedule
:Kubernetes 会避免将不能容忍此污点的 Pod 安排到节点上。NoExecute
:如果 Pod 已在节点上运行,则会将该 Pod 从节点中逐出;如果尚未在节点上运行,则不会将其调度到此节点上。
当节点设置污点后,无论其效果是哪一种,只要 Pod 没有设置相关的容忍度,Pod 就不会调度到此节点上。
系统默认污点
尽管一个节点上的污点完全排斥 Pod,但是某些系统创建的 Pod 可以容忍所有 NoExecute
和 NoSchedule
污点,因此不会被逐出。
例如 master 节点是不会被 Deployment 等分配 Pod 的,因为 master 有个污点,表面它只应该运行kube-system
命名空间中的很多系统 Pod,用户 Pod 会被排斥部署到 master 节点上。
当然我们通过修改污点,可以让用户的 Pod 部署到 master 节点中。
master 节点上会有一个 node-role.kubernetes.io/master:NoSchedule
的污点,Kubernetes 部署用户的 Pod 时会检查节点是否存在此污点,如果有,则不会在此节点上部署 Pod。
除了 node-role.kubernetes.io/master
,某些情况下节点控制器会自动给节点添加一个污点。当前内置的污点包括:
node.kubernetes.io/not-ready
:节点未准备好。这相当于节点状态Ready
的值为 “False
"。node.kubernetes.io/unreachable
:节点控制器访问不到节点. 这相当于节点状态Ready
的值为 “Unknown
"。node.kubernetes.io/out-of-disk
:节点磁盘耗尽。node.kubernetes.io/memory-pressure
:节点存在内存压力。node.kubernetes.io/disk-pressure
:节点存在磁盘压力。node.kubernetes.io/network-unavailable
:节点网络不可用。node.kubernetes.io/unschedulable
: 节点不可调度。node.cloudprovider.kubernetes.io/uninitialized
:如果 kubelet 启动时指定了一个 “外部” 云平台驱动, 它将给当前节点添加一个污点将其标志为不可用。在 cloud-controller-manager 的一个控制器初始化这个节点后,kubelet 将删除这个污点。
当节点上的资源不足时,会添加一个污点,排斥后续 Pod 在此 节点上部署,但不会驱逐已存在的 Pod。如果我们的 Pod 对机器资源有要求,可以排斥相关的污点,如果没要求,则需要容忍相关污点。
容忍度
污点和容忍度相互配合,用来避免 Pod 被分配到不合适的节点上;也可以让真正合适的 Pod 部署到有污点的节点上。
1// 容忍带有 key1 标签的污点,且无论是什么值。
2tolerations:
3- key: "key1"
4 operator: "Exists"
5 effect: "NoSchedule"
也可以设置带 value 的容忍。
1tolerations:
2- key: "key1"
3 operator: "Equal"
4 value: "value1"
5 effect: "NoSchedule"
如果 Pod 的容忍度设置为以下 YAML:
1tolerations:
2 operator: "Exists"
则表示此 Pod 能够容忍任意的污点,无论节点怎么设置 key
、value
、effect
,此 Pod 都不会介意。
如果要在 master 上也能部署 Pod,则可以修改 Pod 的容忍度:
1 spec:
2 tolerations:
3 # this toleration is to have the daemonset runnable on master nodes
4 # remove it if your masters can't run pods
5 - key: node-role.kubernetes.io/master
6 effect: NoSchedule
- 如果
operator
是Exists
此时不需要填写value
字段;如果存在 key 为 key1 的 label,且污点效果为NoSchedule
,无论是什么值都容忍。 - 如果
operator
是Equal
则它们的value
应该相等,如果相同的话,则容忍, - 如果
effect
留空 则表示只要是 label 为key1
的节点,都可以容忍。
Jobs、CronJobs
Job、Cronjob 它们用于创建一个或多个 Pod,来完成某些任务,它们创建的 Pod 不会长久的运行在节点中。
Job
Job 是用来只运行一次任务的对象,Job 对象以一种可靠的方式运行某 Pod 直到完成,适合用于批处理,例如编译程序、执行运算任务。Job 适合一次完整的流程,完成后即可抛弃的任务。
当 Job 启动时,Job 会跟踪成功完成的 Pod 的个数,当成功数量达到某个阈值时,Job 会被终结。当 Job 运行过程中,我们 暂停/挂起 Job,Job 会删除正在运行的 Pod,保留已完成的 Pod 数量,当恢复 Job 时,会创建新的 Pod,继续完成任务。
Job 的结构很简单,下面是一个示例,这个 Job 只有一个镜像,启动后执行 sleep 3
命令,容器会在3秒后自动退出,Job 标记其已经完成。
1apiVersion: batch/v1
2kind: Job
3metadata:
4 name: busybox
5spec:
6 template:
7 spec:
8 containers:
9 - name: busybox
10 image: busybox
11 command: ["/bin/sleep"]
12 args: ["3"]
13 restartPolicy: Never
对于这种简单的 Job,称为非并行的,它的特点有:
- 只启动一个 Pod,除非该 Pod 失败;
- 当 Pod 成功终止时,立即视 Job 为完成状态;
完成数
使用.spec.completions
来设置完成数时,Job 控制器所创建的每个 Pod 使用完全相同的 spec
模板。 这意味着任务的所有 Pod 都有相同的命令行,都使用相同的镜像和数据卷,甚至连 环境变量都(几乎)相同。
我们继续使用上次的 Job 模板,这里增加一个 completions
:
1apiVersion: batch/v1
2kind: Job
3metadata:
4 name: busybox
5spec:
6 completions: 5
7 template:
8 spec:
9 containers:
10 - name: busybox
11 image: busybox
12 command: ["/bin/sleep"]
13 args: ["3"]
14 restartPolicy: Never
查看 Job 和 Pod:
1root@instance-1:~# kubectl get jobs
2NAME COMPLETIONS DURATION AGE
3busybox 5/5 36s 38s
4root@instance-2:~# kubectl get pods
5NAME READY STATUS RESTARTS AGE
6busybox-rfhcj 0/1 Completed 0 9s
7busybox-stkbg 0/1 Completed 0 23s
8busybox-xk6sb 0/1 Completed 0 30s
9busybox-z6h9x 0/1 Completed 0 40s
10busybox-zqgcb 0/1 Completed 0 16s
Pod 的创建是串行的,每次只运行一个 Pod,当一个 Pod 处于 Completed 状态时,创建下一个 Pod。当有 5 个 Pod 处于 Completed 状态时,此 Job 标记完成。
并行
1kubectl get job busybox -o yaml
2... ...
3spec:
4 backoffLimit: 6
5 completions: 5
6 parallelism: 1
7 selector:
8 ... ...
可以看到 parallelism=1
,是控制并行度的字段,由于这个字段默认为 1,所以这个 Job 每次只能运行一个 Pod。
spec.completions
和spec.parallelism
,这两个属性都不设置时,均取默认值 1。
带类型的 Job
Job 中的 Pod 都是一样的,因此如果要 Job 处理不同的工作任务,则需要外界帮忙。
举个例子,平台是一个电商系统,消息队列中有评论、订单等五类消息,那么应该设计五种程序去处理这些消息,但是 Pod 只有一种。此时可以设置原子性的任务领取中心,Job 启动 Pod 后,Pod 便向任务中心领取任务类型,领取到后,开始工作。
那么 Job 中的 Pod 可能是这样完成工作的:
![[Pasted image 20230827163440.png]]
在 Job 创建的 Pod 中,会有个名为 JOB_COMPLETION_INDEX
的环境变量,此环境变量标识了 Pod 的索引,Pod 可以通过此索引标识自己的身份。
示例 YAML 如下:
1apiVersion: batch/v1
2kind: Job
3metadata:
4 name: busybox
5spec:
6 parallelism: 1
7 completions: 5
8 completionMode: Indexed
9 template:
10 spec:
11 containers:
12 - name: busybox
13 image: busybox
14 command: ["env"]
15 restartPolicy: Never
completionMode: Indexed
表明当前 Pod 是带索引的,如果completionMode: NonIndexed
则不带索引。索引会按照 0,1,2,3 这样递增。
执行 kubectl apply -f job.yaml
启动此 Job,会发现:
1root@master:~# kubectl get pods
2NAME READY STATUS RESTARTS AGE
3busybox-0-j8gvz 0/1 Completed 0 29s
4busybox-1-k4kfx 0/1 Completed 0 25s
5busybox-2-zplxl 0/1 Completed 0 14s
6busybox-3-pj4jk 0/1 Completed 0 10s
7busybox-4-q5fq9 0/1 Completed 0 6s
Job 终止和清理
如果我们不希望 Job 运行太长时间,可以为 Job 的 .spec.activeDeadlineSeconds
设置一个秒数值。 在 Job 的整个生命期,无论 Job 创建了多少个 Pod。 一旦 Job 运行时间达到 activeDeadlineSeconds
秒,其所有运行中的 Pod 都会被终止,并且 Job 的状态更新为 type: Failed
及 reason: DeadlineExceeded
。
YAML 示例:
1apiVersion: batch/v1
2kind: Job
3metadata:
4 name: busybox
5spec:
6 completions: 5
7 activeDeadlineSeconds: 2
8 template:
9 spec:
10 containers:
11 - name: busybox
12 image: busybox
13 command: ["/bin/sleep"]
14 args: ["3"]
15 restartPolicy: Never
CronJob
CronJobs 对于创建周期性的、反复重复的任务很有用,例如执行数据备份或者发送邮件。 CronJobs 也可以用来计划在指定时间时来执行的独立任务,例如计划当集群看起来很空闲时 执行某个 Job。
可供实验的 YAML 示例如下:
1apiVersion: batch/v1beta1
2kind: CronJob
3metadata:
4 name: hello
5spec:
6 schedule: "*/1 * * * *"
7 jobTemplate:
8 spec:
9 template:
10 spec:
11 containers:
12 - name: hello
13 image: busybox
14 imagePullPolicy: IfNotPresent
15 command:
16 - /bin/sh
17 - -c
18 - date; echo Hello from the Kubernetes cluster
19 restartPolicy: OnFailure
此 CronJob 会每分钟执行一次。