如何让 Terraform 中的 Docker 提供程序在尝试连接之前等待地址可用?

How can I make the Docker provider in Terraform wait for an address to become available before attempting to connect to it?

我在 Terraform 中有以下资源:

provider "docker" {
    host = "tcp://${digitalocean_droplet.docker_server.ipv4_address}:2376/"
}

这依赖于在连接到 docker 机器之前已知的值 ipv4_address。在供应另一个资源之前,此值是未知的:

resource "digitalocean_droplet" "docker_server" {
    image = "docker-18-04"
    name = "docker_server"
    region = "nyc2"
    size = "512mb"
    private_networking = true
    ssh_keys = [
      var.ssh_fingerprint
    ]

    connection {
        user = "root"
        type = "ssh"
        private_key = file(var.pvt_key)
        timeout = "2m"
    }
}

当我 运行 terraform plan 时,出现以下错误:

Error: Error initializing Docker client: unable to parse docker host ``

on docker.tf line 1, in provider "docker": 1: provider "docker" {

ipv4_address 似乎是空的,因为 docker 插件在配置之前试图连接到 docker 机器。我如何告诉它在尝试连接之前等待机器配置完毕?


我试过的一件事:

provider "docker" {
    host = "tcp://${digitalocean_droplet.docker_server.ipv4_address}:2376/"
    depends_on = [
        digitalocean_droplet.docker_server.ipv4_address,
    ]
}

当我这样做时,我得到这个错误:

Error: Reserved argument name in provider block

on docker.tf line 4, in provider "docker": 4: depends_on = [

The provider argument name "depends_on" is reserved for use by Terraform in a future version.

但仔细阅读 depends_on,我认为无论如何这都不是解决方案。

不幸的是provider block doesn't support expressions referring to a resource attribute

此限制在 provider configuration 文档中有解释:

The configuration arguments defined by the provider may be assigned using expressions, which can for example allow them to be parameterized by input variables.

However, since provider configurations must be evaluated in order to perform any resource type action, provider configurations may refer only to values that are known before the configuration is applied.

In particular, avoid referring to attributes exported by other resources unless their values are specified directly in the configuration.

例如,这可行(但不能解决您的问题):

variable "docker_host" {
  type = string
}

provider "docker" {
  host = "tcp://${var.docker_host}:2376/"
}

但还是有办法的。

解决方案由两步组成:

  1. 将您的 Terraform 配置分成两部分(每个部分都必须位于自己的 目录)其中具有 docker 提供程序的那个取决于那个 部署液滴。请注意,这意味着您必须发出 terraform 单独命令(需要申请两次)。
  2. 在两种状态之间建立一个单向的、只读的"connection" 使用名为 remote 的功能 状态:

Retrieves state data from a Terraform backend. This allows you to use the root-level outputs of one or more Terraform configurations as input data for another configuration.

如果您还没有使用 "real" 远程后端,例如 S3 + DynamoDB,您仍然可以使用 local backend 轻松地进行实验,如下所示。

目录布局:

├── docker                   <== this performs docker operation
│   ├── main.tf
│   └── terraform.tfstate
└── server                   <== this deploys the droplet
    ├── main.tf
    └── terraform.tfstate

下面的代码片段使用的是 AWS,但适应 DO 是微不足道的。

文件 server/main.tf 包含类似于

的内容
resource "aws_instance" "server" {     <= equivalent to the Droplet
  ...
}

output "ipv4_address" {
  value = aws_instance.server.public_ip
}

文件 docker/main.tf 包含类似于

的内容
data "terraform_remote_state" "docker_server" {
  backend = "local"

  config = {
    path = "${path.module}/../server/terraform.tfstate"
  }
}

provider "docker" {
  host = "tcp://${data.terraform_remote_state.docker_server.outputs.ipv4_address}:2376/"
}

最后:

cd server
terraform apply
cd ../docker
terraform apply

记住:您还必须按照后进先出的顺序单独执行 terraform destroy: 先销毁docker,再销毁server.