亚马逊AWS官方博客

Amazon EKS 最佳实践之成本优化

Amazon EKS 是运行在 AWS 上托管的 Kubernetes 服务,EKS 不仅可以让您快速开始云上 Kubernetes 之旅,同时也提供了多种工具和服务来帮助您实现降本增效,加速业务创新。比如您可以为 EKS 集群启用弹性伸缩机制来提高资源利用效率,也可以很轻松的在 EKS 集群中使用 Spot 实例、预留实例或者 Graviton 实例等来降低成本,提高性价比。本篇文章旨在帮助您节省成本,优化运行在 Amazon EKS 上的工作负载。

一般准则

  • 实现可观测性,近乎实时的监控集群的成本和用量指标,为运行容器的计算实例/ Pod 等选择最佳的资源配置,并持续监控其 CPU 和内存等关键指标,确保合理的为工作负载分配资源,避免浪费。可以借助 Amazon CloudWatch Container Insights 或其他第三方工具来实现(比如:Kubecost)。
  • 利用 AWS 提供的多种购买选项和实例类型来降低 EC2 的使用成本(比如:按需使用、Spot 实例、Savings Plans、预留实例以及基于 Amazon 自研 Arm 芯片的 Graviton 实例)。
  • 为您的集群增加弹性,为工作负载提供与之相匹配的资源,提高资源利用率。

最佳实践

Amazon EKS 成本优化主要包含以下 3 个通用的最佳实践:

  • 提高资源利用率(弹性伸缩、合理的规模、购买选项和资源分配策略等)。
  • 培养团队的成本意识(借助 AWS 或者第三方提供的云成本管理工具,比如:Amazon CloudWatch Container Insights、Amazon Cost Explorer、Kubernetes Dashboard 和 Kubecost 等对成本进行可视化)。
  • 长期而持续的进行成本优化(在日常的工作中,贯彻 Right Sizing 理念)。

与其他所有指南一样,EKS 成本优化的最佳实践没有最完美的,只有最适合的。在成本优化的过程中,请首先明确工作负载的重要程度和优先级,再选择最适合的优化方案去执行。

提高资源利用率

要实现成本节省,首先要尽可能做到不浪费资源,提高资源利用率,这也是 Amazon EKS 成本优化的第一条最佳实践。提高资源利用率意味着您需要为运行在 Amazon EKS 里的工作负载提供合理的资源配置。

建议:利用弹性伸缩来满足应用程序的扩缩需求

Amazon EKS 支持多种弹性伸缩方案:Cluster AutoscalerKarpenter 主要用于集群内部计算节点的扩缩容。 Horizontal Pod Autoscaler 和 Vertical Pod Autoscaler 主要用于Pod 的纵向与横向扩缩容。

利用 Karpenter 改善工作负载的运行效率和成本支出

Karpenter 是一个开源的弹性伸缩方案,它提高了 Kubernetes 应用程序的可用性,为您的应用程序提供类型和大小最适合的计算资源。其工作流程如下图所示:

部署

请参考 Getting Started with Karpenter 进行部署。

工作原理

Karpenter 的目标是为了改善工作负载的运行效率和最大程度的节省成本,它取消了节点组的限制(group-less auto scaling),直接提供计算资源,动态的计算 Pending Pod 需要何种类型和大小的 EC2 实例作为节点。其工作原理是:

  • 监视被 Kubernetes 标记为 Unschedulable 的 Pod(Pending Pod)。
  • 评估 Pod 请求的调度约束(资源请求、节点选择器、affinities 和 tolerations 等)。
  • 创建并配置满足 Pod 要求的节点。
  • Kubernetes scheduler 调度 Pod 在新节点上运行。
  • 当不再需要节点时删除节点。

扩容

Karpenter 创建的节点的所有配置都保存在一个名叫 Provisioner 的 CRD(Custom Resource Definition)里。在 Provisioner 里可以定义资源类型、大小、容量限制、缩容配置等信息。一个集群可以包含一到多个 Provisioner,但是最少需要包含一个名为 default 的 Provisioner。示例如下:

apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: default
spec:
  labels:
    type: karpenter
  ttlSecondsAfterEmpty: 30
  # 按需配置,一般不建议配置。
  ttlSecondsUntilExpired: 2592000 # 30 Days = 60 * 60 * 24 * 30 Seconds;
  requirements:
    - key: karpenter.sh/capacity-type
      operator: In
      values: ["on-demand", "spot"]
    - key: "node.kubernetes.io/instance-type"
      operator: In
      values: ["c5.large", "m5.large", "r5.large", "m5.xlarge"]
    - key: "kubernetes.io/arch"
      operator: In
      values: ["arm64", "amd64"]
    - key: "topology.kubernetes.io/zone"
      operator: In
      values: ["us-west-2a", "us-west-2b"]
  limits:
    resources:
      cpu: 1000
      memory: 1000Gi
  providerRef:
    name: default
  weight: 90
---
apiVersion: karpenter.k8s.aws/v1alpha1
kind: AWSNodeTemplate
metadata:
  name: default
