Argo Workflows 原理及应用

01

组件概述

Argo Workflows是一个开源的容器原生工作流引擎,允许用户在Kubernetes集群上定义并执行复杂的业务流程。支持对相关流程进行个性化编排,包括执行顺序、相互之间的依赖等等。

Argo Workflows 是以 Kubernetes 自定义资源定义(Custom Resource Definitions,简称 CRD)的形式来实现其功能。CRD 是 Kubernetes 提供的一种机制,它允许用户在不修改 Kubernetes 核心代码的情况下,扩展 API Server,定义新的资源类型。

常见的应用场景包括:

  • CI/CD;

  • 数据处理和分析;

  • 机器学习;

  • 批处理作业。

Argo是云原生计算基金会(CNCF)的毕业项目。

本文将重点关注CI/CD的应用场景,并通过下表对比Argo与业界常用的其它CI/CD组件的功能和特点:


FluxcdJenkinsArgo
持续集成与部署不完全支持支持支持
编排-Pipeline、插件生态工作流、声明式配置
Gitops支持插件生态支持
云原生支持插件生态支持
扩展性自定义控制器插件生态CRD
UI界面由第三方提供支持支持支持
社区活跃度
运维复杂度简单一般
适用场景轻量级、仅需要持续部署高度定制化、依赖庞大的插件系统支持云原生、轻量级

Argo Workflows的核心优势包括:

  • 云原生:专为Kubernetes设计,可以在任何Kubernetes集群上运行,因此可以更轻松地与基于Kubernetes的环境进行集成;

  • 声明式:使用YAML文件来声明式的定义工作流,这使得它们更容易被版本控制,并且能够以代码的形式进行统一管理;

  • 轻量级:任务的运行基于容器,没有传统虚拟机和基于服务器环境的开销和限制;

  • 灵活性:支持Step和DAG结构,允许用户定义任意复杂度的工作流,包括条件分支、循环、并行任务等。

02

核心概念

2.1 Workflow

Workflow是Argo中最重要的资源,具有两个重要功能:

  • 定义了要执行的工作流,包括元数据、Spec、Status;

  • 存储工作流总体的状态信息,包括阶段信息、启动和完成时间等等。

所以,工作流不仅仅是一个单纯的静态定义,它还代表了一个具体的实例。

数据结构

type Workflow struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"`
    Spec              WorkflowSpec   `json:"spec" protobuf:"bytes,2,opt,name=spec "`
    Status            WorkflowStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
  • metav1.TypeMeta、metav1.ObjectMeta:元数据相关内容,和k8s的书写规范保持一致即可;

  • Spec:工作流的规范,也是用户定义工作流主要内容的位置。核心是"templates"以及"entrypoint",templates可以大致理解为"函数",它们定义要执行的逻辑。entrypoint字段定义"main"函数是什么,即首先要执行的模板是哪个,在后面的Spec一节会详细介绍;

  • Status:包含有关工作流的总体状态信息,例如:Phase(阶段信息,包括Running、Succeeded、Failed等)、StartedAt(工作流的开始时间)、FinishedAt(工作流的完成时间)等。

示例:

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  labels:
    app.kubernetes.io/name: app_name
    app.kubernetes.io/instance: service_name
    app.kubernetes.io/version: app_version
    app.kubernetes.io/managed-by: pipeline
  generateName: output-something-
  namespace: default 
  # 上述内容书写规范参考k8s即可
  
spec:
  entrypoint: main # 定义工作流入口的主模板,此处模板名称为"main"
  templates: # 模板列表,可以定义多个模板
  - name: echo-message1 # 定义了一个"echo-message1"模板
    container:
      image: alpine:3.20.3
      command: ["/bin/sh"] # 启动命令
      args: ["-c", "echo message1"] # 传递给上方command的参数,最终打印"message1"并退出
