Skip to content

Ansible


Ansible is an open-source automation platform that simplifies configuration management, application deployment, and task automation on AWS infrastructure.

Ansible Overview
+------------------------------------------------------------------+
| |
| +------------------------+ |
| | Ansible | |
| +------------------------+ |
| | |
| +---------------------+---------------------+ |
| | | | | |
| v v v v |
| +----------+ +----------+ +----------+ +----------+ |
| | Agentless| | Playbooks| | Modules | | Inventory| |
| | | | | | | | | |
| | - SSH | | - YAML | | - AWS | | - Static | |
| | - WinRM | | - Tasks | | - Cloud | | - Dynamic| |
| | - Python | | - Roles | | - System | | - Groups | |
| +----------+ +----------+ +----------+ +----------+ |
| |
+------------------------------------------------------------------+
FeatureDescription
AgentlessNo software installation required on targets
PlaybooksYAML-based automation scripts
ModulesPre-built automation units
InventoryDynamic or static host management

Ansible Architecture
+------------------------------------------------------------------+
| |
| Control Node |
| +----------------------------------------------------------+ |
| | | |
| | +----------+ +----------+ +----------+ +----------+ | |
| | | Ansible | | Playbooks| | Inventory| | Modules | | |
| | | Core | | | | | | | | |
| | +----------+ +----------+ +----------+ +----------+ | |
| | | |
| +--------------------------+-------------------------------+ |
| | |
| +----------------+----------------+ |
| | | | |
| v v v |
| +----------+ +----------+ +----------+ |
| | Managed | | Managed | | Managed | |
| | Node 1 | | Node 2 | | Node N | |
| | (EC2) | | (EC2) | | (EC2) | |
| +----------+ +----------+ +----------+ |
| | | | |
| v v v |
| SSH/WinRM SSH/WinRM SSH/WinRM |
| |
+------------------------------------------------------------------+
Ansible AWS Integration
+------------------------------------------------------------------+
| |
| +------------------+ +------------------+ +------------------+ |
| | EC2 Instances | | AWS Services | | Infrastructure | |
| | | | | | | |
| | - Configuration | | - S3 Buckets | | - VPC Creation | |
| | - Deployment | | - RDS Databases | | - Security Groups| |
| | - Orchestration | | - Lambda | | - IAM Roles | |
| +------------------+ +------------------+ +------------------+ |
| |
| +------------------+ +------------------+ +------------------+ |
| | Dynamic Inventory| | Cloud Modules | | Automation | |
| | | | | | | |
| | - AWS EC2 | | - ec2_instance | | - Scaling | |
| | - AWS RDS | | - s3_bucket | | - Updates | |
| | - AWS Route53 | | - rds_instance | | - Compliance | |
| +------------------+ +------------------+ +------------------+ |
| |
+------------------------------------------------------------------+

Terminal window
# Ubuntu/Debian
sudo apt update
sudo apt install ansible
# RHEL/CentOS
sudo yum install epel-release
sudo yum install ansible
# Using pip (recommended)
pip install ansible
# Install AWS collection
ansible-galaxy collection install amazon.aws
# Install additional dependencies
pip install boto3 botocore
# ansible.cfg
[defaults]
inventory = ./inventory/aws_ec2.yml
roles_path = ./roles
collections_path = ./collections
remote_user = ec2-user
private_key_file = ~/.ssh/aws-key.pem
host_key_checking = False
stdout_callback = yaml
retry_files_enabled = False
log_path = ./logs/ansible.log
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
[ssh_connection]
ssh_args = -o ForwardAgent=yes -o StrictHostKeyChecking=no
pipelining = True
Terminal window
# Environment variables
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
export AWS_REGION=us-east-1
# AWS CLI configuration
aws configure
# IAM role (recommended for EC2)
# Attach role with appropriate policies to EC2 instance

