GitLab-CI 流水线作业语法解析和实战案例

1. 传统的发布模式

  • 开发团队: 在开发环境中完成软件开发,单元测试,测试通过,提交到代码版本管理库。
  • 运维团队: 把应用部署到测试环境,供QA团队测试,测试通过后部署生产环境。
  • 测试团队: QA团队在测试环境进行测试,测试通过后通知部署人员发布到生产环境。

传统发布面临的挑战

  • 错误发现不及时: 很多错误在项目的早期可能就存在,到最后集成的时候才发现问题。
  • 人工低级错误发生: 产品和服务交付中的关键活动全都需要手动操作。
  • 团队工作效率低: 需要等待他人的工作完成后才能进行自己的工作。
  • 开发运维对立: 开发人员想要快速更新,运维人员追求稳定,各自的针对的方向不同。

2. 持续集成与持续交付

软件开发的连续方法基于自动执行脚本,以最大程度地减少在开发应用程序时引入错误的机会。从开发新代码到部署新代码,他们几乎不需要人工干预,甚至根本不需要干预。 它涉及到在每次小的迭代中就不断地构建,测试和部署代码更改,从而减少了基于错误或失败的先前版本开发新代码的机会。

持续集成(Continuous Integration)

开发人员提交代码的时候一般先在本地测试验证,只要开发人员提交代码到版本控制系统就会触发一条提交流水线,对本次提交进行验证。

持续交付(Continuous Delivery)

持续交付是超越持续集成的一步。不仅会在推送到代码库的每次代码更改时都进行构建和测试,而且,作为附加步骤,即使部署是手动触发的,它也可以连续部署。此方法可确保自动检查代码,但需要人工干预才能从策略上手动触发更改的部署。

持续部署(Continuous Deployment)

类似于持续交付,持续部署也是超越持续集成的又一步。不同之处在于,您无需将其手动部署,而是将其设置为自动部署。部署您的应用程序完全不需要人工干预。

CI/CD的价值体现

  1. 尽早反馈,尽早发现错误。
  2. 减少集成问题,每次发现问题当时解决,避免问题堆积。
  3. 每次更改都能成功发布,降低发布风险。
  4. 更加频繁的交付价值,客户反馈。

3. GitLab CI/CD 组件

GitLab CI/CD

  • GitLab 的一部分,GitLab 是一个 Web 应用程序,具有将其状态存储在数据库中的 API。
  • 除了 GitLab 的所有功能之外,它还管理项目/构建并提供一个不错的用户界面。

GitLab Runner

  • 是一个处理构建的应用程序。
  • 可以单独部署,并通过 API 与 GitLab CI / CD一起使用。
  • .gitlab-ci.yml 定义流水线作业运行,位于应用项目根目录下。

一般来说,构建任务都会占用很多的系统资源 (譬如编译代码),而 GitLab CI 又是 GitLab 的一部分,如果由 GitLab CI 来运行构建任务的话,在执行构建任务的时候,GitLab 的性能会大幅下降。GitLab CI 最大的作用是管理各个项目的构建状态,因此,运行构建任务这种浪费资源的事情就交给 GitLab Runner 来做。因为 GitLab Runner 可以安装到不同的机器上,所以在构建任务运行期间并不会影响到 GitLab 的性能

关于 GitLab-CI 配合发布至阿里云 k8s 集群具体实战,大家可以参考作者的另一篇文章,这里我们主要是介绍 .gitlab-ci.yml 文件的语法介绍,后边专门会对这个文章中的案例进行分析:
【 万字长文细说部署微服务到阿里云 k8s 集群 】

在 k8s 集群中安装 GitLab Runner

常规的 GitLab Runner 安装方式可以参考上边的文章,这里专门给大家介绍另一种安装方式,就是把 Runner 直接安装到阿里云 k8s 集群。

这里我们选择 Helm Chart 来实现安装

git 复制代码
git clone https://github.com/haoshuwei/gitlab-runner.git

接下来,我们知道 GitLab Runner 注册到 GitLab 需要 GitLab 的 url 地址和 token,这里再次贴出查看方式:

接下来修改 values.yaml 中的 gitlabUrl 和 runnerRegistrationToken 字段

yml 复制代码
## GitLab Runner Image
## ref: https://hub.docker.com/r/gitlab/gitlab-runner/tags/
##
image: gitlab/gitlab-runner:alpine-v12.9.0

## Specify a imagePullPolicy
## 'Always' if imageTag is 'latest', else set to 'IfNotPresent'
## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images
##
imagePullPolicy: IfNotPresent

## Default container image to use for initcontainer
init:
  image: busybox
  tag: latest

## The GitLab Server URL (with protocol) that want to register the runner against
## ref: https://docs.gitlab.com/runner/commands/README.html#gitlab-runner-register
##
gitlabUrl: "https://gitlab.com/"

## The Registration Token for adding new Runners to the GitLab Server. This must
## be retreived from your GitLab Instance.
## ref: https://docs.gitlab.com/ce/ci/runners/README.html#creating-and-registering-a-runner
##
runnerRegistrationToken: ""

## The Runner Token for adding new Runners to the GitLab Server. This must
## be retreived from your GitLab Instance. It is token of already registered runner.
## ref: (we don't yet have docs for that, but we want to use existing token)
##
# runnerToken: ""
#
## Unregister all runners before termination
##
## Updating the runner's chart version or configuration will cause the runner container
## to be terminated and created again. This may cause your Gitlab instance to reference
## non-existant runners. Un-registering the runner before termination mitigates this issue.
## ref: https://docs.gitlab.com/runner/commands/README.html#gitlab-runner-unregister
##
unregisterRunners: true

