如何构建有效的初始部署管道

已发表: 2022-03-11

我喜欢构建东西——开发者不喜欢什么? 我喜欢为有趣的问题思考解决方案、编写实现和创建漂亮的代码。 但是,我不喜欢的是操作。 运营是构建优秀软件涉及的一切——从设置服务器到将代码交付到生产环境的一切。

这很有趣,因为作为一名自由 Ruby on Rails 开发人员,我经常需要创建新的 Web 应用程序并重复找出 DevOps 方面的过程。 幸运的是,在创建了几十个应用程序之后,我终于确定了一个完美的初始部署管道。 不幸的是,并不是每个人都像我一样弄清楚了——最终,这些知识使我冒险尝试并记录了我的过程。

在本文中,我将向您介绍我在项目开始时使用的完美管道。 使用我的管道,每次推送都经过测试,主分支被部署到暂存,并使用来自生产的新数据库转储,并且版本化标签被部署到生产,备份和迁移自动发生。

请注意,因为它是的管道,所以它也是固执己见且适合我的需求; 但是,您可以随意更换您不喜欢的任何东西,并用您喜欢的任何东西替换它。 对于我的管道,我们将使用:

  • GitLab托管代码。
    • 原因:我的客户希望他们的代码保密,GitLab 的免费套餐非常棒。 此外,集成的免费 CI 也很棒。 感谢 GitLab!
    • 替代方案:GitHub、BitBucket、AWS CodeCommit 等等。
  • GitLab CI用于构建、测试和部署我们的代码。
    • 为什么:它与 GitLab 集成并且是免费的!
    • 替代方案:TravisCI、Codeship、CircleCI、使用 Fabric8 DIY 等等。
  • Heroku来托管我们的应用程序。
    • 原因:它开箱即用,是开始的完美平台。 您将来可以更改此设置,但并非每个新应用程序都需要在专门构建的 Kubernetes 集群上运行。 甚至 Coinbase 也是从 Heroku 开始的。
    • 替代方案:AWS、DigitalOcean、Vultr、使用 Kubernetes 进行 DIY 等等。

老派:创建一个基本应用程序并将其部署到 Heroku

首先,让我们为不使用任何花哨的 CI/CD 管道而只想部署他们的应用程序的人重新创建一个典型的应用程序。

传统代码托管和部署操作示意图

您要创建什么样的应用程序并不重要,但您需要 Yarn 或 npm。 对于我的示例,我正在创建一个 Ruby on Rails 应用程序,因为它带有迁移和 CLI,并且我已经为它编写了配置。 欢迎您使用您喜欢的任何框架或语言,但您需要 Yarn 来执行我稍后执行的版本控制。 我正在创建一个简单的 CRUD 应用程序,只使用几个命令并且没有身份验证。

让我们测试一下我们的应用程序是否按预期运行。 我继续创建了一些帖子,以确保。

在开发中运行的应用程序

让我们通过推送我们的代码并运行迁移将其部署到 Heroku

 $ heroku create toptal-pipeline Creating ⬢ toptal-pipeline... done https://toptal-pipeline.herokuapp.com/ | https://git.heroku.com/toptal-pipeline.git $ git push heroku master Counting objects: 132, done. ... To https://git.heroku.com/toptal-pipeline.git * [new branch] master -> master $ heroku run rails db:migrate Running rails db:migrate on ⬢ toptal-pipeline... up, run.9653 (Free) ...

最后让我们在生产环境中测试一下

在生产中运行的应用程序

就是这样! 通常,这是大多数开发人员离开他们的业务的地方。 将来,如果您进行更改,则必须重复上述部署和迁移步骤。 如果您没有迟到晚餐,您甚至可以运行测试。 这是一个很好的起点,但让我们更多地考虑一下这种方法。

优点

  • 快速设置。
  • 部署很容易。

缺点

  • Not DRY:需要在每次更改时重复相同的步骤。
  • 未版本化:“我将昨天的部署回滚到上周的”在三周后并不是很具体。
  • 不错的代码证明:你知道你应该运行测试,但没有人在看,所以尽管偶尔会出现错误的测试,但你可能会推动它。
  • 不错的演员证明:如果心怀不满的开发人员决定通过推送代码来破坏您的应用程序,并显示您没有为您的团队订购足够的比萨饼的消息怎么办?
  • 无法扩展:允许每个开发人员进行部署将使他们能够在生产级别访问应用程序,这违反了最小权限原则。
  • 没有暂存环境:特定于生产环境的错误在生产之前不会出现。

