如何让 Terraform 等待 cloudinit 完成?
How to make Terraform wait for cloudinit to finish?
在我的 Terraform AWS Docker Swarm module 中,我使用 cloud-init 来初始化 EC2 实例。但是,Terraform 表示资源在 cloud-init 完成之前就已准备就绪。有没有办法让它等待 cloud-init 完成 理想情况下 而无需 SSH 连接或使用 null
资源检查要启动的端口。
你的 managers and workers 都使用 template_cloudinit_config。他们还有 ec2:CreateTags.
您可以使用 trajano/terraform-docker-swarm-aws/cloudinit-complete 等 EC2 资源标签来指示 cloudinit 已完成。
您可以将这最后一部分添加到每个以调用标记脚本:
部分{
文件名 = "tag_complete.sh"
内容 = local.tag_complete_script
content_type = "text/x-shellscript"
}
并声明tag_complete_script如下:
locals {
tag_complete_script = <<-EOF
#!/bin/bash
instance_id="${TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` \
&& curl -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/meta-data/instance-id}"
aws ec2 create-tags --resources "$instance_id" --tags 'Key=trajano/terraform-docker-swarm-aws/cloudinit-complete,Value=true'
EOF
}
然后用 null_resource,等待标签出现(在我的 phone 上写了这个未经测试和编辑):
resource "null_resource" "wait_for_cloudinit" {
provisioner "local-exec" {
command = <<-EOF
#!/bin/bash
poll_tags="aws ec2 describe-tags --filters 'Name=resource-id,Values=${join(",", aws_instance.managers[*].id)}' 'Name=key,Values=trajano/terraform-docker-swarm-aws/cloudinit-complete' --output text --query 'Tags[*].Value'"
expected='${join(",", formatlist("true", aws_instance.managers[*].id))}'
$tags="$($poll_tags)"
while [[ "$tags" != "$expected" ]] ; do
$tags="$($poll_tags)"
done
EOF
}
}
通过这种方式,在 cloudinit 完成后,您可以对 null_resource.wait_for_cloudinit 需要 运行 的任何资源产生依赖。
另一种可能的方法是使用 AWS Systems Manager Run Command,如果您的 AMI 可用的话。
您使用使用 cloud-init status --wait
命令的 Terraform 创建 SSM 文档,然后从本地供应商触发该命令,并等待它完成。这样你就不用去玩tags了,100%确定cloud-init已经完成了。
这是您可以使用 Terraform 创建的文档示例:
resource "aws_ssm_document" "cloud_init_wait" {
name = "cloud-init-wait"
document_type = "Command"
document_format = "YAML"
content = <<-DOC
schemaVersion: '2.2'
description: Wait for cloud init to finish
mainSteps:
- action: aws:runShellScript
name: StopOnLinux
precondition:
StringEquals:
- platformType
- Linux
inputs:
runCommand:
- cloud-init status --wait
DOC
}
然后您可以在 EC2 实例块内 或空资源中使用本地供应商 ,直到您需要用它做什么。
供应商或多或少是这样的:
provisioner "local-exec" {
interpreter = ["/bin/bash", "-c"]
command = <<-EOF
set -Ee -o pipefail
export AWS_DEFAULT_REGION=${data.aws_region.current.name}
command_id=$(aws ssm send-command --document-name ${aws_ssm_document.cloud_init_wait.arn} --instance-ids ${self.id} --output text --query "Command.CommandId")
if ! aws ssm wait command-executed --command-id $command_id --instance-id ${self.id}; then
echo "Failed to start services on instance ${self.id}!";
echo "stdout:";
aws ssm get-command-invocation --command-id $command_id --instance-id ${self.id} --query StandardOutputContent;
echo "stderr:";
aws ssm get-command-invocation --command-id $command_id --instance-id ${self.id} --query StandardErrorContent;
exit 1;
fi;
echo "Services started successfully on the new instance with id ${self.id}!"
EOF
}
在我的 Terraform AWS Docker Swarm module 中,我使用 cloud-init 来初始化 EC2 实例。但是,Terraform 表示资源在 cloud-init 完成之前就已准备就绪。有没有办法让它等待 cloud-init 完成 理想情况下 而无需 SSH 连接或使用 null
资源检查要启动的端口。
你的 managers and workers 都使用 template_cloudinit_config。他们还有 ec2:CreateTags.
您可以使用 trajano/terraform-docker-swarm-aws/cloudinit-complete 等 EC2 资源标签来指示 cloudinit 已完成。
您可以将这最后一部分添加到每个以调用标记脚本:
部分{ 文件名 = "tag_complete.sh" 内容 = local.tag_complete_script content_type = "text/x-shellscript" }
并声明tag_complete_script如下:
locals {
tag_complete_script = <<-EOF
#!/bin/bash
instance_id="${TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` \
&& curl -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/meta-data/instance-id}"
aws ec2 create-tags --resources "$instance_id" --tags 'Key=trajano/terraform-docker-swarm-aws/cloudinit-complete,Value=true'
EOF
}
然后用 null_resource,等待标签出现(在我的 phone 上写了这个未经测试和编辑):
resource "null_resource" "wait_for_cloudinit" {
provisioner "local-exec" {
command = <<-EOF
#!/bin/bash
poll_tags="aws ec2 describe-tags --filters 'Name=resource-id,Values=${join(",", aws_instance.managers[*].id)}' 'Name=key,Values=trajano/terraform-docker-swarm-aws/cloudinit-complete' --output text --query 'Tags[*].Value'"
expected='${join(",", formatlist("true", aws_instance.managers[*].id))}'
$tags="$($poll_tags)"
while [[ "$tags" != "$expected" ]] ; do
$tags="$($poll_tags)"
done
EOF
}
}
通过这种方式,在 cloudinit 完成后,您可以对 null_resource.wait_for_cloudinit 需要 运行 的任何资源产生依赖。
另一种可能的方法是使用 AWS Systems Manager Run Command,如果您的 AMI 可用的话。
您使用使用 cloud-init status --wait
命令的 Terraform 创建 SSM 文档,然后从本地供应商触发该命令,并等待它完成。这样你就不用去玩tags了,100%确定cloud-init已经完成了。
这是您可以使用 Terraform 创建的文档示例:
resource "aws_ssm_document" "cloud_init_wait" {
name = "cloud-init-wait"
document_type = "Command"
document_format = "YAML"
content = <<-DOC
schemaVersion: '2.2'
description: Wait for cloud init to finish
mainSteps:
- action: aws:runShellScript
name: StopOnLinux
precondition:
StringEquals:
- platformType
- Linux
inputs:
runCommand:
- cloud-init status --wait
DOC
}
然后您可以在 EC2 实例块内 或空资源中使用本地供应商 ,直到您需要用它做什么。
供应商或多或少是这样的:
provisioner "local-exec" {
interpreter = ["/bin/bash", "-c"]
command = <<-EOF
set -Ee -o pipefail
export AWS_DEFAULT_REGION=${data.aws_region.current.name}
command_id=$(aws ssm send-command --document-name ${aws_ssm_document.cloud_init_wait.arn} --instance-ids ${self.id} --output text --query "Command.CommandId")
if ! aws ssm wait command-executed --command-id $command_id --instance-id ${self.id}; then
echo "Failed to start services on instance ${self.id}!";
echo "stdout:";
aws ssm get-command-invocation --command-id $command_id --instance-id ${self.id} --query StandardOutputContent;
echo "stderr:";
aws ssm get-command-invocation --command-id $command_id --instance-id ${self.id} --query StandardErrorContent;
exit 1;
fi;
echo "Services started successfully on the new instance with id ${self.id}!"
EOF
}