首页 文章

捕获Terraform配置器输出?

提问于
浏览
2

Use Case

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

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

Attempts So Far

  • 尝试使用安装的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的一个较小的子集直接相关,但是想要重新询问它以便集中解决问题的范围 .

6 回答

  • 4

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

    这是我如何创建我的consul集群的一个不完整的例子,只是为了给你一个想法 .

    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设置,我认为它是您正在创建的基础结构的属性's out of Terraform scope and I think it should because the token isn't . 所以我同意nbering,您可以尝试使用像Ansible或Chef这样的工具来实现该设置 .

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

  • 2

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

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

  • 0

    当我问自己同样的问题时,“我可以使用供应商的输出来输入另一个资源的变量吗?”,我去了源头寻求答案 .

    在这个时刻,供应商结果只是流式传输到terraform的标准输出,从未被捕获 .

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

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

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

  • 0

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

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

  • 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披露,我是工具作者 .

  • 0

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

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

    然后用 local_file 读取 stdoutstderrexitstatus 文件

    问题是如果文件消失,那么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

相关问题