Terraform Provisioners #

什么是 Provisioner? #

Provisioner 是在资源创建或销毁时执行操作的机制。它允许你在资源创建后进行额外的配置,如在 EC2 实例上安装软件、配置服务等。

text
┌─────────────────────────────────────────────────────────────┐
│                    Provisioner 类型                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  local-exec    在运行 Terraform 的机器上执行命令            │
│  remote-exec   在远程资源上执行命令                         │
│                                                             │
│  执行时机:                                                 │
│  - creation   资源创建后执行                                │
│  - destruction 资源销毁前执行                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

local-exec #

基本语法 #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  provisioner "local-exec" {
    command = "echo ${self.public_ip} > instance_ip.txt"
  }
}

环境变量 #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  provisioner "local-exec" {
    command = "echo $INSTANCE_IP"
    
    environment = {
      INSTANCE_IP = self.public_ip
    }
  }
}

执行脚本 #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  provisioner "local-exec" {
    command = "${path.module}/scripts/configure.sh"
    
    environment = {
      PUBLIC_IP = self.public_ip
      REGION    = var.region
    }
  }
}

调用 AWS CLI #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  provisioner "local-exec" {
    command = "aws s3 cp s3://my-bucket/config.json /tmp/config.json"
  }
}

销毁时执行 #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  provisioner "local-exec" {
    when    = destroy
    command = "echo 'Instance ${self.id} is being destroyed'"
  }
}

remote-exec #

基本语法 #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  connection {
    type        = "ssh"
    user        = "ec2-user"
    private_key = file("~/.ssh/id_rsa")
    host        = self.public_ip
  }
  
  provisioner "remote-exec" {
    inline = [
      "sudo yum update -y",
      "sudo yum install -y httpd",
      "sudo systemctl start httpd"
    ]
  }
}

使用脚本 #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  connection {
    type        = "ssh"
    user        = "ec2-user"
    private_key = file("~/.ssh/id_rsa")
    host        = self.public_ip
  }
  
  provisioner "remote-exec" {
    script = "${path.module}/scripts/setup.sh"
  }
}

上传脚本并执行 #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  connection {
    type        = "ssh"
    user        = "ec2-user"
    private_key = file("~/.ssh/id_rsa")
    host        = self.public_ip
  }
  
  provisioner "file" {
    source      = "${path.module}/scripts/setup.sh"
    destination = "/tmp/setup.sh"
  }
  
  provisioner "remote-exec" {
    inline = [
      "chmod +x /tmp/setup.sh",
      "sudo /tmp/setup.sh"
    ]
  }
}

Connection 配置 #

SSH 连接 #

hcl
connection {
  type        = "ssh"
  user        = "ec2-user"
  private_key = file("~/.ssh/id_rsa")
  host        = self.public_ip
  port        = 22
  timeout     = "5m"
}

使用密码 #

hcl
connection {
  type     = "ssh"
  user     = "admin"
  password = var.ssh_password
  host     = self.public_ip
}

WinRM 连接 #

hcl
connection {
  type     = "winrm"
  user     = "Administrator"
  password = var.admin_password
  host     = self.public_ip
  port     = 5985
  https    = false
  timeout  = "10m"
}

使用跳板机 #

hcl
connection {
  type        = "ssh"
  user        = "ec2-user"
  private_key = file("~/.ssh/id_rsa")
  host        = self.private_ip
  
  bastion_host        = var.bastion_host
  bastion_user        = "ec2-user"
  bastion_private_key = file("~/.ssh/id_rsa")
}

在 Provisioner 中定义连接 #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  provisioner "remote-exec" {
    connection {
      type        = "ssh"
      user        = "ec2-user"
      private_key = file("~/.ssh/id_rsa")
      host        = self.public_ip
    }
    
    inline = [
      "echo 'Hello, World!'"
    ]
  }
}

file Provisioner #

上传文件 #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  connection {
    type        = "ssh"
    user        = "ec2-user"
    private_key = file("~/.ssh/id_rsa")
    host        = self.public_ip
  }
  
  provisioner "file" {
    source      = "${path.module}/config/app.conf"
    destination = "/tmp/app.conf"
  }
}

