Pernah nggak, harus update package di lima server? SSH ke server pertama, apt update, apt upgrade, keluar. SSH ke server kedua, ulangi lagi. Begitu sampai server kelima, sudah lupa tadi server ketiga sudah di-restart nginx-nya atau belum. 😅
Kalau servernya cuma satu, cara manual masih oke. Tapi begitu jumlahnya nambah, capeknya nambah, dan yang paling bahaya: hasilnya nggak konsisten. Di sinilah Ansible jadi penyelamat.
Di tulisan ini saya ceritain pengalaman ngoprek Ansible di lab berisi satu control node dan tiga server Ubuntu 24.04. Semua perintah dan playbook di sini beneran saya jalankan, termasuk error-error yang muncul di tengah jalan. Jadi kalau kamu nemu error yang sama, semoga solusinya ada di sini juga.
📌 Buat siapa tulisan ini? Kamu yang sudah paham dasar Linux dan jaringan (SSH, apt, systemctl), tapi belum pernah pakai Ansible sama sekali.
Ansible itu apa sih?
Singkatnya: Ansible itu alat buat menjalankan tugas administrasi ke banyak server sekaligus, dari satu tempat.
Yang bikin Ansible ramah buat pemula:
- Nggak perlu install agent di server target. Cukup SSH dan Python, yang biasanya sudah ada di Ubuntu.
- Tugasnya ditulis dalam YAML, yang relatif gampang dibaca.
- Tugas yang sama bisa dijalankan berkali-kali dengan hasil yang sama (nanti ada istilahnya: idempotent).
- Playbook-nya sekaligus jadi dokumentasi. Nggak perlu lagi nyatet “kemarin server ini saya apain aja ya?”
Istilah yang bakal sering muncul
Sebelum praktik, kenalan dulu sama istilahnya:
| Istilah | Artinya |
|---|---|
| Control node | Komputer tempat kamu menjalankan Ansible |
| Managed node | Server yang dikelola |
| Inventory | Daftar server, bisa dikelompokkan |
| Playbook | File YAML berisi urutan tugas |
| Task | Satu langkah di dalam playbook |
| Module | “Alat” untuk tugas spesifik, misalnya apt, service, copy, template |
| Handler | Task yang cuma jalan kalau ada task lain yang bikin perubahan |
| Become | Jalanin tugas sebagai root (mirip sudo) |
| Idempotent | Kalau kondisi sudah sesuai, task nggak ngubah apa-apa |
Tenang, nggak perlu hafal sekarang. Nanti kelihatan sendiri waktu praktik.
Lab yang saya pakai
| Peran | OS | Python | Keterangan |
|---|---|---|---|
| Control node | Ubuntu 24.04.4 LTS | 3.11.15 | Ansible core 2.16.3 |
| Managed node 1 | Ubuntu 24.04.5 LTS | 3.12.3 | user dengan sudo |
| Managed node 2 | Ubuntu 24.04.5 LTS | 3.12.3 | user dengan sudo |
| Managed node 3 | Ubuntu 24.04.5 LTS | 3.12.3 | user dengan sudo |
Kalau kamu belum punya server, bikin aja VM di VirtualBox. Caranya pernah saya tulis di Konfigurasi SSH di Mesin Virtual Linux dan Panduan Port Forwarding VirtualBox.
Install Ansible
Di control node, cukup install dari repository Ubuntu:
sudo apt update
sudo apt install ansible -y
ansible --version
Di lab saya hasilnya ansible [core 2.16.3].
⚠️ Versi Ansible di repo Ubuntu bisa berubah dari waktu ke waktu. Cek versi di mesin kamu sendiri, karena beberapa parameter module bisa beda antar versi.
Siapin akses SSH dulu
Ansible ngobrol sama server lewat SSH. Biar nggak ditanya password terus, pakai SSH key.
Bikin key khusus buat lab:
ssh-keygen -t ed25519 -f lab_key -N ''
Kirim public key-nya ke ketiga server:
ssh-copy-id -i lab_key.pub engineer@10.45.1.200
ssh-copy-id -i lab_key.pub engineer@10.45.1.201
ssh-copy-id -i lab_key.pub engineer@10.45.1.202
Tes, harusnya langsung masuk tanpa ditanya password:
ssh -i lab_key engineer@10.45.1.200 hostname
Dengan cara ini, password nggak perlu ditulis di mana pun. Aman dan praktis.
Bikin inventory
Inventory itu daftar server yang mau diurus. Bikin file bernama hosts:
[webserver]
node1 ansible_host=10.45.1.200 ansible_user=engineer
node2 ansible_host=10.45.1.201 ansible_user=engineer
node3 ansible_host=10.45.1.202 ansible_user=engineer
[database]
node3 ansible_host=10.45.1.202 ansible_user=engineer
Perhatikan node3 muncul di dua grup sekaligus, webserver dan database. Itu boleh kok.
Cek apakah Ansible membaca inventory-nya dengan benar:
ansible-inventory --graph
@all:
|--@ungrouped:
|--@webserver:
| |--node1
| |--node2
| |--node3
|--@database:
| |--node3
Bikin ansible.cfg biar perintahnya nggak panjang
Daripada tiap kali nulis -i hosts -u engineer --private-key ..., simpan semua setting di ansible.cfg di folder kerja:
[defaults]
inventory = ./hosts
host_key_checking = False
remote_user = engineer
private_key_file = ./lab_key
interpreter_python = auto_silent
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
⚠️
host_key_checking = Falsecuma biar lab nggak tersendat konfirmasi fingerprint. Jangan dipakai di produksi.
Tes koneksi: ping-pong
Sebelum ngapa-ngapain, pastikan Ansible bisa menjangkau semua server:
ansible all -m ping
node3 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
node1 | SUCCESS => { ... "ping": "pong" }
node2 | SUCCESS => { ... "ping": "pong" }
Ketiganya jawab pong. Artinya SSH dan Python di server sudah siap. 🎉
Playbook pertama: update package
Bikin file update_servers.yml:
---
- name: Update dan upgrade package di server web
hosts: webserver
become: yes
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
register: apt_cache
retries: 5
delay: 5
until: apt_cache is not failed
- name: Upgrade package saat ini
apt:
upgrade: dist
autoremove: yes
Jalankan:
ansible-playbook update_servers.yml
TASK [Gathering Facts] ***
ok: [node1] ok: [node3] ok: [node2]
TASK [Update apt cache] ***
changed: [node1] changed: [node3] changed: [node2]
TASK [Upgrade package saat ini] ***
ok: [node2] ok: [node3] changed: [node1]
PLAY RECAP ***
node1 : ok=3 changed=2 unreachable=0 failed=0
node2 : ok=3 changed=1 unreachable=0 failed=0
node3 : ok=3 changed=1 unreachable=0 failed=0
Bagian yang paling penting dibaca adalah PLAY RECAP. Di situ kelihatan per server: berapa task yang aman (ok), berapa yang ngubah sesuatu (changed), dan ada yang gagal atau nggak (failed).
Drama #1: gagal acak, kadang node1, kadang node2
Eh tapi, playbook di atas itu versi yang sudah diperbaiki. Versi awal saya cuma pakai update_cache: yes tanpa tambahan apa-apa. Hasilnya? Gagal secara acak:
TASK [Update apt cache] ***
changed: [node1]
fatal: [node2]: FAILED! => {"changed": false, "msg": "Failed to update apt cache: unknown reason"}
Saya jalankan lima kali berturut-turut, yang gagal ganti-ganti node. Pesannya juga nggak membantu: unknown reason. 🙃
Ternyata penyebabnya unattended-upgrades, layanan bawaan Ubuntu yang diam-diam update di belakang layar. Kalau dia lagi pegang lock apt pas Ansible jalan, ya tabrakan.
Solusinya dua lapis:
cache_valid_time: 3600. Kalau cache apt umurnya masih di bawah satu jam, Ansible nggak usah jalaninapt-get updatelagi. Lebih jarang tabrakan.retries+until. Kalau tetap gagal, coba lagi sampai 5 kali dengan jeda 5 detik.
Fun fact dari ansible-doc apt: module apt sebenarnya sudah punya retry bawaan buat update cache.
update_cache_retries default: 5
update_cache_retry_max_delay default: 12
cache_valid_time default: 0
Perhatikan cache_valid_time default-nya 0. Artinya kalau nggak diatur, cache selalu di-update.
Kenapa update_cache selalu changed?
Ini sempat bikin saya bingung. Task update_cache: yes selalu lapor changed, padahal nggak ada package baru. Itu karena apt-get update memang selalu memperbarui daftar package.
Jadi tenang, changed di situ bukan tanda masalah. Task ini baru bisa ok (nggak berubah) kalau cache dianggap masih segar lewat cache_valid_time.
Playbook kedua: install nginx
Bikin setup_nginx.yml:
---
- name: Install dan kelola Nginx
hosts: webserver
become: yes
tasks:
- name: Install nginx
apt:
name: nginx
state: present
update_cache: yes
cache_valid_time: 3600
register: apt_nginx
retries: 5
delay: 5
until: apt_nginx is not failed
- name: Pastikan nginx berjalan
service:
name: nginx
state: started
enabled: yes
ansible-playbook setup_nginx.yml
Yang bikin playbook ini aman dijalankan berulang:
state: present→ kalau nginx sudah ada, nggak diinstall ulangstate: started→ kalau sudah jalan, nggak di-restartenabled: yes→ nginx tetap nyala setelah server reboot
Jangan cuma percaya output Ansible, cek langsung ke server:
ansible webserver -m command -a "systemctl is-active nginx"
ansible webserver -m command -a "systemctl is-enabled nginx"
node1 | rc=0 >> active
node2 | rc=0 >> active
node3 | rc=0 >> active
node1 | rc=0 >> enabled
node2 | rc=0 >> enabled
node3 | rc=0 >> enabled
Drama #2: copy vs template, kelihatan mirip tapi beda jauh
Ini jebakan yang saya sendiri kena. 😅
Saya mau setiap server punya halaman index.html yang nampilin nama node-nya. Awalnya saya pakai module copy:
- name: Copy halaman index
copy:
src: files/index.html
dest: /var/www/html/index.html
owner: www-data
group: www-data
mode: '0644'
Isi files/index.html:
<p>Node: {{ inventory_hostname }}</p>
Halamannya kebuka, HTTP 200, aman. Tapi yang tampil di browser:
Node: {{ inventory_hostname }}
Lho, variabelnya nggak diganti! Ternyata copy itu memindahkan file apa adanya, nggak memproses Jinja2.
Solusinya: pakai template
Untuk file yang berisi variabel, pakai module template. Simpan file-nya dengan akhiran .j2, misalnya templates/index.html.j2:
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<title>Lab Ansible</title>
</head>
<body>
<h1>Halaman ini dikelola otomatis oleh Ansible</h1>
<p>Node: {{ inventory_hostname }}</p>
<p>Hostname: {{ ansible_hostname }}</p>
<p>Distribusi: {{ ansible_distribution }} {{ ansible_distribution_version }}</p>
</body>
</html>
Playbook deploy_index.yml:
---
- name: Deploy halaman index dari template Jinja2
hosts: webserver
become: yes
tasks:
- name: Render template index.html
template:
src: templates/index.html.j2
dest: /var/www/html/index.html
owner: www-data
group: www-data
mode: '0644'
notify: reload nginx
handlers:
- name: reload nginx
service:
name: nginx
state: reloaded
Hasil di node2:
<p>Node: node2</p>
<p>Hostname: dwp-mng-2</p>
<p>Distribusi: Ubuntu 24.04</p>
Nah, sekarang tiap server dapat nilai sesuai dirinya sendiri. ✅
| Module | Proses Jinja2? | Cocok untuk |
|---|---|---|
copy | ❌ Nggak | File statis, sertifikat, binary |
template | ✅ Ya | File konfigurasi atau halaman yang ada variabelnya |
Handler: restart cuma kalau perlu
Di playbook tadi ada notify: reload nginx. Itu manggil handler.
Handler cuma jalan kalau task yang manggilnya lapor changed. Jadi nginx nggak di-reload tiap kali playbook dijalankan, cuma kalau halamannya beneran berubah.
Jalan pertama (ada perubahan, handler ikut jalan):
TASK [Render template index.html] ***
changed: [node1] changed: [node2] changed: [node3]
RUNNING HANDLER [reload nginx] ***
changed: [node1] changed: [node2] changed: [node3]
Jalan kedua (nggak ada perubahan, handler dilewati):
TASK [Render template index.html] ***
ok: [node1] ok: [node2] ok: [node3]
PLAY RECAP ***
node1 : ok=2 changed=0 failed=0
node2 : ok=2 changed=0 failed=0
node3 : ok=2 changed=0 failed=0
changed=0 di ketiga node, itulah bukti idempotent yang sebenarnya.
Ubah satu baris config dengan lineinfile
Kadang kita nggak mau kirim file utuh, cuma mau ganti satu baris. Misalnya ganti server_name _; jadi server_name lab.local; di site default nginx. Untuk ini ada lineinfile.
Playbook configure_server_name.yml:
---
- name: Konfigurasi server_name pada nginx
hosts: webserver
become: yes
tasks:
- name: Pastikan server_name diatur pada site default
lineinfile:
path: /etc/nginx/sites-available/default
regexp: '^\s*server_name\s+'
line: ' server_name lab.local;'
state: present
notify: validate nginx config
handlers:
- name: validate nginx config
command: nginx -t
register: nginx_test
failed_when: nginx_test.rc != 0
regexp→ baris mana yang dicari (yang diawaliserver_name)line→ isi penggantinyastate: present→ pastikan baris ini ada
Intip dulu pakai --check --diff
Sebelum ngubah config beneran, biasakan lihat dulu apa yang bakal berubah:
ansible-playbook configure_server_name.yml --check --diff
TASK [Pastikan server_name diatur pada site default] ***
--- before: /etc/nginx/sites-available/default (content)
+++ after: /etc/nginx/sites-available/default (content)
@@ -43,7 +43,7 @@
index index.html index.htm index.nginx-debian.html;
- server_name _;
+ server_name lab.local;
--check itu mode simulasi, nggak ada yang diubah. --diff nunjukin perbandingan sebelum dan sesudah. Menariknya, handler-nya ke-trigger tapi dilewati:
RUNNING HANDLER [validate nginx config] ***
skipping: [node1]
skipping: [node2]
skipping: [node3]
Masuk akal, file-nya kan belum beneran berubah, jadi nginx -t juga nggak perlu jalan.
Jalankan beneran
ansible-playbook configure_server_name.yml
TASK [Pastikan server_name diatur pada site default] ***
changed: [node1]
changed: [node3]
changed: [node2]
RUNNING HANDLER [validate nginx config] ***
changed: [node1]
changed: [node2]
changed: [node3]
PLAY RECAP ***
node1 : ok=3 changed=2 unreachable=0 failed=0
node2 : ok=3 changed=2 unreachable=0 failed=0
node3 : ok=3 changed=2 unreachable=0 failed=0
Run kedua? changed=0 di semua node, handler nggak jalan. Idempotent lagi. ✅
Cek langsung isi file-nya:
ansible webserver -m command -a "grep -n server_name /etc/nginx/sites-available/default"
node1 | CHANGED | rc=0 >>
46: server_name lab.local;
83:# server_name example.com;
Baris 46 berubah, baris 83 yang berupa komentar tetap aman. regexp-nya tepat sasaran.
Drama #3: nginx -t sukses, tapi kok ada warning?
Setelah perubahan itu, nginx -t ngeluarin ini:
[warn] conflicting server name "lab.local" on 0.0.0.0:80, ignored
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Ternyata di /etc/nginx/sites-enabled/ ada dua file yang sama-sama pakai server_name lab.local: default dan lab-site.conf sisa latihan saya sebelumnya. 🤦
Pelajarannya: nginx -t bisa sukses tapi tetap kasih warning. Config-nya valid, tapi salah satu diabaikan nginx. Kalau ketemu kasus begini, cek dulu:
ls -l /etc/nginx/sites-enabled/
Saya bersihkan vhost latihan itu pakai module file:
- name: Hapus symlink sites-enabled/lab-site.conf
file:
path: /etc/nginx/sites-enabled/lab-site.conf
state: absent
Warning hilang, dan tes HTTP-nya:
--- 10.45.1.200 ---
default Host -> HTTP 200
Host lab.local -> HTTP 200
<p>Node: node1</p>
<p>Hostname: dwp-mng-1</p>
Request dengan header Host: lab.local juga jalan, berarti server_name baru sudah berfungsi.
Ringkasan perilaku lineinfile:
| Kondisi | Hasil |
|---|---|
| Baris nggak ketemu | Ditambahkan di akhir file |
| Baris ketemu, isinya beda | Diganti |
| Baris ketemu, isinya sama | Nggak ada perubahan (ok) |
state: absent | Barisnya dihapus |
Kalau mau ubah banyak baris sekaligus, lineinfile kurang cocok. Pakai template atau blockinfile.
Tes dari control node
curl -I http://10.45.1.200/
HTTP/1.1 200 OK
Ketiga node jawab HTTP 200, dan masing-masing nampilin identitasnya sendiri dari hasil render template tadi.
Dua opsi penyelamat: --limit dan --check
Begitu servernya makin banyak, dua opsi ini wajib dikenal.
Coba di satu server dulu:
ansible-playbook deploy_index.yml --limit node1
Simulasi tanpa ngubah apa pun:
ansible-playbook deploy_index.yml --check
Atau gabungin keduanya:
ansible-playbook deploy_index.yml --limit node1 --check
Bayangin punya 20 server dan ternyata playbook-nya salah. Mending ketahuan di satu server, kan?
Troubleshooting: error yang beneran saya temui
Semua di bawah ini muncul waktu ngoprek lab ini, bukan contoh karangan.
1. E: dpkg was interrupted
fatal: [node1]: FAILED! => {
"msg": "'/usr/bin/apt-get dist-upgrade --auto-remove' failed:
E: dpkg was interrupted, you must manually run
'sudo dpkg --configure -a' to correct the problem.",
"rc": 100
}
Artinya ada instalasi package yang nggak selesai. Sering kejadian di VM yang baru dibuat atau sempat kehabisan resource. Solusinya persis seperti saran di pesan error:
sudo dpkg --configure -a
sudo dpkg --audit
Di lab saya, dpkg --audit masih nunjukin apport bermasalah, jadi saya install ulang:
sudo apt-get install --reinstall -y apport
Setelah itu dpkg --audit bersih dan playbook jalan normal.
💡 Tips: sebelum jalanin playbook pertama di server baru, cek dulu
sudo dpkg --audit. Lebih baik ketahuan di awal.
2. Failed to update apt cache: unknown reason
Ini Drama #1 tadi. Biasanya karena lock apt lagi dipegang unattended-upgrades. Cek prosesnya:
sudo fuser -v /var/lib/dpkg/lock-frontend
pgrep -a "apt|dpkg|unattended"
Solusinya ya cache_valid_time + retries/until seperti di playbook pertama.
3. UNREACHABLE: Ansible nggak bisa masuk
fatal: [node3]: UNREACHABLE! => {
"msg": "Failed to connect to the host via ssh:
kex_exchange_identification: read: Connection reset by peer"
}
Urutan ngeceknya:
- Server hidup?
ping -c2 10.45.1.202 - SSH bisa?
ssh -i lab_key engineer@10.45.1.202 hostname - Kalau SSH ditolak, cek status layanan SSH di server itu
- Kalau server baru reboot, tunggu sampai SSH-nya benar-benar siap
Di kasus saya, ternyata node3 lagi reboot. Begitu nyala lagi, playbook jalan normal tanpa diubah apa-apa.
4. Variabel muncul mentah {{ ... }}
Hampir pasti kamu pakai copy untuk file yang ada variabelnya. Ganti ke template. (Lihat Drama #2.)
5. Error YAML
ERROR! Syntax Error while loading YAML
YAML itu sensitif banget sama indentasi. Cek:
- Jangan campur tab dan spasi
- Setiap item di bawah
tasks:diawali-yang sejajar - Baris pertama
---
Cek sintaks tanpa menjalankan playbook:
ansible-playbook update_servers.yml --syntax-check
6. Playbook nge-hang lama pas update package
Ini yang sering disangka “Ansible-nya lemot”, padahal masalahnya di server. Gejalanya: playbook nggak selesai-selesai, nggak ada error. Kalau dipaksa berhenti, run berikutnya malah kena Could not get lock.
Kuncinya: pisahkan masalah Ansible dari masalah server.
# 1. Ada proses lain yang pegang lock apt?
ansible node1 -m shell -a "sudo fuser -v /var/lib/apt/lists/lock; pgrep -a 'apt|dpkg'"
# 2. Server bisa nyampe ke repo package?
ansible node1 -m shell -a "getent hosts archive.ubuntu.com; curl -s -o /dev/null -w '%{http_code}\n' --max-time 10 http://archive.ubuntu.com/ubuntu/dists/noble/InRelease"
Terus coba jalankan langsung di server, tanpa Ansible:
sudo apt-get update
Kalau ini juga nge-hang, berarti bukan salah playbook. Mulai cek dari koneksi server ke repository.
Kalau playbook sudah terlanjur dihentikan paksa, bisa ada proses yatim yang masih pegang lock:
# lihat proses sisa
ansible webserver -m shell -a "pgrep -af '[A]nsiballZ'"
# kalau sudah yakin itu proses yatim, bersihkan
ansible webserver -m shell -a "pkill -9 -f '[A]nsiballZ_apt.py'" --become
⚠️ Jalankan
pkillhanya kalau kamu yakin nggak ada playbook lain yang lagi jalan. Trik[A]di pola itu supayapgrep/pkillnggak ikut nyocokin proses shell-nya sendiri.
7. Error-nya terlalu singkat? Pakai -vvv
ansible-playbook setup_nginx.yml -vvv
Output-nya jadi jauh lebih panjang, termasuk perintah asli yang dikirim ke server. Sering kali penyebabnya langsung kelihatan dari situ.
Struktur folder yang saya pakai
project-ansible/
├── ansible.cfg
├── hosts
├── update_servers.yml
├── setup_nginx.yml
├── deploy_index.yml
├── configure_server_name.yml
├── files/
│ └── index.html
└── templates/
└── index.html.j2
files/ buat file statis, templates/ buat file yang ada variabelnya. Dipisah begini, playbook jadi lebih gampang dibaca.
Status akhir lab
| Yang diuji | Status |
|---|---|
ansible all -m ping ke 3 node | ✅ |
| Inventory dengan grup | ✅ |
update_servers.yml | ✅ (setelah dpkg dibereskan) |
setup_nginx.yml (install + service) | ✅ |
copy untuk file statis | ✅ |
template render variabel | ✅ |
lineinfile ubah server_name | ✅ |
file hapus symlink vhost | ✅ |
Handler reload nginx dan nginx -t | ✅ cuma jalan saat ada perubahan |
--check --diff, --limit | ✅ |
HTTP test (termasuk Host: lab.local) | ✅ HTTP 200 di 3 node |
Idempotent (changed=0 di run ulang) | ✅ update_servers, deploy_index, configure_server_name |
Idempotent setup_nginx.yml | ⏳ belum sempat diverifikasi |
Soal yang terakhir, jujur aja: waktu mau run ulang setup_nginx.yml, server lab lagi nggak bisa nyampe ke repository package (kasus troubleshooting nomor 6). Jadi statusnya belum diverifikasi, bukan gagal. Nanti saya update kalau sudah dites ulang.
Pelajaran yang saya dapat
copydantemplateitu beda. Kelihatan mirip, tapi cumatemplateyang memproses variabel.changedbelum tentu masalah.update_cachememang selalu laporchangedkalau nggak pakaicache_valid_time.- Error di Ansible belum tentu salah playbook. Coba perintah yang sama manual di server. Kalau sama-sama gagal, masalahnya di server atau jaringan.
nginx -tsukses ≠ bersih. Baca juga warning-nya.--check --diffdan--limititu sabuk pengaman. Biasakan dipakai sebelum ngubah server penting.- Cek
dpkg --auditdi server baru. Hemat waktu banget.
Langkah berikutnya
Kalau sudah nyaman sama dasar-dasar ini, topik lanjutan yang layak dioprek:
- Role, biar playbook bisa dipecah dan dipakai ulang
- Ansible Vault, buat nyimpan data sensitif seperti password
- Integrasi ke CI/CD, biar playbook jalan otomatis tiap ada perubahan
Oh iya, simpan playbook dan inventory kamu di Git. Riwayat perubahannya kecatat rapi dan gampang kalau mau kerja bareng tim.
Seru sih. Begitu ketiga node jawab changed=0 di run kedua, rasanya kayak ngurus satu server padahal ada tiga. 😄
Kalau kamu nyoba dan mentok di error yang nggak ada di sini, kabari saya lewat halaman Contact. Siapa tahu bisa jadi bahan tulisan berikutnya.
Sources
[1] Ansible Documentation: Getting started with Ansible
[2] ansible.builtin.apt module