基于 Kubernetes 的云原生 DevOps 第 9 章 管理 Pod
🏷️ Kubernetes 《基于 Kubernetes 的云原生 DevOps》
There are no big problems, there are just a lot of little problems.
-- Henry Ford
9.1 标签
标签是附加到 Kubernetes 对象上的键值对。
标签的主要作用是指定对用户有意义且相关的标志属性,但对核心系统没有直接性的语义含义。
换句话说,标签的存在是为了利用我们能看懂的信息标记资源,但这些信息对 Kubernetes 毫无意义。
apiVersion: v1
kind: Pod
metadata:
labels:
app: demo
2
3
4
5
上文中的 app: demo 就是一个标签,本身没有任何作用。但这类标签可以作为文档,人们看到这个就知道它允许了哪个应用程序。
选择器
选择器是一个表达式,能够匹配一个(或一组)标签。选择器是一种根据标签指定一组资源的方式。
apiVersion: v1
kind: Service
spec:
selector:
app: demo
2
3
4
5
上面的清单匹配任何带有 app 标签且值为 demo 的资源。
还可以通过 --selector
标志(缩写为 -l
)在 kubectl get
时指定:
PS C:\k8s> kubectl get pods --all-namespaces --selector app=myhello
NAMESPACE NAME READY STATUS RESTARTS AGE
default myhello-7b7468bb7f-5wcmr 1/1 Running 5 (6h37m ago) 15d
2
3
还可以通过 --show-labels
标志查看标签:
PS C:\k8s> kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
busybox 0/1 Error 0 3d22h run=busybox
demo 1/1 Running 5 (6h38m ago) 15d run=demo
myhello-7b7468bb7f-5wcmr 1/1 Running 5 (6h38m ago) 15d app=myhello,pod-template-hash=7b7468bb7f
2
3
4
5
结合不同的标签
PS C:\k8s> kubectl get pods -l app=myhello,pod-template-hash=7b7468bb7f
NAME READY STATUS RESTARTS AGE
myhello-7b7468bb7f-5wcmr 1/1 Running 5 (6h41m ago) 15d
2
3
等效的 YAML 清单:
selector:
app: myhello
pod-template-hash: 7b7468bb7f
2
3
不相等的标签
PS C:\k8s> kubectl get pods -l app!=myhello
NAME READY STATUS RESTARTS AGE
busybox 0/1 Error 0 3d22h
demo 1/1 Running 5 (6h41m ago) 15d
2
3
4
通过一组值来过滤标签
kubectl get pods -l run in (demo, busybox)
JiaJia:
说是可以这样用,但是执行报错了。
通过
kubectl get pods -h
查看帮助文档,-l
标志的说明如下:-l, --selector='': Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)
没有看到 in 操作符。另外还有一个 notin 操作符也是同样的情况。
等效的 YAML:
selector:
matchExpressions:
- {key: run, operator: In, values: [demo, busybox]}
2
3
JiaJia:
上面是书中给的写法,其它的清单中还有如下写法,不知道是不是等效的。
yamlselector: matchExpressions: - key: "run" operator: In values: ["demo", "busybox"]
1
2
3
4
5
要求标签不在指定的集合中:
kubectl get pods -l run notin (demo, busybox)
selector:
matchExpressions:
- {key: run, operator: NotIn, values: [demo, busybox]}
2
3
标签的其它用途
- 添加 environment 标签以区分模拟环境(staging)和生产环境(production)。
- 添加 version 标签以区分不同的版本。
- 执行金丝雀部署时,在两个部署中分别指定
track: stable
和track: canary
之类的标签。
标签和注释
- 标签和注释都是键值对,都提供了有关资源的元数据
- 标签可以标识资源
- 标签名和值有严格的限制
- 上限为 63 个字符
- 可拥有 DNS 子域形式的 253 个字符组成的可选前缀,并以斜杠字符将其与标签分开
- 只能字母或数字开头
- 只能包含字母、数字、连字符、下划线和点
9.2 节点亲和性
在大多数情况下,你不需要节点亲和性。Kubernetes 能够将 Pod 调度到正确的节点上。
节点的亲和性有两种类型:
- 硬亲和性:requiredDuringSchedulingIgnoredDuringExecution
必须满足该规则才能调度 Pod - 软亲和性:preferredDuringSchedulingIgnoredDuringExecution
最好能满足该规则,但并非关键
硬亲和性
使用 nodeSelectorTerms 指定匹配的节点。Kubernetes 只会将 Pod 调度到规则匹配的节点。
apiVersion: v1
kind: Pod
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: "failure-domain.beta.kubernetes.io/zone"
operator: In
values: ["us-central1-a"]
2
3
4
5
6
7
8
9
10
11
软亲和性:
使用 preference 指定匹配规则,另外还需要分配 1 ~ 100 的权重(weight)。表示 Kubernetes 可以将 Pod 调度到任何节点,但是会优先考虑规则匹配的节点。
apiVersion: v1
kind: Pod
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 10
preference:
- matchExpressions:
- key: "failure-domain.beta.kubernetes.io/zone"
operator: In
values: ["us-central1-a"]
- weight: 100
preference:
- matchExpressions:
- key: "failure-domain.beta.kubernetes.io/zone"
operator: In
values: ["us-central1-b"]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
9.3 Pod 的亲和性和反亲和性
有时可能需要将一组特定的 Pod 调度到同一个节点,或者反过来,需要避免将一组 Pod 调度到同一个节点。此时就需要使用到 Pod 的亲和性和反亲和性。
将 Pod 调度到一起
apiVersion: v1
kind: Pod
metadata:
name: server
labels:
app: server
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
labelSelector:
- matchExpressions:
- key: app
operator: In
values: ["cache"]
topologyKey: kubernetes.io/hostname
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
如果可能的话,将 server Pod 调度到一个正在运行带有 cache 标签的 Pod 的节点上。
因为是硬亲和,如果没有这样的节点或者匹配的节点没有足够的空闲资源运行 Pod,则该 Pod 将无法运行。
但实际上我们并不会这么做。如果两个 Pod 必须在一起,则请将两个容器放入同一个 Pod 中。
分开 Pod
将上例中的 podAffinity 改为 podAntiAffinity 就可以实现这个效果。
该 Pod 将不会被调度到一个正在运行带有 cache 标签的 Pod 的节点上。
apiVersion: v1
kind: Pod
metadata:
name: server
labels:
app: server
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
labelSelector:
- matchExpressions:
- key: app
operator: In
values: ["cache"]
topologyKey: kubernetes.io/hostname
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
软反亲和性
最好能满足规则,如果不能,Kubernetes 仍然会调度该 Pod。
可以指定多个匹配规则,根据权重排序。
apiVersion: v1
kind: Pod
metadata:
name: server
labels:
app: server
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
- matchExpressions:
- key: app
operator: In
values: ["cache"]
topologyKey: kubernetes.io/hostname
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
何时使用 Pod 亲和性
就像节点亲和性一样,你应该将 Pod 亲和性作为处理特殊情况的微调强化功能。
只有当你已经发现了生产环境的某个问题,而且 Pod 亲和性是唯一的修复办法时,才应当予以考虑。
9.4 污点和容忍
亲和性是 Pod 亲近(或远离)一组节点。污点允许节点根据节点的某些属性排斥一组 Pod。
使用 kubectl taint
命令,将污点添加到特定的节点上。
kubectl taint nodes docker-for-desktop dedicated=true:NoSchedule
在 docker-for-desktop 上添加一个名为 dedicated=true 的污点,其效果为 NoSchedule。
意思是:除非 Pod 拥有匹配的容忍,否则就不能调度到该节点。
查看特定节点上设置的污点:
kubectl describe node docker-for-desktop
删除节点上的污点(污点名称后面必须加一个减号 -
):
kubectl taint nodes docker-for-desktop dedicated:NoSchedule-
容忍 是 Pod 的属性,描述了它们能够忍受的污点。
设置 Pod 能够容忍的节点:
apiVersion: v1
kind: Pod
spec:
tolerations:
- key: "dedicated"
operator: "Equal"
value: "true"
effect: "NoSchedule"
2
3
4
5
6
7
8
效果:允许该 Pod 在拥有 dedicated=true 污点且效果为 NoSchedule 的节点上运行。
如果某个 Pod 由于受污染的节点而导致完全无法运行,则它将保持 Pending 状态。
污点和容忍还可以用于标记带有专有硬件(比如 GPU)的节点,已经允许某些 Pod 容忍某些类型的节点问题。
例如,如果某个几点掉线,Kubernetes 会自动添加污点 node.kubernetes.io/unreachable 。通常,这会导致 kubelet 驱逐节点上的所有 Pod。但是,网络有可能在合理的期限内恢复正常,因此某些 Pod 应该仍然保持运行状态。为此,你可以在这些 Pod 中添加一个与 unreachable 污点相匹配的容忍。
9.5 Pod 控制器
直接使用 docker container run
可以运行容器,但是这种方式非常有局限性:
- 如果容器由于某种原因退出,则必须手动重启。
- 容器只有一个副本;而且在手动运行的情况下,无法在多个副本之间实现负载均衡。
- 如果想实现高可用的副本,则必须决定在哪些节点上运行它们,并注意保持集群平衡。
- 更新容器时,必须注意依次停止每个正在运行的镜像,然后拉取并重新启动新镜像。
大多数应用程序使用部署就可以避免这些繁琐的操作,但除此之外,还有几种其它类型的 Pod 控制器。
守护进程集(DaemonSet)
守护进程(daemon)依次通常指在服务器上长时间运行的后台进程,负责处理日志记录之类的工作。与之类似,Kubernetes 守护进程集会在集群中的每个节点上运行一个守护进程容器。
守护进程集的清单看起来和部署非常相似:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
...
spec:
...
template:
...
spec:
containers:
- name: fluentd-elasticsearch
...
2
3
4
5
6
7
8
9
10
11
12
13
当需要在集群的每个节点上运行 Pod 的一个副本时,应使用守护进程集。
状态集(StatefulSet)
状态集(StatefulSet)也是一种 Pod 控制器,可以按指定的顺序启动和停止 Pod。
Kubernetes 会等到状态集中的每个副本都已运行且准备就绪,再启动下一个副本。
除了这些特殊的属性之外,状态集看起来和普通的部署非常相似。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
spec:
selector:
matchLabels:
app: redis
serviceName: "redis"
replicas: 3
template:
...
2
3
4
5
6
7
8
9
10
11
12
为了通过可预测的 DNS 名称(如 redis-1)访问各个 Pod,需要创建一个服务,并将 ClusterIP 类型设置为 None(称为 无头服务, Headless Service)。
非无头服务会获得一个 DNS 条目(如 redis),它可以在所有后端 Pod 上实现负载均衡。
无头服务也会获得一个服务的 DNS 名称,但是每个 Pod 还会单独获得一个带有编号的 DNS 条目,如 redis-0、redis-1 等。
需要加入 Redis 集群的 Pod 可以专门联系 redis-0,但是只需要负载均衡的 Redis 服务的应用程序则可以通过 DNS 名称 redis 与随机选择的 Redis Pod 对话。
作业(Job)
部署会运行指定数量的 Pod,并不断重启它们,而作业只需运行一定的次数,之后就会被视为完成。如批处理任务、队列任务等。
控制作业执行的字段:
- completions
指定作业在视为完成之前,需要运行多少次指定的 Pod。
只有成功退出才会计入次数。 - parallelism
指定一次运行多少个 Pod。
可以手动启动作业(如 kubectl、Helm),也可以自动启动(如持续部署流水线)。
定时作业(CronJob)
- spec.schedule:指定何时运行作业,与 Unix cron 程序的格式相同。
cron 表达式最低位为秒,这里舍弃了秒,最低位为分钟。 - spec.jobTemplate:指定要运行的作业模板,与普通作业清单完全相同。
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: demo-cron
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
...
2
3
4
5
6
7
8
9
Pod 水平自动伸缩器
水平伸缩是指调整服务的副本数量,垂直伸缩是指调整单个副本的大小。
Pod 水平自动伸缩器(Horizontal Pod Autoscaler, HPA)会监视指定的部署,并通过持续监控给定的指标来判断是否需要增加或减少副本的数量。
最常见的自动伸缩指标之一是 CPU 利用率。
基于 CPU 利用率的 HPA 示例:
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: demo-hpa
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: demo
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
targetAverageUtilization: 80
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
需要注意的字段:
- spec.scaleTargetRef:指定要扩展的部署
- spec.minReplicas 和 spec.maxReplicas:指定伸缩的限制
- spec.metrics:伸缩的判断指标
尽管 CPU 使用率是最常见的伸缩指标,但你可以使用任何 Kubernetes 指标,包括系统内置的指标(比如 CPU 和内存利用率)以及应用程序特有的服务指标(你可以在应用程序中定义和导出这些指标)。
PodPreset
一个尚处于 alpha 实验阶段的功能。
在创建 Pod 时注入信息。
PodPreset 这类对象叫做 准入控制器。
准入控制器会监视 Pod 的创建,当它的选择器与创建的 Pod 匹配时采取一定措施。
PodPreset 定义的设置会合并到每个 Pod 的设置中。
示例:所有与 tier: frontend 选择器匹配的 Pod 添加一个 cache 卷。
apiVersion: settings.k8s.io/v1alpha1
kind: PodPreset
metadata:
name: add-cache
spec:
selector:
matchLabels:
tier: frontend
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
2
3
4
5
6
7
8
9
10
11
12
13
14
操作器和自定义资源定义(CRD)
如果应用程序需要比状态集更复杂的管理,则可以自定义创建新类型的对象,即自定义资源定义(Custom Resource Definition,即 CRD)。
9.6 Ingress 资源
可以将 Ingress 视为位于服务前面的负载均衡器。Ingress 接收来自客户端的请求,并将其发送到服务。然后,服务根据标签选择器将它们发送到正确的 Pod。(实际上,请求直接从 Ingress 转到合适的 Pod,但从概念上可以认为请求经过了服务)
apiVersion: apps/v1
kind: Ingress
metadata:
name: demo-ingress
spec:
backend:
serviceName: demo-service
servicePort: 80
2
3
4
5
6
7
8
Ingress 规则
服务主要负责路由集群中的内部流量,Ingress 则负责将外部的流量路由到集群和适当的微服务上。
分列(fanout):根据请求 URL 将请求路由到不同的地方。
apiVersion: apps/v1
kind: Ingress
metadata:
name: fanout-ingress
spec:
rules:
- http:
paths:
- path: /hello
backend:
serviceName: hello
servicePort: 80
- path: /goodbye
backend:
serviceName: goodbye
servicePort: 80
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
可以根据 HPPT 的 Host 头部,路由不同域名的请求到合适的后端服务。
JiaJia:
书中没有给出示例,这是阿里云中 Kubernetes Host Ingress 的写法示例,其中 tls 使用了 secret 。
yamlapiVersion: extensions/v1beta1 kind: Ingress metadata: name: host-ing spec: rules: - host: host.liujiajia.me http: paths: - backend: serviceName: host-svc servicePort: 80 path: / - backend: serviceName: host-svc servicePort: 443 path: / tls: - hosts: - host.liujiajia.me secretName: liujiajia-me
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
使用 Ingress 终止 TLS
Ingress 可以使用 TLS(以前叫做 SSL 协议)处理安全连接。
apiVersion: apps/v1
kind: Ingress
metadata:
name: demo-tls-ingress
spec:
tls:
- secretName: demo-tls-secret
backend:
serviceName: demo-tls-service
servicePort: 80
2
3
4
5
6
7
8
9
10
使用已有的 TLS 证书
首先创建 Secret ,证书内容放在 tls.crt 字段中,密钥放在 tls.key 中。
apiVersion: v1
kind: Secret
type: kubernetes.io/tls
metadata:
name: demo-tls-secret
data:
tls.crt: LS0tLS1CRU...LQo=
tls.key: LS0tLS1CRU...Cg==
2
3
4
5
6
7
8
Ingress 控制器
Ingress 控制器负责管理集群中的 Ingress 资源。如果想自定义 Ingress 行为的话,可以添加 Ingress 控制器能够识别的特定注解。
阿里云的可以参考 官方文档:Nginx Ingress 高级用法
通过阿里云容器服务管理控制台创建的 Kubernetes 集群在初始化时会自动部署一套 Nginx Ingress Controller,默认其挂载在公网 SLB 实例上。
9.7 Istio
Istio 是一种服务网格,适用于多个应用程序和服务之间的相互通信。它可以处理服务之间的路由,并加密网络流量,此外还有其他重要的功能,例如指标、日志和负载均衡等。
9.8 Envoy
Envoy 提供了更为智能的负载均衡算法,如将请求路由到最不繁忙的后端(leastconn 或 LEAST_REQUEST)。
9.9 小结
- 标签是标识资源的键值对,可与选择器一起使用,以匹配指定的资源组。
- 节点亲和性可以让 Pod 亲近或远离具有指定属性的节点。例如,你可以指定 Pod 只能位于指定区域的节点上运行。
- 硬节点亲和性可能会阻止 Pod 的运行,而软节点亲和性则更像是给调度器的建议。你可以组合多个具有不同权重的软亲和性。
- Pod 亲和性表示我们希望将 Pod 优先安排到其它 Pod 的节点上。例如,希望在同一个节点上运行的 Pod 可以通过 Pod 亲和性来表示。
- Pod 反亲和性会排斥其它 Pod。例如,同一个 Pod 的副本之间的反亲和性有助于在整个集群中均匀地分布副本。
- 污点是一种用特定信息标记节点的方法,通常都是有关节点问题或故障的信息。在默认情况下,Pod 不会被调度到受污染的节点上。
- 容忍允许将 Pod 调度到带有特定污点的节点上。你可以利用这种机制在专用节点上运行某些 Pod。
- 你可以通过守护进程集在每个节点上安排一个 Pod 的副本(比如日志记录代理)。
- 状态集能够以特定的编号顺序启动和停止 Pod 副本,因此你可以通过可预测的 DNS 名称访问每个副本。状态集非常适合用于集群应用程序(例如数据库)。
- 作业在运行 Pod 一次(或指定次数)后完成。与之类似,定时作业会在指定的时间点周期性地运行 Pod。
- Pod 水平自动伸缩器会监视一组 Pod,并尝试优化给定的指标(例如 CPU 利用率)。它们会通过增加或减少所需的副本来实现指定的目标。
- PodPreset 可以在 Pod 创建时,为所有选中的 Pod 注入常用的配置。例如,你可以使用 PodPreset 为所有匹配的 Pod 挂载特定的卷。
- 自定义资源定义(CRD)允许你创建自己的自定义 Kubernetes 对象,以存储所需的数据。操作器是 Kubernetes 的客户端程序,可以为特定的应用程序(例如 MySQL)实现编排行为。
- Ingress 资源可以根据一组规则(比如匹配 URL 的某些部分)将请求路由到不同的服务。它们还可以终止应用程序的 TLS 连接。
- Istio 是一种为微服务应用程序提供高级联网功能的工具,而且还可以像 Kubernetes 应用程序一样使用 Helm 进行安装。
- 与标准的云负载均衡器以及服务网格工具相比,Envoy 提供了更复杂的负载均衡功能。