## Set the certsSecretName in order to pass custom certficates for GitLab Runner to use
## Provide resource name for a Kubernetes Secret Object in the same namespace,
## this is used to populate the /etc/gitlab-runner/certs directory
## ref: https://docs.gitlab.com/runner/configuration/tls-self-signed.html#supported-options-for-self-signed-certificates
##
# certsSecretName:

## Configure the maximum number of concurrent jobs
## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section
##
concurrent: 10

## Defines in seconds how often to check GitLab for a new builds
## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section
##
checkInterval: 30

## Configure GitLab Runner's logging level. Available values are: debug, info, warn, error, fatal, panic
## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section
##
# logLevel:

## For RBAC support:
rbac:
  create: true

  ## Run the gitlab-bastion container with the ability to deploy/manage containers of jobs
  ## cluster-wide or only within namespace
  clusterWideAccess: false

  ## Use the following Kubernetes Service Account name if RBAC is disabled in this Helm chart (see rbac.create)
  ##
  # serviceAccountName: default

## Configure integrated Prometheus metrics exporter
## ref: https://docs.gitlab.com/runner/monitoring/#configuration-of-the-metrics-http-server
metrics:
  enabled: true

## Configuration for the Pods that that the runner launches for each new job
##
runners:
  ## Default container image to use for builds when none is specified
  ##
  image: ubuntu:16.04

  ## Specify one or more imagePullSecrets
  ##
  ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
  ##
  # imagePullSecrets: []

  ## Specify the image pull policy: never, if-not-present, always. The cluster default will be used if not set.
  ##
  # imagePullPolicy: ""

  ## Specify whether the runner should be locked to a specific project: true, false. Defaults to true.
  ##
  # locked: true
  
  ## Specify the tags associated with the runner. Comma-separated list of tags.
  ##
  ## ref: https://docs.gitlab.com/ce/ci/runners/#using-tags
  ##
  tags: "k8s-runner"

  ## Run all containers with the privileged flag enabled
  ## This will allow the docker:dind image to run if you need to run Docker
  ## commands. Please read the docs before turning this on:
  ## ref: https://docs.gitlab.com/runner/executors/kubernetes.html#using-docker-dind
  ##
  privileged: true

  ## The name of the secret containing runnerToken and runnerRegistrationToken
  # secret: gitlab-runner

  ## Namespace to run Kubernetes jobs in (defaults to the same namespace of this release)
  ##
  namespace: gitlab


  cachePath: "/opt/cache"

  ## Distributed runners caching
  ## ref: https://gitlab.com/gitlab-org/gitlab-runner/blob/master/docs/configuration/autoscale.md#distributed-runners-caching
  ##
  ## Create a secret 's3access' containing 'accesskey' & 'secretkey'
  ## ref: https://aws.amazon.com/blogs/security/wheres-my-secret-access-key/
  ##
  ## $ kubectl create secret generic s3access --\
  ##   --from-literal=accesskey="YourAccessKey" \
  ##   --from-literal=secretkey="YourSecretKey"
  ## ref: https://kubernetes.io/docs/concepts/configuration/secret/
  ##
  cache: {}
  # cacheType: s3
    # s3ServerAddress: s3.amazonaws.com
    # s3BucketName:
    # s3BucketLocation:
    # s3CacheInsecure: false
    # s3CachePath: "gitlab_runner"
    # cacheShared: true
    # secretName: s3access
  ## Build Container specific configuration
  ##
  builds: {}
    # cpuLimit: 200m
    # memoryLimit: 256Mi
    # cpuRequests: 100m
    # memoryRequests: 128Mi

  ## Service Container specific configuration
  ##
  services: {}
    # cpuLimit: 200m
    # memoryLimit: 256Mi
    # cpuRequests: 100m
    # memoryRequests: 128Mi

  ## Helper Container specific configuration
  ##
  helpers: {}
    # cpuLimit: 200m
    # memoryLimit: 256Mi
    # cpuRequests: 100m
    # memoryRequests: 128Mi
    # image: gitlab/gitlab-runner-helper:x86_64-latest

  ## Service Account to be used for runners
  ##
  # serviceAccountName:

  ## If Gitlab is not reachable through $CI_SERVER_URL
  ##
  # cloneUrl:

## Configure resource requests and limits
## ref: http://kubernetes.io/docs/user-guide/compute-resources/
##
resources: {}
  # limits:
  #   memory: 256Mi
  #   cpu: 200m
  # requests:
  #   memory: 128Mi
  #   cpu: 100m

## Configure environment variables that will be present when the registration command runs
## This provides further control over the registration process and the config.toml file
## ref: `gitlab-runner register --help`
## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html
##
# envVars:
#   - name: RUNNER_EXECUTOR
#     value: kubernetes

执行以下命令安装 GitLab Runner

执行打包命令:

shell 复制代码
helm package .

预期输出如下:

shell 复制代码
Successfully packaged chart and saved it to: /root/gitlab/gitlab-runner/gitlab-runner-0.1.37.tgz

执行安装命令:

shell 复制代码
helm install --namespace gitlab gitlab-runner *.tgz

注册成功后,能在 GitLab 中看到 Runner 信息:

4. GitLab CI/CD 用法简介

GitLab CI/CD 工作原理

  • 将代码托管到 Git 存储库。
  • 在项目根目录创建 ci 文件 .gitlab-ci.yml ,在文件中指定构建,测试和部署脚本。
  • GitLab 将检测到它并使用名为 GitLab Runner 的工具运行脚本。
  • 脚本被分组为作业,它们共同组成了一个管道。

GitLab CI/CD 相关概念

