Terraform:如何 运行 remote-exec 不止一次?

Terraform: How to run remote-exec more than once?

我注意到 terraform 只会对资源 运行 "file"、"remote-exec" 或 "local-exec" 一次。如果 "remote-exec" 中的命令发生更改或供应者 "file" 中的文件发生更改,则一旦资源被供应,Terraform 将不会对实例进行任何更改。那么,每次我 运行 应用 Terraform 时,如何将 Terraform 获取到 运行 配置器 "file"、"remote-exec" 或 "local-exec"?

更多详情:

我经常部分地配置资源,因为 "remote-exec" 的错误导致 terraform 停止(主要是因为我在编写脚本时输入了错误的命令)。 运行 terraform 在此之后再次创建将导致先前创建的资源被销毁并强制 terraform 从头开始​​创建新资源。这也是我可以在资源上 运行 "remote-exec" 两次的唯一方法......通过从头开始创建它。

与 ansible 相比,这确实是 terraform 的一个缺点,ansible 可以完成与 terraform 完全相同的工作,只是它是完全幂等的。当将 Ansible 与 "ec2"、"shell" 和 "copy" 等任务一起使用时,我可以实现与 terraform 相同的任务,只是这些任务中的每一个都是幂等的。 Ansible 会自动识别何时不需要进行更改,在哪里需要进行更改,因此它可以从失败的 ansible-playbook 停止的地方开始,而不会破坏所有内容并从头开始。 Terraform 缺少此功能。

这里有一个简单的 terraform 资源块,用于同时使用 "remote-exec" 和 "file" 配置器的 ec2 实例:

resource "aws_instance" "test" {

count = ${var.amt}
ami = "ami-2d39803a"
instance_type = "t2.micro"
key_name = "ansible_aws"
tags {
  name = "test${count.index}"
}

#creates ssh connection to consul servers
connection {
  user = "ubuntu"
  private_key="${file("/home/ubuntu/.ssh/id_rsa")}"
  agent = true
  timeout = "3m"
} 

provisioner "remote-exec" {
  inline = [<<EOF

    sudo apt-get update
    sudo apt-get install curl unzip
    echo hi

  EOF
  ]
}

#copying a file over
provisioner "file" {
  source = "scripts/test.txt"
  destination = "/path/to/file/test.txt"
}

}

Terraform docs on provisioning 明确表示它认为使用供应器进行基本引导是一次性任务,不应将其用作适当的配置管理工具(如 Ansible)的替代品:

Provisioners are run only when a resource is created. They are not a replacement for configuration management and changing the software of an already-running server, and are instead just meant as a way to bootstrap a server. For configuration management, you should use Terraform provisioning to invoke a real configuration management solution.

If a resource successfully creates but fails during provision, Terraform will error and mark the resource as "tainted." A resource that is tainted has been physically created, but can't be considered safe to use since provisioning failed.

When you generate your next execution plan, Terraform will remove any tainted resources and create new resources, attempting to provision again. It does not attempt to restart provisioning on the same resource because it isn't guaranteed to be safe.

Terraform does not automatically roll back and destroy the resource during the apply when the failure happens, because that would go against the execution plan: the execution plan would've said a resource will be created, but does not say it will ever be deleted. But if you create an execution plan with a tainted resource, the plan will clearly state that the resource will be destroyed because it is tainted.

Provisioning is important for being able to bootstrap instances. As another reminder, it is not a replacement for configuration management. It is meant to simply bootstrap machines. If you use configuration management, you should use the provisioning as a way to bootstrap the configuration management utility.

将供应商视为类似于 EC2 用户数据脚本,因为它只在创建时运行一次,如果失败,则您需要销毁实例并重试。

这样做的好处是,Terraform 不需要了解如何在操作系统上进行幂等更改,因为 Terraform 的工作级别高于实例本身,更多的是供应整个数据中心.

如果您需要比这更大的灵活性,请考虑使用 Terraform 调用配置管理系统以正确配置实例(然后允许重试,如果失败,与 Terraform 配置阶段分离)或使用编排工具,例如 Jenkins,用于包装 Terraform 和替代配置管理工具,例如 Ansible。

另一种选择是更多地沿着不可变基础架构的路线前进,并使用 Packer 使用 Ansible 或其他工具创建 AMI,然后仅使用 Terraform 按原样部署 AMI,而无需进一步提供实例。

您可以使用 taint 命令将资源标记为受污染,强制其销毁并在下一次应用时重新创建。

在我的搜索中遇到了这个线程并最终找到了解决方案:

resource "null_resource" "ansible" {

  triggers {
    key = "${uuid()}"
  }

  provisioner "local-exec" {
  command = "ansible-playbook -i /usr/local/bin/terraform-inventory -u ubuntu playbook.yml --private-key=/home/user/.ssh/aws_user.pem -u ubuntu"
  }
}

您可以使用 uuid(),它对每个 terraform 运行 都是唯一的,以触发空资源或配置器。

与 Chris Holmes 类似的答案,但使用时间戳,您需要从 Chris 的答案中删除 UUID 或 timestamp() 周围的 ${""},因为现在您会得到消息:

Warning: Interpolation-only expressions are deprecated

除非您使用的是 Terraform 0.11 或更早版本。

resource "null_resource" "ansible" {

  triggers = {
    always_run = timestamp()
  }

  provisioner "local-exec" {
  command = "ansible-playbook -i /usr/local/bin/terraform-inventory -u ubuntu playbook.yml --private-key=/home/user/.ssh/aws_user.pem -u ubuntu"
  }
}