inventory/aws_ec2.yml
plugin: aws_ec2
regions:
- us-east-1
- us-west-2
# Group by tags
keyed_groups:
- key: tags.Environment
prefix: env
- key: tags.Application
prefix: app
- key: tags.Role
prefix: role
# Group by instance type
groups:
web_servers: "'web' in tags.Role"
db_servers: "'database' in tags.Role"
production: "tags.Environment == 'production'"
# Filters
filters:
instance-state-name: running
# Compose variables
compose:
ansible_host: public_ip_address
private_ip: private_ip_address
instance_id: instance_id
# Hostname
hostnames:
- tag:Name
- private-ip-address
# Exclude instances
exclude_filters:
- tag:ExcludeFromAnsible: "true"
Terminal window
# List inventory
ansible-inventory -i inventory/aws_ec2.yml --list
# Output:
{
"_meta": {
"hostvars": {
"i-1234567890abcdef0": {
"ansible_host": "54.123.45.67",
"private_ip": "10.0.1.10",
"instance_id": "i-1234567890abcdef0",
"tags": {
"Environment": "production",
"Role": "web",
"Name": "web-server-01"
}
}
}
},
"env_production": ["i-1234567890abcdef0"],
"role_web": ["i-1234567890abcdef0"],
"web_servers": ["i-1234567890abcdef0"]
}
inventory/hosts
[web_servers]
web-01 ansible_host=54.123.45.67
web-02 ansible_host=54.123.45.68
[db_servers]
db-01 ansible_host=10.0.2.10
db-02 ansible_host=10.0.2.11
[production:children]
web_servers
db_servers
[web_servers:vars]
ansible_user=ec2-user
ansible_ssh_private_key_file=~/.ssh/prod-key.pem
[db_servers:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/prod-key.pem

site.yml
---
- name: Configure web servers
hosts: web_servers
become: yes
vars:
nginx_version: "1.18.0"
app_port: 8080
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Install Nginx
package:
name: nginx
state: present
- name: Configure Nginx
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
notify: Restart Nginx
- name: Start Nginx
service:
name: nginx
state: started
enabled: yes
handlers:
- name: Restart Nginx
service:
name: nginx
state: restarted
multi-play.yml
---
- name: Configure VPC
hosts: localhost
connection: local
gather_facts: no
tasks:
- name: Create VPC
amazon.aws.ec2_vpc:
name: "{{ vpc_name }}"
cidr: "{{ vpc_cidr }}"
region: "{{ aws_region }}"
state: present
register: vpc_result
- name: Provision EC2 instances
hosts: localhost
connection: local
gather_facts: no
tasks:
- name: Launch EC2 instances
amazon.aws.ec2_instance:
name: "{{ item.name }}"
instance_type: "{{ item.type }}"
image_id: "{{ ami_id }}"
region: "{{ aws_region }}"
vpc_subnet_id: "{{ subnet_id }}"
security_group: "{{ security_group }}"
key_name: "{{ key_name }}"
count: "{{ item.count }}"
state: present
loop:
- { name: 'web-server', type: 't3.medium', count: 2 }
- { name: 'app-server', type: 't3.large', count: 2 }
register: ec2_result
- name: Configure instances
hosts: tag_Role_web
become: yes
roles:
- common
- nginx
- application

# Launch EC2 instance
- name: Launch EC2 instance
amazon.aws.ec2_instance:
name: "web-server-{{ inventory_hostname }}"
instance_type: t3.medium
image_id: ami-0abcdef1234567890
region: us-east-1
vpc_subnet_id: subnet-12345678
security_groups:
- sg-12345678
key_name: my-keypair
tags:
Environment: production
Role: web
volumes:
- device_name: /dev/sda1
ebs:
volume_size: 20
volume_type: gp3
encrypted: yes
user_data: |
#!/bin/bash
echo "User data script" > /tmp/userdata.log
state: present
register: ec2_instance
# Terminate instance
- name: Terminate instance
amazon.aws.ec2_instance:
instance_ids: ["i-1234567890abcdef0"]
state: absent
# Start/Stop instance
- name: Stop instance
amazon.aws.ec2_instance:
instance_ids: ["i-1234567890abcdef0"]
state: stopped
- name: Start instance
amazon.aws.ec2_instance:
instance_ids: ["i-1234567890abcdef0"]
state: started
# Create S3 bucket
- name: Create S3 bucket
amazon.aws.s3_bucket:
name: "{{ bucket_name }}"
region: us-east-1
state: present
versioning: yes
encryption: aws:kms
kms_master_key_id: "{{ kms_key_id }}"
public_access:
block_public_acls: true
block_public_policy: true
ignore_public_acls: true
restrict_public_buckets: true
tags:
Environment: production
Project: web-app
# Upload file to S3
- name: Upload file to S3
amazon.aws.s3_object:
bucket: "{{ bucket_name }}"
object: "/config/app.conf"
src: "./files/app.conf"
mode: upload
encryption: aws:kms
# Download file from S3
- name: Download file from S3
amazon.aws.s3_object:
bucket: "{{ bucket_name }}"
object: "/config/app.conf"
dest: "/tmp/app.conf"
mode: get
# Create RDS instance
- name: Create RDS instance
community.aws.rds_instance:
db_instance_identifier: "{{ db_identifier }}"
db_instance_class: db.t3.medium
engine: postgres
engine_version: "14.7"
allocated_storage: 100
storage_type: gp3
master_username: "{{ db_user }}"
master_user_password: "{{ db_password }}"
vpc_security_group_ids:
- "{{ security_group_id }}"
db_subnet_group_name: "{{ subnet_group }}"
multi_az: yes
backup_retention_period: 7
backup_window: "03:00-04:00"
maintenance_window: "sun:04:00-sun:05:00"
storage_encrypted: yes
kms_key_id: "{{ kms_key_id }}"
state: present
register: rds_instance
# Create RDS snapshot
- name: Create RDS snapshot
community.aws.rds_snapshot:
db_instance_identifier: "{{ db_identifier }}"
db_snapshot_identifier: "{{ snapshot_name }}"
state: present
# Delete RDS instance
- name: Delete RDS instance
community.aws.rds_instance:
db_instance_identifier: "{{ db_identifier }}"
state: absent
skip_final_snapshot: no
final_snapshot_identifier: "{{ final_snapshot_name }}"
# Create VPC
- name: Create VPC
amazon.aws.ec2_vpc_net:
name: "{{ vpc_name }}"
cidr_block: "{{ vpc_cidr }}"
region: "{{ aws_region }}"
dns_hostnames: yes
dns_support: yes
state: present
tags:
Environment: production
register: vpc
# Create subnet
- name: Create subnet
amazon.aws.ec2_vpc_subnet:
vpc_id: "{{ vpc.vpc.id }}"
cidr: "{{ subnet_cidr }}"
az: "{{ availability_zone }}"
region: "{{ aws_region }}"
state: present
tags:
Name: "{{ subnet_name }}"
register: subnet
# Create security group
- name: Create security group
amazon.aws.ec2_security_group:
name: "{{ sg_name }}"
description: "{{ sg_description }}"
vpc_id: "{{ vpc.vpc.id }}"
region: "{{ aws_region }}"
rules:
- proto: tcp
from_port: 22
to_port: 22
cidr_ip: 0.0.0.0/0
- proto: tcp
from_port: 443
to_port: 443
cidr_ip: 0.0.0.0/0
state: present
register: security_group
# Create internet gateway
- name: Create internet gateway
amazon.aws.ec2_vpc_igw:
vpc_id: "{{ vpc.vpc.id }}"
region: "{{ aws_region }}"
state: present
tags:
Name: "{{ igw_name }}"
register: igw
# Create IAM role
- name: Create IAM role
amazon.aws.iam_role:
name: "{{ role_name }}"
assume_role_policy_document: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
state: present
register: iam_role
# Attach policy to role
- name: Attach policy to role
amazon.aws.iam_role_policy_attachment:
role_name: "{{ role_name }}"
policy_arn: arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
state: present
# Create IAM user
- name: Create IAM user
amazon.aws.iam_user:
name: "{{ user_name }}"
state: present
tags:
Environment: production
# Create access key
- name: Create access key
amazon.aws.iam_access_key:
user_name: "{{ user_name }}"
state: present
register: access_key

roles/
+------------------------------------------------------------------+
| |
| web-server/ |
| +-- tasks/ |
| | +-- main.yml # Main task list |
| | +-- install.yml # Install tasks |
| | +-- configure.yml # Configuration tasks |
| | |
| +-- handlers/ |
| | +-- main.yml # Handlers |
| | |
| +-- templates/ |
| | +-- nginx.conf.j2 # Nginx config template |
| | +-- app.conf.j2 # App config template |
| | |
| +-- files/ |
| | +-- index.html # Static files |
| | |
| +-- vars/ |
| | +-- main.yml # Role variables |
| | +-- debian.yml # OS-specific variables |
| | |
| +-- defaults/ |
| | +-- main.yml # Default variables |
| | |
| +-- meta/ |
| | +-- main.yml # Role dependencies |
| | |
| +-- tests/ |
| | +-- test.yml # Test playbook |
| | +-- inventory # Test inventory |
| | |
| +-- README.md # Role documentation |
| |
+------------------------------------------------------------------+
roles/web-server/tasks/main.yml
---
- name: Include install tasks
include_tasks: install.yml
tags: install
- name: Include configure tasks
include_tasks: configure.yml
tags: configure
# roles/web-server/tasks/install.yml
---
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Install Nginx
package:
name: nginx
state: present
- name: Install dependencies
package:
name:
- curl
- wget
- jq
state: present
# roles/web-server/tasks/configure.yml
---
- name: Create directories
file:
path: "{{ item }}"
state: directory
owner: www-data
group: www-data
mode: '0755'
loop:
- /var/www/html
- /var/log/nginx
- name: Configure Nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
notify: Restart Nginx
- name: Start Nginx
service:
name: nginx
state: started
enabled: yes
roles/web-server/handlers/main.yml
---
- name: Restart Nginx
service:
name: nginx
state: restarted
- name: Reload Nginx
service:
name: nginx
state: reloaded
- name: Validate Nginx config
command: nginx -t
changed_when: false
roles/web-server/defaults/main.yml
---
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
nginx_listen_port: 80
nginx_server_name: "_"
nginx_root: /var/www/html
nginx_index_files:
- index.html
- index.htm
roles/web-server/meta/main.yml
---
galaxy_info:
author: DevOps Team
description: Web server configuration role
company: Company Name
license: MIT
min_ansible_version: 2.12
platforms:
- name: Ubuntu
versions:
- jammy
- focal
- name: EL
versions:
- 8
- 9
galaxy_tags:
- web
- nginx
- server
dependencies:
- role: common
vars:
common_packages:
- curl
- wget

{# roles/web-server/templates/nginx.conf.j2 #}
# Nginx configuration managed by Ansible
# Do not edit manually
worker_processes {{ nginx_worker_processes }};
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
events {
worker_connections {{ nginx_worker_connections }};
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout {{ nginx_keepalive_timeout }};
gzip on;
{% for upstream in nginx_upstreams %}
upstream {{ upstream.name }} {
{% for server in upstream.servers %}
server {{ server.host }}:{{ server.port }};
{% endfor %}
}
{% endfor %}
server {
listen {{ nginx_listen_port }};
server_name {{ nginx_server_name }};
root {{ nginx_root }};
index {% for file in nginx_index_files %}{{ file }} {% endfor %};
location / {
try_files $uri $uri/ =404;
}
{% if nginx_ssl_enabled %}
location ~ /\.ht {
deny all;
}
{% endif %}
{% for location in nginx_locations %}
location {{ location.path }} {
{{ location.config }}
}
{% endfor %}
}
}
{# roles/app/templates/app.conf.j2 #}
[database]
host = {{ db_host }}
port = {{ db_port }}
name = {{ db_name }}
user = {{ db_user }}
password = {{ db_password | b64decode }}
[application]
environment = {{ app_environment }}
debug = {{ app_debug | default('false') }}
secret_key = {{ app_secret_key }}
log_level = {{ app_log_level | default('INFO') }}
{% if app_features is defined %}
[features]
{% for feature in app_features %}
{{ feature.name }} = {{ feature.enabled }}
{% endfor %}
{% endif %}
[servers]
{% for server in groups['app_servers'] %}
{{ hostvars[server]['inventory_hostname'] }} = {{ hostvars[server]['ansible_host'] }}
{% endfor %}

Variable Precedence (Low to High)
+------------------------------------------------------------------+
| |
| 1. command line values (ansible-playbook -e "var=value") |
| 2. role defaults (roles/x/defaults/main.yml) |
| 3. inventory file or script group vars |
| 4. inventory group_vars/all |
| 5. playbook group_vars/all |
| 6. inventory group_vars/* |
| 7. playbook group_vars/* |
| 8. inventory file or script host vars |
| 9. inventory host_vars/* |
| 10. playbook host_vars/* |
| 11. host facts / cached set_facts |
| 12. play vars |
| 13. play vars_prompt |
| 14. play vars_files |
| 15. role vars (roles/x/vars/main.yml) |
| 16. block vars |
| 17. task vars (only for tasks) |
| 18. include_vars |
| 19. set_facts / registered vars |
| 20. role (and include_role) params |
| 21. include params |
| 22. extra vars (ansible-playbook -e) |
| |
+------------------------------------------------------------------+
group_vars/all.yml
---
aws_region: us-east-1
environment: production
timezone: UTC
# group_vars/web_servers.yml
---
nginx_listen_port: 80
nginx_worker_processes: auto
nginx_ssl_enabled: true
# group_vars/db_servers.yml
---
db_engine: postgres
db_version: "14.7"
db_port: 5432
host_vars/web-01.yml
---
ansible_host: 54.123.45.67
private_ip: 10.0.1.10
nginx_listen_port: 8080
# host_vars/db-01.yml
---
ansible_host: 10.0.2.10
db_name: production_db
db_user: app_user
Terminal window
# Create encrypted file
ansible-vault create secrets.yml
# Edit encrypted file
ansible-vault edit secrets.yml
# Encrypt existing file
ansible-vault encrypt secrets.yml
# Decrypt file
ansible-vault decrypt secrets.yml
# View encrypted file
ansible-vault view secrets.yml
# secrets.yml (encrypted)
---
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
6638643965396638646638646638643965396638646638646638643965396638
...
app_secret_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
6638643965396638646638646638643965396638646638646638643965396638
...
Terminal window
# Run playbook with vault
ansible-playbook site.yml --ask-vault-pass
ansible-playbook site.yml --vault-password-file vault_pass.txt

rolling-update.yml
---
- name: Rolling update
hosts: web_servers
serial: 1
become: yes
pre_tasks:
- name: Remove from load balancer
community.aws.elb_instance:
name: "{{ elb_name }}"
instance_id: "{{ ec2_instance_id }}"
state: absent
delegate_to: localhost
tasks:
- name: Update application
git:
repo: "{{ app_repo }}"
dest: /opt/app
version: "{{ app_version }}"
- name: Restart application
systemd:
name: app
state: restarted
post_tasks:
- name: Wait for application
wait_for:
port: "{{ app_port }}"
delay: 10
timeout: 60
- name: Add back to load balancer
community.aws.elb_instance:
name: "{{ elb_name }}"
instance_id: "{{ ec2_instance_id }}"
state: present
delegate_to: localhost
blue-green-deploy.yml
---
- name: Blue-Green Deployment
hosts: localhost
connection: local
gather_facts: no
vars:
blue_asg: web-asg-blue
green_asg: web-asg-green
elb_name: web-elb
tasks:
- name: Determine active environment
command: aws elb describe-load-balancers --load-balancer-name {{ elb_name }}
register: elb_info
changed_when: false
- name: Set target environment
set_fact:
target_env: "{{ 'green' if 'blue' in active_asg else 'blue' }}"
vars:
active_asg: "{{ elb_info.stdout | from_json | json_query('LoadBalancerDescriptions[0].Instances[0].InstanceId') }}"
- name: Scale up target environment
amazon.aws.ec2_asg:
name: "{{ target_env == 'green' | ternary(green_asg, blue_asg) }}"
desired_capacity: 3
state: present
- name: Wait for instances to be healthy
command: aws elb describe-instance-health --load-balancer-name {{ elb_name }}
register: health_check
until: health_check.stdout | from_json | json_query('InstanceStates[?State==`InService`]') | length == 3
retries: 30
delay: 30
- name: Switch traffic to target environment
command: aws elb deregister-instances-from-load-balancer --load-balancer-name {{ elb_name }} --instances {{ active_instances }}
vars:
active_instances: "{{ elb_info.stdout | from_json | json_query('LoadBalancerDescriptions[0].Instances[*].InstanceId') | join(' ') }}"
- name: Scale down old environment
amazon.aws.ec2_asg:
name: "{{ target_env == 'green' | ternary(blue_asg, green_asg) }}"
desired_capacity: 0
state: present
async-tasks.yml
---
- name: Long-running tasks
hosts: all
become: yes
tasks:
- name: Start long-running update
command: /opt/app/update.sh
async: 3600
poll: 0
register: update_task
- name: Continue with other tasks
debug:
msg: "Update started, continuing with other tasks"
- name: Check update status
async_status:
jid: "{{ update_task.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 60
delay: 60

.gitlab-ci.yml
stages:
- validate
- test
- deploy
variables:
ANSIBLE_HOST_KEY_CHECKING: "false"
validate:
stage: validate
image: ansible/ansible:latest
script:
- ansible-lint playbooks/
- ansible-playbook --syntax-check playbooks/site.yml
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
test:
stage: test
image: ansible/ansible:latest
script:
- ansible-playbook playbooks/test.yml -i inventory/test.yml --check
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
deploy_staging:
stage: deploy
image: ansible/ansible:latest
script:
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- ansible-playbook playbooks/deploy.yml -i inventory/staging.yml
environment:
name: staging
rules:
- if: $CI_COMMIT_BRANCH == "develop"
deploy_production:
stage: deploy
image: ansible/ansible:latest
script:
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- ansible-playbook playbooks/deploy.yml -i inventory/production.yml --vault-password-file $VAULT_PASSWORD
environment:
name: production
when: manual
rules:
- if: $CI_COMMIT_BRANCH == "main"
.github/workflows/ansible.yml
name: Ansible Deployment
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install Ansible
run: |
pip install ansible ansible-lint
ansible-galaxy collection install amazon.aws
- name: Lint playbooks
run: ansible-lint playbooks/
deploy:
needs: lint
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install Ansible
run: |
pip install ansible boto3 botocore
ansible-galaxy collection install amazon.aws
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Run Ansible playbook
run: |
ansible-playbook playbooks/deploy.yml \
-i inventory/aws_ec2.yml \
--vault-password-file <(echo "${{ secrets.VAULT_PASSWORD }}")

Ansible Security Best Practices
+------------------------------------------------------------------+
| |
| 1. Credential Management |
| +--------------------------------------------------------+ |
| | - Use Ansible Vault for secrets | |
| | - Use IAM roles for AWS operations | |
| | - Rotate credentials regularly | |
| | - Never commit secrets to version control | |
| +--------------------------------------------------------+ |
| |
| 2. Access Control |
| +--------------------------------------------------------+ |
| | - Use least privilege for IAM roles | |
| | - Restrict SSH access to control nodes | |
| | - Use bastion hosts for private instances | |
| | - Enable MFA for AWS accounts | |
| +--------------------------------------------------------+ |
| |
| 3. Network Security |
| +--------------------------------------------------------+ |
| | - Use VPN or private links for management | |
| | - Restrict security group rules | |
| | - Use encrypted connections (SSH, WinRM) | |
| +--------------------------------------------------------+ |
| |
+------------------------------------------------------------------+
# Enable pipelining for faster execution
# ansible.cfg
[ssh_connection]
pipelining = True
# Use async for long-running tasks
- name: Long task
command: /opt/long-task.sh
async: 3600
poll: 0
# Use serial for controlled execution
- name: Controlled update
hosts: all
serial: 5 # Process 5 hosts at a time
# Use strategy plugins
- name: Mitogen strategy
hosts: all
strategy: mitogen_linear # Requires mitogen plugin
ansible-project/
+------------------------------------------------------------------+
| |
| playbooks/ # Playbooks |
| +-- site.yml # Main playbook |
| +-- deploy.yml # Deployment playbook |
| +-- update.yml # Update playbook |
| |
| roles/ # Roles |
| +-- common/ |
| +-- web-server/ |
| +-- database/ |
| |
| inventory/ # Inventory |
| +-- aws_ec2.yml # Dynamic inventory |
| +-- staging/ # Staging environment |
| +-- production/ # Production environment |
| |
| group_vars/ # Group variables |
| +-- all.yml |
| +-- web_servers.yml |
| +-- db_servers.yml |
| |
| host_vars/ # Host variables |
| +-- web-01.yml |
| +-- db-01.yml |
| |
| files/ # Static files |
| templates/ # Jinja2 templates |
| vars/ # Extra variables |
| secrets/ # Vault-encrypted files |
| ansible.cfg # Ansible configuration |
| requirements.yml # Galaxy requirements |
| |
+------------------------------------------------------------------+

Ansible Troubleshooting
+------------------------------------------------------------------+
| |
| Issue: SSH Connection Failed |
| +--------------------------------------------------------+ |
| | Solutions: | |
| | - Check SSH key permissions (600) | |
| | - Verify security group allows SSH | |
| | - Check ansible_user matches AMI | |
| | - Enable host_key_checking = False | |
| +--------------------------------------------------------+ |
| |
| Issue: Permission Denied |
| +--------------------------------------------------------+ |
| | Solutions: | |
| | - Add become: yes to playbook | |
| | - Check sudo permissions | |
| | - Verify user has sudo access | |
| +--------------------------------------------------------+ |
| |
| Issue: AWS Module Failures |
| +--------------------------------------------------------+ |
| | Solutions: | |
| | - Check AWS credentials | |
| | - Verify IAM permissions | |
| | - Install boto3/botocore | |
| | - Check region configuration | |
| +--------------------------------------------------------+ |
| |
+------------------------------------------------------------------+
Terminal window
# Verbose output
ansible-playbook site.yml -v
ansible-playbook site.yml -vvv # More verbose
ansible-playbook site.yml -vvvv # Debug level
# Check mode (dry run)
ansible-playbook site.yml --check
# Diff mode (show changes)
ansible-playbook site.yml --diff
# Step mode (confirm each task)
ansible-playbook site.yml --step
# Start at specific task
ansible-playbook site.yml --start-at-task="Install Nginx"
# List hosts
ansible-playbook site.yml --list-hosts
# List tags
ansible-playbook site.yml --list-tags

TopicKey Points
AgentlessNo software installation required on targets
Dynamic InventoryUse aws_ec2 plugin for automatic inventory
ModulesUse AWS modules for infrastructure management
RolesOrganize code into reusable roles
VaultEncrypt sensitive data with Ansible Vault
CI/CDIntegrate with pipelines for automated deployment


Next Chapter: Chapter 44 - AWS Config & Resource Compliance