官方文档

基本概念之类的这里不再赘述,要使用 CI/CD 的前提是需要安装 GitLab Runner 并注册到你的 GitLab 服务器上,所有的 CI/CD 就是在该 Runner 上进行处理交互

对于 GitLab Runner 安装笔者这里没有机会实践,也不进行,使用已经安装好的单机 docker 环境来自动化打包 boot 程序为 docker 镜像

至于初体验,官网 开始 里已经给出了例子,全部是使用 echo 命令输出,定义了几个 job 作业,可以自己去实验下

学习路径:

  1. 先把环境搭建起来, gitlab 服务器、gitlab runner 注册上
  2. CI/DI 官方文档中的开始、流水线、job 等概念先读一遍
  3. .gitlab-ci.yml 章节,可以参考这里的内容完成自己的 yml 编写

打包 bootJar

  1. # 默认设置,所有作业都默认使用这里的配置
  2. default:
  3. tags:
  4. - package # 表示使用哪一个 runner 来运行
  5. ### 定义阶段执行顺序
  6. # 官网文档:https://docs.gitlab.com/ee/ci/yaml/README.html#stage
  7. # 含义:一个阶段的所有作业必须在下一个阶段执行之前完成
  8. # 默认的阶段有:.pre、build、test、deploy、.post 其中 .pre 和 .post 永远是在最开始和最后执行(其他的可以被自定义的顺序控制),而不管在这里定义的顺序是什么
  9. # 可自定义阶段,按照你要让他们运行的顺序进行排序,同一阶段可以并行运行(前提是配置了并行数量)
  10. ## 这里自定义了执行顺序
  11. stages:
  12. - build
  13. - test
  14. - dockerBuild # 新增了一个自定义的 dockerBuild 阶段
  15. - deploy
  16. ## 定义作业名称
  17. build-bootJar:
  18. # 定义阶段,用于执行顺序的控制
  19. stage: build
  20. # 使用镜像运行,选择有 gradle 的镜像
  21. image: bj-docker.xxx.com/gradle:6.7.1-jdk8
  22. # 定义执行脚本
  23. script:
  24. # /builds/data/project-citest/.gradle
  25. - echo "exec bootJar, gradle:`pwd`/.gradle"
  26. - export GRADLE_USER_HOME=`pwd`/.gradle
  27. # 执行 gradle 的时候打印 info 日志信息,可以看到下载依赖的请求
  28. - gradle bootJar --info
  29. - ls -l $GRADLE_USER_HOME;
  30. - ls -l build/libs/

这样就定义了一个执行 gradle 执行 bootJar 生成 boot jar 包的作业。

这里在执行的时候,你会发现每次构建都很耗时(通过 gradle bootJar --info ),通过 --info 看到控制台上输出的日志信息,大部分耗时都在重新下载 jar 包依赖了,我们考虑使用缓存来缓存这些内容

缓存 gradle 的 jar 包依赖

给作业增加了 cache 配置

  1. ## 定义作业名称
  2. build-bootJar:
  3. # 定义阶段,用于执行顺序的控制
  4. stage: build
  5. # 使用镜像运行,选择有 gradle 的镜像
  6. image: xxx/gradle:6.7.1-jdk8
  7. # 定义执行脚本
  8. script:
  9. # /builds/data/project-citest/.gradle
  10. - echo "exec bootJar, gradle:`pwd`/.gradle"
  11. - export GRADLE_USER_HOME=`pwd`/.gradle
  12. # 执行 gradle 的时候打印 info 日志信息,可以看到下载依赖的请求
  13. - gradle bootJar --info
  14. - ls -l $GRADLE_USER_HOME;
  15. - ls -l build/libs/
  16. ### 缓存
  17. # 缓存依赖原理和最佳实践:https://docs.gitlab.com/ee/ci/yaml/README.html#cache
  18. # 缓存语法:https://docs.gitlab.com/ee/ci/yaml/README.html#cache
  19. cache:
  20. # 使用预定义变量,项目名称 作为缓存的 key,目的是为了该项目的所有分支都使用该缓存
  21. ## 所有预定义变量
  22. # 官方文档:https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
  23. # 官方支持的变量,可以直接使用,而不用定义的变量
  24. key: "$CI_PROJECT_NAME"
  25. # 要缓存的内容有哪些?
  26. # 在上面定义了 GRADLE_USER_HOME ,那么 gradle 的相关工作目录则就在该目录下,使用了 pwd,则是当前的项目目录下,所以这里使用相对路径就能获取到
  27. paths:
  28. # image 使用的是 gradle:6.7.1-jdk8,意思是该镜像已经安装了 gradle 6.7.1,
  29. # 如果要使用项目相同的 wrapper 里面的版本,则需要缓存该目录,不用每次都下载 gradle 了,但是所有使用 gradle 的地方需要改成 gradlew
  30. - .gradle/wrapper
  31. # gradle 的 jar 包依赖全部下载到这个地方
  32. - .gradle/caches
  33. ## 缓存策略
  34. # 默认是 pull-push,执行作业前,先下载缓存文件到指定目录,作业完成后,再上传
  35. policy: pull-push

