你好!欢迎!!在这个项目中,我们将使用 Terraform 创建一些简单的基础设施,同时为生产环境协作实现 Terraform 的核心工作流(即编写、计划和应用)。简而言之,我们将以一种允许工程师团队引入系统更改、进行详细审查、批准或拒绝,并(如果获得批准)部署到生产环境的方式,使用 Terraform 部署基础设施。
我们在本项目中构建的系统将适用于一些中小型组织的运营(无论是否可能添加我们将在项目结尾讨论的更多内容)。
出于本项目的目的,你是一名基础设施工程师,负责为一家中型公司创建初始基础设施。我们将从头开始创建基础设施,全程使用版本控制 (Git),通过工作站上的本地后端 (Local Backend) 引导我们的系统,将其迁移到远程后端 (Remote Backend) 以实现协作,并创建脚本来自动化整个持续集成 (CI) 和持续部署 (CD) 流程。
我们将:
- 在 DigitalOcean 和 Cloudflare 上使用 Terraform 创建极简的基础设施
- 配置 Google Cloud Storage 远程后端以支持协作
- 通过 Github Environments 和 Actions 工作流配置对 GCS 后端的访问权限
- 创建 Actions 工作流脚本以初始化、验证、预览和应用我们的基础设施更改。
- 利用 Github Pull Requests 和 Github Bot 来审查(并批准或拒绝)提议的基础设施更改。
这是 Github 上的项目仓库:https://github.com/obiMadu/terraform-github-workflow
让我们直接开始吧 🚀
1. 通过 Terraform 创建我们的基础设施
我们将创建如上图所示的非常简单的基础设施。这是一个在 Digital Ocean 上运行 Nginx 的简单 Web 服务器,拥有一个 IPv4 地址,并且在 Cloudflare 区域中有一个 DNS A record 将一个子域名指向它。我们将仅通过创建两个 Terraform 资源来实现这一点。
iTip+
你可以轻松地将 Cloudflare 替换为你选择的任何其他域名服务。例如,如果你通过 Amazon Route 53 管理域名,则无需为了本教程切换到 Cloudflare。只需创建必要的适当 Terraform 配置,将我们服务器实例的 IPv4 地址映射到域名(它也不一定必须是子域名,你可以映射一个顶级域名,随你喜欢)。
iTip+
不出所料(很可能),你也不需要在 Digital Ocean 上创建服务器,你可以将其部署到 AWS 或你选择的任何其他提供商,只要我们获得一个安装了 Nginx 并具有 IPv4 地址的服务器即可。
iTip+
为了让事情更有趣,你甚至可以配置一个基础服务器,并使用 Ansible 这样的工具来将 Nginx 或任何其他 Web 应用部署到它上面。你也可以使用 Packer 这样的工具构建自己的自定义镜像并使用它。由你决定。本项目旨在快速且易于跟进。
1.1 创建 providers.tf
接下来,我们将创建一个 providers.tf 文件来配置我们不同的提供商 (providers)。提供商的版本限制被设置为撰写本文时的最新版本。
terraform {
required_providers {
digitalocean = {
source = "digitalocean/digitalocean"
version = "2.34.1"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "4.24.0"
}
}
}
# Digital Ocean API credentials
provider "digitalocean" {
token = var.do_token
}
provider "cloudflare" {
api_token = var.cloudflare_api_token
}以上是不同提供商正常运行所需的最绝对的最小参数。创建此文件后,我们将在我们的项目目录中初始化 Git,并通过以下命令将此文件作为我们的第一次 commit;
git init .
git add .
git commit -m "Initial commit"iTip+
我们将参数值变成了变量,以防止将这些敏感值硬编码到我们的配置文件中。还要记住,你可以将 Git commit 消息自定义为你喜欢的任何内容。
1.2 创建 variables.tf
由于我们已将变量作为值分配给我们提供商配置中的参数,因此是时候声明这些变量了。我们在一个名为 variables.tf 的文件中执行此操作,内容如下。
variable "do_token" {
type = string
}
variable "do_region" {
type = string
}
variable "cloudflare_api_token" {
type = string
}
variable cloudflare_zone_id" {
type = string
}现在我们可以继续并将这个新文件 commit 到 Git,并附上适当的 commit 消息。
1.3 初始化 Terraform
现在我们有了基础配置,是时候在我们的项目目录中运行 terraform init 来初始化 Terraform 了。
你的成功输出应该类似于上面的图片。
现在我们必须再做两件事;
- 在我们的项目中添加一个适用于 Terraform 的
.gitignore文件 - 为我们已配置 Terraform 去使用的不同变量提供值。
我们可以从以下地址获取适用于 Terraform 的 .gitignore 文件:https://github.com/github/gitignore/blob/main/Terraform.gitignoretext
iTip+
现在是时候继续并从各自的平台获取我们的提供商所需的不同 API 密钥了。我们将需要;
- 来自 Digital Ocean 具有
write访问权限的 API Token,以及一个区域短代码 (region shortcode)(用于指定我们希望在哪个区域部署我们的资源) - 来自 Cloudflare 的 API Token,以及我们希望在其中创建 DNS 记录的域名的 Zone ID。
这里将不介绍具体如何获取这些凭据(你应该能够获取它们)。
iTip+
为了简单起见,你可以使用 fra1 DigitalOcean 区域。这对应于法兰克福数据中心。
iTip+
现在是时候为 Terraform 提供我们获取的值了。简单起见,我们将创建一个 terraform.tfvars 文件并输入我们的变量值。我们的文件应如下所示;
do_token = "value"
do_region = "fra1"
cloudflare_api_token = "value"
cloudflare_zone_id = "value"iWarning+
记住:你绝不能将 terraform.tfvars 文件 commit 到 Git。此文件包含绝不能与任何人共享的重要应用机密。如果威胁行为者获得了对这些机密的访问权限,我们的整个基础设施就有被攻破的风险。此文件的唯一目的是供本地使用。当我们进入 CI/CD 环境时,我们将采用更安全的方法向 Terraform 提供这些值。
1.4 创建资源
现在是时候实际为我们的基础设施创建资源了。我们将创建两个文件:一个 servers.tf 文件和一个 dns.tf 文件。
- 第一个文件
servers.tf,我们将填充资源配置以使用官方 DigitalOcean Nginx 镜像创建我们的 DigitalOcean 服务器。如下所示(我们将创建一个digitalocean_droplet资源);
#create server with nginx image
resource "digitalocean_droplet" "server" {
name = "gate"
size = "s-1vcpu-1gb"
image = "nginx"
region = var.do_region
}- 第二个文件
dns.tf,我们将按如下进行配置(我们正在创建一个cloudflare_record资源);
# Add a record to the domain
resource "cloudflare_record" "server" {
zone_id = var.cloudflare_zone_id
name = "server"
value = digitalocean_droplet.server.ipv4_address
type = "A"
ttl = 1
proxied = true
}现在保存这两个文件并将它们用适当的 commit 消息 commit 到 Git。
此时,你的目录树应完全如下所示;
2. 创建并初始化远程后端
是时候为我们的项目添加一个远程后端了。现在,因为我是一名 Google Cloud 工程师,所以我将在这个项目中使用 gcs 远程后端。你可以将其切换为您选择的任何其他远程后端,例如 s3 后端。
iTip+
所以我继续创建了一个名为 terraform-github-workflow 的 Google Cloud Storage Bucket。我以相同的名称创建了一个服务账号 (service account),并在 GCS bucket 上为该主体分配了 Storage Object Admin 权限。这将允许服务账号在 terraform-github-workflow bucket 中创建和管理对象。接下来,我为服务账号主体创建了一个 JSON service account key,我下载了它并将其存储在本地工作站中一个安全的位置。
最后一步是将 GOOGLE_APPLICATION_CREDENTIALS 环境变量设置为下载到我 PC 上的 JSON 密钥的绝对路径,如下所示;
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/json/key/fileiWarning+
为了安全起见,你总是可以创建一个不同的 service account key,以便在我们进入 CI/CD 环境时使用。
最后,是时候运行 terraform init 来初始化这个新后端了。根据你选择的后端,你应该会看到与下面非常相似的成功消息;
3. 创建 Github Actions 工作流
现在我们的 Terraform 远程后端已初始化完毕,是时候为我们的 CI/CD 管道创建 Github Actions 脚本了。我们将创建总共 3 个不同的工作流,如下所示;
- 一个
validate工作流,它在我们项目的plan分支上的每次 push 时触发。这将使用terraform validate命令验证对我们代码库的任何新更改。 - 一个
plan工作流,它在每次对项目的main分支提出 Pull request 时触发。此工作流将对即将进行的基础设施更改进行空运行 (dry-run),并使用 Github Bot 将这些更改作为该 Pull request 上的评论发布。 - 一个
deploy工作流,它在每次向main分支 push/merge 时触发。这会部署我们批准的基础设施更改。
3.1 创建 workflows/validate.yml
我们将首先在项目目录的根目录中创建一个 .github/workflows 文件夹。在 workflows 目录中,我们将创建一个 validate.yml 文件。此时我们的目录树应如下所示;
以下是 validate 工作流文件的内容;
name: Validate
on:
push:
branches: [plan]
env:
GOOGLE_APPLICATION_CREDENTIALS: ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true # Fetch submodules (true OR recursive)
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_wrapper: true
- name: Setup Backend Credentials
id: backend
run: echo '${{ secrets.GCS_KEY }}' > ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}
- name: Terraform Init
id: init
run: terraform init
- name: Terraform Validate
id: terraform
run: terraform validate -no-color这个工作流:
- 在每次推送到
plan分支时触发 - 运行在
latest Ubuntu镜像上 - 第一步 checkout 我们的代码
- 然后设置 Terraform
- 设置
gcs后端凭据 - 运行
terraform init& - 在我们的代码库上运行
terraform validate
现在让我们将此文件 commit 到 Git 并继续前进。
3.2 创建 workflows/deploy.yml
接下来,我们将创建包含以下内容的 deploy 工作流;
name: Deploy
on:
push:
branches: [main]
env:
GOOGLE_APPLICATION_CREDENTIALS: ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true # Fetch submodules (true OR recursive)
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Setup Backend Credentials
id: backend
run: echo '${{ secrets.GCS_KEY }}' > ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}
- name: Terraform Init
id: init
run: terraform init
- name: Terraform Apply
id: apply
env:
TF_VAR_do_token: ${{ secrets.do_token }}
TF_VAR_do_region: ${{ secrets.do_region }}
TF_VAR_cloudflare_api_token: ${{ secrets.cloudflare_api_token }}
TF_VAR_cloudflare_zone_id: ${{ secrets.cloudflare_zone_id }}
run: terraform apply -auto-approve这个工作流:
- 在每次推送/合并到
main分支时触发 - 运行在
latest Ubuntu镜像上 - 第一步 checkout 我们的代码
- 然后设置 Terraform
- 设置
gcs后端凭据 - 运行
terraform init& - 运行带
-auto-approve的terraform apply以部署我们的基础设施
再次将此工作流使用适当的 commit 消息 commit 到 Git,我们将创建最后一个工作流。
3.3 创建 workflows/plan.yml
我们将创建的最后一个(但绝对不是最不重要的)工作流是 plan 工作流。我们最后创建此工作流,因为我们将在此工作流中集成我们的 Github Bot,以帮助提供空运行 (dry-runs) 的输出,从而使我们的 Pull-request 审查变得更容易、更有价值。
因此,我们将首先利用必要的功能构建工作流,然后在下一步中集成 Bot。以下是我们的 plan 工作流的代码;
name: Plan
on:
pull_request:
branches: [main]
env:
GOOGLE_APPLICATION_CREDENTIALS: ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v2
with:
submodules: true # Fetch submodules (true OR recursive)
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_wrapper: true
- name: Setup Backend Credentials
id: backend
run: echo '${{ secrets.GCS_KEY }}' > ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}
- name: Terraform Init
id: init
run: terraform init
- name: Terraform fmt
id: fmt
run: terraform fmt -check
continue-on-error: true
- name: Terraform Validate
id: validate
run: terraform validate -no-color
- name: Terraform Plan
id: plan
env:
TF_VAR_do_token: ${{ secrets.do_token }}
TF_VAR_do_region: ${{ secrets.do_region }}
TF_VAR_cloudflare_api_token: ${{ secrets.cloudflare_api_token }}
TF_VAR_cloudflare_zone_id: ${{ secrets.cloudflare_zone_id }}
run: terraform plan -no-color这个工作流:
- 在每次 pull-request 到
main分支时触发 - 运行在
latest Ubuntu镜像上 - 获取对 Pull requests 的写入权限
- 第一步 checkout 我们的代码
- 然后设置 Terraform
- 设置
gcs后端凭据 - 运行
terraform init - 运行
terraform fmt - 运行
terraform validate(再次) - 运行对我们的基础设施进行空运行 (dry-run) 的
terraform plan
完美!现在我们可以继续并在下一节中添加 Bot。记得将你新的 plan 工作流 commit 到 Git。
4. 设置 Github Bot
将 Github Bot 添加到我们的工作流中非常简单。这只是我们的 plan 工作流上的一个额外步骤。
将以下步骤添加到您的 plan.yml 工作流文件中:
- name: Comment plan on PR
uses: actions/github-script@v6
if: github.event_name == 'pull_request'
env:
PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
// 1. Retrieve existing bot comments for the PR
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
})
const botComment = comments.find(comment => {
return comment.user.type === 'Bot' && comment.body.includes('Terraform Format and Style')
})
// 2. Prepare format of the comment
const output = ` ## Terraform Results
#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
#### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
#### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
<details><summary>Validation Output</summary>
\`\`\`\n
${{ steps.validate.outputs.stdout }}
\`\`\`
</details>
#### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
<details><summary>Show Plan</summary>
\`\`\`\n
${process.env.PLAN}
\`\`\`
</details>
*Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ env.tf_actions_working_dir }}\`, Workflow: \`${{ github.workflow }}\`*`;
// 3. If we have a comment, update it, otherwise create a new one
if (botComment) {
github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: output
})
} else {
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
}你的整个 plan.yml 文件现在应该完全如下所示:
name: Plan
on:
pull_request:
branches: [main]
env:
GOOGLE_APPLICATION_CREDENTIALS: ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v2
with:
submodules: true # Fetch submodules (true OR recursive)
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_wrapper: true
- name: Setup Backend Credentials
id: backend
run: echo '${{ secrets.GCS_KEY }}' > ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}
- name: Terraform Init
id: init
run: terraform init
- name: Terraform fmt
id: fmt
run: terraform fmt -check
continue-on-error: true
- name: Terraform Validate
id: validate
run: terraform validate -no-color
- name: Terraform Plan
id: plan
env:
TF_VAR_do_token: ${{ secrets.do_token }}
TF_VAR_do_region: ${{ secrets.do_region }}
TF_VAR_cloudflare_api_token: ${{ secrets.cloudflare_api_token }}
TF_VAR_cloudflare_zone_id: ${{ secrets.cloudflare_zone_id }}
run: terraform plan -no-color
- name: Comment plan on PR
uses: actions/github-script@v6
if: github.event_name == 'pull_request'
env:
PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
// 1. Retrieve existing bot comments for the PR
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
})
const botComment = comments.find(comment => {
return comment.user.type === 'Bot' && comment.body.includes('Terraform Format and Style')
})
// 2. Prepare format of the comment
const output = ` ## Terraform Results
#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
#### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
#### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
<details><summary>Validation Output</summary>
\`\`\`\n
${{ steps.validate.outputs.stdout }}
\`\`\`
</details>
#### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
<details><summary>Show Plan</summary>
\`\`\`\n
${process.env.PLAN}
\`\`\`
</details>
*Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ env.tf_actions_working_dir }}\`, Workflow: \`${{ github.workflow }}\`*`;
// 3. If we have a comment, update it, otherwise create a new one
if (botComment) {
github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: output
})
} else {
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
}5. 将代码库推送到 Github
在你完成上述新添加内容至 Git 的 commit 后,是时候我们将我们的基础设施代码库发布 (publish) 到 Github 了。
现在只需前往 Github 并创建一个公共(或私有)仓库,将该仓库添加为我们本地机器上项目的 origin 远程,然后进行推送 (push)。
这样做之后,你的 deploy 工作流应该会立即开始执行,但是不用担心,因为它会失败。它会失败是因为 Github Actions 环境尚未配置合适的凭据来初始化我们的 Terraform Backend,也没有为我们基础设施中定义的变量提供适当的值。
现在是时候为 Github Actions CI/CD 环境正确配置适当的访问凭据以部署我们的工作负载了。
6. 为 Terraform 后端和变量配置 Github Environments
现在,我们需要前往 Github Environments 并配置我们工作流成功所需的机密 (secrets) 和环境变量 (env vars)。
如果你一直关注我们的 Github Actions 工作流,你会发现对于每一个影响 Terraform 状态的操作,我们都包含了 Terraform 执行所需的不同环境变量。在 deploy 工作流中的 terraform apply 命令处就可以找到一个例子。代码片段如下;
- name: Terraform Apply
id: apply
env:
TF_VAR_do_token: ${{ secrets.do_token }}
TF_VAR_do_region: ${{ secrets.do_region }}
TF_VAR_cloudflare_api_token: ${{ secrets.cloudflare_api_token }}
TF_VAR_cloudflare_zone_id: ${{ secrets.cloudflare_zone_id }}
run: terraform apply -auto-approve因此下面是我们所需设置的所有不同 secrets 和 env vars 的列表(你将看到它们全部都已融入到了不同工作流的合适位置中);
- Terraform 变量
TF_VAR_do_tokenTF_VAR_do_regionTF_VAR_cloudflare_api_tokenTF_VAR_cloudflare_zone_id
- 远程后端机密和变量
GCS_KEY- Google Cloud Storage 密钥。GOOGLE_APPLICATION_CREDENTIALS- 文件系统中 Google Cloud Storage 密钥的路径
iWarning+
切记为 Github Actions 环境创建一个不同的 GCS Key,并禁用我们用于在本地初始化 Terraform 的那个,从现在起,对基础设施的任何新更改都将通过我们的工作流进行处理。你甚至不应将你的密钥用于初始的资源部署。
好的,现在前往你的 Github Dashboard -> Settings -> Secrets and Variables -> Actions 页面。如下面页面所示。
接下来我们将继续填充不同机密和环境变量的值。
iTip+
由于在这个项目中我们只处理一个单一的环境,因此我们将把这些填为仓库 (Repository) 的机密和变量。在一个更高级的设置中,如果我们拥有 dev、stage、qa、prod 环境,我们将利用核心的 Github Environments 解决方案。这个项目没有那么高级,所以我们将坚持使用 Repo 的机密和变量。
iTip+
-
除了
GOOGLE_APPLICATION_CREDENTIALS变量外,其它所有的都应是机密 (secret)。为了简单起见,此变量请使用值./key.json。 -
要在机密 (
secrets) 和变量 (variables) 之间进行切换,请使用页面上相同名称的选项卡。
当一切设置完毕时,我们的页面应如下所示:
现在我们所有的机密和变量都配置好了。 🎉 🎉
7. 测试整个工作流
最后,是时候让这个巨大的轮子转动起来了。
7.1 创建 plan 分支
第一步将是创建项目的 plan 分支。我们在前面的步骤中多次提到了它,是时候真正创建它了。
所以我们将用以下命令来实现:
git checkout -b plan这将创建新的 plan 分支并将其切换 (check-it-out) 到我们当前的工作目录。接下来我们将此分支推送到 Github,并将其设置为跟踪同一个名为 plan 的新 origin 分支。
git push -u origin plan此推送应该会在 Github 中触发一个 validate 工作流,如果此工作流成功,那么说明我们在正确的道路上。如果不成功,那么你需要四处寻找看漏掉了什么以便修复它。查看 Actions 工作流错误日志以理解任何错误。
但是,如果你严格遵循本演练,你应该会看到与以下类似的成功画面。你最初的 validate 工作流应该会成功。
7.2 准备并创建第一个提交至 main 的 PR
现在为了启动我们的工作流,我们需要向 main 分支创建我们的第一个 Pull request。在这一点上这是不可能的,因为我们的 main 和 plan 分支是同步的。为了使其起作用,我们需要对 plan 分支进行一次虚假 (dummy) 的 commit。
为了简单起见,我们只需向我们的一个基础设施文件添加注释,commit 更改,然后向 main 提交 PR。
iTip+
你可以将注释添加到你选择的任何文件中。我将添加到 backend.tf 文件中。我将把以下行添加到文件开头
# Configure the remote gcs backend现在保存你修改后的文件,commit 它 并 将其推送到 Github。 这应该会在 Github 上启动一个新的 validate 工作流运行;
而且它应该会成功。
现在我们可以继续并创建我们向 main 的第一个 Pull request 了。如果一切正常,你应该得到与下面我得到的相似的结果。
现在在你的 Pull Request 中每个地方都应该变绿了 🎉 🎉
7.3 通过 Bot 评论检查计划输出
既然你的工作流已成功运行,你应该能够看到新的 Bot 评论,其中包含了关于你提议的基础设施更改的非常重要的详细信息。
继续展开评论的不同部分以获得更多细节。例如,单击 Show Plan 按钮将向你展示 terraform plan 命令的输出结果。
如你所见,由于我们的配置中仅定义了两个资源,我们看到一个将要创建两个资源的计划。
7.4 审查、迭代并批准(或拒绝)该计划
在这一阶段,利用 Github 原生的分支规则,可以要求最少数量的审查者审查新的基础设施更改并签字批准。团队可以更改任何需要的东西,对 plan 分支的新 commit 会堆积在此 pull request 下,每次都会触发 plan workflow 的重新运行,以确保团队在下次应用时看到最新做出的更改。
现在因为我们想看到这个工作流顺利完成,并确保我们的基础设施真正被创建出来,我们将假设团队非常喜欢我们的新基础设施,并且负责人已经签字。因此,我们将继续并合并 (Merge) 这个 Pull Request #1,让我们的基础设施被部署。
7.5 然后... 就到圣诞节啦
iTip+
我使用了 rebasing 仅仅是因为我喜欢整洁的 commit 历史记录。
这应该会触发我们在 main 分支中的 deploy 工作流,正如预期的那样,它应该会完美无瑕地工作,如下所示;
我们做到了!!! 🎉 🎉
为了确认我们的基础设施在现实世界中处于上线状态,我们可以访问其 web 地址。我的地址是 server.obi.ninja,它运行得非常完美。
iTip+
我使用了 https:// 因为我配置了来自 Cloudflare 的免费 SSL 服务。我是在 Terraform 之外完成的这一配置,当时正在设置 Cloudflare 区域本身,只是为了保持该项目和演练的简单性。
8. 附加说明
8.1 Github 分支保护规则
为了使该项目坚不可摧,从而能够为你的组织可靠地工作,你需要利用 Github 分支保护规则。借助这些规则,你将能够强制执行重要操作,例如;
- 确保不能删除
main和plan分支 - 仅在
validate和plan工作流都成功之后,才允许合并到main - 指定允许对任何新更改进行签字批准的最小人数
- 等等。
8.2 附加工具
你可能希望用额外的工具来扩展该项目的不同部分以增强安全性。工具例如:
- 用于机密管理的 Hashicorp Vault
- 用于附加静态和代码质量分析的 Sonarqube
- 等等。
8.3 销毁基础设施
这是一个团队工作流,而且它是公司基础设施的骨干,因此,对资源的任何销毁都必须通过删除或注释所述资源的 Terraform 代码来执行,并让负责该项目的团队对这种变更进行审查。只要妥善管理基础设施机密,就没有人能够只靠运行 terraform destroy 就去销毁资源。
8.4 结论
在构思、执行和记录此项目的过程中,我获得了很多乐趣。我希望你能发现它的用处。如果有什么想法、任何错误或改进空间,请随时在下面的评论区告诉我。
