terraform setting hostname on multiple aws instances - terraform

I'm wanting to understand how user data can be used to set hostnames for 2 or more ec2 instances that terrafrom creates. Below is my instance.tf which creates 2 instances.
resource "aws_instance" "example" {
count = 2
ami = "${lookup(var.AMIS, var.aws_region)}"
instance_type = "t2.micro"
tags = {Name = "rb-${count.index}"}
# the VPC subnet
subnet_id = "${aws_subnet.dee-main-public-1.id}"
# the security group
vpc_security_group_ids = ["${aws_security_group.allow-ssh.id}"]
# the public SSH key
key_name = "${aws_key_pair.mykeypair.key_name}"
}
resource "aws_key_pair" "mykeypair" {
key_name = "mykeypair"
public_key = "${file("${var.PATH_TO_PUBLIC_KEY}")}"
}
How do I set the hostnames for those 2 instances. i.e web1.example.com, web2.example.com
I understand cloudinit or remote-exec can be used for this but struggling to come up with the code as I'm still a beginner. Really appreciate If I can get some help to come up to speed. Many thanks in advance.
-B

You can do it with data resource for template file which allows you to use count function. The below syntax will allow you use the hostname variable with the count function.
data "template_file" "init" {
count = 2
template = file("$path")
vars = {
hostname = "web${count.index}.example.com"
}
}
resource "aws_instance" "master" {
ami = "$ami"
count = 2
instance_type = t2.medium
user_data = data.template_file.init[count.index].rendered
}

Related

Issues with Terraform creating a string of IP addresses

I would like to create EC2 VMs with the following resource declaration using Terraform.
resource "aws_instance" "wurststand" {
for_each = toset(["bratwurst-01", "bratwurst-02", "bratwurst-03", "currywurst-01", "currywurst-02" , "hotdog-01"])
ami = "ami-0c9354388bb36c088"
instance_type = "t2.micro"
key_name = "wurststand"
tags = {
name = each.key
}
}
Now I need the public_ip addresses of all Bratwurst instances in one string. To create a group_vars file for ansible that fills a JINJA2 template again. How do I do this. I already tried to solder this with join but somehow Terraform doesn't like it.
The final result should look like this: 127.0.0.1, 127.0.0.2, 127.0.0.3.
Thank you for your help.
You need to use a for expression to extract the list of IPs from the set of aws_instance resources, and then pass that to join:
join(", ", [for i in aws_instance.wurststand : i.private_ip])
So this is my solution with which it does what I want.
resource "aws_instance" "wurststand" {
for_each = toset(["bratwurst-01", "bratwurst-02", "currywurst-01", "currywurst-02", "currywurst-03", "hotdog-01", "logstash-01"])
ami = "ami-0c9354388bb36c088"
instance_type = "t2.micro"
key_name = "wurststand"
tags = {
Name = each.key
}
}
resource "local_file" "inventory" {
content = templatefile("templates/inventory.tftpl", {
elastic = tomap({
for instance in aws_instance.wurststand:
instance.tags.Name => instance.public_ip
})
})
filename = format("%s/%s", abspath(path.root), "../ansible/inventory/inventory.cfg")
}
resource "local_file" "bratwurst_hosts_for_group_vars" {
content = templatefile("templates/bratwurst_groupvars.tftpl", {
hosts = join("\" , \"", [for i in aws_instance.wurststand: i.public_ip if contains("bratwurst", i.tags.Name)])
})
filename = format("%s/%s", abspath(path.root), "../ansible/group_vars/Bratwurst.yml")
}
Thanks a lot for your help.

Combine two simple objects to a list

I was wondering if anyone could suggest a trick to do this. Imagine I have a terraform code that retrieves the latest version of multiple AMI.
data "aws_ami" "amzn" {
most_recent = true
owners = ["amazon"]
filters...
}
data "aws_ami" "centos" {
most_recent = true
owners = ["12345678"]
filters...
}
What I would like to have is a list with both AWS AMi. The purpose is to choose between the two when I create an EC2 instance.
resource "aws_instance" "EC2" {
count = 100
ami = choose from list
instance_type = "t2.micro"
...
}
In this example, I have provided only two AMI. But in reality I will have about 20.
How a list will help you? You need to choose from it somehow. It's better to use a map for it, so you can pick a specific ami based on a key.
After you load the data resources, you can define a map using locals:
locals {
amis = {
amzn = data.aws_ami.amzn
centos = data.aws_ami.centos
}
}
Then to access it, you simply address it as follows
resource "aws_instance" "EC2" {
count = 100
ami = local.amis[amzn].id
instance_type = "t2.micro"
...
}

Conditionals in for_each loop around elastic IPs

