Ansible provides extensive support for managing Amazon Web Services (AWS) infrastructure through the amazon.aws collection. This guide covers common AWS operations and provides practical examples for automating your cloud infrastructure.
Install the AWS collection:
ansible-galaxy collection install amazon.aws
Ansible can authenticate with AWS using several methods:
export AWS_ACCESS_KEY_ID="your_access_key"
export AWS_SECRET_ACCESS_KEY="your_secret_key"
export AWS_REGION="us-east-1"
~/.aws/credentials)[default]
aws_access_key_id = your_access_key
aws_secret_access_key = your_secret_key
[production]
aws_access_key_id = your_prod_access_key
aws_secret_access_key = your_prod_secret_key
- name: Launch EC2 instance
hosts: localhost
gather_facts: false
vars:
aws_region: us-east-1
instance_type: t3.micro
ami_id: ami-0c55b159cbfafe1f0
key_name: my-keypair
security_group: default
vpc_subnet_id: subnet-12345678
tasks:
- name: Launch instance
amazon.aws.ec2_instance:
name: web-server-01
instance_type: "{{ instance_type }}"
image_id: "{{ ami_id }}"
key_name: "{{ key_name }}"
security_group: "{{ security_group }}"
vpc_subnet_id: "{{ vpc_subnet_id }}"
region: "{{ aws_region }}"
wait: true
network:
assign_public_ip: true
tags:
Environment: Production
Application: WebServer
register: ec2_instance
- name: Display instance information
ansible.builtin.debug:
var: ec2_instance.instances[0].public_ip_address
- name: Launch multiple EC2 instances
hosts: localhost
gather_facts: false
vars:
aws_region: us-west-2
instance_count: 3
tasks:
- name: Launch instances
amazon.aws.ec2_instance:
name: "app-server-{{ item }}"
instance_type: t3.medium
image_id: ami-0c55b159cbfafe1f0
key_name: my-keypair
region: "{{ aws_region }}"
count: 1
wait: true
network:
assign_public_ip: true
tags:
Application: AppServer
Environment: Development
loop: "{{ range(1, instance_count + 1) | list }}"
register: ec2_instances
- name: Add instances to inventory
ansible.builtin.add_host:
name: "{{ item.public_ip_address }}"
groups: app_servers
ansible_user: ec2-user
ansible_ssh_private_key_file: ~/.ssh/my-keypair.pem
loop: "{{ ec2_instances.results | map(attribute='instances') | flatten | list }}"
- name: Manage EC2 instance state
hosts: localhost
gather_facts: false
vars:
instance_id: i-0123456789abcdef0
tasks:
- name: Stop instance
amazon.aws.ec2_instance:
instance_id: "{{ instance_id }}"
region: us-east-1
state: stopped
delegate_to: localhost
- name: Start instance
amazon.aws.ec2_instance:
instance_id: "{{ instance_id }}"
region: us-east-1
state: running
delegate_to: localhost
- name: Terminate EC2 instances
hosts: localhost
gather_facts: false
tasks:
- name: Terminate instance by ID
amazon.aws.ec2_instance:
instance_id: i-0123456789abcdef0
region: us-east-1
state: absent
terminate: true
- name: Create S3 bucket
hosts: localhost
gather_facts: false
tasks:
- name: Create bucket
amazon.aws.s3_bucket:
name: my-unique-bucket-name-12345
region: us-east-1
state: present
versioning: true
tags:
Environment: Production
Project: DataLake
- name: Upload files to S3
hosts: localhost
gather_facts: false
tasks:
- name: Upload single file
amazon.aws.s3_object:
bucket: my-bucket-name
object: backups/database-{{ ansible_date_time.date }}.sql
src: /path/to/backup.sql
mode: put
region: us-east-1
permission: private
- name: Upload with encryption
amazon.aws.s3_object:
bucket: my-secure-bucket
object: secrets/config.json
src: /path/to/config.json
mode: put
region: us-east-1
encrypt: true
metadata:
encrypted: "true"
- name: Download files from S3
hosts: all
gather_facts: false
tasks:
- name: Download configuration file
amazon.aws.s3_object:
bucket: my-bucket-name
object: configs/app-config.json
dest: /etc/myapp/config.json
mode: get
region: us-east-1
- name: Sync directory with S3
hosts: localhost
gather_facts: false
tasks:
- name: Upload website to S3
amazon.aws.s3_sync:
bucket: my-website-bucket
root: /var/www/html
region: us-east-1
delete: true
file_change_detection: checksum
file_filter:
- '*.html'
- '*.css'
- '*.js'
- 'images/*'
- name: Create RDS PostgreSQL instance
hosts: localhost
gather_facts: false
vars:
db_identifier: my-postgres-db
db_engine: postgres
db_engine_version: "15.4"
db_instance_class: db.t3.medium
db_allocated_storage: 100
db_master_username: admin
db_master_password: "{{ vault_db_password }}"
tasks:
- name: Create RDS instance
amazon.aws.rds_instance:
id: "{{ db_identifier }}"
engine: "{{ db_engine }}"
engine_version: "{{ db_engine_version }}"
db_instance_class: "{{ db_instance_class }}"
allocated_storage: "{{ db_allocated_storage }}"
master_username: "{{ db_master_username }}"
master_user_password: "{{ db_master_password }}"
region: us-east-1
vpc_security_group_ids:
- sg-12345678
db_subnet_group_name: my-db-subnet-group
publicly_accessible: false
storage_encrypted: true
backup_retention_period: 7
wait: true
tags:
Environment: Production
Database: PostgreSQL
- name: Create RDS snapshot
hosts: localhost
gather_facts: false
tasks:
- name: Create manual snapshot
amazon.aws.rds_instance_snapshot:
db_instance_identifier: my-postgres-db
db_snapshot_identifier: my-postgres-db-snapshot-{{ ansible_date_time.date }}
region: us-east-1
wait: true
- name: Restore RDS from snapshot
hosts: localhost
gather_facts: false
tasks:
- name: Restore database
amazon.aws.rds_instance:
id: my-restored-db
creation_source: snapshot
snapshot_identifier: my-postgres-db-snapshot-2024-01-15
region: us-east-1
wait: true
- name: Create VPC infrastructure
hosts: localhost
gather_facts: false
vars:
vpc_cidr: 10.0.0.0/16
region: us-east-1
tasks:
- name: Create VPC
amazon.aws.ec2_vpc_net:
name: production-vpc
cidr_block: "{{ vpc_cidr }}"
region: "{{ region }}"
tenancy: default
register: vpc_result
- name: Create public subnets
amazon.aws.ec2_vpc_subnet:
vpc_id: "{{ vpc_result.vpc.id }}"
cidr: "{{ item.cidr }}"
az: "{{ item.az }}"
region: "{{ region }}"
map_public: true
tags:
Name: "{{ item.name }}"
Type: Public
loop:
- { name: "public-subnet-1a", az: "us-east-1a", cidr: "10.0.1.0/24" }
- { name: "public-subnet-1b", az: "us-east-1b", cidr: "10.0.2.0/24" }
- name: Create private subnets
amazon.aws.ec2_vpc_subnet:
vpc_id: "{{ vpc_result.vpc.id }}"
cidr: "{{ item.cidr }}"
az: "{{ item.az }}"
region: "{{ region }}"
map_public: false
tags:
Name: "{{ item.name }}"
Type: Private
loop:
- { name: "private-subnet-1a", az: "us-east-1a", cidr: "10.0.10.0/24" }
- { name: "private-subnet-1b", az: "us-east-1b", cidr: "10.0.11.0/24" }
- name: Create Internet Gateway
hosts: localhost
gather_facts: false
tasks:
- name: Create IGW
amazon.aws.ec2_vpc_igw:
vpc_id: vpc-12345678
region: us-east-1
state: present
register: igw_result
- name: Create route table with IGW
amazon.aws.ec2_vpc_route_table:
vpc_id: vpc-12345678
region: us-east-1
tags:
Name: public-routes
subnets:
- subnet-11111111
- subnet-22222222
routes:
- dest: 0.0.0.0/0
gateway_id: "{{ igw_result.gateway_id }}"
- name: Create security groups
hosts: localhost
gather_facts: false
tasks:
- name: Create web security group
amazon.aws.ec2_security_group:
name: web-sg
description: Security group for web servers
vpc_id: vpc-12345678
region: us-east-1
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: 10.0.0.0/8
rule_desc: Allow SSH from internal
rules_egress:
- proto: -1
cidr_ip: 0.0.0.0/0
rule_desc: Allow all outbound
- name: Create IAM user
hosts: localhost
gather_facts: false
tasks:
- name: Create user
amazon.aws.iam_user:
name: deploy-user
state: present
tags:
Purpose: Deployment
Environment: Production
- name: Create access keys
amazon.aws.iam_access_key:
name: deploy-user
state: present
register: access_key
- name: Store access keys securely
ansible.builtin.debug:
msg: "Access Key ID: {{ access_key.access_key_id }}"
- name: Attach IAM policies
hosts: localhost
gather_facts: false
tasks:
- name: Attach managed policy
amazon.aws.iam_user_info:
name: deploy-user
register: user_info
- name: Attach S3 read-only policy
amazon.aws.iam_policy:
iam_type: user
iam_name: deploy-user
policy_name: S3ReadOnly
policy_json: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": "*"
}
]
}
- name: Create IAM role for EC2
hosts: localhost
gather_facts: false
tasks:
- name: Create role
amazon.aws.iam_role:
name: ec2-s3-access-role
assume_role_policy_document: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
managed_policies:
- arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
create_instance_profile: true
- name: Create Auto Scaling Group
hosts: localhost
gather_facts: false
tasks:
- name: Create launch template
amazon.aws.ec2_launch_template:
template_name: web-server-template
template_description: Launch template for web servers
region: us-east-1
instance_type: t3.medium
image_id: ami-0c55b159cbfafe1f0
key_name: my-keypair
security_groups:
- web-sg
user_data: |
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
- name: Create Auto Scaling Group
amazon.aws.autoscaling_group:
name: web-asg
launch_template:
launch_template_name: web-server-template
min_size: 2
max_size: 10
desired_capacity: 3
vpc_zone_identifier:
- subnet-11111111
- subnet-22222222
region: us-east-1
health_check_period: 300
health_check_type: ELB
replace_asg_on_instance_config_change: true
- name: Configure scaling policies
hosts: localhost
gather_facts: false
tasks:
- name: Create scale-up policy
amazon.aws.autoscaling_policy:
name: scale-up-policy
asg_name: web-asg
adjustment_type: ChangeInCapacity
scaling_adjustment: 2
cooldown: 300
region: us-east-1
- name: Create scale-down policy
amazon.aws.autoscaling_policy:
name: scale-down-policy
asg_name: web-asg
adjustment_type: ChangeInCapacity
scaling_adjustment: -1
cooldown: 300
region: us-east-1
- name: Deploy Lambda function
hosts: localhost
gather_facts: false
tasks:
- name: Create Lambda function
amazon.aws.lambda_function:
name: my-lambda-function
state: present
runtime: python3.9
role: arn:aws:iam::123456789012:role/lambda-execution-role
handler: index.handler
zip_file: function.zip
region: us-east-1
timeout: 30
memory_size: 256
environment_variables:
LOG_LEVEL: INFO
DB_HOST: mydb.rds.amazonaws.com
tags:
Application: MyApplication
Environment: Production
- name: Create Lambda trigger
hosts: localhost
gather_facts: false
tasks:
- name: Create S3 trigger
amazon.aws.lambda_event:
state: present
function_name: my-lambda-function
region: us-east-1
event_source: s3
source_params:
bucket_name: my-trigger-bucket
events:
- s3:ObjectCreated:*
- name: Create CloudWatch alarms
hosts: localhost
gather_facts: false
tasks:
- name: Create CPU alarm
amazon.aws.cloudwatch_metric_alarm:
name: high-cpu-alarm
state: present
region: us-east-1
metric_name: CPUUtilization
namespace: AWS/EC2
statistic: Average
comparison: GreaterThanThreshold
threshold: 80
period: 300
evaluation_periods: 2
dimensions:
InstanceId: i-0123456789abcdef0
alarm_actions:
- arn:aws:sns:us-east-1:123456789012:alerts-topic
treat_missing_data: notBreaching
- name: Create CloudWatch dashboard
hosts: localhost
gather_facts: false
tasks:
- name: Create dashboard
amazon.aws.cloudwatch_dashboard:
dashboard_name: infrastructure-dashboard
dashboard_body: |
{
"widgets": [
{
"type": "metric",
"x": 0,
"y": 0,
"width": 12,
"height": 6,
"properties": {
"metrics": [
["AWS/EC2", "CPUUtilization", "InstanceId", "i-0123456789abcdef0"]
],
"period": 300,
"stat": "Average",
"region": "us-east-1",
"title": "EC2 CPU Usage"
}
}
]
}
state: present
region: us-east-1
This example provisions a complete web application infrastructure:
- name: Deploy complete AWS infrastructure
hosts: localhost
gather_facts: false
vars:
region: us-east-1
environment: production
app_name: mywebapp
vpc_cidr: 10.0.0.0/16
tasks:
# VPC Setup
- name: Create VPC
amazon.aws.ec2_vpc_net:
name: "{{ app_name }}-vpc"
cidr_block: "{{ vpc_cidr }}"
region: "{{ region }}"
tags:
Environment: "{{ environment }}"
register: vpc
# Internet Gateway
- name: Create Internet Gateway
amazon.aws.ec2_vpc_igw:
vpc_id: "{{ vpc.vpc.id }}"
region: "{{ region }}"
register: igw
# Public Subnet
- name: Create public subnet
amazon.aws.ec2_vpc_subnet:
vpc_id: "{{ vpc.vpc.id }}"
cidr: 10.0.1.0/24
az: "{{ region }}a"
region: "{{ region }}"
map_public: true
register: public_subnet
# Security Group
- name: Create security group
amazon.aws.ec2_security_group:
name: "{{ app_name }}-sg"
description: Security group for {{ app_name }}
vpc_id: "{{ vpc.vpc.id }}"
region: "{{ region }}"
rules:
- proto: tcp
ports:
- 80
cidr_ip: 0.0.0.0/0
- proto: tcp
ports:
- 443
cidr_ip: 0.0.0.0/0
- proto: tcp
ports:
- 22
cidr_ip: 10.0.0.0/8
rules_egress:
- proto: -1
cidr_ip: 0.0.0.0/0
register: security_group
# EC2 Instances
- name: Launch EC2 instances
amazon.aws.ec2_instance:
name: "{{ app_name }}-web-{{ item }}"
instance_type: t3.medium
image_id: ami-0c55b159cbfafe1f0
key_name: my-keypair
region: "{{ region }}"
vpc_subnet_id: "{{ public_subnet.subnet.id }}"
security_group: "{{ security_group.group_id }}"
wait: true
tags:
Application: "{{ app_name }}"
Environment: "{{ environment }}"
Role: WebServer
loop: "{{ range(1, 3) | list }}"
register: ec2_instances
# Output
- name: Display instance IPs
ansible.builtin.debug:
msg: "Instance {{ item.name }}: {{ item.public_ip_address }}"
loop: "{{ ec2_instances.results | map(attribute='instances') | flatten | list }}"
tags:
Name: "{{ app_name }}-{{ role }}-{{ env }}"
Environment: "{{ env }}"
Application: "{{ app_name }}"
ManagedBy: Ansible
CostCenter: "12345"
Always use wait: true for resources that need to be ready before dependent resources:
- name: Create RDS instance
amazon.aws.rds_instance:
# ... parameters ...
wait: true
wait_timeout: 900
Ansible AWS modules are idempotent. Use state: present or state: absent:
- name: Ensure S3 bucket exists
amazon.aws.s3_bucket:
name: my-bucket
state: present
- name: Ensure old bucket is removed
amazon.aws.s3_bucket:
name: old-bucket
state: absent
Never hardcode credentials in playbooks. Use:
- name: Create EC2 with retry
amazon.aws.ec2_instance:
# ... parameters ...
register: ec2_result
retries: 3
delay: 10
until: ec2_result.instances[0].state.name == 'running'
Any questions?
Feel free to contact us. Find all contact information on our contact page.