8 min read

Mastering Hybrid Cloud: A Tutorial on Ansible Playbooks for Cross-Platform IaC

Table of Contents

The reality for most modern enterprises isn’t β€œcloud” or β€œon-premise”—it’s both. The hybrid cloud model offers the best of both worlds: the scalability of public clouds like AWS and Azure combined with the security and control of private data centers. However, this diversity creates a significant challenge: how do you manage and automate a disparate infrastructure estate without creating brittle, provider-specific scripts?

This is where Infrastructure as Code (IaC) becomes critical, and Ansible emerges as a powerful unifying force. Its agentless architecture and simple, human-readable YAML syntax make it the perfect tool for managing everything from bare-metal servers to cloud-native resources.

In this tutorial, we’ll dive deep into creating robust Ansible playbooks designed specifically for hybrid cloud environments. You will learn how to build a single automation workflow that can provision, configure, and manage resources across different platforms, saving you time and dramatically reducing complexity.

Table of contents

Why Ansible for Hybrid and Multi-Cloud?

While cloud-specific tools like AWS CloudFormation or Azure Resource Manager (ARM) templates are powerful, they lock you into a single ecosystem. Other tools like Terraform excel at provisioning infrastructure but are less focused on configuration management.

Ansible strikes a unique balance, making it an ideal choice for hybrid cloud automation:

  • Agentless: Ansible communicates over standard protocols like SSH (for Linux/Unix) and WinRM (for Windows). You don’t need to install and manage a client agent on every target node, which simplifies setup across a diverse fleet.
  • Declarative YAML: You define the desired state of your system in simple YAML playbooks. Ansible handles the procedural steps to get there, making your automation predictable and idempotent.
  • Vast Module Library: With thousands of official and community-supported modules, you can interact with virtually any service, from cloud provider APIs (amazon.aws.ec2_instance) to on-premise hypervisors (community.vmware.vmware_guest) and network devices.
  • Single Pane of Glass: Ansible provides a consistent language and toolset to manage your entire infrastructure, whether it’s a physical server in your data center, a VM on VMware vSphere, or an instance in the cloud.

Step 1: Building a Hybrid Inventory

The foundation of any Ansible setup is the inventory file, which defines the hosts you want to manage. For a hybrid environment, the key is to group your hosts logically by provider or location.

While static inventories are great for learning, production environments should use dynamic inventories. These are scripts that query your cloud provider or virtualization platform (like AWS, Azure, or vSphere) to automatically discover and group hosts. This ensures your inventory is always up-to-date.

For this tutorial, let’s start with a static inventory (inventory.ini) to illustrate the concept. Here, we’ve grouped hosts by their location: public cloud (AWS and Azure) and our on-premise data center.

# inventory.ini

[aws]
ec2-prod-web-01 ansible_host=54.12.34.56
ec2-prod-db-01  ansible_host=54.78.90.12

[azure]
vm-prod-web-01 ansible_host=20.123.45.67
vm-prod-db-01  ansible_host=20.78.90.12

[onprem]
server-dc-web-01 ansible_host=192.168.1.10
server-dc-db-01  ansible_host=192.168.1.11

# Group of groups for easier targeting
[webservers:children]
aws
azure
onprem

[dbservers:children]
aws
azure
onprem

# Cloud-specific group
[cloud:children]
aws
azure

This structure allows you to target tasks to:

  • A specific provider (aws).
  • A specific location (onprem).
  • All cloud hosts (cloud).
  • All web servers, regardless of location (webservers).

Step 2: Crafting a Cross-Platform Playbook with Conditionals

Now, let’s write a playbook that installs and configures a web server (Nginx). The challenge is that package names or service details might differ between environments. We can handle this using variables and conditional logic.

Defining Environment-Specific Variables

Ansible’s group_vars feature is perfect for this. We create a directory named group_vars/ next to our playbook. Inside, we create YAML files named after our inventory groups. Ansible will automatically load variables from these files when running tasks on hosts in that group.

File structure:

.
β”œβ”€β”€ group_vars/
β”‚   β”œβ”€β”€ aws.yml
β”‚   └── onprem.yml
β”œβ”€β”€ inventory.ini
└── deploy_nginx.yml

group_vars/aws.yml: Here we define the user for an Amazon Linux instance and specify a task to install a monitoring agent.

# group_vars/aws.yml
ansible_user: ec2-user
install_monitoring_agent: true

group_vars/onprem.yml: For our on-premise servers (assuming CentOS/RHEL), we use a different user and skip the cloud agent.

# group_vars/onprem.yml
ansible_user: root
install_monitoring_agent: false

The Universal Playbook

Now, our main playbook (deploy_nginx.yml) can use these variables to adapt its behavior. We’ll use the when conditional to run tasks only on certain hosts.

