我们有一个 6 节点 Kubernetes 集群,运行大约 20 个大型副本集工作负载(Java 服务)。每个工作负载 pod(每个工作负载 1 个 pod)平均需要大约 30 秒才能启动,并且会占用大量 CPU。这使得同时启动多个 pod/工作负载成为一个问题 - 以至于当 2 个或 3 个 pod/工作负载在同一节点上同时启动时,它们需要几分钟才能启动,最终被就绪探测杀死。就绪探测相当宽松,但无限期延长宽限时间似乎不是一个好的做法。
可以想象,这使得封锁和耗尽一个节点变得有问题——如果我们耗尽一个节点,所有的 pod 会在其他地方同时重新启动,并可能导致工作程序过载(或使其陷入停滞导致多次重新启动,最终导致数据库锁定)。
为了解决这个问题,我编写了一个 shell 脚本,它使用 kubectl 列出 pod、重新启动每个 pod(通过修补元数据)、等待状态可用并转到下一个。
脚本可以很好地用于服务器修补或工作负载升级,但不能解决节点中断的问题——一切都在 AWS 中运行,当一个节点出现故障时,会通过自动扩展创建一个新的节点,但这意味着 4 个 pod 会尝试同时重新启动(当然通常是在周日早上 3 点)。
一个想法是让一个 init 容器知道其他正在启动的工作负载 - 如果当前没有其他工作负载在同一个节点上启动,则 init 容器退出并允许主容器启动。这需要服务帐户和权限,但可能是一种解决方法,但我想知道是否有更标准的方法通过配置(亲和性规则等)来实现这一点?
答案1
当 Pod 可以在任何地方调度时,就会遇到这种问题。使用亲和性规则,您走在了正确的轨道上。
您可以通过让部署副本集中的 Pod 彼此表达负亲和性(因此它们分散在节点之间)来使这些 Pod 彼此表达反亲和性。这会使调度变得有些繁重,但确实可以防止 Pod 在节点丢失时导致级联故障。它还可以很好地确保它们分散在故障域中,但这更像是副作用。
但是,还有一种更好的方法可以实现这一点 - 通过 pod 拓扑分布约束。通过指定分布约束,调度程序将确保 pod 在故障域(AZ 或节点)之间保持平衡,并且无法平衡 pod 会导致调度失败。
可以这样编写代码,保证 Pod 分布在节点之间,并且节点故障不会导致“聚集”。看一下这个示例 Pod:
kind: Pod
apiVersion: v1
metadata:
name: mypod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
- maxSkew: 1
topologyKey: node
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
containers:
- name: pause
image: k8s.gcr.io/pause:3.1
如果您不希望部署及其副本集与同一节点上的其他部署一起调度,则可以将此与亲和性规则结合使用,从而进一步减少“聚集”效应。在这种情况下,软反亲和性通常是合适的,因此调度程序将“尽量不”将这些工作负载放在一起。