Cloud

AWS Infrastructure Automation with Ansible: Complete Guide

Teach me Ansible | 2025-01-08 | 22 min read

Automate AWS infrastructure with Ansible. Learn to provision EC2 instances, configure VPCs, manage security groups, and deploy applications on AWS using Ansible's powerful cloud modules.

Why Use Ansible for AWS?

  • Unified tool for both infrastructure provisioning and configuration
  • No need to learn CloudFormation or Terraform syntax
  • Idempotent operations ensure safe re-runs
  • Easy integration with existing Ansible workflows
  • Supports multi-cloud environments

Prerequisites

# Install AWS collections
ansible-galaxy collection install amazon.aws community.aws

# Install boto3 (AWS SDK for Python)
pip install boto3 botocore

# Configure AWS credentials
export AWS_ACCESS_KEY_ID=your_access_key
export AWS_SECRET_ACCESS_KEY=your_secret_key
export AWS_DEFAULT_REGION=us-east-1

Example 1: Create VPC and Subnet

---
- name: Create AWS VPC Infrastructure
  hosts: localhost
  gather_facts: no
  vars:
    vpc_cidr: "10.0.0.0/16"
    subnet_cidr: "10.0.1.0/24"
    region: "us-east-1"

  tasks:
    - name: Create VPC
      amazon.aws.ec2_vpc_net:
        name: "my-vpc"
        cidr_block: "{{ vpc_cidr }}"
        region: "{{ region }}"
        tags:
          Environment: Production
      register: vpc

    - name: Create Internet Gateway
      amazon.aws.ec2_vpc_igw:
        vpc_id: "{{ vpc.vpc.id }}"
        region: "{{ region }}"
        tags:
          Name: "my-igw"
      register: igw

    - name: Create Subnet
      amazon.aws.ec2_vpc_subnet:
        vpc_id: "{{ vpc.vpc.id }}"
        cidr: "{{ subnet_cidr }}"
        az: "{{ region }}a"
        region: "{{ region }}"
        tags:
          Name: "my-subnet"
      register: subnet

    - name: Create Route Table
      amazon.aws.ec2_vpc_route_table:
        vpc_id: "{{ vpc.vpc.id }}"
        region: "{{ region }}"
        tags:
          Name: "my-route-table"
        subnets:
          - "{{ subnet.subnet.id }}"
        routes:
          - dest: "0.0.0.0/0"
            gateway_id: "{{ igw.gateway_id }}"

Example 2: Launch EC2 Instances

---
- name: Launch EC2 Instances
  hosts: localhost
  gather_facts: no
  vars:
    instance_type: "t2.micro"
    ami_id: "ami-0c55b159cbfafe1f0"  # Amazon Linux 2
    key_name: "my-key-pair"
    region: "us-east-1"

  tasks:
    - name: Create Security Group
      amazon.aws.ec2_security_group:
        name: "web-server-sg"
        description: "Security group for web servers"
        region: "{{ region }}"
        rules:
          - proto: tcp
            ports: 80
            cidr_ip: 0.0.0.0/0
            rule_desc: "Allow HTTP"
          - proto: tcp
            ports: 443
            cidr_ip: 0.0.0.0/0
            rule_desc: "Allow HTTPS"
          - proto: tcp
            ports: 22
            cidr_ip: 0.0.0.0/0
            rule_desc: "Allow SSH"
      register: security_group

    - name: Launch EC2 instances
      amazon.aws.ec2_instance:
        name: "web-server-{{ item }}"
        instance_type: "{{ instance_type }}"
        image_id: "{{ ami_id }}"
        key_name: "{{ key_name }}"
        region: "{{ region }}"
        security_group: "{{ security_group.group_id }}"
        tags:
          Environment: Production
          Role: WebServer
        wait: yes
      loop: [1, 2, 3]
      register: ec2_instances

    - name: Display instance IPs
      debug:
        msg: "Instance {{ item.instance_id }}: {{ item.public_ip_address }}"
      loop: "{{ ec2_instances.results | map(attribute='instances') | flatten }}"

Example 3: Configure Launched Instances

---
- name: Configure EC2 Instances
  hosts: tag_Role_WebServer
  become: yes
  gather_facts: yes

  tasks:
    - name: Update system packages
      yum:
        name: '*'
        state: latest

    - name: Install web server
      yum:
        name:
          - httpd
          - php
        state: present

    - name: Start and enable Apache
      service:
        name: httpd
        state: started
        enabled: yes

    - name: Deploy application
      copy:
        content: |
          
        dest: /var/www/html/index.php

Example 4: Create Application Load Balancer

