这本手册全面介绍了这套七步操作方案,通过这套方案,某企业的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平台上

  • 如何使用VPC端点来避免NAT Gateway带来的费用

  • 如何将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账单时,他们通常会采取以下措施:

  1. 立即购买节省计划,从而以30%的折扣锁定那些不必要的资源消耗。

  2. 随后开始使用Karpenter工具,却发现自己选择了错误类型的实例。

  3. 接着尝试迁移至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能够动态处理这类变化。

请勿: 在按需实例上运行测试环境或开发环境。虽然临时性的中断可以应对,但成本上的差异却无法忽视。

资源

Comments are closed.