Provider 和资源管理

Provider 概述

Provider 是 Terraform 与云平台、SaaS 服务交互的插件。每个 Provider 负责理解 API 交互并暴露资源。

Provider 架构

┌─────────────┐
│  Terraform  │
│    Core     │
└──────┬──────┘
       │
       ├──────────┬──────────┬──────────┐
       │          │          │          │
       ▼          ▼          ▼          ▼
  ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
  │  AWS   │ │ Azure  │ │  GCP   │ │Alibaba │
  │Provider│ │Provider│ │Provider│ │Provider│
  └────┬───┘ └────┬───┘ └────┬───┘ └────┬───┘
       │          │          │          │
       ▼          ▼          ▼          ▼
  ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
  │AWS API │ │Azure   │ │GCP API │ │Aliyun  │
  │        │ │ARM API │ │        │ │API     │
  └────────┘ └────────┘ └────────┘ └────────┘

配置 Provider

基本配置

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

Provider 版本约束

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"      # >= 5.0.0, < 6.0.0
      # version = ">= 5.0"    # >= 5.0.0
      # version = "= 5.31.0"  # 精确版本
      # version = "!= 5.0.0"  # 排除特定版本
    }
  }
}

多个 Provider 实例

# 默认 Provider
provider "aws" {
  region = "us-east-1"
}

# 别名 Provider
provider "aws" {
  alias  = "west"
  region = "us-west-2"
}

# 使用特定 Provider
resource "aws_instance" "east" {
  provider = aws
  ami      = "ami-xxx"
}

resource "aws_instance" "west" {
  provider = aws.west
  ami      = "ami-yyy"
}

常见 Provider 配置

AWS Provider

provider "aws" {
  region     = "us-east-1"
  access_key = var.aws_access_key
  secret_key = var.aws_secret_key
  
  # 假设角色
  assume_role {
    role_arn = "arn:aws:iam::123456789012:role/TerraformRole"
  }
  
  # 默认标签
  default_tags {
    tags = {
      Environment = "Production"
      ManagedBy   = "Terraform"
    }
  }
}

Azure Provider

provider "azurerm" {
  features {
    resource_group {
      prevent_deletion_if_contains_resources = true
    }
    
    virtual_machine {
      delete_os_disk_on_deletion = true
    }
  }
  
  subscription_id = var.subscription_id
  tenant_id       = var.tenant_id
}

GCP Provider

provider "google" {
  project     = "my-project-id"
  region      = "us-central1"
  zone        = "us-central1-a"
  credentials = file("service-account.json")
}

阿里云 Provider

provider "alicloud" {
  access_key = var.access_key
  secret_key = var.secret_key
  region     = "cn-beijing"
}

Kubernetes Provider

provider "kubernetes" {
  config_path    = "~/.kube/config"
  config_context = "my-context"
}

# 或使用集群配置
provider "kubernetes" {
  host                   = "https://cluster-api.example.com"
  client_certificate     = file("~/.kube/client-cert.pem")
  client_key             = file("~/.kube/client-key.pem")
  cluster_ca_certificate = file("~/.kube/cluster-ca-cert.pem")
}

资源(Resource)

资源语法

resource "<RESOURCE_TYPE>" "<RESOURCE_NAME>" {
  <ARGUMENT_NAME> = <ARGUMENT_VALUE>
  # ...
}

# 示例
resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  tags = {
    Name = "Web Server"
  }
}

资源元参数

1. depends_on - 显式依赖

resource "aws_instance" "web" {
  ami           = "ami-xxx"
  instance_type = "t2.micro"
  
  # 显式依赖(Terraform 通常自动处理)
  depends_on = [aws_security_group.web_sg]
}

resource "aws_security_group" "web_sg" {
  name = "web-sg"
}

2. count - 创建多个资源

resource "aws_instance" "web" {
  count = 3
  
  ami           = "ami-xxx"
  instance_type = "t2.micro"
  
  tags = {
    Name = "web-${count.index}"
  }
}

# 引用
output "instance_ids" {
  value = aws_instance.web[*].id
}

3. for_each - 使用集合创建资源

variable "instances" {
  default = {
    web = "t2.micro"
    db  = "t2.small"
    cache = "t2.micro"
  }
}

resource "aws_instance" "servers" {
  for_each = var.instances
  
  ami           = "ami-xxx"
  instance_type = each.value
  
  tags = {
    Name = each.key
  }
}

# 引用
output "server_ips" {
  value = {
    for name, instance in aws_instance.servers : name => instance.public_ip
  }
}

4. provider - 指定 Provider

resource "aws_instance" "west_server" {
  provider = aws.west
  
  ami           = "ami-xxx"
  instance_type = "t2.micro"
}

5. lifecycle - 生命周期规则

resource "aws_instance" "web" {
  ami           = "ami-xxx"
  instance_type = "t2.micro"
  
  lifecycle {
    # 先创建新资源再删除旧资源
    create_before_destroy = true
    
    # 防止资源被销毁
    prevent_destroy = false
    
    # 忽略特定属性的变更
    ignore_changes = [
      tags,
      user_data,
    ]
    
    # 替换触发条件
    replace_triggered_by = [
      aws_security_group.web_sg
    ]
  }
}

6. provisioner - 配置器

