Provisioner local-exec的always_run触发器未按预期运行问题咨询
根因解释
你配置的timestamp()触发器确实会让null_resource每次apply都重建、执行对应local-exec脚本,问题核心出在Terraform的执行阶段顺序不匹配,以及隧道脚本本身的缺陷:
- Terraform运行分为
plan和apply两个核心阶段,所有Provider的初始化(包括MySQL Provider建立数据库连接的逻辑)是在plan阶段提前完成的,而local-execprovisioner只有在apply阶段、资源触发重建时才会执行。第二次执行terraform apply时,还没等到执行建隧道的脚本,plan阶段的MySQL Provider就已经尝试连接本地3306端口,此时隧道还未建立,直接抛出连接拒绝的错误,根本不会走到apply阶段执行你的隧道脚本。 - 即使执行顺序没问题,你当前的隧道脚本也存在缺陷:如果本地3306端口已经被之前残留的隧道进程占用,新的ssh隧道命令会直接执行失败,同样会导致连接失败。
合理实现方案
方案1:将隧道逻辑剥离到Terraform执行前的前置脚本(最推荐)
不要把隧道这种依赖外部环境的操作放到Terraform资源生命周期里管理,单独拆分执行流程即可:
- 编写独立的前置脚本,逻辑包含:杀残留隧道进程、新建SSH隧道、等待隧道就绪
- 执行
terraform apply - 执行完成后自动销毁SSH隧道
示例前置脚本逻辑:
# 清理旧隧道进程 pkill -f "3306:${RDS_ENDPOINT}" || true # 新建隧道 ssh -i ${PRIVATE_KEY} -L 3306:${RDS_ENDPOINT} -fN ec2-user@${BASTION_IP} # 等待隧道转发就绪 sleep 3
方案2:将数据库操作也改为local-exec实现
如果你一定要把所有逻辑放到Terraform配置里,可以放弃官方MySQL Provider,改用本地执行mysql命令的方式完成库操作,这样所有逻辑都在apply阶段按依赖顺序执行,不会出现顺序错位:
# 创建SSH隧道 resource "null_resource" "ssh_tunnel" { provisioner "local-exec" { command = <<EOT pkill -f "3306:${var.rds_endpoint}" || true ssh -i ${var.private_key} -L 3306:${var.rds_endpoint} -fN ec2-user@${var.bastion_ip} -v >./stdout.log 2>./stderr.log sleep 3 EOT } triggers = { always_run = timestamp() } } # 通过mysql客户端创建数据库 resource "null_resource" "create_db" { provisioner "local-exec" { command = "mysql -h 127.0.0.1 -u ${var.db_user} -p${var.db_password} -e 'CREATE DATABASE IF NOT EXISTS ${var.db_name};'" } depends_on = [null_resource.ssh_tunnel] triggers = { always_run = timestamp() } }
内容的提问来源于stack exchange,提问作者local
相关产品推荐
相关产品推荐

