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

GitLab CI/CD

出事时,没人知道生产环境上跑的是什么

在 TT Lab 中继续学习

目标

配置固定环境、按分支划分的评审环境、关闭环境的作业以及手动生产部署,并用 gitlab-ci-local 实际运行以提交 SHA 留存的部署记录、从该记录中选择的回滚,以及部署冻结规则。

为什么重要

部署必须可以回退才安全。什么时候发往哪里了,要通过 environment 留下来,发出去的东西要用提交 SHA 来识别,出事故时才能在几分钟内回到“当时的那个”。评审环境虽然方便,但如果没有与之成对的关闭机制,就会堆积起来,变成成本和暴露面,而生产部署的审批,仅凭 allow_failure 这一个值,既可能成为门禁,也可能沦为摆设。像冻结期这样“现在不发布”的约定,也必须刻进配置里才能得到遵守。

步骤

  1. 把 /root/glci-deploy 创建为 Git 仓库(.gitignore 中写入 .gitlab-ci-local/),在 .gitlab-ci.yml 中放入 stages [deploy, cleanup]、全局变量 DEPLOY_LOG: /root/glci-deploy-history/deploys.log,以及只在 main 上创建的作业 deploy-staging(deploy)。environment 的名称是 staging,URL 是 https://staging.example.com,脚本是 echo "env=$CI_ENVIRONMENT_NAME url=$CI_ENVIRONMENT_URL"。提交到 main 并运行。
  2. 添加只在非 main 分支上创建的作业 review-app(deploy)。environment 名称是 review/$CI_COMMIT_REF_SLUG,URL 是 https://$CI_COMMIT_REF_SLUG.review.example.com,脚本与第 1 步相同的 echo。提交。评分器会在副本中切换到 Feature/Login-Page 分支,检查名称是否为 review/feature-login-page、URL 是否为 https://feature-login-page.review.example.com。
  3. 给 review-app 的 environment 加上 on_stop: stop-review 和 auto_stop_in: 1 day,并创建作业 stop-review(cleanup)。使用相同的环境名称,action: stop,在非 main 分支上 when: manual,脚本是 echo "stopping $CI_ENVIRONMENT_NAME"。提交。在 feature 分支上,stop-review 必须在列表中为 manual,并且用 --manual stop-review 运行时,必须打印 stopping review/<슬러그>(占位符为 slug)。
  4. 添加在 main 上以 when: manual、allow_failure: false 创建的作业 deploy-prod(deploy)。environment 是 production,URL 是 https://www.example.com,脚本是 echo "env=$CI_ENVIRONMENT_NAME"。提交。直接运行时 deploy-prod 不会运行,而给出 --manual deploy-prod 时必须运行。
  5. 创建 /root/glci-deploy/scripts/deploy.sh <환경>(占位符为环境)。如果 ROLLBACK_TO 为空,就把 tag 定为 $CI_COMMIT_SHORT_SHA,并向 $DEPLOY_LOG 追加一行 <환경> registry.example.com/app:<태그> release(占位符依次为环境与 tag),然后输出 deployed ...。在 deploy-staging 和 deploy-prod 的脚本末尾加上 bash scripts/deploy.sh staging 和 bash scripts/deploy.sh production,并提交。评分器会在副本中依次部署两个提交,检查记录中是否每个提交都留下了不同的 tag。
  6. 让 deploy.sh 在收到 ROLLBACK_TO 时,不创建新 tag,而是重新发布那个 tag。如果该环境的历史记录中没有以 registry.example.com/app:<ROLLBACK_TO> 发布的记录,就输出 이력에 없는 태그입니다(韩文,意为“该标签不在历史记录中”)并失败。记录行的最后一个词是 rollback。评分器会在副本中部署两次后执行回退到第一个 tag 的运行,以及试图回退到不存在的 tag 的运行。
  7. 在 deploy-prod 的 rules 最前面,放入 if: $CI_DEPLOY_FREEZE 时 when: never。提交。用 --variable CI_DEPLOY_FREEZE=1 查看列表,必须没有 deploy-prod,没有这个变量时,它必须以 manual 存在。无论是否冻结,deploy-staging 都必须存在。

参考

给部署作业贴上标签

把 /root/glci-deploy 创建为 Git 仓库(.gitignore 中写入 .gitlab-ci-local/),在 .gitlab-ci.yml 中放入 stages [deploy, cleanup]、全局变量 DEPLOY_LOG: /root/glci-deploy-history/deploys.log,以及只在 main 上创建的作业 deploy-staging(deploy)。environment 的名称是 staging,URL 是 https://staging.example.com,脚本是 echo "env=$CI_ENVIRONMENT_NAME url=$CI_ENVIRONMENT_URL"。提交到 main 并运行。