spec:
  subnetSelector:
    karpenter.sh/discovery: ${EKS_CLUSTER_NAME}
  securityGroupSelector:
    karpenter.sh/discovery: ${EKS_CLUSTER_NAME}
  tags:
    app.kubernetes.io/created-by: eks-workshop
  # 按需配置
  amiFamily: Bottlerocket
  instanceProfile: MyInstanceProfile

上述示例包含了两个部分,第一部分定义了 Provisioner 的相关配置,第二部分 Node Template(AWSNodeTemplate)主要用来提供 Cloud Provider 的独特配置,在 EKS 里使用 Karpenter,您可以通过 AWSNodeTemplate 来设置 subnet、securityGroup、AMI、instanceProfile userData 和 tags 等配置。每一个 Provisioner 都需要使用 spec.providerRef 来引用特定的 Node Template,一个 Node Template 可以同时被多个 Provisioner 来引用。

Provisioner 主要通过如下的配置对新建节点进行约束:

  • Requirements:通过 Requirements 您可以来设置待扩展实例的家族类型/实例代次(最新或上代实例)、OS、CPU 大小和架构、所属可用区、按需/Spot 以及 hypervisor的类型。
  • Limits:用来限制当前provisioner 所能管理的最大资源容量。
  • Labels:被应用到所有节点的键值对。
  • Deprovision:您可以通过设置 ttlSecondsAfterEmpty 或 ttlSecondsUntilExpired 来配置合适进行缩容操作。在本示例中 ttlSecondsAfterEmpty: 30 表示当某节点空闲超过 30s 时会被自动回收,如果不设置 ttlSecondsAfterEmpty,那么节点将永远不会被回收。ttlSecondsUntilExpired: 2592000 表示当节点持续运行 30 天后会被自动回收。Karpenter 还提供 Consolidation 的功能来实现 Deprovision,我们将在缩容章节详细介绍。
  • Weight:建议您在创建多个 Provisioner 时,确保他们彼此是互斥的,这样最终就只会有一个 Provisioner 被选中。如果出现多个 Provisioner 被同时选中的情景,Karpenter 会根据 Weight 来选择权重最高的 Provisoner(Weight 默认值为 0)。

假设当前集群包含 label(type: karpenter)的节点数量为 0,当您运行下述部署时,将会触发 Karpenter 的扩容操作,请思考:Karpenter 将会优先创建哪种类型的 EC2 实例来运行 Pod?

apiVersion: apps/v1
kind: Deployment
metadata:
  name: inflate
  namespace: other
spec:
  replicas: 5
  selector:
    matchLabels:
      app: inflate
  template:
    metadata:
      labels:
        app: inflate
    spec:
      nodeSelector:
        type: karpenter
      terminationGracePeriodSeconds: 0
      containers:
        - name: inflate
          image: public.ecr.aws/eks-distro/kubernetes/pause:3.2
          resources:
            requests:
              memory: 1Gi

要回答我们上面的思考题,需要先明白 Karpenter 在创建节点时的分配策略是什么。正如 Karpenter 的目标所述:它是为了改善工作负载的运行效率和最大程度的节省成本。在 Karpenter 中,分配策略是被自动管理的,无需用户干预,主要的自动分配策略如下:

  • 默认的容量类型选项是 On-demand,当同时配置 On-demand 和 Spot 时, 优先选择 Spot。其中:选择 On-demand 时,优先使用价格最低的实例类型 (lowest-priced),选择 Spot 时,首先为正在启动的实例数识别具有最高容量的池(capacity pools),然后在这些池中选择价格最低的实例类型(price-capacity-optimized)。
  • 当配置了多种类型的 CPU 时,按照性价比优先原则,优先选择 arm 类型的机器,其次选择 x86 类型的机器。
  • Karpenter 默认会在所有类型的机器中进行选择(裸金属和 GPU 除外),建议仅在您有特殊需求的时候限制实例类型。
  • Karpenter 默认会在所有的可用区中进行选择来保证应用的高可用,同样建议仅在您有特殊需求的时候限定可用区范围。
  • Karpenter 支持 Taints and Tolerations,建议您根据业务的特殊需求来对 Pod 的部署进行约束。

缩容

对基础设施资源进行扩容只是经济高效的利用资源的一个方面,在一个持续运行的集群环境里,根据业务工作负载的起伏,缩容也是降低成本支出的关键一环。Karpenter 提供了如下方案来帮助您进行缩容管理:

  • ttlSecondsAfterEmpty:当一个节点变为空闲状态并持续一段时间 (ttlSecondsAfterEmpty)以后,Karpenter 就会将该节点删除。该配置适合于应用程序不能接受中断的场景,即如果节点上有正在运行的工作负载,Karpenter 不能对其进行回收。
  • ttlSecondsUntilExpired:当一个节点持续运行一段时间 (ttlSecondsUntilExpired)以后,Karpenter 就会将该节点删除。
  • Consolidation:相比上面两个设置,该功能会更主动的去压缩为低利用率的工作负载所过度预置的资源,达到节省成本的目标,该功能与 ttlSecondsAfterEmpty 互斥。该配置适合应用程序能够接受中断、无状态服务等场景,这是一种更为激进的回收策略,解决集群碎片化问题,提高资源利用率。