Pipeline:一次 Pipeline 其实相当于一次构建任务,里面可以包含多个流程,如安装依赖、运行测试、编译、部署测试服务器、部署生产服务器等流程。 任何提交或者 Merge Request 的合并都可以触发 Pipeline,如下所示:

shell 复制代码
+------------------+           +----------------+
|                  |  trigger  |                |
|   Commit / MR    +---------->+    Pipeline    |
+------------------+           +----------------+

Stages:表示构建阶段,说白了就是上面提到的流程。 我们可以在一次 Pipeline 中定义多个 Stages,这些 Stages 会有以下特点:

  • 所有 Stages 会按照顺序运行,即当一个 Stage 完成后,下一个 Stage 才会开始;
  • 只有当所有 Stages 完成后,该构建任务 (Pipeline) 才会成功;
  • 如果任何一个 Stage 失败,那么后面的 Stages 不会执行,该构建任务 (Pipeline) 失败;

因此,Stages 和 Pipeline 的关系就是:

shell 复制代码
+--------------------------------------------------------+
|  Pipeline                                              |
|  +-----------+     +------------+      +------------+  |
|  |  Stage 1  |---->|   Stage 2  |----->|   Stage 3  |  |
|  +-----------+     +------------+      +------------+  |
+--------------------------------------------------------+

Jobs: 表示构建工作,表示某个 Stage 里面执行的工作。 我们可以在 Stages 里面定义多个 Jobs,这些 Jobs 会有以下特点:

  • 相同 Stage 中的 Jobs 会并行执行;
  • 相同 Stage 中的 Jobs 都执行成功时,该 Stage 才会成功;
  • 如果任何一个 Job 失败,那么该 Stage 失败,即该构建任务 (Pipeline) 失败;

所以,Jobs 和 Stage 的关系就是:

shell 复制代码
+------------------------------------------+
|  Stage 1                                 |
|  +---------+  +---------+  +---------+   |
|  |  Job 1  |  |  Job 2  |  |  Job 3  |   |
|  +---------+  +---------+  +---------+   |
+------------------------------------------+

.gitlab-ci.yml 文件

配置好 Runner 之后,我们要做的事情就是在项目根目录中添加 .gitlab-ci.yml 文件。当我们添加了 .gitlab-ci.yml 文件后,每次提交代码或者合并 MR 都会自动运行构建任务。

Pipeline 是通过提交代码或者合并 MR 来触发的。那么 Pipeline 和 .gitlab-ci.yml 有什么关系呢? 其实 .gitlab-ci.yml 就是拿来在定义 Pipeline 的。使用 GitLab 自带的流水线,必须要定义流水线的内容,而定义内容的文件默认叫做 .gitlab-ci.yml,使用 yml 的语法进行编写。

.gitlab-ci.yml 常用关键词

  • stages: 定义管道阶段的名称和顺序。
  • stage: 将指定作业(job)分配给指定stage(阶段)。
  • script: 由运行程序执行的 Shell 脚本。[单行/多行]
  • tags: 来选择可用于该项目的 runner。 [project > settings > ci_cd > Runner]
  • retry: 使用retry配置作业多少次失败的情况下重试。
  • image: 指定一个基础Docker镜像作为基础运行环境,经常用到的镜像有 node java python docker。
  • only/except: 限定当前任务的执行条件,如:只有在指定分区才能被看到。
  • when: 实现再发生故障或尽管发生故障时仍能运行的作业。
  • cache: 是将当前工作环境目录中的一些文件,文件夹等存储起来。用于在各个任务初始化的时候恢复。

5. GitLab CI/CD 具体语法

全局关键词

before_script

before_script 关键词是用于在每个任务之前执行的 shell 脚本。

yml 复制代码
before_script:
    - echo "Execute before script."

如果你在 job 中声明,在 job 作用域中会覆盖全局设置执行,而不是两个都执行,后续有些关键字同理。

yml 复制代码
before_script:
    - echo "Execute before script."
    
maven-package:
    before_script:
        - echo "Execute this before script."

after_script

after_script 会在任务执行完成后执行,即使任务失败也会被执行。

yml 复制代码
after_script:
    - echo "Execute after script."

注意 after_script 即使任务失败也会被执行,但是如果任务被取消或者超时是不执行的。

include

使用 include 可以导入一个或多个额外的 yaml 文件到 CI/CD 配置里,这就可以将一个很长的流水线,分隔出来。使用 include 来引入,也可以将几个流水线中相同的配置,提取出来公用。

include 关键词下,有四个可选性:

  • local: 引入一个当前项目的文件;
  • file: 引入一个不同项目的文件;
  • remote: 引入一个公网文件;
  • template: 引入一个由 GitLab 提供的模板;
yml 复制代码
include:
    - local: '/templates/.gitlab-ci-template.yml'

---
include:
    - project: 'my-group/my-project'  ## 另外一个项目
      ref: master   ## master分支
      file: '/templates/.gitlab-ci-template.yml'

---
include:
    - remote: 'https://gitlab.com/awesome-project/master/.gitlab-ci-template.yml'

variables

GitLab CI 允许在 .gitlab-ci.yml 文件中添加变量,并在 Job 环境中起作用。因为这些配置是存储在 git 仓库中,所以最好是存储项目的非敏感配置。

YML 复制代码
variables:
    DATABASE_URL:"postgres://postgres@postgres/my_database"

如果你在 job 中声明变量,则会覆盖全局变量的值。此时在 job 作用域中 $DATABASE_URL 的值是 postgres://postgres@postgres/my_database_2。

YML 复制代码
variables:
    DATABASE_URL:"postgres://postgres@postgres/my_database"
  
