基于argo的CI = gitlab+webhook+argoEvents+argoWorkflows

3,012 阅读5分钟

基于argo项目里的argo-events和argo-workflow可以搭建持续集成CI的流程。通过gitlab触发argo-event支持添加的webhook来触发CI流程。argo-workflow可以定义集成的各个环节,如编译、打包、build镜像以及推送镜像到镜像仓库。本文将详细讲解各个流程,欢迎提问!

argo-events支持的触发机制如下: image.png

eventsource的pod监听到event后,将event写入eventbus消息队列,sensor的pod读取自己订阅的事件后触发预定义的后续流程。

一、环境准备

使用argoCD在腾讯云集群上部署好argoEvents 和 argoWorkflow

argo开源项目访问地址:github.com/argoproj

二、过程

gitlab添加webhook钩子

  • 设置触发源为gitlab无果

从官方文档 argoproj.github.io/argo-events… 中可以可以看出,argo-events可以直接配置触发源是gitlab.

我根据官网给的demo---github.com/argoproj/ar… 配置后,报错如下

kubectl logs gitlab-eventsource-lsbfm-5c69fb6989-cnkhn -n argo

"msg":"failed to create gitlab webhook for project 16668. err: json: cannot unmarshal array into Go value of type gitlab.ProjectHook"

尝试降低argo-events的版本也无果,最终决定迂回解决,不能被限死在这儿。

  • 手动配置webhook

argo-events可以配置触发源是gitlab的原理是:调用gitlab的api接口向gitlab注册一个webhook钩子而已,这个webhook钩子其实就是类似一个回调函数,当gitlab代码仓发生某个event,比如push时,就会向webhook预先定义的一个服务发送一个http消息而已。

argo-events调gitlab接口添加webhook这个过程我们可以通过手动在gitlab上配置就行了。 gitlab配置位置如下 image.png

  • 添加webhook-eventsource

webhook钩子需要配置url,也就是gitlab发生events后gitlab往什么目标发送消息

这个需要我们在集群里添加一个webhook,参考官方样例 github.com/argoproj/ar…


apiVersion: argoproj.io/v1alpha1
kind: EventSource
metadata:
  name: webhook
  annotations:
    service.kubernetes.io/qcloud-loadbalancer-internal-subnetid: subnetxxx
spec:
  service:
    type: LoadBalancer
    ports:
      - port: 12000
        targetPort: 12000
  webhook:
    # event-source can run multiple HTTP servers. Simply define a unique port to start a new HTTP server
    operation-back:
      # port to run HTTP server on
      port: "12000"
      # endpoint to listen to
      endpoint: /xxxxx
      # HTTP request method to allow. In this case, only POST requests are accepted
      method: POST

我的服务部署在腾讯云上,因为要能让gitlab访问到,我给webhook的service添加了一个type为LoadBalancer,但是发现没有效果,这个是crd不支持配置type,我只好直接去腾讯云的控制台手动配置service的网络类型为“内网LB”,因为我的gitlab也在内网,所以直接可以通。你可以根据你的需要配置成“外网LB”。

后来看到官方文档 argoproj.github.io/argo-events… image.png 我们可以单独配置一个Service,然后用选择器eventsource-name: webhook来选择eventsource生成的那个pod实例。

  • gitlab触发webhook测试

image.png 你可以在添加webhook的下面看到所有你添加的webhook,点击右下角的Test,再选择一个events就可以出发想webhook预定义好的目标发送消息

你可以先查看集群里webhook的日志来确定有没有收到gitlab的消息

kubectl logs webhook-eventsource-mfzcs-7d7757cc6d-mxftm -n argo

配置sensor监听webhook事件,然后触发定义了CI流程的WorkflowTemplate

apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
 name: gitlab
spec:
 dependencies:
   - name:test-back
     eventSourceName: gitlab
     eventName:test-back
 triggers:
   - template:
       name: gitlab-workflow-trigger
       k8s:
         group: argoproj.io
         version: v1alpha1
         resource: workflows
         operation: create
         source:
           resource:
             apiVersion: argoproj.io/v1alpha1
             kind: Workflow
             metadata:
               generateName: gitlab-workflow-
             spec:
                template:
                - name: git-checkout
                  templateRef:
                    name:test-back
                    template:test-back
         parameters:
           - src:
               dependencyName: a
             dest: spec.arguments.parameters.0.value

你可以配置一个WorkflowTemplate来定义CI流程的各个流程,然后通过一个项目一个Sensor的方式给WorkflowTemplate传递参数。

apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
  name: test-back
  namespace: argo
spec:
...  ....

参考 使用Argo workflows构建java CI流水线

最终调用接口测试

curl --location --request POST 'http://1.117.170.20:32365/pipeline' \
--header 'Content-Type: application/json' \
--data-raw '{
"repo_url":"https://gitee.com/nativesailor/native-devops.git",
"branch":"master",
"app_code":"test",
"area":"zh",
"env":"dev",
"lang":"java",
"life_cycle":"deploy"
}'

报错

