目录

使用极狐GitLab在K8S中进行CD的几种方式

随着国家数字化转型战略的持续深入,云原生技术在各行各业中获得了更广泛的应用。其中DevOps和Kubernetes作为云原生应用的标准化平台,扮演着非常重要的角色。

作为一体化DevOps平台,极狐GitLab内置了开箱即用的CI/CD引擎,并可以与K8S集成,便于实现更快、更可靠和更高效的云原生应用程序开发、测试和部署。

网络上有很多关于使用极狐GitLab在K8S中进行CI的方案,本身也比较简单。而关于使用极狐GitLab在K8S中进行CD的内容却比较少,总结的也不是很全面。所以我将这部分内容单独抽离出来,汇总成一篇文章,供大家参考。

在开始阅读文章或进行实操前,你需要掌握以下知识:

  • 掌握K8S的基本概念和使用方式:如K8S对象、对象资源YAML、kubectl等。
  • 掌握GitLab CI的使用方式:如Runner、脚本语法等。
  • 掌握镜像仓库的使用:如Docker Registry、Harbor、GitLab制品库、JFrog Artifactory等。

1. 基于认证的K8S集成

其原理是将KubeConfig文件作为GitLab CI/CD 环境变量进行存储,在流水线脚本中使用kubectl通过KubeConfig文件连接到K8S集群并执行命令。

该方案使用简单,但在安全性较差,已被GitLab遗弃,详见:《Kubernetes clusters | GitLab》

但你依然可以根据实际情况选择使用这种方式,比如在测试环境、小型团队或者在K8S CD的起步阶段使用。

  1. 获取K8S集群的KubeConfig文件,示例内容如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    apiVersion: v1
    clusters:
    - cluster:
        certificate-authority-data: xxxxx
        server: https://xx.xx.xx.xx:443
      name: cls-2nyr3x9a
    contexts:
    - context:
        cluster: cls-2nyr3x9a
        user: "100023468845"
      name: cls-2nyr3x9a-100023468845-context-default
    current-context: cls-2nyr3x9a-100023468845-context-default
    kind: Config
    preferences: {}
    users:
    - name: "100023468845"
      user:
        client-certificate-data: xxxxxx
        client-key-data: xxxxxx
    
  2. 在实例级、群组级或项目级设置CI/CD环境变量,如创建名为UAT_KUBE_CONFIG的变量,类型为文件,内容为KubeConfig文件中的内容: /gitlab-cd-in-k8s/1683185383915-c432f647-bec2-4c5c-b33e-3890aa127797.png

  3. 在GitLab项目中添加K8S Manifest,如deploy.yaml文件:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    apiVersion: v1
    kind: Pod
    metadata:
      name: nginx
      namespace: default
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
    
  4. 在GitLab项目中添加流水线脚本,示例内容如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    
    deploy-to-sit:
      stage: deploy
      image:
        name: bitnami/kubectl:latest
        entrypoint: ['']
      # 仅在test分支下执行
      rules:
        - if: $CI_COMMIT_BRANCH == 'test'
      # 读取 SIT_KUBE_CONFIG 环境变量作为 kubeconfig
      before_script:
        - export KUBECONFIG=$SIT_KUBE_CONFIG
      # 执行kubectl命令
      script:
        - kubectl get pods
        - kubectl apply -f deploy.yaml
    
    deploy-to-uat:
      stage: deploy
      image:
        name: bitnami/kubectl:latest
        entrypoint: ['']
      # 仅在main分支下执行
      rules:
        - if: $CI_COMMIT_BRANCH == 'main'
      # 读取 UAT_KUBE_CONFIG 环境变量作为 kubeconfig
      before_script:
        - export KUBECONFIG=$UAT_KUBE_CONFIG
      # 执行kubectl命令
      script:
        - kubectl get pods
        - kubectl apply -f deploy.yaml
    