Spec
数据结构
// 数据结构部分内容较多,下方仅挑选常用字段进行说明,完整信息可以参考官方源码
type  WorkflowSpec struct {
 
    // 模板列表,用户定义实际工作流逻辑的位置
    Templates []Template `json:"templates,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,1,opt,name=templates"`
 
    // 入口点,用于定义主模板,通常为"main"
    Entrypoint string `json:"entrypoint,omitempty" protobuf:"bytes,2,opt,name=entrypoint"`
 
    // 参数,包含parameters及artifacts,发送到上面的entrypoint
    Arguments Arguments `json:"arguments,omitempty" protobuf:"bytes,3,opt,name=arguments"`
 
    // 同k8s中的SA
    ServiceAccountName string `json:"serviceAccountName,omitempty" protobuf:"bytes,4,opt,name=serviceAccountName"`
 
    // 卷列表,具体配置参考k8s中的volume
    Volumes []apiv1.Volume `json:"volumes,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,5,opt,name=volumes"`
 
    // PVC列表,具体配置参考k8s中的PVC
    VolumeClaimTemplates []apiv1.PersistentVolumeClaim `json:"volumeClaimTemplates,omitempty" protobuf:"bytes,6,opt,name=volumeClaimTemplates"`
 
    // 同k8s中的Affinity
    Affinity *apiv1.Affinity `json:"affinity,omitempty" protobuf:"bytes,11,opt,name=affinity"`
 
    // 同k8s中的Toleration
    Tolerations []apiv1.Toleration `json:"tolerations,omitempty" patchStrategy:"merge" patchMergeKey:"key" protobuf:"bytes,12,opt,name=tolerations"`
 
    // 同k8s中的imagePullSecrets
    ImagePullSecrets []apiv1.LocalObjectReference `json:"imagePullSecrets,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,13,opt,name=imagePullSecrets"`
 
    // 模板引用,在工作流结束时调用,无论主工作流是成功、失败还是错误
    OnExit string `json:"onExit,omitempty" protobuf:"bytes,17,opt,name=onExit"`
 
    // 生命周期配置,限制已完成执行的工作流的生命周期,具体取决于它是成功还是失败。如果设置了此结构,则一旦工作流完成,它将在生存时间到期后被删除。
    TTLStrategy *TTLStrategy `json:"ttlStrategy,omitempty" protobuf:"bytes,30,opt,name=ttlStrategy"`
 
    // 模板引用,包含工作流模板及集群模板
    WorkflowTemplateRef *WorkflowTemplateRef `json:"workflowTemplateRef,omitempty" protobuf:"bytes,34,opt,name=workflowTemplateRef"`
 
    // 重试策略
    RetryStrategy *RetryStrategy `json:"retryStrategy,omitempty" protobuf:"bytes,37,opt,name=retryStrategy"`
}
Template

支持的模板类型有很多,下面仅列举一些常用的模板类型,详细的模板信息请参考官方文档。

Container

定义Pod中运行的主容器,也是定义实际业务逻辑的地方,书写规范与k8s容器相同。

示例:

- name: echo-message1
    container:
      image: alpine:3.20.3
      command: ["/bin/sh"]
      args: ["-c", "echo message1"]
ContainerSet

类似于ContainerScript,但允许指定多个容器(并行)在单个Pod内运行,可以指定容器间的依赖关系(容器数量要尽可能的少,推荐不要大于10个)。由于一个Pod内有多个容器运行,所以它们将调度到同一台主机上运行,进而,可以很方便的使用emptyDir(而不是PVC)在各个步骤之间共享数据。

示例:

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: output-something-
spec:
  entrypoint: main
  templates:
    - name: main
      volumes:
        - name: workspace
          emptyDir: {}
      containerSet:
        volumeMounts:
          - mountPath: /workspace
            name: workspace
        containers:
          - name: container-a
            image: alpine:3.20.3
            command: ["/bin/sh", "-c"]
            args: ["echo 'container-a output message1' >> /workspace/message.txt"]
          - name: container-b
            image: alpine:3.20.3
            command: ["/bin/sh", "-c"]
            args: ["echo 'container-b output message2' >> /workspace/message.txt"]
          - name: main
            image: alpine:3.20.3
            command: ["/bin/sh", "-c"]
            args: ["echo 'main output message3' >> /workspace/message.txt"]
            dependencies:
              - a
              - b
      outputs:
        parameters:
          - name: message
            valueFrom:
              path: /workspace/message.txt
Script

对容器进行了包装,在容器基础上新增了"Source"字段,允许用户直接定义脚本,脚本将保存到文件中并执行。

示例:

