Serverless K8s支持Pod粒度的弹性,具有秒级启动、秒级计费、2000每分钟的并发等优势,因此越来越多的用户开始使用Serverless K8s来运行Argo。本文介绍如何基于ACK集群,使用ECI运行Argo工作流。
搭建Kubernetes+Argo环境
搭建阿里云Serverless K8s集群。
(推荐)创建ACK Serverless集群。具体操作,请参见创建集群。
创建ACK集群,并部署ack-virtual-node组件生成虚拟节点。具体操作,请参见创建Kubernetes托管版集群和通过虚拟节点将Pod调度到ECI上运行。
在Kubernetes集群中部署Argo。
(推荐)安装ack-workflow组件。具体操作,请参见ack-workflow。
自行部署Argo。具体操作,请参见Argo Quick Start。
安装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进行验证。
将以下内容保存为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"]
创建Workflow。
argo -n argo submit workflow-oss.yaml
查看Workflow的执行结果。
argo -n argo list
预期返回:
选择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工作流时
创建ECI Pod时
配置多可用区,避免因可用区库存不足导致Pod创建失败。具体操作,请参见多可用区创建Pod、
指定多规格,避免因特定规格库存不足导致Pod创建时报。具体操作,请参见多规格创建Pod。
优先使用指定vCPU和内存的方式创建Pod,ECI会自动根据库存情况匹配符合要求的规格。
使用2 vCPU,4 GiB及以上规格创建Pod,这类规格实例都是独占的企业级实例,可以确保性能的稳定性。
设置Pod故障处理策略,设置Pod创建失败后是否尝试重新创建。具体操作,请参见设置Pod故障处理策略。
配置示例如下:
编辑eci-profile配置多可用区。
kubectl edit -n kube-system cm eci-profile
在
data
中配置vSwitchIds
的值为多个交换机ID:data: ...... vSwitchIds: vsw-2ze23nqzig8inprou****,vsw-2ze94pjtfuj9vaymf**** #指定多个交换机ID来配置多可用区 vpcId: vpc-2zeghwzptn5zii0w7**** ......
创建Pod时使用多个策略提升成功率。
配置
k8s.aliyun.com/eci-use-specs
指定多种规格,本示例指定了三个规格,匹配顺序依次为ecs.c6.large
、ecs.c5.large
、2-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广泛应用于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。
部署Fluid。
登录容器服务管理控制台。
在左侧导航栏,选择市场>应用市场。
找到ack-fluid,单击对应的卡片。
在ack-fluid页面,单击一键部署。
在弹出面板选择目标集群,并完成参数配置,单击确定。
部署完成后,将自动转到ack-fluid发布详情页面,返回Helm页面可以看到ack-fluid的状态为已部署;您也可以通过kubectl命令检查Fluid是否部署成功。
准备测试数据。
部署Fluid后可以通过Fluid的Dataset实现数据加速。执行后续操作前,需要在OSS Bucket中上传一个10 GB的测试文件。
生成测试文件。
dd if=/dev/zero of=/test.dat bs=1G count=10
上传测试文件到OSS Bucket。具体操作,请参见上传文件。
创建加速数据集。
创建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影响。
查看结果。
确认加速数据集的状态,PHASE为Bound表示创建成功。
kubectl -n argo get dataset
预期返回:
查看Pod信息,可以看到通过加速数据集已经创建了10个JindoRuntime缓存节点。
kubectl -n argo get pods
预期返回:
预热数据。
加速数据集就绪后,可以创建Dataload触发数据预热。
创建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
确认Dataload数据预热的进度。
kubectl -n argo get dataload
预期返回如下,可以看到虽然测试文件有10 GB,但预热速度是很快的。
运行Argo工作流。
完成数据预热后即可并发运行Argo任务,建议配合使用镜像缓存进行测试。
准备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
创建Workflow。
argo -n argo submit argo-test.yaml
查看Workflow执行结果。
argo -n argo list
预期返回:
通过
kubectl get pod -n argo --watch
命令进一步观察Pod的执行进度,可以看到示例场景的100个Argo任务基本在2~4分钟左右完成。对于同样的一组Argo任务,如果不使用数据加速,直接从OSS中加载10 G的测试文件并计算MD5,耗时大概在14~15分钟左右。
对比可以得出数据加速Fluid既可以提升计算效率,也可以大幅度降低计算成本。