Skip to content
Published on

Terraform & Ansible 命令完全指南:IaC 与 Configuration Management 的全部要点

分享
Authors

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)正是 TerraformAnsible

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 + DeclarativeState 기반                    Agentless (SSH/WinRM)└──────────────────────────────────────────────────────────────────┘

1.3 本文的结构

本文大致分为三个部分。

  1. Part 1 — Terraform:从 HCL 语法到核心工作流、State 管理、Workspace、模块与高级功能
  2. Part 2 — Ansible:从清单、Ad-hoc 命令、Playbook、Role、Vault 到 Galaxy
  3. 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 FilesHCL 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

项目TerraformOpenTofuPulumi
许可证BSL 1.1MPL 2.0 (OSS)Apache 2.0
语言HCLHCLPython/Go/TS/C#
状态管理terraform.tfstateterraform.tfstatePulumi Cloud
Provider 生态3,000+兼容 Terraform100+
运营主体HashiCorp(IBM)Linux FoundationPulumi Inc.
CLI 命令terraformtofupulumi

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 OutputAnsible 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/devenvs/prod)或使用 Workspace
代码评审terraform plan 输出附到 PR 上
模块化拆成可复用的模块,并打版本标签
变量管理敏感变量用 sensitive = true,配合环境变量或 Vault
锁定文件.terraform.lock.hcl 必须提交到 Git
CI/CDPlan 在 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 官方文档

24.2 推荐学习资源

  • Terraform Up and Running(Yevgeniy Brikman)—— Terraform 圣经
  • Ansible for DevOps(Jeff Geerling)—— Ansible 实战书
  • HashiCorp Learnhttps://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 与 Ansible 是现代基础设施自动化的两大支柱。用 Terraform 以声明式方式预配基础设施,再用 Ansible 在其之上配置软件,这一模式已经成为业界的事实标准。希望你把本文介绍的命令与模式落地到实际工作中,用代码替代重复的手工操作,并在团队里沉淀出安全、可预测地管理基础设施的文化。