- name: java-compile
    script:
      image: maven:3.9.9-eclipse-temurin-11
      volumeMounts: 
        - mountPath: /package # 产物存储,例如编译产生的jar包、zip包等
          name: artifacts-data
        - mountPath: /workspace # 工作目录
          name: workspace
      command: [/bin/sh] # 启动命令
      source: | # 定义脚本
        git clone -b {{workflow.parameters.git_branch}} {{workflow.parameters.app_repo}}
        cd /package/{{workflow.name}}/{{workflow.parameters.app_name}}
        echo "Branch: $(git rev-parse --abbrev-ref HEAD)"
        echo "CommitID: $(git rev-parse HEAD)"
        echo "Maven Command: {{workflow.parameters.mvn_command}}"
        {{workflow.parameters.mvn_command}}

在上方示例中出现了很多{{workflow.parameters.parameter_name}}的写法,该写法为变量(参数)引用,这些引用会由Argo自动替换。例如{{workflow.parameters.git_branch}}被替换为workflow中spec.arguments.parameters.git_branch的值。

Resource

直接对集群资源进行操作,支持的操作包括:getcreateapplydeletereplacepatch,此示例在集群上创建ConfigMap资源。

示例:

- name: create-cm
    resource:
      action: create
      manifest: |
        apiVersion: v1
        kind: ConfigMap
        metadata:
          name: config-env-file
          namespace: default
        data:
          key1: value1
          key2: value2
          key3: value3
Suspend

暂停模板,可以在到达该步骤时暂停工作流程,支持自动恢复或手动恢复。包含一个Duration字段,标识自动恢复前要等待的秒数,必须是字符串。默认单位是秒。也可以是Duration,例如:"2m"、"6h"、"1d"。

示例:

- name: delay
    suspend:
      duration: "5m"
Steps

Steps定义一系列串行/并行的工作流步骤,是对在一系列步骤中执行的模板的引用。模板的结构是列表,外部列表将按顺序运行,内部列表将并行运行,可以使用when语句进行条件控制。

示例:

- name: example
    steps:
    - - name: step1
        template: echo-message1
    - - name: step2-1
        template: echo-message2-1
      - name: step2-2
        template: echo-message2-2
    - - name: step3
        template: echo-message3
    - - name: step4
        template: echo-message4
        when: "{{steps.step3.outputs.result}} == message3"

上面的执行顺序为:

  1. 首先执行step1;

  2. step1完成后step2-1和step2-2将并行运行;

  3. step2-1和step2-2均完成后,step3将运行;

  4. step3完成后,如果when语句的结果为true,则执行step4,否则会跳过。

示例的流程图如下所示:

DAG

同上面的Steps不同,DAG模板允许用户将任务定义为依赖关系图。在DAG中,用户可以列出所有需要运行任务,并可以自由设置某些任务的依赖(比如:在开始某个特定任务之前必须完成哪些其它依赖任务),没有任何依赖关系的任务将立即运行。

示例:

- name: task-test
    dag:
      tasks:
      - name: task-a
        template: echo
        arguments:
          parameters: [{name: message, value: task-a}]
      - name: task-b
        depends: "task-a"
        template: echo
        arguments:
          parameters: [{name: message, value: task-b}]
      - name: task-c
        depends: "task-a"
        template: echo
        arguments:
          parameters: [{name: message, value: task-c}]
      - name: task-d
        depends: "task-b && task-c" # 也可写成 dependencies: [task-b, task-c]
        template: echo
        arguments:
          parameters: [{name: message, value: task-c}]

上面的执行顺序为:

  1. 首先运行task-a;

  2. task-a运行完成后,task-b和task-c将同时运行;

  3. task-b和task-c均完成后,task-d将运行。

WorkflowTemplate

WorkflowTemplate是工作流模板资源的定义,可以通过该资源定义自己的模板库,将定义好的模板提交到集群中,就可以在工作流中进行重复引用了。

数据结构

type WorkflowTemplate struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"`
    Spec WorkflowSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
}

通过上方的数据结构并对比Workflow的数据结构,除了没有Status字段外,其它的和workflow没什么区别,这也就意味着WorkflowTemplate仅仅是一个单纯的静态定义。

示例:

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: output-something--
spec:
  entrypoint: main
  arguments:
    parameters:
      - name: message
        value: "message1"
  workflowTemplateRef:
    name: wftmpl-echo # 引用的模板名称

03

组件架构

db0ff0281c803038cfc338c7f3af1d82.png