完美的初始部署管道

我今天要尝试一些不同的事情:让我们进行假设性对话。 我要给“你”一个声音,我们将讨论如何改进当前的流动。 来吧,说点什么。

说什么? 等等——我会说话吗?

是的,这就是我要给你发言权的意思。 你好吗?

我很好。 这感觉很奇怪

我明白,但只是顺其自然。 现在,让我们谈谈我们的管道。 运行部署最烦人的部分是什么?

哦,这很容易。 我浪费的时间。 你有没有试过推送到 Heroku?

是的,看着你的依赖项下载和应用程序作为git push的一部分被构建是可怕的!

我知道,对吧? 这太疯狂了。 我希望我不必那样做。 还有一个事实是我必须在部署后*运行迁移,所以我必须观看节目并检查以确保我的部署通过

好的,您实际上可以通过使用&&链接两个命令来解决后一个问题,例如git push heroku master && heroku run rails db:migrate ,或者只是创建一个 bash 脚本并将其放入您的代码中,但仍然是一个很好的答案,时间和重复是一种真正的痛苦。

是的,真的很烂

如果我告诉你你可以用 CI/CD 管道立即修复这个问题怎么办?

现在怎么办? 那是什么?

CI/CD 代表持续集成 (CI) 和持续交付/部署 (CD)。 当我刚开始时,我很难准确理解它是什么,因为每个人都使用了诸如“开发和运营的融合”之类的模糊术语,但简单地说:

  • 持续集成:确保所有代码都合并到一个地方。 让您的团队使用 Git,您将使用 CI。
  • 持续交付:确保您的代码持续准备好交付。 这意味着快速生成产品的读取分发版本。
  • 持续部署:从持续交付中无缝获取产品并将其部署到您的服务器。

哦,我现在明白了。 这是关于让我的应用程序神奇地部署到世界!

我最喜欢的解释 CI/CD 的文章是 Atlassian 的。 这应该可以解决您的任何问题。 无论如何,回到问题。

是的,回到那个。 如何避免手动部署?

设置 CI/CD 管道以在推送到master服务器时进行部署

如果我告诉你你可以用 CI/CD 立即修复这个问题怎么办? 您可以推送到您的 GitLab 远程 ( origin ),然后将生成一台计算机以直接将您的代码推送到 Heroku。

没门!

对啊! 让我们再次跳回代码。

一个简单的部署 CI/CD 管道示意图

创建一个包含以下内容的.gitlab-ci.yml ,将toptal-pipeline为您的 Heroku 应用程序的名称:

 image: ruby:2.4 before_script: - > : "${HEROKU_EMAIL:?Please set HEROKU_EMAIL in your CI/CD config vars}" - > : "${HEROKU_AUTH_TOKEN:?Please set HEROKU_AUTH_TOKEN in your CI/CD config vars}" - curl https://cli-assets.heroku.com/install-standalone.sh | sh - | cat >~/.netrc <<EOF machine api.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN machine git.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN EOF - chmod 600 ~/.netrc - git config --global user.email "[email protected]" - git config --global user.name "CI/CD" variables: APPNAME_PRODUCTION: toptal-pipeline deploy_to_production: stage: deploy environment: name: production url: https://$APPNAME_PRODUCTION.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_PRODUCTION.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku run rails db:migrate --app $APPNAME_PRODUCTION only: - master

将其向上推,并在项目的 Pipelines 页面中观察它是否失败。 那是因为它缺少您 Heroku 帐户的身份验证密钥。 不过,解决这个问题相当简单。 首先,您需要您的 Heroku API 密钥。 从“管理帐户”页面获取它,然后在 GitLab 存储库的 CI/CD 设置中添加以下秘密变量

  • HEROKU_EMAIL :您用于登录 Heroku 的电子邮件地址
  • HEROKU_AUTH_KEY :您从 Heroku 获得的密钥

GitLab CI/CD 设置页面中的秘密变量的图像

这应该会导致在每次推送时部署一个工作的 GitLab 到 Heroku。 至于发生了什么:

  • 在推动掌握
    • Heroku CLI 在容器中安装和验证。
    • 你的代码被推送到 Heroku。
    • 在 Heroku 中捕获数据库的备份。
    • 正在运行迁移。

您已经可以看到,您不仅可以通过将所有内容自动化到git push来节省时间,而且还可以在每次部署时创建数据库备份! 如果出现任何问题,您将拥有数据库的副本以恢复。

创建暂存环境

