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:
- We target all hosts in the
webserversgroup, abstracting away their physical location. - The
ansible.builtin.packagetask installs Nginx. For more complex scenarios where package names differ (e.g.,httpdvsapache2), you could define apackage_namevariable in yourgroup_vars. - The
ansible.builtin.templatemodule pushes a configuration file. This is powerful because you can use Jinja2 templating to insert host-specific variables into the config. - The
when: install_monitoring_agentcondition is key. The task to install the Datadog agent will only run on hosts where this variable istrue(ourawsgroup). Thedefault(false)filter ensures the task is skipped if the variable isnβt defined at all. - A
handlerensures 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
- 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.
- 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.
- 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, andcommunity.vmwarefor reliable, up-to-date functionality. - 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 (
--checkmode) and deploy your infrastructure changes. - 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.