Kuasai idempotency Ansible untuk menulis playbook yang aman dijalankan berkali-kali tanpa efek samping. Pelajari cara membangun otomasi tingkat produksi yang mencegah configuration drift dan chaos infrastruktur.

Idempotency adalah konsep paling penting dalam Ansible, namun sering kali disalahpahami. Operasi yang idempotent menghasilkan hasil yang sama baik dijalankan sekali atau seratus kali. Jalankan sekali, sistem mencapai state yang diinginkan. Jalankan lagi, tidak ada yang berubah. Jalankan ketiga kalinya, tetap tidak ada yang berubah.
Ini adalah kebalikan dari script imperatif. Script bash yang menjalankan apt-get install nginx dua kali akan gagal pada kali kedua karena nginx sudah terinstal. Playbook Ansible yang menginstal nginx adalah idempotent—ia memeriksa apakah nginx sudah terinstal, dan jika ya, tidak melakukan apa-apa.
Idempotency adalah yang membuat Ansible aman untuk produksi. Itulah mengapa Anda dapat menjadwalkan playbook untuk berjalan setiap jam tanpa takut merusak infrastruktur Anda. Itulah mengapa Anda dapat menjalankan kembali deployment yang gagal tanpa perlu membersihkan perubahan parsial secara manual.
Panduan ini mencakup cara menulis kode Ansible yang idempotent dan mengapa hal itu penting.
Script shell tradisional bersifat imperatif. Mereka mendeskripsikan urutan perintah yang akan dieksekusi:
#!/bin/bash
apt-get update
apt-get install -y nginx
systemctl start nginx
echo "server_name example.com;" >> /etc/nginx/nginx.conf
systemctl restart nginxJalankan script ini sekali, dan itu berhasil. Jalankan dua kali:
apt-get install nginx gagal karena nginx sudah terinstalecho menambahkan baris yang sama lagi, menduplikasi konfigurasisystemctl restart mungkin gagal jika nginx sudah berjalanScript tidak dirancang untuk dijalankan berkali-kali. Ia mengasumsikan slate yang bersih.
Operasi yang idempotent memeriksa state saat ini sebelum membuat perubahan:
---
- name: Configure nginx
hosts: webservers
tasks:
- name: Install nginx
apt:
name: nginx
state: present
- name: Start nginx
systemd:
name: nginx
state: started
enabled: yes
- name: Configure nginx
lineinfile:
path: /etc/nginx/nginx.conf
line: "server_name example.com;"
state: present
- name: Reload nginx
systemd:
name: nginx
state: reloadedJalankan playbook ini sekali, dan nginx terinstal dan dikonfigurasi. Jalankan lagi, dan Ansible memeriksa:
Tidak ada yang rusak. Tidak ada yang terduplikasi. Sistem mencapai state yang diinginkan dan tetap di sana.
Idempotency memungkinkan beberapa praktik kritis:
Otomasi terjadwal. Jalankan playbook Anda setiap jam untuk mendeteksi dan memperbaiki configuration drift. Jika sysadmin secara manual mengedit file konfigurasi, playbook run berikutnya memperbaikinya.
Retry yang aman. Jika playbook gagal di tengah jalan, jalankan kembali tanpa khawatir perubahan parsial merusak sesuatu.
Infrastructure as Code. Playbook Anda menjadi sumber kebenaran. Infrastruktur aktual harus sesuai dengan apa yang dideskripsikan playbook Anda.
Disaster recovery. Setelah outage server, jalankan kembali playbook Anda untuk memulihkan konfigurasi yang tepat tanpa intervensi manual.
Sebagian besar modul Ansible dirancang idempotent. Mereka memeriksa state saat ini dan hanya membuat perubahan jika diperlukan. Modul apt adalah idempotent:
- name: Install nginx
apt:
name: nginx
state: presentJalankan ini sekali, nginx terinstal. Jalankan lagi, Ansible memeriksa apakah nginx terinstal, melihatnya sudah ada, dan tidak melakukan apa-apa. Task melaporkan changed: false.
Beberapa modul melakukan tindakan yang tidak dapat dibuat idempotent. Modul shell dan command mengeksekusi perintah arbitrer tanpa memeriksa state:
- name: Create a file
shell: touch /tmp/myfile.txtJalankan ini sekali, file dibuat. Jalankan lagi, perintah berjalan lagi, tetapi file sudah ada jadi tidak ada yang terlihat berubah. Namun, Ansible melaporkan changed: true setiap kali karena tidak dapat mengetahui apakah perintah memiliki efek samping.
Ini berbahaya. Jika Anda menggunakan shell untuk restart service, menjalankan playbook dua kali me-restart service dua kali, berpotensi menyebabkan downtime.
Gunakan parameter creates, removes, atau changed_when untuk membuat modul non-idempotent berperilaku idempotent:
- name: Generate SSL certificate
shell: openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/key.pem -out /etc/ssl/certs/cert.pem
args:
creates: /etc/ssl/certs/cert.pemParameter creates memberitahu Ansible: "Hanya jalankan perintah ini jika /etc/ssl/certs/cert.pem tidak ada." Jika file ada, Ansible melewati perintah dan melaporkan changed: false.
Demikian pula, gunakan removes untuk melewati perintah jika file ada:
- name: Clean up old logs
shell: rm -rf /var/log/old/*
args:
removes: /var/log/oldUntuk perintah di mana Anda tidak dapat menggunakan creates atau removes, gunakan changed_when:
- name: Check if service is running
shell: systemctl is-active nginx
register: nginx_status
changed_when: falsechanged_when: false memberitahu Ansible bahwa perintah ini tidak pernah membuat perubahan, jadi selalu laporkan changed: false.
Pola umum adalah me-restart service setelah perubahan konfigurasi:
- name: Update nginx config
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
- name: Restart nginx
systemd:
name: nginx
state: restartedIni me-restart nginx setiap kali playbook berjalan, bahkan jika konfigurasi tidak berubah. Ini menyebabkan downtime yang tidak perlu.
Handler adalah task yang hanya berjalan jika task lain melaporkan changed: true. Mereka sempurna untuk me-restart service:
- name: Configure nginx
hosts: webservers
tasks:
- name: Update nginx config
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
notify: restart nginx
handlers:
- name: restart nginx
systemd:
name: nginx
state: restartedSekarang nginx hanya di-restart jika file konfigurasi benar-benar berubah. Jika Anda menjalankan playbook lagi dan file config identik, task copy melaporkan changed: false, dan handler tidak pernah berjalan.
Handler berjalan di akhir play, setelah semua task selesai. Ini mencegah multiple restart jika multiple task memberitahu handler yang sama:
- name: Configure nginx
hosts: webservers
tasks:
- name: Update main config
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
notify: restart nginx
- name: Update SSL config
copy:
src: ssl.conf
dest: /etc/nginx/conf.d/ssl.conf
notify: restart nginx
- name: Update security headers
copy:
src: security.conf
dest: /etc/nginx/conf.d/security.conf
notify: restart nginx
handlers:
- name: restart nginx
systemd:
name: nginx
state: restartedMeskipun tiga task memberitahu handler, nginx di-restart hanya sekali, di akhir play. Ini lebih efisien dan lebih aman daripada restart setelah setiap perubahan.
when untuk Task KondisionalKlausa when memungkinkan Anda menjalankan task hanya dalam kondisi tertentu:
- name: Install nginx on Debian
apt:
name: nginx
state: present
when: ansible_os_family == "Debian"
- name: Install nginx on RedHat
yum:
name: nginx
state: present
when: ansible_os_family == "RedHat"Ini adalah idempotent karena setiap task memeriksa kondisi sebelum berjalan. Pada sistem Debian, task RedHat tidak pernah berjalan.
Gunakan stat atau command dengan changed_when: false untuk memeriksa apakah sesuatu ada:
- name: Check if SSL certificate exists
stat:
path: /etc/ssl/certs/cert.pem
register: cert_file
- name: Generate SSL certificate
shell: openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/key.pem -out /etc/ssl/certs/cert.pem
when: not cert_file.stat.existsModul stat memeriksa apakah sertifikat ada tanpa membuat perubahan. Task shell hanya berjalan jika sertifikat tidak ada.
Modul template adalah idempotent. Ia merender template Jinja2 dan menyalinnya ke tujuan:
- name: Deploy application config
template:
src: app.conf.j2
dest: /etc/app/app.conf
owner: root
group: root
mode: '0644'
notify: restart appAnsible membandingkan template yang dirender dengan file yang ada. Jika identik, ia melaporkan changed: false dan handler tidak berjalan. Jika berbeda, ia memperbarui file dan memberitahu handler.
Untuk file konfigurasi kritis, validasi sintaks sebelum deployment:
- name: Deploy nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
validate: /usr/sbin/nginx -t -c %s
notify: reload nginxParameter validate menjalankan perintah untuk memeriksa konfigurasi. Jika validasi gagal, Ansible tidak men-deploy file dan melaporkan error. Ini mencegah konfigurasi yang rusak mencapai produksi.
shell Daripada Modul yang Tepat- name: Install nginx
shell: apt-get install -y nginxIni tidak idempotent. Gunakan modul apt sebagai gantinya:
- name: Install nginx
apt:
name: nginx
state: present- name: Add line to config
shell: echo "new_setting=value" >> /etc/app/config.confIni menambahkan baris setiap kali. Gunakan lineinfile sebagai gantinya:
- name: Add line to config
lineinfile:
path: /etc/app/config.conf
line: "new_setting=value"
state: present- name: Create directory
shell: mkdir /opt/app
ignore_errors: yesIni menyembunyikan error nyata. Gunakan modul yang tepat:
- name: Create directory
file:
path: /opt/app
state: directory
mode: '0755'- name: Update config
copy:
src: app.conf
dest: /etc/app/app.conf
- name: Restart app
systemd:
name: app
state: restartedIni me-restart service setiap kali. Gunakan handler:
- name: Update config
copy:
src: app.conf
dest: /etc/app/app.conf
notify: restart app
handlers:
- name: restart app
systemd:
name: app
state: restartedAnsible memiliki modul untuk hampir semuanya. Gunakannya. Mereka idempotent, well-tested, dan maintainable.
# Baik
- name: Install packages
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- curl
- git
# Hindari
- name: Install packages
shell: apt-get install -y nginx curl gitchanged_when dan failed_when Secara EksplisitBuat intent Anda jelas:
- name: Check service status
shell: systemctl is-active nginx
register: nginx_status
changed_when: false
failed_when: nginx_status.rc not in [0, 3]Gunakan parameter validate saat men-deploy file konfigurasi:
- name: Deploy nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
validate: /usr/sbin/nginx -t -c %s
notify: reload nginxJangan pernah restart service secara langsung dalam task. Gunakan handler:
tasks:
- name: Update config
copy:
src: app.conf
dest: /etc/app/app.conf
notify: restart app
handlers:
- name: restart app
systemd:
name: app
state: restartedSelalu test dengan --check sebelum menjalankan di produksi:
ansible-playbook site.yml --checkIni menunjukkan apa yang akan berubah tanpa benar-benar membuat perubahan.
Jalankan playbook Anda dua kali dalam pipeline CI/CD Anda. Run kedua harus melaporkan tidak ada perubahan:
#!/bin/bash
ansible-playbook site.yml
FIRST_RUN=$?
ansible-playbook site.yml
SECOND_RUN=$?
if [ $FIRST_RUN -ne 0 ] || [ $SECOND_RUN -ne 0 ]; then
echo "Playbook failed"
exit 1
fi
# Check that second run made no changes
if ansible-playbook site.yml --check | grep -q "changed=0"; then
echo "Playbook is idempotent"
else
echo "Playbook is not idempotent"
exit 1
fiJalankan playbook Anda dua kali dan verifikasi run kedua tidak membuat perubahan:
# First run
ansible-playbook site.yml
# Second run - should show changed=0
ansible-playbook site.ymlCari changed=0 dalam output. Jika ada task yang menunjukkan changed=1 pada run kedua, playbook Anda tidak idempotent.
Molecule adalah framework testing untuk Ansible. Ia dapat test idempotency secara otomatis:
---
driver:
name: docker
platforms:
- name: ubuntu
image: ubuntu:22.04
provisioner:
name: ansible
verifier:
name: ansible
scenario:
name: default
test_sequence:
- lint
- destroy
- dependency
- create
- prepare
- converge
- idempotence
- verify
- destroyLangkah idempotence menjalankan playbook Anda dua kali dan memverifikasi run kedua tidak membuat perubahan.
Beberapa task adalah operasi genuinely satu kali. Gunakan changed_when: false untuk mengakui ini:
- name: Initialize database
shell: /opt/app/bin/init-db.sh
changed_when: false
run_once: yesTask yang memanggil external API mungkin tidak idempotent. Dokumentasikan ini:
- name: Deploy to production
uri:
url: https://api.example.com/deploy
method: POST
body_format: json
body:
version: "{{ app_version }}"
changed_when: falseSelama troubleshooting, Anda mungkin perlu menjalankan perintah non-idempotent. Itu baik-baik saja—hanya jangan commit ke playbook utama Anda.
Idempotency adalah fondasi otomasi infrastruktur yang andal. Itulah yang membuat Ansible aman untuk produksi, memungkinkan otomasi terjadwal, dan memungkinkan Anda memperlakukan infrastruktur Anda sebagai kode.
Takeaway kunci:
changed_when dan failed_when untuk mengontrol perilaku taskTulis playbook idempotent dari awal. Ini membutuhkan sedikit lebih banyak usaha di awal tetapi menghemat jumlah debugging dan firefighting yang sangat besar kemudian. Diri Anda di masa depan akan berterima kasih.