该方案操作简单,容易实现,但在流水线脚本中可以通过cat $KUBECONFIG命令读取KubeConfig文件内容,存在安全风险,对此可参考《如何安全使用GitLab CICD SSH部署》文章中的内容,对KubeConfig的部分内容进行隐藏,可在一定程度上提高安全性。 /gitlab-cd-in-k8s/1683185990416-44eca336-5fa2-4180-b3cb-ebc00462bbeb.png

2. 基于Agent的K8S集成

为解决基于认证的K8S集成所带来的安全性问题,提高效率和性能,以及实现更多的功能,GitLab设计了GitLab Agent for K8S来作为K8S和GitLab沟通的桥梁,详见:《GitLab Agent for Kubernetes | GitLab》

GitLab Agent for K8S在GitLab 14.5版本之后从专业版下放到标准版(社区版)。GitLab Agent for K8S同时支持Push模型和Pull模型,关于这两者的介绍和区别可参考文章《云原生时代,你还不懂 GitOps》

在GitLab中,基于传统的Push模型的CD方式称之为"GitLab CI/CD Workflow",而基于Pull模型的CD方式称之为"GitOps Workflow",接下来将分别说明这两种方式如何实现。

2.1 安装Agent

不论是GitLab CI/CD Workflow还是GitOps Workflow,都需要安装GitLab Agent for K8S,目前GitLab Agent for K8S支持的K8S版本如下:

  • 1.26 (support ends on March 22, 2024 or when 1.29 becomes supported)
  • 1.25 (support ends on October 22, 2023 or when 1.28 becomes supported)
  • 1.24 (support ends on July 22, 2023 or when 1.27 becomes supported)

此外需要在本地电脑安装helmkubectl用于链接K8S集群并安装Agent。

安装方式详见文档:《Installing the agent for Kubernetes | GitLab》

以下是安装Agent的主要步骤:

  1. 创建一个根群组,如ci。在根群组ci下创建子群组,如agents。在子群组agents下创建一个项目,如agent1。在该项目下创建文件,路径为.gitlab/agents/<agent-name,如my-agent>/config.yaml,内容留空,用于作为Agent的配置文件。

  2. agent1项目左侧的菜单栏中,选择“基础设置—Kubernetes集群”,新建一个集群,如my-agent,需注意集群名称需与上一步文件路径中的<agent-name>一致。

    /gitlab-cd-in-k8s/1683210103324-0c888ef5-4b9b-455c-be48-bc07233a5cff.png

  3. 使用kubectl连接到K8S集群,根据指引使用helm命令安装Agent.

    /gitlab-cd-in-k8s/1683179979676-73426691-d9e6-4fc5-a8f2-adb37f778000.png

  4. 安装完成后检查Agent的连接状态。

    /gitlab-cd-in-k8s/1683188518261-bca7dc44-dbf4-4f11-b2d1-15cbdc1de680.png /gitlab-cd-in-k8s/1683188251155-4c0a36a4-8031-4701-92ac-e0ca35996149.png

2.2 GitLab CI/CD Workflow

需注意GitLab Agent for K8S只能安装在指定的项目中,不能安装在实例或群组中。如果有很多项目都需要用到Agent,虽然可以给每个项目创建Agent,但管理比较复杂,而且一点也不优雅,所以我们希望尽可能的去复用同一个Agent。

Agent支持给其他项目或者群组复用,但这些项目或群组需要与Agent这个项目本身处于同一个根群组下,不能跨根群组复用Agent,详见:《Using GitLab CI/CD with a Kubernetes cluster | GitLab》

所以基于GitLab CI/CD Workflow的群组划分方式一般建议如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
├── Group: ci  # 该群组中的项目都可以复用agents中的GitLab Agent项目
|   ├── SubGroup:agents # 不用的agent可用于区分不同的环境
|       ├── Project: agent1
|       └── Project: agent2
|   ├── SubGroup: gitlab-cicd-workflow
|       ├── Project: push-model-demo
|       └── Project: xxx
|   ├── SubGroup: gitops-workflow 
|       ├── Project: pull-model-demo
|       └── Project: xxx

