Ansible: playbooks & inventory
Agentless config over SSH.
Where Terraform provisions infrastructure, Ansible configures it — installing packages, editing config files, starting services, running commands across many machines. Its defining traits: it is agentless (it connects over plain SSH, so there is nothing to install on the targets) and it is written in readable YAML. You describe the desired configuration in a playbook, point Ansible at an inventory of hosts, and it makes each host match. It is the standard tool for "set up what runs on these servers."
- name: Configure web servershosts: webservers # which group from the inventorybecome: true # run with sudotasks:- name: Install nginxapt: { name: nginx, state: present } # a module: desired state- name: Write the site configcopy: { src: site.conf, dest: /etc/nginx/conf.d/site.conf }notify: reload nginx # trigger a handler if this changed- name: Ensure nginx is runningservice: { name: nginx, state: started, enabled: true }handlers:- name: reload nginxservice: { name: nginx, state: reloaded }
Inventory: which hosts
The inventory lists the machines Ansible manages, organized into groups (webservers, dbservers) so a playbook can target a whole role at once. It can be a simple static file or generated dynamically from your cloud (a "dynamic inventory" that asks AWS which instances exist). Grouping is the key idea: you write a playbook for "webservers" and Ansible runs it against every host in that group, which is how one playbook configures a fleet.
[webservers]web-01.acme.internalweb-02.acme.internal[dbservers]db-01.acme.internal[all:vars]ansible_user=deploy # connect as this user over SSH
Modules and idempotence
Each task uses a module (apt, copy, service, template, ...) that declares a desired state, and — crucially — modules are idempotent: apt: name=nginx state=present installs nginx only if it is not already there, and reports "changed" only when it actually did something. So running a playbook repeatedly is safe and converges the host to the desired state, the same declarative-in-effect behavior as Terraform. The "changed" vs "ok" count in Ansible’s output tells you exactly what it altered on this run.
$ ansible-playbook -i inventory.ini playbook.ymlPLAY [Configure web servers] ****TASK [Install nginx] ****changed: [web-01] # nginx was installed (a change was made)ok: [web-02] # nginx already present (no change — idempotent)PLAY RECAP ****web-01 : ok=4 changed=2 web-02 : ok=4 changed=0