查看缓存是否成功,可以看 ci 控制台输出的内容

  1. ...
  2. # 在作业执行前显示了一条 Restoring cache
  3. # 这个就是在还原缓存,没有其他信息,因为我们是第一次执行
  4. Restoring cache
  5. ....
  6. # 这里是上面 script 中执行的内容,目的是为了看看 gradle 工作目录下有哪些文件
  7. $ ls -l $GRADLE_USER_HOME;
  8. total 40
  9. drwxr-xr-x 7 root root 4096 Mar 8 06:35 6.7.1
  10. drwxr-xr-x 2 root root 4096 Mar 8 06:34 buildOutputCleanup
  11. drwxr-xr-x 7 root root 4096 Mar 8 06:29 caches # 这个是 jar 包依赖文件目录
  12. drwxr-xr-x 2 root root 4096 Mar 8 06:30 checksums
  13. drwxr-xr-x 2 root root 4096 Mar 8 06:35 configuration-cache
  14. drwxr-xr-x 3 root root 4096 Mar 8 06:29 daemon
  15. drwxr-xr-x 2 root root 4096 Mar 8 06:34 jdks
  16. drwxr-xr-x 5 root root 4096 Mar 8 06:29 native
  17. drwxr-xr-x 3 root root 4096 Mar 8 06:29 notifications
  18. drwxr-xr-x 2 root root 4096 Mar 8 06:35 vcs-1
  19. # bootJar 也出来了
  20. $ ls -l build/libs/
  21. total 16648
  22. -rw-r--r-- 1 root root 17047394 Mar 8 06:35 citest-0.0.1-SNAPSHOT.jar
  23. Saving cache for successful job
  24. 00:02
  25. # 下面是开始创建缓存了
  26. Creating cache project-citest...
  27. # .gradle/wrapper 没有匹配到文件,这个是我们没有使用 gradlew 命令,是正常的
  28. WARNING: .gradle/wrapper: no matching files
  29. # 这里找到了 1086 个文件或则目录,说明缓存生效了
  30. .gradle/caches: found 1086 matching files and directories
  31. # 没有提供 URL,缓存将不会上传到共享缓存服务器。缓存将只存储在本地。
  32. No URL provided, cache will be not uploaded to shared cache server. Cache will be stored only locally.
  33. Created cache
  34. Cleaning up file based variables
  35. 00:01
  36. Job succeeded

下面再次修改文件,上传后,查看缓存 Restoring cache 步骤的输出信息是什么

  1. ...
  2. Restoring cache
  3. Checking cache for project-citest...
  4. # 没有提供网址,缓存将不会从共享缓存服务器下载。相反,将提取缓存的本地版本。
  5. No URL provided, cache will not be downloaded from shared cache server. Instead a local version of cache will be extracted.
  6. # 成功提取缓存
  7. Successfully extracted cache
  8. ...
  9. ....
  10. Saving cache for successful job
  11. 00:02
  12. Creating cache project-citest...
  13. WARNING: .gradle/wrapper: no matching files
  14. .gradle/caches: found 1086 matching files and directories
  15. No URL provided, cache will be not uploaded to shared cache server. Cache will be stored only locally.
  16. Created cache
  17. Cleaning up file based variables
  18. 00:01
  19. Job succeeded

