A complete, production-ready guide to building an Ansible automation lab with 1 master node and 1000 target containers on a single GCP VM. This repository documents the entire setup process, from infrastructure provisioning to advanced playbook execution.
This lab environment enables DevOps engineers and Ansible practitioners to:
- Practice Ansible automation on a massive scale (1000+ managed nodes)
- Test configuration management workflows without cloud costs
- Learn inventory management, playbooks, and best practices
- Prepare for production Ansible deployments
Lab Capacity: 600-1000 containers on a single e2-standard-16 VM (16 vCPUs, 64 GB RAM, 500 GB disk)
- VM: Google Cloud Platform e2-standard-16 instance (or equivalent)
- 16 vCPUs
- 64 GB RAM
- 500 GB SSD
- OS: Ubuntu 22.04 LTS or later
- Network: Public IP for SSH access
- SSH client
- Docker CLI (installed on VM)
- Text editor (vim, nano, or similar)
# Update system packages
apt update -y && apt upgrade -y
# Install Docker and utilities
apt install -y vim docker.io curl git# Create and start master container
docker run -itd --name master ubuntu:latest bash
# Install Ansible inside master
docker exec -it master bash -c '
apt update -y && apt upgrade -y
apt install -y python-is-python3 vim iputils-ping openssh-server openssh-client sshpass
apt install -y software-properties-common
add-apt-repository --yes --update ppa:ansible/ansible
apt install -y ansible
ansible --version
'Save the following as Dockerfile.target:
FROM ubuntu:latest
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Kolkata
RUN apt-get update && \
apt-get install -y --no-install-recommends \
tzdata openssh-server openssh-client python3 python-is-python3 sudo vim iputils-ping && \
ln -fs /usr/share/zoneinfo/Asia/Kolkata /etc/localtime && \
dpkg-reconfigure --frontend noninteractive tzdata && \
rm -rf /var/lib/apt/lists/*
RUN mkdir /var/run/sshd && \
echo 'root:admin' | chpasswd && \
sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]Build the image:
docker build -f Dockerfile.target -t target-img .Save the following as deploy_containers.sh:
#!/bin/bash
echo "Deploying 1000 target containers..."
for i in $(seq 1 1000); do
docker run -d --name target-$i target-img
[ $((i % 100)) -eq 0 ] && echo "Deployed $i containers..."
done
echo "Deployment complete!"Run the deployment:
chmod +x deploy_containers.sh
./deploy_containers.sh
# Verify
docker ps | grep target | wc -lSave the following as generate_inventory.sh:
#!/bin/bash
INVENTORY_FILE="hosts"
IP_FILE="ips.txt"
echo "[targets]" > "$INVENTORY_FILE"
> "$IP_FILE"
for i in $(seq 1 1000); do
name="target-$i"
ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$name" 2>/dev/null)
[ -z "$ip" ] && continue
echo "${name} ansible_host=${ip} ansible_user=root" >> "$INVENTORY_FILE"
echo "${ip}" >> "$IP_FILE"
done
echo "Generated inventory with $(grep -c '.' $INVENTORY_FILE) hosts"Run and copy to master:
chmod +x generate_inventory.sh
./generate_inventory.sh
docker cp hosts master:/etc/ansible/hosts
docker cp ips.txt master:/etc/ansible/ips.txtInside the master container:
docker exec -it master bash
cd /etc/ansible
ssh-keygen -t rsa -N "" -f /root/.ssh/id_rsaCreate distribute_keys.sh inside master:
#!/bin/bash
export SSHPASS='admin'
echo "Distributing SSH keys to all targets..."
count=0
for ip in $(grep -v '^#' /etc/ansible/ips.txt); do
sshpass -e ssh-copy-id -o StrictHostKeyChecking=no root@$ip 2>/dev/null
((count++))
[ $((count % 100)) -eq 0 ] && echo "Keys distributed to $count hosts..."
done
echo "SSH key distribution complete!"Make executable and run:
chmod +x /etc/ansible/distribute_keys.sh
/etc/ansible/distribute_keys.shansible all -i hosts -m pingExpected output: All targets return "ping": "pong" β
File: gather_info.yml
---
- name: Gather System Information
hosts: targets
gather_facts: yes
tasks:
- name: Get Hostname and IP
debug:
msg: "{{ ansible_hostname }} | {{ ansible_all_ipv4_addresses | first }}"
- name: Get OS Information
debug:
msg: "{{ ansible_distribution }} {{ ansible_distribution_version }}"Run:
ansible-playbook -i hosts gather_info.ymlFile: patch_systems.yml
---
- name: Patch All Systems
hosts: targets
become: yes
tasks:
- name: Update package cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Upgrade all packages
apt:
upgrade: dist
autoremove: yes
autoclean: yes
- name: Check if reboot required
stat:
path: /var/run/reboot-required
register: reboot_required
- name: Reboot if necessary
reboot:
msg: "Rebooting due to kernel updates"
connect_timeout: 5
reboot_timeout: 300
post_reboot_delay: 30
when: reboot_required.stat.existsRun with limited parallelism:
ansible-playbook -i hosts patch_systems.yml --forks 50File: install_packages.yml
---
- name: Install Development Tools
hosts: targets
become: yes
tasks:
- name: Update apt cache
apt:
update_cache: yes
- name: Install packages
apt:
name:
- curl
- wget
- git
- vim
- htop
- net-tools
state: present# Increase simultaneous connections (default: 5)
ansible-playbook -i hosts playbook.yml --forks 100On the host VM:
# Increase file descriptors
ulimit -n 65535
# Make permanent (add to /etc/security/limits.conf)
echo "* soft nofile 65535" >> /etc/security/limits.conf
echo "* hard nofile 65535" >> /etc/security/limits.conf# Check memory and CPU usage
docker stats --no-stream
# List top memory-consuming containers
docker stats --no-stream | sort -k4 -h | tail -20# Check container logs
docker logs target-1
# Inspect container state
docker inspect target-1 | grep -A 5 "State"# Test SSH connectivity from master
docker exec master ssh -v root@<target-ip>
# Verify keys were copied
docker exec master ssh -i /root/.ssh/id_rsa root@<target-ip> "echo OK"# Check if Python is installed on targets
docker exec target-1 python --version
# Test raw SSH command
ansible all -i hosts -m raw -a "whoami"# Reduce number of containers or increase forks limit
ansible-playbook -i hosts playbook.yml --forks 20
# Check memory usage
free -h
docker stats --no-stream | head -20| Component | Capacity | Bottleneck |
|---|---|---|
| Memory | ~60 GB usable | Idle: 1200 containers, Active: 600 containers |
| CPU | 16 vCPUs | Concurrent playbook execution |
| Disk | ~30 GB for 1000 containers | Minimal (Docker uses layered filesystem) |
| Network | VM-dependent | SSH key distribution, large file transfers |
Use Docker API to dynamically discover containers:
# docker_inventory.yml
plugin: community.docker.docker_containers
docker_host: unix://var/run/docker.sock
compose:
ansible_host: NetworkSettings.Networks.bridge.IPAddress
ansible_user: root
groups:
targets: "'target-' in Name"Organize playbooks using Ansible roles for complex deployments:
roles/
βββ base/
β βββ tasks/main.yml
βββ security/
β βββ tasks/main.yml
βββ monitoring/
βββ tasks/main.yml
# Create encrypted variables
ansible-vault create secrets.yml
# Run playbook with vault
ansible-playbook -i hosts playbook.yml --ask-vault-pass- Use Dynamic Inventory: Avoid manual inventory updates for large deployments
- Limit Parallelism: Start with
--forks 20-50to avoid overwhelming the system - Test on Small Groups: Always test playbooks on a subset before rolling out to all hosts
- Monitor Resources: Continuously check CPU, memory, and disk usage
- Version Control: Keep all playbooks and scripts in Git
- Documentation: Document playbook purposes and dependencies
This project is licensed under the MIT License - see LICENSE file for details.
Contributions are welcome! Please submit pull requests or open issues for bugs and feature requests.
Created as a comprehensive Ansible practice lab for DevOps engineers and infrastructure automation practitioners.
Last Updated: December 2025
Status: Production-Ready β