terraform local-exec与remote-exec详解
在 Terraform 中,local-exec
和 remote-exec
是两种常用的 provisioner(资源调配器),用于在资源创建前后执行脚本或命令。它们的核心区别在于执行位置:local-exec
在运行 Terraform 的本地机器上执行命令,而 remote-exec
则通过 SSH 或 WinRM 在远程资源上执行命令。以下是详细解析:
一、local-exec provisioner
1. 核心用途
- 在本地机器上执行命令(如生成证书、调用其他工具)。
- 资源创建前 / 后执行初始化或清理操作。
2. 基本语法
resource "null_resource" "example" {provisioner "local-exec" {command = "echo 'Hello from local machine'"# 可选参数interpreter = ["bash", "-c"] # 指定解释器working_dir = "/path/to/dir" # 工作目录environment = { # 环境变量VAR = "value"}}
}
3. 典型场景
生成 SSH 密钥对:
resource "null_resource" "generate_key" {provisioner "local-exec" {command = "ssh-keygen -t rsa -b 4096 -f ./id_rsa -N ''"creates = "./id_rsa" # 避免重复执行}
}
调用外部 API:
resource "null_resource" "notify_deploy" {provisioner "local-exec" {command = "curl -X POST https://api.example.com/deploy -d 'env=prod'"}
}
二、remote-exec provisioner
1. 核心用途
- 在远程资源(如 EC2 实例、VM)上执行配置命令。
- 安装软件、配置服务、启动应用等。
2. 基本语法(SSH)
resource "aws_instance" "example" {# 实例配置...provisioner "remote-exec" {inline = ["sudo apt-get update","sudo apt-get install -y nginx","sudo systemctl start nginx"]# SSH 连接配置connection {type = "ssh"user = "ubuntu"private_key = file("~/.ssh/id_rsa")host = self.public_ip}}
}
3. 典型场景
部署应用到服务器:
provisioner "remote-exec" {inline = ["git clone https://github.com/example/app.git /opt/app","cd /opt/app && npm install","npm start"]
}
配置数据库:
provisioner "remote-exec" {inline = ["sudo apt-get install postgresql","sudo -u postgres psql -c \"CREATE DATABASE mydb;\"","sudo -u postgres psql -c \"CREATE USER user WITH PASSWORD 'pass';\""]
}
三、关键区别对比
特性 | local-exec | remote-exec |
---|---|---|
执行位置 | 运行 Terraform 的本地机器 | 通过 SSH/WinRM 连接的远程机器 |
典型用途 | 本地环境准备、调用外部工具 | 远程服务器配置、软件安装 |
依赖 | 本地命令行工具 | 远程服务器可访问 + SSH/WinRM 配置 |
连接配置 | 无 | 需要 connection 块指定认证和主机 |
错误处理 | 直接影响 Terraform 执行 | 失败可能导致资源处于部分配置状态 |
四、高级用法
1. 文件传输(与 remote-exec 配合)
使用 file
provisioner 上传本地文件到远程服务器:
resource "aws_instance" "example" {# ...provisioner "file" {source = "config.txt"destination = "/home/ubuntu/config.txt"connection {# SSH 配置}}provisioner "remote-exec" {inline = ["cat /home/ubuntu/config.txt"]connection {# 同上 SSH 配置}}
}
2. 条件执行
1)when
通过 when
参数控制执行时机:
provisioner "local-exec" {when = "destroy" # 可选值:create, destroycommand = "cleanup.sh"
}
2)on_failure
控制 provisioner 失败时的行为:
on_failure = "fail"
(默认值):失败时终止操作并尝试回滚。on_failure = "continue"
:失败时继续执行,可能导致资源处于不一致状态。
provisioner "remote-exec" {on_failure = "continue" # 即使命令失败也继续inline = ["command-that-might-fail"]
}
3) depends_on
显式指定依赖关系,确保在特定资源就绪后执行:
resource "null_resource" "deploy_app" {provisioner "local-exec" {command = "deploy.sh"}depends_on = [aws_instance.server] # 确保服务器创建完成后执行
}
4)create_before_destroy
控制资源替换时的顺序:
resource "aws_instance" "web_server" {# ...lifecycle {create_before_destroy = true # 先创建新资源,再销毁旧资源}
}
五、最佳实践
1. 优先使用 Terraform 资源
能用资源(如 aws_security_group
、azurerm_virtual_machine_extension
)实现的,尽量不依赖 provisioner。例如:
- 使用
aws_instance
的user_data
替代remote-exec
安装软件。 - 使用
null_resource
+local-exec
调用 AWS CLI 替代复杂脚本。
2. 最小化 provisioner 使用
Provisioner 属于 "side effect" 操作,难以测试和维护。尽量将配置逻辑封装为可复用的模块。
3. 安全处理敏感信息
- 避免在
remote-exec
命令中硬编码密码或密钥。 - 使用
sensitive
变量和环境变量传递敏感信息。
4. 幂等性设计
确保脚本可重复执行而不产生副作用(例如添加 creates
参数或检查文件是否存在)。
5. 与 CI/CD 集成
在 CI/CD 流水线中使用 provisioner 执行部署后测试:
resource "null_resource" "test_deployment" {provisioner "local-exec" {command = "curl -s http://${aws_instance.example.public_ip} | grep 'Welcome'"}depends_on = [aws_instance.example]
}
六、注意事项
状态管理:
- Provisioner 失败可能导致资源处于不一致状态。建议使用
on_failure = continue
并手动清理。
生命周期管理:
create
类型的 provisioner 在资源创建时执行,destroy
类型在资源销毁前执行。
调试技巧:
- 使用
TF_LOG=DEBUG
查看详细执行日志。 - 在本地测试脚本,确保其可独立运行。
替代方案:
- 复杂配置管理建议使用专业工具(如 Ansible、Chef、Puppet),通过
local-exec
调用。
七、总结
- local-exec:适合本地环境准备、调用外部工具或生成依赖文件。
- remote-exec:适合直接在远程资源上执行配置命令,但依赖 SSH/WinRM 连接。
两者都是 Terraform 中灵活但需谨慎使用的工具。合理设计 provisioner 能简化基础设施部署,但过度依赖会导致代码难以维护。建议结合资源原生功能和外部配置管理工具,构建更健壮的基础设施自动化流程。