本文介绍如何将 Terraform 结合 Github action 实现自动化部署。
前置条件
3. 获取凭证,在 API密钥管理 页面中创建并复制 SecretId 和 SecretKey。
创建项目
在 GitHub 中新建代码仓库,目录结构如下:
.├── README.md├── environments│ ├── dev│ │ ├── main.tf│ │ └── provider.tf│ └── prod│ ├── cicd│ │ └── main.tf│ ├── local.tf│ ├── main.tf│ ├── provider.tf│ └── qta│ └── main.tf└── modules├── network│ ├── main.tf│ ├── outputs.tf│ ├── provider.tf│ └── variables.tf├── security_group│ ├── main.tf│ ├── outputs.tf│ ├── provider.tf│ └── variables.tf└── tke├── main.tf├── outputs.tf├── provider.tf└── variables.tf
目录结构说明:
1. 项目结构主要分为
environments
和modules
两个目录。2.
environments
为目录方式隔离环境dev
和prod
,用来给不同环境设置各自的配置,每个环境目录都是独立的根模块。说明:
dev 中演示创建一个 vpc。
prod 中演示通过 workespace 进行业务隔离。在 cicd 目录中创建 vpc,在 qta 目录下创建容器集群。
3.
modules
为封装的资源信息,用以复用。本目录中包含 vpc、安全组和容器服务 TKE 的 Module 演示。4. 完整代码请参考 gitops-terraform。
流水线配置
1. 为防止 AKSK 等安全信息泄流造成安全问题,您需要在
https://github.com/${USER}/${PROJECT}/settings/secrets/actions
设置环境变量。请替换为已复制的 SecretId 和 SecretKey。
2. 通过 GitHub Actions 配置流水线。
检查流水线
1. Terraform 根模块资源不能过多。同理,在执行检查的时候也应该尽可能的避免全部资源的读取,需要以细粒度的方式触发检查。
2. 本文档采用按分支区分触发的环境。例如,dev 中的配置需要更新时,只能在 dev 分支上进行更新。配置更新完成后提交 PR 将代码合入到 main(主分支)。目的是每次更新的检查不需要全量扫描 environments 下的所有子目录(环境),减少不必要的状态同步消耗。
3. 该流水线主要通过执行
terraform fmt
、terraform init
、terraform validate
、terraform plan
来检查代码和展示构建计划,方便判断是否执行部署。部署流水线
1. 如果检查流水线中的检查操作都成功且
terraform plan
的输出符合预期,那么就可以进行 merge 操作。2. 在 merge 完成的时候触发部署(即
terraform apply
)的操作。示意图如下:
相关操作
在进入 environment 的指定环境目录后,会判断是否还有子目录,如果有则通过 workspace 隔离不同业务环境(例如 qta,ci),如果没有则等价于普通的根模块。
创建校验流水线
参考以下代码,下载 Terraform 和校验 Terraform 代码:
# This is a basic workflow to help you get started with Actionsname: CI# Controls when the workflow will runon:pull_request:# A workflow run is made up of one or more jobs that can run sequentially or in paralleljobs:# This workflow contains a single job called "build"build:# The type of runner that the job will run onruns-on: ubuntu-latestenv:TENCENTCLOUD_SECRET_KEY: ${{ secrets.TENCENTCLOUD_SECRET_KEY }}TENCENTCLOUD_SECRET_ID: ${{ secrets.TENCENTCLOUD_SECRET_ID }}# Steps represent a sequence of tasks that will be executed as part of the jobsteps:- uses: actions/checkout@v3- uses: hashicorp/setup-terraform@v2with:terraform_wrapper: false- name: check envrun: |if [ ! -d "environments/$GITHUB_HEAD_REF" ]; thenecho "*************************SKIPPING************************************"echo "Branch '$GITHUB_HEAD_REF' does not represent an oficial environment."echo "*********************************************************************"exit 1fi- name: terraform fmtid: fmtrun: terraform fmt -recursive -check- name: terraform initid: initworking-directory: environments/${{ github.head_ref }}run: terraform init- name: terraform validateid: validateworking-directory: environments/${{ github.head_ref }}run: terraform validate- name: terraform planid: planif: github.event_name == 'pull_request'working-directory: environments/${{ github.head_ref }}run: |plan_info=""dir_count=`ls -l | grep "^d" | wc -l`if [ $dir_count -gt 0 ]; thenfor dir in ./*/doenv=${dir%*/}env=${env#*/}echo ""echo "========> Terraform Plan <========"echo "At environment: ${{ github.head_ref }}"echo "At workspace: ${env}"echo "=================================="terraform workspace select ${env} || terraform workspace new ${env}plan_info="$plan_info\\n$(terraform plan -no-color)"doneelseplan_info="$(terraform plan -no-color)"fiplan_info="${plan_info//'%'/'%25'}"plan_info="${plan_info//$'\\n'/'%0A'}"plan_info="${plan_info//$'\\r'/'%0D'}"echo "::set-output name=plan_info::$plan_info"continue-on-error: true- uses: actions/github-script@v6if: github.event_name == 'pull_request'with:script: |const output = `#### Terraform Format and Style \\`${{ steps.fmt.outcome }}\\`#### Terraform Initialization \\`${{ steps.init.outcome }}\\`#### Terraform Validation \\`${{ steps.validate.outcome }}\\`#### Terraform Plan \\`${{ steps.plan.outcome }}\\`<details><summary>Show Plan</summary>\\`\\`\\`\\n${{ steps.plan.outputs.plan_info }}\\`\\`\\`</details>*Pushed by: @${{ github.actor }}, Action: \\`${{ github.event_name }}\\`*`;github.rest.issues.createComment({issue_number: context.issue.number,owner: context.repo.owner,repo: context.repo.repo,body: output})
创建部署流水线
name: Applyon:pull_request:types:- closedbranches:- mainjobs:build:if: github.event.pull_request.merged == trueruns-on: ubuntu-latestenv:TENCENTCLOUD_SECRET_KEY: ${{ secrets.TENCENTCLOUD_SECRET_KEY }}TENCENTCLOUD_SECRET_ID: ${{ secrets.TENCENTCLOUD_SECRET_ID }}steps:- uses: actions/checkout@v3- uses: hashicorp/setup-terraform@v2- name: terraform initid: initworking-directory: environments/${{ github.head_ref }}run: terraform init- name: terraform applyworking-directory: environments/${{ github.head_ref }}run: |dir_count=`ls -l | grep "^d" | wc -l`if [ $dir_count -gt 0 ]; thenfor dir in ./*/doenv=${dir%*/}env=${env#*/}echo ""echo "========> Terraform Apply <========"echo "At environment: ${{ github.head_ref }}"echo "At workspace: ${env}"echo "=================================="terraform workspace select ${env} || terraform workspace new ${env}terraform apply -auto-approvedoneelseterraform apply -auto-approvefi