请注意,如果不显式的配置上述的缩容条件,集群中的节点将会被永久保留,有可能造成成本的上升。要启用 Consolidation,需要对 Provisioner 的配置进行修改:

apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: default
spec:
  consolidation:
    enabled: true

当启用 Consolidation 以后,Karpenter 会主动的去识别:

  • 可以被删除的节点:当该节点上的 Pod 可以被迁移并运行在集群里的其他节点时。
  • 可以被替换的节点:当该节点可以被成本更便宜的节点替换时。

当 Kapenter 判定有多个潜在的节点可以进行 consolidation 时,在同一时刻只会选择其中一个节点进行缩容,其选择的标准是对该节点进行 consolidation 所带来的集群中断影响最小,选择依据是:

  • 该节点上正在运行的 Pod 最少。
  • 该节点距离过期时间最近(ttlSecondsUntilExpired)。
  • 该节点上运行着优先级比较低的 Pod。

利用 Cluster Autoscaler 自动调整 Kubernetes 集群的规模

Cluster Autoscaler(CA)是一个自动扩展和收缩 Kubernetes 集群 Node 规模的工具,其主要的目标是在不浪费资源的前提下确保集群中有足够的 Node 来运行 Pod。当满足以下条件时自动触发扩缩动作:

  • 当集群因为资源不足,无法启动新的 Pod 时,CA 会主动扩展,增加集群的 Node 数量。
  • 当集群里存在资源长时间(默认 10 分钟,可定制)未被充分利用的 Node (已请求的资源低于 Node 可分配资源的 50%,可定制),并且这个 Node 上的 Pod 可以被移动到其他 Node 时,CA 会主动去尝试减少集群的 Node 数量。

部署

在 AWS 上,CA 利用 Amazon EC2 Auto Scaling Groups 来管理节点组,CA 以 Deployment 的形式运行在您的集群里。请参考 Cluster Autoscaler on AWS 进行部署。

工作原理

CA 的基本工作原理是:每隔 10s 去检查集群中是否存在 unschedulable pods,这些 unschedulable pods 也被称为 pending pods。如果存在,CA 会主动开始扩容操作,如果不存在,CA 会去检查集群中是否存在可以被移除的未被充分利用的 Node,如果存在,CA 会主动开始缩容操作。CA 借助 Node group 来进行集群的扩容和缩容,每个 Node group 和 Amazon Auto Scaling Group (ASG) 一一对应。我们分别来介绍这两种操作:

扩容

CA 通过 API Server 查询所有 Pod 的状态。每隔 10s(可通过 —scan-interval 配置) CA 会确认是否存在因为资源不足而无法正常启动的 Pod。如果存在启动失败的 Pod,CA 会尝试通过 ASG 来创建新的 EC2 节点,并将其注册到集群中,最后 Kubernetes scheduler 会重新将 Pod 调度到新创建的节点。

当集群中存在多个 Node group/ASG 时,CA 会根据您的 Expander 配置来选择合适的 Node group 执行扩展操作,Amazon EKS 支持一下四种 Expander 策略:

  • random:默认策略,CA 会从多个 Node group 中随机选择一个来扩展。
  • most-pods:选择能够部署最多 Pod 的 Node group。
  • least-waste:选择部署完 pending pods 后空闲 CPU 最少的 Node group。
  • priority:选择优先级最高的 Node group 进行扩容,优先级数值越大越优先。

当您有多个 Node group,并且同时包含 Spot Node group 和 On-demand Node group 时,为了让 CA 优先启动 Spot 实例,可以设置 Expander 的策略为 priority (./cluster-autoscaler --expander=priority)为了设置优先级,需要在启动 CA 前创建一个名为 cluster-autoscaler-priority-expander 的 ConfigMap,通过设置不同的优先级数值,来进行优先级排序,示例如下:

缩容

如上文所讲,每隔 10s,如果 CA 没有发现 unschedulable pods,那么它会继续去检查是否有节点可以被删除。当某个满足以下所有条件的时候,该节点会被 CA 移除:

  • 节点的资源利用率小于50%(可通过参数--scale-down-utilization-threshold 来修改),资源利用率的计算是通过判断该节点上所有 Pod 请求的 CPU 和 Memory 总和除以节点可分配的资源大小获得。需要注意的是,只要有一个指标满足就可能会触发缩容。
  • 节点上的所有 Pod 可以被移动到集群里的其他节点。
  • 满足以上条件,并且该状态持续超过 10 分钟,CA 就会开始缩容操作。

当有以下任意情况发生时,CA 不会对该节点进行缩容:

  • 当您设置了严格的 PodDisruptionBudget 的 Pod 不满足 PDB 时,不会缩容。
  • Kube-system 下的 Pod。通过参数--skip-nodes-with-system-pods 控制。
  • 节点上有非 deployment,replica set,job,stateful set 等控制器创建的 Pod。
  • Pod 有本地存储。通过 annotation "cluster-autoscaler.kubernetes.io/safe-to-evict-local-volumes": "volume-1,volume-2,.." 控制。
  • Pod 不能被调度到其他节点上。例如资源不满足、与 node selectors 的要求不匹配、与 affinity / anti-affinity 要求不符等。
  • Pod 有如下 annotation 设置:"cluster-autoscaler.kubernetes.io/safe-to-evict": "false"

