这本手册全面介绍了这套七步操作方案,通过这套方案,某企业的EKS使用成本从每月85,000美元降到了34,000美元,而且完全没有修改过任何产品配置代码。
我曾经为10多家企业审核过他们的EKS集群,每次都会发现一些相同的浪费现象:节点资源被过度配置、跨区域的数据传输、闲置的EBS卷等等。而最代价高昂的错误,就是在没有先调整资源配置规模的情况下就购买计算资源。
这本手册正是解决这些问题的方案。在我实施这套七步操作方案的每家企业中,EKS的使用成本都降低了50%到60%。整个过程中既不需要修改任何产品配置代码,也不会导致任何系统停机时间,只是按照正确的顺序对基础设施进行了优化而已。
读完这本手册后,你将了解到如何根据实际需求调整Pod资源请求的规模,如何使用Karpenter工具实现智能的资源分配与成本优化,如何将兼容的工作负载迁移到Graviton平台上以降低20%的计算成本,以及如何通过VPC端点完全避免NAT Gateway带来的费用。
本手册中提到的所有Terraform模块、NodePool模板及自动化脚本,都可以在以下链接对应的仓库中找到:github.com/aayostem/eks-cost-optimization。该仓库为每一步操作提供了可以直接部署的配置方案,因此你可以在同一个下午内完成从阅读资料到实际应用的整个过程。
目录
您将学到什么
-
如何根据VPA的建议调整Pod资源请求的规模
-
Karpenter工具的完整配置方法,包括智能的资源分配与成本优化功能
-
如何将所有非GPU类型的工作负载迁移到Graviton3平台上
-
如何将EBS卷从gp2版本升级到gp3版本,从而降低20%的成本且性能不受任何影响
-
如何通过整合负载均衡器来优化系统性能
-
这套七步操作流程为何如此重要,以及为什么必须严格按照这个顺序来执行
让我们开始吧。
先决条件
在继续之前,您需要具备以下条件:
知识要求:
-
熟悉Kubernetes的使用——能够部署应用程序并检查Pod状态
-
具备基本的AWS知识——了解EC2实例类型、VPC以及EBS卷
-
能熟练阅读Terraform HCL文件和Kubernetes YAML配置文件
所需工具及权限:
-
一个运行着Kubernetes 1.27或更高版本的EKS集群
-
已配置好并指向您的集群的
kubectl命令行工具 - 已安装AWS CLI v2,并具有相应的操作权限
- 已安装Helm 3(用于使用Karpenter和Kubecost工具)
-
已在您的集群中安装了Metrics Server
配套代码库:在开始之前,请克隆该代码库。其中包含了本指南中提到的所有YAML文件、Terraform脚本以及Shell命令:
git clone https://github.com/aayostem/eks-cost-optimization
cd eks-cost-optimization
预计节省费用:对于一个每月运行成本为85,000美元的集群来说,如果采取本指南中的所有建议,通常可以每月节省40,000至55,000美元。而对于那些每月运营成本低于10,000美元的较小规模集群而言,费用削减幅度也会在40%到50%之间。
第1部分:成本构成分析——您的EKS费用都花在了哪里
1.1 典型的EKS成本构成
在采取任何优化措施之前,首先需要明确资金的具体使用去向。如果先错误地针对某个环节进行优化,团队可能会浪费数周的时间却看不到任何实际效果。
以下是每月运营成本为85,000美元的EKS集群的成本构成明细:
| 费用类别 | 月度支出金额 | 所占比例 | 可优化的空间 |
|---|---|---|---|
| 计算资源(EC2实例) | 52,000美元 | 61% | 优化空间较大——可能存在过度配置或选择了不合适的实例类型 |
| 数据传输费用 | 15,300美元 | 18% | 优化空间很大——尤其是跨区域的数据传输和NAT Gateway费用 |
| 存储费用(EBS卷) | 10,200美元 | 12% | 优化空间中等——未使用的EBS卷以及gp2与gp3存储类型的差异 |
| 负载均衡器费用 | 4,250美元 | 5% | 优化空间较小——通常适用于单一服务的负载均衡场景 |
| EKS控制平面费用 | 72美元 | <1% | 此项为固定成本,无需优化 |
| 其他费用 | 3,178美元 | 4% | 优化空间较小 |
计算资源和数据传输费用合计占总支出的79%,同时也是最容易进行优化的环节。这些才是我们应该重点关注的对象。
在开始任何操作之前,请运行以下命令查看您自己集群的实际成本构成情况:
# 获取上个月各服务的成本明细
# 将此输出结果保存下来——它将会成为你后续分析的基准数据
aws ce get-cost-and-usage \
--time-period Start=\((date -d 'last month' +%Y-%m-01),End=\)(date +%Y-%m-01) \
--granularity MONTHLY \
--group-by Type=DIMENSION,Key=SERVICE \
--metrics UnblendedCost \
--query 'ResultsByTime[0].Groups[*].{Service:Keys[0],Cost:Metrics.UnblendedCost.Amount}' \
--output table | sort -k3 -rn
将输出结果截图并保存下来。在执行每一步之后,你都可以将其与保存的截图进行对比,从而确认实际节省了多少资源,然后再进入下一步。
1.2 最昂贵的错误:错误的优化顺序
当大多数团队收到高额的AWS账单时,他们通常会采取以下措施:
-
立即购买节省计划,从而以30%的折扣锁定那些不必要的资源消耗。
-
随后开始使用Karpenter工具,却发现自己选择了错误类型的实例。
-
接着尝试迁移至Graviton技术,却发现自己的节省计划并不适用于ARM架构的实例。
最终的结果就是:他们需要签订为期12到36个月的合同,来支付那些本可以在三周内就避免产生的额外费用。
正确的操作顺序应该是:
步骤1:首先确定合适的资源需求量 ← 这是第一步
步骤2:实施Karpenter工具进行优化 ← 根据确定的资源需求量动态配置资源
步骤3:为非生产环境启用临时资源 ← Karpenter会自动处理备用资源的分配
步骤4:迁移至Graviton技术 ← Karpenter能确保迁移过程顺利进行
步骤5:添加VPC终端节点 ← 这样可以避免数据传输费用
步骤6:优化EBS存储空间 ← 这是一个快速见效的优化措施,可以与其他步骤同时进行
步骤7:整合负载均衡器 ← 最后进行整体资源结构的优化
只有在这之后,你才可以根据已经优化的配置结果来购买节省计划。
有一条原则必须遵守:先进行优化,然后再做出长期承诺。在购买节省计划之前执行的每一步操作,都能帮助你减少未来1到3年内需要承担的固定费用。
第二部分:确定合适的Pod资源需求量
2.1 为什么过度配置的资源会带来如此高的成本
Kubernetes会根据资源请求量来安排Pod的调度,而不是根据实际使用情况。例如,一个请求了2个vCPU和4GB内存的Pod,系统就会为它分配具备这些配置的节点,而无论该Pod是否真的需要这么多资源。
如果将资源请求量设置为最坏情况下的峰值数值,就会出现以下问题:
# 错误做法:在初始部署时设置资源请求量,之后不再进行任何调整
# 实际上,这个Pod平均只需要250毫秒级的CPU运算能力和512MB内存
resources:
requests:
cpu: "2" # 这个数值是实际需求的8倍
memory: "4Gi" # 同样也是实际需求的8倍
limits:
cpu: "4"
memory: "8Gi"
当每个Pod的资源需求量都被夸大了8倍时,你的集群所需的节点数量就会是实际工作负载所需数量的8倍。这就是你的账单中会出现61%计算费用的原因。
在做出任何调整之前,首先需要核实实际的资源使用情况:
# 如果还没有安装Metrics Server,请先进行安装
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 检查每个Pod的实际CPU和内存使用量
# 将这些数值与你之前设置的资源请求量进行对比
kubectl top pods --all-namespaces --sort-by=cpu
预期输出会显示典型的资源使用差距:
命名空间 名称 CPU核心数 内存大小(字节)
production payment-api-xxx 25百万 128兆字节
production user-api-xxx 15百万 96兆字节
production notification-svc-xxx 5百万 64兆字节
staging worker-xxx 10百万 256兆字节
如果你的Pod每个都请求2个CPU核心,但实际上只使用了15百万到25百万个核心,那么你的资源申请比例实际上高达50到80倍。这意味着你为集群中的每一个节点支付的费用,其实大部分都是被闲置的空间所占据的。
2.2 使用垂直Pod自动扩展器进行资源优化配置
垂直Pod自动扩展器(VPA)是Kubernetes中的一个组件,它能够分析每个部署任务的历史CPU和内存使用情况,并推荐最佳的资源配置方案。你可以先将其设置为仅提供建议模式——它会告诉你应该设置什么参数,但不会自动进行任何更改,这样你就可以自行审核并决定是否应用这些建议。
正确的配置方式如下:
# 正确的配置:将VPA设置为仅提供建议模式
# VPA会监测Pod的实际资源使用情况24小时以上,然后给出合适的资源配置建议
# updateMode: "Off"表示它只会提供建议,而不会自动重启Pod
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: payment-api-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: payment-api
updatePolicy:
updateMode: "Off" # 仅提供建议,需手动应用
resourcePolicy:
containerPolicies:
- containerName: "*"
minAllowed:
cpu: "100百万" # VPA不会推荐低于这个最低值的配置
memory: "256兆字节"
max Allowed:
cpu: "2" # VPA不会推荐高于这个最高值的配置
memory: "4吉字节"
安装VPA并获取资源优化建议:
# 安装VPA组件
kubectl apply -f https://github.com/kubernetes/autoscaler/releases/download/vertical-pod-autoscaler-1.0.0/vpa-v1.0.0.yaml
# 为每个需要调整资源配置的部署任务应用VPA配置文件
kubectl apply -f vpa/payment-api-vpa.yaml
# 等待24小时,让VPA收集足够的数据,然后查看建议方案
kubectl describe vpa payment-api-vpa -n production
VPA提供的资源优化建议示例:
建议配置:
容器推荐配置:
容器名称:payment-api
最低配置限制:
cpu: 50百万
memory: 128兆字节
目标配置:
cpu: 250百万 ← 将你的请求值设置为这个数值
memory: 512兆字节 ← 将你的请求值设置为这个数值
最高配置限制:
cpu: 500百万
memory: 1吉字节
将这些建议应用到你的部署任务中:
# 正确的操作:根据VPA的建议值调整资源配置
resources:
requests:
cpu: "250百万" # 从原来的2000百万减少到了80%
memory: "512兆字节" # 从原来的4096兆字节减少到了80%
limits:
cpu: "500百万" # 配置值是建议值的2倍,留有应对突发高峰的需求空间
memory: "1吉字节" # 配置值是建议值的2倍
所有适用于常见部署类型的VPA配置文件都位于配套仓库中的vpa/目录下。
2.3 优化资源配置的收益
| 指标 | 调整前 | 调整后 | 改善幅度 |
|---|---|---|---|
| 平均CPU利用率 | 18% | 65% | +47个百分点 |
| 所需节点数量 | 42个 | 28个 | -33% |
| 每月计算成本 | 52,000美元 | 36,400美元 | 每月节省15,600美元 |
应用这些建议后,可以验证实际带来的改善效果:
# 调整资源配置后,检查集群的整体利用率
# 目标是使所有节点的CPU和内存利用率保持在60–75%之间
kubectl top nodes
第3部分:Karpenter在资源打包与多样化配置中的应用
Karpenter是由AWS开发的一款开源Kubernetes节点配置工具,后来被捐赠给了CNCF。
与默认的Kubernetes集群自动扩展器不同,Karpenter会实时监测待启动Pod的实际资源需求,并动态选择最适合的EC2实例类型来满足这些需求——它可以从数千种可用的实例类型中灵活挑选,而不仅仅局限于你预先配置的几种选项。此外,它还会持续监控运行中的节点,发现资源利用率不足的情况时,会将工作负载整合到更少的节点上,同时自动关闭那些闲置的节点。
这样一来,集群的大小就能始终根据当前的工作负载需求来调整,而不是在初始化时预设的值。
3.1 使用集群自动扩展器的局限性
集群自动扩展器是基于预先配置的节点组来工作的。你需要指定哪些实例类型是可用的,然后它才会根据这些配置来调整节点的数量。
它的缺点在于:它只能使用你预先设定的实例类型进行资源分配,无法根据当前工作负载的实际需求动态选择最合适的实例类型。
以下是一个使用静态节点组的错误示例:
# 错误做法:设置两个静态节点组,每个组都针对最坏情况进行了过度配置
# 适用于CPU密集型工作的节点组会在内存需求较低时依然运行
# 适用于内存密集型工作的节点组也会在CPU资源充足时继续运行
eksctl create nodegroup \
--cluster my-cluster \
--name cpu-optimized \
--instance-types c5.2xlarge \
--nodes-min 5 --nodes-max 20
eksctl create nodegroup \
--cluster my-cluster \
--name memory-optimized \
--instance-types r5.2xlarge \
--nodes-min 3 --nodes-max 10
这种做法相当于同时为两种极端情况准备资源:要么所有节点都过于强大,要么有些节点根本无法满足需求。这两种方案都没有达到最佳效果。
3.2 Karpenter是如何解决这个问题的
Karpenter会观察待创建的Pod实际所需的资源需求,然后精确地选择合适的实例类型来满足这些需求。它可以从数千种可用的实例类型中进行选择,而不仅仅是你预先配置的那两种类型。此外,当节点的使用率下降时,它会将正在运行的工作负载整合到更少的节点上,并自动关闭那些使用率较低的节点。
以下是正确的实现方式:
# 正确的配置:Karpenter节点池
# Karpenter会根据待创建Pod的需求来选择最优的实例类型
# 会优先尝试使用“Spot”资源,如果“Spot”不可用,则会自动切换到“按需”资源
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
# 允许使用x86和ARM(Graviton)两种架构——Karpenter会选择成本更低的选项
- key: kubernetes.io/arch
operator: In
values: ["amd64", "arm64"]
# 优先尝试使用“Spot”资源,如果不可用则切换到“按需”资源
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
# 排除那些性价比低下的实例类型
- key: karpenter.k8s.aws/instance-family
operator: NotIn
values: ["t2", "t3a"]
limits:
cpu: "1000"
memory: "4000Gi"
disruption:
# 自动删除使用率较低的节点,并重新安排这些节点上的Pod任务
consolidationPolicy: WhenUnderutilized
# 30天后自动回收这些节点,以确保使用的是最新版本且安装了安全补丁的AMI镜像
expireAfter: 720h
各配置项的作用如下:
-
consolidationPolicy: WhenUnderutilized:Karpenter会持续监控节点的使用情况,一旦发现某个节点使用率较低,就会将其上的Pod任务重新安排到其他节点上。因此,当负载减少时,节点的数量会自动减少,而无需人工干预。 -
expireAfter: 720h:系统会自动替换那些运行时间超过30天的节点,从而确保你的基础设施始终使用的是最新版本、且安装了安全补丁的AMI镜像。 -
values: ["spot", "on-demand"]:Karpenter会优先尝试使用“Spot”资源。如果所需的实例类型无法通过“Spot”资源获得,它会自动切换到“按需”资源,而不会发出任何警报,也不需要人工进行操作。
如何安全地从Cluster Autoscaler迁移到Karpenter:
# 第一步:同时安装Karpenter和Cluster Autoscaler——暂时不要删除Cluster Autoscaler
helm repo add karpenter https://charts.karpenter.sh
helm install karpenter karpenter/karpenter \
--namespace karpenter \
--create-namespace \
--set settings.clusterName=your-cluster-name
# 第二步:应用NodePool和NodeClass配置
kubectl apply -f karpenter/nodepool.yaml
kubectl apply -f karpenter/nodeclass.yaml
# 第三步:对现有的旧节点进行标记,以便新的Pod任务能够被安排到Karpenter管理的节点上
# 这个过程是逐步进行的,因此不会导致任何停机时间
kubectl taint nodes -l eks.amazonaws.com/nodegroup=cpu-optimized \
group=legacy:NoSchedule
# 第四步:观察接下来一小时内,Pod任务是否被重新安排到了Karpenter管理的节点上
kubectl get pods -o wide --all-namespaces | grep -v legacy
# 第五步:在系统稳定运行30天后,删除旧的节点组
eksctl delete nodegroup --cluster my-cluster --name cpu-optimized
eksctl delete nodegroup --cluster my-cluster --name memory-optimized
可供直接部署的NodePool和NodeClass模板位于配套仓库中的karpenter/目录下。
3.3 非生产工作负载的Spot实例
用于测试和开发的工作负载并不需要按需实例所提供的可靠性保障。将这类任务切换到Spot实例上,可以节省60%至90%的节点使用成本。Karpenter会自动重新安排Pod的任务调度,从而有效应对Spot实例可能出现的中断情况;对于无状态工作负载而言,这些中断对用户来说是完全察觉不到的。
# 示例:仅用于测试环境的Spot节点池配置
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: staging-spot
spec:
template:
metadata:
labels:
billing/environment: staging
spec:
taints:
- key: environment
value: staging
effect: NoSchedule # 只有能够容忍这种标签的Pod才会被调度到这里
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"] # 仅用于非生产环境
disruption:
consolidationPolicy: WhenUnderutilized
3.4 Karpenter与Spot实例的性价比
| 指标 | 使用Cluster Autoscaler之前的效果 | 使用Karpenter和Spot实例后的效果 | 改善幅度 |
|---|---|---|---|
| 平均节点数量 | 28个 | 18个 | -36% |
| 平均CPU使用率 | 65% | 82% | +17个百分点 |
| 测试环境的成本 | 每月8,000美元 | 每月2,400美元 | -70% |
| 新Pod的扩展时间 | 3–5分钟 | 30–60秒 | -80% |
第4部分:Graviton实例的迁移方案
AWS Graviton是亚马逊自主研发的基于ARM架构的处理器系列,这类处理器被应用于那些名称以g结尾的EC2实例类型中,例如
Graviton实例的价格大约比同级别的Intel或AMD x86架构实例低20%;对于大多数服务器端应用程序(如Node.js、Python、Go、Java等)而言,由于其处理器架构是专门为这些工作负载类型优化的,因此Graviton实例在性能方面也能带来20%至40%的提升。
您无需修改应用程序代码即可使用Graviton实例——只需在构建容器镜像时设置相应的架构参数,在Kubernetes部署配置中指定合适的节点选择条件即可。
4.1 为什么Graviton能在不降低性能的情况下降低成本
在开始迁移之前,首先需要确认您的容器镜像是否支持ARM64架构。Docker Hub上提供的大多数官方镜像都是多架构版本的;因此,您需要为自己开发的应用程序镜像分别针对ARM64和x86架构进行定制构建。
检查您的镜像是否支持ARM64架构:
# 检查某个镜像是否包含ARM64配置信息
docker manifest inspect your-registry/your-app:latest | jq '.manifests[].platform'
对于多架构镜像,预期的输出结果如下:
{"architecture": "amd64", "os": "linux"},
{"architecture": "arm64", "os": "linux", "variant": "v8"}
如果输出中包含“arm64”,说明该镜像已经支持ARM64架构;否则,您需要先构建并推送多架构镜像。
构建并推送多架构镜像的操作步骤如下:
# 使用Docker Buildx一次性构建适用于x86和ARM架构的镜像
docker buildx create --use --name multi-arch-builder
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag your-registry/your-app:latest \
--push \
.
4.2 将工作负载迁移至Graviton平台
在Karpenter已经安装好的情况下,将工作负载迁移到Graviton平台只需修改部署配置中的标签即可。Karpenter会自动为系统分配合适的ARM64架构节点。
正确的配置示例如下:
# 正确的配置:nodeSelector会确保Pod运行在Graviton节点上
# 如果系统中没有ARM64节点,Karpenter会自动为其分配一个
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-api
spec:
template:
spec:
nodeSelector:
kubernetes.io/arch: arm64 # 仅安排在Graviton节点上运行
containers:
- name: api
image: your-registry/payment-api:latest # 必须是多架构镜像
建议先从无状态服务开始逐步进行迁移:
# 第一步:迁移一个无状态服务,并观察48小时内的运行情况
kubectl patch deployment payment-api \
-p '{"spec":{"template":{"spec":{"nodeSelector":{"kubernetes.io/arch":"arm64"}}}}}'
# 第二步:在最初的30分钟内密切关注是否有错误发生
kubectl logs -l app=payment-api --tail=100 -f
# 第三步:确认Pod是否运行在Graviton节点上
# “NODE”列应显示Graviton实例的类型(如m7g、c7g、r7g)
kubectl get pods -l app=payment-api -o wide
# 第四步:在稳定运行48小时后,再迁移下一个服务
然而,在某些情况下,您不应该将工作负载迁移到Graviton平台:例如那些依赖GPU的计算任务、需要使用原生x86二进制文件的应用程序,或者那些尚未构建多架构镜像的工作负载。
4.3 Graviton平台的成本效益
| 工作负载类型 | x86平台的月度成本 | Graviton平台的月度成本 | 节省的费用 |
|---|---|---|---|
| Web服务(Node.js、Python) | $18,000 | $14,400 | $3,600/月 |
| 数据处理任务 | $12,000 | $9,600 | $2,400/月 |
| API服务(Go、Java) | $8,000 | $6,400 | $1,600/月 |
| 总计 | $38,000 | $30,400 | $7,600/月 |
第5部分:用于数据传输的VPC端点
5.1 NAT网关费用
如果未配置VPC端点,那么从您的EKSPod发送到任何AWS服务(如S3、DynamoDB、ECR或SQS)的每一种数据,在传输过程中都会经过NAT网关。NAT网关会对每处理1GB的数据收取0.045美元的费用。
一个繁忙的EKS集群,如果需要从ECR获取容器镜像、将数据写入S3以及查询SQS队列,那么每个月通过NAT网关传输的数据量可能会达到数百TB,由此产生的费用也可能高达数千美元——而这些数据实际上从未离开过AWS网络。
在添加VPC端点之前,请先了解您当前的NAT网关使用成本:
# 查看上个月的NAT网关处理费用
aws ce get-cost-and-usage \
--time-period Start=\((date -d 'last month' +%Y-%m-01),End=\)(date +%Y-%m-01) \
--granularity DAILY \
--filter '{
"Dimensions": {
"Key": "USAGE_TYPE",
"Values": ["NATGateway-Bytes"]
}
}' \
--metrics UnblendedCost \
--query 'ResultsByTime[*].{Date:TimePeriod.Start,Cost:Total.UnblendedCost.Amount}' \
--output table
5.2 VPC端点——只需30分钟即可完成配置
VPC端点能够在您的VPC与AWS服务之间建立私密连接,使数据传输直接通过AWS的核心网络进行,而无需经过NAT网关。因此,使用VPC端点进行数据传输是免费的。每个VPC端点的费用大约为0.01美元/小时,也就是每月约7.20美元——这个费用远低于原本需要支付的NAT网关使用费。
以下是为四种最常见的EKS数据传输目的地配置VPC端点的具体步骤:
# 首先获取您的VPC ID和主路由表ID
VPC_ID=$(aws eks describe-cluster --name your-cluster \
--query 'cluster.resourcesVpcConfig.vpcId' --output text)
ROUTE_TABLE_ID=$(aws ec2 describe-route-tables \
--filters Name=vpc-id,Values=$VPC_ID Name=association.main,Values=true \
--query 'RouteTables[0].RouteTableId' --output text)
echo "VPC: \(VPC_ID | Route Table: \)ROUTE_TABLE_ID"
# S3网关端点——创建费用免费,可完全避免S3数据传输时产生的NAT网关费用
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--service-name com.amazonaws.us-east-1.s3 \
--route-table-ids $ROUTE_TABLE_ID
# DynamoDB网关端点——同样免费,配置机制与S3相同
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--service-name com.amazonaws.us-east-1.dynamodb \
--route-table-ids $ROUTE_TABLE_ID
# ECR API接口端点——可避免在获取镜像时产生的NAT网关费用
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--vpc-endpoint-type Interface \
--service-name com.amazonaws.us-east-1.ecr.api \
--subnet-ids $(aws ec2 describe-subnets \
--filters Name=vpc-id,Values=$VPC_ID Name=tag:Tier,Values=private \
--query 'Subnets[*].SubnetId' --output text)
# ECR Docker端点——与ECR API接口端点结合使用,才能确保所有镜像获取操作都能通过VPC端点完成
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--vpc-endpoint-type Interface \
--service-name com.amazonaws.us-east-1.ecr.dkr \
--subnet-ids $(aws ec2 describe-subnets \
--filters Name=vpc-id,Values=$VPC_ID Name=tag:Tier,Values=private \
--query 'Subnets[*].SubnetId' --output text)
用于在一次`apply`操作中创建所有四个端点的Terraform模块位于配套仓库中的`terraform/vpc-endpoints/`目录下。
请确认这些端点能够正确地路由流量:
aws ec2 describe-vpc-endpoints \
--filters Name=vpc-id,Values=$VPC_ID \
--query 'VpcEndpoints[*].{Service:ServiceName,State:State,Type:VpcEndpointType}' \
--output table
# 预期结果:所有端点的状态都应为“available”
5.3 VPC端点的投资回报率
| 服务类型 | 使用NAT时的费用 | 使用VPC端点后的费用 | 每月节省的费用 |
|---|---|---|---|
| S3数据传输 | $4,500 | $0 | $4,500 |
| ECR镜像下载 | $800 | $0 | $800 |
| DynamoDB查询 | $1,200 | $0 | $1,200 |
| 端点使用费用 | — | $29(4个端点) | -$29 |
| 净节省金额 | 每月$6,471 |
第6部分:EBS卷优化
6.1 从gp2迁移到gp3
EBS gp2卷的IOPS费用是根据存储容量来计算的——每GB存储容量提供3 IOPS,最低要求为100 IOPS。而EBS gp3卷无论容量大小都提供3,000 IOPS的IOPS性能,并且每GB的成本降低了20%。这种迁移过程是在线进行的,不会导致任何服务中断。
请查找所有gp2卷并执行迁移操作:
# 第1步:列出所有的gp2卷及其容量
aws ec2 describe-volumes \
--filters Name=volume-type,Values=gp2 \
--query 'Volumes[*].{ID:VolumeId,Size:Size,State:State}' \
--output table
# 第2步:将每个gp2卷迁移到gp3版本——无需停止任何实例
# 修改操作是在线进行的,迁移过程中卷会保持连接状态并继续被使用
aws ec2 describe-volumes \
--filters Name=volume-type,Values=gp2 \
--query 'Volumes[*].VolumeId' \
--output text | tr '\t' '\n' | while read vol; do
echo "正在将$vol从gp2迁移到gp3..."
aws ec2 modify-volume \
--volume-id $vol \
--volume-type gp3
done
# 第3步:确认所有卷都已成功迁移为gp3版本
aws ec2 describe-volumes \
--filters Name=volume-type,Values=gp2 \
--query 'Volumes[*].VolumeId' \
--output text
# 预期结果:输出为空——说明没有剩余的gp2卷
6.2 查找并删除孤立的卷和快照
当Kubernetes中的PersistentVolumeClaims被删除后,对应的EBS卷有时并不会被自动清除。这些卷会继续运行,并持续产生费用。
# 查找未关联到任何实例的EBS卷——状态为“available”表示没有与任何实例连接
aws ec2 describe-volumes \
--filters Name=status,Values=available \
--query 'Volumes[*].{ID:VolumeId,Size:Size,Created:CreateTime}' \
--output table
# 查找创建时间超过90天的EBS快照
aws ec2 describe-snapshots \
--owner-ids self \
--query "Snapshots[?StartTime<='$(date -d '90 days ago' --iso-8601=seconds)'].[SnapshotId,StartTime,VolumeSize]" \
--output table
在删除任何快照之前,请先核实它是否不是该生产数据库的唯一备份副本,可以通过与您的RDS自动备份计划进行对照来确认这一点。
6.3 EBS优化的投资回报率
| 资源 | 优化前 | 优化后 | 每月节省的费用 |
|---|---|---|---|
| 将gp2类型磁盘升级为gp3类型磁盘(总容量为1TB) | $102 | $72 | $30 |
| 删除未使用的卷(共50个,每个体积为100GB) | $500 | $0 | $500 |
| 清理旧的快照文件(总容量为500GB) | $25 | $0 | $25 |
| 总计 | $627 | $72 | 每月可节省$555 |
第7部分:负载均衡器的整合
7.1 问题所在——每个服务都使用单独的负载均衡器
许多开发团队会为每一个微服务创建一个独立的LoadBalancer服务。在AWS平台上,每个应用程序负载均衡器的基本使用费用约为每月16.20美元,此外,每处理一个请求还需额外支付0.008美元/LCU小时的费用。如果拥有20个微服务,那么在开始处理任何请求之前,每月所需的费用就已经达到了324美元。
以下是错误的实现方式:
# 错误的做法:每次为新的微服务创建一个独立的AWS ALB
# 20个微服务 = 20个ALB = 每月324美元,还不包括任何流量处理费用
apiVersion: v1
kind: Service
metadata:
name: payment-api
spec:
type: LoadBalancer # 为每个微服务创建独立的ALB
ports:
- port: 80
targetPort: 8080
7.2 正确的解决方案——使用共享入口控制器
入口控制器是Kubernetes中的一个组件,它以Pod的形式运行在集群内部,能够根据主机名和URL路径将外部请求路由到相应的服务。因此,我们不需要为每个微服务都创建一个AWS应用程序负载均衡器,而是只需要一个统一的ALB即可。通过基于路径的路由规则,每个请求都会被正确地转发到目标后端服务,从而实现与之前相同的功能,但成本却大大降低。
正确的实现方式如下:
# 正确的做法:使用一个共享入口控制器来处理所有外部请求
# AWS负载均衡器控制器会为这里列出的所有服务创建一个统一的ALB
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shared-ingress
namespace: production
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
alb.ingress.kubernetes.io/ssl-redirect: "443"
spec:
rules:
- host: api.company.com
http:
paths:
- path: /payments
pathType: Prefix
backend:
service:
name: payment-service
port:
number: 8080
- path: /users
pathType: Prefix
backend:
service:
name: user-service
port:
number: 8080
- host: dashboard.company.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dashboard-service
port:
number: 3000
tls:
- hosts:
- api.company.com
- dashboard.company.com
secretName: tls-wildcard-cert
请确认Ingress已配置完成,且ALB的DNS名称也已设置:
# 等待直到“ADDRESS”列显示ALB的DNS名称(通常需要2到3分钟)
kubectl get ingress shared-ingress -n production -w
成本对比如下:
| 方法 | 负载均衡器数量 | 每月成本 |
|---|---|---|
| 每个微服务使用独立的LoadBalancer Service(共20个微服务) | 20个ALB | 约400美元/月 |
| 使用单个Ingress控制器 | 1个ALB | 约27美元/月 |
| 每月节省的成本 | 约373美元/月 |
共享Ingress的相关配置文件位于配套仓库中的`k8s/ingress/`目录下。
完整的7步优化流程
| 步骤 | 操作内容 | 实施所需时间 | 预计每月可节省的成本 |
|---|---|---|---|
| 1 | 根据实际需求调整Pod资源请求的大小(使用VPA机制) | 1周 | 15,600美元 |
| 2 | 安装Karpenter工具并配置资源整合功能 | 1周 | 8,400美元 |
| 3 | 将测试环境和开发环境中的任务迁移至Spot实例 | 1周 | 11,200美元 |
| 4 | 将兼容的 workload迁移至Graviton平台 | 2周 | 7,600美元 |
| 5 | 为S3、ECR和DynamoDB添加VPC终端节点 | 1天 | 6,471美元 |
| 6 | 将gp2类型的存储卷升级为gp3类型,并删除多余的卷 | 1天 | 555美元 |
| 7 | 使用共享Ingress整合所有的负载均衡器资源 | 1天 | 373美元 |
| 总计 | 3至4周 | 49,799美元/月 |
按照此方案实施,每年可节省597,588美元。所需工程人力为:每步优化需一名工程师,共需进行一次迭代开发。
EKS成本优化的最佳实践
✅ 务必执行:在开展任何其他优化操作之前,先根据实际需求调整Pod资源请求的大小。后续的所有步骤都依赖于这些准确的配置信息。
✅ 务必执行:在使用Karpenter工具时,需将`consolidationPolicy`设置为`WhenUnderutilized`,这样该工具就能自动优化节点数量了。
✅ 务必执行:将测试环境和开发环境中的任务迁移至Spot实例。对于能够容忍中断的任务来说,这种方式可以节省60%到90%的成本。
✅ 务必执行:将兼容的 workload迁移至Graviton平台。大多数Web服务和API在迁移后无需对代码进行任何修改即可正常运行。
✅ 务必执行:在评估数据传输成本之前,先为S3、DynamoDB和ECR添加VPC终端节点。
✅ 务必执行:将gp2类型的存储卷升级为gp3类型。这种迁移方式是在线进行的,不会导致任何停机时间,而且成本能立即降低20%。
✅ 务必执行:对于所有外部流量,应使用单一的共享Ingress控制器进行管理,而不是为每个服务分别配置负载均衡器。
❌ 请勿: 在完成步骤1至6之前购买储蓄计划。否则,您将会在1到3年内陷入不必要的支出困境。
❌ 请勿: 当工作负载组成发生变化时,仍使用静态节点组与Cluster Autoscaler配合使用。Karpenter能够动态处理这类变化。
❌ 请勿: 在按需实例上运行测试环境或开发环境。虽然临时性的中断可以应对,但成本上的差异却无法忽视。
资源
-
Karpenter文档 — 官方NodePool配置参考及安装指南
-
AWS Graviton入门指南 — AWS提供的针对不同语言的兼容性说明及迁移指导