{"level":"error","ts":1671339252.5666783,"logger":"argo-events.sensor","caller":"sensors/listener.go:345","msg":"Failed to execute a trigger","sensorName":"sensor-for-pipeline","error":"failed to execute trigger, failed after retries: workflows.argoproj.io is forbidden: User \"system:serviceaccount:argo:default\" cannot create resource \"workflows\" in API group \"argoproj.io\" in the namespace \"argo\"","triggerName":"pipeline-workflow-trigger","triggeredBy":["pipeline"],"triggeredByEvents":["33376339613733372d383830622d346135612d396334342d373434326261633362643036"],"stacktrace":"github.com/argoproj/argo-events/sensors.(*SensorContext).triggerWithRateLimit\n\t/home/runner/work/argo-events/argo-events/sensors/listener.go:345"}

按example添加rbac后解决。 github.com/argoproj/ar…

如何区别分支

我们可以通过gitlab给webhook发送的reqest的body里的ref字段中获取到是哪个分支发生了event image.png

webhook会将这个body都发送给sensor。 这是消息格式

image.png

参考: argoproj.github.io/argo-events…

github.com/argoproj/ar…

可以从中提取出分支信息

         parameters:
           - src:
               dependencyName: operation-back
               dataTemplate: "{{ .Input.body.ref }}"
             dest: spec.arguments.parameters.0.value
           - src:
               dependencyName: operation-back
               dataTemplate: "{{ .Input.body.project.web_url }}"
             dest: spec.arguments.parameters.1.value

我们需要截取ref才能得到分支名,使用go的插件Sprig可以实现

参考sprig提供的function

String Functions有类似java的subString

Get a substring from a string. It takes three parameters:

-   start (int)
-   end (int)
-   string (string)

substr 0 5 "hello world"

The above returns `hello`

改成:

 dataTemplate: "{{ .Input.body.ref | trimPrefix \"refs\/heads\/\" }}"
或者
 dataTemplate: "{{ .Input.body.ref | substr 11 17 }}"

发现总是转化失败,试了好久,很奇怪upper title啥的都能支持,难道trimPrefix和substr就不支持???

后来才明白,应该是不支持 | 这种管道方式

改成

 dataTemplate: "{{ trimPrefix \"refs/heads/\" .Input.body.ref }}"

就ok了

改进后的sensor

apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
  name: webhook
spec:
#  template:
  #  serviceAccountName: operate-workflow-sa
  dependencies:
    - name: operation
      eventSourceName: webhook-for-java
      eventName: operation
  triggers:
    - template:
        conditions: "operation"
        name: java-project-workflow-trigger
        k8s:
          group: argoproj.io
          version: v1alpha1
          resource: workflows
          operation: create
          source:
            resource:
              apiVersion: argoproj.io/v1alpha1
              kind: Workflow
              metadata:
                generateName: THIS_WILL_BE_REPLACED_BY_PROJECT_NAME_FROM_EVENT
              spec:
                  arguments:
                    parameters:
                    - name: branch
                    - name: web-url
                    - name: project-name
                    - name: timestamp
                  workflowTemplateRef:
                    inputs:
                      parameters:
                        - name: branch
                        - name: web-url
                        - name: project-name
                        - name: timestamp
                    name: java-operation
          parameters:
            - src:
                dependencyName: operation
                dataTemplate: "{{ trimPrefix \"refs/heads/\" .Input.body.ref }}"
              dest: spec.arguments.parameters.0.value
            - src:
                dependencyName: operation
                dataTemplate: "{{ trimPrefix \"https://\" .Input.body.project.web_url}}"  
              dest: spec.arguments.parameters.1.value
            - src:
                dependencyName: vue-operation
                dataTemplate: "{{ lower .Input.body.project.path_with_namespace}}"  
              dest: spec.arguments.parameters.2.value
            - src:
                dependencyName: operation
                dataTemplate: "{{ (first .Input.body.commits).timestamp | replace \":\" \"-\" }}"  
              dest: spec.arguments.parameters.3.value
        # Apply parameters at the template level.
      parameters:
          - src:
              dependencyName: vue-operation
              dataTemplate: "{{ lower .Input.body.project.path_with_namespace | replace \"/\" \"-\"  }}-{{ trimPrefix \"refs/heads/\" .Input.body.ref }}-"  
            dest: k8s.source.resource.metadata.generateName

dataTemplate: "{{ trimPrefix "refs/heads/" .Input.body.ref }}" 获取分支名

dataTemplate: "{{ trimPrefix "https://" .Input.body.project.web_url}}" 获取去掉“https://” 的代码仓地址

dataTemplate: "{{ lower .Input.body.project.path_with_namespace}}" 获取工程名

dataTemplate: "{{ (first .Input.body.commits).timestamp | replace ":" "-" }}" 获取触发时间

      parameters:
          - src:
              dependencyName: vue-operation
              dataTemplate: "{{ lower .Input.body.project.path_with_namespace | replace \"/\" \"-\"  }}-{{ trimPrefix \"refs/heads/\" .Input.body.ref }}-"  
            dest: k8s.source.resource.metadata.generateName

[metadata.generateName: Invalid value: "xxxx/XXXX-FF/app-main-dev-": a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is 'a-z0-9?(\.a-z0-9?)*'),

metadata.generateName有规则,不能有斜杠或者大写字母,通过 lower .Input.body.project.path_with_namespace | replace "/" "-" 进行适配。

文章参考

arctiq.ca/2020/07/13/…

blog.csdn.net/weixin_4395…

wnote.com/post/cicd-a…