maven-package:
    variables:
      DATABASE_URL:"postgres://postgres@postgres/my_database_2"

GitLab CI 内置了许多变量,主要以 CI_XXX 开头的跟 git 仓库项目相关的变量: docs.gitlab.com/ee/ci/varia...

Stage 关键词

stages

stages 用来定义可以被 job 调用的 stages。stages 的规范允许有灵活的多级 pipelines。一个 stage 可以设置多个 job,每个 job 通过 stage 关键字进行关联。

yml 复制代码
stages:
  - build
  - test
  - deploy
  
maven-package:
    stage: build
    script:  
        - mvn clean package -Dmaven.test.skip=true

stages 中的元素顺序决定了对应 job 的执行顺序:

  1. 相同 stage 的 job 可以平行执行;
  2. 下一个 stage 的 job 会在前一个 stage 的 job 成功后开始执行。

接下仔细看看上边这个例子,它包含了 3 个 stage:

  1. 首先,所有 build 的 jobs 都是并行执行的;
  2. 所有 build 的 jobs 执行成功后,test 的 jobs 才会开始并行执行;
  3. 所有 test 的 jobs 执行成功,deploy 的 jobs 才会开始并行执行;
  4. 所有的 deploy 的 jobs 执行成功,commit 才会标记为 success;
  5. 任何一个前置的 jobs 失败了,commit 会标记为 failed 并且下一个 stages 的 jobs 都不会执行。

Job 关键词

script

任务要执行的 shell 脚本内容,内容会被 runner 执行。在这里,你不需要克隆当前的项目来进行操作,因为在流水线中,每一个的 job 的执行都会将项目下载,恢复缓存这些流程,不需要你再使用脚本恢复。你只需要在这里写你的项目安装,编译执行。

yml 复制代码
npm-install:
    script:
        - npm config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/
        - npm install --registry=http://registry.npm.taobao.org

image

指定一个基础 Docker 镜像作为基础运行环境,经常用到的镜像有 node,nginx,docker 等。

yml 复制代码
npm-install:
    image: node:latest
    script:
        - npm config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/
        - npm install --registry=http://registry.npm.taobao.org

image 的作用就是给当前任务或者当前流水线设置一个基础环境,有可能是 nodejs,也有可能是 java,go,php,可以设置当前流水线的,也可以设置当前任务的。

artifacts

流水线制品,作用是将流水线过程中的一些文件,文件夹,打包压缩,提供一个外链供人下载,另外还能在后续的 job 中缓存。 比如我们构建一个前端项目后将 dist 目录做成一个压缩包。

yml 复制代码
build:
  script:
    - npm run build
  artifacts:
    paths:
      - dist/
    exclude:
      - binaries/**/*.o
    expose_as: 'artifact 1'
    name: "$CI_COMMIT_REF_NAME"
    untracked: false
    expire_in: 2 hrs 20 min
    when: on_failure

paths 是用来指定将哪些文件,目录放入制品中, 也可以使用 exclude 关键词,将那些目录,文件排除,支持正则表达式。 此外还有以下几个属性:

  • paths:文件路径;
  • exclude: 排除的文件;
  • name:制品名称;
  • expose_as: 在 UI 页面导出的名称;
  • untracked:布尔类型,是否将 git 忽略的文件加到制品中;
  • when:何时上传制品,可选值:on_success;on_failure;always;
  • expire_in: 过期时间默认30天;
  • reports: 收集测试报告;

tags

tags 关键词是用于指定 runner,即在 UI 界面上看到注册的 runner 的 tag。不设置则默认使用公有 runner 去执行流水线。每个任务可以指定一个 runner,可以指定多个标签,但 runner 却只能一个,以一个为准。

yml 复制代码
npm-install:
    tags:
        - vue-runner
    script:
        - npm config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/
        - npm install --registry=http://registry.npm.taobao.org

cache

缓存是将当前工作环境目录中的一些文件,一些文件夹存储起来,用于在各个任务初始化的时候恢复。避免多个下载同样的包,能够大大优化流水线效率。在前端项目中,我们经常把 node_modules 缓存起来,这样一条流水线都可以使用这些下载好的包。在 java 项目中经常把 maven 下载的包缓存起来。

yml 复制代码
cache:
  key: vue-cache
  paths:
    - node_modules

---
cache:
  key:
    files:
      - Gemfile.lock
      - package.json
  paths:
    - vendor/ruby
    - node_modules

缓存可以设置流水线全局,也可以在 job 中设置,cache 下的参数有:

  • paths:当前工作环境下的目录;
  • key:存储的 key,key 不变不会重新生成缓存;
  • prefix:使用一些文件制作成文件 hash 值,当做 key 的一部分;
  • untracked :是否缓存 git 忽略的文件;
  • when :定义何时存储缓存,值 on_success;on_failure;always;
  • policy:缓存是否要在 job 完成后重新上传;

stage

stage 是阶段的意思,用于归档一部分的 job,按照定义的 stage 顺序来执行。默认的 stage 有 build,test,deploy, 此外还有两个特殊的 .pre 和 .post。以下执行顺序: job_1,job_0。

yml 复制代码
stages:
  - build
  - test
  - deploy

job_0:
  stage: test
  script: echo 'tets'

job_1:
  stage: build
  script: echo 'build'

when

when 关键字是实现在发生故障或尽管发生故障时仍能运行的作业。比如你要在任务失败后需要触发一个 job, 或者你需要手动执行任务,或者当你一个任务执行成功后,执行另一个任务。

yml 复制代码
build:
    script:
        - npm run build
    when: manual

