消除大数据处理的资源浪费,实现 90% 成本降低

149 阅读6分钟

本⽂介绍如何通过 Karpenter 动态调度阿⾥云 Spot 实例运⾏ Spark 作业,实现90%+成本节省。涵盖集群搭建、Spark Operator/Karpenter 部署、Executor 弹性扩缩配置及效果验证,提供完整代码示例。
阅读时间约 10 分钟(技术细节较多,含代码及配置步骤)。

Apache Spark 是⼀个专门为大规模数据处理设计的计算引擎,广泛应⽤于数据分析和机器学习等场景。随着 Spark 处理数据量的指数级增⻓,传统的固定资源池模式⾯临 30-50% 的资源浪费,主要源 于 Executor 空置、机型不匹配等问题。为了解决这⼀问题,业内开始引⼊ Karpenter⸺⼀个 Kubernetes 原⽣的弹性扩缩容控制器,通过以下核⼼能⼒有效实现成本优化:

  • 智能调度算法:实时分析 Pod 资源需求,⾃动选择最优机型组合(如 Spot 实例、⾼性价比机型)
  • 秒级弹性伸缩:作业启动时 40~45 秒内完成节点供给,作业结束后自动回收资源
  • 碎片化资源整合:将分散的⼩资源请求聚合到⼤节点,提升整体利⽤率⾄ 85%+

文章首发于公众号【五分钟学大数据】,在公众号后台发送:大数据面试,送你一份最牛X的大数据面试题

为什么选择 Karpenter?

图片

Karpenter 是 Kubernetes 生态中开源的弹性扩缩容控制器,专为提升资源利⽤率和降低调度延迟设计。它通过实时监测集群中 Pending 状态的 Pod 资源需求,智能决策最优节点规格并⾃动创建/回收 实例,40-45 秒内即可完成资源供给(相⽐ Cluster Autoscaler 分钟级响应)。特别适合 Spark 等批处理场景,⽀持动态扩展 Executor 节点池,结合 Spot 实例智能选择最优机型,实现 90%+ 资源成本节省和碎⽚化资源整合。

Spark + Karpenter 的降本实践

1. 创建⼀个集群(以阿里云举例, 已有可跳过)

karpenter 云⼚商适配现状:github.com/kubernetes-…

图片

1.1 terraform create cluster

创建云上K8s集群

aliyun configure
export ALICLOUD_ACCESS_KEY=<aliyun access key>
export ALICLOUD_SECRET_KEY=<aliyun secret key>
export ALICLOUD_REGION="cn-shenzhen"
git clone https://github.com/cloudpilot-ai/examples.git
cd examples/clusters/ack-spot-flannel
export TF_VAR_CLUSTER_NAME=<your_cluster_name>
terraform init
terraform apply --auto-approve

获取集群kubeconfig

export CLUSTER_NAME=<your_cluster_name>
export KUBECONFIG=~/.kube/demo
export CLUSTER_ID=$(aliyun cs GET /clusters | jq -r --arg CLUSTER_NAME
"$CLUSTER_NAME" '.[] | select(.name == $CLUSTER_NAME) | .cluster_id')
aliyun cs GET /k8s/$CLUSTER_ID/user_config | jq -r '.config' > $KUBECONFIG

1.2 检查集群状态

kubectl get node
NAME                       STATUS   ROLES    AGE   VERSION
cn-shenzhen.172.16.1.159   Ready    <none>   32m   v1.31.1-aliyun.1
cn-shenzhen.172.16.2.181   Ready    <none>   32m   v1.31.1-aliyun.1

2. 部署 spark-operator

Spark Operator 提供了在 Kubernetes 集群中自动化部署和管理 Spark 作业⽣命周期的功能。

2.1 安装 spark-operator

# Add the Helm repository
helm repo add spark-operator https://kubeflow.github.io/spark-operator
helm repo update
# Install the operator into the spark-operator namespace and wait for
deployments to be ready
helm install spark-operator spark-operator/spark-operator \
--namespace spark-operator --create-namespace --wait

请注意:Spark History Server 也需要安装,尽管未在此处单独列出。

2.2 测试spark

# Create an example application in the default namespace
kubectl apply -f https://raw.githubusercontent.com/kubeflow/sparkoperator/refs/heads/master/examples/spark-pi.yaml
# Get the status of the application
kubectl get sparkapp spark-pi

3 部署 karpenter(karpenter-provider-alibabacloud)

docs.cloudpilot.ai/karpenter-a…

  1. 配置环境变量
