乔克
乔克
Published on 2024-11-15 / 35 Visits
0
0

Argo Workflows-Kubernetes的工作流引擎

什么是 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 界面如下:

图片.png

再配置一个 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):
图片.png

安装 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 字段中,其主要包括 templatesentrypoint,如下:

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,执行效果如下:

图片.png

除了使用 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,输出结果如下:

图片.png

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

图片.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 的输入参数,然后在使用 "{{}}" 形式引用变量。

变量还可以进行一些函数运算,主要有:

  • 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,运行结果如下:

图片.png

然后在 minio 中可以看到生成的制品,制品经过了压缩,如下:

图片.png

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,如下:

图片.png

4、创建 Workflow,可以手动创建,如下:

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: workflow-template-devops-java-
spec:
  workflowTemplateRef:
    name: devops-java

也可以直接在 UI 界面点击创建,我这里直接在 UI 界面点击创建。选择刚创建的 WorkflowTemplate,点击创建,如下:

图片.png

然后就会生成一条 Workflow,如下:

图片.png

点进去,可以看到每个具体的步骤,如下

图片.png

点击每个具体的步骤,可以看日志,如下:

图片.png

也可以在命令行界面看到 Workflow 的执行结果,如下:

图片.png

初次使用到这里就结束了,后期会逐步去优化。

参考文档


Comment