# deploy_nginx.yml
---
- name: Deploy and Configure Nginx on all Web Servers
  hosts: webservers
  become: true
  tasks:
    - name: Install Nginx (EPEL for on-prem, default for cloud)
      ansible.builtin.package:
        name: nginx
        state: present
      # This block demonstrates a more complex package handling scenario
      # In this simple case, `nginx` works for most, but you could use vars:
      # name: "{{ nginx_package_name | default('nginx') }}"

    - name: Ensure Nginx service is running and enabled
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true

    - name: Deploy custom Nginx configuration
      ansible.builtin.template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: Restart Nginx

    - name: Install Datadog agent on cloud hosts
      ansible.builtin.include_tasks: tasks/install_datadog.yml
      when: install_monitoring_agent | default(false)

  handlers:
    - name: Restart Nginx
      ansible.builtin.service:
        name: nginx
        state: restarted

In this playbook:

  1. We target all hosts in the webservers group, abstracting away their physical location.
  2. The ansible.builtin.package task installs Nginx. For more complex scenarios where package names differ (e.g., httpd vs apache2), you could define a package_name variable in your group_vars.
  3. The ansible.builtin.template module pushes a configuration file. This is powerful because you can use Jinja2 templating to insert host-specific variables into the config.
  4. The when: install_monitoring_agent condition is key. The task to install the Datadog agent will only run on hosts where this variable is true (our aws group). The default(false) filter ensures the task is skipped if the variable isn’t defined at all.
  5. A handler ensures Nginx is restarted only if the configuration file changes, making the playbook efficient.

Step 3: Abstracting Logic with Roles

As your playbooks grow, managing them in a single file becomes cumbersome. Ansible Roles are the solution. A role is a standardized, reusable collection of tasks, handlers, variables, and templates.

Let’s refactor our Nginx deployment into a nginx role.

Directory Structure for the Role:

.
β”œβ”€β”€ roles/
β”‚   └── nginx/
β”‚       β”œβ”€β”€ tasks/
β”‚       β”‚   └── main.yml
β”‚       β”œβ”€β”€ handlers/
β”‚       β”‚   └── main.yml
β”‚       β”œβ”€β”€ templates/
β”‚       β”‚   └── nginx.conf.j2
β”‚       └── defaults/
β”‚           └── main.yml
β”œβ”€β”€ inventory.ini
└── site.yml
  • tasks/main.yml: Contains the core tasks for installing and configuring Nginx.
  • handlers/main.yml: Contains the β€œRestart Nginx” handler.
  • defaults/main.yml: Defines default variables for the role, which can be easily overridden.
  • templates/: Holds the Jinja2 template for the Nginx config.

Now, our main site playbook (site.yml) becomes incredibly clean and readable:

# site.yml
---
- name: Configure Web Servers
  hosts: webservers
  roles:
    - nginx

- name: Configure Database Servers
  hosts: dbservers
  roles:
    - role: postgresql
      # We can pass role-specific variables here
      pg_version: 15

This approach is the cornerstone of scalable Infrastructure as Code. You can now apply the nginx role to any new server group, regardless of its location, with full confidence that it will be configured correctly.

Best Practices for Hybrid Cloud Automation

  1. Embrace Dynamic Inventories: Manually updating inventory files is error-prone. Use official or community plugins to query AWS, Azure, GCP, VMware, and other platforms directly.
  2. Use Ansible Vault: Never store secrets like API keys, passwords, or SSH keys in plain text. Use Ansible Vault to encrypt sensitive variables within your repository.
  3. Leverage Collections: The Ansible ecosystem is moving towards Collections, which are bundles of modules, roles, and plugins. Use certified collections like amazon.aws, azure.azcollection, and community.vmware for reliable, up-to-date functionality.
  4. Integrate with CI/CD: Store your playbooks in a Git repository. Use a CI/CD pipeline with tools like GitHub Actions or GitLab CI/CD to automatically test (--check mode) and deploy your infrastructure changes.
  5. Think in Roles: From the beginning, structure your automation around reusable roles. A role for your web server, another for your database, a third for common security hardening, etc.

Conclusion

Managing a hybrid cloud environment doesn’t have to be a nightmare of disparate tools and scripts. By adopting Ansible as your primary IaC engine, you can create a single, unified automation framework that spans your entire infrastructure estate.

By mastering hybrid inventories, conditional logic, and reusable roles, you can build playbooks that are not only powerful but also elegant and maintainable. You gain the ability to deploy applications, manage configurations, and orchestrate complex workflows consistently, whether your servers are in a rack down the hall or in a data center on another continent.

Start small. Automate one common task across your hybrid environment. As you see the benefits, you can progressively expand your Ansible-powered automation to conquer the complexity of the hybrid cloud.