局限性

EKS 的 Node group 是和 ASG 一一对应的,CA 对集群节点的增加和减少需要借助 ASG 来完成,这就天然增加了节点加入或者移出集群的时间(一般为分钟级别),如果您的业务需要更迅速的来增加节点,CA 就不再是最优方案了。另外,如果您的业务需求多种多样(比如:业务种类多,需求资源类型多样等),EKS 就需要创建越来越多的 Node group 来提供计算资源,对于多个 Node group 的管理也会变得越来越复杂。

利用 Horizontal Pod Autoscaler 自动调整 Pod 的数量

工作原理

Horizontal Pod Autoscaler(HPA)的工作对象是 Pod,其工作原理是:通过在 control-manager 注册一个 control-loop,每隔 15s 去扫描 api server(metric server),获取并计算某一个预设指标的平均资源使用率,HPA 会对比平均资源使用率和预设的目标使用率,生成一个扩缩建议,来自动调整 Pod 的数量,以此来提高资源利用率,使成本支出更合理。创建 HPA 可以通过如下命令来实现:

kubectl autoscale deployment <your deployment name> --cpu-percent=50 \ #目标 CPU 使用率
--min=1 \ #Pod 数量下限 
--max=10  #Pod 数量上限

HPA 不仅支持从 kubelet 收集的监控指标(CPU / 内存),也支持通过自定义或者通过 External Metrics API 从第三方(例如 Prometheus, Amazon Cloudwatch)获取的监控指标 (比如:Custom: SQS/ApproximateNumberOfMessagesVisible),如下所示:

示例 1:创建外部指标

apiVersion: metrics.aws/v1alpha1
kind: ExternalMetric:
  metadata:
    name: hello-queue-length
  spec:
    name: hello-queue-length
    resource:
      resource: "deployment"
    queries:
      - id: sqs_helloworld
        metricStat:
          metric:
            namespace: "AWS/SQS"
            metricName: "ApproximateNumberOfMessagesVisible"
            dimensions:
              - name: QueueName
                value: "helloworld"
          period: 300
          stat: Average
          unit: Count
        returnData: true

示例 2:创建 HPA ,使用上述外部指标

kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2beta1
metadata:
  name: sqs-consumer-scaler
spec:
  scaleTargetRef:
    apiVersion: apps/v1beta1
    kind: Deployment
    name: sqs-consumer
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: External
    external:
      metricName: hello-queue-length
      targetAverageValue: 30

HPA 与 Karpenter 可以搭配来使用,HPA 从 Pod 的维度,Karpenter 从节点的维度,共同运行来提高整个集群的资源利用率,使成本的效益最大化。

利用 Vertical Pod Autoscaler 自动调整 Pod 的资源分配

Vertical Pod Autoscaler(VPA)根据容器资源使用情况自动设置 CPU 和内存的 requests 及 limit,从而允许在节点上进行适当的调度,以便为每个 Pod 提供大小合适的资源。它既可以缩小过度请求资源的容器,也可以根据其使用情况随时提升资源不足的容量。

部署

请参考 Vertical Pod Autoscaler 进行部署。

工作原理

使用 VPA 时,用户无需为 Pod 中的容器设置资源请求。配置后,它将根据资源(CPU 与内存)使用情况自动设置 requests。在对 Pod 的调度过程中,使得每个 Pod 都可以使用适当的资源量从而分配到适合的节点上。它既可以缩小资源请求过多的 Pod,也可以根据一段时间内的使用情况扩大资源请求不足的 Pod。

示例:为 Deployment 设置 VPA

在如下示例中,我们为名为 hamster 的 Deployment 设置了 VPA,允许 VPA 在 hamster 资源使用量过多或者不足的时候自动调整其资源请求量。需要注意的是,因为 Kubernetes 目前仍然不支持 in-place upgrade, VPA 对 Pod 进行资源请求量的调整意味着需要 recreate 这个 Pod,这个操作会引起 Pod 的中断,在生产环境中需要对这个中断进行服务可用性评估。

---
apiVersion: "autoscaling.k8s.io/v1"
kind: VerticalPodAutoscaler
metadata:
  name: hamster-vpa
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: hamster
  resourcePolicy:
    containerPolicies:
      - containerName: '*'
        minAllowed:
          cpu: 100m
          memory: 50Mi
        maxAllowed:
          cpu: 1
          memory: 500Mi
        controlledResources: ["cpu", "memory"]
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hamster
spec:
  selector:
    matchLabels:
      app: hamster
  replicas: 2
  template:
    metadata:
      labels:
        app: hamster
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 65534 # nobody
      containers:
        - name: hamster
          image: registry.k8s.io/ubuntu-slim:0.1
          resources:
            requests:
              cpu: 100m
              memory: 50Mi
          command: ["/bin/sh"]
          args:
            - "-c"
            - "while true; do timeout 0.5s yes >/dev/null; sleep 0.5s; done"

