Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

Idempotence and drift

After this lesson: I can run a playbook twice and say what changed and why.

60 min lesson10 commandsAnsible lab: configure a machine with a playbook, in your browser

The server somebody fixed by hand

Idempotence means that running the same playbook again changes nothing once the machine is right. Drift is when the real machine stops matching its description. It matters because a playbook that is safe to run again can also find a hand edit and undo it.

At two in the morning a shop's notice says Open when it should say Closed. Somebody edited the file on the live server and told nobody.

By the end of this lesson

Note

I can run a playbook twice and say what changed and why.

Key words

WordWhat it meansExample
Idempotentrunning it again changes nothing more
Driftthe real thing no longer matches its description
Check modea run that reports changes without making them--check
Diffthe lines that would change--diff
Recapthe totals printed at the end of a run

The cells students fill in are left blank in this preview.

A playbook describes the end, not the steps

A task says how a file should be, not what to type. If the file is already right, the task does nothing and reports ok. If it is not, the task fixes it and reports changed.

So a good playbook is idempotent: the second run reports no changes.

Make a folder and go in

Command
mkdir -p ~/iac-demo/drift && cd ~/iac-demo/drift

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write a playbook with one task

Command
printf '%s\n' '- hosts: localhost' '  connection: local' '  gather_facts: false' '  vars:' '    hours: "Open 9 to 5"' '  tasks:' '    - name: write the notice' '      ansible.builtin.copy:' '        dest: ./notice.txt' '        content: "{{ hours }}\n"' '        mode: "0644"' > site.yml

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Run it the first time

Command
ansible-playbook -i localhost, site.yml | grep -E "changed="

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Run it again

Command
ansible-playbook -i localhost, site.yml | grep -E "changed="

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Drift is a change nobody wrote down

Drift is when the real thing stops matching the description, often because somebody edited it by hand. Idempotence is what makes drift easy to repair.

Run the playbook in check mode first. It reports what it would change, and --diff shows the lines, but nothing is written.

Edit the file by hand

Command
echo "Closed all week" > notice.txt; cat notice.txt

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Look first with check mode and a diff

Command
ansible-playbook -i localhost, site.yml --check --diff | grep -E "^[-+][^-+]|changed="

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Read the file again

Command
cat notice.txt

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Run it for real, then once more

Command
ansible-playbook -i localhost, site.yml | grep -E "changed="; cat notice.txt; ansible-playbook -i localhost, site.yml | grep -E "changed="

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

A shell task cannot see a change

The copy module compares the file with the wish. The shell module only runs a command, so it cannot know whether anything changed. It reports changed every time.

The Ansible lab refuses a playbook that is only shell commands for this reason.

Write the same job as a shell command

Command
printf '%s\n' '- hosts: localhost' '  connection: local' '  gather_facts: false' '  tasks:' '    - name: write with a shell' '      ansible.builtin.shell: echo "Open 9 to 5" > ./shell.txt' > shell.yml

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Run it twice

Command
ansible-playbook -i localhost, shell.yml | grep -E "changed="; ansible-playbook -i localhost, shell.yml | grep -E "changed="

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

A common mistake

Warning

Many people think changed=0 on the first run means the playbook did nothing useful. It means the machine already matched the wish. A run that always says changed hides drift, because a real change looks the same as a normal one.

OpenTofu finds drift by refreshing the state

With a real provider, tofu plan first asks the real service what exists, and shows any difference from the state. That is how it notices a hand edit.

The lab's terraform_data stores a value and nothing outside OpenTofu, so there is nothing to edit by hand. This lesson uses a file instead.

A convergence worked out in Python

Code: python
# Bring a machine to the wish and count what changed.
wish = {"hours": "Open 9 to 5", "phone": "0100"}
machine = {"hours": "Closed", "extra": "keep me"}
changed = 0
for name in wish:
    if name not in machine or machine[name] != wish[name]:
        machine[name] = wish[name]
        changed = changed + 1
print("changed =", changed)
# Run the loop again, and then look at changed.

Going further: change the wish

Run ansible-playbook -i localhost, site.yml -e '{"hours": "Open 10 to 4"}' | grep -E "changed=" and then cat notice.txt. Run the same command again.

Why does the first run change the file and the second not, and which variable won?

About this lab and its source: Ansible lab: configure a machine with a playbook, in your browser

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.