when 可以设置以下值:

  • on_success:所有任务执行成功后;
  • on_failure:当至少一个任务失败后;
  • always:执行作业,而不考虑作业在早期阶段的状态;
  • manual:手动执行任务;
  • delayed:延迟执行任务;

only/except

only/except 是规定当前 job 的可见状态,一个项目有很多 branch,tag。我们的流水线,为了对特定的分支,特定的 branch 执行不同的 job,这里就要使用 only 和 except。

yml 复制代码
maven-package:
    image: maven:3-jdk-8-alpine
    stage: package
    script:
        - mvn clean package -Dmaven.test.skip=true
    only:
        - dev@hsh/service/hsh-planner-service

dependencies

dependencies 关键词是定义特定的 job 运行规则。默认 artifacts 是从当前阶段产生,在后续的阶段都会被下载,但我们可以使用 dependencies 关键词来控制 artifacts 从哪里下载。

yml 复制代码
build:osx:
  stage: build
  script: make build:osx
  artifacts:
    paths:
      - binaries/

build:linux:
  stage: build
  script: make build:linux
  artifacts:
    paths:
      - binaries/

test:osx:
  stage: test
  script: make test:osx
  dependencies:
    - build:osx  #依赖

test:linux:
  stage: test
  script: make test:linux
  dependencies:
    - build:linux  #依赖

deploy:
  stage: deploy
  script: make deploy

任务 test:osx 依赖 build:osx。任务 test:linux 依赖 build:linux。这样配置以后 任务 test:linux 就不用等任务 build:osx 执行完成在执行了,只需要等待任务 build:linux 完成。

extends

这个关键词可以使一个任务 继承 另一个任务。

yml 复制代码
.tests:
  script: rake test
  stage: test
  only:
    refs:
      - branches

rspec:
  extends: .tests
  script: rake rspec
  only:
    variables:
      - $RSPEC

任务 rspec 继承了 .tests 任务,在流水线中 .tests 是一个隐藏的任务,在流水线中,以点(.) 开头的任务名,都是隐藏的任务,不会被执行。 被 rspec 继承后,相同的 key 会以 rspec 为准,rspec 没有的,而 .tests 有的,则合并到 rspec 中。

trigger

trigger 是应对那些更加复杂的 CI/CD 流程,如多流水线,父子流水线。使用它可以定义一个下游的流水线,配置了 trigger 的任务是不能跑脚本的,就是说不能定义 script, before_script, 和 after_script。

yml 复制代码
rspec: 
    stage: test 
    script: bundle exec rspec 
staging: 
    stage: deploy 
    trigger: 
        project: my/deployment 
        branch: master
        ## 流水线执行完test任务后就会去执行my/deployment项目的流水线

rules

rules 是用于规定任务的执行规则,使用一个表达式,来规范哪些任务执行,哪些任务不执行。还可以在任务成功,或者失败后,触发另一个任务。

yml 复制代码
docker build:
    script: docker build -t my-image:$CI_COMMIT_REF_SLUG .
    rules: 
        - if: '$CI_COMMIT_BRANCH == "master"'
          when: delayed
          start_in: '3 hours'
          allow_failure: true
          ## 如果当前的分支是master分支则任务延迟3个小时执行,并且允许失败。

rules 的下面可选属性:

  • if:使用 if 表达式 添加或移除一个任务, 类似 only:variables;
  • changes:根据某些个文件是否改变来追加或移除一些任务。类似 only:changes;
  • exists :根据是否存在特定文件来追加或移除一些任务;

allow_failure

allow_failure 是一个布尔类型, true 或 false, 默认为 false,表示当前任务是否允许失败。如果一个任务设置了 allow_failure: true,并且这个任务报错了,那么它将会显示黄色警告。但有种情况任务失败了也会停止的, 那就是任务设置了 when: manual,即手动操作的任务。手动启动的任务,报错了就会停止,不会继续执行后续任务,除非在 rule 设置报错的处理逻辑。

yml 复制代码
build:
    script:
        - npm run build
    when: manual
    allow_failure: true

pages

pages 是一项特殊的工作,用于将静态内容上传到 GitLab,可用于为您的网站提供服务,其实就是可以托管你的网站。它具有特殊的语法,因此必须满足以下两个要求:

  • 任何静态内容都必须放在 public/ 目录下;
  • 制品 artifacts 必须是目录 public/,就是编译后的文件必须存放在 public 中;

下面的示例将所有文件从项目的根目录移至 public/ 目录。这里必须先创建一个 .public 目录,防止根目录下已经存在 public 了,导致循环复制。

yml 复制代码
pages:
    stage: deploy
    script:
        - mkdir .public
        - cp -r * .public
        - mv .public public
    artifacts:
        paths:
            - public
    only:
        - dev@hsh/service/hsh-planner-service

resource_group

有时在环境中同时运行多个作业或流水线时可能会导致在部署过程中出错。为了避免这些错误,resource_group 可以使用该属性来确保运行程序不会同时运行某些任务。资源组的行为类似于其他编程语言中的信号灯。

yml 复制代码
deploy-to-production:
    script: deploy
    resource_group: production

当一个任务设置了 resource_group , 同一项目的不同管道之间任务的运行是互斥的。如果属于同一资源组的多个任务同时进入队列,则运行程序仅选择其中一个作业。其他作业将等到 resource_group 释放。

在这种情况下,两个 deploy-to-production 单独流水线中的两个作业永远无法同时运行。最后的结果及时你可以确保永远不会在生产环境中发生并发部署。

您可以为每个环境定义多个资源组。例如,当部署到物理设备时,您可能有多个物理设备。可以将每个设备部署到,但是在任何给定时间每个设备只能部署一个。 resource_group 值只能包含字母,数字,-, _, /, $, {, }, ., 和空格。它不能以开头或结尾 /

