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 以及親和性反親和性,指定更靈活的策略進行節點的調度與約束