Ansible AWS Cloud Management

Ansible's cloud automation capabilities extend well beyond configuring servers — it can provision the servers themselves, create the network infrastructure they run on, manage storage, configure security groups, and orchestrate complex cloud architectures. This lesson introduces AWS automation with Ansible, covering the modules that practitioners use most frequently and the patterns that keep cloud automation maintainable.

Prerequisites: AWS Collection and Credentials

# Install the AWS collection
ansible-galaxy collection install amazon.aws

# Install Python dependencies
pip3 install boto3 botocore

Configure AWS credentials using any of these methods (in order of preference for automation):

# Environment variables (good for CI/CD)
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
export AWS_DEFAULT_REGION=us-east-1

# AWS credentials file (~/.aws/credentials)
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

For production automation, use IAM roles attached to the control node EC2 instance rather than long-lived access keys. This eliminates credential management overhead and follows the principle of least privilege.

Provisioning EC2 Instances

---
- name: Provision AWS infrastructure
  hosts: localhost
  connection: local
  gather_facts: false

  vars:
    region: us-east-1
    instance_type: t3.micro
    ami_id: ami-0c02fb55956c7d316   # Ubuntu 22.04 LTS us-east-1
    key_name: my-key-pair
    instance_count: 3

  tasks:
    - name: Create EC2 instances
      amazon.aws.ec2_instance:
        name: "webserver-{{ item }}"
        key_name: "{{ key_name }}"
        instance_type: "{{ instance_type }}"
        image_id: "{{ ami_id }}"
        region: "{{ region }}"
        vpc_subnet_id: subnet-12345678
        security_groups:
          - web-sg
        state: running
        wait: true
        tags:
          Environment: production
          Role: webserver
          ManagedBy: Ansible
      loop: "{{ range(1, instance_count + 1) | list }}"
      register: ec2_instances

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

Managing Security Groups

- name: Create web server security group
  amazon.aws.ec2_security_group:
    name: web-sg
    description: Security group for web servers
    region: "{{ region }}"
    vpc_id: vpc-12345678
    rules:
      - proto: tcp
        ports: [80, 443]
        cidr_ip: 0.0.0.0/0
        rule_desc: Allow HTTP and HTTPS from anywhere
      - proto: tcp
        ports: [22]
        cidr_ip: 10.0.0.0/8
        rule_desc: Allow SSH from internal network only
    rules_egress:
      - proto: all
        cidr_ip: 0.0.0.0/0
  register: web_sg

Creating VPC Infrastructure

- name: Create VPC
  amazon.aws.ec2_vpc_net:
    name: production-vpc
    cidr_block: 10.0.0.0/16
    region: "{{ region }}"
    tags:
      Environment: production
  register: vpc

- name: Create public subnet
  amazon.aws.ec2_vpc_subnet:
    vpc_id: "{{ vpc.vpc.id }}"
    cidr: 10.0.1.0/24
    az: "{{ region }}a"
    map_public: true
    tags:
      Name: public-subnet-a
  register: public_subnet

Managing S3 Buckets

- name: Create S3 bucket for application assets
  amazon.aws.s3_bucket:
    name: myapp-assets-{{ env }}
    state: present
    region: "{{ region }}"
    versioning: true
    encryption: AES256
    tags:
      Environment: "{{ env }}"

- name: Upload static assets
  amazon.aws.s3_object:
    bucket: myapp-assets-{{ env }}
    object: "assets/{{ item | basename }}"
    src: "{{ item }}"
    mode: put
  loop: "{{ lookup('fileglob', 'files/assets/*', wantlist=True) }}"

Managing RDS Databases

- name: Create RDS MySQL instance
  amazon.aws.rds_instance:
    id: myapp-production-db
    state: present
    engine: mysql
    engine_version: "8.0"
    instance_type: db.t3.micro
    master_username: admin
    master_user_password: "{{ vault_rds_password }}"
    db_name: myapp
    allocated_storage: 20
    storage_type: gp3
    multi_az: true
    backup_retention_period: 7
    region: "{{ region }}"
    tags:
      Environment: production
  register: rds_instance
  no_log: true

Infrastructure State Management

Cloud provisioning playbooks need to handle the full lifecycle: create, update, and destroy. Ansible's cloud modules are idempotent — running the EC2 provisioning playbook on already-existing instances makes no changes. For destruction:

- name: Terminate EC2 instances (use with extreme caution)
  amazon.aws.ec2_instance:
    instance_ids: "{{ item }}"
    state: absent
    region: "{{ region }}"
  loop: "{{ instance_ids_to_terminate }}"
  when: teardown_environment | default(false) | bool

The when: teardown_environment guard prevents accidental destruction — the teardown only runs when explicitly passed as a variable: -e teardown_environment=true.

Combining Provisioning and Configuration

The most powerful pattern is a single playbook that provisions infrastructure and then immediately configures it:

---
# Play 1: Provision infrastructure (runs on localhost)
- name: Provision AWS infrastructure
  hosts: localhost
  connection: local
  tasks:
    - name: Create EC2 instances
      amazon.aws.ec2_instance: ...
      register: new_instances

    - name: Add new instances to inventory for next play
      add_host:
        name: "{{ item.public_ip_address }}"
        groups: newly_provisioned
        ansible_user: ubuntu
        ansible_ssh_private_key_file: ~/.ssh/my-key.pem
      loop: "{{ new_instances.instances }}"

    - name: Wait for SSH to become available
      wait_for:
        host: "{{ item.public_ip_address }}"
        port: 22
        timeout: 120
      loop: "{{ new_instances.instances }}"

# Play 2: Configure the new instances
- name: Configure newly provisioned servers
  hosts: newly_provisioned
  become: true
  roles:
    - common
    - nginx
    - myapp

Try This: Provision and Configure a Server

Using your AWS Free Tier account, write a playbook that provisions one t2.micro Ubuntu EC2 instance with a security group allowing SSH and HTTP, waits for it to be reachable, and then configures it with your Nginx role from Topic 22. Run the full playbook and verify Nginx is serving a page. Then add a state: absent task and re-run to terminate the instance. This end-to-end exercise demonstrates the complete infrastructure lifecycle management capability that employers look for in DevOps candidates.

Summary

Ansible's AWS modules (from the amazon.aws collection) enable full infrastructure lifecycle management: provisioning EC2 instances, creating VPCs and security groups, managing S3 buckets, and provisioning RDS databases. All cloud modules are idempotent. Destruction operations should be guarded by explicit boolean conditions. The add_host module bridges provisioning and configuration plays, enabling complete infrastructure-as-code workflows in a single playbook run.

Leave a Comment