What are common Ansible anti-patterns and pitfalls to avoid?
Avoid common Ansible anti-patterns: shell overuse, non-idempotent tasks, hardcoded secrets, monolithic playbooks and wrong change reporting, with fixes.
Expected Interview Answer
Common Ansible anti-patterns include overusing the shell and command modules instead of idempotent native modules, ignoring idempotency so re-runs cause changes, hardcoding secrets and IPs, building unreadable monolithic playbooks, and abusing loops or facts in ways that make runs slow and fragile.
The biggest pitfall is treating Ansible like a shell-scripting engine: raw shell/command tasks are not idempotent and hide changes from Ansible's state reporting, so you lose check mode and drift detection. Other traps include committing plaintext passwords instead of using Vault, ignoring changed_when and failed_when so status is wrong, disabling gather_facts blindly or gathering when unneeded, deep-nesting includes, and using with_items patterns that make debugging painful. Avoiding these keeps playbooks predictable, safe to re-run, and readable.
- Idempotent modules keep re-runs safe and predictable
- Vault prevents secret leaks in source control
- Accurate changed/failed reporting enables drift detection
- Readable structure speeds debugging and onboarding
- Proper fact and loop use keeps runs fast
AI Mentor Explanation
A batter who slogs wildly at every ball might score once but gets out often and can't repeat it. Relying on raw shell commands in Ansible is that reckless slog: it may work once, but it isn't idempotent or reliable. Good technique — using native, repeatable modules — is like playing proper cricketing shots that you can reproduce innings after innings.
Step-by-Step Explanation
Step 1
Prefer native modules
Replace shell/command tasks with purpose-built modules; if you must use shell, add changed_when and creates guards.
Step 2
Guarantee idempotency
Ensure tasks converge on re-run; verify with a second run showing zero changes and use check mode.
Step 3
Never hardcode secrets or IPs
Move credentials into Vault and hosts into inventory or dynamic inventory instead of literals in tasks.
Step 4
Keep playbooks modular
Break monoliths into roles; avoid deep nested includes and giant with_items loops that are hard to debug.
Step 5
Report status accurately
Set changed_when and failed_when correctly so drift detection, check mode, and CI reflect real outcomes.
What Interviewer Expects
- Why shell/command overuse breaks idempotency
- How to make necessary shell tasks idempotent
- Dangers of hardcoded secrets and IPs
- Problems with monolithic playbooks and deep includes
- Correct use of changed_when and failed_when
Common Mistakes
- Using shell/command for everything instead of modules
- Assuming a task is idempotent without verifying re-runs
- Committing plaintext passwords instead of using Vault
- Writing one huge playbook with no roles
- Leaving changed_when/failed_when unset so status is wrong
Best Answer (HR Friendly)
“The main mistakes in Ansible are treating it like a shell script, leaving passwords in plain text, and building one giant unreadable file. The fix is to use Ansible's built-in modules that can safely run again and again, store secrets securely, and split work into tidy reusable pieces.”
Code Example
# Anti-pattern: non-idempotent, hides changes, plaintext secret
- name: Install nginx the bad way
ansible.builtin.shell: apt-get install -y nginx && echo pass=Secret123 > /etc/app.key
# Better: native module + Vault + guarded shell
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
- name: Write app key from Vault
ansible.builtin.copy:
content: "pass={{ vault_app_key }}"
dest: /etc/app.key
mode: '0600'
- name: Run one-off migration only once
ansible.builtin.shell: /opt/app/migrate.sh
args:
creates: /opt/app/.migrated
changed_when: falseFollow-up Questions
- Why is the shell module not idempotent by default?
- How do creates and changed_when make shell tasks safer?
- What are the risks of disabling gather_facts?
- How do you refactor a monolithic playbook into roles?
- How do you detect configuration drift with Ansible?
MCQ Practice
1. Why is overusing the shell module an anti-pattern?
shell runs commands blindly every time, hiding changes from Ansible and breaking idempotency and check mode.
2. Which argument makes a shell task skip if a file already exists?
The creates argument tells Ansible to skip the command when the given path already exists, adding idempotency.
3. What is the correct way to store passwords in Ansible?
Ansible Vault encrypts secrets so they are never exposed as plaintext in source control.
Flash Cards
Why avoid overusing shell/command? — They aren't idempotent by default and hide changes, breaking check mode and drift detection.
How to make a shell task idempotent? — Use guards like creates/removes and set changed_when/failed_when so Ansible reports state correctly.
Biggest secret-handling pitfall? — Committing plaintext credentials; encrypt them with Ansible Vault instead.
Problem with monolithic playbooks? — They're unreadable and hard to reuse or debug; refactor into modular roles.