Production Readiness

Production Readiness #

Ada jurang pemisah yang sangat lebar antara sistem yang sekadar “berjalan di production” dengan sistem yang benar-benar “siap untuk production” (production ready). Sebuah playbook Ansible mungkin sukses dieksekusi sekali di server produksi kita, namun itu tidak membuktikan bahwa sistem kita siap menghadapi lonjakan trafik, kegagalan disk, atau kesalahan konfigurasi manusia. Kesiapan produksi (production readiness) bukan sekadar status ketika kode kita sukses dijalankan tanpa error merah di terminal. Kesiapan produksi adalah tentang ketahanan (resilience), kemampuan pemulihan (recoverability), visibilitas (observability), serta jaminan bahwa konfigurasi kita dapat dioperasikan secara aman oleh seluruh tim dalam situasi krisis sekalipun.

Ketika kita mengelola infrastruktur menggunakan Ansible, kita memegang kendali atas ratusan atau ribuan server sekaligus. Skala kekuatan ini membawa risiko yang setara: satu kesalahan kecil dalam variabel playbook dapat melumpuhkan seluruh kluster dalam hitungan detik. Oleh karena itu, kita harus menerapkan disiplin kesiapan produksi secara ketat. Kita perlu membangun mekanisme validasi kondisi sebelum perubahan dilakukan, taktik uji kelayakan pasca-perubahan (smoke testing), serta playbook pemulihan bencana (disaster recovery) yang teruji secara berkala.


Siklus Hidup Perubahan Infrastruktur yang Aman #

Untuk menjaga stabilitas lingkungan produksi, setiap modifikasi konfigurasi harus melalui siklus hidup yang terstandarisasi. Kita tidak boleh langsung menerapkan perubahan secara membabi buta tanpa menyiapkan jaring pengaman terlebih dahulu.

Berikut adalah alur kerja siklus hidup perubahan infrastruktur yang aman:

flowchart TD
    A["Mulai Perubahan"] --> B["Ambil Baseline State"]
    B --> C["Terapkan Perubahan (Playbook)"]
    C --> D{"Apakah Eksekusi Sukses?"}
    D -- "Tidak" --> E["Mulai Disaster Recovery / Rollback"]
    D -- "Ya" --> F["Verifikasi Post-Change (Smoke Test)"]
    F --> G{"Apakah Verifikasi Lolos?"}
    G -- "Tidak" --> E
    G -- "Ya" --> H["Kirim Notifikasi Sukses"]
    H --> I["Selesai (Production Ready)"]

Dengan mengadopsi siklus ini, kita memastikan bahwa setiap perubahan selalu diawali dengan perekaman status sistem saat ini (baseline) dan diakhiri dengan verifikasi menyeluruh untuk menjamin tidak ada degradasi layanan.


Lembar Kesiapan Produksi (Production Readiness Checklist) #

Sebelum kita menaikkan status repositori Ansible kita menjadi “siap produksi”, kita harus mengaudit seluruh komponen infrastruktur dan playbook kita menggunakan kriteria peninjauan berikut:

1. Kategori Deployment dan Pengujian #

  • Pengujian Staging yang Representatif: Kita harus memastikan playbook telah diuji di lingkungan staging yang memilki spesifikasi sistem, topologi jaringan, dan volume data yang menyerupai lingkungan produksi.
  • Review Dry Run Mandiri: Sebelum eksekusi riil, jalankan perintah ansible-playbook -i inventory/production site.yml --check --diff dan lakukan review baris-demi-baris pada diff output yang dihasilkan.
  • Prosedur Rollback Teruji: Setiap Pull Request yang mengubah konfigurasi sensitif harus disertai dengan dokumentasi langkah rollback yang konkret dan telah disimulasikan keberhasilannya.

2. Kategori Backup dan Pemulihan (Backup & Recovery) #

  • Backup Sebelum Maintenance: Playbook pemeliharaan kita harus memiliki tugas otomatis untuk mencadangkan database atau berkas konfigurasi penting sebelum melakukan tindakan modifikasi apa pun.
  • Verifikasi Integritas Backup: Kita harus menjadwalkan pengujian restore otomatis secara berkala. Ingat, file cadangan yang tidak pernah diuji restore-nya bukanlah backup yang valid.
  • Definisi RTO dan RPO: Tim kita harus memiliki kesepakatan tertulis mengenai Recovery Time Objective (RTO) dan Recovery Point Objective (RPO) untuk setiap tingkatan layanan infrastruktur.