建议:利用 Down Scaling 主动缩减 Kubernetes 里的工作负载规模

在非生产环境中(比如:测试环境、开发环境、演示环境等),最有效的降低成本的方法是:只在必要的时候使用资源,除此之外,安全的将资源删除来减少支出。下图展示了将 24×7 运行的服务改为只在必要的时间段运行,最高的成本节省可以达到 70%。

在开源社区有一些工具,比如:Cluster TurndownKubernetes Downscaler Descheduler for Kubernetes 可以来实现集群的主动缩容。在本文中,我们以 Cluster Turndown 为例来进行说明。

在 EKS 上,Cluster Turndown 可以根据您自定义的时间段和频率来自动的关闭和恢复集群的工作节点,比如:在您自定义时间开始的时候,Cluster Turndown 将托管节点组里的节点数量减为 0, 在您自定义时间结束的时候,Cluster Turndown 将托管节点组恢复至初始状态。请注意,目前 Cluster Turndown 仅支持 EKS 的托管节点组。

部署

请参考 EKS & Cluster Turndown 进行部署。

工作原理

使用 Cluster Turndown 最关键的是需要创建一个对象类型为 TurndownSchedule 的 Kubernetes CRD,示例如下:

apiVersion: kubecost.com/v1alpha1
kind: TurndownSchedule
metadata:
  name: example-schedule
  finalizers:
  - "finalizer.kubecost.com"
spec:
  start: 2023-04-12T00:00:00Z
  end: 2023-04-12T12:00:00Z
  repeat: daily

上面示例创建了一个定时任务,从 2023-04-12 开始,每天从 UTC 0 点开始主动将集群内的工作节点减少为 0,并在 UTC 12 点的时候恢复集群到初始状态。该任务的执行频率可以有以下选项:

  • 从不:仅在设定的时间执行一次
  • 每天:每 24 小时执行一次
  • 每周:每 7 天执行一次

当 Cluster Turndown 设置的定时任务开始的时候,会在 EKS 集群中创建一个规格为 t2.small 的小机器,并为这台机器打上污点(Taints),用来阻止除了 Cluster Turndown 相关 Pod 之外的 Pod 被部署到这台机器上。当 Cluster Turndown 的 Pod 启动以后,就会开始 Cordon 集群里节点,并逐步将这些工作节点删除,直到所有托管节点组的容量变为 0(Cluster Trundown 的 Pod 所在的节点会被保留)。当 Cluster Turndown 设置的定时任务结束的时候,Cluster Turndown 会负责恢复集群至初始状态,在集群恢复以后,会将一开始创建出来的类型为 t2.small 的小机器删除。

建议:利用 Limit Ranges 和 Resource Quotas 来管理资源分配

默认情况下,容器可以无限使用集群内的所有资源。集群管理员可以借助 Resource Quotas 在 Namespace 范围内限制和管理资源的分配和使用。建议您通过 Resource Quotas 来限制一个 Namespace 可以使用的资源总量,通过 Limit Ranges 来限制一个 Pod 或者容器所能使用的资源数量。合理的使用 Resource Quotas 和 Limit Ranges 将有助于您提高资源使用效率,避免因为无节制的使用资源而造成的成本上升。

Limit Ranges

Limit Ranges 是在 Namespace 里创建的用来管理资源分配(针对 Pod 或者容器)的策略。

示例:Limit Range

apiVersion: v1
kind: LimitRange
metadata:
  name: mem-limit-range
spec:
  limits:
  - default:
      memory: 512Mi
    defaultRequest:
      memory: 256Mi
    type: Container

当您在名为 mem-limit-range 的 Namespace 里创建 Pod 时,如果不为该 Pod 里运行的容器显式分配资源,那么该 Pod 里容器的初始内存分配和最高内存使用将分别是 256Mi 和 512 Mi。

Resource Quotas

当有多个用户或者内部团队共同使用一个集群的时候,有可能会出现其中一个用户或者团队过度占用集群资源的情况,从而导致其他用户或者团队的服务无法正常部署。集群管理员可以借助 Resources Quatas 来解决这个问题。

示例:Resource Quotas

apiVersion: v1
kind: ResourceQuota
metadata:
  name: mem-cpu-demo
spec:
  hard:
    requests.cpu: "1"
    requests.memory: 1Gi
    limits.cpu: "2"
    limits.memory: 2Gi

上述示例意味着在名为 mem-cpu-demo 的 Namespace 范围内:

  • 对于每个 Pod 的每个容器,都必须显式地设置 memory request,memory limit,cpu request 和 cpu limit。
  • 该命名空间中所有 Pod 的内存请求总量不得超过 1 GiB。
  • 该命名空间中所有 Pod 使用的总内存最多不得超过 2 GiB。
  • 该命名空间中所有 Pod 的 CPU 请求总数不得超过 1 个 CPU。
  • 该命名空间中所有 Pod 使用的 CPU 总数不得超过 2 个 CPU。

