πŸ—οΈ Provision AWS EC2 Instances with Terraform and Set Up Docker via User Data

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5175

    #1

    πŸ—οΈ Provision AWS EC2 Instances with Terraform and Set Up Docker via User Data

    πŸ“‹Workflow

    1️⃣ πŸ“ Structure

    2️⃣ 🌱 Root module

    3️⃣ πŸ’» EC2 module

    4️⃣ πŸ›‘οΈ Security Group module


    πŸ”—Link to project



    1️⃣ πŸ“ Structure




    β”œβ”€β”€ dev.tfvars
    β”œβ”€β”€ main.tf
    β”œβ”€β”€ provider.tf
    β”œβ”€β”€ modules
    β”‚ β”œβ”€β”€ ec2
    β”‚ β”‚ β”œβ”€β”€ install_docker.sh
    β”‚ β”‚ β”œβ”€β”€ main.tf
    β”‚ β”‚ β”œβ”€β”€ output.tf
    β”‚ β”‚ └── variables.tf
    β”‚ └── security_group
    β”‚ β”œβ”€β”€ igw.tf
    β”‚ β”œβ”€β”€ output.tf
    β”‚ β”œβ”€β”€ rt.tf
    β”‚ β”œβ”€β”€ sg.tf
    β”‚ β”œβ”€β”€ subnet.tf
    β”‚ └── vpc.tf







    Why it's important to split terraform into modules?

    1. πŸ”„ Code reuse
    2. πŸ“ˆ Improved scalability
    3. 🧩 Modularity and abstraction
    4. πŸ‘₯ Clear separation of responsibilities.
    5. πŸ› οΈ Simplified maintenance
    6. πŸ“– Improved readability
    7. πŸ”„ Efficient version control

    2️⃣ 🌱 Root module




    # main.tf
    variable "ami" {}
    variable "instance_type" {}
    variable "key_name" {}


    #Replace the resource with a module
    module "ec2_instance" {
    #References file location of new module
    source = "./modules/ec2"

    ami = var.ami
    instance_type = var.instance_type
    key_name = var.key_name

    subnet_id = module.security_group.subnet_id # Call the name of variable output

    sg_id = module.security_group.sg_id


    }

    # Specify the name for the security group
    module "security_group" {
    source = "./modules/security_group"

    }









    #provider.tf
    terraform {
    required_providers {
    aws = {
    source = "hashicorp/aws"
    version = "~> 5.52.0"
    }
    }

    }


    provider "aws" {
    region = "us-east-2"
    }









    # dev.tfvars
    ami = "ami-0b4624933067d393a" # each ami is region specific
    instance_type = "t2.micro"
    key_name = "ec2_key"





    3️⃣ πŸ’» EC2 module




    # modules/ec2/main.tf
    variable "ami" {}
    variable "instance_type" {}
    variable "key_name" {}

    resource "aws_instance" "ec2" {
    ami = var.ami
    instance_type = var.instance_type
    key_name = var.key_name

    vpc_security_group_ids = [var.sg_id]
    subnet_id = var.subnet_id
    user_data = "${file("./modules/ec2/install_docker.sh")}"

    tags = {
    Name = "traefik-demo"
    }
    }










    # modules/ec2/variables.tf
    variable "sg_id" {
    description = "Security Group ID"
    type = string
    }
    variable "subnet_id" {
    description = "Subnet ID"
    type = string
    }






    πŸ”‘ Key points.

    The EC2 module requires two essential fields:
    • πŸ›‘οΈ Security Group ID.
    • 🌐 Subnet ID



    # modules/ec2/output.tf
    output "instance_id" {
    value = aws_instance.ec2.id
    }

    output "public_ip" {
    value = aws_instance.ec2.public_ip
    }









    #!/bin/bash
    # Update and install Docker
    sudo yum update -y
    sudo yum install -y jq docker


    # Enable Docker service
    sudo service docker start
    sudo usermod -a -G docker ec2-user

    # Get the latest version of Docker Compose
    DOCKER_COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/...eleases/latest | jq -r .tag_name)

    # Install Docker Compose
    sudo curl -L "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    sudo chmod +x /usr/local/bin/docker-compose





    4️⃣ πŸ›‘οΈ Security Group module

    vpc.tf






    #modules/security_group/vpc.tf
    resource "aws_vpc" "traefik_vpc" {
    cidr_block = "10.0.0.0/16"

    tags = {
    Name = "main-vpc"
    }
    }











    #modules/security_group/subnet.tf
    resource "aws_subnet" "traefik_subnet" {
    vpc_id = aws_vpc.traefik_vpc.id
    cidr_block = "10.0.1.0/24"
    map_public_ip_on_launch = true

    tags = {
    Name = "example-subnet"
    }
    }











    #modules/security_group/sg.tf
    resource "aws_security_group" "traefik_sg" {
    name = "traefik-security-group"
    vpc_id = aws_vpc.traefik_vpc.id
    description = "Allow HTTP and HTTPS traffic"

    ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    }

    ingress {
    from_port = 443
    to_port = 443
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    }
    ingress {
    from_port = 22
    to_port = 22
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"] # Allow ssh from internet
    }

    egress {
    from_port = 0
    to_port = 0
    protocol = "-1" # Allow all traffic
    cidr_blocks = ["0.0.0.0/0"]
    }

    tags = {
    Name = "traefik_sg"
    }
    }












    #modules/security_group/rt.tf
    resource "aws_route_table" "traefik_route_table" {
    vpc_id = aws_vpc.traefik_vpc.id

    route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.traefik_igw.id
    }

    tags = {
    Name = "route_table"
    }
    }
    resource "aws_route_table_association" "a" {
    subnet_id = aws_subnet.traefik_subnet.id
    route_table_id = aws_route_table.traefik_route_table.id
    }











    #modules/security_group/igw.tf
    resource "aws_internet_gateway" "traefik_igw" {
    vpc_id = aws_vpc.traefik_vpc.id

    tags = {
    Name = "traefik_IGW"
    }
    }











    #modules/security_group/output.tf
    output "subnet_id" {
    value = aws_subnet.traefik_subnet.id
    }

    output "sg_id" {
    value = aws_security_group.traefik_sg.id
    }







    πŸ—οΈ Terraform deploy





    terraform init
    terraform validate --var-file=dev.tfvars
    terraform plan --var-file=dev.tfvars
    terraform apply --var-file=dev.tfvars -auto-approve









    More...
Working...