基于2.1中配置的Agent,实现GitLab CI/CD Workflow的主要步骤如下:

  1. agent1项目中,修改.gitlab/agents/<agent-name>/config.yaml文件,增加以下内容:

    1
    2
    3
    4
    5
    6
    
    ci_access:
      projects:
        - id: <其他需要复用该agent的项目的相对路径,最多100个,需与agent项目处于相同的父群组。>
      groups:
        - id: ci/gitlab-cicd-workflow
        - id: <其他需要复用该agent的群组的相对路径,最多100个,需与agent项目处于相同的父群组。>
    
  2. 在根群组ci下创建子群组,如gitlab-cicd-workflow。在子群组gitlab-cicd-workflow中创建项目,如push-model-demo,该项目的相对路径是ci/gitlab-cicd-workflow/push-model-demo

  3. push-model-demo项目中添加K8S Manifest,如一个deploy.yaml文件。

  4. push-model-demo项目中添加流水线脚本,示例内容如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    deploy:
      image:
        name: bitnami/kubectl:latest
        entrypoint: ['']
      script:
        - kubectl config get-contexts
        # kubectl config use-context path/to/agent/repository:agent-name
        - kubectl config use-context ci/agents/agent1:my-agent
        - kubectl get pods
        - kubectl apply -f deploy.yaml
    
  5. 运行push-model-demo项目的流水线,验证结果。

    /gitlab-cd-in-k8s/1683190298427-dcb6d630-731a-40fa-83cf-b774e2cb321a.png

使用GitLab CI/CD Workflow基于Agent的K8S集成比基于认证的K8S集成略显复杂,但它不会泄露KubeConfig文件,也不直接操作K8S API,此外在agent项目中可修改配置文件实现对指定项目的CD授权,也从多方面增加了系统的安全性。

但是由于Push模型本身在设计上就会出现“配置漂移”和安全合规问题,所以GitLab CI/CD Workflow依然被认为是一种“不安全”的CD模式。

2.3 GitOps Workflow

基于GitLab Agent for K8S的GitOps Workflow如下图所示:

/gitlab-cd-in-k8s/1683208193398-36cb1df9-1483-4034-a052-c2b5b458b4af.png

开发人员使用GitLab CI对代码进行自动构建,将打包的镜像存放到制品库,将配置清单存放到配置库。部署在K8S集群上的GitLab Agent监听配置库,当发现配置库有变化时,基于配置库中配置清单自动执行部署任务。

需要注意的是目前基于GitLab Agent for K8S的GitOps Workflow存在一些缺陷:

如果要复用Agent,则配置清单项目需设置可见性为公开(Public)。而一个项目的可见性要设置为公开(Public),则它的群组、父群组的可见性也需要设置为公开(Public)。这增加了管理上的风险。

或者在每个配置清单项目里设置单独的Agent,这样这些项目的可见性就可设置为私有(Private)。但这又增加了管理的复杂度。

GitLab官方目前正在解决这个问题,详见:https://gitlab.com/groups/gitlab-org/-/epics/7704

