Kubernetes 中 Pod 亲和性、反亲和性及应用开发最佳实践
1. Pod 亲和性与反亲和性调度
在 Kubernetes 中,通过 Pod 亲和性(Pod Affinity)和反亲和性(Pod Anti - Affinity)可以控制 Pod 的调度位置,以满足不同的业务需求。
1.1 Pod 调度评分示例
在调度后端 Pod 时,会根据不同的优先级规则为节点打分。例如:
... backend-qhqj6 -> node1.k8s: Taint Toleration Priority, Score: (10)
... backend-qhqj6 -> node2.k8s: InterPodAffinityPriority, Score: (10)
... backend-qhqj6 -> node1.k8s: InterPodAffinityPriority, Score: (0)
... backend-qhqj6 -> node2.k8s: SelectorSpreadPriority, Score: (10)
... backend-qhqj6 -> node1.k8s: SelectorSpreadPriority, Score: (10)
... backend-qhqj6 -> node2.k8s: NodeAffinityPriority, Score: (0)
... backend-qhqj6 -> node1.k8s: NodeAffinityPriority, Score: (0)
... Host node2.k8s => Score 100030
... Host node1.k8s => Score 100022
... Attempting to bind backend-257820-qhqj6 to node2.k8s
从上述输出可以看出,由于 Pod 间亲和性,node2 在调度后端 Pod 时获得了比 node1 更高的分数,最终 Pod 会被绑定到 node2 上。
1.2 在同一区域部署 Pod
-
同一可用区
:若要将前端 Pod 部署到与后端 Pod 相同的可用区,只需将
topologyKey属性设置为failure - domain.beta.kubernetes.io/zone。 -
同一地理区域
:若要将 Pod 部署到同一地理区域而非同一可用区,可将
topologyKey设置为failure - domain.beta.kubernetes.io/region。
1.3
topologyKey
工作原理
topologyKey
的工作原理较为简单。例如,你可以自定义
topologyKey
,如
rack
,前提是要为节点添加相应的标签。假设你有 20 个节点,10 个在一个机架,10 个在另一个机架,你可以将前 10 个节点标记为
rack = rack1
,后 10 个标记为
rack = rack2
。在定义 Pod 的
podAffinity
时,将
topologyKey
设置为
rack
。调度器在决定 Pod 部署位置时,会检查 Pod 的
podAffinity
配置,找到匹配标签选择器的 Pod,查找它们所在的节点,并根据
topologyKey
选择具有相同标签值的节点。
1.4 表达 Pod 亲和性偏好而非硬性要求
与节点亲和性类似,Pod 亲和性也可以表达偏好而非硬性要求。以下是一个使用
preferredDuringSchedulingIgnoredDuringExecution
Pod 亲和性规则的 Deployment 示例:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 5
template:
...
spec:
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: backend
containers: ...
在这个示例中,调度器会优先将前端 Pod 调度到与后端 Pod 相同的节点,但如果无法满足,也可以调度到其他节点。
1.5 使用 Pod 反亲和性调度 Pod 远离彼此
Pod 反亲和性用于让调度器避免将 Pod 调度到运行着匹配
podAntiAffinity
标签选择器的 Pod 的节点上。以下是一个强制前端 Pod 调度到不同节点的示例:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 5
template:
metadata:
labels:
app: frontend
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: frontend
containers: ...
创建这个 Deployment 后,只有部分 Pod 会被调度,因为调度器不允许将 Pod 调度到同一节点。在某些情况下,使用
preferredDuringSchedulingIgnoredDuringExecution
这样的软要求可能更合适。
2. 典型应用中的 Kubernetes 资源
一个典型的 Kubernetes 应用包含多种资源,它们相互协作以确保应用的正常运行。以下是这些资源的概述:
|资源类型|说明|
| ---- | ---- |
|Deployment/StatefulSet|包含 Pod 模板,其中包含一个或多个容器,每个容器有存活探针和就绪探针。|
|Service|用于暴露提供服务的 Pod,可配置为 LoadBalancer 或 NodePort 类型,或通过 Ingress 资源暴露。|
|Secret|分为用于从私有镜像仓库拉取容器镜像的 Secret 和供 Pod 内进程直接使用的 Secret,通常由运维团队配置。|
|ConfigMap|用于初始化环境变量或作为配置卷挂载到 Pod 中。|
|PersistentVolumeClaim|用于需要持久存储的 Pod,其引用的 StorageClass 由系统管理员预先创建。|
|Job/CronJob|在某些应用中需要使用。|
|DaemonSet|通常由系统管理员创建,用于在所有或部分节点上运行系统服务。|
|HorizontalPodAutoscaler|可由开发人员包含在清单中,也可由运维团队后期添加。|
|LimitRange/ResourceQuota|由集群管理员创建,用于控制单个 Pod 和所有 Pod 的计算资源使用。|
以下是这些资源之间关系的 mermaid 流程图:
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(Deployment/StatefulSet):::process --> B(Pod Template):::process
B --> C(Container):::process
C --> D(Liveness Probe):::process
C --> E(Readiness Probe):::process
B --> F(Volume):::process
A --> G(ReplicaSet):::process
G --> H(Pod):::process
H --> I(Service):::process
I --> J(Endpoints):::process
H --> K(Secret):::process
H --> L(ConfigMap):::process
H --> M(PersistentVolumeClaim):::process
M --> N(PersistentVolume):::process
N --> O(StorageClass):::process
A --> P(Job/CronJob):::process
Q(DaemonSet):::process --> R(Pod):::process
S(HorizontalPodAutoscaler):::process --> A
T(LimitRange/ResourceQuota):::process --> H
通过对这些资源的合理配置和使用,可以构建出高效、稳定的 Kubernetes 应用。
3. 理解 Pod 的生命周期
在 Kubernetes 中,Pod 的生命周期与传统虚拟机中的应用有所不同,应用开发者需要充分理解这些差异,以确保应用能够适应 Kubernetes 环境。
3.1 应用必须预期被杀死和迁移
在 Kubernetes 之外,运行在虚拟机中的应用很少从一台机器迁移到另一台机器,并且迁移时可以手动重新配置和检查应用状态。但在 Kubernetes 中,应用会更频繁且自动地被迁移,这就要求应用开发者确保应用能够相对频繁地被移动。
- 预期本地 IP 和主机名更改 :当 Pod 被杀死并在其他地方重新运行时,它会有新的 IP 地址、名称和主机名。大多数无状态应用通常可以处理这种变化,但有状态应用通常需要通过 StatefulSet 来运行,以确保在重新调度后启动时仍能看到相同的主机名和持久状态。不过,Pod 的 IP 地址仍然会改变,因此应用开发者不应基于成员的 IP 地址来确定集群应用的成员身份,如果基于主机名,则应始终使用 StatefulSet。
- 预期写入磁盘的数据消失 :如果应用将数据写入磁盘,除非挂载了持久存储,否则这些数据在应用在新 Pod 中启动后可能不可用。即使在单个 Pod 的生命周期内,由于容器可能因各种原因(如进程崩溃、存活探针失败、节点内存不足等)重启,写入容器文件系统的数据也会丢失。因为新容器会以全新的可写层启动。
3.2 使用卷来跨容器重启保留数据
为了确保在容器重启时数据不丢失,可以使用至少一个 Pod 作用域的卷。由于卷与 Pod 共存亡,新容器可以重用前一个容器写入卷的数据。以下是使用卷的示意图:
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(Container):::process --> B(Process):::process
B --> C(Writes to):::process
C --> D(Filesystem):::process
D --> E(volumeMount):::process
E --> F(Volume):::process
A --> G(Container crashes or is killed):::process
G --> H(New Container):::process
H --> I(New Process):::process
I --> J(Can read the same files):::process
J --> F
4. 总结
通过对 Pod 亲和性、反亲和性以及 Pod 生命周期的理解,我们可以更好地在 Kubernetes 中调度和管理应用。以下是关键要点总结:
1.
Taint 和 Toleration
:给节点添加 Taint 后,除非 Pod 容忍该 Taint,否则不会被调度到该节点。有 NoSchedule、PreferNoSchedule 和 NoExecute 三种类型的 Taint。
2.
Node Affinity
:可以指定 Pod 应调度到哪些节点,分为硬要求和节点偏好。
3.
Pod Affinity
:使调度器将 Pod 部署到与另一个 Pod 相同的节点(基于 Pod 的标签),
topologyKey
指定了 Pod 部署的接近程度。
4.
Pod Anti - Affinity
:用于使某些 Pod 相互远离,也可以指定硬要求或偏好。
5.
Pod 生命周期
:应用需要预期被杀死和迁移,包括 IP 和主机名的变化以及写入磁盘数据的丢失,可使用卷来保留数据。
合理运用这些知识,可以构建出更高效、稳定且适应 Kubernetes 环境的应用。在实际开发中,开发者应根据具体的业务需求和场景,灵活配置和使用这些特性,以充分发挥 Kubernetes 的优势。
超级会员免费看
1165

被折叠的 条评论
为什么被折叠?



