- InfraCoffee
- Posts
- Automating Server Configuration with Ansible: A Secure, Scalable Guide for Linux DevOps
Automating Server Configuration with Ansible: A Secure, Scalable Guide for Linux DevOps
This article is all about getting started with Ansible on Linux to automate configuration management—think updating software, setting up users, or hardening security across multiple servers. It's a fresh topic: While we've covered CI/CD, containers, IaC, orchestration, and monitoring before, this focuses on agentless automation for consistent environments. Juniors will love it for building automation skills; seniors can dive into advanced playbooks and roles. We'll follow best practices like idempotency (running the same script multiple times without breaking things), version control, and security governance (e.g., SSH key management, secrets handling per OWASP and NIST). Let's go deep: Full commands, YAML examples, troubleshooting, and integrations. We'll use Ubuntu 22.04 LTS as the control node (your main computer) and a target node (a remote server or VM).
Why Ansible for Juniors? The DevOps Magic
Ansible is "agentless"—no extra software on targets, just SSH. It's YAML-based (human-readable), push-based (you initiate changes), and great for Linux pros. Benefits:
Consistency: Enforce the same config everywhere, reducing "works on my machine" issues.
Security: Bake in governance like encrypted vars and audit trails.
Scalability: Manage 1 or 1000 servers easily.
Learning Curve: Simple for juniors, powerful for complex workflows.
Best practice: Always use Git for playbooks. Security: Never store plaincommand secrets; use Ansible Vault.
Prerequisites: Setting Up Your Linux Control Node
Install Ubuntu 22.04 LTS: Download from ubuntu.com, install on a VM (VirtualBox) or bare metal. Why? LTS for stability and security patches.
Update and Secure the System:
command
sudo apt update && sudo apt full-upgrade -y
sudo apt install software-properties-common -y
Enable firewall:
command
sudo ufw allow OpenSSH
sudo ufw enable
Create a non-root user:
command
sudo adduser ansibleuser
sudo usermod -aG sudo ansibleuser
su - ansibleuser
Governance: Use sudo, enable automatic security updates (sudo apt install unattended-upgrades -y && sudo dpkg-reconfigure unattended-upgrades).
Install Ansible: Add PPA for latest (v2.16+ as of 2025):
command
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible -y
ansible --version # Confirm installation
Set Up SSH Keys for Agentless Access: Generate keys:
command
ssh-keygen -t ed25519 -C "ansible@control" -f ~/.ssh/ansible_key
Copy to target (replace user@target-ip):
command
ssh-copy-id -i ~/.ssh/ansible_key.pub user@target-ip
Test: ssh -i ~/.ssh/ansible_key user@target-ip. Security: Use passphrases on keys, restrict with ~/.ssh/config:
Host *
IdentityFile ~/.ssh/ansible_key
IdentitiesOnly yes
Governance: Rotate keys every 90 days, use bastion hosts for prod.
Create a Project Directory with Git:
command
mkdir ansible-project && cd ansible-project
git init
touch inventory.ini # For hosts
mkdir group_vars host_vars roles
Step 1: Define Your Inventory – Listing Your Robots
Inventory is a file listing targets.
Edit inventory.ini:
yaml file:
[webservers]
target1 ansible_host=192.168.1.10 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/ansible_key
[dbservers]
target2 ansible_host=192.168.1.11 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/ansible_key
[all:vars]
ansible_python_interpreter=/usr/bin/python3
Best practice: Use dynamic inventories (e.g., AWS plugin) for cloud.
Test Connectivity:
command
ansible all -i inventory.ini -m ping
Output: "pong" if successful. Troubleshoot: Check SSH, firewall (allow from control IP).
Security: Encrypt inventory if sensitive (ansible-vault encrypt inventory.ini).
Step 2: Write Your First Playbook – The Magic Instructions
Playbooks are YAML files with plays (tasks on hosts).
Create simple-playbook.yml:
yaml file:
---
- name: Update and install Nginx on webservers
hosts: webservers
become: true # Like sudo
tasks:
- name: Update apt cache
apt:
update_cache: yes
changed_when: false # Idempotency helper
- name: Install Nginx
apt:
name: nginx
state: latest
- name: Start Nginx
service:
name: nginx
state: started
enabled: true
Deep dive: become: true escalates privileges—use become_user for specific users. Modules like apt are idempotent (safe to rerun).
Run It:
command
ansible-playbook -i inventory.ini simple-playbook.yml
Verbose: Add -vvv for debugging.
Error Handling: Use ignore_errors: true sparingly. For retries: until: condition in loops.
Step 3: Secure Your Playbooks – Locking the Storybook
Ansible Vault for Secrets: Create vault file:
ansible-vault create group_vars/webservers/vars.yml
# Enter password, add: my_secret: "supersecret"
Use in playbook:
yaml file:
- name: Set secret config
copy:
content: "{{ my_secret }}"
dest: /etc/app/secret.conf
vars_files:
- group_vars/webservers/vars.yml
Run: ansible-playbook ... --vault-id @prompt or use --vault-id prod@prompt.
Governance: Store vault passwords in secure managers like AWS SSM. Audit with ansible-playbook --check.
Role-Based Structure: For reusability, create roles.
command
ansible-galaxy init roles/nginx
Edit roles/nginx/tasks/main.yml (similar to above tasks). In playbook:
yaml file:
- hosts: webservers
roles:
- nginx
Hardening Tasks: Add security modules.
Fail2Ban for brute-force protection:
yaml file:
- name: Install Fail2Ban
apt:
name: fail2ban
state: present
- name: Configure Fail2Ban
template:
src: templates/jail.local.j2
dest: /etc/fail2ban/jail.local
notify: Restart Fail2Ban
Template example (jail.local.j2): Enable SSH jail with maxretry=3.
UFW Firewall:
yaml file:
- name: Allow HTTP/HTTPS
ufw:
rule: allow
port: '{{ item }}'
loop: [80, 443]
Best practice: Use handlers for restarts:
yaml file:
handlers:
- name: Restart Fail2Ban
service:
name: fail2ban
state: restarted
Step 4: Advanced Automation – Scaling the Magic
Conditionals and Loops:
yaml file:
- name: Install packages
apt:
name: "{{ item }}"
state: present
loop: "{{ packages }}"
when: ansible_distribution == 'Ubuntu'
Vars: Define in group_vars.
Facts and Variables: Gather facts with setup module. Custom facts: Create /etc/ansible/facts.d/custom.fact on target.
Error Handling Deep Dive: Use block/rescue/always:
yaml file:
- block:
- name: Risky task
command: /bin/false
rescue:
- name: Fix it
debug:
msg: "Oops, fixed!"
always:
- name: Cleanup
debug:
msg: "Always runs"
Integrations:
With Git: Commit playbooks, use pre-commit hooks for linting (ansible-lint install via pip).
CI/CD: In Jenkins/GitLab, run ansible-playbook in pipelines.
Cloud: Use ec2 module for dynamic provisioning.
Monitoring: Add tasks to install Node Exporter.
Performance Tweaks: Forks in ansible.cfg ([defaults] forks=50) for parallel runs. Pipelining for speed.
Security Audits: Run ansible-lint playbook.yml. Use check_mode: true for dry runs. Compliance: Align with CIS benchmarks by automating hardening playbooks.
Step 5: Testing and Troubleshooting – Fixing Robot Glitches
Dry Run: ansible-playbook ... --check --diff
Debug: ansible-playbook ... -vvv, or use debug module.
Common Issues:
SSH fails: Check keys, known_hosts.
Module errors: Ensure Python on target.
Vault: Forgot password? Recreate, but backup first.
Testing: Use Molecule (pip install molecule) for role tests.
Wrapping Up: Your Ansible Adventure Begins
You've now got a secure Ansible setup to automate Linux configs like a pro! Start small with one target, scale to fleets. Remember, DevOps is about people too—document playbooks in README.md. For governance, review changes via pull requests.
Practice by adding Let's Encrypt for SSL in your Nginx role. Happy automating, little wizard!