3. Kategori Observabilitas (Observability) #

  • Health Check Endpoint: Setiap aplikasi yang di-deploy oleh Ansible harus dilengkapi dengan endpoint /health atau /ready yang memverifikasi status internal aplikasi (seperti koneksi database dan sisa penyimpanan disk).
  • Alerting Konfigurasi: Pastikan Ansible juga mengonfigurasi alert rules pada Prometheus atau Datadog agar kita segera mendapatkan notifikasi jika service yang baru kita pasang mengalami kendala setelah dideploy.

4. Kategori Keamanan (Security) #

  • Enkripsi Vault Menyeluruh: Tidak boleh ada password database, API token, atau kunci enkripsi yang tersimpan dalam format plaintext di Git. Seluruh variabel sensitif wajib berada di dalam Ansible Vault.
  • Prinsip Hak Akses Minimal (Least Privilege): Gunakan parameter become: true dan become_user secara selektif hanya pada task yang membutuhkan hak akses administratif tinggi (root), hindari menerapkannya di level global playbook jika tidak diperlukan.

Validasi Status Sistem Sebelum Perubahan Besar #

Salah satu kesalahan paling fatal saat melakukan pemeliharaan adalah tidak mengetahui apa status awal sistem sebelum kita melakukan perubahan. Akibatnya, ketika terjadi error setelah playbook selesai dijalankan, kita kesulitan menentukan apakah error tersebut disebabkan oleh konfigurasi baru kita atau memang sudah terjadi sebelum pemeliharaan dimulai.

Untuk mengatasi masalah ini, kita harus membuat playbook khusus bernama capture-baseline.yml. Playbook ini berfungsi mengumpulkan data status operasional server, versi paket yang terinstal, serta file konfigurasi aktif, lalu menyimpannya sebagai arsip lokal di komputer kita sebelum maintenance dimulai.

Berikut adalah playbook capture-baseline.yml standar produksi yang kita gunakan:

# playbooks/capture-baseline.yml
---
- name: Capture baseline state sebelum perubahan besar
  hosts: "{{ target_hosts | default('all') }}"
  gather_facts: true
  become: true

  vars:
    baseline_dir: "/tmp/ansible-baselines/{{ lookup('pipe', 'date +%Y-%m-%d_%H-%M-%S') }}"

  pre_tasks:
    - name: Pastikan direktori baseline lokal tersedia
      file:
        path: "{{ baseline_dir }}/{{ inventory_hostname }}"
        state: directory
        mode: '0755'
      delegate_to: localhost
      become: false

  tasks:
    - name: 1. Snapshot daftar dan versi package yang terinstal
      package_facts:
        manager: auto

    - name: Simpan daftar package ke file lokal
      copy:
        content: >
          {{ ansible_facts.packages
             | dict2items
             | selectattr('value.0.version', 'defined')
             | map(attribute='key')
             | sort
             | join('\n') }}          
        dest: "{{ baseline_dir }}/{{ inventory_hostname }}/packages-installed.txt"
      delegate_to: localhost
      become: false

    - name: 2. Snapshot status layanan (Service Status)
      service_facts:

    - name: Simpan status layanan berjalan ke file lokal
      copy:
        content: >
          {{ ansible_facts.services
             | dict2items
             | selectattr('value.state', 'equalto', 'running')
             | map(attribute='key')
             | sort
             | join('\n') }}          
        dest: "{{ baseline_dir }}/{{ inventory_hostname }}/services-running.txt"
      delegate_to: localhost
      become: false

    - name: 3. Snapshot port TCP yang sedang mendengarkan (Listen)
      shell: ss -tlnp
      register: listen_ports
      changed_when: false

    - name: Simpan daftar port TCP ke file lokal
      copy:
        content: "{{ listen_ports.stdout }}"
        dest: "{{ baseline_dir }}/{{ inventory_hostname }}/tcp-ports-listen.txt"
      delegate_to: localhost
      become: false

    - name: 4. Ambil salinan file konfigurasi kritis saat ini
      fetch:
        src: "{{ item }}"
        dest: "{{ baseline_dir }}/{{ inventory_hostname }}/configs/"
        flat: yes
      loop:
        - /etc/nginx/nginx.conf
        - /etc/sysctl.conf
        - /etc/hosts
      ignore_errors: true

    - name: 5. Catat sisa ruang penyimpanan disk (Disk Usage)
      shell: df -h /
      register: disk_usage
      changed_when: false

    - name: Simpan info disk ke file lokal
      copy:
        content: "{{ disk_usage.stdout }}"
        dest: "{{ baseline_dir }}/{{ inventory_hostname }}/disk-usage.txt"
      delegate_to: localhost
      become: false