基于2.1中配置的Agent,实现GitLab CI/CD Workflow的主要步骤如下:

  1. 在根群组ci下创建子群组,如gitops-workflow。在子群组gitops-workflow中创建公开(Public)项目,如pull-model-demo,该项目的相对路径是ci/gitops-workflow/pull-model-demo

  2. pull-model-demo项目中添加K8S Manifest,如一个deploy.yaml文件。

  3. agent1项目中,修改.gitlab/agents/<agent-name>/config.yaml文件,增加以下内容:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    gitops:
      manifest_projects:
      - id: ci/gitops-workflow/pull-model-demo
        ref: # either `branch`, `tag` or `commit` can be specified
          branch: main
          # commit: <mysha>
          # tag: v1.0
        paths:
          # Read all YAML files from this directory.
        - glob: '*.yaml'
          # Read all .yaml files from team2/apps and all subdirectories.
          # - glob: '/team2/apps/**/*.yaml'
          # If 'paths' is not specified or is an empty list, the configuration below is used.
          # - glob: '/**/*.{yaml,yml,json}'
        reconcile_timeout: 3600s
        dry_run_strategy: none
        prune: true
        prune_timeout: 3600s
        prune_propagation_policy: foreground
        inventory_policy: must_match
    
  4. 等待片刻后使用kubectl get pod -A 查看部署情况。

    /gitlab-cd-in-k8s/1683254123258-1128599a-481e-4b40-b3d6-30036509b825.png

  5. 可使用以下命令查看Agent日志或进行调试:

    1
    2
    3
    4
    5
    6
    
    # 查询所有GitLab Agent的命名空间
    kubectl get ns | grep gitlab-agent
    # 查询指定命名空间下的GitLab Agent Pod
    kubectl get pod -n gitlab-agent-my-agent
    # 查询指定GitLab Agent Pod的日志
    kubectl logs my-agent-gitlab-agent-88b4c67db-5nhz9 -n gitlab-agent-my-agent
    

    /gitlab-cd-in-k8s/1683255011776-04d8e242-9393-42ea-bec3-1a93e10cf301.png

正如上文所述,基于GitLab Agent for K8S的GitOps Workflow实现了GitOps,但它目前还存在一些问题,在这些问题得到解决之前,建议你充分考虑使用这种方式的利弊,或者考虑使用第三方的GitOps工具,如Flux、ArgoCD等。

关于GitOps Workflow的更多内容可以参考:《Using GitOps with a Kubernetes cluster | GitLab》

3. 第三方GitOps工具与GitLab集成

3.1 Flux

Flux 是一个 GitOps 工具,用于自动化地管理 Kubernetes 应用程序的部署和更新。它的主要思想是将 Kubernetes 集群配置文件存储在 Git 存储库中,并使用 Git 的工作流来管理应用程序的生命周期,包括部署、升级和回滚。

Flux 可以通过轮询 Git 存储库或使用 Webhooks 自动同步 Kubernetes 应用程序的部署状态。当 Git 存储库中的配置文件发生更改时,Flux 会自动检测并将更改推送到 Kubernetes 集群中,从而实现自动部署和更新应用程序的能力。

/gitlab-cd-in-k8s/1683195080055-687d7016-daaa-4160-8cd0-a4ad6ae40c0f-20230505120424940.png

2023年2月,GitLab官方也宣布了未来将会与Flux深度集成,计划将Flux作为GitLab GitOps解决方案的一部分来替代GitLab Agent for K8S。详见:《GitOps with GitLab: What you need to know about the Flux CD integration | GitLab》

目前GitLab与Flux的集成还是依靠Flux原生的能力,后续会在GitLab上开发相关的UI界面以增强用户体验,预计2024年会发布GA版本。

使用Flux与GitLab集成实现GitOps可参考官方文档:《Tutorial: Set up Flux for GitOps | GitLab》

