使用ECI运行Argo工作流

Serverless K8s支持Pod粒度的弹性,具有秒级启动、秒级计费、2000每分钟的并发等优势,因此越来越多的用户开始使用Serverless K8s来运行Argo。本文介绍如何基于ACK集群,使用ECI运行Argo工作流。

搭建Kubernetes+Argo环境

  1. 搭建阿里云Serverless K8s集群。

  2. 在Kubernetes集群中部署Argo。

    • (推荐)安装ack-workflow组件。具体操作,请参见ack-workflow

    • 自行部署Argo。具体操作,请参见Argo Quick Start

  3. 安装Argo命令。具体操作,请参见argo-workflows

优化基础资源配置

默认情况下,完成Argo部署后,argo-server和workflow-controller这两个核心组件并没有指定对应Pod的resources,这会导致这两个组件对应Pod的QoS级别较低,在集群资源不足时会出现组件OOM Kill、Pod被驱逐的情况。因此,建议您根据自身集群规模调整上述两个组件对应Pod的resources,建议其requests或limits设置在2 vCPU,4 GiB内存及以上。

使用OSS作为artifacts仓库

默认情况下,Argo使用minio作为artifacts仓库,在生产环境中则需要考虑artifacts仓库的稳定性,ack-workflow支持使用OSS作为artifacts仓库。关于如何配置OSS作为artifacts仓库,请参见Configuring Alibaba Cloud OSS

配置成功后,您可以使用以下示例创建Wokrflow进行验证。

  1. 将以下内容保存为workflow-oss.yaml。

    apiVersion: argoproj.io/v1alpha1
    kind: Workflow
    metadata:
      generateName: artifact-passing-
    spec:
      entrypoint: artifact-example
      templates:
      - name: artifact-example
        steps:
        - - name: generate-artifact
            template: whalesay
        - - name: consume-artifact
            template: print-message
            arguments:
              artifacts:
              # bind message to the hello-art artifact
              # generated by the generate-artifact step
              - name: message
                from: "{{steps.generate-artifact.outputs.artifacts.hello-art}}"
    
      - name: whalesay
        container:
          image: docker/whalesay:latest
          command: [sh, -c]
          args: ["cowsay hello world | tee /tmp/hello_world.txt"]
        outputs:
          artifacts:
          # generate hello-art artifact from /tmp/hello_world.txt
          # artifacts can be directories as well as files
          - name: hello-art
            path: /tmp/hello_world.txt
    
      - name: print-message
        inputs:
          artifacts:
          # unpack the message input artifact
          # and put it at /tmp/message
          - name: message
            path: /tmp/message
        container:
          image: alpine:latest
          command: [sh, -c]
          args: ["cat /tmp/message"]
  2. 创建Workflow。

    argo -n argo submit workflow-oss.yaml
  3. 查看Workflow的执行结果。

    argo -n argo list

    预期返回:

    argo-oss

选择Executor

Argo创建的每个工作Pod中至少会有以下两个容器:

  • main容器

    实际的业务容器,真正运行业务逻辑。

  • wait容器

    Argo的系统组件,以Sidecar的形式注入到Pod内。其核心作用如下:

    • 启动阶段

      • 加载main容器依赖的artifacts、inputs。

    • 运行阶段

      • 等待main容器退出,Kill关联的Sidecars容器。

      • 收集main容器的outputs、artifacts,上报main容器状态

Executor是wait容器访问和控制main容器的“桥梁”,Argo将其抽象为ContainerRuntimeExecutor,其接口定义如下:

  • GetFileContents:获取main容器的输出参数(outputs/parameters)。

  • CopyFile:获取main容器的产物(outputs/artifacts)。

  • GetOutputStream:获取main的标准输出(含标准错误)。

  • Wait:等待main容器退出。

  • Kill:Kill关联的Sidecar容器。

  • ListContainerNames:列举Pod内容器的名称列表。

