Skip to content

基于 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 毫无意义。

yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: demo

上文中的 app: demo 就是一个标签,本身没有任何作用。但这类标签可以作为文档,人们看到这个就知道它允许了哪个应用程序。

选择器

选择器是一个表达式,能够匹配一个(或一组)标签。选择器是一种根据标签指定一组资源的方式。

yaml
apiVersion: v1
kind: Service
spec:
  selector:
    app: demo

上面的清单匹配任何带有 app 标签且值为 demo 的资源。

还可以通过 --selector 标志(缩写为 -l)在 kubectl get 时指定:

powershell
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

还可以通过 --show-labels 标志查看标签:

powershell
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

结合不同的标签

powershell
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

等效的 YAML 清单:

yaml
selector:
  app: myhello
  pod-template-hash: 7b7468bb7f

不相等的标签

powershell
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

通过一组值来过滤标签

powershell
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:

yaml
selector:
  matchExpressions:
  - {key: run, operator: In, values: [demo, busybox]}

JiaJia:

上面是书中给的写法,其它的清单中还有如下写法,不知道是不是等效的。

yaml
selector:
  matchExpressions:
  - key: "run"
    operator: In
    values: ["demo", "busybox"]

要求标签不在指定的集合中:

powershell
kubectl get pods -l run notin (demo, busybox)
yaml
selector:
  matchExpressions:
  - {key: run, operator: NotIn, values: [demo, busybox]}

标签的其它用途

  • 添加 environment 标签以区分模拟环境(staging)和生产环境(production)。
  • 添加 version 标签以区分不同的版本。
  • 执行金丝雀部署时,在两个部署中分别指定 track: stabletrack: canary 之类的标签。

标签和注释

  • 标签和注释都是键值对,都提供了有关资源的元数据
  • 标签可以标识资源
  • 标签名和值有严格的限制
    • 上限为 63 个字符
    • 可拥有 DNS 子域形式的 253 个字符组成的可选前缀,并以斜杠字符将其与标签分开
    • 只能字母或数字开头
    • 只能包含字母、数字、连字符、下划线和点

9.2 节点亲和性

在大多数情况下,你不需要节点亲和性。Kubernetes 能够将 Pod 调度到正确的节点上。

节点的亲和性有两种类型:

  • 硬亲和性:requiredDuringSchedulingIgnoredDuringExecution
    必须满足该规则才能调度 Pod
  • 软亲和性:preferredDuringSchedulingIgnoredDuringExecution
    最好能满足该规则,但并非关键

硬亲和性

使用 nodeSelectorTerms 指定匹配的节点。Kubernetes 只会将 Pod 调度到规则匹配的节点。

yaml
apiVersion: v1
kind: Pod
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: "failure-domain.beta.kubernetes.io/zone"
            operator: In
            values: ["us-central1-a"]

软亲和性:

使用 preference 指定匹配规则,另外还需要分配 1 ~ 100 的权重(weight)。表示 Kubernetes 可以将 Pod 调度到任何节点,但是会优先考虑规则匹配的节点。

yaml
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"]

9.3 Pod 的亲和性和反亲和性

有时可能需要将一组特定的 Pod 调度到同一个节点,或者反过来,需要避免将一组 Pod 调度到同一个节点。此时就需要使用到 Pod 的亲和性和反亲和性。

将 Pod 调度到一起

yaml
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

如果可能的话,将 server Pod 调度到一个正在运行带有 cache 标签的 Pod 的节点上。
因为是硬亲和,如果没有这样的节点或者匹配的节点没有足够的空闲资源运行 Pod,则该 Pod 将无法运行。

但实际上我们并不会这么做。如果两个 Pod 必须在一起,则请将两个容器放入同一个 Pod 中。

分开 Pod

将上例中的 podAffinity 改为 podAntiAffinity 就可以实现这个效果。
该 Pod 将不会被调度到一个正在运行带有 cache 标签的 Pod 的节点上。

yaml
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