除了利用 Limit Ranges 和 Resource Quotas 来管理资源分配,您也可以使用 QoS (Quality of Service)来提高资源利用率,比如把关键的服务设置为 Guaranteed,而其他服务根据重要程度可分别设置为 Burstable 或 Best-Effort。

建议:利用 Spot 来降低 Amazon EC2 的成本支出

与 Amazon EC2 按需实例的价格相比,使用 EC2 Spot 实例最高可以节省 90%,用好 Spot 可以在保证业务稳定运行的前提下获得最高的成本节省。Spot 实例的好处有:

  • 物美价廉:Spot 实例的底层物理设施与按需实例完全一致,但是成本最高节省 90%,Spot 实例的价格是由 EC2 实例的长期供需趋势决定的,波动幅度小,并且永远不会超过您的设定价格或者按需价格。
  • 立竿见影:Spot 实例一旦申请成功,立刻就能运行您的工作负载,使用方式与按需实例相同。
  • 简单易用:Spot 实例可以单独使用,也可以与其他 AWS 服务无缝集成(比如:EKS,ECS,EMR 等等),同样也支持与第三发解决方案集成。

Spot 容量池

了解什么是 Spot 容量池有助于您提升申请 Spot 实例成功的概率,如下图所示每个区域中每个可用区中的每个实例类型都是一个单独的 Spot 容量池,这就意味着在我们申请 Spot 实例时,确保可用区和实例类型灵活性(例如:系列、代系、大小) 是成功预置和维护目标容量的关键。

Spot 中断

Spot 实例本质上是 AWS 中暂时没有被用户使用的 EC2 资源,如果有越来越多的按需实例的请求过来,Spot 实例有可能会被提前回收,为了帮助您来更好的处理中断带来的业务变化,AWS 会在 Spot 实例被回收前 2 分钟发出警告通知,您可以利用这 2 分钟时间来对即将中断的业务进行善后,或者状态信息保存等。

正是因为 Spot 的天然会被中断的特性,它特别适用来运行无状态、容错性或灵活性好的工作负载,简单来说就是这些业务或者应用程序可以暂停和恢复或重新启动部分或全部工作,比如:Container、大数据分析、CI/CD、HPC 高性能计算、机器学习、图像和媒体渲染等。尽管 Spot 中断随时可能会发生,但是经过对 2023 年前 3 个月内启动的 Spot 实例进行分析,发现有 95% 的 Spot 实例并没有因为中断而停止,都是在工作负载运行完毕后,由用户主动关闭 Spot 实例的。

Spot 中断主要是利用 EC2 实例元数据服务和 Amazon EventBridge 来通知的,目前主要有以下两类通知:

  • Spot – 实例中断通知(被动):该通知在 Spot 实例被停止或终止之前 2 分钟发出,您可以使用这两分钟时间来自定义实例中断的处理方式。
  • Spot – 实例再平衡建议(主动):该通知可在 Spot 实例处于较高的中断风险时通知您,然后系统会主动尝试创建新的 Spot 实例来取代原有 Spot 实例。这项功能已经与 EC2 Auto Scaling,EC2 Fleet,Spot Fleet 和 EKS Managed Node Groups 进行了集成,您无需手动配置即可使用。

Spot 和 EKS 托管节点组

在 EKS 的托管节点组与 Spot 实例进行集成,有如下特性:

  • 灵活性是成功申请 Spot 实例的关键。这里的灵活性包含:实例灵活性、时间灵活性和地域灵活性。EKS 底层是通过 Managed Node Group / EC2 Auto Scaling Group 来管理 Spot 实例的,在创建节点组的时候尽可能设置多个实例类型,允许多可用区等可以来帮助您快速申请到 Spot 机器,并在 Spot 发生中断的时候,尽快找到替换实例,保证业务持续稳定。
  • 优先考虑 Capacity optimized 或 Price capacity optimized 的分配策略。您可以修改 EC2 Auto Scaling Group 的配置来设置 Capacity optimized 或 Price capacity optimized 的分配策略,前者可以保证容量优先,成功申请到 Spot 实例的概率最高,或者兼顾价格,在成本和业务稳定性之间取得平衡,您可以根据自己业务的需求选择其中一个做为分配策略。
  • 通过托管节点组创建出来的 Spot 实例,默认都会打上 label:amazonaws.com/capacityType: SPOT。您可以在部署应用程序的时候使用这个 label 对 Spot 节点进行选择。
  • EC2 Spot 的主动再平衡功能默认开启,确保您处于高中断风险的 Spot 实例可以提前被识别,并自动被新 Spot 来替换,无需认为干预,最大程度提高业务的连续性和稳定性。

建议:利用 Compute Savings Plans 进一步降低成本