但是等等,快速提问,您的生产特定问题会发生什么? 如果您因为开发环境与生产环境差异太大而遇到奇怪的错误怎么办? 我曾经在运行迁移时遇到过一些奇怪的 SQLite 3 和 PostgreSQL 问题。 具体细节我不知道,但这是很有可能的。

我在开发中严格使用 PostgreSQL,我从不与这样的数据库引擎不匹配,并且我会努力监控我的堆栈是否存在潜在的不兼容性。

嗯,这是乏味的工作,我为你的纪律鼓掌。 就个人而言,我太懒惰了。 但是,您能否保证所有潜在的未来开发人员、合作者或贡献者都尽职尽责?

Errrr——是的,不。 你让我到了那里。 其他人会搞砸的。 不过,你的意思是什么?

我的观点是,你需要一个登台环境。 这就像生产,但不是。 暂存环境是您排练部署到生产并及早发现所有错误的地方。 我的登台环境通常镜像生产,并且我在登台部署时转储生产数据库的副本,以确保没有讨厌的极端情况扰乱我的迁移。 使用暂存环境,您可以停止像豚鼠一样对待您的用户。

这是有道理的! 那么我该怎么做呢?

这就是有趣的地方。 我喜欢将master直接部署到staging。

等等,这不是我们现在部署生产的地方吗?

是的,但现在我们将部署到登台。

但是如果master部署到staging,我们如何部署到生产中呢?

通过使用几年前你应该做的事情:版本化我们的代码并推送 Git 标签。

Git标签? 谁使用 Git 标签?! 这听起来像是很多工作。

确实是这样,但幸运的是,我已经完成了所有这些工作,您只需转储我的代码,它就会工作。

暂存和生产部署如何工作的概述

首先,在.gitlab-ci.yml文件中添加一个关于 staging deploy 的块,我创建了一个名为toptal-pipeline-staging的新 Heroku 应用程序:

 … variables: APPNAME_PRODUCTION: toptal-pipeline APPNAME_STAGING: toptal-pipeline-staging deploy_to_staging: stage: deploy environment: name: staging url: https://$APPNAME_STAGING.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_STAGING.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku pg:backups:restore `heroku pg:backups:url --app $APPNAME_PRODUCTION` --app $APPNAME_STAGING --confirm $APPNAME_STAGING - heroku run rails db:migrate --app $APPNAME_STAGING only: - master - tags ...

