Error Handling

Error Handling #

Playbook yang hanya berjalan di kondisi sempurna tidak cukup untuk production. Server terkadang tidak responsif, network putus di tengah deployment, atau proses yang dijalankan mengembalikan error yang tidak terduga. Tanpa error handling yang tepat, satu kegagalan bisa membiarkan sistem dalam kondisi setengah jalan — sebagian server sudah diupdate, sebagian belum, dan tidak ada cara yang jelas untuk melanjutkan atau membatalkan. Artikel ini membahas mekanisme error handling Ansible dan pola untuk recovery yang bersih.

block / rescue / always #

Ansible menyediakan konstruksi block yang mirip try-catch-finally di bahasa pemrograman:

- name: Deployment dengan error handling
  block:
    # Task-task di sini dieksekusi secara normal
    - name: Stop aplikasi lama
      systemd:
        name: myapp
        state: stopped

    - name: Backup konfigurasi saat ini
      copy:
        src: /etc/app/app.conf
        dest: /etc/app/app.conf.bak
        remote_src: true

    - name: Deploy konfigurasi baru
      template:
        src: app.conf.j2
        dest: /etc/app/app.conf
      notify: Restart myapp

    - name: Start aplikasi dengan konfigurasi baru
      systemd:
        name: myapp
        state: started

  rescue:
    # Dieksekusi HANYA jika ada task di block yang gagal
    - name: Kembalikan konfigurasi backup
      copy:
        src: /etc/app/app.conf.bak
        dest: /etc/app/app.conf
        remote_src: true

    - name: Restart aplikasi dengan konfigurasi lama
      systemd:
        name: myapp
        state: restarted

    - name: Catat kegagalan deployment
      debug:
        msg: "Deployment gagal! Aplikasi dikembalikan ke konfigurasi sebelumnya."

    - name: Set flag bahwa rescue dijalankan
      set_fact:
        deployment_failed: true

  always:
    # Selalu dieksekusi, baik block berhasil maupun rescue berjalan
    - name: Hapus file backup sementara
      file:
        path: /etc/app/app.conf.bak
        state: absent

    - name: Log hasil deployment
      local_action:
        module: lineinfile
        path: /var/log/deployments.log
        line: >
          {{ ansible_date_time.iso8601 }}
          {{ inventory_hostname }}
          {{ 'FAILED — rollback dilakukan' if deployment_failed | default(false) else 'SUCCESS' }}          
        create: true

- name: Hentikan playbook jika deployment gagal
  fail:
    msg: "Deployment gagal di {{ inventory_hostname }}. Lihat log untuk detail."
  when: deployment_failed | default(false)

ignore_errors: Lanjutkan Meski Gagal #

Ada situasi di mana kegagalan sebuah task tidak perlu menghentikan seluruh play — misalnya saat menghapus resource yang mungkin sudah tidak ada:

- name: Hentikan service lama jika ada
  systemd:
    name: old-service
    state: stopped
  ignore_errors: true     # Tidak apa-apa jika service tidak ada

- name: Hapus file lama jika ada
  file:
    path: /opt/old-app
    state: absent
  ignore_errors: true
ignore_errors: true bisa menyembunyikan masalah nyata. Gunakan secara selektif dan hanya saat kamu benar-benar tahu bahwa error bisa diabaikan. Lebih baik gunakan kondisi when yang tepat atau failed_when untuk kontrol yang lebih presisi.

failed_when: Kontrol Kapan Task Dianggap Gagal #

# Task yang selalu return exit code 0 meski ada error di output
- name: Cek status layanan eksternal
  command: curl -s -o /dev/null -w "%{http_code}" https://api.external.com/health
  register: health_status
  failed_when:
    - health_status.stdout != "200"
    - health_status.stdout != "204"
  changed_when: false

# Abaikan error tertentu, gagalkan yang lain
- name: Jalankan migrasi database
  command: python manage.py migrate
  register: migration_result
  failed_when:
    - migration_result.rc != 0
    - "'No migrations to apply' not in migration_result.stdout"

Rollback Otomatis saat Deployment Gagal #

Pola rollback yang lebih lengkap untuk deployment aplikasi:

# playbooks/deploy-with-rollback.yml
---
- name: Deploy dengan rollback otomatis
  hosts: appservers
  vars:
    deploy_success: false

  pre_tasks:
    - name: Catat versi yang sedang berjalan
      command: cat /opt/app/VERSION
      register: current_version
      changed_when: false
      ignore_errors: true

    - name: Simpan versi saat ini untuk rollback
      set_fact:
        rollback_version: "{{ current_version.stdout | default('unknown') }}"

  tasks:
    - block:
        - name: Pull kode versi baru
          git:
            repo: https://github.com/company/app.git
            dest: /opt/app
            version: "{{ deploy_version }}"

        - name: Install dependencies
          pip:
            requirements: /opt/app/requirements.txt
            virtualenv: /opt/app/venv

        - name: Jalankan migrasi database
          command: /opt/app/venv/bin/python manage.py migrate
          args:
            chdir: /opt/app

        - name: Restart aplikasi
          systemd:
            name: myapp
            state: restarted

        - name: Tunggu aplikasi siap
          uri:
            url: "http://localhost:{{ app_port }}/health"
            status_code: 200
          register: health_check
          until: health_check.status == 200
          retries: 12
          delay: 5

        - name: Tandai deployment berhasil
          set_fact:
            deploy_success: true

      rescue:
        - name: Rollback ke versi sebelumnya
          git:
            repo: https://github.com/company/app.git
            dest: /opt/app
            version: "{{ rollback_version }}"
          when: rollback_version != 'unknown'

        - name: Restart aplikasi dengan versi lama
          systemd:
            name: myapp
            state: restarted
          when: rollback_version != 'unknown'

        - name: Gagalkan playbook setelah rollback
          fail:
            msg: >
              Deployment {{ deploy_version }} gagal.
              Rollback ke {{ rollback_version }} sudah dilakukan.              

any_errors_fatal vs max_fail_percentage #

Dua cara mengontrol perilaku saat ada host yang gagal:

# any_errors_fatal — hentikan SEMUA host saat satu gagal
- name: Deploy kritis yang harus atomik
  hosts: webservers
  any_errors_fatal: true
  tasks:
    - name: Update konfigurasi kritikal
      template:
        src: critical.conf.j2
        dest: /etc/app/critical.conf

# max_fail_percentage — toleransi sejumlah host yang boleh gagal
- name: Update dengan toleransi kegagalan 10%
  hosts: webservers
  max_fail_percentage: 10    # Hentikan jika lebih dari 10% host gagal
  serial: 5                  # Update 5 host sekaligus
  tasks:
    - name: Apply patch
      apt:
        upgrade: dist

Ringkasan #

  • block / rescue / always adalah mekanisme try-catch-finally Ansible — gunakan untuk membungkus task yang butuh rollback jika gagal.
  • rescue hanya berjalan saat ada kegagalan di block; always selalu berjalan — cocok untuk cleanup dan logging.
  • ignore_errors: true untuk kegagalan yang bisa diabaikan; lebih baik gunakan failed_when untuk kontrol yang lebih presisi.
  • Pola deployment yang baik selalu menyimpan versi saat ini sebelum update, sehingga rollback bisa dilakukan ke kondisi yang dikenal baik.
  • any_errors_fatal: true untuk deployment yang harus atomik — satu kegagalan menghentikan semua.
  • max_fail_percentage untuk toleransi kegagalan yang lebih fleksibel — update bisa lanjut selama persentase kegagalan di bawah threshold.

← Sebelumnya: Scheduled Task   Berikutnya: Delegation & Local Action →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact