IaC - Deploying AWS EC2 user-data with Terraform

IaC - Deploying AWS EC2 user-data with Terraform

·

5 min read

When we launch an EC2 instance, we can pass user data to the instance for performing common automated configuration tasks or running scripts after the instance boot.

With Terraform, like using the console, we could 'paste' the script we would like to use in user-data.

Here is a sample user-data embedded into tf file:

resource "aws_instance" "my-instance" {
     ami = "ami-04169656fea786776"
     instance_type = "t2.nano"
     user_data = << EOF
         #! /bin/bash
        apt-get update
         apt-get install -y apache2
         systemctl start apache2
         systemctl enable apache2
         echo "<h1>Deployed via Terraform</h1>" | sudo tee /var/www/html/index.html
     EOF
     tags = {
         Name = "Terraform"    
     }
 }

But how if the script is quite long?

So we prefer to use file() function.

Here is the code example of installing metricbeat on Amazon Linux.

ec2.tf

# https://aws.amazon.com/blogs/compute/query-for-the-latest-amazon-linux-ami-ids-using-aws-systems-manager-parameter-store/
data "aws_ssm_parameter" "amazonlinux2" {
  name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
}

resource "aws_instance" "metricbeat" {
  ami                         = data.aws_ssm_parameter.amazonlinux2.value
  associate_public_ip_address = false
  iam_instance_profile        = aws_iam_instance_profile.ec2_metricbeat_instance_profile.name
  instance_type               = var.ec2_instance_type
  user_data = file("metricbeat.sh")
  subnet_id = var.private_subnet
  timeouts { create = "30m" }

}

metricbeat.sh

#!/bin/bash -xe
 cd /tmp/
 yum update -y
 curl -L -O https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-7.8.0-x86_64.rpm
 rpm -iv metricbeat-7.8.0-x86_64.rpm
 metricbeat modules disable system
 rm /etc/metricbeat/metricbeat.yml
 touch /etc/metricbeat/metricbeat.yml

 cat > /etc/metricbeat/metricbeat.yml << EOM
 ##################### Metricbeat Configuration Example #######################

 # =========================== Modules configuration ============================
 metricbeat.config.modules:
 # Glob pattern for configuration loading
   path: /etc/metricbeat/modules.d/*.yml

 # Set to true to enable config reloading
 reload.enabled: false

 # ======================= Elasticsearch template setting =======================
 setup.template:
 name: "metricbeat--inframetrics"
 pattern: "metricbeat--inframetrics-*"
 setup.ilm.enabled: true
 setup.ilm.rollover_alias: "metricbeat--inframetrics"
 setup.ilm.policy_name: "metricbeat-*" # should be ALWAYS <beatsname>-*
 setup.ilm.pattern: "{now/M{yyyy.MM}}-001"
 # =================================== Kibana ===================================
 setup.kibana:
   host: "https://"
   space.id: "dev"
 # ---------------------------- Elasticsearch Output ----------------------------
 output.elasticsearch:
   hosts: ["https://"]

 # Authentication credentials - either API key or username/password.
   api_key: "key"
 # ================================= Processors =================================

 # Configure processors to enhance or manipulate events generated by the beat.
 processors:
   - add_host_metadata: ~
   - add_docker_metadata: ~
   - add_kubernetes_metadata: ~

 logging.level: debug
 EOM

 rm /etc/metricbeat/modules.d/aws.yml.disabled
 touch /etc/metricbeat/modules.d/aws.yml

 cat > /etc/metricbeat/modules.d/aws.yml << \EOM
 - module: aws
   period: 2m
   region: eu-west-1
   metricsets:
     - cloudwatch
   metrics:
     - namespace: AWS/SQS
 - module: aws
   period: 2m
   region: eu-west-1
   metricsets:
     - cloudwatch
   metrics:
     - namespace: AWS/ECS

 - module: aws
   period: 300s
   region: eu-west-1
   metricsets:
     - sqs

 - module: aws
   period: 86400s
   region: eu-west-1
   metricsets:
     - s3_daily_storage
     - s3_request
 EOM

 systemctl start metricbeat
 systemctl enable metricbeat

Furthermore, at some point, we would like to pass terraform attributes or variables to the script.

How do we do that?

templatefile() function to the rescue.

templatefile reads the file at the given path and renders its content as a template using a supplied set of template variables.

ec2.tf

# https://aws.amazon.com/blogs/compute/query-for-the-latest-amazon-linux-ami-ids-using-aws-systems-manager-parameter-store/
data "aws_ssm_parameter" "amazonlinux2" {
  name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
}

resource "aws_instance" "metricbeat" {
  ami                         = data.aws_ssm_parameter.amazonlinux2.value
  associate_public_ip_address = false
  iam_instance_profile        = aws_iam_instance_profile.ec2_metricbeat_instance_profile.name
  instance_type               = var.ec2_instance_type
  user_data = templatefile("${path.module}/metricbeat.tpl", {
  space = "${var.short_name}_${var.environment}", api_key = var.elk_api_key })  
  subnet_id = var.private_subnet
  timeouts { create = "30m" }

}

metricbeat.tpl

#!/bin/bash -xe
 cd /tmp/
 yum update -y
 curl -L -O https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-7.8.0-x86_64.rpm
 rpm -iv metricbeat-7.8.0-x86_64.rpm
 metricbeat modules disable system
 rm /etc/metricbeat/metricbeat.yml
 touch /etc/metricbeat/metricbeat.yml

 cat > /etc/metricbeat/metricbeat.yml << EOM
 ##################### Metricbeat Configuration Example #######################

 # =========================== Modules configuration ============================
 metricbeat.config.modules:
 # Glob pattern for configuration loading
   path: /etc/metricbeat/modules.d/*.yml

 # Set to true to enable config reloading
 reload.enabled: false

 # ======================= Elasticsearch template setting =======================
 setup.template:
 name: "metricbeat--inframetrics"
 pattern: "metricbeat--inframetrics-*"
 setup.ilm.enabled: true
 setup.ilm.rollover_alias: "metricbeat--inframetrics"
 setup.ilm.policy_name: "metricbeat-*" # should be ALWAYS <beatsname>-*
 setup.ilm.pattern: "{now/M{yyyy.MM}}-001"
 # =================================== Kibana ===================================
 setup.kibana:
   host: "https://"
   space.id: ${space}
 # ---------------------------- Elasticsearch Output ----------------------------
 output.elasticsearch:
   hosts: ["https://"]

 # Authentication credentials - either API key or username/password.
   api_key: ${api_key}
 # ================================= Processors =================================

 #Configure processors to enhance or manipulate events generated by the beat.
 processors:
   - add_host_metadata: ~
   - add_docker_metadata: ~
   - add_kubernetes_metadata: ~

 logging.level: debug
 EOM

 rm /etc/metricbeat/modules.d/aws.yml.disabled
 touch /etc/metricbeat/modules.d/aws.yml

 cat > /etc/metricbeat/modules.d/aws.yml << \EOM
 - module: aws
   period: 2m
   region: eu-west-1
   metricsets:
     - cloudwatch
   metrics:
     - namespace: AWS/SQS
 - module: aws
   period: 2m
   region: eu-west-1
   metricsets:
     - cloudwatch
   metrics:
     - namespace: AWS/ECS

 - module: aws
   period: 300s
   region: eu-west-1
   metricsets:
     - sqs

 - module: aws
   period: 86400s
   region: eu-west-1
   metricsets:
     - s3_daily_storage
     - s3_request
 EOM

 systemctl start metricbeat
 systemctl enable metricbeat

Thanks for visiting the blog.

See you in the next post.