然后将生产块的最后一行更改为在语义版本化的 Git 标记上运行,而不是在 master 分支上运行:

 deploy_to_production: ... only: - /^v(?'MAJOR'(?:0|(?:[1-9]\d*)))\.(?'MINOR'(?:0|(?:[1-9]\d*)))\.(?'PATCH'(?:0|(?:[1-9]\d*)))(?:-(?'prerelease'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?(?:\+(?'build'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$/ # semver pattern above is adapted from https://github.com/semver/semver.org/issues/59#issuecomment-57884619

现在运行它会失败,因为 GitLab 足够聪明,只允许“受保护”分支访问我们的秘密变量。 要添加版本标签,请转到 GitLab 项目的存储库设置页面并将v*添加到受保护的标签。

在存储库设置页面中添加到受保护标签的版本标签的图像

让我们回顾一下现在发生的事情:

  • 在推送到 master 或推送标记的提交时
    • Heroku CLI 在容器中安装和验证。
    • 你的代码被推送到 Heroku。
    • 在 Heroku 中捕获数据库生产的备份。
    • 备份将转储到您的登台环境中。
    • 迁移在暂存数据库上运行。
  • 在推送带有语义版本标签的提交时
    • Heroku CLI 在容器中安装和验证。
    • 你的代码被推送到 Heroku。
    • 在 Heroku 中捕获数据库生产的备份。
    • 迁移在生产数据库上运行。

你现在觉得强大吗? 我觉得很强大。 我记得,我第一次来这么远的时候,我打电话给我的妻子,详细解释了整个管道。 而且她连技术都没有。 我对自己印象非常深刻,你也应该如此! 干得好!

测试每一次推送

但还有更多,因为计算机无论如何都会为你做事,它也可以运行你懒得做的所有事情:测试、linting 错误,几乎任何你想做的事情,如果其中任何一个失败,他们就赢了不要继续部署。

我喜欢在我的管道中使用它,它使我的代码审查变得有趣。 如果一个合并请求通过了我所有的代码检查,它就值得被审查。

每次推送测试的图片

添加test块:

 test: stage: test variables: POSTGRES_USER: test POSTGRES_PASSSWORD: test-password POSTGRES_DB: test DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSSWORD}@postgres/${POSTGRES_DB} RAILS_ENV: test services: - postgres:alpine before_script: - curl -sL https://deb.nodesource.com/setup_8.x | bash - apt-get update -qq && apt-get install -yqq nodejs libpq-dev - curl -o- -L https://yarnpkg.com/install.sh | bash - source ~/.bashrc - yarn - gem install bundler --no-ri --no-rdoc - bundle install -j $(nproc) --path vendor - bundle exec rake db:setup RAILS_ENV=test script: - bundle exec rake spec - bundle exec rubocop

让我们回顾一下现在发生的事情:

  • 在每次推送或合并请求时
    • Ruby 和 Node 设置在一个容器中。
    • 已安装依赖项。
    • 该应用程序经过测试。
  • 在推送到 master 或推送标记的提交时,并且只有在所有测试都通过时
    • Heroku CLI 在容器中安装和验证。
    • 你的代码被推送到 Heroku。
    • 在 Heroku 中捕获数据库生产的备份。
    • 备份将转储到您的登台环境中。
    • 迁移在暂存数据库上运行。
  • 在推送带有语义版本标记的提交时,并且仅当所有测试都通过时
    • Heroku CLI 在容器中安装和验证。
    • 你的代码被推送到 Heroku。
    • 在 Heroku 中捕获数据库生产的备份。
    • 迁移在生产数据库上运行。

退后一步,惊叹于您已经完成的自动化水平。 从现在开始,您所要做的就是编写代码并推送。 如果您愿意,可以在 staging 中手动测试您的应用程序,当您有足够的信心将其推向世界时,请使用语义版本控制对其进行标记!

自动语义版本控制

是的,它很完美,但是缺少一些东西。 我不喜欢查找应用程序的最新版本并明确标记它。 这需要多个命令并分散我几秒钟的注意力。

好吧,伙计,停下! 够了。 你现在只是过度设计它。 它有效,它很棒,不要因为太过分而毁了一件好事。

好吧,我有充分的理由去做我将要做的事情。

求教,赐教。

我曾经和你一样。 我对这个设置很满意,但后来我搞砸了。 git tag按字母顺序列出标签, v0.0.11高于v0.0.2 。 我曾经不小心标记了一个版本,并继续这样做了大约六个版本,直到我看到我的错误。 那时我决定也将其自动化。

我们重新来过吧

好的,所以,幸运的是,我们拥有 npm 的强大功能,所以我找到了一个合适的包:运行yarn add --dev standard-version并将以下内容添加到您的package.json文件中:

 "scripts": { "release": "standard-version", "major": "yarn release --release-as major", "minor": "yarn release --release-as minor", "patch": "yarn release --release-as patch" },

现在您需要做最后一件事,将 Git 配置为默认推送标签。 目前,您需要运行git push --tags来上推标签,但在常规git push上自动执行此操作就像运行git config --global push.followTags true一样简单。

要使用新管道,只要您想创建发布运行:

  • 补丁发布的yarn patch
  • 次要版本的yarn minor
  • 主要版本的yarn major

如果您不确定“主要”、“次要”和“补丁”这三个词的含义,请在语义版本控制网站上阅读更多相关信息。

现在您终于完成了管道,让我们回顾一下如何使用它!

  • 写代码。
  • 提交并推送它以测试并将其部署到登台。
  • 使用yarn patch来标记补丁版本。
  • git push将其推出生产。

总结和进一步的步骤

我只是触及了 CI/CD 管道可能实现的表面。 这是一个相当简单的例子。 通过将 Heroku 替换为 Kubernetes,您可以做更多事情。 如果您决定使用 GitLab CI,请阅读 yaml 文档,因为您可以通过在部署之间缓存文件或保存工件来做更多事情!

您可以对此管道进行的另一个巨大更改是引入外部触发器来运行语义版本控制和发布。 目前,ChatOps 是他们付费计划的一部分,我希望他们将其发布为免费计划。 但想象一下能够通过单个 Slack 命令触发下一张图像!

CI/CD 部署管道图,其中生产部署在外部触发,可能通过聊天或 webhook

最终,随着您的应用程序开始变得复杂并需要系统级依赖项,您可能需要使用容器。 发生这种情况时,请查看我们的指南:Docker 入门:简化 Devops。

这个示例应用程序确实是实时的,您可以在此处找到它的源代码。