软反亲和性

最好能满足规则,如果不能,Kubernetes 仍然会调度该 Pod。
可以指定多个匹配规则,根据权重排序。

yaml
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

何时使用 Pod 亲和性

就像节点亲和性一样,你应该将 Pod 亲和性作为处理特殊情况的微调强化功能。

只有当你已经发现了生产环境的某个问题,而且 Pod 亲和性是唯一的修复办法时,才应当予以考虑。

9.4 污点和容忍

亲和性是 Pod 亲近(或远离)一组节点。污点允许节点根据节点的某些属性排斥一组 Pod。

使用 kubectl taint 命令,将污点添加到特定的节点上。

powershell
kubectl taint nodes docker-for-desktop dedicated=true:NoSchedule

docker-for-desktop 上添加一个名为 dedicated=true 的污点,其效果为 NoSchedule
意思是:除非 Pod 拥有匹配的容忍,否则就不能调度到该节点。

查看特定节点上设置的污点:

powershell
kubectl describe node docker-for-desktop

删除节点上的污点(污点名称后面必须加一个减号 -):

powershell
kubectl taint nodes docker-for-desktop dedicated:NoSchedule-

容忍Pod 的属性,描述了它们能够忍受的污点。

设置 Pod 能够容忍的节点:

yaml
apiVersion: v1
kind: Pod
spec:
  tolerations:
  - key: "dedicated"
    operator: "Equal"
    value: "true"
    effect: "NoSchedule"

效果:允许该 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 守护进程集会在集群中的每个节点上运行一个守护进程容器。

守护进程集的清单看起来和部署非常相似:

yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-elasticsearch
  ...
spec:
  ...
  template:
    ...
    spec:
      containers:
      - name: fluentd-elasticsearch
        ...

当需要在集群的每个节点上运行 Pod 的一个副本时,应使用守护进程集。

状态集(StatefulSet)

状态集(StatefulSet)也是一种 Pod 控制器,可以按指定的顺序启动和停止 Pod。
Kubernetes 会等到状态集中的每个副本都已运行且准备就绪,再启动下一个副本。
除了这些特殊的属性之外,状态集看起来和普通的部署非常相似。

yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  selector:
    matchLabels:
      app: redis
  serviceName: "redis"
  replicas: 3
  template:
    ...

为了通过可预测的 DNS 名称(如 redis-1)访问各个 Pod,需要创建一个服务,并将 ClusterIP 类型设置为 None(称为 无头服务Headless Service)。
非无头服务会获得一个 DNS 条目(如 redis),它可以在所有后端 Pod 上实现负载均衡。
无头服务也会获得一个服务的 DNS 名称,但是每个 Pod 还会单独获得一个带有编号的 DNS 条目,如 redis-0redis-1 等。
需要加入 Redis 集群的 Pod 可以专门联系 redis-0,但是只需要负载均衡的 Redis 服务的应用程序则可以通过 DNS 名称 redis 与随机选择的 Redis Pod 对话。

作业(Job)

部署会运行指定数量的 Pod,并不断重启它们,而作业只需运行一定的次数,之后就会被视为完成。如批处理任务、队列任务等。

控制作业执行的字段:

  1. completions
    指定作业在视为完成之前,需要运行多少次指定的 Pod。
    只有成功退出才会计入次数。
  2. parallelism
    指定一次运行多少个 Pod。

可以手动启动作业(如 kubectlHelm),也可以自动启动(如持续部署流水线)。

定时作业(CronJob)

  • spec.schedule:指定何时运行作业,与 Unix cron 程序的格式相同。
    cron 表达式最低位为秒,这里舍弃了秒,最低位为分钟。
  • spec.jobTemplate:指定要运行的作业模板,与普通作业清单完全相同。
yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: demo-cron
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      ...

Pod 水平自动伸缩器

