【问题标题】:Capture Terraform provisioner output?捕获 Terraform 供应商输出?
【发布时间】:2017-11-14 13:16:33
【问题描述】:

用例

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

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

目前的尝试

  • 尝试初始化安装了 AWS 开发工具包的所有节点,并将节点 0 的连接密钥存储在 S3 上,然后在其他节点上获取该连接密钥。这是通过带有“remote-exec”供应商的 null_resource 完成的。由于 Terraform 并行执行事物的方式,存在一些竞争类型的条件,并且可以预见,节点 1 和 2 经常尝试从 S3 获取尚未存在的密钥(例如,节点 0 尚未完成其工作)。

  • 尝试使用“local-exec”配置程序通过 SSH 连接到节点 0 并捕获其连接密钥输出。这效果不好,或者我做的很烂。


我已阅读文档。和堆栈溢出。还有 Github 问题,例如 this really long outstanding one。彻底。如果这已在其他地方得到解决,链接表示赞赏!


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

【问题讨论】:

    标签: terraform


    【解决方案1】:

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

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

    然后用local_file读取stdoutstderrexitstatus文件

    问题是如果文件消失了,那么 terraform apply 就会失败。

    在 terraform 0.11 中,我通过使用外部数据源读取文件并将结果存储在 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",
        ]
      }
    }
    

    但在 0.12 中,这可以替换为 file()

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

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

    查看模块 https://github.com/matti/terraform-shell-resource 了解完整实现

    【讨论】:

      【解决方案2】:

      当我问自己同样的问题时,“我可以使用配置器的输出来输入另一个资源的变量吗?”,我去源头寻找答案。

      此时,provisioner 结果只是简单地流式传输到 terraform 的标准输出,并且从未被捕获。

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

      一般来说,Terraform 想要预先访问所有变量,或者作为提供者的结果。在 Terraform 中,Provisioner 不一定被视为一流的。我不在核心团队,所以我不能说为什么,但我的猜测是,除了成功或失败之外,忽略配置器结果会降低复杂性,因为配置器只是脚本,因此它们的结果通常是非结构化的。

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

      【讨论】:

      • 得出了与您在上面阐述的相同的结论 - 我认为 terraform 更适合基础架构组件(仅),而最好将配置和引导的东西留给更适合处理的工具就像 Ansible 一样。谢谢!
      【解决方案3】:

      您可以使用外部数据:

      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

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

          【解决方案5】:

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

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

          【讨论】:

            【解决方案6】:

            使用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 后端。

            【讨论】:

            • 感谢发布您如何处理领事设置。我有点同意 docker swarm 设置可能超出了 Terraform 的范围——但它与设置 consul 集群是一样的,所以如果引导 Consul 有意义,那么我会说引导 Docker Swarm 是有意义的也是。仍在考虑...此外,不幸的是,使用最新的 Docker Swarm(截至 1.13),后端不再可配置,以代替 Swarm 自己的分布式状态存储。无论如何 - 感谢您的回复。
            【解决方案7】:

            Sparrowform - 是基于 Terraform 的基础设施的轻量级供应商,可以处理您的情况。这是 aws ec2 实例的示例。

            假设我们有 3 个用于 consul 集群的 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披露,我是工具作者。

            【讨论】:

              猜你喜欢
              • 2020-07-01
              • 1970-01-01
              • 1970-01-01
              • 2017-10-23
              • 1970-01-01
              • 2017-02-14
              • 2019-06-06
              • 1970-01-01
              • 2016-01-23
              相关资源
              最近更新 更多