retry

retry 可以设置一个任务的重试次数,值的类型是数字最大是2,如果设置2,就表明该任务最多可以执行3次,其中包括2次重试。对于网络不稳定的部署,非常有用。

yml 复制代码
deploy-to-production:
    script: deploy
    resource_group: production
    retry: 2

timeout

timeout 是用于设置一个任务的超时时间。

yml 复制代码
maven-package:
    image: maven:3-jdk-8-alpine
    stage: package
    timeout: 3 hours 30 minutes
    script:
        - mvn clean package -Dmaven.test.skip=true
    only:
        - dev@hsh/service/hsh-planner-service

6. GitLab CI/CD 具体项目案例

这里主要分析 .gitlab-ci.yml 文件的内容, Dockerfile 和 deployment.yml 文件请读者自行学习。下边是一个 Java 的 SpingBoot 的项目发布到阿里云 k8s 的例子:

Dockerfile

shell 复制代码
FROM azul/zulu-openjdk-centos:8

ENV LANG en_US.UTF-8
ENV LC_ALL en_US.UTF-8
ENV LANGUAGE en_US:en

ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

ADD ./target/belife-web.jar /srv

WORKDIR /srv
CMD java ${JAVA_OPTS} -jar /srv/belife-web.jar

deployment.yml

yml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: $DEPLOY_NAME # Pod名称称
  namespace: $NAMESPACE # 命名空间
spec:
  replicas: $REPLICAS # 分片个数
  revisionHistoryLimit: 3
  selector:
    matchLabels:
      app: $PROJECT_NAME
  template:
    metadata:
      labels:
        app: $PROJECT_NAME
    spec:
      restartPolicy: Always
      containers:
        - name: $CI_PROJECT_NAME
          image: ${IMAGE_NAME}:$CI_COMMIT_SHORT_SHA # 镜像名称
          imagePullPolicy: Always # 总是拉取镜像
          resources:
            limits: # 资源限制
              cpu: 500m
              memory: 2000Mi
            requests: # 所需资源
              cpu: 200m
              memory: 1200Mi
          ports:
            - name: http
              containerPort: $PORT # 端口设置
          env:
            - name: JAVA_OPTS # 环境变量
              value: $JAVA_OPTS
            - name: $LOG_STORE_OUT # 阿里云SLS相关
              value: stdout
            - name: $LOG_STORE_INIT # 阿里云SLS相关
              value: $PROJECT_NAME
          startupProbe: # 启动检查探针
            httpGet:
              path: $HEALTH_URL
              port: $PORT
              scheme: HTTP
            initialDelaySeconds: 5
            timeoutSeconds: 1
            periodSeconds: 5
            successThreshold: 1
            failureThreshold: 60
          livenessProbe: # 存活检查探针
            httpGet:
              path: $HEALTH_URL
              port: $PORT
              scheme: HTTP
            initialDelaySeconds: 5
            timeoutSeconds: 2
            periodSeconds: 5
            successThreshold: 1
            failureThreshold: 3
          readinessProbe: # 就绪检查探针
            httpGet:
              path: $HEALTH_URL
              port: $PORT
              scheme: HTTP
            timeoutSeconds: 2
            periodSeconds: 5
            successThreshold: 1
            failureThreshold: 3
      affinity:
        nodeAffinity: # 节点亲和性,软亲和性
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 1
              preference:
                matchExpressions:
                  - key: node.labels.deploy
                    operator: NotIn # 防止分片启动到同一个节点
                    values:
                      - $NODE_LABELS

service-test.yml

yml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: $SERVICE_NAME
  namespace: $NAMESPACE
spec:
  selector:
    app: $PROJECT_NAME
  type: ClusterIP
  ports:
    - port: $PORT
      targetPort: $PORT

ingress-test.yml

yml 复制代码
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: $INGRESS_NAME
  namespace: $NAMESPACE
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
    - host: test.home.belifeapp.net
      http:
        paths:
          - path: /v1
            pathType: Prefix
            backend:
              service:
                name: ${SERVICE_NAME}
                port:
                  number: $PORT

.gitlab-ci.yml

yml 复制代码
stages:
  - package
  - build
  - deploy

variables:
  NAMESPACE: belife-${CI_COMMIT_BRANCH}
  NODE_LABELS: deploy-${CI_COMMIT_BRANCH}
  REGISTRY_NAME: "registry.ap-southeast-1.aliyuncs.com"
  REGISTRY_USERNAME: belife
  REGISTRY_PASSWORD: 7e8086e81b9d5804b4b7d68c97207bb9
  PROJECT_NAME: ${CI_PROJECT_NAME}-${CI_COMMIT_BRANCH}
  IMAGE_NAME: /belife/${REGISTRY_NAME}/${PROJECT_NAME}
  DEPLOY_NAME: ${PROJECT_NAME}
  SERVICE_NAME: service-${PROJECT_NAME}
  INGRESS_NAME: ingress-${PROJECT_NAME}
  LOG_STORE_OUT: aliyun_logs_${PROJECT_NAME}
  LOG_STORE_INIT: aliyun_logs_${PROJECT_NAME}_logstore
  PORT: "80"
  HEALTH_URL: "/v1/heartbeat"
  JAVA_OPTS: "-Dspring.profiles.active=${CI_COMMIT_BRANCH} -Dserver.port=${PORT} \
  -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=utf8 \
  -Xms1024m -Xmx1024m -XX:MetaspaceSize=256M -XX:MaxNewSize=512m -XX:MaxMetaspaceSize=512m"

cache: &global_cache
  key: ${CI_COMMIT_BRANCH}
  paths:
    - "target/${CI_PROJECT_NAME}.jar"

