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 能够容忍任意的污点,无论节点怎么设置 keyvalue 、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 会每分钟执行一次。