---
- name: Create Application Load Balancer
  hosts: localhost
  gather_facts: no

  tasks:
    - name: Create Target Group
      community.aws.elb_target_group:
        name: "web-servers-tg"
        protocol: http
        port: 80
        vpc_id: "{{ vpc_id }}"
        health_check_path: "/"
        health_check_interval: 30
        region: "{{ region }}"
      register: target_group

    - name: Create Application Load Balancer
      amazon.aws.elb_application_lb:
        name: "web-alb"
        region: "{{ region }}"
        subnets:
          - "{{ subnet1_id }}"
          - "{{ subnet2_id }}"
        security_groups:
          - "{{ alb_security_group_id }}"
        listeners:
          - Protocol: HTTP
            Port: 80
            DefaultActions:
              - Type: forward
                TargetGroupArn: "{{ target_group.target_group_arn }}"
      register: alb

    - name: Register instances with target group
      community.aws.elb_target:
        target_group_arn: "{{ target_group.target_group_arn }}"
        target_id: "{{ item.instance_id }}"
        region: "{{ region }}"
        state: present
      loop: "{{ ec2_instances }}"

Example 5: S3 Bucket Management

---
- name: Manage S3 Buckets
  hosts: localhost
  gather_facts: no

  tasks:
    - name: Create S3 bucket
      amazon.aws.s3_bucket:
        name: "my-app-static-files"
        region: "{{ region }}"
        versioning: yes
        encryption: "AES256"
        tags:
          Environment: Production

    - name: Upload files to S3
      amazon.aws.s3_object:
        bucket: "my-app-static-files"
        object: "{{ item.key }}"
        src: "{{ item.src }}"
        mode: put
      loop:
        - { key: "css/style.css", src: "/local/path/style.css" }
        - { key: "js/app.js", src: "/local/path/app.js" }

    - name: Set bucket policy for public read
      amazon.aws.s3_bucket:
        name: "my-app-static-files"
        region: "{{ region }}"
        policy:
          Version: "2012-10-17"
          Statement:
            - Sid: "PublicReadGetObject"
              Effect: "Allow"
              Principal: "*"
              Action: "s3:GetObject"
              Resource: "arn:aws:s3:::my-app-static-files/*"

Example 6: RDS Database Provisioning

---
- name: Create RDS MySQL Database
  hosts: localhost
  gather_facts: no

  tasks:
    - name: Create RDS subnet group
      amazon.aws.rds_subnet_group:
        name: "my-db-subnet-group"
        description: "Subnet group for RDS"
        region: "{{ region }}"
        subnets:
          - "{{ subnet1_id }}"
          - "{{ subnet2_id }}"

    - name: Create RDS instance
      amazon.aws.rds_instance:
        db_instance_identifier: "my-database"
        engine: mysql
        engine_version: "8.0"
        instance_type: "db.t3.micro"
        allocated_storage: 20
        master_username: "admin"
        master_user_password: "{{ db_password }}"
        db_subnet_group_name: "my-db-subnet-group"
        vpc_security_group_ids:
          - "{{ db_security_group_id }}"
        backup_retention_period: 7
        region: "{{ region }}"
      register: rds

    - name: Wait for RDS to be available
      amazon.aws.rds_instance_info:
        db_instance_identifier: "my-database"
        region: "{{ region }}"
      register: rds_info
      until: rds_info.instances[0].db_instance_status == "available"
      retries: 30
      delay: 60

Dynamic Inventory for AWS

Use AWS EC2 dynamic inventory to automatically discover instances:

aws_ec2.yml (Inventory Plugin)

plugin: amazon.aws.aws_ec2
regions:
  - us-east-1
  - us-west-2
filters:
  instance-state-name: running
keyed_groups:
  - key: tags.Environment
    prefix: env
  - key: tags.Role
    prefix: role
hostnames:
  - ip-address
compose:
  ansible_host: public_ip_address

Use it:

# List discovered hosts
ansible-inventory -i aws_ec2.yml --graph

# Run playbook against discovered hosts
ansible-playbook -i aws_ec2.yml site.yml

Best Practices for AWS Automation

  1. Use IAM Roles - Assign IAM roles to EC2 instances instead of embedding credentials
  2. Tag Everything - Use consistent tagging for resource management
  3. Use Variables - Store AMI IDs, regions, and configurations in variables
  4. Implement State Management - Save resource IDs for reuse (registered variables)
  5. Use Ansible Vault - Encrypt sensitive data like database passwords
  6. Test in Staging - Always test infrastructure changes in non-production first
  7. Use Check Mode - Run with --check before making changes

Complete Infrastructure Example

Combine everything into a complete stack:

---
- name: Deploy Complete AWS Infrastructure
  hosts: localhost
  gather_facts: no

  roles:
    - aws_vpc
    - aws_security_groups
    - aws_ec2_instances
    - aws_load_balancer
    - aws_rds
    - aws_s3

- name: Configure Launched Instances
  hosts: tag_Environment_Production
  become: yes
  roles:
    - common
    - nginx
    - app_deployment

Cost Warning

Remember that AWS resources incur costs! Always terminate resources when done testing: ansible-playbook teardown.yml

Conclusion

Ansible provides powerful AWS automation capabilities without needing to learn CloudFormation or Terraform. Start small, test thoroughly, and gradually build more complex infrastructures.