Argo Workflow主要包含两个组件"Argo Server""Workflow Controller",这两个组件均部署在argo命名空间下且保证高可用:

  • Argo Server提供了Web UI及API服务,通过ingress对外暴露访问地址,客户端及用户可以通过域名访问Argo Server;

  • Workflow Controller是个控制器,负责整体的调谐过程。

通过Thanos或者Prometheus对工作流组件进行监控、告警,通过Grafana进行可视化展示。

用户可以通过Web UI或者Argo命令行等方式对相关资源进行操作。

用户创建的业务工作流均运行在业务命名空间下,底层通过分布式存储和镜像仓库提供工作流所需的输入、输出制品的存储。

每个工作流的每个Step或DAG都会创建独立的pod,每个pod中都会有三个容器,分别是:initmainwait,图示如下:

  • init:init容器运行在main容器之前,用于初始化一些前置条件或准备工作。例如:获取制品和参数、挂载卷等等;

  • main:main容器包含了实际要执行的业务逻辑,在所有的init容器成功完成后执行;

  • wait:wait容器等待main容器完成并执行一系列其它动作。例如:捕获输出脚本结果、保存输出参数、保存输出制品等。

3.1 Controller 架构

Workflow Controller也同样遵循k8s项目中的一个通用的编排模式,即:“控制循环”

简单来说,就是不断获取集群中相关资源的实际状态,同时和期望状态做一个diff,根据diff的结果对具体资源执行实际操作(增、删、改等等),这个操作通常称为“调谐”

上图中绿色虚线框内展示了Informer的工作流程。其中Informers包含很多Informer,具体实现几乎都是SharedIndexInformer,例如wfInformerwftmplInformerpodInformer等等,Informer的作用及详细的工作流程本文不再赘述,感兴趣的同学可以参考Kubernetes go client library

我们主要关注Worker部分,这部分包含主要的处理逻辑,核心是“processNextItem()”方法。该方法的主要功能是从wfQueue中取出下一个待处理的工作流项,并进行一系列处理操作。其中处理工作流的具体执行操作发生在“woc.operate()”方法,它评估工作流及其 pod 的当前状态,并决定如何继续执行。感兴趣的同学可以参考controller.go

04

安装部署及监控

4.1 Argo CLI安装

安装过程不再赘述,详细的安装步骤请参考官方安装文档

4.2 Controller and Server安装

安装过程不再赘述,详细的安装步骤请参考官方安装文档

相关镜像请提前同步至私有镜像仓库,并替换yaml文件中镜像地址。如果开启了认证还需要额外配置镜像拉取密钥。

4.3 配置组件监控

  1. Controller默认通过9090端口的/metrics公布指标,所以需要在组件命名空间下添加svc,参考内容如下,按需更改:

apiVersion: v1
kind: Service
metadata:
  annotations:
    prometheus.io/scheme: http
    prometheus.io/scrape: "true"
  labels:
    app: workflow-controller
  name: workflow-controller-metrics
  namespace: argo
spec:
  clusterIP: None
  ports:
  - name: metrics
    port: 9090
    protocol: TCP
    targetPort: 9090
  selector:
    app: workflow-controller
  1. Prometheus中添加配置,部分参考内容如下,按需更改:

- bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      honor_labels: true
      job_name: kubernetes-service-endpoints
      kubernetes_sd_configs:
      - role: endpoints
      tls_config:
        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        insecure_skip_verify: true
      relabel_configs:
      - action: keep
        regex: true
        source_labels:
        - __meta_kubernetes_service_annotation_prometheus_io_scrape
      - action: drop
        regex: true
        source_labels:
        - __meta_kubernetes_service_annotation_prometheus_io_scrape_slow
      - action: replace
        regex: (https?)
        source_labels:
        - __meta_kubernetes_service_annotation_prometheus_io_scheme
        target_label: __scheme__
      - action: replace
        regex: (.+)
        source_labels:
        - __meta_kubernetes_service_annotation_prometheus_io_path
        target_label: __metrics_path__
      - action: replace
        regex: (.+?)(?::\d+)?;(\d+)
        replacement: $1:$2
        source_labels:
        - __address__
        - __meta_kubernetes_service_annotation_prometheus_io_port
        target_label: __address__
      - action: labelmap
        regex: __meta_kubernetes_service_annotation_prometheus_io_param_(.+)
        replacement: __param_$1
      - action: replace
        source_labels:
        - __meta_kubernetes_namespace
        target_label: namespace
      - action: replace
        source_labels:
        - __meta_kubernetes_service_name
        target_label: service
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_node_name
        target_label: node
      - action: keep
        source_labels: [__meta_kubernetes_endpoint_port_name]
        regex: .+
  1. 配置完成并应用到集群后,查看目标状态是否是UP,参考图示如下:

