- 1. 引言:Infrastructure as Code 的时代
- Part 1: Terraform 完全指南
- Part 2: Ansible 完全指南
- Part 3: 集成、速查表与故障排查
1. 引言:Infrastructure as Code 的时代
1.1 为什么需要 IaC 与 Configuration Management?
在云原生时代,手工管理基础设施已经不再是一个选项。如果几百台服务器、几十个 VPC、错综复杂的安全组与 IAM 策略都靠在控制台点鼠标来管理,那就是在批量制造 “Snowflake Server” —— 像雪花一样各不相同的服务器 —— 的捷径。无法复现,难以审计追踪,一次失误就可能让整套基础设施崩塌。
Infrastructure as Code(IaC)与 Configuration Management(CM)就是业界给出的答案。
- IaC(Infrastructure as Code):把基础设施本身声明为代码,做版本管理、代码评审,并自动完成预配。代表工具:Terraform、Pulumi、AWS CloudFormation、OpenTofu
- CM(Configuration Management):在已预配的基础设施之上安装软件、统一配置、维持状态。代表工具:Ansible、Chef、Puppet、SaltStack
这两个领域的事实标准(de facto standard)正是 Terraform 和 Ansible。
1.2 Terraform 与 Ansible 的职责分工
Terraform 和 Ansible 不是竞争工具,而是 互补工具。两者的责任边界截然不同。
┌──────────────────────────────────────────────────────────────────┐
│ IaC + CM 워크플로우 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ ┌─────────────────────────┐ │
│ │ Terraform │ │ Ansible │ │
│ │ (Provisioning) │──────▶│ (Configuration) │ │
│ │ │ │ │ │
│ │ - VPC/Subnet 생성 │ │ - 패키지 설치 │ │
│ │ - EC2/RDS 생성 │ │ - Nginx/Apache 설정 │ │
│ │ - S3 버킷 생성 │ │ - 애플리케이션 배포 │ │
│ │ - IAM Role 생성 │ │ - 보안 하드닝 │ │
│ │ - Security Group │ │ - 모니터링 에이전트 설치 │ │
│ └─────────────────────┘ └─────────────────────────┘ │
│ │
│ "인프라를 만든다" "인프라를 구성한다" │
│ Declarative (선언적) Procedural + Declarative │
│ State 기반 Agentless (SSH/WinRM) │
└──────────────────────────────────────────────────────────────────┘
1.3 本文的结构
本文大致分为三个部分。
- Part 1 — Terraform:从 HCL 语法到核心工作流、State 管理、Workspace、模块与高级功能
- Part 2 — Ansible:从清单、Ad-hoc 命令、Playbook、Role、Vault 到 Galaxy
- Part 3 — 集成与速查表:Terraform + Ansible 联动、命令速查表、故障排查
Part 1: Terraform 完全指南
2. Terraform 简介与架构
2.1 什么是 Terraform?
Terraform 是 HashiCorp 于 2014 年发布的开源 IaC 工具。用 HCL(HashiCorp Configuration Language)这种声明式语言定义基础设施后,Terraform 会比较当前状态(State)与期望状态(Configuration),只对差异部分应用变更。
2024 年 8 月,HashiCorp 被 IBM 收购,Terraform 的许可证也变更为 BSL(Business Source License)。作为社区对此的回应,OpenTofu(隶属 Linux Foundation)诞生了。本文涉及的绝大多数命令在 OpenTofu 上同样适用。
2.2 Terraform 架构
Terraform 的核心架构由 Core + Providers + State 三个要素构成。
┌──────────────────────────────────────────────────────────────┐
│ Terraform Architecture │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ .tf Files │ HCL Configuration │
│ │ (desired │ │
│ │ state) │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ Terraform Core │ │
│ │ │ │
│ │ ┌───────────┐ ┌──────────────┐ │ │
│ │ │ Resource │ │ Dependency │ │ │
│ │ │ Graph │ │ Resolution │ │ │
│ │ └───────────┘ └──────────────┘ │ │
│ │ │ │
│ │ ┌───────────┐ ┌──────────────┐ │ │
│ │ │ Plan │ │ Apply │ │ │
│ │ │ Engine │ │ Engine │ │ │
│ │ └───────────┘ └──────────────┘ │ │
│ └─────────┬───────────────┬────────────────┘ │
│ │ │ │
│ ┌──────▼──────┐ ┌─────▼──────────┐ │
│ │ Providers │ │ State File │ │
│ │ │ │ │ │
│ │ - AWS │ │ terraform.tfstate│ │
│ │ - Azure │ │ (JSON) │ │
│ │ - GCP │ │ │ │
│ │ - Kubernetes │ │ Local / Remote │ │
│ │ - 3000+ │ │ (S3, GCS, etc.) │ │
│ └──────────────┘ └─────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
- Terraform Core:HCL 解析、依赖图构建、Plan/Apply 引擎
- Providers:把各云厂商/服务 API 抽象出来的插件(AWS、Azure、GCP、Kubernetes 等 3,000 个以上)
- State:追踪当前基础设施状态的 JSON 文件
2.3 Terraform vs OpenTofu vs Pulumi
| 项目 | Terraform | OpenTofu | Pulumi |
|---|---|---|---|
| 许可证 | BSL 1.1 | MPL 2.0 (OSS) | Apache 2.0 |
| 语言 | HCL | HCL | Python/Go/TS/C# |
| 状态管理 | terraform.tfstate | terraform.tfstate | Pulumi Cloud |
| Provider 生态 | 3,000+ | 兼容 Terraform | 100+ |
| 运营主体 | HashiCorp(IBM) | Linux Foundation | Pulumi Inc. |
| CLI 命令 | terraform | tofu | pulumi |
3. Terraform 安装与环境配置
3.1 使用 tfenv 管理版本
不同项目常常需要使用不同的 Terraform 版本,因此强烈建议使用 tfenv(Terraform Version Manager)。
# macOS (Homebrew)
brew install tfenv
# Linux (Git Clone)
git clone https://github.com/tfutils/tfenv.git ~/.tfenv
echo 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.bashrc
# 查看可用版本列表
tfenv list-remote
# 安装指定版本
tfenv install 1.9.8
tfenv install 1.10.3
# 设置全局默认版本
tfenv use 1.10.3
# 按项目锁定版本(.terraform-version 文件)
echo "1.9.8" > .terraform-version
# 已安装版本列表
tfenv list
# 查看当前版本
terraform version
3.2 直接安装
# macOS (Homebrew)
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
# Linux (APT)
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
# 查看版本
terraform version
# Terraform v1.10.3
# on darwin_arm64
3.3 自动补全与编辑器配置
# Bash/Zsh 自动补全
terraform -install-autocomplete
# VS Code 扩展
# - HashiCorp Terraform (官方)
# - Terraform Autocomplete
4. Terraform 核心工作流
Terraform 的核心工作流是 Write → Plan → Apply 三个阶段。下面详细看看支撑这三个阶段的命令。
4.1 terraform init — 初始化项目
terraform init 是 Terraform 项目的第一条命令。它会下载 Provider 插件、下载模块并初始化 Backend。
# 基本初始化
terraform init
# 主要标志
terraform init -upgrade # 将 Provider/模块升级到允许范围内的最新版本
terraform init -reconfigure # 重新配置 Backend(忽略已有 state)
terraform init -migrate-state # 变更 Backend 时迁移 state
terraform init -backend=false # 跳过 Backend 初始化(用于校验)
terraform init -get=false # 跳过模块下载
terraform init -input=false # 禁用交互式输入(用于 CI/CD)
terraform init -no-color # 禁用彩色输出(便于日志解析)
terraform init -lockfile=readonly # 禁止修改 .terraform.lock.hcl(用于 CI)
# 通过 CLI 传入 Backend 配置(在 CI/CD 中很有用)
terraform init \
-backend-config="bucket=my-tf-state" \
-backend-config="key=prod/terraform.tfstate" \
-backend-config="region=ap-northeast-2" \
-backend-config="dynamodb_table=tf-lock"
执行 terraform init 后生成的文件/目录:
.terraform/ # Provider 插件、模块缓存
.terraform.lock.hcl # Provider 版本锁定文件(需要提交)
4.2 terraform validate — 语法校验
# 语法有效性检查(init 之后可用)
terraform validate
# JSON 输出(用于 CI/CD 流水线)
terraform validate -json
# 输出示例(成功)
# Success! The configuration is valid.
# 输出示例(失败,JSON)
# {
# "valid": false,
# "error_count": 1,
# "diagnostics": [
# {
# "severity": "error",
# "summary": "Unsupported argument",
# "detail": "An argument named \"vps_id\" is not expected here. Did you mean \"vpc_id\"?"
# }
# ]
# }
4.3 terraform fmt — 代码格式化
# 格式化当前目录下的 .tf 文件
terraform fmt
# 递归格式化所有子目录
terraform fmt -recursive
# 只显示需要变更的文件(用于 CI 检查)
terraform fmt -check
# 输出 diff
terraform fmt -diff
# 在 CI/CD 流水线中的用法
terraform fmt -check -recursive -diff
# 退出码 0:格式无需变更
# 退出码 3:需要调整格式
4.4 terraform plan — 执行计划
terraform plan 是 Terraform 最重要的命令之一。它会对比当前 State 与 Configuration,预先展示将要发生哪些变更。它不会对实际基础设施做任何改动。
# 基本 Plan
terraform plan
# 主要标志
terraform plan -out=tfplan # 把 Plan 保存为文件(供 apply 使用)
terraform plan -destroy # 查看删除计划
terraform plan -target=aws_instance.web # 只对特定资源做 Plan
terraform plan -var="instance_type=t3.large" # 传入变量
terraform plan -var-file="prod.tfvars" # 指定变量文件
terraform plan -refresh=false # 跳过 State 刷新(提升速度)
terraform plan -parallelism=20 # 并发任务数(默认值:10)
terraform plan -compact-warnings # 精简警告信息
terraform plan -no-color # 禁用彩色输出
terraform plan -input=false # 禁用交互式输入
terraform plan -json # JSON 输出(用于自动化)
terraform plan -detailed-exitcode # 详细退出码
# 退出码 0:无变更
# 退出码 1:发生错误
# 退出码 2:存在变更
# CI/CD 流水线推荐模式
terraform plan -out=tfplan -input=false -no-color -detailed-exitcode
Plan 输出中的符号含义:
# + create (创建资源)
# - destroy (删除资源)
# ~ update (修改资源, in-place)
# -/+ replace (删除后重新创建资源)
# <= read (读取数据源)
4.5 terraform apply — 应用变更
# 基本 Apply(先 Plan 再弹出确认提示)
terraform apply
# 用保存的 Plan 文件 Apply(无确认提示)
terraform apply tfplan
# 自动批准(用于 CI/CD,需谨慎!)
terraform apply -auto-approve
# 主要标志
terraform apply -target=aws_instance.web # 只应用特定资源
terraform apply -var="instance_type=t3.large" # 传入变量
terraform apply -var-file="prod.tfvars" # 指定变量文件
terraform apply -parallelism=20 # 并发任务数
terraform apply -refresh=false # 跳过 State 刷新
terraform apply -replace=aws_instance.web # 强制重建资源(替代 taint)
terraform apply -lock=false # 禁用 State 锁(不推荐)
terraform apply -lock-timeout=5m # State 锁等待时间
# 安全的 CI/CD 模式(Plan → Save → Apply)
terraform plan -out=tfplan -input=false
# ... 评审 ...
terraform apply tfplan
4.6 terraform destroy — 删除基础设施
# 删除所有资源(带确认提示)
terraform destroy
# 自动批准
terraform destroy -auto-approve
# 只删除特定资源
terraform destroy -target=aws_instance.web
# 指定变量文件
terraform destroy -var-file="prod.tfvars"
# 预先查看删除 Plan
terraform plan -destroy
4.7 terraform output — 查看输出值
# 显示所有 Output 值
terraform output
# 特定 Output 值
terraform output vpc_id
# Raw 值(不带引号,供脚本使用)
terraform output -raw vpc_id
# JSON 输出
terraform output -json
# 在其他 Terraform 项目或 Ansible 中使用
VPC_ID=$(terraform output -raw vpc_id)
echo "VPC ID: $VPC_ID"
5. Terraform State 管理
5.1 什么是 State?
Terraform State(terraform.tfstate)是记录 Terraform 所管理基础设施当前状态的 JSON 文件。Terraform 通过这个文件计算 Configuration(期望状态)与实际基础设施(当前状态)之间的差异。
State 文件中包含资源 ID、属性值和元数据,而且敏感信息(密码、访问密钥等)也可能以明文形式保存,因此必须使用加密的远程 Backend(S3 + KMS 等)。
5.2 配置 Remote Backend
# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/network/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
dynamodb_table = "terraform-lock" # 用于 State 锁的 DynamoDB 表
kms_key_id = "alias/terraform" # KMS 加密密钥
}
}
5.3 terraform state 命令
# ── 查看资源列表 ──
terraform state list
# aws_vpc.main
# aws_subnet.public[0]
# aws_subnet.public[1]
# aws_instance.web
# aws_db_instance.main
# 过滤
terraform state list aws_subnet.*
terraform state list module.network
# ── 查看资源详情 ──
terraform state show aws_instance.web
# resource "aws_instance" "web" {
# ami = "ami-0c55b159cbfafe1f0"
# arn = "arn:aws:ec2:ap-northeast-2:123456789:instance/i-0abc123def456"
# instance_type = "t3.medium"
# private_ip = "10.0.1.50"
# public_ip = "54.180.xxx.xxx"
# ...
# }
# ── 移动资源(重命名/移入模块)──
# 重命名资源(代码中也需要同步修改)
terraform state mv aws_instance.web aws_instance.app_server
# 移入模块
terraform state mv aws_vpc.main module.network.aws_vpc.main
# 移动到其他 State 文件
terraform state mv -state-out=other.tfstate aws_instance.web aws_instance.web
# Dry-run(不做实际变更,仅确认)
terraform state mv -dry-run aws_instance.web aws_instance.app
# ── 从 State 中移除资源(实际基础设施保留)──
terraform state rm aws_instance.web
# Terraform 不再管理该资源
# 实际的 EC2 实例不会被删除
# ── 下载 Remote State ──
terraform state pull > terraform.tfstate.backup
# ── 把 Local State 上传到 Remote ──
terraform state push terraform.tfstate
# 强制上传(忽略 serial 序号冲突,危险!)
terraform state push -force terraform.tfstate
# ── 解除 State 锁 ──
# 异常退出导致锁没有释放时
terraform force-unlock LOCK_ID
# LOCK_ID 会显示在错误信息中
# 不带确认提示强制解锁
terraform force-unlock -force LOCK_ID
5.4 terraform import — 导入既有资源
要把此前手工创建的资源纳入 Terraform 管理,就使用 import。
# 传统 CLI import(Terraform 1.5 之前)
terraform import aws_instance.web i-0abc123def456
terraform import aws_vpc.main vpc-0abc123def
terraform import 'aws_subnet.public[0]' subnet-0abc123def
terraform import module.network.aws_vpc.main vpc-0abc123def
Terraform 1.5+ 的 import block(声明式 Import,推荐):
# import.tf
import {
to = aws_instance.web
id = "i-0abc123def456"
}
import {
to = aws_vpc.main
id = "vpc-0abc123def"
}
# 基于 import block 的 Plan(自动生成代码)
terraform plan -generate-config-out=generated.tf
# 确认生成的代码后再 Apply
terraform apply
6. Terraform Workspace
在用同一份 Configuration 管理多个环境(dev/staging/prod)时,Workspace 非常有用。每个 Workspace 拥有独立的 State 文件。
# ── Workspace 列表 ──
terraform workspace list
# * default
# dev
# staging
# prod
# ── 创建新 Workspace ──
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
# ── 切换 Workspace ──
terraform workspace select prod
# ── 查看当前 Workspace ──
terraform workspace show
# prod
# ── 删除 Workspace(仅限空 State)──
terraform workspace delete dev
# 强制删除(即使还有 State 也删除)
terraform workspace delete -force dev
在 HCL 中使用 Workspace 的模式:
# 按环境切换实例类型
locals {
instance_type = {
dev = "t3.micro"
staging = "t3.small"
prod = "t3.large"
}
}
resource "aws_instance" "app" {
ami = data.aws_ami.ubuntu.id
instance_type = local.instance_type[terraform.workspace]
tags = {
Name = "app-${terraform.workspace}"
Environment = terraform.workspace
}
}
7. Terraform 模块管理
7.1 模块结构
modules/
├── network/
│ ├── main.tf # VPC, Subnet, IGW, NAT
│ ├── variables.tf # 输入变量
│ ├── outputs.tf # 输出值
│ └── README.md
├── compute/
│ ├── main.tf # EC2, ASG, ALB
│ ├── variables.tf
│ └── outputs.tf
└── database/
├── main.tf # RDS, ElastiCache
├── variables.tf
└── outputs.tf
7.2 模块来源类型
# 本地模块
module "network" {
source = "./modules/network"
}
# Terraform Registry
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.16.0"
}
# GitHub
module "vpc" {
source = "github.com/terraform-aws-modules/terraform-aws-vpc?ref=v5.16.0"
}
# S3 Bucket
module "vpc" {
source = "s3::https://s3-ap-northeast-2.amazonaws.com/my-modules/vpc.zip"
}
# Git (SSH)
module "vpc" {
source = "git::ssh://git@github.com/myorg/modules.git//network?ref=v1.0.0"
}
7.3 模块相关命令
# 下载/更新模块
terraform init -upgrade
# 查看模块使用的 Provider
terraform providers
# 更新 Provider 锁定文件(支持多平台)
terraform providers lock \
-platform=linux_amd64 \
-platform=darwin_arm64
# Provider 镜像(气隙环境)
terraform providers mirror /path/to/mirror
8. Terraform 高级功能
8.1 terraform console — 交互式控制台
# 交互式表达式求值
terraform console
# 使用示例
> var.instance_type
"t3.medium"
> length(var.subnet_ids)
3
> cidrsubnet("10.0.0.0/16", 8, 1)
"10.0.1.0/24"
> formatdate("YYYY-MM-DD", timestamp())
"2026-03-01"
> [for s in var.subnet_ids : upper(s)]
["SUBNET-AAA", "SUBNET-BBB", "SUBNET-CCC"]
# 退出:Ctrl+D 或 exit
8.2 terraform graph — 依赖图
# 以 DOT 格式输出依赖图
terraform graph
# 用 Graphviz 生成图片
terraform graph | dot -Tpng > graph.png
terraform graph | dot -Tsvg > graph.svg
# 基于 Plan 的图
terraform graph -type=plan
# 以特定资源为中心的图
terraform graph -draw-cycles
8.3 其他实用命令
# 显示当前配置使用的 Provider 树
terraform providers
# 以 JSON 输出 Provider 模式
terraform providers schema -json
# 查看 Terraform 版本
terraform version
# JSON 输出
terraform version -json
# 显示 Terraform 配置文件位置
terraform -help
# 特定命令的帮助
terraform plan -help
9. HCL 语法速查表
9.1 Variables(输入变量)
# variables.tf
# 基本类型
variable "region" {
description = "AWS Region"
type = string
default = "ap-northeast-2"
}
variable "instance_count" {
description = "Number of instances"
type = number
default = 2
}
variable "enable_monitoring" {
description = "Enable CloudWatch monitoring"
type = bool
default = true
}
# 复合类型 - List
variable "availability_zones" {
type = list(string)
default = ["ap-northeast-2a", "ap-northeast-2c"]
}
# 复合类型 - Map
variable "instance_types" {
type = map(string)
default = {
dev = "t3.micro"
prod = "t3.large"
}
}
# 复合类型 - Object
variable "database_config" {
type = object({
engine = string
engine_version = string
instance_class = string
multi_az = bool
storage_gb = number
})
default = {
engine = "mysql"
engine_version = "8.0"
instance_class = "db.t3.medium"
multi_az = true
storage_gb = 100
}
}
# 敏感变量
variable "db_password" {
description = "Database master password"
type = string
sensitive = true # 在 Plan/Apply 输出中被掩码
}
# Validation Rule
variable "instance_type" {
type = string
validation {
condition = can(regex("^t3\\.", var.instance_type))
error_message = "Instance type must start with t3."
}
}
# Nullable
variable "override_name" {
type = string
default = null
nullable = true
}
传入变量值的方式(按优先级从高到低):
# 1. CLI -var 标志(最高优先级)
terraform apply -var="region=us-east-1"
# 2. -var-file 标志
terraform apply -var-file="prod.tfvars"
# 3. *.auto.tfvars(自动加载)
# terraform.tfvars, *.auto.tfvars
# 4. 环境变量(TF_VAR_ 前缀)
export TF_VAR_region="us-east-1"
export TF_VAR_db_password="SuperSecret123!"
# 5. default 值
9.2 Locals(局部变量)
locals {
project_name = "my-app"
environment = terraform.workspace
common_tags = {
Project = local.project_name
Environment = local.environment
ManagedBy = "terraform"
Team = "platform"
}
# 条件值
is_prod = local.environment == "prod"
# 计算值
name_prefix = "${local.project_name}-${local.environment}"
}
resource "aws_instance" "app" {
# ...
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-app"
})
}
9.3 Outputs(输出值)
# outputs.tf
output "vpc_id" {
description = "The ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "List of public subnet IDs"
value = aws_subnet.public[*].id
}
output "db_endpoint" {
description = "RDS endpoint"
value = aws_db_instance.main.endpoint
sensitive = true # 掩码敏感输出值
}
# 在其他模块中引用
# module.network.vpc_id
9.4 count 与 for_each
# count — 创建 N 个相同的资源
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = var.availability_zones[count.index]
tags = {
Name = "public-subnet-${count.index}"
}
}
# 引用:aws_subnet.public[0], aws_subnet.public[1]
# for_each — 基于 Map 或 Set 的循环(推荐)
resource "aws_iam_user" "users" {
for_each = toset(["alice", "bob", "charlie"])
name = each.value
}
# 基于 Map 的 for_each
variable "instances" {
default = {
web = { type = "t3.small", az = "ap-northeast-2a" }
api = { type = "t3.medium", az = "ap-northeast-2c" }
worker = { type = "t3.large", az = "ap-northeast-2a" }
}
}
resource "aws_instance" "servers" {
for_each = var.instances
ami = data.aws_ami.ubuntu.id
instance_type = each.value.type
availability_zone = each.value.az
tags = {
Name = "${each.key}-server"
}
}
# 引用:aws_instance.servers["web"], aws_instance.servers["api"]
9.5 dynamic Block
# 动态生成安全组规则
variable "ingress_rules" {
default = [
{ port = 80, cidr = "0.0.0.0/0", description = "HTTP" },
{ port = 443, cidr = "0.0.0.0/0", description = "HTTPS" },
{ port = 22, cidr = "10.0.0.0/8", description = "SSH (internal)" },
]
}
resource "aws_security_group" "web" {
name = "web-sg"
description = "Web server security group"
vpc_id = aws_vpc.main.id
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = "tcp"
cidr_blocks = [ingress.value.cidr]
description = ingress.value.description
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
9.6 lifecycle 元参数
resource "aws_instance" "app" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
lifecycle {
# 删除之前先创建新资源(把停机时间降到最低)
create_before_destroy = true
# 忽略特定属性的变更(例如被外部修改的标签)
ignore_changes = [
tags["LastModified"],
ami,
]
# 防止删除(避免误执行 destroy)
prevent_destroy = true
# 前置/后置条件
precondition {
condition = var.instance_type != "t3.nano"
error_message = "t3.nano is too small for this application."
}
postcondition {
condition = self.public_ip != ""
error_message = "Instance must have a public IP."
}
# 资源替换触发器(值变化时重建资源)
replace_triggered_by = [
aws_ami.app_ami.id
]
}
}
10. Terraform 实战示例 — AWS VPC + EC2 + RDS
10.1 项目结构
terraform-aws-project/
├── main.tf # Provider, Backend 配置
├── variables.tf # 输入变量定义
├── outputs.tf # 输出值定义
├── terraform.tfvars # 变量值(应加入 gitignore)
├── network.tf # VPC, Subnet, IGW, NAT, Route Table
├── compute.tf # EC2, Security Group, Key Pair
├── database.tf # RDS, Subnet Group
├── data.tf # Data Sources(AMI 查询等)
└── versions.tf # Provider/Terraform 版本约束
10.2 完整代码
# versions.tf
terraform {
required_version = ">= 1.9.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.80"
}
}
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
dynamodb_table = "terraform-lock"
}
}
# main.tf
provider "aws" {
region = var.region
default_tags {
tags = {
Project = var.project_name
Environment = terraform.workspace
ManagedBy = "terraform"
}
}
}
# variables.tf
variable "region" {
type = string
default = "ap-northeast-2"
}
variable "project_name" {
type = string
default = "myapp"
}
variable "vpc_cidr" {
type = string
default = "10.0.0.0/16"
}
variable "azs" {
type = list(string)
default = ["ap-northeast-2a", "ap-northeast-2c"]
}
variable "db_password" {
type = string
sensitive = true
}
# data.tf
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
}
}
# network.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = { Name = "${var.project_name}-vpc" }
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = { Name = "${var.project_name}-igw" }
}
resource "aws_subnet" "public" {
count = length(var.azs)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = var.azs[count.index]
map_public_ip_on_launch = true
tags = { Name = "${var.project_name}-public-${count.index}" }
}
resource "aws_subnet" "private" {
count = length(var.azs)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 10)
availability_zone = var.azs[count.index]
tags = { Name = "${var.project_name}-private-${count.index}" }
}
resource "aws_eip" "nat" {
domain = "vpc"
tags = { Name = "${var.project_name}-nat-eip" }
}
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public[0].id
tags = { Name = "${var.project_name}-nat" }
depends_on = [aws_internet_gateway.main]
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = { Name = "${var.project_name}-public-rt" }
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
tags = { Name = "${var.project_name}-private-rt" }
}
resource "aws_route_table_association" "public" {
count = length(var.azs)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "private" {
count = length(var.azs)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private.id
}
# compute.tf
resource "aws_security_group" "web" {
name = "${var.project_name}-web-sg"
description = "Security group for web servers"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTP"
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTPS"
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
description = "SSH internal"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = { Name = "${var.project_name}-web-sg" }
}
resource "aws_instance" "web" {
count = 2
ami = data.aws_ami.ubuntu.id
instance_type = "t3.small"
subnet_id = aws_subnet.public[count.index % length(var.azs)].id
vpc_security_group_ids = [aws_security_group.web.id]
root_block_device {
volume_size = 30
volume_type = "gp3"
encrypted = true
}
tags = { Name = "${var.project_name}-web-${count.index}" }
lifecycle {
create_before_destroy = true
ignore_changes = [ami]
}
}
# database.tf
resource "aws_security_group" "db" {
name = "${var.project_name}-db-sg"
description = "Security group for RDS"
vpc_id = aws_vpc.main.id
ingress {
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.web.id]
description = "MySQL from web servers"
}
tags = { Name = "${var.project_name}-db-sg" }
}
resource "aws_db_subnet_group" "main" {
name = "${var.project_name}-db-subnet-group"
subnet_ids = aws_subnet.private[*].id
tags = { Name = "${var.project_name}-db-subnet-group" }
}
resource "aws_db_instance" "main" {
identifier = "${var.project_name}-mysql"
engine = "mysql"
engine_version = "8.0"
instance_class = "db.t3.medium"
allocated_storage = 100
storage_type = "gp3"
storage_encrypted = true
db_name = "myapp"
username = "admin"
password = var.db_password
multi_az = true
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.db.id]
skip_final_snapshot = false
final_snapshot_identifier = "${var.project_name}-final-snapshot"
backup_retention_period = 7
tags = { Name = "${var.project_name}-mysql" }
lifecycle {
prevent_destroy = true
}
}
# outputs.tf
output "vpc_id" {
value = aws_vpc.main.id
}
output "public_subnet_ids" {
value = aws_subnet.public[*].id
}
output "web_instance_ips" {
value = aws_instance.web[*].public_ip
}
output "rds_endpoint" {
value = aws_db_instance.main.endpoint
sensitive = true
}
Part 2: Ansible 完全指南
11. Ansible 简介与架构
11.1 什么是 Ansible?
Ansible 是由 Red Hat 维护的开源自动化工具,由 Michael DeHaan 于 2012 年创建。它可以用一套工具同时完成 Configuration Management、Application Deployment 和 Task Automation。
Ansible 的核心理念:
- Agentless:目标服务器上无需安装代理(基于 SSH/WinRM)
- Idempotent:同一个 Playbook 执行多次,结果保持一致
- 基于 YAML:不是编程语言,而是用 YAML 编写,入门门槛低
- Push 模型:由 Control Node 把任务推送到 Managed Node
11.2 Ansible 架构
┌──────────────────────────────────────────────────────────────────┐
│ Ansible Architecture │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Control Node │ │
│ │ │ │
│ │ ┌────────────┐ ┌──────────┐ ┌────────────┐ │ │
│ │ │ Playbook │ │ Inventory │ │ Modules │ │ │
│ │ │ (.yml) │ │ (hosts) │ │ (2,500+) │ │ │
│ │ └─────┬──────┘ └────┬─────┘ └──────┬─────┘ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ Ansible Engine │ │ │
│ │ │ │ │ │
│ │ │ - Task Execution │ │ │
│ │ │ - Variable Resolution │ │ │
│ │ │ - Connection Management (SSH/WinRM) │ │ │
│ │ │ - Fact Gathering │ │ │
│ │ └─────────────┬─────────────────────────────┘ │ │
│ └────────────────┼─────────────────────────────────┘ │
│ │ │
│ SSH / WinRM (No Agent!) │
│ │ │
│ ┌─────────────┼──────────────────────────────┐ │
│ │ ▼ │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │
│ │ │ Web1 │ │ Web2 │ │ DB1 │ │ DB2 │ │ │
│ │ └──────┘ └──────┘ └──────┘ └──────┘ │ │
│ │ Managed Nodes │ │
│ └────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
- Control Node:安装并运行 Ansible 的节点(Linux/macOS,Windows 需要 WSL)
- Managed Node:Ansible 管理的目标服务器(只要能 SSH 连接即可)
- Inventory:管理对象主机列表
- Module:执行实际任务的代码单元(2,500+ 内置模块)
- Playbook:定义任务顺序的 YAML 文件
12. Ansible 安装
# ── 用 pip 安装(推荐)──
pip install ansible
# 安装指定版本
pip install ansible==10.6.0
# 只安装 ansible-core(最小安装)
pip install ansible-core
# ── macOS (Homebrew) ──
brew install ansible
# ── Ubuntu/Debian ──
sudo apt update
sudo apt install ansible
# ── RHEL/CentOS/Fedora ──
sudo dnf install ansible
# ── 查看版本 ──
ansible --version
# ansible [core 2.17.7]
# config file = /etc/ansible/ansible.cfg
# configured module search path = ['~/.ansible/plugins/modules']
# ansible python module location = /usr/lib/python3.12/site-packages/ansible
# python version = 3.12.8
# ── 安装 ansible-navigator(TUI,支持 Execution Environment)──
pip install ansible-navigator
13. Ansible 清单
13.1 静态清单(INI 格式)
# inventory/hosts.ini
# 单个主机
web1.example.com
web2.example.com
# 组定义
[webservers]
web1.example.com ansible_host=10.0.1.10
web2.example.com ansible_host=10.0.1.11
[dbservers]
db1.example.com ansible_host=10.0.2.10 ansible_port=2222
db2.example.com ansible_host=10.0.2.11
[monitoring]
grafana.example.com
# 组的变量
[webservers:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/web_key.pem
http_port=80
[dbservers:vars]
ansible_user=ec2-user
ansible_ssh_private_key_file=~/.ssh/db_key.pem
# 组的组(Children)
[production:children]
webservers
dbservers
monitoring
[production:vars]
env=production
# 主机范围模式
[loadbalancers]
lb[01:03].example.com # lb01, lb02, lb03
13.2 静态清单(YAML 格式)
# inventory/hosts.yml
all:
children:
production:
children:
webservers:
hosts:
web1.example.com:
ansible_host: 10.0.1.10
web2.example.com:
ansible_host: 10.0.1.11
vars:
ansible_user: ubuntu
ansible_ssh_private_key_file: ~/.ssh/web_key.pem
http_port: 80
dbservers:
hosts:
db1.example.com:
ansible_host: 10.0.2.10
db2.example.com:
ansible_host: 10.0.2.11
vars:
ansible_user: ec2-user
vars:
env: production
staging:
children:
webservers_stg:
hosts:
stg-web1.example.com:
ansible_host: 10.1.1.10
13.3 动态清单
动态清单会从云 API 实时获取主机列表。
# inventory/aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
- ap-northeast-2
keyed_groups:
- key: tags.Environment
prefix: env
- key: instance_type
prefix: type
- key: placement.availability_zone
prefix: az
filters:
tag:ManagedBy: ansible
instance-state-name: running
compose:
ansible_host: private_ip_address
# 测试动态清单
ansible-inventory -i inventory/aws_ec2.yml --list
ansible-inventory -i inventory/aws_ec2.yml --graph
13.4 清单相关命令
# 查看清单主机列表
ansible-inventory -i inventory/hosts.yml --list
ansible-inventory -i inventory/hosts.yml --graph
# 只显示特定组
ansible-inventory -i inventory/hosts.yml --graph webservers
# 查看主机变量
ansible-inventory -i inventory/hosts.yml --host web1.example.com
# JSON 输出
ansible-inventory -i inventory/hosts.yml --list --yaml
14. Ansible Ad-hoc 命令
Ad-hoc 命令是不需要 Playbook、用一行就能执行的一次性命令。适合快速确认或简单操作。
14.1 基本语法
ansible [主机模式] -i [清单] -m [模块] -a "[参数]" [选项]
14.2 主要模块示例
# ── ping: 确认连通性 ──
ansible all -i inventory/hosts.yml -m ping
ansible webservers -i inventory/hosts.yml -m ping
# ── command: 执行命令(默认模块,不支持 shell 特性)──
ansible webservers -m command -a "uptime"
ansible webservers -m command -a "df -h"
ansible webservers -m command -a "free -m"
# ── shell: 执行 shell 命令(支持管道、重定向)──
ansible webservers -m shell -a "ps aux | grep nginx | wc -l"
ansible webservers -m shell -a "cat /etc/os-release | head -5"
ansible dbservers -m shell -a "mysql -e 'SHOW DATABASES;'"
# ── copy: 复制文件 ──
ansible webservers -m copy -a "src=./app.conf dest=/etc/nginx/conf.d/app.conf owner=root group=root mode=0644"
# ── file: 文件/目录管理 ──
ansible webservers -m file -a "path=/opt/app state=directory owner=www-data mode=0755"
ansible webservers -m file -a "path=/tmp/old_file state=absent"
ansible webservers -m file -a "src=/etc/nginx/sites-available/app dest=/etc/nginx/sites-enabled/app state=link"
# ── apt: 包管理(Debian/Ubuntu)──
ansible webservers -m apt -a "name=nginx state=present update_cache=yes" --become
ansible webservers -m apt -a "name=nginx state=latest" --become
ansible webservers -m apt -a "name=nginx state=absent" --become
ansible webservers -m apt -a "upgrade=dist" --become
# ── yum/dnf: 包管理(RHEL/CentOS)──
ansible dbservers -m dnf -a "name=mysql-server state=present" --become
# ── service/systemd: 服务管理 ──
ansible webservers -m service -a "name=nginx state=started enabled=yes" --become
ansible webservers -m service -a "name=nginx state=restarted" --become
ansible webservers -m service -a "name=nginx state=stopped" --become
ansible webservers -m systemd -a "name=nginx state=reloaded daemon_reload=yes" --become
# ── user: 用户管理 ──
ansible all -m user -a "name=deploy state=present groups=sudo shell=/bin/bash" --become
ansible all -m user -a "name=olduser state=absent remove=yes" --become
# ── setup: 收集 Fact(系统信息)──
ansible webservers -m setup
ansible webservers -m setup -a "filter=ansible_os_family"
ansible webservers -m setup -a "filter=ansible_memtotal_mb"
ansible webservers -m setup -a "filter=ansible_distribution*"
# ── lineinfile: 管理文件中的行 ──
ansible webservers -m lineinfile -a "path=/etc/ssh/sshd_config regexp='^PermitRootLogin' line='PermitRootLogin no'" --become
# ── cron: 定时任务管理 ──
ansible webservers -m cron -a "name='log cleanup' minute='0' hour='3' job='find /var/log -name \"*.gz\" -mtime +30 -delete'"
# ── get_url: 从 URL 下载文件 ──
ansible webservers -m get_url -a "url=https://example.com/app.tar.gz dest=/tmp/app.tar.gz"
# ── git: 克隆/拉取 Git 仓库 ──
ansible webservers -m git -a "repo=https://github.com/myorg/myapp.git dest=/opt/myapp version=main"
14.3 Ad-hoc 主要选项
# 主要选项
-i inventory/hosts.yml # 指定清单文件
-m module_name # 指定模块(默认:command)
-a "arguments" # 模块参数
--become (-b) # sudo 提权
--become-user root # 提权目标用户
--become-method sudo # 提权方式
-u ubuntu # SSH 登录用户
--private-key ~/.ssh/key # SSH 密钥
-f 10 # 并行执行数(默认:5)
--limit web1 # 只在特定主机上执行
-v / -vv / -vvv / -vvvv # 详细输出级别
--check # Dry-run(不做实际变更)
--diff # 显示变更内容 diff
-o # 单行输出(摘要)
--ask-pass (-k) # 提示输入 SSH 密码
--ask-become-pass (-K) # 提示输入 sudo 密码
15. Ansible Playbook
15.1 ansible-playbook 命令选项
# 基本执行
ansible-playbook -i inventory/hosts.yml playbook.yml
# 主要选项
ansible-playbook playbook.yml \
-i inventory/hosts.yml \ # 清单
--limit webservers \ # 限定目标主机
--tags "nginx,ssl" \ # 只执行特定标签
--skip-tags "debug" \ # 跳过特定标签
-e "env=prod version=2.1" \ # 传入额外变量
-e @vars/prod.yml \ # 传入变量文件
--check \ # Dry-run
--diff \ # 变更内容 diff
--start-at-task "Install Nginx" \ # 从特定任务开始
--step \ # 每个任务都弹出确认提示
--list-tasks \ # 只列出任务清单
--list-tags \ # 只列出标签清单
--list-hosts \ # 只列出目标主机清单
-f 20 \ # 并行执行数
--become \ # sudo
--vault-password-file .vault_pass \ # Vault 密码文件
--ask-vault-pass \ # 提示输入 Vault 密码
-v # 详细输出
# 语法检查
ansible-playbook playbook.yml --syntax-check
15.2 Playbook 基本结构
# site.yml
---
- name: Configure web servers
hosts: webservers
become: yes
gather_facts: yes
vars:
http_port: 80
app_version: '2.1.0'
pre_tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
tasks:
- name: Install Nginx
apt:
name: nginx
state: present
tags: [nginx]
- name: Deploy Nginx config
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
notify: Restart Nginx
tags: [nginx, config]
- name: Deploy application
git:
repo: 'https://github.com/myorg/myapp.git'
dest: /opt/myapp
version: '{{ app_version }}'
tags: [deploy]
- name: Ensure Nginx is running
service:
name: nginx
state: started
enabled: yes
tags: [nginx]
post_tasks:
- name: Verify HTTP response
uri:
url: 'http://localhost:{{ http_port }}'
status_code: 200
register: result
retries: 3
delay: 5
until: result.status == 200
handlers:
- name: Restart Nginx
service:
name: nginx
state: restarted
15.3 条件语句(when)
tasks:
# 基本条件
- name: Install Apache (Debian)
apt:
name: apache2
state: present
when: ansible_os_family == "Debian"
- name: Install httpd (RedHat)
dnf:
name: httpd
state: present
when: ansible_os_family == "RedHat"
# 复合条件
- name: Configure for production
template:
src: prod.conf.j2
dest: /etc/app/app.conf
when:
- env == "prod"
- ansible_memtotal_mb >= 4096
# OR 条件
- name: Install on Debian or Ubuntu
apt:
name: curl
state: present
when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"
# 变量是否存在
- name: Configure custom DNS
template:
src: resolv.conf.j2
dest: /etc/resolv.conf
when: custom_dns is defined
# 基于上一个任务的结果
- name: Check if config exists
stat:
path: /etc/app/app.conf
register: config_file
- name: Create default config
template:
src: default.conf.j2
dest: /etc/app/app.conf
when: not config_file.stat.exists
15.4 循环语句(loop)
tasks:
# 基本 loop
- name: Install packages
apt:
name: '{{ item }}'
state: present
loop:
- nginx
- python3
- git
- curl
- htop
# 更高效的写法(一次性安装)
- name: Install packages (optimized)
apt:
name:
- nginx
- python3
- git
state: present
# Dict loop
- name: Create users
user:
name: '{{ item.name }}'
groups: '{{ item.groups }}'
shell: '{{ item.shell }}'
state: present
loop:
- { name: 'alice', groups: 'sudo', shell: '/bin/bash' }
- { name: 'bob', groups: 'developers', shell: '/bin/zsh' }
- { name: 'charlie', groups: 'developers', shell: '/bin/bash' }
# with_fileglob(文件通配)
- name: Copy all config files
copy:
src: '{{ item }}'
dest: /etc/app/conf.d/
with_fileglob:
- 'files/configs/*.conf'
# loop_control
- name: Create directories with index
file:
path: '/opt/app/data-{{ idx }}'
state: directory
loop:
- logs
- cache
- uploads
loop_control:
index_var: idx
label: '{{ item }}' # 输出中显示的标签(隐藏敏感数据)
15.5 Handlers
tasks:
- name: Update Nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify:
- Validate Nginx config
- Restart Nginx
- name: Update SSL certificate
copy:
src: ssl/cert.pem
dest: /etc/ssl/certs/app.pem
notify: Restart Nginx
handlers:
# Handler 只在被 notify 时执行,即使被 notify 多次也只执行一次
- name: Validate Nginx config
command: nginx -t
listen: 'Validate Nginx config'
- name: Restart Nginx
service:
name: nginx
state: restarted
listen: 'Restart Nginx'
15.6 Block(错误处理)
tasks:
- name: Application deployment with rollback
block:
- name: Pull latest code
git:
repo: 'https://github.com/myorg/myapp.git'
dest: /opt/myapp
version: '{{ app_version }}'
- name: Install dependencies
pip:
requirements: /opt/myapp/requirements.txt
virtualenv: /opt/myapp/venv
- name: Run database migrations
command: /opt/myapp/venv/bin/python manage.py migrate
args:
chdir: /opt/myapp
- name: Restart application
systemd:
name: myapp
state: restarted
rescue:
- name: Rollback to previous version
git:
repo: 'https://github.com/myorg/myapp.git'
dest: /opt/myapp
version: '{{ previous_version }}'
- name: Restart application (rollback)
systemd:
name: myapp
state: restarted
- name: Send failure notification
slack:
token: '{{ slack_token }}'
channel: '#deploy'
msg: 'Deployment of {{ app_version }} FAILED. Rolled back to {{ previous_version }}.'
always:
- name: Clean up temp files
file:
path: /tmp/deploy_artifacts
state: absent
- name: Log deployment result
lineinfile:
path: /var/log/deployments.log
line: "{{ ansible_date_time.iso8601 }} - {{ app_version }} - {{ ansible_failed_task.name | default('SUCCESS') }}"
create: yes
16. Ansible Role
16.1 Role 结构
roles/
└── nginx/
├── tasks/
│ ├── main.yml # 主任务
│ ├── install.yml # 安装任务
│ └── configure.yml # 配置任务
├── handlers/
│ └── main.yml # 处理器
├── templates/
│ └── nginx.conf.j2 # Jinja2 模板
├── files/
│ └── index.html # 静态文件
├── vars/
│ └── main.yml # 变量(高优先级)
├── defaults/
│ └── main.yml # 默认值(低优先级)
├── meta/
│ └── main.yml # 元数据、依赖
├── tests/
│ ├── inventory
│ └── test.yml
└── README.md
16.2 ansible-galaxy 命令
# ── Role 管理 ──
# 初始化 Role(生成目录结构)
ansible-galaxy role init roles/nginx
ansible-galaxy role init --init-path=./roles nginx
# 从 Galaxy 安装 Role
ansible-galaxy role install geerlingguy.nginx
ansible-galaxy role install geerlingguy.docker -p roles/
# 安装指定版本
ansible-galaxy role install geerlingguy.nginx,3.1.0
# 从 requirements.yml 批量安装
ansible-galaxy role install -r requirements.yml
# 已安装 Role 列表
ansible-galaxy role list
# 删除 Role
ansible-galaxy role remove geerlingguy.nginx
# 搜索 Role
ansible-galaxy role search nginx --author geerlingguy
# 查看 Role 信息
ansible-galaxy role info geerlingguy.nginx
# ── Collection 管理 ──
# 安装 Collection
ansible-galaxy collection install amazon.aws
ansible-galaxy collection install community.general
# 从 requirements.yml 批量安装
ansible-galaxy collection install -r requirements.yml
# 已安装 Collection 列表
ansible-galaxy collection list
# 构建 Collection
ansible-galaxy collection build
# 发布 Collection
ansible-galaxy collection publish ./myns-mycoll-1.0.0.tar.gz
requirements.yml 示例:
# requirements.yml
---
roles:
- name: geerlingguy.nginx
version: '3.1.0'
- name: geerlingguy.docker
version: '7.4.1'
- name: geerlingguy.certbot
- src: https://github.com/myorg/ansible-role-custom.git
scm: git
version: main
name: custom_role
collections:
- name: amazon.aws
version: '>=8.0.0'
- name: community.general
- name: ansible.posix
16.3 Role 使用示例
# site.yml
---
- name: Configure web servers
hosts: webservers
become: yes
roles:
# 基本用法
- nginx
# 传入变量
- role: nginx
vars:
nginx_worker_processes: 4
nginx_worker_connections: 2048
# 条件执行
- role: certbot
when: enable_ssl | default(false)
# 指定标签
- role: monitoring
tags: [monitoring, observability]
17. Ansible Vault
Ansible Vault 是对敏感数据(密码、API 密钥、证书等)进行加密的功能。
17.1 Vault 命令
# ── 创建加密文件 ──
ansible-vault create secrets.yml
# 编辑器会打开,保存时自动加密
# 用指定编辑器创建
EDITOR=nano ansible-vault create secrets.yml
# ── 编辑加密文件 ──
ansible-vault edit secrets.yml
# ── 加密既有文件 ──
ansible-vault encrypt vars/prod_secrets.yml
# 同时加密多个文件
ansible-vault encrypt vars/secret1.yml vars/secret2.yml
# ── 解密加密文件 ──
ansible-vault decrypt vars/prod_secrets.yml
# ── 查看加密文件内容(不解密)──
ansible-vault view secrets.yml
# ── 修改密码 ──
ansible-vault rekey secrets.yml
# ── 加密字符串(内联)──
ansible-vault encrypt_string 'SuperSecretPassword123!' --name 'db_password'
# 输出:
# db_password: !vault |
# $ANSIBLE_VAULT;1.1;AES256
# 61636530396131653661313936353764...
ansible-vault encrypt_string --vault-password-file .vault_pass 'my_api_key_value' --name 'api_key'
# ── 传入 Vault 密码的方式 ──
# 1. 提示输入
ansible-playbook site.yml --ask-vault-pass
# 2. 文件
ansible-playbook site.yml --vault-password-file .vault_pass
# 3. 环境变量
export ANSIBLE_VAULT_PASSWORD_FILE=.vault_pass
ansible-playbook site.yml
# 4. 脚本(对接密码管理工具)
ansible-playbook site.yml --vault-password-file ./get_vault_pass.sh
# ── 使用多个 Vault ID(按环境使用不同密码)──
ansible-vault create --vault-id prod@prompt secrets_prod.yml
ansible-vault create --vault-id dev@.vault_pass_dev secrets_dev.yml
ansible-playbook site.yml \
--vault-id prod@prompt \
--vault-id dev@.vault_pass_dev
17.2 Vault 使用示例
# vars/secrets.yml(已加密)
---
db_password: 'SuperSecretPassword123!'
api_key: 'sk-1234567890abcdef'
ssl_private_key: |
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASC...
-----END PRIVATE KEY-----
# 在 playbook 中使用
- name: Deploy application
hosts: webservers
become: yes
vars_files:
- vars/defaults.yml
- vars/secrets.yml # 用 Vault 加密的文件
tasks:
- name: Configure database connection
template:
src: db_config.j2
dest: /etc/app/database.yml
mode: '0600'
18. Ansible 高级功能
18.1 ansible-doc — 模块文档
# 查看模块帮助
ansible-doc apt
ansible-doc copy
ansible-doc template
ansible-doc amazon.aws.ec2_instance
# 查看模块列表
ansible-doc --list
ansible-doc --list | grep aws
# 简短帮助(使用示例)
ansible-doc -s apt
ansible-doc -s copy
# 按插件类型查看文档
ansible-doc -t callback -l # Callback 插件列表
ansible-doc -t connection -l # Connection 插件列表
ansible-doc -t inventory -l # Inventory 插件列表
ansible-doc -t lookup -l # Lookup 插件列表
18.2 ansible-navigator — 基于 TUI 的工具
# 以 TUI 模式运行 Playbook
ansible-navigator run site.yml -i inventory/hosts.yml
# stdout 模式(输出与传统 ansible-playbook 类似)
ansible-navigator run site.yml -i inventory/hosts.yml -m stdout
# 浏览清单
ansible-navigator inventory -i inventory/hosts.yml
# 浏览模块文档
ansible-navigator doc apt
# 浏览 Collection
ansible-navigator collections
# 查看配置
ansible-navigator config
18.3 ansible-lint — 代码质量
# 安装
pip install ansible-lint
# 对 Playbook 做 lint
ansible-lint site.yml
# 对整个目录做 lint
ansible-lint
# 忽略特定规则
ansible-lint -x yaml[truthy]
# 自动修复
ansible-lint --fix
18.4 ansible.cfg 配置
# ansible.cfg
[defaults]
inventory = inventory/hosts.yml
remote_user = ubuntu
private_key_file = ~/.ssh/id_rsa
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
callback_whitelist = timer, profile_tasks
forks = 20
timeout = 30
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 86400
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no
control_path_dir = ~/.ansible/cp
Part 3: 集成、速查表与故障排查
19. Terraform + Ansible 集成
19.1 集成架构
把 Terraform 与 Ansible 联动起来,最常见的模式如下。
┌─────────────────────────────────────────────────────────────┐
│ Terraform + Ansible Integration │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Terraform으로 인프라 프로비저닝 │
│ terraform apply │
│ │ │
│ ├── VPC, Subnet, SG 생성 │
│ ├── EC2 인스턴스 생성 │
│ └── terraform output -json │
│ │ │
│ ▼ │
│ 2. Terraform Output → Ansible Dynamic Inventory │
│ terraform output -json > tf_output.json │
│ │ │
│ ▼ │
│ 3. Ansible로 Configuration Management │
│ ansible-playbook -i dynamic_inventory.py site.yml │
│ │ │
│ ├── 패키지 설치 │
│ ├── 애플리케이션 설정 │
│ └── 서비스 시작 │
│ │
└─────────────────────────────────────────────────────────────┘
19.2 在 Ansible 中使用 Terraform Output
# Terraform outputs.tf
output "web_server_ips" {
value = aws_instance.web[*].public_ip
}
output "db_endpoint" {
value = aws_db_instance.main.endpoint
sensitive = true
}
output "ssh_key_name" {
value = aws_key_pair.deployer.key_name
}
#!/usr/bin/env python3
# dynamic_inventory.py — 基于 Terraform Output 的动态清单
import json
import subprocess
import sys
def get_terraform_output():
result = subprocess.run(
["terraform", "output", "-json"],
capture_output=True, text=True
)
return json.loads(result.stdout)
def main():
tf_output = get_terraform_output()
web_ips = tf_output["web_server_ips"]["value"]
inventory = {
"webservers": {
"hosts": web_ips,
"vars": {
"ansible_user": "ubuntu",
"ansible_ssh_private_key_file": "~/.ssh/deployer.pem",
"db_endpoint": tf_output["db_endpoint"]["value"]
}
},
"_meta": {
"hostvars": {}
}
}
print(json.dumps(inventory, indent=2))
if __name__ == "__main__":
main()
# 执行
chmod +x dynamic_inventory.py
ansible-playbook -i dynamic_inventory.py site.yml
19.3 用 Terraform local-exec Provisioner 调用 Ansible
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.small"
key_name = aws_key_pair.deployer.key_name
# 执行 Ansible(Provisioner 只应作为最后手段使用)
provisioner "local-exec" {
command = <<-EOT
sleep 30 # 等待 SSH 就绪
ANSIBLE_HOST_KEY_CHECKING=False \
ansible-playbook \
-i '${self.public_ip},' \
-u ubuntu \
--private-key ~/.ssh/deployer.pem \
-e "db_endpoint=${aws_db_instance.main.endpoint}" \
ansible/site.yml
EOT
}
depends_on = [aws_db_instance.main]
}
19.4 Terraform + Ansible 自动化脚本
#!/bin/bash
# deploy.sh — 完整的基础设施 + 配置部署
set -euo pipefail
echo "=== Step 1: Terraform Init ==="
terraform init -input=false
echo "=== Step 2: Terraform Plan ==="
terraform plan -out=tfplan -input=false
echo "=== Step 3: Terraform Apply ==="
terraform apply tfplan
echo "=== Step 4: Generate Ansible Inventory ==="
terraform output -json > tf_output.json
echo "=== Step 5: Wait for instances to be ready ==="
WEB_IPS=$(terraform output -json web_server_ips | jq -r '.[]')
for ip in $WEB_IPS; do
echo "Waiting for $ip..."
until ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 ubuntu@"$ip" true 2>/dev/null; do
sleep 5
done
echo "$ip is ready!"
done
echo "=== Step 6: Run Ansible Playbook ==="
ANSIBLE_HOST_KEY_CHECKING=False \
ansible-playbook \
-i ./dynamic_inventory.py \
--vault-password-file .vault_pass \
site.yml
echo "=== Deployment Complete ==="
terraform output
20. Terraform 命令速查表 Top 20
# ── 初始化与校验 ──
terraform init # 1. 初始化项目
terraform init -upgrade # 2. 升级 Provider/模块
terraform validate # 3. 语法校验
terraform fmt -recursive -check # 4. 格式检查
# ── 核心工作流 ──
terraform plan -out=tfplan # 5. 保存执行计划
terraform apply tfplan # 6. 应用计划
terraform apply -auto-approve # 7. 自动批准应用(CI/CD)
terraform destroy -auto-approve # 8. 全部删除
# ── State 管理 ──
terraform state list # 9. 资源列表
terraform state show aws_instance.web # 10. 资源详情
terraform state mv OLD NEW # 11. 资源移动/重命名
terraform state rm RESOURCE # 12. 从 State 移除
terraform state pull > backup.tfstate # 13. State 备份
terraform force-unlock LOCK_ID # 14. 解除锁定
# ── Import ──
terraform import RESOURCE ID # 15. 导入既有资源
terraform plan -generate-config-out=g.tf # 16. 自动生成 Import 代码
# ── Workspace ──
terraform workspace new ENV # 17. 创建 Workspace
terraform workspace select ENV # 18. 切换 Workspace
# ── 实用工具 ──
terraform output -json # 19. 输出值(JSON)
terraform console # 20. 交互式控制台
21. Ansible 命令速查表 Top 20
# ── 连通性确认与信息收集 ──
ansible all -m ping # 1. 对全部主机 Ping
ansible all -m setup -a "filter=ansible_os*" # 2. 收集系统信息
# ── Ad-hoc 命令 ──
ansible web -m shell -a "uptime" # 3. 执行 shell 命令
ansible web -m apt -a "name=nginx state=present" -b # 4. 安装软件包
ansible web -m service -a "name=nginx state=started" -b # 5. 启动服务
ansible web -m copy -a "src=f dest=/etc/app/" -b # 6. 复制文件
ansible web -m user -a "name=deploy state=present" -b # 7. 创建用户
# ── 执行 Playbook ──
ansible-playbook site.yml # 8. 执行 Playbook
ansible-playbook site.yml --check --diff # 9. Dry-run + diff
ansible-playbook site.yml --limit web1 # 10. 只对特定主机
ansible-playbook site.yml --tags deploy # 11. 只执行特定标签
ansible-playbook site.yml -e "env=prod" # 12. 传入变量
ansible-playbook site.yml --syntax-check # 13. 语法检查
# ── Vault ──
ansible-vault create secrets.yml # 14. 创建加密文件
ansible-vault edit secrets.yml # 15. 编辑加密文件
ansible-vault encrypt file.yml # 16. 加密文件
ansible-vault decrypt file.yml # 17. 解密文件
ansible-vault encrypt_string 'secret' --name key # 18. 加密字符串
# ── Galaxy 与实用工具 ──
ansible-galaxy role install geerlingguy.nginx # 19. 安装 Role
ansible-doc -s apt # 20. 模块帮助
22. 故障排查
22.1 Terraform 故障排查
# ── 1. State Lock 问题 ──
# 错误:Error acquiring the state lock
# 原因:上一次 terraform apply 异常退出
# 解决:
terraform force-unlock LOCK_ID
# ── 2. Provider 认证失败 ──
# 错误:NoCredentialProviders
# 解决:确认 AWS 凭证
aws sts get-caller-identity
export AWS_PROFILE=myprofile
# ── 3. State 与实际基础设施不一致 ──
# 解决:刷新 State
terraform apply -refresh-only
# 从 State 移除特定资源后重新 Import
terraform state rm aws_instance.web
terraform import aws_instance.web i-0abc123def
# ── 4. Provider 版本冲突 ──
# 解决:重新生成锁定文件
rm .terraform.lock.hcl
terraform init -upgrade
# ── 5. 循环依赖 ──
# 错误:Cycle detected
# 解决:检查 depends_on,拆分资源
terraform graph | dot -Tpng > graph.png # 依赖可视化
# ── 6. 启用调试日志 ──
export TF_LOG=DEBUG # TRACE, DEBUG, INFO, WARN, ERROR
export TF_LOG_PATH=terraform.log
terraform apply
# ── 7. Plan 成功但 Apply 失败 ──
# 原因:API 权限不足、资源配额限制、网络问题
# 解决:用 -parallelism=1 顺序执行,定位准确的错误
terraform apply -parallelism=1
# ── 8. 大规模 State 的性能问题 ──
# 解决:拆分 State(拆成多个 Terraform 项目)
# 按 network/ compute/ database/ 等拆分,
# 再用 terraform_remote_state data source 连接
22.2 Ansible 故障排查
# ── 1. SSH 连接失败 ──
# 错误:UNREACHABLE!
# 调试:
ansible webservers -m ping -vvvv # 最详细的日志
# 直接测试 SSH
ssh -i ~/.ssh/key.pem -o StrictHostKeyChecking=no ubuntu@10.0.1.10
# ── 2. sudo 权限问题 ──
# 错误:Missing sudo password
# 解决:
ansible-playbook site.yml --ask-become-pass
# 或者在 ansible.cfg 中配置:
# [privilege_escalation]
# become_ask_pass = True
# ── 3. 找不到模块 ──
# 错误:MODULE FAILURE
# 解决:安装 Collection
ansible-galaxy collection install amazon.aws
# ── 4. Jinja2 模板错误 ──
# 错误:AnsibleUndefinedVariable
# 解决:确认变量定义
ansible-playbook site.yml -e "@vars/defaults.yml" --check
# 或者使用 default 过滤器: "{{ my_var | default('fallback') }}"
# ── 5. 性能优化 ──
# ansible.cfg 配置:
# [defaults]
# forks = 20 # 增加并行执行数
# gathering = smart # Fact 缓存
# [ssh_connection]
# pipelining = True # 启用 SSH 流水线
# ssh_args = -o ControlMaster=auto -o ControlPersist=60s
# ── 6. Vault 密码遗失 ──
# 解决:无法恢复。必须用新密码重新加密
# 在还记得密码的时候:
ansible-vault rekey secrets.yml
# ── 7. 清单解析错误 ──
# 调试:校验清单有效性
ansible-inventory -i inventory/hosts.yml --list --export
# ── 8. 幂等性被破坏(反复出现 changed)──
# 给 command/shell 模块加上 creates/removes 条件
- name: Initialize database
command: /opt/app/init_db.sh
args:
creates: /opt/app/.db_initialized
23. 最佳实践总结
23.1 Terraform 最佳实践
| 项目 | 推荐做法 |
|---|---|
| State 管理 | 使用 Remote Backend(S3 + DynamoDB),绝不提交到 Git |
| 目录结构 | 按环境拆分(envs/dev、envs/prod)或使用 Workspace |
| 代码评审 | 把 terraform plan 输出附到 PR 上 |
| 模块化 | 拆成可复用的模块,并打版本标签 |
| 变量管理 | 敏感变量用 sensitive = true,配合环境变量或 Vault |
| 锁定文件 | .terraform.lock.hcl 必须提交到 Git |
| CI/CD | Plan 在 PR 阶段执行,Apply 在 merge 后自动执行 |
| 命名规范 | 一致地使用 project-env-resource 模式 |
23.2 Ansible 最佳实践
| 项目 | 推荐做法 |
|---|---|
| 清单 | 云环境使用动态清单 |
| 敏感数据 | 必须用 Ansible Vault 加密 |
| Role 使用 | 把任务结构化为 Role,积极利用 Galaxy Role |
| 幂等性 | 用专用模块替代 command/shell,善用 creates/removes |
| 测试 | 用 --check --diff 做事前验证,用 Molecule 测试 Role |
| 变量优先级 | 理解变量优先级,把变量定义在合适的位置 |
| 标签运用 | 给所有任务打标签,让选择性执行成为可能 |
| 日志 | 用 callback_whitelist = timer, profile_tasks 做性能测量 |
24. 参考资料
24.1 官方文档
- Terraform:https://developer.hashicorp.com/terraform/docs
- Terraform Registry:https://registry.terraform.io
- OpenTofu:https://opentofu.org/docs
- Ansible:https://docs.ansible.com
- Ansible Galaxy:https://galaxy.ansible.com
24.2 推荐学习资源
- Terraform Up and Running(Yevgeniy Brikman)—— Terraform 圣经
- Ansible for DevOps(Jeff Geerling)—— Ansible 实战书
- HashiCorp Learn:https://developer.hashicorp.com/terraform/tutorials
- Jeff Geerling YouTube:大量 Ansible 实操视频
24.3 相关认证
- HashiCorp Certified: Terraform Associate (003):Terraform 基础认证
- Red Hat Certified Specialist in Ansible Automation (EX374):Ansible 专家认证
24.4 社区
- Terraform GitHub:https://github.com/hashicorp/terraform
- Ansible GitHub:https://github.com/ansible/ansible
- OpenTofu GitHub:https://github.com/opentofu/opentofu
- r/Terraform:Reddit 社区
- r/ansible:Reddit 社区
Terraform 与 Ansible 是现代基础设施自动化的两大支柱。用 Terraform 以声明式方式预配基础设施,再用 Ansible 在其之上配置软件,这一模式已经成为业界的事实标准。希望你把本文介绍的命令与模式落地到实际工作中,用代码替代重复的手工操作,并在团队里沉淀出安全、可预测地管理基础设施的文化。
현재 단락 (1/1446)
在云原生时代,手工管理基础设施已经不再是一个选项。如果几百台服务器、几十个 VPC、错综复杂的安全组与 IAM 策略都靠在控制台点鼠标来管理,那就是在批量制造 “**Snowflake Server*...