目前Argo已支持多种Executor,其工作原理不同,但都是针对原生K8s架构设计的。由于阿里云Serverless K8s与原生K8s在架构上存在差异,因此需要选择合适的Executor。建议您选择Emisarry作为Serverless K8s场景运行Argo的Executor,相关说明如下:

Executor

说明

Emisarry

通过EmptyDir共享文件的方式实现相关能力,依赖EmptyDir。

由于该Executor仅依赖标准能力EmptyDir,无其他依赖,因此推荐使用该Executor。

Kubernetes API

通过Kubernetes API方式实现相关功能,依赖Kubernetes API但功能不完整。

由于该Executor功能不完整,且在大规模场景中会给K8s控制层面带来压力,影响集群规模,因此不推荐使用该Executor。

PNS

基于Pod内的PID共享和chroot实现相关功能,会污染Pod的进程空间,并且依赖特权。

由于Serverless K8s具有更高的安全隔离性,不支持使用特权,因此无法使用该Executor。

Docker

通过Docker CLI实现相关功能,依赖底层容器运行时Docker。

由于Serverless K8s没有真实节点,不支持访问节点上的Docker组件,因此无法使用该Executor。

Kubelet

通过Kubelet Client API实现相关功能,依赖Kubernetes底层组件Kubelet。

由于Serverless K8s没有真实节点,不支持访问节点上的Kubelet组件,因此无法使用该Executor。

调度Argo任务到ECI

ACK Serverless集群默认会将所有Pod都调度到ECI,因此不需要额外配置。ACK集群需要配置后才能将Pod调度到ECI,具体配置方式请参见调度Pod到x86架构的虚拟节点

以下YAML以配置Label的方式为例:

  • 添加alibabacloud.com/eci: "true"的Label:添加相应Label后,Pod会被自动调度到ECI。

  • (可选)指定{"schedulerName": "eci-scheduler"}:建议配置,虚拟节点(VK)升级或变更时,WebHook会有短暂不可用,配置后可以避免Pod调度到普通节点。

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: parallelism-limit1-
spec:
  entrypoint: parallelism-limit1
  parallelism: 10
  podSpecPatch: '{"schedulerName": "eci-scheduler"}'  #指定调度到ECI
  podMetadata:
    labels:
      alibabacloud.com/eci: "true"   #配置Label将Pod调度到ECI
  templates:
  - name: parallelism-limit1
    steps:
    - - name: sleep
        template: sleep
        withSequence:
          start: "1"
          end: "10"
  - name: sleep
    container:
      image: alpine:latest
      command: ["sh", "-c", "sleep 30"]

提升Pod创建成功率

在生产环境中,运行一条Argo工作流通常会涉及到多个计算Pod,工作流中的任意一个Pod失败都会导致整条工作流失败。如果Argo工作流的成功率不高,您就需要多次重新运行Argo工作流,这不仅影响任务的执行效率,也会增加计算成本。因此,您需要采取一些策略来提升Pod成功率:

  • 定义Argo工作流时

    • 配置Argo重试策略,用于失败后自动进行重试。具体操作,请参见Retries

    • 配置工作流超时,限制工作流的运行时间。具体操作,请参见Timeouts

  • 创建ECI Pod时

    • 配置多可用区,避免因可用区库存不足导致Pod创建失败。具体操作,请参见多可用区创建Pod

    • 指定多规格,避免因特定规格库存不足导致Pod创建时报。具体操作,请参见多规格创建Pod

    • 优先使用指定vCPU和内存的方式创建Pod,ECI会自动根据库存情况匹配符合要求的规格。

    • 使用2 vCPU,4 GiB及以上规格创建Pod,这类规格实例都是独占的企业级实例,可以确保性能的稳定性。

    • 设置Pod故障处理策略,设置Pod创建失败后是否尝试重新创建。具体操作,请参见设置Pod故障处理策略