上传目录 #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  connection {
    type        = "ssh"
    user        = "ec2-user"
    private_key = file("~/.ssh/id_rsa")
    host        = self.public_ip
  }
  
  provisioner "file" {
    source      = "${path.module}/config/"
    destination = "/tmp/config/"
  }
}

上传内容 #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  connection {
    type        = "ssh"
    user        = "ec2-user"
    private_key = file("~/.ssh/id_rsa")
    host        = self.public_ip
  }
  
  provisioner "file" {
    content     = "Hello, ${var.environment}!"
    destination = "/tmp/hello.txt"
  }
}

执行时机 #

创建时执行 #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  provisioner "local-exec" {
    when    = create
    command = "echo 'Instance created'"
  }
}

销毁时执行 #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  provisioner "local-exec" {
    when    = destroy
    command = "echo 'Instance ${self.id} destroyed'"
  }
}

失败处理 #

on_failure #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  provisioner "local-exec" {
    command    = "exit 1"
    on_failure = continue
  }
}

resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  provisioner "local-exec" {
    command    = "exit 1"
    on_failure = fail
  }
}

最佳实践 #

1. 优先使用 user_data #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  user_data = <<-EOF
    #!/bin/bash
    yum update -y
    yum install -y httpd
    systemctl start httpd
  EOF
}

2. 使用配置管理工具 #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  user_data = templatefile("${path.module}/user_data.sh.tpl", {
    environment = var.environment
  })
}

3. 使用 Packer 构建镜像 #

text
┌─────────────────────────────────────────────────────────────┐
│                    推荐架构                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. Packer 构建预配置镜像                                   │
│     - 安装软件                                             │
│     - 配置服务                                             │
│     - 安全加固                                             │
│                                                             │
│  2. Terraform 部署基础设施                                  │
│     - 创建资源                                             │
│     - 使用预配置镜像                                       │
│     - 最小化 provisioner 使用                               │
│                                                             │
│  3. 配置管理工具                                            │
│     - Ansible                                              │
│     - Chef                                                 │
│     - Puppet                                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

4. 仅在必要时使用 #

text
┌─────────────────────────────────────────────────────────────┐
│                    Provisioner 使用场景                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  适合使用:                                                 │
│  ✅ 记录资源信息到本地文件                                  │
│  ✅ 调用外部 API                                           │
│  ✅ 触发 CI/CD 流水线                                      │
│  ✅ 临时测试环境                                           │
│                                                             │
│  不推荐使用:                                               │
│  ❌ 安装软件包                                             │
│  ❌ 配置服务                                               │
│  ❌ 部署应用                                               │
│  ❌ 生产环境配置                                           │
│                                                             │
│  替代方案:                                                 │
│  - user_data                                               │
│  - Packer                                                  │
│  - Ansible/Chef/Puppet                                     │
│  - 容器化                                                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

完整示例 #

记录实例信息 #

hcl
resource "aws_instance" "web" {
  count         = var.instance_count
  ami           = data.aws_ami.amazon_linux.id
  instance_type = var.instance_type
  subnet_id     = var.subnet_id
  
  tags = {
    Name = "${var.project_name}-web-${count.index + 1}"
  }
  
  provisioner "local-exec" {
    command = <<-EOT
      echo "${self.public_ip}" >> ${path.module}/instance_ips.txt
    EOT
  }
  
  provisioner "local-exec" {
    when    = destroy
    command = <<-EOT
      sed -i "/${self.public_ip}/d" ${path.module}/instance_ips.txt
    EOT
  }
}

触发配置更新 #

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  provisioner "local-exec" {
    command = <<-EOT
      curl -X POST ${var.webhook_url} \
        -H "Content-Type: application/json" \
        -d '{"instance_id": "${self.id}", "public_ip": "${self.public_ip}"}'
    EOT
  }
}

下一步 #

掌握了 Provisioners 后,接下来学习 工作空间,了解如何管理多个环境!

最后更新:2026-03-29