I have the following code and I would like to have a conditional that will only create an elastic IP if the instance is part of a public subnet (or based off of a boolean value if needed). This is the code that I currently have that works, but I want it to not create elastic IPs for resources on the private subnets:
locals {
instances_beta = {
my-ec2 = {
name = "myec2",
ami = "ami-029e27fb2fc8ce9d8",
instancetype = "t3.xlarge"
environment = "Beta",
securitygroups = [var.mysg],
subnetid = var.public-a,
elasticip = true
}
}
}
resource "aws_instance" "beta-instance" {
for_each = local.instances_beta
ami = each.value.ami
instance_type = each.value.instancetype
subnet_id = each.value.subnetid
key_name = "mykey"
vpc_security_group_ids = each.value.securitygroups
tags = {
Name = each.value.name
Environment = each.value.environment
}
}
resource "aws_eip" "beta-eip" {
for_each = local.instances_beta
instance = aws_instance.beta-instance[each.key].id
vpc = true
}
It sounds like count is the best way to do something like that, but I cannot do that as I am already using a for_each to achieve the resource creation. I was trying to do this with a nested for loop, but I cannot quite figure out how to get the syntax correct or if this is the best way to do it. For reference , the best resource I found on it was here around for_each conditionals: https://blog.gruntwork.io/terraform-tips-tricks-loops-if-statements-and-gotchas-f739bbae55f9
You can use for loop to create filtered map, for example:
for_each = {
for key, value in local.instances_beta: key => value if value.subnetid == var.public-a
}
It will filter local.instances_beta and leave items where subnetid equals var.public-a. You can adjust condition according to your needs.
More details in terraform documentation.

Terraform provisioner for multiple instances

I am trying to accomplish adding 2 instances to a file called aws_worker_nodes_IP. This is the code below...again...I do not just need one worker IP listed I need both or all if my variables were to change.
I was told to use self.public_IP but that just list one. I need it for both.
#-----key pair for Workernodes-----
resource "aws_key_pair" "k8s-node_auth" {
key_name = "${var.key_name2}"
public_key = "${file(var.public_key_path2)}"
}
#-----Workernodes-----
resource "aws_instance" "nodes-opt-us1-k8s" {
instance_type = "${var.k8s-node_instance_type}"
ami = "${var.k8s-node_ami}"
count = "${var.NodeCount}"
tags {
Name = "nodes-opt-us1-k8s"
}
key_name = "${aws_key_pair.k8s-node_auth.id}"
vpc_security_group_ids = ["${aws_security_group.opt-us1-k8s_sg.id}"]
subnet_id = "${aws_subnet.opt-us1-k8s.id}"
#-----Link Terraform worker nodes to Ansible playbooks-----
provisioner "local-exec" {
command = <<EOD
cat <<EOF > aws_worker_nodes_IP
[workers]
${self.public_ip} <------need both here not just one
EOF
EOD
}
}
#----this has two nodes----- "count = "${var.NodeCount}"
Sorry if I did not explain correctly in my first question, and I appreciate the help. I have only been working with terraform for a few months. Also, I am a Network Engineer learning to write code.

Variance in attributes based on count.index in terraform

I'm using Hashicorp terraform to create a MySQL cluster on AWS. I created a module named mysql and want to tag the first instance created as the master. However, per terraform documentation:
Modules don't currently support the count parameter.
How do I work around this problem? Currently, I have these in my files:
$ cat project/main.tf
module "mysql_cluster" {
source = "./modules/mysql"
cluster_role = "${count.index == "0" ? "master" : "slave"}"
}
$ cat project/modules/mysql/main.tf
..
resource "aws_instance" "mysql" {
ami = "ami-123456"
instance_type = "t2.xlarge"
key_name = "rsa_2048"
tags {
Role = "${var.cluster_role}"
}
count = 3
}
This throws an error:
$ project git:(master) ✗ terraform plan
Error: module "mysql_cluster": count variables are only valid within resources
I have the necessary variables declared in the variables.tf files in my mysql module and root module. How do I work around this problem? Thanks in advance for any help!
The way you have count in the module resource would infer that you want 3 modules created, rather than 3 resources within the module created. You can stipulate the count from the module resource but any logic using count.index needs to sit within the module.
main.tf
module "mysql_cluster" {
source = "./modules/mysql"
instance_count = 3
}
mysql.tf
resource "aws_instance" "mysql" {
count = "${var.instance_count}"
ami = "ami-123456"
instance_type = "t2.xlarge"
key_name = "rsa_2048"
tags {
Role = "${count.index == "0" ? "master" : "slave"}"
}
}
Since Terraform 0.13 you can use either for_each or count to create multiple instances of a module.
variable "regions" {
type = map(object({
region = string
network = string
subnetwork = string
ip_range_pods = string
ip_range_services = string
}))
}
module "kubernetes_cluster" {
source = "terraform-google-modules/kubernetes-engine/google"
for_each = var.regions
project_id = var.project_id
name = each.key
region = each.value.region
network = each.value.network
subnetwork = each.value.subnetwork
ip_range_pods = each.value.ip_range_pods
ip_range_services = each.value.ip_range_services
}
Code snipped from
https://github.com/hashicorp/terraform/tree/guide-v0.13-beta/module-repetition
Official documentation
https://www.terraform.io/docs/configuration/modules.html
module doesn't have count. It is available at resources only.

Resources