Provider 是什么?工作原理解析
terraform init 时,Terraform 会根据 required_providers 声明从 Terraform Registry 下载对应的 Provider 二进制文件。terraform 块内声明当前配置所需的所有 Provider,包括 source(注册表路径)和 version(版本约束)。这是 Terraform 0.13+ 的标准做法,比旧版在 provider 块内声明版本更明确、更规范。# terraform.tf — 统一声明所需 Provider(推荐单独文件)
terraform {
required_version = ">= 1.5" # 指定最低 Terraform 版本
required_providers {
# AWS Provider:最常用,source 格式为 "命名空间/名称"
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # ~> 5.0 意为 >= 5.0, < 6.0
}
# GCP Provider
google = {
source = "hashicorp/google"
version = ">= 5.0, < 6.0"
}
# random Provider:生成随机值(不与外部 API 通信)
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
# 社区 Provider 示例(Cloudflare)
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
}
}
版本约束语法详解
理解版本约束符号对于维护稳定的 Terraform 项目至关重要。版本号遵循语义化版本(SemVer):MAJOR.MINOR.PATCH,其中 MAJOR 变更意味着不兼容的 API 改动。
!= 5.45.0 表示跳过这个有问题的版本。~> 5.0,但更明确易读。可组合多个约束,用逗号分隔,所有约束必须同时满足。团队项目中推荐使用 ~> 5.0 或 ~> 5.50 这样的约束。>= 5.0 没有上限,6.0 发布后 terraform init -upgrade 可能拉取带有破坏性变更的新版本。执行 init 后,.terraform.lock.hcl 会锁定精确版本,应提交到 Git。
AWS Provider 配置
# provider.tf — AWS Provider 完整配置示例
provider "aws" {
region = var.aws_region # 使用变量,不硬编码
# 方式1:使用 Named Profile(~/.aws/credentials 或 ~/.aws/config 中定义)
profile = "my-work-account"
# 方式2:assume role(跨账号访问,推荐用于 CI/CD)
assume_role {
role_arn = "arn:aws:iam::123456789:role/TerraformRole"
session_name = "TerraformSession"
duration = "1h" # 临时凭证有效期
}
# default_tags:自动给所有支持 tags 的资源添加全局标签
# 无需在每个资源中重复声明这些标签
default_tags {
tags = {
ManagedBy = "Terraform"
Environment = var.environment
Team = "platform"
Repository = "github.com/my-org/infra"
}
}
# 重试配置:API 限速时自动重试
retry_mode = "adaptive"
max_retries = 3
}
# 多区域 Provider(使用 alias)
# 默认 provider:us-east-1(不加 alias)
provider "aws" {
region = "us-east-1"
}
# 别名 provider:us-west-2
provider "aws" {
alias = "us_west"
region = "us-west-2"
}
# 别名 provider:eu-west-1(欧洲合规数据存储)
provider "aws" {
alias = "eu_west"
region = "eu-west-1"
}
# 资源中通过 provider 参数指定使用哪个 provider 实例
resource "aws_s3_bucket" "west_bucket" {
provider = aws.us_west # 格式:provider_type.alias
bucket = "my-west-bucket"
}
resource "aws_s3_bucket" "eu_bucket" {
provider = aws.eu_west
bucket = "my-eu-data-bucket"
}
AWS 常用资源创建
以下示例展示了如何用 Terraform 创建一个完整的基础网络和计算层,包括 VPC、子网、安全组和 EC2 实例。这些资源之间存在严格的依赖关系,Terraform 会自动推断依赖顺序。
VPC 网络基础设施
# 1. VPC:虚拟私有网络,是所有网络资源的容器
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16" # 整个 VPC 的 IP 地址范围
enable_dns_hostnames = true # 允许实例获取 DNS 名称
enable_dns_support = true # 启用 VPC DNS 解析
tags = { Name = "main-vpc" }
}
# 2. 公有子网:路由到 Internet Gateway,实例可访问互联网
resource "aws_subnet" "public" {
count = 2 # 在 2 个可用区各建一个子网,实现高可用
vpc_id = aws_vpc.main.id # 引用上面 VPC 的 ID
cidr_block = "10.0.${count.index + 1}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true # 实例启动时自动分配公网 IP
tags = { Name = "public-${count.index + 1}", Type = "Public" }
}
# 3. 私有子网:无直接出站路由,通过 NAT Gateway 访问互联网
resource "aws_subnet" "private" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 10}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = { Name = "private-${count.index + 1}", Type = "Private" }
}
# 4. Internet Gateway:VPC 连接互联网的门户
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = { Name = "main-igw" }
}
# 5. 路由表:定义子网流量走向
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 # 路由到 IGW
}
tags = { Name = "public-rt" }
}
# 6. 关联路由表与公有子网
resource "aws_route_table_association" "public" {
count = 2
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
# 数据源:动态获取当前 region 的可用区列表(避免硬编码)
data "aws_availability_zones" "available" {
state = "available"
}
安全组与 EC2 实例
# 安全组:虚拟防火墙,控制进出实例的流量
resource "aws_security_group" "web" {
name = "web-sg"
description = "Web 服务器安全组"
vpc_id = aws_vpc.main.id
# ingress:入站规则(允许哪些流量进入实例)
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # HTTP:允许所有来源
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # HTTPS:允许所有来源
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"] # SSH:只允许内网访问(生产不开放公网 SSH)
}
# egress:出站规则(实例发出的流量)
egress {
from_port = 0
to_port = 0
protocol = "-1" # -1 表示所有协议
cidr_blocks = ["0.0.0.0/0"] # 允许所有出站流量
}
tags = { Name = "web-sg" }
}
# 数据源:动态查找最新的 Amazon Linux 2023 AMI
# 避免硬编码 AMI ID(不同区域 AMI ID 不同,且会定期更新)
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
filter {
name = "state"
values = ["available"]
}
}
# EC2 实例
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id # 使用数据源动态获取的 AMI
instance_type = "t3.micro"
subnet_id = aws_subnet.public[0].id
vpc_security_group_ids = [aws_security_group.web.id] # 列表,可附加多个安全组
key_name = aws_key_pair.deployer.key_name
# user_data:实例首次启动时执行的 shell 脚本(Bash heredoc)
user_data = <<-EOF
#!/bin/bash
dnf update -y
dnf install -y nginx
systemctl enable --now nginx
echo "<h1>Hello from $(hostname)</h1>" > /usr/share/nginx/html/index.html
EOF
# 启用终止保护(防止意外删除生产实例)
disable_api_termination = var.environment == "prod" ? true : false
# 根卷配置
root_block_device {
volume_type = "gp3"
volume_size = 20 # GB
encrypted = true # 加密根卷
}
tags = { Name = "web-server" }
}
# 导入 SSH 公钥(密钥对)
resource "aws_key_pair" "deployer" {
key_name = "deployer-key"
public_key = file("~/.ssh/id_rsa.pub") # 读取本地公钥文件
}
S3 存储桶完整配置
# 生成随机后缀,避免 S3 bucket 名称全局冲突
resource "random_id" "suffix" {
byte_length = 4 # 生成 8 位十六进制字符串(如 "a1b2c3d4")
}
# S3 Bucket 主体
resource "aws_s3_bucket" "assets" {
bucket = "my-assets-${random_id.suffix.hex}" # 全局唯一的 bucket 名称
tags = { Purpose = "static-assets" }
}
# 版本控制:保存对象的历史版本,支持恢复误删除的文件
resource "aws_s3_bucket_versioning" "assets" {
bucket = aws_s3_bucket.assets.id
versioning_configuration {
status = "Enabled" # Enabled / Suspended / Disabled
}
}
# 服务端加密:静态加密所有存储的对象
resource "aws_s3_bucket_server_side_encryption_configuration" "assets" {
bucket = aws_s3_bucket.assets.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms" # 使用 AWS KMS 管理密钥(更安全)
kms_master_key_id = aws_kms_key.s3.arn # 指定自定义 KMS 密钥
}
bucket_key_enabled = true # 减少 KMS API 调用次数,降低成本
}
}
# 阻止公共访问(所有 S3 bucket 默认应该阻止公共访问)
resource "aws_s3_bucket_public_access_block" "assets" {
bucket = aws_s3_bucket.assets.id
block_public_acls = true # 阻止设置公共 ACL
block_public_policy = true # 阻止公共 bucket 策略
ignore_public_acls = true # 忽略现有公共 ACL
restrict_public_buckets = true # 限制公共 bucket 访问
}
# 生命周期策略:自动管理对象存储层级和过期
resource "aws_s3_bucket_lifecycle_configuration" "assets" {
bucket = aws_s3_bucket.assets.id
rule {
id = "archive-old-versions"
status = "Enabled"
# 非当前版本(旧版本)在 30 天后转到 Glacier 节省成本
noncurrent_version_transition {
noncurrent_days = 30
storage_class = "GLACIER"
}
# 非当前版本在 90 天后彻底删除
noncurrent_version_expiration {
noncurrent_days = 90
}
}
}
AWS 认证方式详解
AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY、AWS_DEFAULT_REGION 等环境变量。AWS Provider 自动读取,无需在 provider 块中配置。GitHub Actions 等 CI 平台将密钥存在 Secrets 中,运行时注入为环境变量。aws configure 生成的凭证文件,支持多个 Named Profile(如 [work]、[personal])。在 provider 块中通过 profile = "work" 指定。assume_role 块让 Terraform 临时扮演另一个 IAM 角色。常用于:CI 账号承担生产账号的只读角色,或一个 Terraform 工作流管理多个 AWS 账号的资源。# ===== 环境变量认证(CI/CD 中常用)=====
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export AWS_DEFAULT_REGION="us-east-1"
# 可选:指定 Profile(覆盖 profile 配置)
export AWS_PROFILE="my-work-account"
# ===== GCP 认证 =====
# 方式1:服务账号密钥文件(适合 CI/CD,不推荐在本地使用)
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
# 方式2:ADC(Application Default Credentials)本地开发推荐
gcloud auth application-default login
# 这会在 ~/.config/gcloud/application_default_credentials.json 生成临时凭证
# Terraform google provider 会自动使用 ADC,无需任何 provider 块配置
# ===== Azure 认证 =====
# 方式1:Azure CLI 登录(本地开发)
az login
# 选择订阅
az account set --subscription "00000000-0000-0000-0000-000000000000"
# 方式2:Service Principal(CI/CD 使用)
export ARM_SUBSCRIPTION_ID="00000000-0000-0000-0000-000000000000"
export ARM_TENANT_ID="11111111-1111-1111-1111-111111111111"
export ARM_CLIENT_ID="22222222-2222-2222-2222-222222222222"
export ARM_CLIENT_SECRET="your-client-secret"
不要在 .tf 文件中硬编码 Access Key 或密码。应使用环境变量、AWS Secrets Manager、HashiCorp Vault 或 CI 平台内置的 Secrets 管理。
在 .gitignore 中始终添加:
*.tfvars(可能包含密码)
terraform.tfstate 和 terraform.tfstate.backup(包含敏感输出,见第4章)
*.tfvars.json(JSON 格式的变量文件,同样可能含密码)
多 Provider 配置:跨账号、跨云部署
# 场景:应用资源在账号 A,日志/监控资源在账号 B(隔离安全域)
# 账号 A 的 provider(默认,无 alias)
provider "aws" {
region = "us-east-1"
assume_role {
role_arn = "arn:aws:iam::111111111111:role/TerraformRole"
}
}
# 账号 B 的 provider(带 alias)
provider "aws" {
alias = "logging"
region = "us-east-1"
assume_role {
role_arn = "arn:aws:iam::222222222222:role/LoggingRole"
}
}
# 在账号 A 创建应用资源(省略 provider 参数,使用默认 provider)
resource "aws_ecs_cluster" "app" {
name = "production"
}
# 在账号 B 创建日志存储(显式指定别名 provider)
resource "aws_s3_bucket" "logs" {
provider = aws.logging # 这个资源在账号 B 创建
bucket = "central-logs-bucket"
}
# 将 provider 传递给模块(模块内使用别名 provider)
module "monitoring" {
source = "../../modules/monitoring"
providers = {
aws = aws.logging # 模块内的所有 aws 资源使用 aws.logging provider
}
}
# 混合多云:同一 Terraform 配置使用 AWS + GCP
resource "aws_ses_domain_identity" "email" {
domain = "example.com"
}
# 将 AWS SES 验证记录自动添加到 GCP Cloud DNS
resource "google_dns_record_set" "ses_verification" {
name = "_amazonses.example.com."
type = "TXT"
managed_zone = "example-com-zone"
rrdatas = [aws_ses_domain_identity.email.verification_token]
}
GCP 和 Azure Provider 完整示例
# GCP Provider 配置
provider "google" {
project = "my-gcp-project-id"
region = "us-central1"
zone = "us-central1-a"
# 认证:优先读 GOOGLE_APPLICATION_CREDENTIALS 环境变量
# 若未设置则使用 Application Default Credentials(本地 gcloud 登录)
}
# GCP Compute Engine VM 实例
resource "google_compute_instance" "web" {
name = "web-server"
machine_type = "e2-medium"
zone = "us-central1-a"
boot_disk {
initialize_params {
image = "debian-cloud/debian-11"
size = 20 # GB
type = "pd-ssd"
}
}
network_interface {
network = "default"
subnetwork = "default"
access_config {} # 空 access_config 块表示分配临时公网 IP
}
# 服务账号(最小权限原则)
service_account {
email = google_service_account.vm.email
scopes = ["cloud-platform"]
}
tags = ["web", "http-server"]
}
# GCP 防火墙规则(类似 AWS 安全组)
resource "google_compute_firewall" "allow_http" {
name = "allow-http"
network = "default"
allow {
protocol = "tcp"
ports = ["80", "443"]
}
target_tags = ["web"] # 规则应用到带 "web" 标签的实例
source_ranges = ["0.0.0.0/0"] # 来源:所有 IP
}
# Azure Provider 配置
provider "azurerm" {
features {
# features 块是必须的,即使为空;可配置删除行为
resource_group {
prevent_deletion_if_contains_resources = true # 防止误删含资源的 RG
}
virtual_machine {
delete_os_disk_on_deletion = true # 删除 VM 时同时删除系统盘
graceful_shutdown = false # 不等待 OS 优雅关机
}
}
# 认证:从 ARM_ 环境变量读取(推荐),或从 Azure CLI 登录状态读取
subscription_id = var.azure_subscription_id # 也可通过变量传入
}
# Azure Resource Group:所有 Azure 资源必须归属一个 Resource Group
resource "azurerm_resource_group" "main" {
name = "production-rg"
location = "East US"
}
# Azure Virtual Network(类似 AWS VPC)
resource "azurerm_virtual_network" "main" {
name = "main-vnet"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
address_space = ["10.0.0.0/16"]
}
# Azure Linux VM
resource "azurerm_linux_virtual_machine" "web" {
name = "web-vm"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
size = "Standard_B2s"
admin_username = "adminuser"
admin_ssh_key {
username = "adminuser"
public_key = file("~/.ssh/id_rsa.pub")
}
network_interface_ids = [azurerm_network_interface.web.id]
os_disk {
caching = "ReadWrite"
storage_account_type = "Premium_LRS" # SSD 类型
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts-gen2"
version = "latest"
}
}
Data Source:读取已有资源
Data Source(数据源)允许 Terraform 查询已有的云资源信息,而不创建新资源。这对于读取不由当前 Terraform 管理的资源(如共享的 VPC、其他团队创建的资源)非常有用。
# 查询已有的 VPC(由另一个 Terraform 工作区管理)
data "aws_vpc" "shared" {
tags = {
Name = "shared-vpc"
}
}
# 查询已有 VPC 的所有私有子网
data "aws_subnets" "private" {
filter {
name = "vpc-id"
values = [data.aws_vpc.shared.id]
}
tags = { Type = "Private" }
}
# 读取 AWS SSM Parameter Store 中存储的配置值
data "aws_ssm_parameter" "db_password" {
name = "/myapp/prod/db-password"
with_decryption = true # 解密 SecureString 类型的参数
}
# 使用数据源返回的值
resource "aws_db_instance" "main" {
db_subnet_group_name = aws_db_subnet_group.main.name
password = data.aws_ssm_parameter.db_password.value
# ...
}
# 读取当前 AWS 账号 ID 和 Region(无需硬编码)
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
# 在资源中引用
locals {
account_id = data.aws_caller_identity.current.account_id
region = data.aws_region.current.name
# 动态构建 ARN,无需硬编码账号 ID
role_arn = "arn:aws:iam::${local.account_id}:role/MyRole"
}
认证的优先级顺序(AWS)
AWS Provider 按以下顺序依次尝试认证方式,找到第一个有效的凭证后停止:
- provider 块中直接配置的
access_key/secret_key(不推荐,安全风险高) - 环境变量:
AWS_ACCESS_KEY_ID+AWS_SECRET_ACCESS_KEY - 环境变量:
AWS_PROFILE(或 provider 块中的profile参数) ~/.aws/credentials文件中的[default]profile~/.aws/config文件中的配置- EC2 Instance Metadata(IAM Instance Profile)/ ECS Task Role / Lambda 执行角色
- Web Identity(OIDC)Token(GitHub Actions 等 CI 系统)
AWS Provider 的 default_tags 块会自动将指定的标签应用到所有支持 tags 属性的资源上。资源级别的 tags 与 default_tags 会合并,若有同名键则资源级别优先。这个功能在 Terraform plan 输出中会以 tags_all 属性展示合并后的最终标签。注意:并非所有资源都支持标签,如 aws_iam_policy_attachment 等资源不支持。
本章小结
- Provider 是 Terraform 与云 API 的桥梁:每个 Provider 封装一个平台的 API;
terraform init负责下载和安装;required_providers声明 source 和 version 约束。 - 版本约束语法:
~> 5.0允许 5.x 但不允许 6.x;~> 5.50允许 5.50.x;精确锁定用= 5.50.0;组合约束如>= 5.0, < 6.0。 - Provider 别名(alias):同类型 Provider 可有多个实例(不同区域/账号),用
alias区分;资源通过provider = aws.别名指定;可通过providers参数传递给模块。 - 认证最佳实践:本地开发用 AWS CLI profile 或 ADC;CI/CD 优先用 OIDC(无密钥);跨账号用 assume_role;绝不硬编码 Access Key。
- default_tags:AWS Provider 的全局标签功能,自动给所有支持 tags 的资源打标签,
tags_all属性展示合并结果。 - Data Source:用于读取已有资源(非 Terraform 管理)的属性,如已有 VPC、SSM 参数、当前账号 ID 等,避免硬编码。