Ansible for Cloud Automation: AWS, Azure, and GCP Complete Guide
By Luca Berton · Published 2024-01-01 · Category: installation
Complete guide to Ansible cloud automation for AWS, Azure, and GCP. Provision EC2 instances, Azure VMs, and GCE instances.
Ansible manages all three major clouds through dedicated collections — amazon.aws, azure.azcollection, and google.cloud. Same YAML playbooks, same automation patterns, different providers. Here's how to provision and manage infrastructure across AWS, Azure, and GCP.
Setup
Install Collections
# AWS
ansible-galaxy collection install amazon.aws
pip install boto3 botocore
# Azure
ansible-galaxy collection install azure.azcollection
pip install -r ~/.ansible/collections/ansible_collections/azure/azcollection/requirements.txt
# GCP
ansible-galaxy collection install google.cloud
pip install google-auth google-api-python-client
Authentication
AWS
# Option 1: Environment variables
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="secret..."
export AWS_REGION="us-east-1"
# Option 2: AWS CLI profile
aws configure --profile ansible
export AWS_PROFILE=ansible
# Option 3: IAM role (on EC2 instances) — no credentials needed
# Or in playbook
- amazon.aws.ec2_instance:
access_key: "{{ vault_aws_access_key }}"
secret_key: "{{ vault_aws_secret_key }}"
region: us-east-1
Azure
# Option 1: Environment variables
export AZURE_SUBSCRIPTION_ID="..."
export AZURE_CLIENT_ID="..."
export AZURE_SECRET="..."
export AZURE_TENANT="..."
# Option 2: Azure CLI (interactive)
az login
GCP
# Option 1: Service account JSON
export GCP_SERVICE_ACCOUNT_FILE=/path/to/service-account.json
# Option 2: Application Default Credentials
gcloud auth application-default login
# Or in playbook
- google.cloud.gcp_compute_instance:
auth_kind: serviceaccount
service_account_file: /path/to/sa.json
project: my-project
See also: AAP 2.6 Cloud Automation: AWS, Azure, and GCP with Ansible
AWS Automation
Provision EC2 Instances
---
- name: AWS Infrastructure
hosts: localhost
connection: local
vars:
region: us-east-1
instance_type: t3.medium
ami: ami-0c55b159cbfafe1f0
tasks:
- 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 subnet
amazon.aws.ec2_vpc_subnet:
vpc_id: "{{ vpc.vpc.id }}"
cidr: 10.0.1.0/24
az: "{{ region }}a"
region: "{{ region }}"
tags:
Name: public-subnet
register: subnet
- name: Create internet gateway
amazon.aws.ec2_vpc_igw:
vpc_id: "{{ vpc.vpc.id }}"
region: "{{ region }}"
register: igw
- name: Create security group
amazon.aws.ec2_security_group:
name: web-sg
description: Web server security group
vpc_id: "{{ vpc.vpc.id }}"
region: "{{ region }}"
rules:
- proto: tcp
ports: [80, 443]
cidr_ip: 0.0.0.0/0
- proto: tcp
ports: [22]
cidr_ip: 10.0.0.0/8
register: sg
- name: Launch EC2 instances
amazon.aws.ec2_instance:
name: "web-{{ item }}"
instance_type: "{{ instance_type }}"
image_id: "{{ ami }}"
subnet_id: "{{ subnet.subnet.id }}"
security_group: "{{ sg.group_id }}"
key_name: deploy-key
region: "{{ region }}"
network:
assign_public_ip: true
tags:
Role: webserver
Environment: production
wait: true
loop: "{{ range(1, 4) | list }}"
register: ec2_instances
S3 Bucket Management
- name: Create S3 bucket
amazon.aws.s3_bucket:
name: my-app-assets-{{ aws_account_id }}
region: "{{ region }}"
versioning: true
encryption: AES256
public_access:
block_public_acls: true
block_public_policy: true
ignore_public_acls: true
restrict_public_buckets: true
tags:
Purpose: application-assets
- name: Upload files to S3
amazon.aws.s3_object:
bucket: my-app-assets-{{ aws_account_id }}
object: "config/{{ item | basename }}"
src: "{{ item }}"
mode: put
loop: "{{ lookup('fileglob', 'configs/*', wantlist=True) }}"
Application Load Balancer
- name: Create ALB
amazon.aws.elb_application_lb:
name: web-alb
region: "{{ region }}"
subnets:
- "{{ subnet_a.subnet.id }}"
- "{{ subnet_b.subnet.id }}"
security_groups:
- "{{ sg.group_id }}"
listeners:
- Protocol: HTTPS
Port: 443
Certificates:
- CertificateArn: "{{ acm_cert_arn }}"
DefaultActions:
- Type: forward
TargetGroupName: web-targets
- name: Create target group
amazon.aws.elb_target_group:
name: web-targets
protocol: HTTP
port: 80
vpc_id: "{{ vpc.vpc.id }}"
region: "{{ region }}"
health_check_path: /health
targets:
- Id: "{{ item.instance_id }}"
Port: 80
loop: "{{ ec2_instances.results }}"
Azure Automation
Provision Azure VMs
---
- name: Azure Infrastructure
hosts: localhost
connection: local
vars:
resource_group: production-rg
location: eastus
tasks:
- name: Create resource group
azure.azcollection.azure_rm_resourcegroup:
name: "{{ resource_group }}"
location: "{{ location }}"
tags:
Environment: production
- name: Create virtual network
azure.azcollection.azure_rm_virtualnetwork:
resource_group: "{{ resource_group }}"
name: production-vnet
address_prefixes: "10.0.0.0/16"
- name: Create subnet
azure.azcollection.azure_rm_subnet:
resource_group: "{{ resource_group }}"
virtual_network: production-vnet
name: web-subnet
address_prefix: "10.0.1.0/24"
- name: Create network security group
azure.azcollection.azure_rm_securitygroup:
resource_group: "{{ resource_group }}"
name: web-nsg
rules:
- name: AllowHTTP
protocol: Tcp
destination_port_range: [80, 443]
access: Allow
priority: 100
direction: Inbound
- name: AllowSSH
protocol: Tcp
destination_port_range: 22
access: Allow
priority: 200
direction: Inbound
source_address_prefix: 10.0.0.0/8
- name: Create public IP
azure.azcollection.azure_rm_publicipaddress:
resource_group: "{{ resource_group }}"
name: "web-{{ item }}-pip"
allocation_method: Static
sku: Standard
loop: "{{ range(1, 4) | list }}"
register: public_ips
- name: Create VM
azure.azcollection.azure_rm_virtualmachine:
resource_group: "{{ resource_group }}"
name: "web-{{ item }}"
vm_size: Standard_B2s
admin_username: deploy
ssh_password_enabled: false
ssh_public_keys:
- path: /home/deploy/.ssh/authorized_keys
key_data: "{{ lookup('file', '~/.ssh/deploy_key.pub') }}"
image:
offer: 0001-com-ubuntu-server-jammy
publisher: Canonical
sku: 22_04-lts
version: latest
os_disk_size_gb: 30
virtual_network: production-vnet
subnet: web-subnet
tags:
Role: webserver
loop: "{{ range(1, 4) | list }}"
Azure Kubernetes Service (AKS)
- name: Create AKS cluster
azure.azcollection.azure_rm_aks:
resource_group: "{{ resource_group }}"
name: production-aks
location: "{{ location }}"
kubernetes_version: "1.30"
dns_prefix: prod-aks
agent_pool_profiles:
- name: default
count: 3
vm_size: Standard_D2s_v3
os_disk_size_gb: 100
mode: System
network_profile:
network_plugin: azure
service_cidr: 172.16.0.0/16
dns_service_ip: 172.16.0.10
tags:
Environment: production
See also: Ansible Dynamic Inventory: Complete Guide to AWS, Azure, GCP, and Custom Plugins
GCP Automation
Provision GCE Instances
---
- name: GCP Infrastructure
hosts: localhost
connection: local
vars:
project: my-gcp-project
zone: us-central1-a
region: us-central1
tasks:
- name: Create VPC network
google.cloud.gcp_compute_network:
name: production-network
project: "{{ project }}"
auto_create_subnetworks: false
auth_kind: serviceaccount
service_account_file: "{{ gcp_sa_file }}"
register: network
- name: Create subnet
google.cloud.gcp_compute_subnetwork:
name: web-subnet
ip_cidr_range: 10.0.1.0/24
network: "{{ network }}"
region: "{{ region }}"
project: "{{ project }}"
auth_kind: serviceaccount
service_account_file: "{{ gcp_sa_file }}"
register: subnet
- name: Create firewall rules
google.cloud.gcp_compute_firewall:
name: allow-web
network: "{{ network }}"
project: "{{ project }}"
allowed:
- ip_protocol: tcp
ports: ['80', '443']
source_ranges: ['0.0.0.0/0']
target_tags: ['web']
auth_kind: serviceaccount
service_account_file: "{{ gcp_sa_file }}"
- name: Create GCE instances
google.cloud.gcp_compute_instance:
name: "web-{{ item }}"
machine_type: e2-medium
zone: "{{ zone }}"
project: "{{ project }}"
disks:
- auto_delete: true
boot: true
initialize_params:
source_image: projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts
disk_size_gb: 30
network_interfaces:
- network: "{{ network }}"
subnetwork: "{{ subnet }}"
access_configs:
- name: External NAT
type: ONE_TO_ONE_NAT
tags:
items: ['web']
metadata:
ssh-keys: "deploy:{{ lookup('file', '~/.ssh/deploy_key.pub') }}"
auth_kind: serviceaccount
service_account_file: "{{ gcp_sa_file }}"
loop: "{{ range(1, 4) | list }}"
GKE Cluster
- name: Create GKE cluster
google.cloud.gcp_container_cluster:
name: production-gke
location: "{{ region }}"
project: "{{ project }}"
initial_node_count: 3
node_config:
machine_type: e2-standard-4
disk_size_gb: 100
oauth_scopes:
- https://www.googleapis.com/auth/cloud-platform
network: "{{ network.name }}"
subnetwork: "{{ subnet.name }}"
auth_kind: serviceaccount
service_account_file: "{{ gcp_sa_file }}"
Multi-Cloud Patterns
Dynamic Inventory
# aws_inventory.yml
plugin: amazon.aws.aws_ec2
regions:
- us-east-1
- eu-west-1
filters:
tag:Managed: ansible
keyed_groups:
- key: tags.Role
prefix: role
- key: placement.region
prefix: region
compose:
ansible_host: public_ip_address
# azure_inventory.yml
plugin: azure.azcollection.azure_rm
include_vm_resource_groups:
- production-rg
keyed_groups:
- key: tags.Role
prefix: role
compose:
ansible_host: public_ipv4_addresses[0]
# Use multiple inventories
ansible-playbook -i aws_inventory.yml -i azure_inventory.yml site.yml
Multi-Cloud Deployment Playbook
---
- name: Deploy to AWS
hosts: localhost
connection: local
tasks:
- name: Provision AWS infrastructure
ansible.builtin.include_role:
name: aws_infra
vars:
instance_count: 3
instance_type: t3.medium
- name: Deploy to Azure
hosts: localhost
connection: local
tasks:
- name: Provision Azure infrastructure
ansible.builtin.include_role:
name: azure_infra
vars:
instance_count: 3
vm_size: Standard_B2s
- name: Configure all servers
hosts: role_webserver
become: true
roles:
- common
- nginx
- app_deploy
Cloud-Agnostic Variables
# group_vars/all.yml
cloud_config:
aws:
instance_type: t3.medium
ami: ami-0c55b159cbfafe1f0
region: us-east-1
azure:
vm_size: Standard_B2s
image: Canonical:0001-com-ubuntu-server-jammy:22_04-lts:latest
location: eastus
gcp:
machine_type: e2-medium
image: ubuntu-2204-lts
zone: us-central1-a
# Use: cloud_config[cloud_provider].instance_type
See also: Automating Azure DevTest Labs Course by Luca Berton | Pluralsight
Common Operations
Snapshot and Backup
# AWS EBS snapshot
- amazon.aws.ec2_snapshot:
instance_id: "{{ instance_id }}"
description: "Backup {{ ansible_date_time.date }}"
wait: true
tags:
Backup: daily
# Azure disk snapshot
- azure.azcollection.azure_rm_snapshot:
resource_group: "{{ resource_group }}"
name: "backup-{{ ansible_date_time.date }}"
creation_data:
create_option: Copy
source_id: "{{ disk_id }}"
# GCP disk snapshot
- google.cloud.gcp_compute_snapshot:
name: "backup-{{ ansible_date_time.date }}"
source_disk: "{{ disk }}"
zone: "{{ zone }}"
project: "{{ project }}"
auth_kind: serviceaccount
service_account_file: "{{ gcp_sa_file }}"
DNS Management
# AWS Route53
- amazon.aws.route53:
zone: example.com
record: app.example.com
type: A
value: "{{ alb_dns_name }}"
alias: true
alias_hosted_zone_id: "{{ alb_zone_id }}"
# Azure DNS
- azure.azcollection.azure_rm_dnsrecordset:
resource_group: dns-rg
zone_name: example.com
relative_name: app
record_type: A
records:
- entry: "{{ public_ip }}"
# GCP Cloud DNS
- google.cloud.gcp_dns_resource_record_set:
name: app.example.com.
managed_zone: "{{ dns_zone }}"
type: A
ttl: 300
target:
- "{{ external_ip }}"
project: "{{ project }}"
auth_kind: serviceaccount
service_account_file: "{{ gcp_sa_file }}"
FAQ
Should I use Ansible or Terraform for cloud provisioning?
Use Terraform for infrastructure provisioning (VMs, networks, load balancers) — it has state tracking and plan/apply workflows. Use Ansible for configuring what runs on that infrastructure. For simple cloud setups, Ansible alone works fine. For complex multi-resource dependencies, Terraform excels.
How do I handle credentials securely?
Use Ansible Vault for storing cloud credentials, or better — use IAM roles (AWS), Managed Identity (Azure), or Service Accounts (GCP) that don't require storing secrets at all. In CI/CD, use the platform's secret management (GitHub Actions secrets, GitLab CI variables).
Can Ansible manage serverless resources?
Yes. AWS Lambda, Azure Functions, and GCP Cloud Functions all have Ansible modules. You can deploy function code, configure triggers, and manage API gateways through playbooks.
How does dynamic inventory work with auto-scaling?
Dynamic inventory plugins query the cloud API in real-time. When auto-scaling adds instances, they appear in the next inventory refresh. Tag-based grouping ensures new instances get the correct role assignments automatically.
What about cloud costs?
Ansible can help manage costs: schedule instance start/stop, right-size instances based on metrics, clean up unused resources, and enforce tagging policies. Combine with cloud-native cost tools for full visibility.
Conclusion
Ansible's cloud collections give you consistent YAML-based automation across AWS, Azure, and GCP. Use dynamic inventory for auto-discovery, cloud-agnostic variables for portability, and combine with Terraform for complex provisioning. The same team, same playbook patterns, same CI/CD pipeline — regardless of which cloud you're deploying to.
Related Articles
• Ansible vs Terraform 2026 • Ansible for Kubernetes • Ansible Vault Deep Dive • Ansible for AWS Cloud AutomationCategory: installation