4.4 效果图展示

05

应用实践

5.1 架构方案

上图中组件概述如下:

  • 代码仓库:代码存储、唯一事实源;

  • 统一平台:功能平台化;

工作流管理:功能包括:工作流模板管理、工作流实例管理、webhook配置、自动创建APP、工作流异常告警等;

云部署配置管理:服务部署模板管理、服务发布管理、日志收集等;

  • CI:底层CI相关组件,用于处理整体的CI流程,可选的协同组件包括:代码扫描、镜像仓库等;

  • CD:底层CD相关组件,用于处理整体的CD流程,以Git作为唯一事实源;

  • K8S管理系统:多集群、混合云集群管理。配合CD组件实现混合云场景下多集群分发及差异化管理。

5.2 示例流程

整体流程如下:

  1. 平台侧提交工作流到集群中,CI流程开始;

  2. 拉取代码进行编译打包(以及可选的代码扫描及扫描结果的检查通知);

  3. 初始化dockerfile(支持自定义dockerfile);

  4. 构建镜像并推送到镜像仓库;

  5. 更新镜像tag并同步到代码仓库;

  6. 执行全局退出清理脚本(清理工作流运行期间产出的临时产物)。

5.3 示例配置

apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
  name: java-compile-build-deploy
spec:
  entrypoint: main
  ttlStrategy:
    secondsAfterCompletion: 86400
    secondsAfterFailure: 86400
    secondsAfterSuccess: 86400
  imagePullSecrets:
    - name: harbor-secret
  onExit: global-exit
  arguments:
    parameters:
    - name: app_module
      value: "example"
    - name: app_name
      value: "example"
    - name: app_repo
      value: "example"
    - name: context_path
      value: "example"
    - name: custom_dockerfile
      value: "example"
    - name: dockerfile_path
      value: "example"
    - name: environment
      value: "example"
    - name: git_branch
      value: "example"
    - name: java_version
      value: "example"
    - name: ns
      value: "example"
    - name: product_name
      value: "example"
    - name: repository_name
      value: "example"
    - name: run_timestamp
      value: "example"
    - name: service_name
      value: "example"
    - name: mvn_command
      value: "example"
    - name: product_type
      value: "example"
  volumes:
    - name: artifacts-data
      persistentVolumeClaim:
        claimName: artifacts-data-pvc
    - name: workspace
      emptyDir:
        sizeLimit: 10240Mi
    - name: harbor
      secret:
        secretName: harbor
    - name: cert
      secret:
        secretName: harbor-tls
    - name: git-ssh
      secret:
        secretName: git-ssh
        defaultMode: 384
  templates:
    - name: main
      steps:
      - - name: compile
          templateRef:
            name: generic-java-compile
            template: java-compile
      - - name: init-jar
          templateRef:
            name: generic-init-dockerfile
            template: init-dockerfile
          when: >-
            {{workflow.parameters.custom_dockerfile}} == false
            &&
            ( '{{workflow.parameters.product_type}}' == '' || 
              '{{workflow.parameters.product_type}}' == 'jar' )
      - - name: init-zip
          templateRef:
            name: generic-init-dockerfile-zip
            template: init-dockerfile
          when: >-
            {{workflow.parameters.custom_dockerfile}} == false
            && 
            '{{workflow.parameters.product_type}}' == 'zip'
      - - name: build
          templateRef:
            name: generic-image-build
            template: image-build
      - - name: update-tag
          templateRef:
            name: generic-update-tag
            template: update-tag
          arguments:
            parameters:
            - name: pipeline_repo
              value: "ssh://git@gitlab.example.com:group/example.git"
            - name: pipeline_branch
              value: 'main'
    - name: global-exit
      steps:
      - - name: exit
          templateRef:
            name: generic-exit
            template: exit

5.4 效果展示

工作流管理


工作流模板


工作流步骤

06

参考文档

  1. 官方文档(https://argo-workflows.readthedocs.io/en/latest/)

  2. 项目源码(https://github.com/argoproj/argo-workflows)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值