使用 AWS CDK 和 GitHub Actions 对每个 Pull Request 进行环境预览
📖 目录
👋 介绍
🎯 塔吉特
✅ 先决条件
☁️ AWS
🤖 GitHub 自动化
📸 工作流程图解
🌇 结论
TL;DR:代码在这里👉👈 。本文其余部分将提供相关信息和背景。
📖 目录
👋 介绍
如今,软件交付面临的最大挑战之一就是快速迭代。快速迭代的同时还要保证代码的高质量和可靠性并非易事。在Clovis,建立高效的反馈机制是我们的首要任务。
Vercel、Netlify、Render、Qovery和Railway等服务都在朝着这个目标努力,让我们的工作变得更加轻松。所有这些平台都让我们能够快速迭代,专注于代码本身,从而交付价值,而无需再为 DevOps 的种种麻烦而烦恼。我由衷地感谢它们。
但如果 DevOps 能像普通代码一样简单易用呢?只需几行代码就能轻松搭建整个基础设施。直接使用云服务提供商,你将不再受任何限制,而且成本更低!
举例来说,我将向您展示如何使用 AWS CDK 和 GitHub Actions 在 GitHub 的 Pull Request 中设置预览环境。预览环境是一个非常棒的工具,可以在产品上线前分享您的工作成果,或者运行端到端测试。拥有一个与生产环境镜像相同的预览环境,可以极大地增强您的信心。
🎯 塔吉特
完成本教程后,我们应该能够:
- 通过给 Pull Request 添加标签来触发预览环境部署
🚀 deploy - 只要 Pull Request 带有 `<preview-env>` 标签
🚀 deploy,每次推送时我们的预览环境都应该会更新。 - 当拉取请求中的标签
🚀 deploy被移除或拉取请求被关闭时,预览环境应该被销毁。 - 所有部署都应与 GitHub Deployments API 集成,以便在 Pull Request 或仓库主页上获得有用的反馈。
✅ 先决条件
- Node.js LTS (v14.16.0)
- AWS:
- 注册并获取您的访问密钥
- 将访问密钥设置为存储库中指定的名称下的
AWS_ACCESS_KEY_ID密钥。AWS_SECRET_ACCESS_KEY - 在您的开发机器上设置它们
us-east-1以运行各种 CDK 命令。如果您想部署到与当前区域不同的区域,请不要忘记设置默认区域(之前的链接) 。
☁️ AWS
建筑学
该架构由位于私有S3 存储桶(文件存储)前端的CloudFront (CDN)组成。CloudFront 将存储在存储桶中的资源提供给外部网络。
CDK
AWS Cloud Development K是一个开源软件开发框架,用于使用熟悉的编程语言(例如本文中的 TypeScript)来定义云应用程序资源。
TypeScript 为我们基础设施的每个部分提供了自动补全和类型安全功能,无需再反复查阅文档,只需按几下键即可获得所有功能。
您可以告别数百行 CloudFormation YAML 文件或令人困惑的 AWS 控制台用户界面。作为基础设施即代码框架,CDK 对这些底层结构进行了抽象,并提供了合理的默认值。
文件结构
一个典型的 CDK 项目由以下部分组成:
bin/app.ts主文件用于实例化堆栈,并由 CDK CLI 调用。lib/*-stack.ts栈的实现cdk.jsonCDK CLI 的配置文件
我们来看看每个文件里都有些什么内容。
cdk.json
{
"app": "yarn ts-node bin/app.ts",
"context": {
"@aws-cdk/core:newStyleStackSynthesis": true
}
}
该app密钥用于告诉 CDK 如何启动我们的应用程序。
密钥context可以包含类似此处使用的功能标志。在未来的 AWS CDK 版本中,“新式”堆栈合成将成为默认设置,但目前我们需要通过功能标志来启用它。
bin/app.ts
import * as cdk from '@aws-cdk/core';
import AwesomeStack from '../lib/awesome-stack';
const app = new cdk.App();
/**
* The name of the stack depends on the STAGE environment variable so we can deploy the infrastructure multiple times in parallel
* @example
* AwesomeStack-pr-1-awesome-branch
* AwesomeStack-production
*/
const stackName = 'AwesomeStack-' + process.env.STAGE;
new AwesomeStack(app, stackName);
堆栈名称是一个重要的概念。在使用 CDK CLI 进行部署时,我们将参考这个名称。需要注意的是,在我们的例子中,这个名称取决于 STAGE 环境变量。这使得我们可以通过 Pull Request 动态部署一个全新的堆栈,因为 STAGE 变量是由 Pull Request 编号和分支名称(稍后会详细介绍)生成的!
lib/awesome-stack.ts
import * as cloudfront from "@aws-cdk/aws-cloudfront";
import * as cloudfrontOrigins from "@aws-cdk/aws-cloudfront-origins";
import * as s3 from "@aws-cdk/aws-s3";
import * as s3Deployment from "@aws-cdk/aws-s3-deployment";
import * as cdk from "@aws-cdk/core";
/**
* The CloudFormation stack holding all our resources
*/
export default class AwesomeStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
/**
* The S3 Bucket hosting our build
*/
const bucket = new s3.Bucket(this, "Bucket", {
autoDeleteObjects: true,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
/**
* The CloudFront distribution caching and proxying our requests to our bucket
*/
const distribution = new cloudfront.Distribution(this, "Distribution", {
defaultBehavior: {
origin: new cloudfrontOrigins.S3Origin(bucket),
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
defaultRootObject: "index.html",
});
/**
* Output the distribution's url so we can pass it to external systems
*/
new cdk.CfnOutput(this, "DeploymentUrl", {
value: "https://" + distribution.distributionDomainName
});
/**
* Upload our build to the bucket and invalidate the distribution's cache
*/
new s3Deployment.BucketDeployment(this, "BucketDeployment", {
destinationBucket: bucket,
distribution,
distributionPaths: ["/", "/index.html"],
sources: [s3Deployment.Source.asset('./website')],
});
}
}
堆栈类是声明部署期间将创建的所有资源的单元。要创建堆栈,您需要扩展该类cdk.Stack。
该removalPolicy功能通常适用于有状态资源,例如 S3 存储桶或 RDS 数据库。默认情况下,为了防止用户操作失误,即使销毁环境后也会保留数据。但在我们的例子中,我们需要清理所有数据,因此必须明确告诉 AWS 销毁DESTROY我们的存储桶。
本文开头我提到 CDK 提供了合理的默认设置。一般而言,你创建的每个资源默认都是私有的。这意味着,如果你想公开它们,就必须显式地进行设置。从安全角度来看,这一点非常重要。我们这里存储桶中的对象无法公开访问,只有 CloudFront 可以读取它们。
cdk.CfnOutput这是一个允许你在部署结束时导出任意值的结构。将已部署的 URL 传递给 GitHub Deployment API 会非常有用。
这就是我们的基础设施,仅用了30行代码就搭建完成。不错吧!
命令行界面
现在基础架构已经搭建完毕,让我们来看看如何与 CDK CLI 进行交互。我在文件中定义了 3 个主要命令作为脚本package.json。
纱线引导
CDK_NEW_BOOTSTRAP=1 cdk bootstrap --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess
将 AWS CDK 应用部署到 AWS 环境(AWS 账户和区域的组合)可能需要您预置 AWS CDK 执行部署所需的资源。这些资源包括用于存储文件的 Amazon S3 存储桶以及授予执行部署所需权限的 IAM 角色。预置这些初始资源的过程称为引导。更多信息请点击此处。
基本上,对于要部署到的每个帐户/区域,您只需手动调用此命令一次即可。
yarn deploy
cdk deploy "AwesomeStack-${STAGE}" --require-approval never --outputs-file cdk.out.json
这条命令会读取cdk.json配置文件并触发资源部署。这里我指定了它不是交互式的(我接受所有资源/角色的创建)。此外,我还将所有导出的值保存cdk.CfnOutput到一个 JSON 文件中。
如果多次运行该命令,CloudFormation 会自动比较更改并相应地更新资源!
您可以注意到,运行此命令需要设置 STAGE 环境变量。此外,我目前只针对单个堆栈,但cdk deploy如果您需要部署多个堆栈,也接受通配符(glob)。
纱线破坏
cdk destroy "AwesomeStack-${STAGE}" --force
以下是在 AWS 上销毁堆栈的操作,之所以--force选择非交互式操作,是因为我们将在 Github Actions 中调用这些命令。
本文的基础设施部分到此结束,希望您还能继续关注自动化部分!
🤖 GitHub 自动化
GitHub 部署 API
GitHub 在代码仓库部署时提供了专门的用户界面和状态集成。这项功能集成在其REST API中,而且我并没有注意到很多人使用这项功能。
我觉得使用它有很多好处:
- 在 Pull Request 中直接收到关于当前部署状态的良好反馈。
- 从仓库主页查看所有活跃部署。
- 自动与Checkly等第三方服务进行通信,这些服务需要有关部署的信息才能启动自己的工作流程。
GitHub 将环境的概念用于按顺序将部署分组在一起。在我们的案例中,每个 Pull Request 都会创建一个环境。
GitHub Actions
GitHub Actions 让您轻松实现所有软件工作流程的自动化,现在更配备了世界一流的 CI/CD 功能。直接在 GitHub 上构建、测试和部署您的代码。
GitHub Actions 的一大优势在于其生态系统和市场。作为一名经常使用 npm 开源软件包的用户,我感觉就像回到了家一样,总能找到满足我需求的软件包!
为了实现我们的自动化目标,我们需要执行 2 个操作。一个用于在 Pull Request 的生命周期内部署堆栈,另一个用于在 Pull Request 关闭时清理所有内容。
拉取请求部署
第一个操作会在两种情况下触发:
🚀 deploy当你在拉取请求中添加标签时- 当您打开或推送带有标签的 Pull Request 时
🚀 deploy,pushPull Request 上的事件是该synchronized事件的一部分。
首次部署会创建环境所需的所有资源,通常速度最慢。后续部署速度会快得多,因为 CloudFormation 会比较变更并仅更新所需的资源。
为了确保每个 Pull Request 都拥有一个独立的环境,我们需要根据 Pull Request 编号和分支名称来推导出 STAGE 环境变量,从而使生成的堆栈名称在 AWS 上对我们的 Pull Request 是唯一的。为此,我专门设计了一个步骤。
还有一个我之前没提到的 npm 脚本,它可以简化部署 URL 在不同步骤之间的传递deploy the stack on AWS,update the github deployment status该postdeploy脚本如下:
node --eval "console.log('::set-output name=env_url::' + require('./cdk.out.json')['AwesomeStack-${STAGE}'].DeploymentUrl)"
它会读取部署结束后导出DeploymentUrl的数据,并将该值设置为步骤的输出,以便后续步骤可以访问。由于前置脚本和后置脚本的约定, 该脚本会在后续脚本之后自动调用。cdk.CfnOutputcdk.out.jsonpostdeploydeploy
社区中一些对工作流程非常有帮助的显著举措:
rlespinasse/github-slug-action它会在工作流中暴露一些 GitHub 环境变量的别名/短版本值。这让我可以直接获取分支名称的别名版本,从而构建 STAGE 环境变量。(refs/heads/feat/new_feature -> feat-new-feature)aws-actions/configure-aws-credentials配置 AWS 凭证和区域环境变量,供其他 GitHub Actions 使用。所有 AWS 工具都会检测这些环境变量,以确定 AWS API 调用要使用的凭证和区域。bobheadxi/deployments它对 Github Deployments API 进行了抽象,使得创建/更新/删除 Github 部署变得轻而易举。
name: "Pull Request deploy"
on:
pull_request:
types: [labeled, opened, synchronize]
jobs:
deploy:
if: |
(github.event.action == 'labeled' && github.event.label.name == ':rocket: deploy') ||
(github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, ':rocket: deploy'))
runs-on: ubuntu-latest
steps:
- name: inject slug/short variables
uses: rlespinasse/github-slug-action@v3.x
- name: set STAGE variable in environment for next steps
run: echo "STAGE=pr-${{ github.event.number }}-${{ env.GITHUB_HEAD_REF_SLUG }}" >> $GITHUB_ENV
- name: create a github deployment
uses: bobheadxi/deployments@v0.5.2
id: deployment
with:
step: start
token: ${{ secrets.GITHUB_TOKEN }}
env: ${{ env.STAGE }}
ref: ${{ github.head_ref }}
no_override: false
transient: true
- name: checkout the files
uses: actions/checkout@v2
- name: install node dependencies
uses: bahmutov/npm-install@v1
- name: configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-west-3
- name: deploy the stack on AWS
id: cdk_deploy
run: yarn deploy
- name: update the github deployment status
uses: bobheadxi/deployments@v0.5.2
if: always()
with:
step: finish
token: ${{ secrets.GITHUB_TOKEN }}
status: ${{ job.status }}
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
env_url: ${{ steps.cdk_deploy.outputs.env_url }}
拉取请求清理
清理操作会在两种情况下触发:
🚀 deploy当你从拉取请求中移除标签时🚀 deploy当您关闭带有.标记的 Pull Request时,该closed事件也会在 Pull Request 合并后发出,因为它会被 Github 自动关闭。
此操作可确保您的预览环境在您不再需要时立即在 AWS 上被销毁。它还会删除关联的 GitHub 部署,因此您将不再在用户界面中看到它们。
社区采取的显著行动对此次工作流程非常有帮助:
strumwolf/delete-deployment-environment查找并删除所有部署及其所在的 GitHub 环境。如果 GitHub 环境包含任何非inactive部署,则无法删除该环境。此操作会将所有部署标记为非活动状态并删除整个部署链。
name: "Pull Request clean-up"
on:
pull_request:
types: [unlabeled, closed]
jobs:
clean-up:
if: |
(github.event.action == 'unlabeled' && github.event.label.name == ':rocket: deploy') ||
(github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, ':rocket: deploy'))
runs-on: ubuntu-latest
steps:
- name: inject slug/short variables
uses: rlespinasse/github-slug-action@v3.x
- name: set STAGE variable in environment for next steps
run: echo "STAGE=pr-${{ github.event.number }}-${{ env.GITHUB_HEAD_REF_SLUG }}" >> $GITHUB_ENV
- name: checkout the files
uses: actions/checkout@v2
# there is a bug with the actions/cache used in bahmutov/npm-install@v1 on "closed" event
# more infos here : https://github.com/actions/cache/issues/478
- name: install node dependencies
run: yarn --frozen-lockfile
- name: configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-west-3
- name: destroy the stack on AWS
run: yarn destroy
- name: delete the github deployments and the corresponding environment
uses: strumwolf/delete-deployment-environment@v1.1.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
environment: ${{ env.STAGE }}
📸 工作流程图解
🚀 deploy已添加到 Pull Request 中,即将启动
点击View deployment链接就来到了这里,真有趣。
🌇 结论
现在你已经拥有了构建自己工作流程的所有要素,是不是很棒?!云端才是极限!
以下是一些想法(或许也会成为未来的文章主题):
https://pr-1-my-awesome-branch.jgoux.dev使用您自己的自定义域名,即可拥有类似AWS Route53 的美观预览 URL 。- 实现全栈无服务器架构,在堆栈中添加 Lambda 函数和 RDS 数据库,并保持相同的预览和隔离保证!
- 根据应用程序代码动态生成基础架构
- 通过 CDK 加快部署速度,并将堆栈拆分为始终运行的共享堆栈
StableStack和临时堆栈来优化成本DynamicStack-${STAGE}。
我想借此机会感谢所有抽出时间在 Twitter、Github issues、Discord、Zoom、Slack 以及最近的AWS CDK Slack 社区回答我众多问题的各位。
我没办法一一列举,我问的问题太多了😂,但我特别要感谢Thorsten Hoeger和Kenneth Winner帮我校对了这篇文章。能身处这样一个充满才华横溢、思想开放、友善的人们的社群,我感到非常荣幸。
这是我的第一篇帖子,多年来我从开源软件中受益匪浅,我想回馈社区。❤️
如果你想和我聊任何事,欢迎随时在推特上联系我🐦,我的私信永远开放!
文章来源:https://dev.to/jgoux/preview-environments-per-pull-request-using-aws-cdk-and-github-actions-bfi