主要步骤如下:

  1. 创建空项目,如flux-config,作为Flux的配置数据源。并为该项目创建访问令牌,角色为Maintainer,范围是api

    /gitlab-cd-in-k8s/1683199352549-0d24abc6-3976-4023-8c23-261953644576.png

  2. 在本地电脑安装kubectl,配置上下文以访问K8S集群,用于安装Flux。

  3. 在本地电脑安装Flux CLIFlux CLI的安装方式可参考《Install the Flux CLI》,以Mac和Linux为例,可执行以下命令安装:

    1
    
    curl -s https://fluxcd.io/install.sh | sudo bash
    
  4. 在本地电脑通过Flux CLI在K8S集群中安装Flux:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    # 第1步中获取的访问令牌
    export GITLAB_TOKEN=xxxxx
    
    flux bootstrap gitlab \
      # GitLab实例地址
      --hostname=jhgitlab.com \
      # Flux配置仓库所在的群组 
      --owner=mycompany/ci/flux-gitops \
      # Flux配置仓库名称
      --repository=flux-config \
      # Flux配置仓库的默认分支
      --branch=main \
      # Flux配置存储路径
      --path=clusters/my-cluster \
      # 验证方式
      --token-auth 
    

    安装成功后显示内容如下: /gitlab-cd-in-k8s/1683199393880-89efbc6c-c7d8-4e01-8417-0bef55a5db99.png 执行kubectl get pod -n flux-system查看Flux的部署情况。 /gitlab-cd-in-k8s/1683199492705-7fc03648-cd08-4ea8-80e4-ef3ba5075049.png

  5. 创建项目,如web-app-manifests,用于托管某项目的K8S Manifest,如nginx-deployment.yaml

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    apiVersion: apps/v1
    
    kind: Deployment
    
    metadata:
      name: nginx-deployment
      labels:
        app: nginx
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - name: nginx
            image: nginx:1.14.2
            ports:
            - containerPort: 80
    
  6. 创建该项目的部署令牌,设置名称,如flux_deploy_token,范围是read_repository

    /gitlab-cd-in-k8s/1683199688276-43e25fae-6bff-4600-9c11-443cd2d6fb26.png 为了避免每个项目都要创建部署令牌,也可使用群组访问令牌或者个人访问令牌,范围同样也是read_repository/gitlab-cd-in-k8s/1683247605506-cc7c08ed-8071-4640-8ec5-92e985a0b831.png

  7. 在本地电脑通过Flux CLI在K8S集群中生成该部署令牌的Secret:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    # flux-deploy-authentication 是Secret名称
    flux create secret git flux-deploy-authentication \
         # web-app-manifests项目的路径
         --url=https://jhgitlab.com/mycompany/ci/flux-gitops/web-app-manifests \
         # Secret的命名空间
         --namespace=default \
         --bearer-token=glpat-EkAVMryjoxBVqgH1EDKq
         # 部署令牌/访问令牌名称
         --username=flux_deploy_token \
         # 部署令牌密码/访问令牌
         --password=rLbLreiR2jeUWrD_W7vH
    

    使用命令kubectl -n default get secrets flux-deploy-authentication -o yaml验证Secret是否生成成功。

    /gitlab-cd-in-k8s/1683199848698-3eaa6a14-36be-4b62-aa84-2f0f2b5c8520.png

  8. 在Flux配置项目flux-config中添加文件clusters/my-cluster/web-app/web-app-manifests-source.yaml,内容如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    ---
    apiVersion: source.toolkit.fluxcd.io/v1beta2
    kind: GitRepository
    metadata:
      name: web-app-manifests
      namespace: default
    spec:
      # 同步时间间隔
      interval: 1m0s
      # 同步manifest项目的分支
      ref:
        branch: main
      # 使用secret的名称
      secretRef:
        name: flux-deploy-authentication
      # 同步manifest项目的地址
      url: https://jhgitlab.com/mycompany/ci/flux-gitops/web-app-manifests
    

    该文件用于将web-app-manifests项目以GitRepository类型同步到K8S集群中。

    /gitlab-cd-in-k8s/1683202569755-e4ebf383-974f-42f8-ad3b-f260d1c742e4.png

  9. 在Flux配置项目flux-config中添加文件clusters/my-cluster/web-app/clusters/my-cluster/web-app/web-app-manifests-kustomization.yaml,内容如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    ---
    apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
    kind: Kustomization
    metadata:
      name: nginx-source-kustomization
      namespace: default
    spec:
      interval: 1m0s
      path: ./
      prune: true
      sourceRef:
        kind: GitRepository
        name: web-app-manifests
        namespace: default
      targetNamespace: default
    

    该文件用于监听K8S中的GitRepository资源,当资源发生变化时,使用kustomize来运行这些Manifest。

  10. 使用kubectl get pods -n default命令,可以看到Flux根据web-app-manifests项目中的nginx-deployment.yaml部署了3个nginx-deployment

    /gitlab-cd-in-k8s/1683200204967-4dc6606f-84a8-4046-895b-f6bd729db9e3.png

  11. 修改web-app-manifests项目中的nginx-deployment.yaml,如将replicas修改为2,等待片刻,再次使用kubectl命令查看,Flux自动将nginx-deployment的副本数量调整为2

    /gitlab-cd-in-k8s/1683200283164-09ea2ffe-c9fc-4c4c-a515-19938cec2529.png

  12. 如果想划分部署环境,可参考以下方式:

    • 相同集群,不同命名空间:无需修改Flux配置库,只需用不同名称的配置清单或配置清单库的不同分支来区分Manifest和Namespace即可。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      ├── Group: A
      |   ├── Project:flux-config
      |       ├── Folder: clusters/my-cluster
      |   ├── SubGroup:web-app-manifest
      |       ├── Project: staging
      |       └── Project: production
      |   ├── Project:server-manifest
      |       ├── Branch: staging
      |       └── Branch: production
      
    • 不同集群:需修改Flux配置库,用Flux配置库中的不同目录区分不同的K8S环境,用不同名称的配置清单或配置清单库的不同分支来区分Manifest和Namespace。

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      
      export GITLAB_TOKEN=xxxxx
      
      export KUBECONFIG=$HOME/.kube/config-cluster-staging
      flux bootstrap gitlab \
        --hostname=jhgitlab.com \
        --owner=mycompany/ci/flux-gitops \
        --repository=flux-config \
        --branch=main \
        --path=clusters/cluster-staging \
        --token-auth 
      
      export KUBECONFIG=$HOME/.kube/config-cluster-production
      flux bootstrap gitlab \
        --hostname=jhgitlab.com \
        --owner=mycompany/ci/flux-gitops \
        --repository=flux-config \
        --branch=main \
        --path=clusters/cluster-production \
        --token-auth  
      
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      
         ├── Group: A
         |   ├── Project:flux-config
         |       ├── Folder: clusters/staging
         |       └── Folder: clusters/production
         |   ├── SubGroup:web-app-manifest
         |       ├── Project: staging
         |       └── Project: production
         |   ├── Project:server-manifest
         |       ├── Branch: staging
         |       └── Branch: production
      