maven-package:
  stage: package
  tags:
    - runner-k8s
  only:
    - test@belife/belife-web
    - gray@belife/belife-web
    - prod@belife/belife-web
  cache:
    <<: *global_cache
    policy: push
  script:
    - mvn clean package -Dmaven.test.skip=true

docker-build:
  stage: build
  tags:
    - runner-k8s
  only:
    - test@belife/belife-web
    - gray@belife/belife-web
    - prod@belife/belife-web
  cache:
    <<: *global_cache
    policy: pull
  script:
    - docker login -u ${REGISTRY_USERNAME} -p ${REGISTRY_PASSWORD} ${REGISTRY_NAME}
    - docker build -t ${IMAGE_NAME}:$CI_COMMIT_SHORT_SHA .
    - docker push ${IMAGE_NAME}:$CI_COMMIT_SHORT_SHA

k8s-deploy-test:
  stage: deploy
  tags:
    - runner-k8s
  only:
    - test@belife/belife-web
  cache: {}
  script:
    - envsubst < deployment.yml | cat -
    - envsubst < deployment.yml | kubectl apply -f -
    - envsubst < service-test.yml | cat -
    - envsubst < service-test.yml | kubectl apply -f -
    - envsubst < ingress-test.yml | cat -
    - envsubst < ingress-test.yml | kubectl apply -f -
    - kubectl rollout status deployment/$DEPLOY_NAME -n $NAMESPACE
  variables:
    REPLICAS: 1

k8s-deploy-gray:
  stage: deploy
  tags:
    - runner-k8s
  only:
    - gray@belife/belife-web
  cache: {}
  script:
    - envsubst < deployment.yml | cat -
    - envsubst < deployment.yml | kubectl apply -f -
    - envsubst < service-gray.yml | cat -
    - envsubst < service-gray.yml | kubectl apply -f -
    - envsubst < ingress-gray.yml | cat -
    - envsubst < ingress-gray.yml | kubectl apply -f -
    - kubectl rollout status deployment/$DEPLOY_NAME -n $NAMESPACE
  variables:
    REPLICAS: 1
    NAMESPACE: belife-prod

k8s-deploy-prod:
  stage: deploy
  tags:
    - runner-k8s
  only:
    - prod@belife/belife-web
  cache: {}
  script:
    - envsubst < deployment.yml | cat -
    - envsubst < deployment.yml | kubectl apply -f -
    - envsubst < service-prod.yml | cat -
    - envsubst < service-prod.yml | kubectl apply -f -
    - envsubst < ingress-prod.yml | cat -
    - envsubst < ingress-prod.yml | kubectl apply -f -
    - kubectl rollout status deployment/$DEPLOY_NAME -n $NAMESPACE
  variables:
    REPLICAS: 2
  when: manual

分段解析 .gitlab-ci.yml

yml 复制代码
stages:
  - package
  - build
  - deploy

我们认为项目发布,除去各种通知警告等,最少需要三个步骤:

  1. package -- Maven 打包 SpringBoot 项目成 jar 包;
  2. build -- 使用 jar 包并打成 Docker 镜像,上传至 Docker 远程仓库;
  3. deploy -- 拉取远程 Docker 镜像并发布到阿里云 k8s 集群;
yml 复制代码
variables:
  NAMESPACE: belife-${CI_COMMIT_BRANCH}
  NODE_LABELS: deploy-${CI_COMMIT_BRANCH}
  REGISTRY_NAME: "registry.ap-southeast-1.aliyuncs.com"
  REGISTRY_USERNAME: belife
  REGISTRY_PASSWORD: 7e8086e81b9d5804b4b7d68c97207bb9
  PROJECT_NAME: ${CI_PROJECT_NAME}-${CI_COMMIT_BRANCH}
  IMAGE_NAME: /belife/${REGISTRY_NAME}/${PROJECT_NAME}
  DEPLOY_NAME: ${PROJECT_NAME}
  SERVICE_NAME: service-${PROJECT_NAME}
  INGRESS_NAME: ingress-${PROJECT_NAME}
  LOG_STORE_OUT: aliyun_logs_${PROJECT_NAME}
  LOG_STORE_INIT: aliyun_logs_${PROJECT_NAME}_logstore
  PORT: "80"
  HEALTH_URL: "/v1/heartbeat"
  JAVA_OPTS: "-Dspring.profiles.active=${CI_COMMIT_BRANCH} -Dserver.port=${PORT} \
  -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=utf8 \
  -Xms1024m -Xmx1024m -XX:MetaspaceSize=256M -XX:MaxNewSize=512m -XX:MaxMetaspaceSize=512m"

这里我们定义各种全局变量,这些变量甚至能在 Dockerfile 和 deployment.yml 中使用:

  • CI_PROJECT_NAME -- git 的项目名
  • CI_COMMIT_BRANCH -- git 的分支名

上边两个是 GitLab-CI 的内置变量,其他可用的内置变量还有很多,可以参考上文全局变量的章节中的官方网站地址。

  • NAMESPACE -- 发布的 k8s 集群的 namespace
  • NODE_LABELS -- k8s 发布 node 节点的标签,便于做节点亲和性设置
  • REGISTRY_NAME -- 阿里云远程 Docker 镜像仓库的地址
  • REGISTRY_USERNAME -- 阿里云远程 Docker 镜像仓库的用户名
  • REGISTRY_PASSWORD -- 阿里云远程 Docker 镜像仓库的密码
  • PROJECT_NAME -- 自定义的工程名,格式为 "git项目名-git的分支名"
  • IMAGE_NAME -- Docker 打包完成的镜像名和 Tag 名称
  • DEPLOY_NAME -- k8s 发布的 pod 的名称
  • SERVICE_NAME -- k8s 发布的 service 的名称
  • INGRESS_NAME -- k8s 发布的 ingress 的名称
  • LOG_STORE_OUT -- 阿里云日志服务的日志库名称
  • LOG_STORE_INIT -- 阿里云日志服务的日志库初始化信息
  • PORT -- pod 端口和 service 端口
  • HEALTH_URL -- k8s 发布中探针健康检测接口
  • JAVA_OPTS -- Java 项目容器启动的 JVM 参数
