什么是 Argo Workflows?
Argo Workflows 是一个开源项目,为 Kubernetes 提供 container-native 工作流程,其主要通过 Kubernetes CRD 实现的。
特点如下:
- 工作流的每一步都是一个容器
- 将多步骤工作流建模为一系列任务,或者使用有向无环图(DAG)描述任务之间的依赖关系
- 可以在短时间内轻松运行用于机器学习或数据处理的计算密集型作业
- 在 Kubernetes 上运行 CI/CD Pipeline,无需复杂的软件配置
安装
安装控制器端
Argo Wordflows 的安装非常简单,直接使用以下命令安装即可。
kubectl create ns argo
kubectl apply -n argo -f https://raw.githubusercontent.com/argoproj/argo-workflows/stable/manifests/quick-start-postgres.yaml
安装完成后,会生成以下 4 个 pod。
# kubectl get po -n argo
NAME READY STATUS RESTARTS AGE
argo-server-574ddc66b-62rjc 1/1 Running 4 4h25m
minio 1/1 Running 0 4h25m
postgres-56fd897cf4-k8fwd 1/1 Running 0 4h25m
workflow-controller-77658c77cc-p25ll 1/1 Running 4 4h25m
其中:
- argo-server 是 argo 服务端
- mino 是进行制品仓库
- postgres 是数据库
- workflow-controller 是流程控制器
然后配置一个 server 端的 ingress,即可访问 UI,配置清单如下(我这里使用的是 traefik):
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: argo-ui
namespace: argo
spec:
entryPoints:
- web
routes:
- match: Host(`argowork-test.coolops.cn`)
kind: Rule
services:
- name: argo-server
port: 2746
UI 界面如下:
再配置一个 minio 的 ingress,配置清单如下:
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: minio
namespace: argo
spec:
entryPoints:
- web
routes:
- match: Host(`minio-test.coolops.cn`)
kind: Rule
services:
- name: minio
port: 9000
UI 界面如下(默认用户名密码是:admin:password):
安装 Client 端
Argo Workflows 提供 Argo CLI,其安装方式也非常简单,如下:
Linux 系统:
# Download the binary
curl -sLO https://github.com/argoproj/argo/releases/download/v3.0.0-rc4/argo-linux-amd64.gz
# Unzip
gunzip argo-linux-amd64.gz
# Make binary executable
chmod +x argo-linux-amd64
# Move binary to path
mv ./argo-linux-amd64 /usr/local/bin/argo
安装完成后,使用以下命令校验是否安装成功。
# argo version
argo: v3.0.0-rc4
BuildDate: 2021-03-02T21:42:55Z
GitCommit: ae5587e97dad0e4806f7a230672b998fe140a767
GitTreeState: clean
GitTag: v3.0.0-rc4
GoVersion: go1.13
Compiler: gc
Platform: linux/amd64
其主要的命令有:
list 列出工作流
logs 查看工作流的日志
submit 创建工作流
watch 实时监听工作流
get 现实详细信息
delete 删除工作流
stop 停止工作流
更多命令可以使用 argo --help
进行查看。
然后可以使用一个简单的 hello world 的 WorkFlow,如下:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: hello-world-
labels:
workflows.argoproj.io/archive-strategy: "false"
spec:
entrypoint: whalesay
templates:
- name: whalesay
container:
image: docker/whalesay:latest
command: [cowsay]
args: ["hello world"]
使用如下命令创建并观察 workflow。
$ argo submit -n argo helloworld.yaml --watch
然后可以看到以下输出。
Name: hello-world-9pw7v
Namespace: argo
ServiceAccount: default
Status: Succeeded
Conditions:
Completed True
Created: Mon Mar 08 14:51:35 +0800 (10 seconds ago)
Started: Mon Mar 08 14:51:35 +0800 (10 seconds ago)
Finished: Mon Mar 08 14:51:45 +0800 (now)
Duration: 10 seconds
Progress: 1/1
ResourcesDuration: 4s*(1 cpu),4s*(100Mi memory)
STEP TEMPLATE PODNAME DURATION MESSAGE
✔ hello-world-9pw7v whalesay hello-world-9pw7v 5s
还可以通过 argo list 来查看状态,如下:
# argo list -n argo
NAME STATUS AGE DURATION PRIORITY
hello-world-9pw7v Succeeded 1m 10s 0
使用 argo logs 来查看具体的日志,如下:
# argo logs -n argo hello-world-9pw7v
hello-world-9pw7v: _____________
hello-world-9pw7v: < hello world >
hello-world-9pw7v: -------------
hello-world-9pw7v: \
hello-world-9pw7v: \
hello-world-9pw7v: \
hello-world-9pw7v: ## .
hello-world-9pw7v: ## ## ## ==
hello-world-9pw7v: ## ## ## ## ===
hello-world-9pw7v: /""""""""""""""""___/ ===
hello-world-9pw7v: ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~
hello-world-9pw7v: \______ o __/
hello-world-9pw7v: \ \ __/
hello-world-9pw7v: \____\______/
核心概念
Workflow
Workflow 是 Argo 中最重要的资源,其主要有两个重要功能:
- 它定义要执行的工作流
- 它存储工作流程的状态
要执行的工作流定义在 Workflow.spec
字段中,其主要包括 templates
和 entrypoint
,如下:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: hello-world- # Workflow的配置名称
spec:
entrypoint: whalesay # 解析whalesay templates
templates:
- name: whalesay # 定义whalesay templates,和entrypoint保持一致
container: # 定义一个容器,输出"helloworld"
image: docker/whalesay
command: [cowsay]
args: ["hello world"]
Templates
templates 是列表结构,主要分为两类:
- 定义具体的工作流
- 调用其他模板提供并行控制
定义具体的工作流
定义具体的工作流有 4 种类别,如下:
- Container
- Script
- Resource
- Suspend
Container
container 是最常用的模板类型,它将调度一个 container,其模板规范和 K8S 的容器规范相同,如下:
- name: whalesay
container:
image: docker/whalesay
command: [cowsay]
args: ["hello world"]
Script
Script 是 Container 的另一种包装实现,其定义方式和 Container 相同,只是增加了 source
字段用于自定义脚本,如下:
- name: gen-random-int
script:
image: python:alpine3.6
command: [python]
source: |
import random
i = random.randint(1, 100)
print(i)
脚本的输出结果会根据调用方式自动导出到 {{tasks.<NAME>.outputs.result}}
或 {{steps.<NAME>.outputs.result}}
中。
Resource
Resource 主要用于直接在 K8S 集群上执行集群资源操作,可以 get, create, apply, delete, replace, patch 集群资源。如下在集群中创建一个 ConfigMap 类型资源:
- name: k8s-owner-reference
resource:
action: create
manifest: |
apiVersion: v1
kind: ConfigMap
metadata:
generateName: owned-eg-
data:
some: value
Suspend
Suspend 主要用于暂停,可以暂停一段时间,也可以手动恢复,命令使用 argo resume
进行恢复。定义格式如下:
- name: delay
suspend:
duration: "20s"
调用其他模板提供并行控制
调用其他模板也有两种类别:
- Steps
- Dag
Steps
Steps 主要是通过定义一系列步骤来定义任务,其结构是 "list of lists",外部列表将顺序执行,内部列表将并行执行。如下:
- name: hello-hello-hello
steps:
- - name: step1
template: prepare-data
- - name: step2a
template: run-data-first-half
- name: step2b
template: run-data-second-half
其中 step1 和 step2a 是顺序执行,而 step2a 和 step2b 是并行执行。
还可以通过 When
来进行条件判断。如下:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: coinflip-
spec:
entrypoint: coinflip
templates:
- name: coinflip
steps:
- - name: flip-coin
template: flip-coin
- - name: heads
template: heads
when: "{{steps.flip-coin.outputs.result}} == heads"
- name: tails
template: tails
when: "{{steps.flip-coin.outputs.result}} == tails"
- name: flip-coin
script:
image: python:alpine3.6
command: [python]
source: |
import random
result = "heads" if random.randint(0,1) == 0 else "tails"
print(result)
- name: heads
container:
image: alpine:3.6
command: [sh, -c]
args: ["echo \"it was heads\""]
- name: tails
container:
image: alpine:3.6
command: [sh, -c]
args: ["echo \"it was tails\""]
提交这个 Workflow,执行效果如下:
除了使用 When 进行条件判断,还可以进行循环操作,示例代码如下:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: loops-
spec:
entrypoint: loop-example
templates:
- name: loop-example
steps:
- - name: print-message
template: whalesay
arguments:
parameters:
- name: message
value: "{{item}}"
withItems:
- hello world
- goodbye world
- name: whalesay
inputs:
parameters:
- name: message
container:
image: docker/whalesay:latest
command: [cowsay]
args: ["{{inputs.parameters.message}}"]
提交 Workflow,输出结果如下:
Dag
Dag 主要用于定义任务的依赖关系,可以设置开始特定任务之前必须完成其他任务,没有任何依赖关系的任务将立即执行。
如下:
- name: diamond
dag:
tasks:
- name: A
template: echo
- name: B
dependencies: [A]
template: echo
- name: C
dependencies: [A]
template: echo
- name: D
dependencies: [B, C]
template: echo
其中 A 会立即执行,B 和 C 会依赖 A,D 依赖 B 和 C。
然后运行一个示例看看效果,示例如下:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: dag-diamond-
spec:
entrypoint: diamond
templates:
- name: diamond
dag:
tasks:
- name: A
template: echo
arguments:
parameters: [{name: message, value: A}]
- name: B
dependencies: [A]
template: echo
arguments:
parameters: [{name: message, value: B}]
- name: C
dependencies: [A]
template: echo
arguments:
parameters: [{name: message, value: C}]
- name: D
dependencies: [B, C]
template: echo
arguments:
parameters: [{name: message, value: D}]
- name: echo
inputs:
parameters:
- name: message
container:
image: alpine:3.7
command: [echo, "{{inputs.parameters.message}}"]
提交 workflow。
argo submit -n argo dag.yam --watch
Variables
在 argo 的 Workflow 中允许使用变量的,如下:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: hello-world-parameters-
spec:
entrypoint: whalesay
arguments:
parameters:
- name: message
value: hello world
templates:
- name: whalesay
inputs:
parameters:
- name: message
container:
image: docker/whalesay
command: [ cowsay ]
args: [ "{{inputs.parameters.message}}" ]
首先在 spec 字段定义 arguments
,定义变量 message
,其值是 hello world
,然后在 templates
字段中需要先定义一个 inputs
字段,用于 templates
的输入参数,然后在使用 "{{}}"
形式引用变量。
变量还可以进行一些函数运算,主要有:
- filter:过滤
- asInt:转换为 Int
- asFloat:转换为 Float
- string:转换为 String
- toJson:转换为 Json
例子:
filter([1, 2], { # > 1})
asInt(inputs.parameters["my-int-param"])
asFloat(inputs.parameters["my-float-param"])
string(1)
toJson([1, 2])
更多语法可以访问 https://github.com/antonmedv/expr/blob/master/docs/Language-Definition.md 进行学习。
制品库
在安装 argo 的时候,已经安装了 mino 作为制品库,那么到底该如何使用呢?
先看一个官方的例子,如下:
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:
- name: message
from: "{{steps.generate-artifact.outputs.artifacts.hello-art}}"
- name: whalesay
container:
image: docker/whalesay:latest
command: [sh, -c]
args: ["sleep 1; cowsay hello world | tee /tmp/hello_world.txt"]
outputs:
artifacts:
- name: hello-art
path: /tmp/hello_world.txt
- name: print-message
inputs:
artifacts:
- name: message
path: /tmp/message
container:
image: alpine:latest
command: [sh, -c]
args: ["cat /tmp/message"]
其分为两步:
- 首先生成制品
- 然后获取制品
提交 Workflow,运行结果如下:
然后在 minio 中可以看到生成的制品,制品经过了压缩,如下:
WorkflowTemplate
WorkflowTemplate 是 Workflow 的模板,可以从 WorkflowTemplate 内部或者集群上其他 Workflow 和 WorkflowTemplate 引用它们。
WorkflowTemplate 和 template 的区别:
- template 只是 Workflow 中 templates 下的一个任务,当我们定义一个 Workflow 时,至少需要定义一个 template
- WorkflowTemplate 是驻留在集群中的 Workflow 的定义,它是 Workflow 的定义,因为它包含模板,可以从 WorkflowTemplate 内部或者集群上其他 Workflow 和 WorkflowTemplate 引用它们。
在 2.7 版本后,WorkflowTemplate 的定义和 Workflow 的定义一样,我们可以简单的将 kind:Workflow
改成 kind:WorkflowTemplate
。比如:
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: workflow-template-1
spec:
entrypoint: whalesay-template
arguments:
parameters:
- name: message
value: hello world
templates:
- name: whalesay-template
inputs:
parameters:
- name: message
container:
image: docker/whalesay
command: [cowsay]
args: ["{{inputs.parameters.message}}"]
创建 WorkflowTemplate,如下
argo template create workflowtemplate.yaml
然后在 Workflow 中引用,如下:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: workflow-template-hello-world-
spec:
entrypoint: whalesay
templates:
- name: whalesay
steps: # 引用模板必须在steps/dag/template下
- - name: call-whalesay-template
templateRef: # 应用模板字段
name: workflow-template-1 # WorkflowTemplate名
template: whalesay-template # 具体的template名
arguments: # 参数
parameters:
- name: message
value: "hello world"
ClusterWorkflowTemplate
ClusterWorkflowTemplate 创建的是一个集群范围内的 WorkflowTemplate,其他 workflow 可以引用它。
如下定义一个 ClusterWorkflow。
apiVersion: argoproj.io/v1alpha1
kind: ClusterWorkflowTemplate
metadata:
name: cluster-workflow-template-whalesay-template
spec:
templates:
- name: whalesay-template
inputs:
parameters:
- name: message
container:
image: docker/whalesay
command: [cowsay]
args: ["{{inputs.parameters.message}}"]
然后在 workflow 中使用 templateRef
去引用它,如下:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: workflow-template-hello-world-
spec:
entrypoint: whalesay
templates:
- name: whalesay
steps:
- - name: call-whalesay-template
templateRef: #引用模板
name: cluster-workflow-template-whalesay-template # ClusterWorkflow名
template: whalesay-template # 具体的模板名
clusterScope: true # 表示是ClusterWorkflow
arguments: # 参数
parameters:
- name: message
value: "hello world"
实践
上面大概叙述了一下 argo 的基本理论知识,更多的理论知识可以到官网去学习。
下面将使用一个简单的 CI/CD 实践,来了解一下用 argo workflow 应该如何做。
CI/CD 的整个流程很简单,即:拉代码 -> 编译 -> 构建镜像 -> 上传镜像 -> 部署。
定义一个 WorkflowTemplate,如下:
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
annotations:
workflows.argoproj.io/description: |
Checkout out from Git, build and deploy application.
workflows.argoproj.io/maintainer: '@joker'
workflows.argoproj.io/tags: java, git
workflows.argoproj.io/version: '>= 2.9.0'
name: devops-java
spec:
entrypoint: main
arguments:
parameters:
- name: repo
value: gitlab-test.coolops.cn:32080/root/springboot-helloworld.git
- name: branch
value: master
- name: image
value: registry.cn-hangzhou.aliyuncs.com/rookieops/myapp:202103101613
- name: cache-image
value: registry.cn-hangzhou.aliyuncs.com/rookieops/myapp
- name: dockerfile
value: Dockerfile
- name: devops-cd-repo
value: gitlab-test.coolops.cn:32080/root/devops-cd.git
- name: gitlabUsername
value: devops-argo
- name: gitlabPassword
value: devops123456
templates:
- name: main
steps:
- - name: Checkout
template: Checkout
- - name: Build
template: Build
- - name: BuildImage
template: BuildImage
- - name: Deploy
template: Deploy
# 拉取代码
- name: Checkout
script:
image: registry.cn-hangzhou.aliyuncs.com/rookieops/maven:3.5.0-alpine
workingDir: /work
command:
- sh
source: |
git clone --branch {{workflow.parameters.branch}} http://{{workflow.parameters.gitlabUsername}}:{{workflow.parameters.gitlabPassword}}@{{workflow.parameters.repo}} .
volumeMounts:
- mountPath: /work
name: work
# 编译打包
- name: Build
script:
image: registry.cn-hangzhou.aliyuncs.com/rookieops/maven:3.5.0-alpine
workingDir: /work
command:
- sh
source: mvn -B clean package -Dmaven.test.skip=true -Dautoconfig.skip
volumeMounts:
- mountPath: /work
name: work
# 构建镜像
- name: BuildImage
volumes:
- name: docker-config
secret:
secretName: docker-config
container:
image: registry.cn-hangzhou.aliyuncs.com/rookieops/kaniko-executor:v1.5.0
workingDir: /work
args:
- --context=.
- --dockerfile={{workflow.parameters.dockerfile}}
- --destination={{workflow.parameters.image}}
- --skip-tls-verify
- --reproducible
- --cache=true
- --cache-repo={{workflow.parameters.cache-image}}
volumeMounts:
- mountPath: /work
name: work
- name: docker-config
mountPath: /kaniko/.docker/
# 部署
- name: Deploy
script:
image: registry.cn-hangzhou.aliyuncs.com/rookieops/kustomize:v3.8.1
workingDir: /work
command:
- sh
source: |
git remote set-url origin http://{{workflow.parameters.gitlabUsername}}:{{workflow.parameters.gitlabPassword}}@{{workflow.parameters.devops-cd-repo}}
git config --global user.name "Administrator"
git config --global user.email "coolops@163.com"
git clone http://{{workflow.parameters.gitlabUsername}}:{{workflow.parameters.gitlabPassword}}@{{workflow.parameters.devops-cd-repo}} /work/devops-cd
cd /work/devops-cd
git pull
cd /work/devops-cd/devops-simple-java
kustomize edit set image {{workflow.parameters.image}}
git commit -am 'image update'
git push origin master
volumeMounts:
- mountPath: /work
name: work
volumeClaimTemplates:
- name: work
metadata:
name: work
spec:
storageClassName: nfs-client-storageclass
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
说明:
1、使用 kaniko 来创建镜像,不用挂载 docker.sock,但是 push 镜像的时候需要 config.json,所以首先需要创建一个 secret,如下:
kubectl create secret generic docker-config --from-file=.docker/config.json -n argo
2、准备好 storageClass,当然也可以不需要,直接使用 empty,不过可以将缓存文件这些持久化,可以加速构建(我上面没有做)。
3、创建 WorkflowTemplate,命令如下:
argo template create -n argo devops-java.yaml
创建完成后,可以在 UI 界面看到刚创建的 WorkflowTemplate,如下:
4、创建 Workflow,可以手动创建,如下:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: workflow-template-devops-java-
spec:
workflowTemplateRef:
name: devops-java
也可以直接在 UI 界面点击创建,我这里直接在 UI 界面点击创建。选择刚创建的 WorkflowTemplate,点击创建,如下:
然后就会生成一条 Workflow,如下:
点进去,可以看到每个具体的步骤,如下
点击每个具体的步骤,可以看日志,如下:
也可以在命令行界面看到 Workflow 的执行结果,如下:
初次使用到这里就结束了,后期会逐步去优化。