Playbook di atas memanfaatkan modul bawaan seperti package_facts dan service_facts untuk mengabstraksikan pengumpulan data tanpa bergantung pada perintah spesifik OS. Hasil eksekusi playbook ini akan menghasilkan struktur folder arsip lokal berikut di laptop kita:

/tmp/ansible-baselines/2026-06-18_21-30-00/
  ├── web-server-01/
  │     ├── packages-installed.txt
  │     ├── services-running.txt
  │     ├── tcp-ports-listen.txt
  │     ├── disk-usage.txt
  │     └── configs/
  │           ├── nginx.conf
  │           └── sysctl.conf
  └── db-server-01/
        ├── packages-installed.txt
        └── ...

Dengan memiliki arsip baseline ini, kita memiliki titik acuan pemulihan yang sangat jelas jika konfigurasi baru kita merusak status layanan di server.


Verifikasi Kelayakan Pasca-Perubahan (Post-Change Verification) #

Setelah proses deployment selesai dijalankan oleh Ansible, kita tidak boleh berasumsi bahwa semuanya baik-baik saja hanya karena log eksekusi tidak menunjukkan warna merah. Kita wajib melakukan smoke testing otomatis untuk memverifikasi fungsionalitas riil dari aplikasi dan server kita.

Berikut adalah playbook verify-post-change.yml yang dirancang untuk melakukan serangkaian pengujian logika bisnis dan stabilitas sistem setelah perubahan selesai diterapkan:

# playbooks/verify-post-change.yml
---
- name: Verifikasi kelayakan sistem pasca-perubahan
  hosts: "{{ target_hosts | default('all') }}"
  gather_facts: true
  become: true

  vars:
    critical_services:
      - nginx
      - postgresql
    health_check_endpoints:
      - name: "Aplikasi API Utama"
        port: 8080
        path: "/health"
      - name: "Portal Frontend Web"
        port: 80
        path: "/"

  tasks:
    - name: 1. Ambil status layanan pasca-perubahan
      service_facts:

    - name: Pastikan semua layanan kritis berstatus berjalan (Running)
      assert:
        that:
          - ansible_facts.services[item + '.service'] is defined
          - ansible_facts.services[item + '.service'].state == 'running'
        fail_msg: "ERROR KRITIS: Layanan {{ item }} terdeteksi mati setelah deployment!"
        success_msg: "Sukses: Layanan {{ item }} berjalan dengan aman."
      loop: "{{ critical_services }}"

    - name: 2. Uji HTTP Health Check Endpoint secara lokal
      uri:
        url: "http://localhost:{{ item.port }}{{ item.path }}"
        status_code: 200
        timeout: 10
      loop: "{{ health_check_endpoints }}"
      loop_control:
        label: "{{ item.name }}"
      register: health_results

    - name: Pastikan respons health check memuat status OK
      assert:
        that:
          - "'ok' in item.json.status or item.status == 200"
        fail_msg: "ERROR KRITIS: Health check endpoint {{ item.item.name }} mengembalikan status tidak sehat!"
      loop: "{{ health_results.results }}"
      loop_control:
        label: "{{ item.item.name }}"

    - name: 3. Periksa kemunculan error baru di log sistem (5 menit terakhir)
      shell: |
        journalctl --since "5 minutes ago" -p err --no-pager | wc -l        
      register: system_errors
      changed_when: false

    - name: Berikan peringatan jika terdeteksi log error baru
      debug:
        msg: "⚠️ PERINGATAN: Terdeteksi ada {{ system_errors.stdout }} error baru di journald dalam 5 menit terakhir!"
      when: system_errors.stdout | int > 0

Dengan menjalankan playbook verifikasi ini setelah eksekusi playbook utama, kita dapat langsung mendeteksi degradasi performa atau matinya service tanpa perlu menunggu komplain dari pengguna (user).


Playbook Pemulihan Bencana (Disaster Recovery) yang Teruji #