在 AWS 上承诺一定的用量即可享受相应的折扣,购买折扣计划可以在不改变当前云环境,保持服务稳定的前提下立竿见影的节省成本。对于计算资源,目前 AWS 支持的折扣计划是 Savings Plans(SP)和预留实例(Reserved Instances,RI)。SP 与 RI 相比,为客户提供了更多的灵活性和更少的管理成本,客户通过承诺固定的每小时计算资源使用量(以 USD/小时为单位衡量)来节省最多 72% 的计算资源成本,该计划包含 1 年期和 3 年期两种选择。考虑到集群里工作节点动态特性以及实例类型的多样化,推荐您采用 Compute Savings Plans(CSP)进一步降低成本。CSP 是 SP 的一种,可提供更高的灵活性,最高可帮您降低 66% 的成本。这些计划会自动应用于 EC2 实例使用量、Amazon Fargate 和 Amazon Lambda 服务使用量,而不受实例系列、大小、可用区、区域、操作系统或租期的限制。

为了准确预估需要承诺的 CSP 用量,您可以借助于 Amazon Cost Explorer 来帮助您预估(AWS Cost Management > Savings Plans > Recommendations)。

建议:利用 Graviton 进一步提高性价比

Amazon Graviton 处理器是 AWS 自研的,旨在为云上工作负载提供最高性价比。与基于 x86 的 EC2 实例相比,有 40% 的性价比提升。搭有 Graviton 处理器的 EC2 实例可适用于应用服务器、微服务、基于 CPU 的机器学习推理、视频编码、游戏、MySQL 和缓存数据库等负载,尤其可以应用于一下场景:

  • 容器系统的底层资源
  • 大数据在线 / 离线计算集群
  • MySQL / Redis 等在线数据存储系统

建议在 Karpenter 里配置使用 Graviton,根据 Karpenter 的设计原则,它会优先创建 Graviton 类型的机器来运行工作负载,降低整体成本。

培养团队的成本意识

所谓培养成本意识就是确保团队知晓:谁在使用云资源?成本支出被用在了哪里?以及是什么造成了成本支出?准确的获得这些信息可以帮助您提高对成本支出的认知,并在需要的时候快速采取补救措施来进行成本优化。

建议:利用 Kubecost 实现成本可视化

Kubecost 是一个开源的成本管理和优化的工具,可以为您的 Kubernetes 环境提供成本的可视化和深度分析,帮助您持续不断地降低云成本。Kubecost 的主要功能包括:

成本分配

Kubecost 允许您以 Kubernetes 原生对象/概念的维度来对成本进行聚合和展示,基于此您可以为您的业务团队提供完全透明的、准确的、及时的成本数据。

  • 在主要的云提供商和私有部署的 Kubernetes 环境中,按 namespace、deployment、service 以及其他(eg. labels)来细分成本,支持将不同环境的集群在同一个控制台中进行成本分配。
  • 支持以组织的概念来分配成本,进行成本分摊和计费(例如:团队、独立应用、产品/项目、部门或者运行环境等)。
  • 提供单一视图和单一 API 端口来横跨多个集群,甚至多个云提供商进行成本展示。

统一的成本监控

Kubecost 支持与云提供商的账单数据进行集成,可以在单一视图展示集群内和集群外的成本支出。集群内的成本支出指的是计算资源包含的 CPU 和内存等支出,集群外的成本支出指的是集群内部应用所使用的其他云服务,比如数据库服务、AI 类服务、对象存储服务等。

  • 支持将集群内部的实时成本(CPU、内存、存储、网络等)和集群外部的各个云服务成本(RDS 服务、数据仓库、S3 桶等) 在单一视图进行统一展示。
  • 支持将特定的云服务成本和与之对应的集群组件相关联(namespace、deployment 等)。
  • 在整个组织中分配共享的集群内部和集群外部成本,以获得完整而准确的基础设施支出情况。

成本优化

Kubecost 基于您的环境信息和服务使用模式提供个性化的成本优化建议。通过 Kubecost 提供的优化建议,可以帮助您节省高达 30%-50% 的成本,同时您的数据会一直保持在内部,不会被暴露出来,保证成本数据的私密和安全。

  • 通过分析您的工作负载,提供集群级别的优化建议,帮助您在成本和性能之间找到平衡点,提高资源利用率。
  • 通过分析报告向您展示节点和 Pod 级别的资源过度预置和资源预置不足的状况,避免浪费和性能问题。
  • 您不但可以通过 UI 和 API 来查看优化建议,还可以通过 Kubecost 提供的便利方案来快速应用优化建议。

如下图所示,Kubecost 通过监控集群里的资源使用情况,生成节点和 Container 的 Right Sizing 建议。

我们以 Container Right Sizing 为例,请看下图红框部分,Kubecost 会根据 Container 的请求量和实际用量给出合理的推荐值,您可以以此为参考对相应的 Container 进行 Right Sizing 来提高资源利用率。Kubecost 还支持设置自动修复,当检查到某个 Container 资源用量过大或过小时,自动进行资源的配置调整(该功能目前是 beta 版本,资源的配置调整过程会 re-create Pod,请在生产环境中谨慎使用)。

警报和治理

凭借实时警报和定期报告,Kubecost 可以使您的团队将 Kubernetest 相关的成本控制在预算以内。

  • 根据业务需求,为不同的维度来设置预算,比如:team、application 等。
  • 当检测到成本支出超出预算、异常成本变化和较低的成本支出效率时,通过 Slack 或者邮件等发出实时警报。
  • 创建多个不同维度的成本报告,并设置重复周期,Kubecost 可以使您持续的了解成本支出变化和趋势。

