捕获Terraform配置器输出?

时间:2017-06-12 23:11:27

标签: terraform

使用案例

尝试配置(Docker Swarm或Consul)集群,其中首先在一个节点上初始化集群,该节点生成一些令牌,然后需要由加入集群的其他节点使用。关键是节点1和2在节点0生成连接键之前不应尝试加入集群。

EG。在节点0上,运行docker swarm init ...将返回加入令牌。然后在节点1和2上,您需要将该令牌传递给相同的命令,如docker swarm init ${JOIN_TOKEN} ${NODE_0_IP_ADDRESS}:{SOME_PORT}。而且魔术,你有一个整洁的小集群...

尝试到目前为止

  • 尝试使用安装的AWS SDK初始化所有节点,并在S3上存储来自节点0的连接密钥,然后在其他节点上获取该连接密钥。这是通过带有'remote-exec'配置程序的null_resource完成的。由于Terraform并行执行的方式,有一些活泼的类型条件,并且可预测的是节点1和2经常尝试从S3获取一个尚未存在的密钥(例如,节点0还没有完成它的东西)。

  • 尝试使用'local-exec'配置程序SSH到节点0并捕获其连接键输出。这种方法效果不佳,或者我这样做很糟糕。

我已经阅读了文档。并且堆栈溢出。和Github问题,如this really long outstanding one。彻底。如果这已经在其他地方得到了解决,那么链接就会受到赞赏!

PS - 这与this question直接相关并且是{{3}}的较小子集,但是想重新询问它以便集中解决问题的范围。

7 个答案:

答案 0 :(得分:7)

您可以将输出重定向到文件:

resource "null_resource" "shell" {

  provisioner "local-exec" {
    command = "uptime 2>stderr >stdout; echo $? >exitstatus"
  }
}

然后使用stdout

阅读stderrexitstatuslocal_file个文件

问题是如果文件消失,则terraform apply将失败。

我通过使用外部数据源读取文件并将结果存储在null_resource触发器(!)

中来解决方法
resource "null_resource" "contents" {
  triggers = {
    stdout     = "${data.external.read.result["stdout"]}"
    stderr     = "${data.external.read.result["stderr"]}"
    exitstatus = "${data.external.read.result["exitstatus"]}"
  }

  lifecycle {
    ignore_changes = [
      "triggers",
    ]
  }
}

然后最后我可以使用/输出那些:

output "stdout" {
  value = "${chomp(null_resource.contents.triggers["stdout"])}"
}

请参阅模块https://github.com/matti/terraform-shell-resource以获取完整实施

答案 1 :(得分:3)

当我问自己同样的问题时,“我可以使用供应商的输出来输入其他资源的变量吗?”,我去了消息来源寻求答案。

此时,供应商结果只是流式传输到terraform的标准输出,从未被捕获。

鉴于您在两个节点上运行远程配置程序,并且您正在尝试从S3访问值 - 我同意这种方法,我会这样做 - 您可能需要做的是处理竞争条件在您的脚本中使用sleep命令,或通过安排脚本稍后使用atcron或类似的计划系统运行。

通常,Terraform希望预先访问所有变量,或者作为提供者的结果。在Terraform中,供应商不一定被视为头等舱。我不是核心团队,所以我不能说为什么,但我的猜测是,它降低了复杂性,忽略了成功或失败之外的供应商结果,因为供应商只是脚本,因此他们的结果通常是非结构化的。

如果您需要更多增强功能来设置实例,我建议使用专用工具,如Ansible,Chef,Puppet等.Terraform的重点是基础架构,而不是软件组件。

答案 2 :(得分:1)

更简单的解决方案是自己提供令牌。

创建ACL令牌时,只需传入ID值,consul就会使用它而不是随机生成一个。

答案 3 :(得分:1)

您可以有效地将节点0的docker swarm init步骤作为Terraform外部数据源运行,并让它返回JSON。使剩余节点的供应依赖于此步骤,并参考由外部数据源生成的加入令牌。

https://www.terraform.io/docs/providers/external/data_source.html

答案 4 :(得分:1)

您可以使用外部数据:

data "external" "docker_token" {
  program = ["/bin/bash", "-c" "echo \"{\\\"token\\\":\\\"$(docker swarm init...)\\\"}\""]
}

然后令牌将以data.external.docker_token.result.token的形式提供。 如果您需要传递参数,则可以使用脚本(例如相对于path.module的脚本)。有关详细信息,请参见https://www.terraform.io/docs/providers/external/data_source.html

答案 5 :(得分:0)

使用resource dependencies,您可以确保在另一个资源之前创建资源。

这是一个不完整的例子,说明我如何创建我的领事群,只是为了给你一个想法。

resource "aws_instance" "consul_1" {
    user_data = <<EOF
    #cloud-config
    runcmd:
    - 'docker pull consul:0.7.5'
    - 'docker run -d -v /etc/localtime:/etc/localtime:ro -v $(pwd)/consul-data:/consul/data --restart=unless-stopped --net=host consul:0.7.5 agent -server -advertise=${self.private_ip} -bootstrap-expect=2 -datacenter=wordpress -log-level=info -data-dir=/consul/data'
    EOF

}

resource "aws_instance" "consul_2" {

    depends_on = ["aws_instance.consul_1"]

    user_data = <<EOF
    #cloud-config
    runcmd:
    - 'docker pull consul:0.7.5'
    - 'docker run -d -v /etc/localtime:/etc/localtime:ro -v $(pwd)/consul-data:/consul/data --restart=unless-stopped --net=host consul:0.7.5 agent -server -advertise=${self.private_ip} -retry-join=${aws_instance.consul_1.private_ip} -datacenter=wordpress -log-level=info -data-dir=/consul/data'
    EOF

 }

对于docker swarm设置我认为它不在Terraform范围内,我认为它应该是因为令牌不是你正在创建的基础设施的属性。所以我同意nbering,您可以尝试使用像Ansible或Chef这样的工具来实现该设置。

但无论如何,如果该示例可以帮助您设置您的consul集群,我认为您只需要将consul配置为docker swarm后端。

答案 6 :(得分:0)

Sparrowform - 是基于Terraform的基础架构的轻量级配置程序,可以处理您的案例。这是aws ec2实例的示例。

假设我们为consul集群有3个ec2实例:node0,node1和node2。第一个(node0)是我们从中获取令牌并将其保存在S3存储桶中的位置。其他两个稍后从S3加载令牌。

$ nano aws_instance.node0.sparrowfile 

#!/usr/bin/env perl6

# have not checked this command, but that's the idea ...
bash "docker swarm init | aws s3 cp - s3://alexey-bucket/stream.txt"

$ nano aws_instance.node1.sparrowfile

#!/usr/bin/env perl6

my $i=0;
my $token;

try {

  while True {
    my $s3-token = run 'aws', 's3', 'cp', 's3://alexey-bucket/stream.txt', '-', :out;
    $token = $s3-token.out.lines[0];
    $s3-token.out.close;
    last if $i++ > 8 or $token;
    say "retry num $i ...";
    sleep 2*$i;
  }

  CATCH { { .resume } }

}

die "we have not succeed in fetching token" unless $token;

bash "docker swarm init $token";

$ nano aws_instance.node2.sparrowfile - the same setup as for node1


$ terrafrom apply # bootstrap infrastructure

$ sparrowform --ssh_private_key=~/.ssh/aws.pub --ssh_user=ec2-user # run provisioning on node0, node1, node2

PS披露,我是工具作者。