Kesiapan produksi tingkat lanjut menuntut kita memiliki prosedur tertulis untuk memulihkan sistem ketika skenario terburuk terjadi (misalnya, korupsi data database atau kegagalan disk total). Menulis instruksi pemulihan saat server kita sedang mati dan tim manajemen sedang panik adalah resep terbaik untuk melakukan kesalahan operasional yang fatal.

Kita harus mendesain playbook pemulihan bencana (disaster recovery) yang memiliki sifat defensif: memaksa adanya konfirmasi eksplisit dari operator, melakukan backup preventif sebelum memuntahkan data lama, serta memvalidasi ketersediaan berkas cadangan sebelum eksekusi dimulai.

Di bawah ini adalah contoh playbook pemulihan database PostgreSQL dari cadangan S3 yang aman untuk lingkungan produksi:

# playbooks/disaster-recovery/restore-database.yml
---
# PERINGATAN DARURAT: Playbook ini akan menimpa data database produksi yang ada saat ini!
# Eksekusi hanya setelah berkoordinasi dengan Incident Commander.

- name: Eksekusi pemulihan database dari backup S3
  hosts: db_servers
  become: true
  serial: 1  # Lakukan satu-per-satu untuk menghindari pemadaman kluster total jika salah sasaran

  vars:
    s3_bucket: "company-production-backups-secure"
    restore_target_date: "{{ target_date | mandatory }}" # Format: YYYY-MM-DD
    confirm_action: "{{ confirm | default('no') }}"

  pre_tasks:
    - name: 1. Proteksi konfirmasi eksplisit dari operator
      assert:
        that:
          - confirm_action == "SAYA-SADAR-INI-AKAN-MENIMPA-DATA-PRODUKSI"
        fail_msg: >
          Gagal: Kita harus menyertakan argumen konfirmasi secara eksplisit untuk menjalankan pemulihan ini!
          Contoh jalankan:
          ansible-playbook -i inventory/production restore-database.yml -e "target_date=2026-06-18 confirm=SAYA-SADAR-INI-AKAN-MENIMPA-DATA-PRODUKSI"          

    - name: 2. Rekam log awal aktivitas pemulihan bencana
      lineinfile:
        path: /var/log/disaster-recovery-audit.log
        line: >
          [{{ ansible_date_time.iso8601 }}] RESTORE DB DIMULAI oleh user: {{ ansible_user }}
          menggunakan backup tanggal: {{ restore_target_date }}          
        create: yes
        owner: root
        group: root
        mode: '0600'

  tasks:
    - name: 3. Hentikan layanan aplikasi yang terhubung ke database
      systemd:
        name: "{{ item }}"
        state: stopped
      loop:
        - node-api-service
        - background-worker-service
      delegate_to: "{{ item }}"
      loop_control:
        label: "Menghentikan layanan {{ item }}"
      # Kita mengasumsikan host layanan aplikasi terdaftar di inventory

    - name: 4. Buat cadangan darurat (Snapshot) database saat ini sebelum ditimpa
      shell: |
        pg_dump -U postgres -d main_production -F c -b -v -f "/tmp/emergency-pre-restore-{{ ansible_date_time.epoch }}.dump"        
      become_user: postgres
      ignore_errors: yes # Tetap lanjutkan restore meskipun backup darurat gagal (misal karena disk penuh)

    - name: 5. Unduh berkas cadangan dari AWS S3
      amazon.aws.s3_object:
        bucket: "{{ s3_bucket }}"
        object: "backups/postgres/{{ restore_target_date }}/main_production.dump"
        dest: "/tmp/restore-target.dump"
        mode: get
      register: s3_download
      until: s3_download is success
      retries: 3
      delay: 10

    - name: 6. Bersihkan dan buat ulang database kosong
      postgresql_db:
        name: main_production
        state: absent
      become_user: postgres

    - name: Buat ulang database kosong dengan skema standar
      postgresql_db:
        name: main_production
        state: present
        encoding: UTF-8
      become_user: postgres

    - name: 7. Terapkan pemulihan data dari file dump
      shell: |
        pg_restore -U postgres -d main_production -v "/tmp/restore-target.dump"        
      become_user: postgres
      register: restore_execution
      failed_when: restore_execution.rc not in [0, 1] # pg_restore kadang mengembalikan rc=1 jika ada warning non-kritis

    - name: 8. Hidupkan kembali layanan database dan aplikasi
      systemd:
        name: "{{ item }}"
        state: started
      loop:
        - node-api-service
        - background-worker-service
      delegate_to: "{{ item }}"

    - name: 9. Hapus file pemulihan sementara untuk membersihkan disk
      file:
        path: "{{ item }}"
        state: absent
      loop:
        - "/tmp/restore-target.dump"

  post_tasks:
    - name: Rekam log akhir aktivitas pemulihan
      lineinfile:
        path: /var/log/disaster-recovery-audit.log
        line: >
          [{{ ansible_date_time.iso8601 }}] RESTORE DB SELESAI status: {{ 'SUKSES' if not ansible_failed_result is defined else 'GAGAL' }}          
      delegate_to: localhost
      become: false