与 EKS 集成

Kubecost 与 EKS 的集成主要是通过 Amazon Cost & Usage Report(CUR)来完成。通过与 CUR 集成,Kubecost 可以在成本分配与展示的时候将您的企业折扣、RI/SP、Spot 用量等因素考虑进来,给您提供一个完整的视图来展示成本支出情况。与 EKS 的集成同时也赋予您查看集群外成本的功能,比如:RDS 、S3 等成本。这部分功能的实现需要您按照如下表所示的标签规则给相应的云服务打上标签。

K8s Concept AWS Tag Key AWS Tag Value
Cluster kubernetes_cluster cluster-name
Namespace kubernetes_namespace namespace-name
Deployment kubernetes_deployment deployment-name
Label kubernetes_label_NAME* label-value
DaemonSet kubernetes_daemonset daemonset-name
Pod kubernetes_pod pod-name
Container kubernetes_container container-name

建议:利用 Amazon Cost Explorer 对成本进行多维度的可视化

Amazon Cost Explorer 是 AWS 提供的免费的成本可视化工具,用来帮助您了解和管理过去一年的云成本和使用情况。它还提供了筛选器供您在不同维度分析成本和用量。

EKS 控制平面成本

您可以通过如下图所示的过滤条件来按月或者按天查看 EKS 的控制平面成本。

EKS 数据平面成本

EKS 数据平面成本指的是集群工作节点所属的 EC2 的成本,要对这部分成本进行不同维度的分配和展示就需要您为这些 EC2 资源激活成本分配标签,然后在 Cost Explorer 中对标签进行过滤来进行相应的成本展示。AWS 标签分为 AWS 预置标签和用户自定义的标签。EKS 提供了一个 AWS 预置标签:aws:eks:cluster-name,要想使用这个标签对成本进行分类展示,需要先在控制台激活该成本分配标签,如下图所示:

对成本进行更细粒度的管控和展示,需要您自己定义标签,用户自定义标签的最佳实践是:将成本分配标签与财务报告维度保持一致,并标记一切资源。比如:可以按照业务方向、业务部门、项目名称、产品归属、开发团队、环境划分、时间周期等来定义标签的键和值。

建议:利用 Amazon Trusted Advisor 查看成本优化建议

Amazon Trusted Advisor(TA)是一款在线工具,它可以分析 AWS 环境,并为您提供实时指导,以帮助您基于 AWS 架构完善的框架,进而更加经济高效地预置自己的资源。TA 不仅仅可以用来提供成本优化建议,还支持对 AWS 环境的安全性、容错能力、性能和服务配额进行审查和评估,并给出改进和修改意见。

对于成本优化,Trusted Advisor 可通过分析用量、配置和支出,提出可行的建议以帮助您节省成本。比如:识别空闲 RDS 数据库实例、未充分利用的 EBS 卷、无关联的弹性 IP 地、低利用率的 EC2 等。

Trusted Advisor 还可以提供 Savings Plans 和 Reserved Instances 的购买建议来帮助您通过承诺用量进行成本节省(请注意这些建议是针对所有的 EC2 资源,并不是只针对 EKS 所使用的 EC2 资源)。

建议:利用 EKS 可观测性洞察资源使用情况

CloudWatch Container Insights 能够帮助您从 Amazon ECS、AWS Fargate、Amazon EKS 以及 Kubernetes 环境中收集、聚合并汇总多种指标与查询日志,借此监控容器环境中各项关键资源的运行情况。这些指标包括 CPU、内存、磁盘和网络等等。CloudWatch Container Insights 支持在集群、节点、Pod、任务和服务维度创建 CloudWatch 聚合指标,您还可以根据 CloudWatch Container Insights 收集到的指标设置 CloudWatch 警报。

示例:按照节点的平均 CPU 利用率对节点进行排序

STATS avg(node_cpu_utilization) as avg_node_cpu_utilization by NodeName
| SORT avg_node_cpu_utilization DESC 

示例:按照容器的名字显示其 CPU 的使用情况

stats pct(container_cpu_usage_total, 50) as CPUPercMedian by kubernetes.container_name 
| filter Type="Container"

示例:按照容器的名字显示其磁盘的使用情况

stats floor(avg(container_filesystem_usage/1024)) as container_filesystem_usage_avg_kb by InstanceId, kubernetes.container_name, device 
| filter Type="ContainerFS" 
| sort container_filesystem_usage_avg_kb desc

您可以查看 Container Insights 文档获取更多示例。
通过 Container Insights 您可以更详细的了解各类资源的使用情况,基于这些数据可以更好的帮助您来对成本进行管理和优化。

总结

Amazon EKS 的成本优化是一个系统工程,涉及到的方案和工具也多种多样。需要您根据业务的需求和成本优化的目标,合理的采用一种或者多种方案和工具来协作完成,实现在云中降本增效的目标。

本篇作者

朱修磊

亚马逊云科技技术客户经理,负责企业级客户的架构和成本优化、技术支持等工作,拥有多年的产品研发、技术布道、IT 架构设计和运维经验。