yml 复制代码
cache: &global_cache
  key: ${CI_COMMIT_BRANCH}
  paths:
    - "target/${CI_PROJECT_NAME}.jar"

我们设置一个缓存,将产物缓存起来,其实就是 Maven 打出来的 jar 包。

yml 复制代码
maven-package:
  stage: package
  tags:
    - runner-k8s
  only:
    - test@belife/belife-web
    - gray@belife/belife-web
    - prod@belife/belife-web
  cache:
    <<: *global_cache
    policy: push
  script:
    - mvn clean package -Dmaven.test.skip=true

对应的 package 步骤,命令其实就是 mvn 打包命令,对应的条件是三个分支有提交,测试/灰度/生产 都需要进行这个步骤,打包好的产物 push 至缓存中供后续步骤使用。

yml 复制代码
docker-build:
  stage: build
  tags:
    - runner-k8s
  only:
    - test@belife/belife-web
    - gray@belife/belife-web
    - prod@belife/belife-web
  cache:
    <<: *global_cache
    policy: pull
  script:
    - docker login -u ${REGISTRY_USERNAME} -p ${REGISTRY_PASSWORD} ${REGISTRY_NAME}
    - docker build -t ${IMAGE_NAME}:$CI_COMMIT_SHORT_SHA .
    - docker push ${IMAGE_NAME}:$CI_COMMIT_SHORT_SHA

对应的 build 步骤,命令其实就是 docker 打包和上传命令,对应的条件是三个分支有提交,测试/灰度/生产 都需要进行这个步骤,docker 打包时将缓存中的 jar 包 pull 拉取后进行。

yml 复制代码
k8s-deploy-test:
  stage: deploy
  tags:
    - runner-k8s
  only:
    - test@belife/belife-web
  cache: {}
  script:
    - envsubst < deployment.yml | cat -
    - envsubst < deployment.yml | kubectl apply -f -
    - envsubst < service-test.yml | cat -
    - envsubst < service-test.yml | kubectl apply -f -
    - envsubst < ingress-test.yml | cat -
    - envsubst < ingress-test.yml | kubectl apply -f -
    - kubectl rollout status deployment/$DEPLOY_NAME -n $NAMESPACE
  variables:
    REPLICAS: 1

k8s-deploy-gray:
  stage: deploy
  tags:
    - runner-k8s
  only:
    - gray@belife/belife-web
  cache: {}
  script:
    - envsubst < deployment.yml | cat -
    - envsubst < deployment.yml | kubectl apply -f -
    - envsubst < service-gray.yml | cat -
    - envsubst < service-gray.yml | kubectl apply -f -
    - envsubst < ingress-gray.yml | cat -
    - envsubst < ingress-gray.yml | kubectl apply -f -
    - kubectl rollout status deployment/$DEPLOY_NAME -n $NAMESPACE
  variables:
    REPLICAS: 1
    NAMESPACE: belife-prod

k8s-deploy-prod:
  stage: deploy
  tags:
    - runner-k8s
  only:
    - prod@belife/belife-web
  cache: {}
  script:
    - envsubst < deployment.yml | cat -
    - envsubst < deployment.yml | kubectl apply -f -
    - envsubst < service-prod.yml | cat -
    - envsubst < service-prod.yml | kubectl apply -f -
    - envsubst < ingress-prod.yml | cat -
    - envsubst < ingress-prod.yml | kubectl apply -f -
    - kubectl rollout status deployment/$DEPLOY_NAME -n $NAMESPACE
  variables:
    REPLICAS: 2
  when: manual

最后这里其实对应的是 deploy 步骤,only 分别对应三个不同环境的分支。gray 灰度环境 NAMESPACE 设置为 和 生产环境一致方便流量切换;prod 生产环境我们设置分片数为 2,并且设置为 手动执行这个步骤。

具体项目发布可参考作者文章:【 万字长文细说部署微服务到阿里云 k8s 集群 】

相关推荐
2401_882727571 小时前
低代码配置式组态软件-BY组态
前端·后端·物联网·低代码·前端框架
追逐时光者2 小时前
.NET 在 Visual Studio 中的高效编程技巧集
后端·.net·visual studio
web135085886352 小时前
使用docker compose安装gitlab
docker·容器·gitlab
大梦百万秋3 小时前
Spring Boot实战:构建一个简单的RESTful API
spring boot·后端·restful
斌斌_____3 小时前
Spring Boot 配置文件的加载顺序
java·spring boot·后端
路在脚下@3 小时前
Spring如何处理循环依赖
java·后端·spring
海绵波波1074 小时前
flask后端开发(1):第一个Flask项目
后端·python·flask
小奏技术5 小时前
RocketMQ结合源码告诉你消息量大为啥不需要手动压缩消息
后端·消息队列
AI人H哥会Java7 小时前
【Spring】控制反转(IoC)与依赖注入(DI)—IoC容器在系统中的位置
java·开发语言·spring boot·后端·spring
凡人的AI工具箱7 小时前
每天40分玩转Django:Django表单集
开发语言·数据库·后端·python·缓存·django