配置示例如下:

  1. 编辑eci-profile配置多可用区。

    kubectl edit -n kube-system cm eci-profile

    data中配置vSwitchIds的值为多个交换机ID:

    data:
    ......
      vSwitchIds: vsw-2ze23nqzig8inprou****,vsw-2ze94pjtfuj9vaymf****  #指定多个交换机ID来配置多可用区
      vpcId: vpc-2zeghwzptn5zii0w7****
    ......
  2. 创建Pod时使用多个策略提升成功率。

    • 配置k8s.aliyun.com/eci-use-specs指定多种规格,本示例指定了三个规格,匹配顺序依次为ecs.c6.largeecs.c5.large2-4Gi

    • 配置k8s.aliyun.com/eci-schedule-strategy设置多可用区调度策略,本示例使用VSwitchRandom,表示随机调度。

    • 配置retryStrategy设置Argo重试策略,本示例设置retryPolicy: "Always",表示重试所有失败的步骤。

    • 配置k8s.aliyun.com/eci-fail-strategy设置Pod故障处理策略,本示例使用fail-fast,表示快速失败。Pod创建失败后直接报错。Pod显示为ProviderFailed状态,由上层编排决定是否重试,或者把Pod创建调度到普通节点。

    apiVersion: argoproj.io/v1alpha1
    kind: Workflow
    metadata:
      generateName: parallelism-limit1-
    spec:
      entrypoint: parallelism-limit1
      parallelism: 10
      podSpecPatch: '{"schedulerName": "eci-scheduler"}'
      podMetadata:
        labels:
          alibabacloud.com/eci: "true"
        annotations:
          k8s.aliyun.com/eci-use-specs: "ecs.c6.large,ecs.c5.large,2-4Gi"
          k8s.aliyun.com/eci-schedule-strategy: "VSwitchRandom"
          k8s.aliyun.com/eci-fail-strategy: "fail-fast"
      templates:
      - name: parallelism-limit1
        steps:
        - - name: sleep
            template: sleep
            withSequence:
              start: "1"
              end: "10"
      - name: sleep
        retryStrategy:
          limit: "3"
          retryPolicy: "Always"
        container:
          image: alpine:latest
          command: [sh, -c, "sleep 30"]

优化Pod使用成本

ECI支持多种计费方式,对于不同的业务负载,合理规划其计费方式可以有效降低计算资源的使用成本。

具体优化成本的方式请参见:

加速Pod创建

启动Pod时需要先拉取您指定的容器镜像,但因网络和容器镜像大小等因素,镜像拉取耗时往往成了Pod启动的主要耗时。为加速ECI Pod的创建速度,ECI提供镜像缓存功能。您可以预先将需要使用的镜像制作成镜像缓存,然后基于该镜像缓存来创建ECI Pod,以此来避免或者减少镜像层的下载,从而提升Pod创建速度。

镜像缓存分为以下两类:

  • 自动创建:ECI默认已开启自动创建镜像缓存功能。创建ECI Pod时,如果没有完全匹配的镜像,ECI会自动使用该Pod对应的镜像制作镜像缓存。

  • 手动创建:支持通过CRD的方式

    执行高并发Argo任务前建议先手动创建镜像缓存。镜像缓存完成创建后,指定该镜像缓存,并将Pod的镜像拉取策略设置为IfNotPresent,实现Pod在启动阶段无需拉取镜像,可以加速Pod创建,缩短Argo任务的运行时长,降低运行成本。更多信息,请参见使用ImageCache加速创建Pod

如果之前已经执行了上文的示例进行测试,则目前ECI已经自动创建了镜像缓存,您可以登录弹性容器实例控制台查看镜像缓存状态。基于已有的镜像缓存,您可以使用以下YAML创建Wokrflow,以此测试Pod启动速度。

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: parallelism-limit1-
spec:
  entrypoint: parallelism-limit1
  parallelism: 100
  podSpecPatch: '{"schedulerName": "eci-scheduler"}'
  podMetadata:
    labels:
      alibabacloud.com/eci: "true"
    annotations:
      k8s.aliyun.com/eci-use-specs: "ecs.c6.large,ecs.c5.large,2-4Gi"
      k8s.aliyun.com/eci-schedule-strategy: "VSwitchRandom"
      k8s.aliyun.com/eci-fail-strategy: "fail-fast"
  templates:
  - name: parallelism-limit1
    steps:
    - - name: sleep
        template: sleep
        withSequence:
          start: "1"
          end: "100"
  - name: sleep
    retryStrategy:
      limit: "3"
      retryPolicy: "Always"
    container:
      imagePullPolicy: IfNotPresent
      image: alpine:latest
      command: [sh, -c, "sleep 30"]