水平伸缩是指调整服务的副本数量垂直伸缩是指调整单个副本的大小
Pod 水平自动伸缩器(Horizontal Pod Autoscaler, HPA)会监视指定的部署,并通过持续监控给定的指标来判断是否需要增加或减少副本的数量。
最常见的自动伸缩指标之一是 CPU 利用率

基于 CPU 利用率的 HPA 示例:

yaml
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

需要注意的字段:

  • spec.scaleTargetRef:指定要扩展的部署
  • spec.minReplicasspec.maxReplicas:指定伸缩的限制
  • spec.metrics:伸缩的判断指标

尽管 CPU 使用率是最常见的伸缩指标,但你可以使用任何 Kubernetes 指标,包括系统内置的指标(比如 CPU 和内存利用率)以及应用程序特有的服务指标(你可以在应用程序中定义和导出这些指标)。

PodPreset

一个尚处于 alpha 实验阶段的功能。
在创建 Pod 时注入信息。
PodPreset 这类对象叫做 准入控制器
准入控制器会监视 Pod 的创建,当它的选择器与创建的 Pod 匹配时采取一定措施。
PodPreset 定义的设置会合并到每个 Pod 的设置中。

示例:所有与 tier: frontend 选择器匹配的 Pod 添加一个 cache 卷。

yaml
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: {}

操作器和自定义资源定义(CRD)

如果应用程序需要比状态集更复杂的管理,则可以自定义创建新类型的对象,即自定义资源定义(Custom Resource Definition,即 CRD)。

9.6 Ingress 资源

可以将 Ingress 视为位于服务前面的负载均衡器。Ingress 接收来自客户端的请求,并将其发送到服务。然后,服务根据标签选择器将它们发送到正确的 Pod。(实际上,请求直接从 Ingress 转到合适的 Pod,但从概念上可以认为请求经过了服务)

yaml
apiVersion: apps/v1
kind: Ingress
metadata:
  name: demo-ingress
spec:
  backend:
    serviceName: demo-service
    servicePort: 80

Ingress 规则

服务主要负责路由集群中的内部流量,Ingress 则负责将外部的流量路由到集群和适当的微服务上。

分列fanout):根据请求 URL 将请求路由到不同的地方。

yaml
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

可以根据 HPPT 的 Host 头部,路由不同域名的请求到合适的后端服务。

JiaJia:

书中没有给出示例,这是阿里云中 Kubernetes Host Ingress 的写法示例,其中 tls 使用了 secret

yaml
apiVersion: 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

使用 Ingress 终止 TLS

Ingress 可以使用 TLS(以前叫做 SSL 协议)处理安全连接。

yaml
apiVersion: apps/v1
kind: Ingress
metadata:
  name: demo-tls-ingress
spec:
  tls:
  - secretName: demo-tls-secret
  backend:
    serviceName: demo-tls-service
    servicePort: 80

使用已有的 TLS 证书

首先创建 Secret ,证书内容放在 tls.crt 字段中,密钥放在 tls.key 中。

yaml
apiVersion: v1
kind: Secret
type: kubernetes.io/tls
metadata:
  name: demo-tls-secret
data:
  tls.crt: LS0tLS1CRU...LQo=
  tls.key: LS0tLS1CRU...Cg==

Ingress 控制器

Ingress 控制器负责管理集群中的 Ingress 资源。如果想自定义 Ingress 行为的话,可以添加 Ingress 控制器能够识别的特定注解。

阿里云的可以参考 官方文档:Nginx Ingress 高级用法

通过阿里云容器服务管理控制台创建的 Kubernetes 集群在初始化时会自动部署一套 Nginx Ingress Controller,默认其挂载在公网 SLB 实例上。

9.7 Istio

Istio 是一种服务网格,适用于多个应用程序和服务之间的相互通信。它可以处理服务之间的路由,并加密网络流量,此外还有其他重要的功能,例如指标、日志和负载均衡等。

9.8 Envoy

Envoy 提供了更为智能的负载均衡算法,如将请求路由到最不繁忙的后端(leastconnLEAST_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 提供了更复杂的负载均衡功能。