cloudpilot-ai

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

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

本文介绍如何通过 Karpenter 动态调度阿里云 Spot 实例运行 Spark 作业,实现 90%+ 成本节省。

涵盖集群搭建、Spark Operator/Karpenter 部署、Executor 弹性扩缩配置及效果验证,提供完整代码示例。

阅读时间约 10 分钟(技术细节较多,含代码及配置步骤)。

Apache Spark 是⼀个专门为大规模数据处理设计的计算引擎,广泛应用于数据分析和机器学习等场景。随着 Spark 处理数据量的指数级增长,传统的固定资源池模式面临 30-50% 的资源浪费,主要源于 Executor 空置、机型不匹配等问题。

为了解决这⼀问题,业内开始引⼊ Karpenter ⸺⼀个 Kubernetes 原生的弹性扩缩容控制器,通过以下核心能力有效实现成本优化:

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

为什么选择 Karpenter?

Karpenter 是 Kubernetes 生态中开源的弹性扩缩容控制器,专为提升资源利用率和降低调度延迟设计。

它通过实时监测集群中 Pending 状态的 Pod 资源需求,智能决策最优节点规格并自动创建/回收实例,40-45 秒内即可完成资源供给(相比 Cluster Autoscaler 分钟级响应)。

特别适合 Spark 等批处理场景,支持动态扩展 Executor 节点池,结合 Spot 实例智能选择最优机型,实现 90%+ 资源成本节省和碎⽚化资源整合。

Spark + Karpenter 的降本实践

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

Karpenter 云⼚商适配现状:https://github.com/kubernetes-sigs/karpenter?tab=readme-ov-file#karpenter-implementations

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)

https://docs.cloudpilot.ai/karpenter-alibabacloud/v0_1/getting_started/set-up-a-cluster-andadd-karpenter#step-3-config-the-environment-variables

  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')
  1. 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"
  1. 安装 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
  1. 创建 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

总结

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

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

相关开源项目(期待 Star):

推荐阅读

弹性工具选Karpenter还是Cluster Autoscaler?看这篇就知道啦!

劲省85%云成本!在K8s上使用Karpenter私有部署DeepSeek-R1

服务600+客户的3D生成AIGC公司如何实现GPU成本降低70%?

posted on   CloudPilotAI  阅读(15)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示