export CLUSTER_NAME=<your_cluster_name> # Config your cluster
name
export CLUSTER_REGION=<your_cluster_region> # Config the
alibabacloud region
export ALIBABACLOUD_AK=<alibaba_cloud_access_key> # Config the
alibabacloud AK
export ALIBABACLOUD_SK=<alibaba_cloud_secret_key> # Config the
alibabacloud SK
export CLUSTER_ID=$(aliyun cs GET /clusters | jq -r --arg CLUSTER_NAME
"$CLUSTER_NAME" '.[] | select(.name == $CLUSTER_NAME) | .cluster_id')
export VSWITCH_IDS=$(aliyun cs GET /api/v1/clusters --header "ContentType=application/json;" | jq -r --arg CLUSTER_NAME "$CLUSTER_NAME"
'.clusters[] | select(.name == $CLUSTER_NAME) | .vswitch_ids[]')
export SECURITYGROUP_ID=$(aliyun cs GET /api/v1/clusters --header "ContentType=application/json;" | jq -r --arg CLUSTER_NAME "$CLUSTER_NAME"
'.clusters[] | select(.name == $CLUSTER_NAME) | .security_group_id')

2. Tag安全组和vSwitch

# Tag the security group
aliyun tag TagResources --region ${CLUSTER_REGION} --RegionId
${CLUSTER_REGION} --ResourceARN.1
"acs:ecs:*:*:securitygroup/${SECURITYGROUP_ID}" --Tags "
{"karpenter.sh/discovery": "$CLUSTER_NAME"}"
# Tag the vswitch
IFS=' '
while IFS= read -r vs_id; do
aliyun tag TagResources --region ${CLUSTER_REGION} --RegionId
${CLUSTER_REGION} --ResourceARN.1 "acs:vpc:*:*:vswitch/${vs_id}" --Tags "
{"karpenter.sh/discovery": "$CLUSTER_NAME"}"
done <<< "$VSWITCH_IDS"

3. 安装Karpenter

helm repo add karpenter-provider-alibabacloud https://cloudpilot-ai.github.io/karpenter-provider-alibabacloud

helm upgrade karpenter karpenter-provider-alibabacloud/karpenter --install --version 0.1.4 \
  --namespace karpenter-system --create-namespace \
  --set "alibabacloud.access_key_id"=${ALIBABACLOUD_AK} \
  --set "alibabacloud.access_key_secret"=${ALIBABACLOUD_SK} \
  --set "alibabacloud.region_id"=${CLUSTER_REGION} \
  --set controller.settings.clusterID=${CLUSTER_ID} \
  --wait

4. 创建NodePool/ECSNodeClass

还可通过nodepool指定instance-family的⽅式, 调度出所期望的不同规格实例类型(如下注释部分)。

cat > nodeclass.yaml <<EOF
apiVersion: karpenter.k8s.alibabacloud/v1alpha1
kind: ECSNodeClass
metadata:
  name: defaultnodeclass
spec:
  vSwitchSelectorTerms:
    - tags:
        karpenter.sh/discovery: "${CLUSTER_NAME}" # replace with your cluster name
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: "${CLUSTER_NAME}" # replace with your cluster name
  imageSelectorTerms:
    # ContainerOS only support x86_64 linux nodes, and it's faster to initialize
    - alias: ContainerOS
  systemDisk:
    categories:
    - cloud_auto
    performanceLevel: PL0
    size: 20
EOF

kubectl apply -f nodeclass.yaml

cat > nodepool.yaml <<EOF
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: ecsnodepool
spec:
  disruption:
    budgets:
      - nodes: 95%
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 1m
  template:
    spec:
      requirements:
        # - key: karpenter.k8s.alibabacloud/instance-family
        #  operatorIn
        #  values: [ "ecs.g5" ]
        - key: kubernetes.io/arch
          operatorIn
          values: [ "amd64" ]
        - key: kubernetes.io/os
          operatorIn
          values: ["linux"]
        - key: karpenter.sh/capacity-type
          operatorIn
          values: ["spot""on-demand"]
      nodeClassRef:
        group"karpenter.k8s.alibabacloud"
        kind: ECSNodeClass
        name: defaultnodeclass
EOF

kubectl apply -f nodepool.yaml

4. 测试spark 作业弹性运行

4.1 修改spark-pi

为了最大程度降低运行成本,同时确保稳定性,将driver运⾏在on-demand节点,executor运⾏在 Spot节点。如下为具体原因:

1.Driver 的重要性与稳定性要求
Driver 是 Spark 作业的核⼼,负责协调整个作业的执行,向 executors 分配任务,监控任务执⾏状 态,并且最终收集和输出结果。
如果 driver 运⾏在 Spot Instances 上,可能会在实例被中断时导致整个 Spark 作业失败或需要重新启动。由于 Spot Instances 可能会被回收,因此 driver 的稳定性⾄关重要,为了防止作业因为Spot 实例中断⽽失败,通常会将 driver 部署在 On-Demand Instances 上。
2.Executor 的容错性和弹性Executor 是负责执行实际计算任务的工作单元,它们可以在作业执⾏期间动态启动和停⽌。虽然 Spot Instances 具有较高的中断风险,但它们通常⽐ On-Demand Instances 便宜,适合用来处理计算密集型任务。
如果⼀个 executor 在 Spot Instance 上被回收,Spark 会⾃动将任务重新调度到其他 executor。这种容错机制保证了即使某些 Spot Instances 被中断,作业依然能够继续执⾏,因此在 Spot Instances 上运⾏ executors 是可⾏的。

通过 节点亲和性策略 实现 Driver/Executor 差异化节点弹性&调度:

spec:
  arguments:
  - "5000"
  deps: {}
  driver:
    affinity:
      nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
          - matchExpressions:
            - key: karpenter.sh/capacity-type
              operator: NotIn
              values:
              - spot
    cores: 1
    labels:
      version: 3.5.3
    memory: 512m
    serviceAccount: spark-operator-spark
  executor:
    affinity:
      nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
          - matchExpressions:
            - key: karpenter.sh/capacity-type
              operator: In
              values:
              - spot
    cores: 1
    instances: 20
    labels:
      version: 3.5.3
    memory: 512m

4.2 查看karpenter 创建节点情况

kubectl get nodeclaim
NAME                TYPE                  CAPACITY   ZONE            NODE                      READY   AGE
ecsnodepool-n6b5h   ecs.e-c1m2.2xlarge    spot       cn-shenzhen-f   cn-shenzhen.172.16.4.94   True    55s
ecsnodepool-q4w5l   ecs.u1-c1m2.4xlarge   spot       cn-shenzhen-f   cn-shenzhen.172.16.4.95   True    45s

4.3 等待spark作业运⾏结束

kubectl get sparkapplications spark-pi
NAME       STATUS      ATTEMPTS   START                  FINISH                 AGE
spark-pi   COMPLETED   1          2025-02-20T10:24:14Z   2025-02-20T10:26:18Z   45m

4.4 instance成本

本次 Spark 作业中,Karpenter 根据 Executor 资源需求⾃动创建 2 个 Spot 实例节点(规格:ecs.ec1m2.2xlarge/ecs.u1-c1m2.4xlarge),作业完成后⾃动回收节点,避免资源闲置。

通过对⽐实验:

  • Spot 实例成本:仅为按需实例的 10-30%(参⻅实时价格截图)
  • 总成本对⽐:相⽐传统 Cluster Autoscaler ⽅案,Karpenter 通过 机型选择算法 + 碎片化资源整合,综合成本降低 80%+
  • 包年包⽉场景:若原有集群采⽤固定资源,改用 Karpenter 弹性架构后,闲置时段资源归零,预估 可再降低 40-60% 长期⽀出。

下⽅图片显⽰了ecs.e-c1m2.2xlarge及ecs.u1-c1m2.4xlarge的实时价格。截取自 Spot 查询⼯具 Spot

Insights(spot.cloudpilot.ai),目前支持阿里云和 AWS Spot实例价格和中断率查询,欢迎访问!

图片

图片

5. (可选) 删除spark作业

kubectl delete -f https://raw.githubusercontent.com/kubeflow/sparkoperator/refs/heads/master/examples/spark-pi.yaml

6. (可选) 卸载karpenter

helm uninstall karpenter --namespace karpenter-system
kubectl delete ns karpenter-system

文章首发于公众号【五分钟学大数据】,在公众号后台发送:大数据面试,送你一份最牛X的大数据面试题

总结

本⽂展示了如何将 Spark 作业与 Karpenter 相结合,通过动态调度阿里⾥云 Spot 实例,实现超过 90%的显著成本节省。我们详细探讨了从集群搭建、Spark Operator 部署、Karpenter 配置,到 Spark 作业弹性伸缩及成本优化的全过程。

通过具体的部署步骤和配置示例,展⽰了在 Kubernetes 环境中如何最⼤化利用 Karpenter 的弹性扩 缩容能力,优化资源使⽤,并有效降低计算成本。这为重度使⽤ Spark 的企业提供了⼀种高效、经济的云计算解决⽅。

相关开源项⽬(期待Star)