github ci
最近,我们启动了一个新项目,并决定是时候尝试使用GitHub的最新CI / CD工具,该工具于去年11月开始普遍提供 。
我们在GitHub动作方面的经验基本上是积极的,因此我想分享一下我们如何在生产中使用它。
本文是两部分的第一部分-每个工作流程均撰写一篇文章。
- 集成 :在每次推送时测试应用程序,然后将应用程序映像发布到容器注册表。
- 部署 :重复集成步骤,但还要通过自定义操作验证发布标签,然后将应用程序映像部署到Kubernetes集群 。
因此,让我们从持续集成过程开始:
工作流程概述
我们的持续集成工作流程有两项工作:
- 使用Flake8整理代码,然后使用Pytest测试应用程序。
- 构建一个Docker映像并将其发布到Google的容器注册表。
项目概况
如果您了解我们项目的设置方式,那么这些说明将更有意义。
这是一家大型房地产科技创业公司的网络应用。 它由四个相互交互的子应用程序以及几个外部服务组成。
平台架构的基本概述
除了前端之外,所有应用都需要与Google Pub / Sub消息队列进行交互(这说明了为什么我们在工作流程中使用Google Pub / Sub模拟器)。
例如,当某人在前端更改客户联系详细信息时,将触发以下事件:
- 后端数据库将立即更新。
- 在后台,一条消息放置在中间件的队列中-我们自己的开源项目 “ Herbie”。
- Herbie从队列中拉出消息,并根据存储的数据模式(在这种情况下,对于业务实体“客户”)验证消息。
- 然后,Herbie将消息转换为Salesforce可以理解的格式,并将其放入Salesforce推送工作者的队列中。
- Salesforce推送工作者从队列中提取消息,并将更改推送到Salesforce。
这样,Web应用程序和Salesforce中的客户详细信息始终是最新的,这使销售人员感到满意。
在下一节中,我将描述如何为后端应用程序设置CI。 但是,每个应用程序的工作流程基本上遵循相同的结构。
要继续进行操作,您首先需要创建一个工作流文件。
创建工作流程文件
这在GitHub的用户界面中很容易做到。
在您的仓库中,转到“操作”选项卡,然后单击“ 新建工作流” 。 在以下屏幕中,单击“自行设置工作流程”。 这将创建一个带注释的工作流文件,然后我们可以对其进行编辑。
定义触发事件
我们希望工作流能够在有人推送到任何分支时发生。 因此,我们如下更新了工作流程文件。
name: CI on: push: branches: - '**'
有关触发器语法的更多信息,请参阅GitHub的事件官方文档 。
定义工作
每次推送之后,我们想要测试应用程序,对其进行快照,并将其作为Docker映像存档在我们的容器注册表中(以防万一我们需要回滚到应用程序的早期迭代)。
为此,我们定义了两个作业:
- 职位1 :运行测试
- 职位2 :构建并发布Docker映像
如果第一个作业失败,则无需运行第二个作业。 我们只想存档该应用程序的工作版本。
作业1:运行测试
如前所述,我们希望使用Flake8编写代码,并使用Pytest测试应用程序-但是我们需要设置环境并首先安装所有依赖项。
定义环境和服务容器
要运行测试,我们将大多数依赖项直接安装在虚拟机上。
但是,如果要直接安装某些组件,它们的设置将过于复杂,例如:
- 职位1 :运行测试
- 数据库 :后端使用PostgreSQL数据库。
- 发布/订阅模拟器 :这是Google在本地模拟其发布/订阅消息服务的工具。
- 通常,后端会推送到运行其他应用程序(Herbie)的队列,但是我们不需要真正的队列进行测试。
- 我们只想测试后端是否与队列正确交互。
对于这些组件,我们可以改为使用Docker容器。 我们使用以下语法将它们定义为“ Service Containers ”:
jobs: tests: # Job ID name: Tests # Name of the job displayed on GitHub. runs-on: ubuntu-latest # OS for the GitHub-hosted virtual machine services: # Define Docker containers to use for this job. backend-db: # Container ID for our database container image: postgres:12.1 # Image to pull from Docker Hub env: POSTGRES_USER: user POSTGRES_PASSWORD: password POSTGRES_DB: backend_db ports: - 5432 :5432 # TCP port to expose on Docker container and host environment backend-gcloud-pubsub: # Container ID for our Pub/Sub container image: 'knarz/pubsub-emulator' # Image to pull from Docker Hub ports: - '8085:8085' # TCP port to expose on Docker container and host environment
触发工作流程后,GithHub从Docker Hub提取以下图像:
- “ backend-db”容器的官方Postgres图像
- Pub / Sub模拟器图像,它是Googles Pub / Sub模拟器的容器化版本。
定义步骤和行动
接下来,我们开始定义工作步骤。 许多步骤都使用GitHub所谓的“动作”,这些动作本质上是执行一个特定任务的小型预定义脚本。 GitHub提供了自己的内置动作,其他用户提供了动作的市场。 您还可以构建自己的操作-我将在有关持续部署工作流的配套文章中描述我们如何做到这一点。
但是对于本指南,我们仅使用来自GitHub市场的操作。
1)检出工作分支并设置SSH凭据
我们需要在虚拟机上签出工作分支,但是我们没有使用git checkout,而是使用了内置的GitHub checkout动作 。
接下来,我们在虚拟机上设置ssh凭据。 在此步骤中,我们从秘密中获取SSH密钥。
steps: - name: Checkout working branch uses: actions/checkout@v1 - name: Setup ssh run: | mkdir ~/.ssh/ echo "$ {{ secrets.SSH_PRIVATE_KEY }} " > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa touch ~/.ssh/known_hosts ssh-keyscan github.com >> ~/.ssh/known_hosts
机密是已加密的环境变量,仅对选定的操作公开。 您可以在存储库设置中创建这些机密。 在本例中,我们正在为GitHub机器用户使用私钥。
我们需要一个SSH密钥,以便计算机用户可以通过Github进行身份验证并将某些其他私有存储库克隆到虚拟机。
2)设置Python并安装PostgreSQL客户端
接下来,我们使用另一个内置操作来设置所需的Python版本。 然后,我们安装PostgreSQL客户端。 我们的数据库在docker容器中运行,但是我们需要一个客户端来访问它。
- name: Set up Python 3.8 uses: actions/setup-python@v1 with: python-version: 3.8 - name: Install PostgreSQL 11 client run: | sudo apt-get -yqq install libpq-dev
3)设置缓存
我们缓存了先前运行中已安装的依赖项,因此我们不必继续重新安装它们。
我们使用内置的cache action设置了缓存。
它检查定义了依赖项的任何文件(例如Python的“ requirements.txt”)以查看其是否已更新。 如果没有,它将从缓存中加载依赖项。
但是,我们不使用“ requirements.txt”。 但是,我们不使用“ requirements.txt”。 相反,我们使用一个叫做Poetry的工具来管理Python依赖项。 使用此工具,您可以在一个名为“ pyproject.toml”的文件中定义依赖项,我们已将其提交给我们的仓库。 当您初始化或更新Poetry项目时,Poetry会根据.toml文件的内容自动生成一个“ poetry.lock”文件。
因此,我们将缓存操作配置为检查poetry.lock文件的更改。 它通过比较锁定文件的缓存版本和传入版本的文件哈希来完成此操作。
- name: Cache Poetry uses: actions/cache@v1 id: cache with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/poetry.lock') }} restore-keys: | $ {{ runner.os }} -pip-
4)安装依赖项
接下来,我们升级pip并安装Poetry。
一旦安装了Poetry,我们将使用它来安装poetry.lock文件中列出的依赖项。 这些依赖项包括在以下步骤中将使用的整理和测试工具。
- name: Install dependencies, config poetry virtualenv run: | python -m pip install --upgrade pip pip install poetry poetry config virtualenvs.create false poetry install --no-interaction
5)整理代码
接下来,我们对Python代码进行整理,以确保其遵循我们的样式指南。 为此,我们使用在此处运行的Flake8工具 。
- name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
6)运行测试
我们将测试本地化的任何问题,因此我们需要在准备步骤中安装的GNU gettext实用程序 。 在主要测试步骤中,我们首先编译本地化消息,然后使用pytest运行我们的测试。
我们为pytest提供了一堆环境变量,这些环境变量大多具有伪值。 并非所有变量都在测试中使用,但是在构建用于测试的应用程序容器时需要将它们存在。
- name: Install gettext for translations run: | sudo apt-get update && sudo apt-get install -y gettext - name: Test with pytest run: | python manage.py compilemessages pytest --verbose env: CLUSTER_ENV: test RUN_DEV: 0 POSTGRES_DB: backend_db POSTGRES_USER: user POSTGRES_PASSWORD: password POSTGRES_DB_PORT: 5432 POSTGRES_DB_HOST: localhost SECRET_KEY: thisisasecret SENDGRID_API_KEY: 'test-key' PUBSUB_EMULATOR_HOST: localhost:8085 GCLOUD_PUBSUB_PROJECT_ID: 'test-project' HERBIE_TOKEN: 'random-key' HERBIE_HOST: 'http://localhost:8000'
好的,就这些用于测试。 如果您想完整地查看工作,请查看我们工作流程文件的要点 。 如果测试全部通过,我们将运行下一个作业。
作业2:构建Docker映像并将其发布到注册表
假设测试已通过,我们将应用程序映像构建并存档在私有容器注册表中。 在我们的例子中,它是一个Google Container Registry。
定义运行条件
与上次一样,我们将作业ID设置为“ docker-image”,为其命名并定义主机操作系统。
我们还使用“ 需求 ”语法指定需要上一个作业返回成功错误代码。
docker-image: name: Build & Publish Docker Image needs: [tests] runs-on: ubuntu-latest
定义工作步骤
1)检出分支并设置环境变量
第一步是我在第一份工作中描述的“签出分支”操作。 我们必须再次执行此操作,因为数据不会在作业之间持久化(除非您明确配置“ 作业工件 ”)。
签出步骤之后,我们定义特定的环境变量:
- Google容器注册URL-在这里我们设置“ eu.gcr.io ”,它用于托管在欧盟的图片。
- 使用注册表URL作为前缀的Docker映像名称,在这种情况下,解析为“ eu.gcr.io/acme-555555/backend ”(路径的中间部分是Google客户名称和客户ID)。
steps: - name: Checkout working branch uses: actions/checkout@v1 - name: Set Docker Registry run: echo ::set-env name=DOCKER_REGISTRY::eu.gcr.io - name: Set Docker Image run: echo ::set-env name=DOCKER_IMAGE::${{ env.DOCKER_REGISTRY }}/acme-555555/backend
2)登录到Google Container Registry,然后构建并推送图像
对于此任务,我们使用两个用户提供的操作来登录到容器注册表 , 并将Docker映像推送到注册表 。 GitHub的虚拟机包括许多预装的软件 ,例如Google Cloud SDK,我们的“登录”和“推送”操作使用了该软件。要登录Google Cloud,我们使用另一个机密-“' secrets.GCLOUD_KEY ”,这是我们Google Cloud项目的服务帐户密钥。 同样,这是在存储库设置中秘密定义的 。
登录步骤完成后,GitHub动作将输出容器注册表的用户名和密码-然后我们将其用于“构建并推送”步骤。
- name: Login to gcloud registry id: gcloud uses: elgohr/gcloud-login-action@0.2 with: account_key: ${{ secrets.GCLOUD_KEY }} - name: Publish Docker Image uses: elgohr/Publish-Docker-Github-Action@2.14 env: SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} with: name: ${{ env.DOCKER_IMAGE }} username: ${{ steps.gcloud.outputs.username }} password: ${{ steps.gcloud.outputs.password }} registry: ${{ env.DOCKER_REGISTRY }} buildargs: SSH_PRIVATE_KEY
- 密码“ secrets.SSH_PRIVATE_KEY ”是我们计算机用户的SSH密钥,然后将其作为Docker映像的构建参数传入。 这使容器能够执行需要SSH凭据的操作,例如克隆私有git存储库(我们需要对几个依赖项进行此操作)。
- 用户名和密码参数来自使用输出机制的上一步。
默认情况下,“发布”操作标记分支的Docker映像名称。
每个分支都以JIRA票证编号为前缀,以便JIRA可以自动检测到它并将其链接到票证。 在Docker注册表中,我们的图像如下所示:
名称: 8cd6851d850b 标签: XYZ-123_add_special_field_todo_magic
我之所以仅提及这一点,是因为该操作提供了标记图像的其他方法。 在有关部署工作流的文章中,我将向您展示另一种方法。
至此,工作流程结束了。 如果您想完整地查看工作流程文件,请查看此要点 。
关于容器存放的注意事项
在不远的将来的某一天,我们的容器注册表将变得非常庞大。
如果我们在每次推送时都存储333 MB的映像,则在三次推送后我们最多可以达到1 GB。 每月每GB的集装箱存储费用为$ 0.026,因此这笔钱不会破钱。 但是,您正在使用另一个容器注册表,您可能想要管理存储容量并清理较旧的映像。
幸运的是,有一些脚本可以执行此操作,例如本脚本。
摘要
如您所见,工作流非常容易设置。 另外,它们不会花费任何额外费用。 我们拥有GitHub Enterprise许可证,但所有其他GitHub产品也可以使用它们。 请注意,操作消耗的分钟数有限制,并且这些限制根据您的GitHub许可证而有所不同。
显然,您的存储库必须在GitHub上,并且无法在本地测试工作流程-它们必须在GitHub的服务器上运行。 但是您可以做的是使用自己的虚拟机来运行特定作业。
对于未来的项目,我们可能会坚持使用GitHub CI / CD工作流程,因为我们发现它们比Jenkins或Travis等其他工具更易于使用。
如果您认为本演练很有用,请查看有关我们的部署工作流的配套文章。
关于作者和项目A的披露说明Merlin在Project A上发布了有关开发人员创新和新技术的博客,这是一家专注于早期创业公司的风险投资机构。 项目A向其投资组合公司提供运营支持,包括开发人员专业知识。 作为IT团队的一员,他介绍了Project A的工作重点,帮助初创公司发展成为蒸蒸日上的成功案例。
翻译自: https://hackernoon.com/how-we-set-up-a-production-ready-ci-workflow-using-github-actions-ca2n3w1j
github ci
- 通常,后端会推送到运行其他应用程序(Herbie)的队列,但是我们不需要真正的队列进行测试。