Master Ansible Automation: Deploy Nginx Web Servers at Scale in Minutes
This comprehensive guide walks you through why Ansible is essential for modern ops, explains its agentless, declarative, and idempotent advantages, shows step‑by‑step environment setup, inventory creation, playbook and template writing, and covers advanced techniques like rolling updates, vault encryption, dynamic inventory, performance tuning, CI/CD integration, monitoring, and rollback strategies.
Ansible Automation Basics: Bulk Deploy Web Servers
Introduction – Why Every Ops Engineer Should Master Ansible
Remember the night you were woken at 3 am to manually SSH into 20 servers for a critical update? After two exhausting hours you vowed to find an automation tool. This article will change that workflow by teaching you Ansible from scratch through a real‑world Nginx deployment project.
Deploy Nginx on 50 servers in 10 minutes
One‑click rolling updates and rollbacks
Build reusable automation pipelines
Reduce repetitive work by over 90%
1. What Is Ansible and What Problems Does It Solve?
1.1 Traditional Ops Pain Points
Configuration drift : 100 servers should be identical, but ad‑hoc changes cause divergence and failures.
Scale challenges : Growing from 10 to 100 servers turns a 30‑minute deployment into a 5‑hour ordeal, increasing human error.
Knowledge transfer difficulty : When senior staff leave, only scattered shell scripts remain, leaving newcomers guessing task order and parameters.
1.2 Ansible Advantages
Agentless : No client software required; manage nodes over SSH.
Declarative : Describe the desired state instead of how to achieve it.
Idempotent : Re‑running yields the same result, avoiding duplicate actions.
Easy to learn : Simple YAML syntax lowers the learning curve.
Rich module library : 3000+ built‑in modules cover most automation scenarios.
2. Quick Start – Set Up Ansible in 15 Minutes
2.1 Environment Preparation
We create a lab with one control node and three managed nodes:
# inventory.ini (example)
[webservers]
web-01 ansible_host=192.168.1.11
web-02 ansible_host=192.168.1.12
web-03 ansible_host=192.168.1.132.2 Install Ansible
# CentOS/RHEL
sudo yum install -y epel-release
sudo yum install -y ansible
# Ubuntu/Debian
sudo apt update
sudo apt install -y ansible
# Or via pip (recommended for latest version)
sudo pip3 install ansible
# Verify installation
ansible --version2.3 Configure SSH Password‑less Login
# Generate SSH key if needed
ssh-keygen -t rsa -b 2048
# Copy public key to all managed nodes
for ip in 192.168.1.11 192.168.1.12 192.168.1.13; do
ssh-copy-id -i ~/.ssh/id_rsa.pub root@$ip
done
# Test connection
ssh [email protected] 'hostname'2.4 Create Inventory File
# inventory.ini
[webservers]
web-01 ansible_host=192.168.1.11
web-02 ansible_host=192.168.1.12
web-03 ansible_host=192.168.1.13
[webservers:vars]
ansible_user=root
ansible_python_interpreter=/usr/bin/python3
[all:vars]
ansible_connection=sshTest connectivity:
ansible -i inventory.ini all -m pingIf every host returns "pong", the environment is ready.
3. Hands‑On Project – Bulk Deploy Nginx Web Servers
We will implement four tasks: install Nginx, deploy custom config, serve a static site, and enable rolling updates.
Batch install Nginx
Deploy custom configuration
Deploy static website
Implement rolling updates
3.1 Project Structure
nginx-deployment/
├── inventory.ini # host list
├── ansible.cfg # optional config
├── site.yml # main playbook
├── roles/
│ └── nginx/
│ ├── tasks/main.yml
│ ├── templates/nginx.conf.j2
│ ├── templates/index.html.j2
│ ├── handlers/main.yml
│ └── vars/main.yml
└── group_vars/webservers.yml3.2 Write the Playbook (site.yml)
---
- name: Deploy Nginx Web Servers
hosts: webservers
become: yes
gather_facts: yes
vars:
nginx_port: 80
nginx_worker_processes: "{{ ansible_processor_vcpus }}"
nginx_worker_connections: 1024
website_title: "Ansible Automated Deployment Demo"
tasks:
- name: Update package cache (Debian)
apt:
update_cache: yes
when: ansible_os_family == "Debian"
- name: Install Nginx
package:
name: nginx
state: present
- name: Create website directory
file:
path: /var/www/html
state: directory
mode: '0755'
- name: Deploy Nginx configuration
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
backup: yes
notify: restart nginx
- name: Deploy website homepage
template:
src: index.html.j2
dest: /var/www/html/index.html
mode: '0644'
- name: Ensure Nginx service is running
service:
name: nginx
state: started
enabled: yes
- name: Wait for port to be ready
wait_for:
port: "{{ nginx_port }}"
host: "{{ ansible_default_ipv4.address }}"
delay: 5
timeout: 30
handlers:
- name: restart nginx
service:
name: nginx
state: restarted3.3 Configuration Templates
nginx.conf.j2 (simplified):
user www-data;
worker_processes {{ nginx_worker_processes }};
pid /run/nginx.pid;
events {
worker_connections {{ nginx_worker_connections }};
multi_accept on;
use epoll;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
gzip on;
server {
listen {{ nginx_port }} default_server;
root /var/www/html;
index index.html index.htm;
server_name {{ ansible_hostname }}.example.com;
location / {
try_files $uri $uri/ =404;
}
location /health {
access_log off;
return 200 "healthy
";
add_header Content-Type text/plain;
}
}
}index.html.j2 (simplified):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{ website_title }}</title>
<style>
body {font-family: sans-serif; background: linear-gradient(135deg,#667eea,#764ba2); color: white; display:flex; justify-content:center; align-items:center; height:100vh; margin:0;}
.container {text-align:center; padding:2rem; background:rgba(255,255,255,0.1); border-radius:15px; backdrop-filter:blur(10px);}
h1{font-size:3rem; margin-bottom:1rem;}
.info{background:rgba(255,255,255,0.2); padding:1rem; border-radius:10px; margin-top:2rem;}
</style>
</head>
<body>
<div class="container">
<h1>🚀 {{ website_title }}</h1>
<p>Congratulations! You have successfully deployed this page with Ansible.</p>
<div class="info">
<p><strong>Server Name:</strong> {{ ansible_hostname }}</p>
<p><strong>IP Address:</strong> {{ ansible_default_ipv4.address }}</p>
<p><strong>OS:</strong> {{ ansible_distribution }} {{ ansible_distribution_version }}</p>
<p><strong>Deploy Time:</strong> {{ ansible_date_time.iso8601 }}</p>
</div>
</div>
</body>
</html>3.4 Execute Deployment
# Syntax check
ansible-playbook -i inventory.ini site.yml --syntax-check
# Dry run
ansible-playbook -i inventory.ini site.yml --check
# Actual deployment
ansible-playbook -i inventory.ini site.yml
# Verbose output
ansible-playbook -i inventory.ini site.yml -vvv4. Advanced Techniques – Strengthen Your Automation
4.1 Rolling Update Strategy
---
- name: Rolling update Web servers
hosts: webservers
become: yes
serial: 1
max_fail_percentage: 30
pre_tasks:
- name: Remove from load balancer
uri:
url: "http://lb.example.com/api/remove"
method: POST
body_format: json
body: {"server": "{{ ansible_hostname }}"}
delegate_to: localhost
tasks:
- name: Update application code
git:
repo: https://github.com/yourapp/webapp.git
dest: /var/www/html
version: "{{ app_version | default('master') }}"
- name: Restart service
service:
name: nginx
state: restarted
post_tasks:
- name: Health check
uri:
url: "http://{{ ansible_default_ipv4.address }}/health"
status_code: 200
retries: 5
delay: 10
- name: Re‑add to load balancer
uri:
url: "http://lb.example.com/api/add"
method: POST
body_format: json
body: {"server": "{{ ansible_hostname }}"}
delegate_to: localhost4.2 Protect Sensitive Data with Ansible Vault
# Create encrypted file
ansible-vault create secrets.yml
# Edit encrypted file
ansible-vault edit secrets.yml
# Example content of secrets.yml
db_password: "SuperSecret123!"
api_key: "sk-1234567890abcdef"
# Run playbook using vault password
ansible-playbook -i inventory.ini site.yml --ask-vault-pass4.3 Dynamic Inventory (Python Example)
#!/usr/bin/env python3
import json, boto3
def get_inventory():
ec2 = boto3.client('ec2', region_name='us-west-2')
response = ec2.describe_instances(Filters=[
{'Name': 'tag:Environment', 'Values': ['production']},
{'Name': 'instance-state-name', 'Values': ['running']}
])
inventory = {'webservers': {'hosts': [], 'vars': {'ansible_user': 'ubuntu', 'ansible_ssh_private_key_file': '~/.ssh/aws-key.pem'}}}
for reservation in response['Reservations']:
for instance in reservation['Instances']:
inventory['webservers']['hosts'].append(instance['PublicIpAddress'])
return inventory
if __name__ == '__main__':
print(json.dumps(get_inventory()))4.4 Performance Optimizations (ansible.cfg)
[defaults]
host_key_checking = False
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_cache
fact_caching_timeout = 86400
pipelining = True
forks = 50
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
control_path = /tmp/ansible-%%h-%%p-%%r5. Full CI/CD Pipeline Example
---
- name: Complete deployment pipeline
hosts: webservers
become: yes
vars:
app_name: mywebapp
app_version: "{{ lookup('env','BUILD_NUMBER') | default('latest') }}"
deploy_user: webapp
deploy_dir: /opt/{{ app_name }}
backup_dir: /opt/backups/{{ app_name }}
tasks:
- name: Create deployment user
user:
name: "{{ deploy_user }}"
shell: /bin/bash
groups: www-data
append: yes
- name: Create required directories
file:
path: "{{ item }}"
state: directory
owner: "{{ deploy_user }}"
group: "{{ deploy_user }}"
mode: '0755'
loop:
- "{{ deploy_dir }}"
- "{{ backup_dir }}"
- /var/log/{{ app_name }}
- name: Backup current version
archive:
path: "{{ deploy_dir }}"
dest: "{{ backup_dir }}/backup-{{ ansible_date_time.epoch }}.tar.gz"
when: deploy_dir is directory
- name: Pull latest code
git:
repo: "https://github.com/company/{{ app_name }}.git"
dest: "{{ deploy_dir }}"
version: "{{ app_version }}"
force: yes
become_user: "{{ deploy_user }}"
- name: Install dependencies
pip:
requirements: "{{ deploy_dir }}/requirements.txt"
virtualenv: "{{ deploy_dir }}/venv"
virtualenv_python: python3
become_user: "{{ deploy_user }}"
- name: Run database migrations
command: "{{ deploy_dir }}/venv/bin/python manage.py migrate"
args:
chdir: "{{ deploy_dir }}"
become_user: "{{ deploy_user }}"
run_once: true
- name: Collect static files
command: "{{ deploy_dir }}/venv/bin/python manage.py collectstatic --noinput"
args:
chdir: "{{ deploy_dir }}"
become_user: "{{ deploy_user }}"
- name: Configure Systemd service
template:
src: app.service.j2
dest: /etc/systemd/system/{{ app_name }}.service
notify:
- reload systemd
- restart app
- name: Configure Nginx reverse proxy
template:
src: nginx_app.conf.j2
dest: /etc/nginx/sites-available/{{ app_name }}
notify: reload nginx
- name: Enable site
file:
src: /etc/nginx/sites-available/{{ app_name }}
dest: /etc/nginx/sites-enabled/{{ app_name }}
state: link
notify: reload nginx
- name: Run smoke test
uri:
url: "http://localhost/api/health"
status_code: 200
retries: 5
delay: 10
handlers:
- name: reload systemd
systemd:
daemon_reload: yes
- name: restart app
systemd:
name: "{{ app_name }}"
state: restarted
enabled: yes
- name: reload nginx
service:
name: nginx
state: reloaded6. Monitoring & Logging – Ensure Observability
---
- name: Configure monitoring and log collection
hosts: webservers
become: yes
tasks:
- name: Install monitoring agents
package:
name:
- prometheus-node-exporter
- filebeat
state: present
- name: Configure Prometheus Node Exporter
lineinfile:
path: /etc/default/prometheus-node-exporter
regexp: '^ARGS='
line: 'ARGS="--collector.filesystem.ignored-mount-points=^/(sys|proc|dev|run)($|/)"'
notify: restart node-exporter
- name: Deploy Filebeat configuration
template:
src: filebeat.yml.j2
dest: /etc/filebeat/filebeat.yml
mode: '0600'
notify: restart filebeat
- name: Add custom metric collection script
copy:
content: |
#!/bin/bash
echo "app_requests_total $(curl -s localhost/metrics | grep requests_total | awk '{print $2}')"
echo "app_errors_total $(grep ERROR /var/log/{{ app_name }}/app.log | wc -l)"
echo "app_response_time_seconds $(tail -n 100 /var/log/nginx/access.log | awk '{sum+=$10} END {print sum/NR}')"
dest: /usr/local/bin/collect_metrics.sh
mode: '0755'
- name: Schedule metric collection every 5 minutes
cron:
name: "Collect application metrics"
minute: "*/5"
job: "/usr/local/bin/collect_metrics.sh > /var/lib/node_exporter/textfile_collector/app_metrics.prom"
handlers:
- name: restart node-exporter
service:
name: prometheus-node-exporter
state: restarted
- name: restart filebeat
service:
name: filebeat
state: restarted7. Failure Recovery – Quick Rollback Playbook
---
- name: Emergency rollback procedure
hosts: webservers
become: yes
serial: 1
vars_prompt:
- name: confirm_rollback
prompt: "Confirm rollback to previous version? (yes/no)"
private: no
tasks:
- name: Verify confirmation
fail:
msg: "Rollback cancelled"
when: confirm_rollback != "yes"
- name: Find latest backup
find:
paths: "{{ backup_dir }}"
patterns: "backup-*.tar.gz"
register: backup_files
- name: Ensure a backup exists
fail:
msg: "No backup files found"
when: backup_files.files | length == 0
- name: Get latest backup path
set_fact:
latest_backup: "{{ (backup_files.files | sort(attribute='mtime') | last).path }}"
- name: Stop application service
systemd:
name: "{{ app_name }}"
state: stopped
- name: Remove current version
file:
path: "{{ deploy_dir }}"
state: absent
- name: Restore backup
unarchive:
src: "{{ latest_backup }}"
dest: /opt/
remote_src: yes
- name: Start application service
systemd:
name: "{{ app_name }}"
state: started
- name: Verify service health
uri:
url: "http://localhost/api/health"
status_code: 200
retries: 3
delay: 5
- name: Send rollback notification
mail:
to: [email protected]
subject: "Emergency rollback completed - {{ ansible_hostname }}"
body: "Server {{ ansible_hostname }} successfully rolled back to {{ latest_backup }}"
delegate_to: localhostConclusion – From Manual to Automated Ops
By following this article you have transformed a tedious manual deployment into a fast, reliable, and repeatable automation workflow. Key takeaways include massive efficiency gains, configuration consistency, full auditability, reusable playbooks, and reduced risk through automated rollbacks.
Beyond the basics, Ansible offers enterprise‑grade tools like Tower/AWX, a vast Galaxy role ecosystem, and deep integrations with Kubernetes, Docker, and cloud platforms, opening the door to true cloud‑native automation.
Next Steps
Practice immediately : Automate a simple repetitive task with Ansible.
Gradual rollout : Start in development, then expand to staging and production.
Continuous learning : Follow the official Ansible documentation and community best practices.
Share knowledge : Teach your team what you learned and grow together.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
MaGe Linux Operations
Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
