<!-- 《基于 Kubernetes 的云原生 DevOps》第9章 管理 Pod --> <!-- cloud-native-devops-with-kubernetes-ch-09 --> --- > There are no big problems, there are just a lot of little problems. > > -- Henry Ford --- [TOC] ## 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: stable` 和 `track: 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-0*、*redis-1* 等。 需要加入 Redis 集群的 Pod 可以专门联系 *redis-0*,但是只需要负载均衡的 Redis 服务的应用程序则可以通过 DNS 名称 *redis* 与随机选择的 Redis Pod 对话。 **作业(Job)** 部署会运行指定数量的 Pod ,并不断重启它们,而作业只需运行一定的次数,之后就会被视为完成。如批处理任务、队列任务等。 控制作业执行的字段: 1. *completions* 指定作业在视为完成之前,需要运行多少次指定的 Pod 。 只有成功退出才会计入次数。 2. *parallelism* 指定一次运行多少个 Pod 。 可以手动启动作业(如 *kubectl*、*Helm*),也可以自动启动(如持续部署流水线)。 **定时作业(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.minReplicas* 和 *spec.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高级用法](https://help.aliyun.com/document_detail/86533.html)** > 通过阿里云容器服务管理控制台创建的 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 提供了更复杂的负载均衡功能。 Loading... 版权声明:本文为博主「佳佳」的原创文章,遵循 CC 4.0 BY-NC-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://www.liujiajia.me/2022/5/16/cloud-native-devops-with-kubernetes-ch-09 提交