使用案例
尝试配置(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}}的较小子集,但是想重新询问它以便集中解决问题的范围。
答案 0 :(得分:7)
您可以将输出重定向到文件:
resource "null_resource" "shell" {
provisioner "local-exec" {
command = "uptime 2>stderr >stdout; echo $? >exitstatus"
}
}
然后使用stdout
stderr
,exitstatus
和local_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
命令,或通过安排脚本稍后使用at
或cron
或类似的计划系统运行。
通常,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披露,我是工具作者。