Karpenter 是一个 AWS 提供的节点生命周期管理器,它会观察传入的 pod 并根据情况启动正确的节点。节点选择决策基于策略并由传入的 pod 的规范驱动,包括资源请求和调度约束。
它的主要作用:
- 为不可调度的 Pod 启动节点
- 替换现有的节点提高资源利用率
- 如果超时或者不需要,则终止节点
- 在抢占前优雅终止节点
优点#
Karpenter 观察未调度的 Pod 的聚合资源请求,并做出启动和终止节点的决定,以最大限度地减少调度延迟和基础设施成本。
优势
- 更快:Karpenter 在 AWS 上直接与 EC2 队列 API 通信,可以根据 pod 的规格直接选择正确的实例类型,以实现快速自动扩展
- 更省钱:Karpenter 根据工作负载要求提供节点,可以将 pod 打包成最小数量的适当大小节点,并节省成本
- 更灵活:使用 karpenter 无需创建数十个节点即可实现灵活性和多样性
- 对比原生的 Cluster-Autoscaler,规避其原生的一些缺陷:例如在集群中添加或删除的容量限制、不能在 Auto Scaling 组中使用混合大小的集群...
- Karpenter 自动检测存储依赖,例如如果原来节点拉取在 A 区,Pod 第一次拉起时有创建 PV(Persistent Volume),后续节点启动决策时会自动创建在 A 区(因为 EBS 是单可用区的,如果手动创建跨 AZ 可能会导致访问不通)
使用方法#
前置准备#
- 需要部署 Metrics Server, 采集 pod 和 node 的指标用于资源约束
- 安装 Karpenter controller, 这是 Karpenter 的核心控制器
- 安装 Karpenter dashboard, 用于查看 Karpenter 管理的节点以及性能指标
karpenter provisioner 的模板
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
name: default
spec:
providerRef:
name: default
taints:
- key: example.com/special-taint
effect: NoSchedule
startupTaints:
- key: example.com/another-taint
effect: NoSchedule
labels:
billing-team: my-team
# Requirements that constrain the parameters of provisioned nodes.
# These requirements are combined with pod.spec.affinity.nodeAffinity rules.
# Operators { In, NotIn } are supported to enable including or excluding values
requirements:
- key: "karpenter.k8s.aws/instance-category"
operator: In
values: ["c", "m", "r"]
- key: "karpenter.k8s.aws/instance-cpu"
operator: In
values: ["4", "8", "16", "32"]
- key: "karpenter.k8s.aws/instance-hypervisor"
operator: In
values: ["nitro"]
- key: "topology.kubernetes.io/zone"
operator: In
values: ["us-west-2a", "us-west-2b"]
- key: "kubernetes.io/arch"
operator: In
values: ["arm64", "amd64"]
- key: "karpenter.sh/capacity-type" # If not included, the webhook for the AWS cloud provider will default to on-demand
operator: In
values: ["spot", "on-demand"]
kubeletConfiguration:
clusterDNS: ["10.0.1.100"]
containerRuntime: containerd
systemReserved:
cpu: 100m
memory: 100Mi
ephemeral-storage: 1Gi
kubeReserved:
cpu: 200m
memory: 100Mi
ephemeral-storage: 3Gi
evictionHard:
memory.available: 5%
nodefs.available: 10%
nodefs.inodesFree: 10%
evictionSoft:
memory.available: 500Mi
nodefs.available: 15%
nodefs.inodesFree: 15%
evictionSoftGracePeriod:
memory.available: 1m
nodefs.available: 1m30s
nodefs.inodesFree: 2m
evictionMaxPodGracePeriod: 3m
podsPerCore: 2
maxPods: 20
limits:
resources:
cpu: "1000"
memory: 1000Gi
consolidation:
enabled: true
ttlSecondsUntilExpired: 2592000 # 30 Days = 60 * 60 * 24 * 30 Seconds;
ttlSecondsAfterEmpty: 30
weight: 10
同时需要配置 node template 模板
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
name: default
spec:
providerRef:
name: default
taints:
- key: example.com/special-taint
effect: NoSchedule
startupTaints:
- key: example.com/another-taint
effect: NoSchedule
labels:
billing-team: my-team
# Requirements that constrain the parameters of provisioned nodes.
# These requirements are combined with pod.spec.affinity.nodeAffinity rules.
# Operators { In, NotIn } are supported to enable including or excluding values
requirements:
- key: "karpenter.k8s.aws/instance-category"
operator: In
values: ["c", "m", "r"]
- key: "karpenter.k8s.aws/instance-cpu"
operator: In
values: ["4", "8", "16", "32"]
- key: "karpenter.k8s.aws/instance-hypervisor"
operator: In
values: ["nitro"]
- key: "topology.kubernetes.io/zone"
operator: In
values: ["us-west-2a", "us-west-2b"]
- key: "kubernetes.io/arch"
operator: In
values: ["arm64", "amd64"]
- key: "karpenter.sh/capacity-type" # If not included, the webhook for the AWS cloud provider will default to on-demand
operator: In
values: ["spot", "on-demand"]
kubeletConfiguration:
clusterDNS: ["10.0.1.100"]
containerRuntime: containerd
systemReserved:
cpu: 100m
memory: 100Mi
ephemeral-storage: 1Gi
kubeReserved:
cpu: 200m
memory: 100Mi
ephemeral-storage: 3Gi
evictionHard:
memory.available: 5%
nodefs.available: 10%
nodefs.inodesFree: 10%
evictionSoft:
memory.available: 500Mi
nodefs.available: 15%
nodefs.inodesFree: 15%
evictionSoftGracePeriod:
memory.available: 1m
nodefs.available: 1m30s
nodefs.inodesFree: 2m
evictionMaxPodGracePeriod: 3m
podsPerCore: 2
maxPods: 20
limits:
resources:
cpu: "1000"
memory: 1000Gi
consolidation:
enabled: true
ttlSecondsUntilExpired: 2592000 # 30 Days = 60 * 60 * 24 * 30 Seconds;
ttlSecondsAfterEmpty: 30
weight: 10
原理#
利用 Karpenter 的分层约束模型,pod 运行受到三层约束
- 需要在依赖的应用程序或存储可用的区域中运行
- 需要特定种类的处理器或者硬件(需要有对应的 Node)
- 希望使用拓扑扩展等技术来确保高可用性
第一层由云服务器厂商对硬件的类型,区域进行限制;第三层由其他技术控制 pod 调度;Karpenter 通过设置 Provisioner 对指定节点进行调度和决策,从而满足第二层的控制
通过 Provisioner 可以做到的限制包括
- 资源请求:请求一定数量的内存或 CPU。
- 节点选择:选择在具有特定标签 (nodeSelector) 的节点上运行。
- 节点亲缘性: 绘制 pod 以在具有特定属性 (亲缘性) 的节点上运行。
- 拓扑扩展: 使用拓扑扩展来确保应用程序的可用性。
Pod 亲和性 / 反亲和性: 根据其他 Pod 的调度将 Pod 引向或远离拓扑域。
Karpenter 控制节点上下线的方式包括以下几种,
- 删除 Provisioner:删除 Provisioner 就会优雅下线其所有的节点
- Emptiness: 当节点上不存在非 daemonset 的工作负载,同时启动了 ttlSecondsAfterEmpty 时,会在时间结束后回收节点
- Expiration: 当设置了 ttlSecondsUntilExpired 后,节点到达过期时间就会自动下线
- Consolidation: Karpenter 基于积极的节约策略,主动删除或者将节点换成更便宜的节点
- 具体策略:
- 如果一个节点的所有 pod 都可以在集群中其他节点的空闲容量上运行,则可以删除节点。
- 如果一个节点的所有 pod 都可以在集群中其他节点的空闲容量和一个更便宜的替换节点的组合上运行,那么它就可以被替换。
- Interruption: 启动中断检测后 karpenter 会判断节点是否即将发生中断事件并主动下线节点
Drift:karpenter 会下线出现漂移的节点,同时也会主动检测,把实例上使用的 AMI 与 AWSNodeTemplate 设置的 AMI 不匹配的节点打上标记 - 手动删除: 通过 eksctl 主动删除节点,同样会被 karpenter 优雅下线处理
使用场景#
在 aws 中可以代替固定节点组的创建,基于 taint 以及 亲和性反亲和性,指定更灵活的策略进行节点的调度与约束