AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

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.

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home