带有 environment 的作业,在 GitLab 中会被记录为部署,并在环境页面上留下什么时候发布了什么。在作业内部,可以通过 CI_ENVIRONMENT_NAME 和 CI_ENVIRONMENT_URL 知道自己要去哪里。

每个分支生成的评审环境

添加只在非 main 分支上创建的作业 review-app(deploy)。environment 名称是 review/$CI_COMMIT_REF_SLUG,URL 是 https://$CI_COMMIT_REF_SLUG.review.example.com,脚本与第 1 步相同的 echo。提交。评分器会在副本中切换到 Feature/Login-Page 分支,检查名称是否为 review/feature-login-page、URL 是否为 https://feature-login-page.review.example.com。

分支名称中会混有大写字母、斜杠这类不能用于主机名的字符。CI_COMMIT_REF_SLUG 是转换为小写、并把不允许的字符替换为连字符后的值,所以可以原样用于 URL 和环境名称。

评审环境要与关闭它的作业成对

给 review-app 的 environment 加上 on_stop: stop-review 和 auto_stop_in: 1 day,并创建作业 stop-review(cleanup)。使用相同的环境名称,action: stop,在非 main 分支上 when: manual,脚本是 echo "stopping $CI_ENVIRONMENT_NAME"。提交。在 feature 分支上,stop-review 必须在列表中为 manual,并且用 --manual stop-review 运行时,必须打印 stopping review/<슬러그>(占位符为 slug)。

评审环境会随分支数量而堆积。on_stop 是“关闭这个环境的作业是那个”这样的连接,删除或合并分支时,GitLab 会调用那个作业。auto_stop_in 会让被遗忘的环境在一段时间之后自动关闭。

生产部署由人来点击

添加在 main 上以 when: manual、allow_failure: false 创建的作业 deploy-prod(deploy)。environment 是 production,URL 是 https://www.example.com,脚本是 echo "env=$CI_ENVIRONMENT_NAME"。提交。直接运行时 deploy-prod 不会运行,而给出 --manual deploy-prod 时必须运行。

在 GitLab 中,allow_failure: false 的手动作业,会在被点击之前让流水线保持“已阻塞”。如果是 true,即使不点击,流水线也会以成功结束,无法成为审批门禁。gitlab-ci-local 不会重现这种阻塞,所以通过列表的 allowFailure 列来确认。

把发布了什么以提交 SHA 留存

创建 /root/glci-deploy/scripts/deploy.sh <환경>(占位符为环境)。如果 ROLLBACK_TO 为空,就把 tag 定为 $CI_COMMIT_SHORT_SHA,并向 $DEPLOY_LOG 追加一行 <환경> registry.example.com/app:<태그> release(占位符依次为环境与 tag),然后输出 deployed ...。在 deploy-staging 和 deploy-prod 的脚本末尾加上 bash scripts/deploy.sh staging 和 bash scripts/deploy.sh production,并提交。评分器会在副本中依次部署两个提交,检查记录中是否每个提交都留下了不同的 tag。

如果用 latest 这样会移动的 tag 来部署,出事故时没有人知道“当时发布的是什么”。提交 SHA 是把源码、构建、部署用一行连起来的标识符。部署记录路径要用变量接收,使其可以在运行时更换。

回退就是重新发布历史中已有的 tag

让 deploy.sh 在收到 ROLLBACK_TO 时,不创建新 tag,而是重新发布那个 tag。如果该环境的历史记录中没有以 registry.example.com/app:<ROLLBACK_TO> 发布的记录,就输出 이력에 없는 태그입니다(韩文,意为“该标签不在历史记录中”)并失败。记录行的最后一个词是 rollback。评分器会在副本中部署两次后执行回退到第一个 tag 的运行,以及试图回退到不存在的 tag 的运行。

回退不是“用旧源码重新构建”,而是“把已经验证过并发布过的产物重新发布”。必须拦截历史中没有的 tag,才能避免因一个笔误而把未经验证的镜像发到生产。运行时用 --variable ROLLBACK_TO=<태그>(占位符为 tag)传入。

部署冻结期间不创建生产部署

在 deploy-prod 的 rules 最前面,放入 if: $CI_DEPLOY_FREEZE 时 when: never。提交。用 --variable CI_DEPLOY_FREEZE=1 查看列表,必须没有 deploy-prod,没有这个变量时,它必须以 manual 存在。无论是否冻结,deploy-staging 都必须存在。

如果在项目中设置了冻结期,GitLab 会在那段时间内填充 CI_DEPLOY_FREEZE。规则只使用第一个匹配项,所以冻结规则必须放在 main 规则之前。这是一个不把冻结交给人的记忆,而是让流水线来拒绝的装置。