Mengapa playbook pemulihan bencana ini tergolong aman untuk tingkat produksi?

  1. Pagar Pengaman Eksplisit (assert): Playbook tidak akan mengeksekusi apa pun jika operator salah mengetik perintah atau tidak sengaja menekan enter. Operator dipaksa menulis string konfirmasi yang panjang dan spesifik.
  2. Serialisasi Terkendali (serial: 1): Jika kita mengaplikasikannya pada kluster database, playbook dijalankan satu-per-satu untuk memastikan kita tidak merusak semua server sekaligus jika ada parameter yang salah.
  3. Backup Cadangan Darurat: Sebelum memusnahkan database produksi lama, playbook mencoba membuat dump lokal terakhir. Jika ternyata file restore kita rusak, kita masih memiliki salinan database tepat sebelum proses pemulihan dimulai.
  4. Audit Trail: Segala aktivitas pemulihan dicatat ke file audit log lokal dengan timestamp yang presisi, membantu analisis pasca-insiden (post-mortem).

Integrasi Visibilitas dan Pemantauan (Observability) #

Sistem yang siap produksi harus dapat dipantau status kesehatannya setiap saat. Sebagai bagian dari siklus hidup deployment Ansible kita, pastikan setiap role server yang kita pasang secara otomatis mendaftarkan dirinya ke sistem monitoring pusat.

Kita harus mengotomatiskan langkah-langkah berikut di setiap target deployment:

  1. Instalasi Node Exporter: Pasang Prometheus Node Exporter di setiap VM untuk mengekspor metrik sistem dasar (CPU, RAM, Disk, Jaringan).
  2. Konfigurasi Log Shipping: Gunakan Ansible untuk mengonfigurasi Promtail atau Filebeat agar mengirimkan log sistem secara real-time ke Loki atau Elasticsearch.
  3. Pendaftaran Target Monitoring: Gunakan modul HTTP Ansible (uri) untuk memanggil API Prometheus server dan mendaftarkan IP node baru ke dalam konfigurasi monitoring secara dinamis.

Dengan mengintegrasikan aspek observabilitas secara langsung di dalam kode infrastruktur kita, kita menghilangkan kebiasaan buruk di mana tim melupakan pemasangan monitoring pada server-server baru.


Ringkasan #

  • Kesiapan Produksi adalah Budaya — Bukan sekadar hilangnya pesan error pada playbook, melainkan jaminan ketahanan sistem, kemampuan pemulihan yang cepat, dan visibilitas operasional yang lengkap.
  • Pentingnya Baseline — Selalu jalankan perekaman status awal sistem sebelum memulai pemeliharaan besar menggunakan playbook snapshot (capture-baseline.yml) untuk melacak regresi konfigurasi dengan akurat.
  • Smoke Testing Otomatis — Integrasikan playbook uji kelayakan pasca-perubahan (verify-post-change.yml) yang melakukan asersi status layanan dan HTTP health check secara real-time sebelum menutup sesi pemeliharaan.
  • Playbook Pemulihan Defensif — Desain playbook penanganan bencana dengan jaring pengaman ketat: paksa konfirmasi string eksplisit, lakukan backup darurat sebelum menimpa data, dan catat riwayat eksekusi pada audit log.
  • Otomatisasikan Pemantauan — Setiap VM atau layanan yang dipasang via Ansible wajib dikonfigurasi untuk mengekspor metrik dasar dan mengirimkan log sistem ke repositori log terpusat sejak hari pertama.

← Sebelumnya: Team Workflow   Berikutnya: Lessons Learned →

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