I am trying to use cloud-init and terraform to execute scripts on first boot of my instance. A simplified version of what I'm trying to do looks like the following:
#cloud-config
runcmd:
- echo { node_name}
data "template_file" "user_data" {
template = file("../../cloud-init/init-vm.yaml")
# Set variables for the template
vars = {
bacalhauservice = aws_instance.instance.public_dns
}
}
resource "aws_instance" "instance" {
ami = var.instance_ami
instance_type = var.instance_type
subnet_id = var.subnet_public_id
vpc_security_group_ids = var.security_group_ids
key_name = var.key_pair_name
availability_zone = var.availability_zone
user_data = data.template_file.user_data.rendered
}
This doesn't work - it's circular. What are my options?
It isn't possible for the user_data
value for an EC2 instance to include the IP address of that same instance, because the IP address isn't allocated until after the API call to create the EC2 instance, which must include the user_data
value.
Therefore you will need to find a different approach to solve your problem. There are a few different options:
If you only need the IP address of the same instance running the code anyway, then you don't need to include it in the user_data
string because by the time any code you supply there is running the EC2 instance must already know its own IP address from having been able to request the user_data
script in the first place. You can use standard features of the operating system in your chosen AMI to determine the allocated IP address.
If you need IP addresses of this and other related IP addresses then one common answer is to access that information by calling the EC2 API from inside your boot script. To do this you will need to assign your EC2 instance an IAM instance profile which has access to call whatever EC2 API actions you choose to use.
For example, you could choose to use ec2:DescribeInstances
to find all of the EC2 instances belonging to a particular security group, and then make sure you add all of the instances you want to include to that security group so you can discover their IP addresses dynamically at runtime.
If you adopt this strategy then you will need to decide how to handle the fact that the EC2 instances won't all start up simultaneously and so each one will see a different subset of available EC2 instances. One possible answer is to politely poll the API every minute or two so that you can see which instances have been added or removed, and then react to those changes dynamically. This approach also makes it more feasible to use EC2 autoscaling if you need to do that later, since your system will be able to respond automatically to changes to the instances made by the autoscaling system.
A final option is to preallocate private IP addresses separately from creating the EC2 instances that will use them, in which case you can use the private IP of the network interface to populate your template.
For more on that possibility, see How to keep the same public IP addresses for a set of AWS EC2 instances with Terraform.
Also please note that template_file
and the provider it belongs to have been deprecated for a long time. The modern replacement is the built-in function templatefile
, which you can use like this:
user_data = templatefile("${path.module}/../../cloud-init/init-vm.yaml", {
bacalhauservice = network_interface.example.private_ip
})