使用Flux与GitLab集成,开发人员只需通过CI将应用打包成镜像存放在镜像库,开发人员或运维人员维护该应用对应的配置清单库如web-app-manifests,运维人员维护Flux配置库如flux-config,即可实现GitOps。

3.2 ArgoCD

ArgoCD 是一款开源且主要针对 Kubernetes 来做 GitOps 的持续交付工具,是 CNCF 的孵化项目。

相较于Flux,ArgoCD提供了更完整的GitOps解决方案,包括多集群支持、应用程序版本控制、可视化部署状态等功能。

/gitlab-cd-in-k8s/1683191584425-ac7cb272-2db6-48bf-a95b-7a9e2f03821d.gif

/gitlab-cd-in-k8s/1683191599522-f0c528ce-96f9-4199-878c-88860c25be1f.png

ArgoCD作为目前使用最为广泛的GitOps工具,亦提供与极狐GitLab集成,将极狐GitLab作为单一可信源,从而实现GitOps。网络上关于ArgoCD+GitLab的相关文章和介绍很多,也可直接参考极狐GitLab官方的技术博客:《极狐GitLab 和 ArgoCD 的集成实践-极狐GitLab》

作为诞生于社区的开源产品,GitLab在CI/CD方面大部分的功能是免费的,基于极狐GitLab和这篇文章,你可以实现在K8S中进行CD的基础功能。如果要做的更深、更好,肯定需要花费更多的时间来实践、打磨。当然如果你的团队和企业需要一些技术支持和一些最佳实践的经验指导,少踩坑、快上线、有兜底,也可以使用极狐GitLab的企业版,在拥有更多企业级功能、性能的基础上,获得更全面、更可靠的服务。

/gitlab-cd-in-k8s/1683257655714-26185a36-5a65-46d4-a901-ccbf933f8787.jpeg


参考资料