resource "aws_instance" "web" {
  ami           = "ami-xxx"
  instance_type = "t2.micro"
  
  # 本地执行
  provisioner "local-exec" {
    command = "echo ${self.private_ip} >> private_ips.txt"
  }
  
  # 远程执行
  provisioner "remote-exec" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get install -y nginx",
    ]
    
    connection {
      type        = "ssh"
      user        = "ubuntu"
      private_key = file("~/.ssh/id_rsa")
      host        = self.public_ip
    }
  }
  
  # 文件上传
  provisioner "file" {
    source      = "app.conf"
    destination = "/etc/app/app.conf"
    
    connection {
      type = "ssh"
      user = "ubuntu"
      host = self.public_ip
    }
  }
}

资源示例

AWS EC2 实例

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  key_name      = "my-key-pair"
  
  vpc_security_group_ids = [aws_security_group.web_sg.id]
  subnet_id              = aws_subnet.public.id
  
  user_data = <<-EOF
              #!/bin/bash
              apt-get update
              apt-get install -y nginx
              systemctl start nginx
              EOF
  
  tags = {
    Name        = "Web Server"
    Environment = "Production"
  }
  
  root_block_device {
    volume_size = 20
    volume_type = "gp3"
    encrypted   = true
  }
}

AWS S3 Bucket

resource "aws_s3_bucket" "data" {
  bucket = "my-unique-bucket-name"
  
  tags = {
    Name        = "Data Bucket"
    Environment = "Production"
  }
}

resource "aws_s3_bucket_versioning" "data" {
  bucket = aws_s3_bucket.data.id
  
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_encryption" "data" {
  bucket = aws_s3_bucket.data.id
  
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

AWS VPC 网络

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true
  
  tags = {
    Name = "Main VPC"
  }
}

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "us-east-1a"
  map_public_ip_on_launch = true
  
  tags = {
    Name = "Public Subnet"
  }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  
  tags = {
    Name = "Main IGW"
  }
}

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 = "Public Route Table"
  }
}

resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

AWS Security Group

resource "aws_security_group" "web" {
  name        = "web-sg"
  description = "Security group for web servers"
  vpc_id      = aws_vpc.main.id
  
  # HTTP
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  
  # HTTPS
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  
  # SSH (限制 IP)
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["203.0.113.0/24"]
  }
  
  # 出站流量
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  
  tags = {
    Name = "Web Security Group"
  }
}

数据源(Data Source)

数据源允许你查询现有资源的信息。

语法

data "<DATA_SOURCE_TYPE>" "<NAME>" {
  <ARGUMENT_NAME> = <ARGUMENT_VALUE>
}

AWS 示例

查询 AMI

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]  # Canonical
  
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }
  
  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t2.micro"
}

查询可用区

data "aws_availability_zones" "available" {
  state = "available"
}

resource "aws_subnet" "example" {
  count             = 2
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
  availability_zone = data.aws_availability_zones.available.names[count.index]
}

查询现有 VPC

data "aws_vpc" "default" {
  default = true
}

data "aws_subnets" "default" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.default.id]
  }
}

资源导入

导入现有资源到 Terraform 管理。

导入步骤

1. 编写资源配置

resource "aws_instance" "imported" {
  # 配置参数
  ami           = "ami-xxx"
  instance_type = "t2.micro"
}

2. 导入资源

terraform import aws_instance.imported i-1234567890abcdef0

3. 查看状态并更新配置

terraform show aws_instance.imported
# 根据输出更新配置文件

导入块(Terraform 1.5+)

import {
  to = aws_instance.example
  id = "i-1234567890abcdef0"
}

resource "aws_instance" "example" {
  ami           = "ami-xxx"
  instance_type = "t2.micro"
}

然后运行:

terraform plan -generate-config-out=generated.tf

最佳实践

1. Provider 版本锁定

terraform {
  required_version = ">= 1.0"
  
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"  # 锁定主版本
    }
  }
}

2. 使用数据源而非硬编码

# ❌ 硬编码
resource "aws_instance" "web" {
  ami = "ami-0c55b159cbfafe1f0"  # 可能过时
}

# ✅ 使用数据源
data "aws_ami" "latest_ubuntu" {
  most_recent = true
  owners      = ["099720109477"]
  
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }
}

resource "aws_instance" "web" {
  ami = data.aws_ami.latest_ubuntu.id
}

3. 合理使用生命周期规则

resource "aws_instance" "web" {
  ami           = "ami-xxx"
  instance_type = "t2.micro"
  
  lifecycle {
    create_before_destroy = true  # 避免停机
    ignore_changes        = [tags] # 允许手动修改标签
  }
}

4. 使用 for_each 而非 count

# ✅ for_each - 更稳定
resource "aws_instance" "servers" {
  for_each = toset(["web", "api", "worker"])
  
  ami           = "ami-xxx"
  instance_type = "t2.micro"
  
  tags = {
    Name = each.key
  }
}

5. 避免过度使用 Provisioner

# ❌ 避免
resource "aws_instance" "web" {
  provisioner "remote-exec" {
    # 复杂的配置管理
  }
}

# ✅ 推荐
# 使用 user_data、cloud-init 或 Ansible
resource "aws_instance" "web" {
  user_data = file("user-data.sh")
}

总结

  • Provider:连接云平台的插件
  • Resource:要创建和管理的基础设施组件
  • Data Source:查询现有资源信息
  • 元参数:count、for_each、depends_on、lifecycle
  • 最佳实践:版本锁定、使用数据源、合理生命周期

掌握 Provider 和资源管理是使用 Terraform 的核心技能!


下一章变量和输出