Terraform实战12:CI/CD集成——GitLab CI + Terraform自动化
本篇目标
把Terraform集成到GitLab CI Pipeline中,实现基础设施变更的自动化:提MR自动plan预览,审批合并后自动apply执行。
学完本篇你将掌握:
- GitLab CI Pipeline配置(.gitlab-ci.yml)
- Terraform在CI中的标准工作流
- Plan结果的artifact传递
- 多环境Pipeline设计
- prod环境的手动审批机制
- CI中的凭证安全管理
前置条件
- 熟悉GitLab CI(你工作中就在用)
- 理解Terraform的init/plan/apply流程
为什么需要CI/CD集成
手动在本地执行terraform的问题:
| 问题 | 后果 |
|---|---|
| 每个人本地执行 | 不知道谁改了什么,没有审批流程 |
| 直接apply没review | 可能误操作生产环境 |
| 凭证在本地 | 不安全,离职后风险 |
| 没有执行记录 | 出问题追溯困难 |
CI/CD集成后:
- 所有变更通过MR → 有review、有记录
- Plan结果自动展示 → reviewer能看到会改什么
- Apply需要审批 → 防止误操作
- 凭证在CI Variables中 → 不在本地,可以随时撤销
标准工作流
开发者修改 .tf 文件 │ ▼ 提交MR ┌─────────────────┐ │ validate │ ← 语法检查 + 格式检查 └────────┬────────┘ ▼ ┌─────────────────┐ │ plan │ ← 预览变更,结果显示在MR中 └────────┬────────┘ ▼ Reviewer审批,合并MR ┌─────────────────┐ │ apply │ ← 执行变更(dev自动,prod手动确认) └─────────────────┘完整Pipeline配置
基础版:.gitlab-ci.yml
stages:-validate-plan-apply-destroyvariables:TF_VERSION:"1.9.0"TF_ROOT:"${CI_PROJECT_DIR}/infrastructure"# 基础镜像和初始化default:image:name:hashicorp/terraform:${TF_VERSION}entrypoint:[""]before_script:-cd ${TF_ROOT}-terraform init-input=false# Stage 1:语法校验(每次push自动执行)validate:stage:validatescript:-terraform validate-terraform fmt-check-diffrules:-if:'$CI_PIPELINE_SOURCE == "merge_request_event"'-if:'$CI_COMMIT_BRANCH == "main"'# Stage 2:Plan(预览变更,保存plan文件)plan:stage:planscript:-terraform plan-out=tfplan-input=false-terraform show-no-color tfplan>plan_output.txtartifacts:paths:-${TF_ROOT}/tfplan-${TF_ROOT}/plan_output.txtexpire_in:1 hourrules:-if:'$CI_PIPELINE_SOURCE == "merge_request_event"'-if:'$CI_COMMIT_BRANCH == "main"'# Stage 3a:自动Apply(main分支push时)apply:auto:stage:applyscript:-terraform apply-auto-approve-input=false tfplandependencies:-planrules:-if:'$CI_COMMIT_BRANCH == "main"'when:on_success# Stage 3b:手动Apply(MR中点击按钮)apply:manual:stage:applyscript:-terraform apply-auto-approve-input=false tfplandependencies:-planrules:-if:'$CI_PIPELINE_SOURCE == "merge_request_event"'when:manualallow_failure:true# Stage 4:Destroy(手动触发)destroy:stage:destroyscript:-terraform destroy-auto-approve-input=falserules:-if:'$CI_COMMIT_BRANCH == "main"'when:manualallow_failure:true进阶版:多环境Pipeline
stages:-validate-plan-applyvariables:TF_VERSION:"1.9.0"default:image:name:hashicorp/terraform:${TF_VERSION}entrypoint:[""]# 模板复用.terraform_init:&terraform_initbefore_script:-cd infrastructure/environments/${ENVIRONMENT}-terraform init-input=false# Plan:dev(dev目录变更时触发)plan:dev:stage:plan<<:*terraform_initvariables:ENVIRONMENT:devscript:-terraform plan-out=tfplan-input=falseartifacts:paths:-infrastructure/environments/dev/tfplanexpire_in:1 hourrules:-if:'$CI_COMMIT_BRANCH == "main"'changes:-infrastructure/environments/dev/**/*-infrastructure/modules/**/*# Plan:prodplan:prod:stage:plan<<:*terraform_initvariables:ENVIRONMENT:prodscript:-terraform plan-out=tfplan-input=falseartifacts:paths:-infrastructure/environments/prod/tfplanexpire_in:1 hourrules:-if:'$CI_COMMIT_BRANCH == "main"'changes:-infrastructure/environments/prod/**/*-infrastructure/modules/**/*# Apply:dev(自动)apply:dev:stage:apply<<:*terraform_initvariables:ENVIRONMENT:devscript:-terraform apply-auto-approve-input=false tfplandependencies:-plan:devrules:-if:'$CI_COMMIT_BRANCH == "main"'changes:-infrastructure/environments/dev/**/*-infrastructure/modules/**/*# Apply:prod(手动审批!)apply:prod:stage:apply<<:*terraform_initvariables:ENVIRONMENT:prodscript:-terraform apply-auto-approve-input=false tfplandependencies:-plan:prodrules:-if:'$CI_COMMIT_BRANCH == "main"'changes:-infrastructure/environments/prod/**/*-infrastructure/modules/**/*when:manual# prod必须手动确认关键配置说明
GitLab CI Variables(凭证管理)
在 GitLab → Settings → CI/CD → Variables 中添加:
| Variable | Protected | Masked | 说明 |
|---|---|---|---|
AWS_ACCESS_KEY_ID | ✅ | ✅ | AWS AK |
AWS_SECRET_ACCESS_KEY | ✅ | ✅ | AWS SK |
AWS_DEFAULT_REGION | ❌ | ❌ | 区域 |
- Protected= 只有protected分支(main)能用 → 防止feature分支随便apply
- Masked= 日志中不显示明文 → 防止泄露
Plan文件传递(artifacts)
plan:artifacts:paths:-${TF_ROOT}/tfplan# plan阶段保存expire_in:1 hourapply:dependencies:-plan# apply阶段下载plan的artifactscript:-terraform apply tfplan# 用保存的plan执行(不重新plan)为什么要这样做?
- 保证plan和apply是同一个计划
- 防止plan和apply之间有人改了代码导致不一致
changes触发(只改了相关目录才执行)
rules:-if:'$CI_COMMIT_BRANCH == "main"'changes:-infrastructure/environments/dev/**/*# dev目录变更-infrastructure/modules/**/*# 共用模块变更改了dev的配置不会触发prod的pipeline,精准控制。
when: manual(手动审批)
apply:prod:when:manual# 需要人工点击按钮才执行prod环境的apply不自动执行,必须有人在GitLab UI中点击"Run"按钮确认。
项目目录结构建议
your-repo/ ├── .gitlab-ci.yml # Pipeline配置 ├── infrastructure/ │ ├── modules/ # 共用模块 │ │ └── vpc/ │ └── environments/ # 各环境 │ ├── dev/ │ │ ├── main.tf │ │ ├── variables.tf │ │ └── terraform.tfvars │ └── prod/ │ ├── main.tf │ ├── variables.tf │ └── terraform.tfvars └── README.md延伸思考:面试常见问题
| 面试问题 | 答案要点 |
|---|---|
| 你们Terraform怎么跑的? | GitLab CI集成,MR时自动plan,合并后apply,prod手动审批 |
| 怎么防止误操作生产? | prod的apply设为manual + Protected Variables + MR审批 |
| CI中凭证怎么管理? | GitLab CI Variables,Protected+Masked,不在代码中 |
| plan和apply之间代码变了怎么办? | 用-out保存plan文件,apply时用同一个plan,不重新生成 |
| 多人同时apply会冲突吗? | 远程State用DynamoDB锁,同时只能一人操作 |
费用说明
本篇不创建AWS资源,费用为0。只是编写Pipeline配置文件。
小结
本篇核心收获:
- 标准工作流:validate → plan → apply,通过MR驱动
- Plan文件传递:artifacts保证plan和apply一致性
- prod手动审批:
when: manual防止自动部署到生产 - 凭证安全:GitLab Variables + Protected + Masked
- changes触发:精准控制哪个环境的pipeline被触发
- 这跟你工作中的GitLab CI是同一套逻辑——只是跑的是Terraform而不是构建镜像
下一篇预告
Terraform实战13:安全最佳实践——IAM、加密与合规检查
下一篇我们将学习:
- 用tfsec/checkov扫描Terraform代码的安全问题
- IAM最小权限原则的实现
- 加密配置的最佳实践
- 如何在CI中集成安全扫描
参考链接
- 本系列配套代码(GitHub)
- GitLab CI + Terraform官方指南
- Terraform CI/CD最佳实践
- GitLab CI Variables文档
- GitLab CI rules文档