TT Lab
开始
学习 学习路径 课程

GitLab CI/CD

放进缓存的构建产物在部署作业里不见了

在 TT Lab 中继续学习

目标

把流水线运行多次,通过日志看到缓存命中、未命中和失效,确认产物的排除、接收方的限制、dotenv 值的传递,然后创建产物检查脚本。

为什么重要

产物如果没有,后面的作业就应该失败才对,而缓存即使没有,所有作业也应该能运行才对。如果把两者混淆,在缓存为空的那天,部署作业就会悄悄发出一个空目录,或者没有过期时间的产物会把仓库容量填满。缓存的全部取决于用什么生成键,如果把键设为固定字符串,就会带着过时的依赖到处跑,如果让消费者也上传,污染就会蔓延。产物没有办法让人每次都确认“上传了什么”,所以最好把检查自动化。

步骤

  1. 把 /root/glci-transfer 创建为 Git 仓库(.gitignore 中写入 .gitlab-ci-local/),在 .gitlab-ci.yml 中放入 stages [build, test, deploy] 和两个作业。package(build)创建 dist/app.tgz 和 dist/app.js.map,用 artifacts 上传 dist/,但排除 dist/*.map,并设置 expire_in: 1 week。ship(deploy)执行 test -f dist/app.tgz && echo has-tgz、test -e dist/app.js.map && echo has-map || echo no-map、test -d public && echo has-public || echo no-public。运行后,ship 日志必须是 has-tgz、no-map、no-public。
  2. 添加作业 docs(build),创建 public/index.html,并用 artifacts 上传 public/(expire_in 2 days)。给 ship 设置 dependencies: [package],使其只接收 package 的产物。运行后,ship 日志仍必须是 has-tgz、no-map、no-public。
  3. 在 requirements.txt 中放入一行 requests==2.32.3,并加入提交对象。作业 deps(build)把 cache 设为 key: files: [requirements.txt]、paths: [.deps/]、policy: pull-push,并在 mkdir -p .deps 之后,如果有 .deps/installed 就输出 cache-hit,没有就输出 cache-miss,并创建该文件。连续运行两次,必须是先 miss 后 hit。评分器也会在副本中修改 requirements.txt,检查是否再次出现 miss。
  4. 再添加两个作业。unit(test)把与 deps 相同的键和路径的 cache 设为 policy: pull,如果有 .deps/installed 就输出 unit-hit,没有就输出 unit-miss,然后创建 .deps/junk。verify-cache(deploy)也以 pull 接收同一个缓存,如果有 .deps/junk 就输出 junk-saved,没有就输出 junk-not-saved。运行后,必须出现 unit-hit 和 junk-not-saved。
  5. 把 deps 的 cache 改成两个列表项。第一个是在 key: files: [requirements.txt] 上加了 prefix: py 的 .deps/(pull-push),第二个是 key: tools-$CI_COMMIT_REF_SLUG 的 .tools/。在 deps 脚本中,如果有 .tools/lint 就输出 tools-hit,没有就输出 tools-miss,并加上创建它的那一行。unit 和 verify-cache 的键中也要放置同样的 prefix。运行两次,第二次运行时两个缓存都必须是 hit。
  6. 让作业 version(build)把一行 VERSION=1.4.2-${CI_COMMIT_SHORT_SHA} 写入 build.env,并用 artifacts: reports: dotenv: build.env 上传。作业 announce(deploy)以 needs: [version] 执行 echo "version=$VERSION"。运行后,announce 日志必须是 version=1.4.2-<짧은 커밋 해시>(占位符为短提交哈希)。
  7. 创建 /root/glci-transfer/artifact-audit.sh <저장소>(占位符为仓库)。把仓库复制成临时副本,提交所有文件并运行流水线之后,如果已上传的产物(.gitlab-ci-local/artifacts 之下)中有 *.map、*.env、*.pem 或超过 1MiB 的文件,就输出一行 BAD <잡>/<경로> …(占位符依次为作业与路径,以空格分隔、排序)并以 3 结束;没有则输出 OK 并以 0 结束;流水线失败则输出 ERROR 并以 1 结束。报告(.gitlab-ci-reports/ 之下)不纳入检查。不要在原仓库中留下任何东西。评分器会用这个仓库(OK)和放入了会泄漏到产物中的文件的副本来确认。

参考

产物只上传需要的

把 /root/glci-transfer 创建为 Git 仓库(.gitignore 中写入 .gitlab-ci-local/),在 .gitlab-ci.yml 中放入 stages [build, test, deploy] 和两个作业。package(build)创建 dist/app.tgz 和 dist/app.js.map,用 artifacts 上传 dist/,但排除 dist/*.map,并设置 expire_in: 1 week。ship(deploy)执行 test -f dist/app.tgz && echo has-tgz、test -e dist/app.js.map && echo has-map || echo no-map、test -d public && echo has-public || echo no-public。运行后,ship 日志必须是 has-tgz、no-map、no-public。

artifacts:exclude 会从用 paths 选中的文件中去掉不上传的文件。它可以防止像 source map、调试符号这样体积大、部署又不需要的文件在每条流水线中堆积。expire_in 是何时删除,实际的删除由服务器执行。

部署作业不接收用不到的文档

添加作业 docs(build),创建 public/index.html,并用 artifacts 上传 public/(expire_in 2 days)。给 ship 设置 dependencies: [package],使其只接收 package 的产物。运行后,ship 日志仍必须是 has-tgz、no-map、no-public。

既没有 needs 也没有 dependencies 的作业,会下载前面 stage 的全部产物。这是部署作业因为连测试报告和文档也要接收而变慢的常见原因。dependencies 保持 stage 顺序不变,只挑选要接收的产物。

锁文件一变,缓存也随之改变

在 requirements.txt 中放入一行 requests==2.32.3,并加入提交对象。作业 deps(build)把 cache 设为 key: files: [requirements.txt]、paths: [.deps/]、policy: pull-push,并在 mkdir -p .deps 之后,如果有 .deps/installed 就输出 cache-hit,没有就输出 cache-miss,并创建该文件。连续运行两次,必须是先 miss 后 hit。评分器也会在副本中修改 requirements.txt,检查是否再次出现 miss。

如果给缓存键指定文件列表,那些文件内容的哈希就成为键。依赖列表一变,键自然就变了,不会带着过时的缓存到处跑。写成即使没有缓存,作业也能一直运行到结束,是缓存的契约。

消费者只接收缓存

再添加两个作业。unit(test)把与 deps 相同的键和路径的 cache 设为 policy: pull,如果有 .deps/installed 就输出 unit-hit,没有就输出 unit-miss,然后创建 .deps/junk。verify-cache(deploy)也以 pull 接收同一个缓存,如果有 .deps/junk 就输出 junk-saved,没有就输出 junk-not-saved。运行后,必须出现 unit-hit 和 junk-not-saved。

pull 只在开始时接收,结束时不上传。如果只让创建缓存的一个作业使用 pull-push,消费者就省去了把同样内容再次压缩上传的时间,消费者污染缓存的事也就被堵住了。

寿命不同的缓存要分开键

把 deps 的 cache 改成两个列表项。第一个是在 key: files: [requirements.txt] 上加了 prefix: py 的 .deps/(pull-push),第二个是 key: tools-$CI_COMMIT_REF_SLUG 的 .tools/。在 deps 脚本中,如果有 .tools/lint 就输出 tools-hit,没有就输出 tools-miss,并加上创建它的那一行。unit 和 verify-cache 的键中也要放置同样的 prefix。运行两次,第二次运行时两个缓存都必须是 hit。

一个作业可以设置多个缓存。如果把像依赖那样随锁文件变化的缓存,与想按分支分别保存的工具缓存混在同一个键里,一边的变化就会让另一边也失效。prefix 可以避免与使用相同文件哈希的其他缓存名称重叠。

把前面作业计算出的值传给后面的作业

让作业 version(build)把一行 VERSION=1.4.2-${CI_COMMIT_SHORT_SHA} 写入 build.env,并用 artifacts: reports: dotenv: build.env 上传。作业 announce(deploy)以 needs: [version] 执行 echo "version=$VERSION"。运行后,announce 日志必须是 version=1.4.2-<짧은 커밋 해시>(占位符为短提交哈希)。

变量是在创建流水线时确定的,所以运行中计算出的值,通常的方法无法传给后面的作业。dotenv 报告会把作为产物上传的 KEY=VALUE 文件,作为环境变量放进后面的作业。rules 已经评估完毕,看不到这个值。

找出不该进入产物的文件

创建 /root/glci-transfer/artifact-audit.sh <저장소>(占位符为仓库)。把仓库复制成临时副本,提交所有文件并运行流水线之后,如果已上传的产物(.gitlab-ci-local/artifacts 之下)中有 *.map、*.env、*.pem 或超过 1MiB 的文件,就输出一行 BAD <잡>/<경로> …(占位符依次为作业与路径,以空格分隔、排序)并以 3 结束;没有则输出 OK 并以 0 结束;流水线失败则输出 ERROR 并以 1 结束。报告(.gitlab-ci-reports/ 之下)不纳入检查。不要在原仓库中留下任何东西。评分器会用这个仓库(OK)和放入了会泄漏到产物中的文件的副本来确认。

gitlab-ci-local 会把每个作业上传的产物放在 .gitlab-ci-local/artifacts/<잡이름>/(占位符为作业名称)之下。dotenv 报告是有意传递的值,所以 gitlab-ci-local 把它单独放在 .gitlab-ci-reports/ 之下,检查时必须排除这个位置,正常配置才不会变成 BAD。find 的 -size 可以用 k 为单位来计算。