詳解Argo Workflows-Kubernetes的工作流引擎
什么是Argo Workflows?
Argo Workflows是一個開源項(xiàng)目,為Kubernetes提供container-native工作流程,其主要通過Kubernetes CRD實(shí)現(xiàn)的。
特點(diǎn)如下:
- 工作流的每一步都是一個容器
- 將多步驟工作流建模為一系列任務(wù),或者使用有向無環(huán)圖(DAG)描述任務(wù)之間的依賴關(guān)系
- 可以在短時間內(nèi)輕松運(yùn)行用于機(jī)器學(xué)習(xí)或數(shù)據(jù)處理的計(jì)算密集型作業(yè)
- 在Kubernetes上運(yùn)行CI/CD Pipeline,無需復(fù)雜的軟件配置
安裝
安裝控制器端
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服務(wù)端
- mino是進(jìn)行制品倉庫
- postgres是數(shù)據(jù)庫
- 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界面如下(默認(rèn)用戶名密碼是:admin:password):
安裝Client端
Argo Workflows提供Argo CLI,其安裝方式也非常簡單,如下:Linux系統(tǒng):
- # 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
安裝完成后,使用以下命令校驗(yàn)是否安裝成功。
- # 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 創(chuàng)建工作流
- watch 實(shí)時監(jiān)聽工作流
- get 現(xiàn)實(shí)詳細(xì)信息
- delete 刪除工作流
- stop 停止工作流
更多命令可以使用argo --help進(jìn)行查看。
然后可以使用一個簡單的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"]
使用如下命令創(chuàng)建并觀察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來查看狀態(tài),如下:
- # 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中最重要的資源,其主要有兩個重要功能:
- 它定義要執(zhí)行的工作流
- 它存儲工作流程的狀態(tài)
要執(zhí)行的工作流定義在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是列表結(jié)構(gòu),主要分為兩類:
- 定義具體的工作流
- 調(diào)用其他模板提供并行控制
定義具體的工作流
定義具體的工作流有4種類別,如下:
- Container
- Script
- Resource
- Suspend
Container
container是最常用的模板類型,它將調(diào)度一個container,其模板規(guī)范和K8S的容器規(guī)范相同,如下:
- - name: whalesay
- container:
- image: docker/whalesay
- command: [cowsay]
- args: ["hello world"]
Script
Script是Container的另一種包裝實(shí)現(xiàn),其定義方式和Container相同,只是增加了source字段用于自定義腳本,如下:
- - name: gen-random-int
- script:
- image: python:alpine3.6
- command: [python]
- source: |
- import random
- i = random.randint(1, 100)
- print(i)
腳本的輸出結(jié)果會根據(jù)調(diào)用方式自動導(dǎo)出到{{tasks.
Resource
Resource主要用于直接在K8S集群上執(zhí)行集群資源操作,可以 get, create, apply, delete, replace, patch集群資源。如下在集群中創(chuàng)建一個ConfigMap類型資源:
- - name: k8s-owner-reference
- resource:
- action: create
- manifest: |
- apiVersion: v1
- kind: ConfigMap
- metadata:
- generateName: owned-eg-
- data:
- some: value
Suspend
Suspend主要用于暫停,可以暫停一段時間,也可以手動恢復(fù),命令使用argo resume進(jìn)行恢復(fù)。定義格式如下:
- - name: delay
- suspend:
- duration: "20s"
調(diào)用其他模板提供并行控制
調(diào)用其他模板也有兩種類別:
- Steps
- Dag
Steps
Steps主要是通過定義一系列步驟來定義任務(wù),其結(jié)構(gòu)是"list of lists",外部列表將順序執(zhí)行,內(nèi)部列表將并行執(zhí)行。如下:
- - 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是順序執(zhí)行,而step2a和step2b是并行執(zhí)行。
還可以通過When來進(jìn)行條件判斷。如下:
- 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,執(zhí)行效果如下:
除了使用When進(jìn)行條件判斷,還可以進(jìn)行循環(huán)操作,示例代碼如下:
- 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,輸出結(jié)果如下:
Dag
Dag主要用于定義任務(wù)的依賴關(guān)系,可以設(shè)置開始特定任務(wù)之前必須完成其他任務(wù),沒有任何依賴關(guān)系的任務(wù)將立即執(zhí)行。如下:
- - 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會立即執(zhí)行,B和C會依賴A,D依賴B和C。
然后運(yùn)行一個示例看看效果,示例如下:
- 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
image.png
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的輸入?yún)?shù),然后在使用"{{}}"形式引用變量。
變量還可以進(jìn)行一些函數(shù)運(yùn)算,主要有:
- filter:過濾
- asInt:轉(zhuǎn)換為Int
- asFloat:轉(zhuǎn)換為Float
- string:轉(zhuǎn)換為String
- toJson:轉(zhuǎn)換為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進(jìn)行學(xué)習(xí)。
制品庫
在安裝argo的時候,已經(jīng)安裝了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,運(yùn)行結(jié)果如下:
然后在minio中可以看到生成的制品,制品經(jīng)過了壓縮,如下:
WorkflowTemplate
WorkflowTemplate是Workflow的模板,可以從WorkflowTemplate內(nèi)部或者集群上其他Workflow和WorkflowTemplate引用它們。
WorkflowTemplate和template的區(qū)別:
- template只是Workflow中templates下的一個任務(wù),當(dāng)我們定義一個Workflow時,至少需要定義一個template
- WorkflowTemplate是駐留在集群中的Workflow的定義,它是Workflow的定義,因?yàn)樗0澹梢詮腤orkflowTemplate內(nèi)部或者集群上其他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}}"]
創(chuàng)建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: # 應(yīng)用模板字段
- name: workflow-template-1 # WorkflowTemplate名
- template: whalesay-template # 具體的template名
- arguments: # 參數(shù)
- parameters:
- - name: message
- value: "hello world"
ClusterWorkflowTemplate
ClusterWorkflowTemplate創(chuàng)建的是一個集群范圍內(nèi)的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: # 參數(shù)
- parameters:
- - name: message
- value: "hello world"
實(shí)踐
上面大概敘述了一下argo的基本理論知識,更多的理論知識可以到官網(wǎng)去學(xué)習(xí)。
下面將使用一個簡單的CI/CD實(shí)踐,來了解一下用argo workflow應(yīng)該如何做。
CI/CD的整個流程很簡單,即:拉代碼->編譯->構(gòu)建鏡像->上傳鏡像->部署。
定義一個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/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/root/devops-cd.git
- - name: gitlabUsername
- value: devops
- - 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
- # 構(gòu)建鏡像
- - 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來創(chuàng)建鏡像,不用掛載docker.sock,但是push鏡像的時候需要config.json,所以首先需要創(chuàng)建一個secret,如下:
- kubectl create secret generic docker-config --from-file=.docker/config.json -n argo
2、準(zhǔn)備好storageClass,當(dāng)然也可以不需要,直接使用empty,不過可以將緩存文件這些持久化,可以加速構(gòu)建(我上面沒有做)。
3、創(chuàng)建WorkflowTemplate,命令如下:
- argo template create -n argo devops-java.yaml
4、創(chuàng)建Workflow,可以手動創(chuàng)建,如下:
- apiVersion: argoproj.io/v1alpha1
- kind: Workflow
- metadata:
- generateName: workflow-template-devops-java-
- spec:
- workflowTemplateRef:
- name: devops-java
也可以直接在UI界面點(diǎn)擊創(chuàng)建,我這里直接在UI界面點(diǎn)擊創(chuàng)建。選擇剛創(chuàng)建的WorkflowTemplate,點(diǎn)擊創(chuàng)建,如下:
然后就會生成一條Workflow,如下:
點(diǎn)進(jìn)去,可以看到每個具體的步驟,如下
點(diǎn)擊每個具體的步驟,可以看日志,如下:
也可以在命令行界面看到Workflow的執(zhí)行結(jié)果,如下:
初次使用到這里就結(jié)束了,后期會逐步去優(yōu)化。
參考文檔
https://github.com/argoproj/argo-workflows/releases
https://argoproj.github.io/argo-workflows
https://github.com/antonmedv/expr/blob/master/docs/Language-Definition.md
https://github.com/argoproj/argo-workflows/tree/master/examples