起初,Karpenter 是专为 AWS 环境设计的 Kubernetes 集群节点扩展工具。随着开源社区的繁荣发展,Karpenter 对阿里云和 Azure 提供了支持。
多年来,Kubernetes 节点的扩展解决方案一直是 Cluster Autoscaler(支持多个云厂商)。而在 AWS 上,这意味着通过调整 Auto Scaling Group 的实例数量来实现扩缩容,极大限制了扩缩容的速度。
这种方法存在一些问题:
Karpenter
Karpenter 是一款开源产品,可根据模板驱动实例创建(就像 ASG 根据模板驱动实例创建一样),但跳过了 ASG,从而实现了超快速的实例启动。节点几乎可以立即加入集群,但需要注意的是,节点准备就绪通常需要 60 秒左右。
01/Karpenter 如何工作?
Karpenter 会评估处于待调度状态的 Pod 数量,并立即将可以调度的 Pod 其调度到可用的容量。对于其余的 Pod,Karpenter 会根据用户提供的约束条件计算最佳的机器类型,并启动一台或几台能满足工作负载的机器。由于每台机器只需拉取一次镜像,因此 Pod 的启动速度会更快。
当 Karpenter 发现某个节点上没有运行任何 Pod 时,它就会缩小该 EC2 实例的规模。
02/Karpenter的优点
Karpenter 可以灵活组合不同规格 EC2 实例以适配工作负载,使 Spot 实例的利用效率和容错性大大提高。
Karpenter 的"consolidate"功能可主动检查节点利用率,并将工作负载整合到现有或新的节点中。
与 Cluster Autoscaler 相比,Karpenter 可以在一个节点上安装更多 Pod,从而节省成本。Karpenter 强制要求我们确保集群节点具备容错能力并能够恢复,这有助于提高集群的稳定性。
Karpenter 可以设置节点自动回收和更新最新 AMI 补丁的到期时间(TTL),从而减少故障影响范围。
Karpenter 对节点进行管理的方式是,当节点被删除时,它会首先将该节点标记为不可调度(Cordon),并停止向其调度更多的节点,然后将节点排空(Drain),将 Pod 迁移到其他节点上。
这种方式使得升级过程更加可控,并且如果操作得当(不是所有节点同时删除,并且设置了 Pod 中断预算),可以实现零停机时间。
节约成本
在 Karpenter 中节省的成本取决于您当前的工作负载和您在 Cluster Autoscaler 上使用的 EC2 实例类型。
下面是一个从R5.Large实例切换到一系列实例类型时每月成本节约的示例:
如何设置 Karpenter
您可以从官方 Helm Chart 中安装 Karpenter,但请注意,第一次安装可能会失败,因为 webhook 没有及时启动(这可能会在未来的版本中得到修复),在旧 Helm 的基础上重新安装一次即可正常工作。
接下来,为运行 Karpenter 创建专用节点。
Karpenter 自带用于指定 Provisioners 的自定义资源,这些 Provisioners 控制着要启动的实例类型和约束条件。
Provisioner 资源的示例:
apiVersion: karpenter.sh/v1alpha5kind: Provisionermetadata:name: defaultspec:# Enables consolidation which attempts to reduce cluster cost by both removing un-needed nodes and down-sizing those# that can't be removed. Mutually exclusive with the ttlSecondsAfterEmpty parameter.consolidation:enabled: true# If omitted, the feature is disabled and nodes will never expire. If set to less time than it requires for a node# to become ready, the node may expire before any pods successfully start.ttlSecondsUntilExpired: 2592000 # 30 Days = 60 * 60 * 24 * 30 Seconds;# If omitted, the feature is disabled, nodes will never scale down due to low utilizationttlSecondsAfterEmpty: 30# Priority given to the provisioner when the scheduler considers which provisioner# to select. Higher weights indicate higher priority when comparing provisioners.# Specifying no weight is equivalent to specifying a weight of 0.weight: 10# Provisioned nodes will have these taints# Taints may prevent pods from scheduling if they are not tolerated by the pod.taints:- key: example.com/special-tainteffect: NoSchedule# Provisioned nodes will have these taints, but pods do not need to tolerate these taints to be provisioned by this# provisioner. These taints are expected to be temporary and some other entity (e.g. a DaemonSet) is responsible for# removing the taint after it has finished initializing the node.startupTaints:- key: example.com/another-tainteffect: NoSchedule# Labels are arbitrary key-values that are applied to all nodeslabels: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 valuesrequirements:- key: "karpenter.k8s.aws/instance-category"operator: Invalues: ["c", "m", "r"]- key: "karpenter.k8s.aws/instance-cpu"operator: Invalues: ["4", "8", "16", "32"]- key: karpenter.k8s.aws/instance-hypervisoroperator: Invalues: ["nitro"]- key: "topology.kubernetes.io/zone"operator: Invalues: ["us-west-2a", "us-west-2b"]- key: "kubernetes.io/arch"operator: Invalues: ["arm64", "amd64"]- key: "karpenter.sh/capacity-type" # If not included, the webhook for the AWS cloud provider will default to on-demandoperator: Invalues: ["spot", "on-demand"]# Karpenter provides the ability to specify a few additional Kubelet args.# These are all optional and provide support for additional customization and use cases.kubeletConfiguration:clusterDNS: ["10.0.1.100"]containerRuntime: containerdsystemReserved:cpu: 100mmemory: 100Miephemeral-storage: 1GikubeReserved:cpu: 200mmemory: 100Miephemeral-storage: 3GievictionHard:memory.available: 5%nodefs.available: 10%nodefs.inodesFree: 10%evictionSoft:memory.available: 500Minodefs.available: 15%nodefs.inodesFree: 15%evictionSoftGracePeriod:memory.available: 1mnodefs.available: 1m30snodefs.inodesFree: 2mevictionMaxPodGracePeriod: 3mpodsPerCore: 2maxPods: 20# Resource limits constrain the total size of the cluster.# Limits prevent Karpenter from creating new instances once the limit is exceeded.limits:resources:cpu: "1000"memory: 1000Gi# References cloud provider-specific custom resource, see your cloud provider specific documentationproviderRef:name: default
如你所见,你甚至可以调整 Kubelet 的配置。
避免使用 Karpenter 的自定义启动模板
Karpenter 强烈建议不要使用自定义启动模板(Launch Templates)。使用自定义启动模板会导致以下问题:
此外,使用启动模板可能会引起困惑,因为在 Karpenter 的 Provisioners 中,有些字段被重复定义,而有些字段则会被 Karpenter 忽略,例如子网和实例类型。
您通常可以通过使用自定义用户数据或直接在 AWS 节点模板中指定自定义 AMI 来避免使用启动模板。
自定义 UserData:
Karpenter 提供了一个名为 AWSNodeTemplate 的资源,允许您在节点启动时注入用户数据(UserData):
apiVersion: karpenter.k8s.aws/v1alpha1kind: AWSNodeTemplatemetadata:name: al2-examplespec:amiFamily: AL2instanceProfile: MyInstanceProfilesubnetSelector:: my-clustersecurityGroupSelector:: my-clusteruserData: |: 1.0: multipart/mixed; boundary="BOUNDARY"--BOUNDARY: text/x-shellscript; charset="us-ascii"#!/bin/bashmkdir -p ~ec2-user/.ssh/touch ~ec2-user/.ssh/authorized_keyscat >> ~ec2-user/.ssh/authorized_keys <<EOFinsertFile "../my-authorized_keys" | indent 4 }}EOFchmod -R go-w ~ec2-user/.ssh/authorized_keyschown -R ec2-user ~ec2-user/.ssh--BOUNDARY--
排除不适合您工作负载的实例类型
- key: node.kubernetes.io/instance-typeoperator: NotInvalues:'m6g.16xlarge''m6gd.16xlarge''r6g.16xlarge''r6gd.16xlarge''c6g.16xlarge'
或者
spec:requirements:- key: karpenter.sh/capacity-typeoperator: Invalues:- spot- key: node.kubernetes.io/lifecycleoperator: Invalues:- spot- key: karpenter.k8s.aws/instance-memoryoperator: Gtvalues:- "4096"- key: karpenter.k8s.aws/instance-memoryoperator: Ltvalues:- "65536"- key: karpenter.k8s.aws/instance-cpuoperator: Gtvalues:- "2"- key: karpenter.k8s.aws/instance-cpuoperator: Ltvalues:- "32"- key: karpenter.k8s.aws/instance-familyoperator: Invalues:- c4- c5- c6- r5- r6i- key: kubernetes.io/archoperator: Invalues:- amd64
03/Karpenter 注意事项与建议
1.Karpenter 启动时默认使用容器运行时containerd,因此,如果您运行的是 Docker 命令(例如 Jenkins 等),或者您有类似 localhost 的 DNS 主机名,这些主机名可能会解析为 IPv6 地址::1,从而导致您的设置出现问题。
您可以通过配置 Provisioner,使其以DockerD启动:
spec:kubeletConfiguration:containerRuntime: dockerd
2.确保可以零停机终止 Pod,检查您的 preStopHooks,确保它们正确配置,以便在 Pod 终止前完成必要的清理操作。避免将终止进程分叉到新的 subshell 中运行,因为这可能导致终止流程的延迟或失败,从而影响服务的平稳过渡。
3.Karpenter 是一个非常快速的启动器,这意味着如果您在超过 1000 个 Pod 的规模下进行测试,可能会出现以下问题:
您可以通过以下设置调整 kubeletConfiguration 部分:
"registryPullQPS": 500,"registryBurst": 100,"kubeAPIQPS": 200,"kubeAPIBurst": 100
4.确保您不使用hostname topology,而是采用zone topology spread constrinats
5.监控并调整 Pod 对 CPU 和 MEM 的请求,使其达到实际运行水平,并留出足够的余量以防止 OOM 杀死进程。
6.确保为您的 Pod 配置了 PDB(Pod 中断预算),这样,当 Karpenter 清空节点时,它会遵守服务的 Pod 可用性,确保不会导致停机。
7.启用 Karpenter 的指标监控,并设置警报,如果 Karpenter 的就绪节点数少于其 ASG 中的节点数,则触发发出警报。
8.通过添加 finalizer 来保护您的 Karpenter 节点免受意外删除。
(在本例中,我的节点标签注为 core,请根据实际使用的标签进行更改)
kubectl get nodes -l core=true - no-headers | awk {'print $1'} | xargs kubectl patch node -p '{"metadata":{"finalizers":["kubernetes"]}}' - type=merge
9.点击下方卡片,回复【最佳实践】获取 Karpenter 在 EKS 上的最佳实践指南。
推荐阅读
劲省85%云成本!在K8s上使用Karpenter私有部署DeepSeek-R1
弹性工具选Karpenter还是Cluster Autoscaler?看这篇就知道啦!

项目介绍
Karpenter 于2021年11月推出并开源,是一款开源的Kubernetes集群自动扩缩容工具,专为优化 Kubernetes 集群的工作负载设计,旨在以灵活、高性能和简洁的方式实现节点的弹性扩展。今年9月已发布1.0版本。目前,Karpenter 已为全球超500家知名企业在生产环境中提供服务,包括阿迪达斯、Anthropic、Slack、Figma等。
Karpenter项目地址:
https://github.com/kubernetes-sigs/karpenter