创建成功后,从Wokrflow对应的ECI Pod的事件中,可以看到匹配到的镜像缓存ID,且Pod启动时跳过了镜像拉取过程。

argo-imc

加速数据加载

Argo广泛应用于AI推理领域,计算任务通常需要访问大量的数据,在目前流行的存算分离的架构中,计算节点加载数据的效率会直接影响整批任务的耗时和成本。如果Argo任务需要大量并发访问存储中的数据,存储的带宽和性能会成为瓶颈。以OSS为例,当Argo任务并发加载OSS中的数据,OSS Bucket的带宽达到瓶颈时,Argo任务的每个计算节点都会阻塞在数据加载阶段,导致每个计算节点耗时延长,不仅影响计算效率,也会增加计算成本。

对于上述问题,数据加速Fluid可以有效改善这类问题。执行批量计算之前,您可以先创建Fluid Dataset并进行预热,将OSS中的数据预缓存到少量的缓存节点,然后再启动并发的Argo任务。使用Fluid后,Argo任务从缓存节点读取数据,缓存节点可以扩展OSS的带宽,提升计算节点的数据加载效率,最终提升Argo任务的执行效率,降低Argo任务的运行成本。更多关于Fluid的信息,请参见数据加速Fluid概述

配置示例如下,以下示例演示100个并发任务从OSS加载10 GB的测试文件并计算MD5。

  1. 部署Fluid。

    1. 登录容器服务管理控制台

    2. 在左侧导航栏,选择市场>应用市场

    3. 找到ack-fluid,单击对应的卡片。

    4. ack-fluid页面,单击一键部署

    5. 在弹出面板选择目标集群,并完成参数配置,单击确定

      部署完成后,将自动转到ack-fluid发布详情页面,返回Helm页面可以看到ack-fluid的状态为已部署;您也可以通过kubectl命令检查Fluid是否部署成功。

      argo-fluid

  2. 准备测试数据。

    部署Fluid后可以通过Fluid的Dataset实现数据加速。执行后续操作前,需要在OSS Bucket中上传一个10 GB的测试文件。

    1. 生成测试文件。

      dd if=/dev/zero of=/test.dat bs=1G count=10
    2. 上传测试文件到OSS Bucket。具体操作,请参见上传文件

  3. 创建加速数据集。

    1. 创建Dataset和JindoRuntime。

      kubectl -n argo apply -f dataset.yaml

      dataset.yaml的内容示例如下,请根据实际情况替换YAML中的AccessKey和OSS Bucket信息等。

      apiVersion: v1
      kind: Secret
      metadata:
        name: access-key
      stringData:
        fs.oss.accessKeyId: ***************         # 有权限访问OSS Bucket的AccessKeyID
        fs.oss.accessKeySecret: ******************  # 有权限访问OSS Bucket的AccessKeySecret
      ---
      apiVersion: data.fluid.io/v1alpha1
      kind: Dataset
      metadata:
        name: serverless-data
      spec:
        mounts:
        - mountPoint: oss://oss-bucket-name/            # 您的OSS Bucket路径
          name: demo
          path: /
          options:
            fs.oss.endpoint: oss-cn-shanghai-internal.aliyuncs.com  #OSS Bucket对应的Endpoint
          encryptOptions:
            - name: fs.oss.accessKeyId
              valueFrom:
                secretKeyRef:
                  name: access-key
                  key: fs.oss.accessKeyId
            - name: fs.oss.accessKeySecret
              valueFrom:
                secretKeyRef:
                  name: access-key
                  key: fs.oss.accessKeySecret
      ---
      apiVersion: data.fluid.io/v1alpha1
      kind: JindoRuntime
      metadata:
        name: serverless-data
      spec:
        replicas: 10         #创建JindoRuntime缓存节点的数量
        podMetadata:
          annotations:
            k8s.aliyun.com/eci-use-specs: ecs.g6.2xlarge  #指定合适的规格
            k8s.aliyun.com/eci-image-cache: "true"
          labels:
            alibabacloud.com/eci: "true"
        worker:
          podMetadata:
            annotations:
              k8s.aliyun.com/eci-use-specs: ecs.g6.2xlarge #指定合适的规格
        tieredstore:
          levels:
            - mediumtype: MEM          #缓存类型。如果指定了本地盘规格,可使用LoadRaid0
              volumeType: emptyDir
              path: /local-storage     #缓存路径
              quota: 12Gi              #缓存最大容量
              high: "0.99"             #存储容量上限
              low: "0.99"              #存储容量下限
      说明

      本文使用ECI Pod内存作为数据缓存节点,由于每个ECI Pod都有独享的VPC网卡,因此带宽上不会受其他Pod影响。

    2. 查看结果。

      • 确认加速数据集的状态,PHASE为Bound表示创建成功。

        kubectl -n argo get dataset

        预期返回:

        argo-dataset

      • 查看Pod信息,可以看到通过加速数据集已经创建了10个JindoRuntime缓存节点。

        kubectl -n argo get pods

        预期返回:

        argo-dataset2

  4. 预热数据。

    加速数据集就绪后,可以创建Dataload触发数据预热。

    1. 创建Dataload触发数据预热。

      kubectl -n argo apply -f dataload.yaml

      dataload.yaml的内容示例如下

      apiVersion: data.fluid.io/v1alpha1
      kind: DataLoad
      metadata:
        name: serverless-data-warmup
        namespace: argo
      spec:
        dataset:
          name: serverless-data
          namespace: argo
        loadMetadata: true
    2. 确认Dataload数据预热的进度。

      kubectl -n argo get dataload

      预期返回如下,可以看到虽然测试文件有10 GB,但预热速度是很快的。

      argo-dataload

  5. 运行Argo工作流。

    完成数据预热后即可并发运行Argo任务,建议配合使用镜像缓存进行测试。

    1. 准备Argo工作流配置文件argo-test.yaml。

      argo-test.yaml的内容示例如下:

      apiVersion: argoproj.io/v1alpha1
      kind: Workflow
      metadata:
        generateName: parallelism-fluid-
      spec:
        entrypoint: parallelism-fluid
        parallelism: 100
        podSpecPatch: '{"terminationGracePeriodSeconds": 0, "schedulerName": "eci-scheduler"}'
        podMetadata:
          labels:
            alibabacloud.com/fluid-sidecar-target: eci
            alibabacloud.com/eci: "true"
          annotations:
            k8s.aliyun.com/eci-use-specs: 8-16Gi
        templates:
        - name: parallelism-fluid
          steps:
          - - name: domd5sum
              template: md5sum
              withSequence:
                start: "1"
                end: "100"
        - name: md5sum
          container:
            imagePullPolicy: IfNotPresent
            image: alpine:latest
            command: ["sh", "-c", "cp /data/test.dat /test.dat && md5sum test.dat"]
            volumeMounts:
            - name: data-vol
              mountPath: /data
          volumes:
          - name: data-vol
            persistentVolumeClaim:
              claimName: serverless-data
    2. 创建Workflow。

      argo -n argo submit argo-test.yaml
    3. 查看Workflow执行结果。

      argo -n argo list

      预期返回:

      argo-fluid-1

      通过kubectl get pod -n argo --watch命令进一步观察Pod的执行进度,可以看到示例场景的100个Argo任务基本在2~4分钟左右完成。

      argo-fluid-2

      对于同样的一组Argo任务,如果不使用数据加速,直接从OSS中加载10 G的测试文件并计算MD5,耗时大概在14~15分钟左右。

      argo-fluid-3

      对比可以得出数据加速Fluid既可以提升计算效率,也可以大幅度降低计算成本。