可以看到缓存已经生效了,它将缓存保存到了本地(应该是 GitLab Runner 机器上,我们这里实验的 GitLab Runner 是单机版的 docker)

下面在另外一个任务中尝试获取我们打好的 bootJar 包

  1. # 将 jar 包打成 image
  2. dockerBuild:
  3. stage: dockerBuild
  4. script:
  5. - ls -l build/libs/

如果不使用 image ,则 gitlab 会默认使用 ruby:2.6 这个 image 来执行我们的 script,那么上述结果如下

  1. $ ls -l build/libs/
  2. ls: cannot access 'build/libs/': No such file or directory

这个肯定是找不到的。在官网文档中就解释了 缓存与工件,artifacts (工件)用于阶段之间传递阶段的结果

配置 artifacts

  1. build-bootJar:
  2. ...
  3. artifacts:
  4. paths:
  5. - build/libs/*.jar
  6. expire_in: 1 days

控制台输出

  1. Uploading artifacts for successful job
  2. 00:02
  3. Uploading artifacts...
  4. build/libs/*.jar: found 1 matching files and directories
  5. Uploading artifacts as "archive" to coordinator... ok id=42690 responseStatus=201 Created token=15iuajwR

那么在 dockerBuild 流程中的输出关键信息如下

  1. Downloading artifacts
  2. 00:01
  3. Downloading artifacts for build-bootJar (42690)...
  4. Downloading artifacts from coordinator... ok id=42690 responseStatus=200 OK token=15iuajwR
  5. Executing "step_script" stage of the job script
  6. 00:01
  7. $ ls -l build/libs/
  8. total 16648
  9. -rw-r--r-- 1 root root 17047394 Mar 8 07:20 citest-0.0.1-SNAPSHOT.jar

使用 docker 打成 image

实现思路

  1. 使用有 docker 功能的 image
  2. 使用命令登录到私有 docker:

    1. echo "密码" | docker login -u "用户名" --password-stdin "私有 docker 仓地址 "
  3. 完成 docker build 和 push 操作

在此之前,需要准备一个 Dockerfile 文件,从上面的测试学习中可知,我们操作的目录是在项目目录下,这个从控制台的日志输出中能看到

  1. ...
  2. Getting source from Git repository
  3. 00:01
  4. Fetching changes with git depth set to 50...
  5. # 重新调整这个仓库
  6. Reinitialized existing Git repository in /builds/data/project-citest/.git/
  7. # checkout
  8. Checking out 6de1661f as main...

那么我们的思路就是,在我们的项目中编写一份 Dockerfile 文件,然后在这个作业中,执行这个文件打包镜像

具体代码如下

Dockerfile

  1. FROM xxx/openjdk:8u212-jre-slim
  2. # 定义工作目录
  3. WORKDIR /app
  4. # 复制 build/libs/*.jar 并重命名为 app.jar
  5. # 这里写的 *.jar 事实上,我们知道只会产生一个 jar 包
  6. # 这里的路径,是作业中下载的工件路径
  7. COPY build/libs/*.jar app.jar
  8. EXPOSE 80
  9. ENTRYPOINT ["java", "-jar", "app.jar"]

作业定义如下

  1. ### 将 jar 包打成 image
  2. ## 实现思路
  3. # 1. 使用有 docker 功能的 image
  4. # 2. 使用命令登录到私有 docker: echo "密码" | docker login -u "用户名" --password-stdin "私有 docker 仓地址 "
  5. # 3. 完成 docker 操作
  6. dockerBuild:
  7. image: xxx/docker:stable
  8. stage: dockerBuild
  9. script:
  10. - ls -l build/libs/
  11. # 这里使用了自定义的环境变量
  12. # 这个变量配置入口在项目的 -/settings/ci_cd,设置 -> CI/CD 页面中的 变量 中
  13. # 分为项目独有变量 和 群组变量(该群组下的项目可以直接使用)
  14. - echo "$xx_BUILD_REGISTRY_PASSWORD" | docker login -u "$xx_BUILD_REGISTRY_USER" --password-stdin "$xx_BUILD_REGISTRY"
  15. - image_version="1.0"
  16. - image_name="${xx_BUILD_REGISTRY}/project-test/citest"
  17. - echo $image_name
  18. # -f 指定 Dockerfile 路径
  19. # 一条语句写不下,还可以换行写,注意下这个语法
  20. - docker build -f ./Dockerfile
  21. --tag "$image_name:$image_version"
  22. .
  23. - docker push "$image_name:$image_version"
  24. ### needs 用于无序执行作业,可以将作业之间的关系可视化为有向无环图
  25. # 官方文档:https://docs.gitlab.com/ee/ci/yaml/README.html#needs
  26. # needs:[] 定义,但是不定义依赖的作业,表示 立即执行,需要注意的是它忽略了 stages 定义的执行顺序,类似于可以和其他作业并行执行,这里是立即执行
  27. # needs:[xxx] 必须要等待 xxx 作业完成,才能执行该作业
  28. needs:
  29. - job: build-bootJar
  30. #依赖 build-bootJar,并且需要下载它产生的工件,也就是之前打出来的 xx.jar
  31. artifacts: true
  32. ### rules 用于在管道中包括或排除作业,简单说就是符合条件的才会执行该作业
  33. # 官方文档:https://docs.gitlab.com/ee/ci/yaml/README.html#rules
  34. # 它可以写多个规则(条件),规则将按顺序进行评估,直到第一个匹配为止
  35. rules:
  36. # 这里判定触发 CI 的分支是 main 才执行该作业
  37. - if: "$CI_COMMIT_REF_NAME == 'main'"
  38. when: on_success

已经完成了目标,另外,在这基础上应该已经明白了核心的流程配置,后续可以个性化自己的配置。

一些关键文档配置路径

semantic-release:可以用该工具生成版本号,有 docker 封装好的镜像,在该镜像上直接使用

  1. version:
  2. stage: version
  3. image: xxx/semantic-release:17.1.1
  4. script:
  5. # 只在这些分支上才执行 semantic-release 命令
  6. - if [ $CI_COMMIT_REF_NAME == 'alpha' -o $CI_COMMIT_REF_NAME == 'master' ];
  7. then
  8. semantic-release;
  9. fi
  10. - if [ ! -f ./.version ];
  11. then
  12. echo "VERSION=${xxx_BUILD_VERSION:-$(date +%Y%m%d%H%M%S)}">.version;
  13. fi
  14. - cat .version
  15. artifacts:
  16. paths:
  17. - .version
  18. expire_in: 1 days

加载环境变量

有一些环境变量需要指定,我们可以将他们写成脚本的方式来执行,这可以利用 before_script 来加载

  1. # 使用语 & 取一个别名,方便别的地方引用
  2. .load_environment_variables: &load_environment_variables
  3. - echo "init environment variables."
  4. # 在项目根目录下创建 .ci/environments 目录,里面存储环境变量
  5. # 下面循环将该目录下的所有 sh 文件都加载
  6. - for environment in $(ls -d -1 .ci/environments/*.sh);
  7. do source $environment;
  8. done
  9. # 默认设置,所有作业都默认使用这里的配置
  10. default:
  11. tags:
  12. - package # 表示使用哪一个 runner 来运行
  13. before_script:
  14. # 引用上面定义的环境加载脚本
  15. - *load_environment_variables

那么在伤处的 sh 文件中,你可以定义如下的脚本导出环境变量

  1. # 设置 xxxx1 环境变量
  2. export xxx1=123
  3. # 也可以通过此方式来使用 gitlab ci 中的环境变量来判定一些逻辑
  4. if echo $CI_COMMIT_REF_NAME | grep -Eq 'dev';
  5. then
  6. 准备做些什么
  7. fi