Scheduled Task

Scheduled Task #

Mengotomatiskan infrastruktur tidak hanya tentang memicu perubahan secara reaktif saat ada kode baru atau masalah darurat. Sebagian besar tugas operasi harian bersifat rutin dan berkala: mencadangkan database setiap malam, menerapkan patch keamanan setiap akhir pekan, membersihkan direktori penyimpanan sementara, dan melakukan audit kepatuhan konfigurasi secara reguler. Semua ini adalah tugas terencana (scheduled task) yang berjalan otomatis berdasarkan jadwal tanpa intervensi manual. Namun, menjalankan skrip otomatis tanpa pengawasan manusia menuntut tingkat ketahanan yang jauh lebih tinggi. Kita harus memastikan bahwa tugas tidak berjalan tumpang tindih, kegagalan terdeteksi secara instan, dan log eksekusi tercatat dengan rapi untuk keperluan audit kepatuhan.


Sistem Penjadwalan: Cron vs Systemd Timer #

Ketika kita ingin menjadwalkan eksekusi Ansible di control node, kita memiliki dua pilihan mekanisme utama pada sistem operasi Linux: penjadwal tradisional cron atau penjadwal modern systemd timer. Meskipun cron telah menjadi standar industri selama puluhan tahun karena kesederhanaannya, systemd timer menawarkan kontrol yang jauh lebih unggul di lingkungan produksi berskala besar.

Berikut adalah diagram alur logis eksekusi scheduled task yang aman dengan mekanisme deteksi tumpang tindih dan pengiriman notifikasi kegagalan:

flowchart TD
      A["Jadwal Terpicu (Cron/Timer)"] --> B["Cek Ketersediaan Lock File (/var/run/job.lock)"]
      B --> C{"Apakah Lock File Ada?"}
      C -- "Ya (Overlapping)" --> D["Hentikan Eksekusi & Kirim Alert Duplikasi"]
      C -- "Tidak" --> E["Buat Lock File (Touch)"]
      E --> F["Jalankan Pre-flight Assertions (Misal: Ruang Disk)"]
      F --> G{"Apakah Lolos Assertions?"}
      G -- "Tidak" --> H["Kirim Alert Kegagalan & Hapus Lock File"]
      G -- "Ya" --> I["Eksekusi Tugas Utama (Backup/Patch)"]
      I --> J{"Apakah Sukses?"}
      J -- "Tidak" --> H
      J -- "Ya" --> K["Hapus Lock File (Cleanup)"]
      K --> L["Kirim Laporan Sukses & Selesai"]

Perbandingan Karakteristik Penjadwal #

Mari kita bandingkan kedua opsi ini secara mendalam untuk membantu kita mendesain arsitektur otomatisasi yang tepat:

Karakteristik Cron Job (cron) Systemd Timer (timer)
Kesederhanaan Sangat tinggi, cukup satu baris konfigurasi teks di crontab. Rendah-Sedang, membutuhkan pembuatan dua file (.service & .timer).
Manajemen Log Mengirim output ke syslog lokal atau file log statis secara manual. Terintegrasi penuh dengan journald untuk pencatatan log terperinci.
Penanganan Dependensi Tidak ada, berjalan secara membabi buta tanpa memedulikan status sistem. Sangat baik, dapat dipasang aturan agar hanya berjalan jika layanan lain aktif.
Kontrol Sumber Daya Sulit dibatasi tanpa bantuan utilitas eksternal seperti nice atau cgroups. Sangat mudah membatasi batas CPU/RAM melalui opsi unit systemd.
Penanganan Keterlambatan Jika sistem mati saat jadwal tiba, tugas akan dilewati sepenuhnya. Dapat diset menggunakan Persistent=true agar langsung berjalan setelah boot.

Mengonfigurasi Penjadwalan dengan Ansible #

Berikut adalah contoh bagaimana kita menggunakan Ansible untuk mengonfigurasi kedua jenis penjadwal tersebut pada mesin target.

Penerapan dengan Cron Modul #

- name: Setup Cron Job Harian untuk Backup database
  hosts: control_node
  become: true
  tasks:
    - name: Jadwalkan eksekusi backup database setiap pukul 02:00 pagi
      ansible.builtin.cron:
        name: "Backup Database Produksi"
        minute: "0"
        hour: "2"
        job: "/usr/bin/ansible-playbook -i /opt/inventory/ /opt/playbooks/backup.yml >> /var/log/ansible-backup.log 2>&1"
        user: "ansible"
        state: present

Penerapan dengan Systemd Timer #

Untuk menjadwalkan dengan systemd timer, pertama kita buat file unit service (/etc/systemd/system/ansible-backup.service):

[Unit]
Description=Jalankan Ansible Playbook Backup Database
After=network.target

[Service]
Type=oneshot
User=ansible
ExecStart=/usr/bin/ansible-playbook -i /opt/inventory/ /opt/playbooks/backup.yml
StandardOutput=journal
StandardError=journal

Kedua, kita buat file unit timer (/etc/systemd/system/ansible-backup.timer):

[Unit]
Description=Jadwal Mingguan untuk Ansible Backup

[Timer]
OnCalendar=Sun *-*-* 03:00:00
Persistent=true
Unit=ansible-backup.service

[Install]
WantedBy=timers.target

Kita dapat mengotomatiskan penyebaran kedua file ini dan mengaktifkan timernya menggunakan modul ansible.builtin.systemd di playbook konfigurasi kontroler kita.


Penanganan Konkurensi dan Proteksi Lock File #

Masalah paling sering terjadi pada tugas otomatisasi terjadwal adalah konkurensi yang tumpang tindih (job overlap). Bayangkan kita menjadwalkan tugas sinkronisasi data atau pencadangan disk setiap 30 menit. Di suatu hari, jaringan melambat secara drastis sehingga tugas pertama yang berjalan pukul 08.00 belum selesai ketika jadwal berikutnya dipicu pada pukul 08.30.

Jika kita tidak melindungi alur kerja kita, tugas kedua akan mulai berjalan secara paralel di server yang sama. Dua proses yang mencoba memanipulasi data atau menulis ke file cadangan yang sama secara bersamaan akan menyebabkan kondisi balapan (race condition), kelebihan beban penggunaan CPU/RAM, dan berakhir dengan kerusakan data yang fatal.

Menggunakan Modul tempfile dan file untuk Proteksi Lock File #

Kita harus menerapkan mekanisme penguncian (locking mechanism) di awal playbook kita. Sebelum mengeksekusi tugas utama, playbook akan memeriksa keberadaan sebuah file kunci (lock file). Jika file tersebut ada, playbook akan langsung berhenti dengan aman. Jika tidak ada, playbook akan membuat file tersebut terlebih dahulu, menjalankan tugas utama, dan menghapusnya kembali setelah seluruh proses selesai.

Berikut adalah implementasi playbook defensif yang menerapkan proteksi lock file:

# playbooks/backup-safe.yml
---
- name: Backup Terjadwal dengan Proteksi Lock File
  hosts: db_servers
  become: true
  vars:
    lock_file_path: "/var/run/ansible-backup.lock"
    backup_dest: "/mnt/backup-nas"

  tasks:
    - name: Evaluasi Keberadaan Lock File
      block:
        - name: Cek apakah ada file kunci dari eksekusi sebelumnya
          ansible.builtin.stat:
            path: "{{ lock_file_path }}"
          register: current_lock_status

        - name: Hentikan eksekusi jika proses sebelumnya masih aktif
          ansible.builtin.fail:
            msg: "Kegagalan: File kunci {{ lock_file_path }} ditemukan! Proses sebelumnya masih berjalan."
          when: current_lock_status.stat.exists

        - name: Buat file kunci baru (Touch Lock File)
          ansible.builtin.file:
            path: "{{ lock_file_path }}"
            state: touch
            mode: '0600'
            owner: root
            group: root

        - name: Jalankan Pre-flight Assertions (Verifikasi Ruang Disk)
          ansible.builtin.setup:
            filter: "ansible_mounts"

        - name: Hitung ketersediaan ruang penyimpanan
          set_fact:
            mount_point: "{{ ansible_mounts | selectattr('mount', 'equalto', '/') | first }}"

        - name: Pastikan ruang kosong minimal 10GB
          ansible.builtin.assert:
            that:
              - "mount_point.size_available > 10737418240"
            fail_msg: "Ruang penyimpanan tidak mencukupi untuk menampung backup baru!"

        - name: Eksekusi Tugas Pencadangan Utama
          ansible.builtin.command: "/opt/scripts/backup-db.sh"
          register: backup_execution_result
          changed_when: true

      always:
        - name: Bersihkan File Kunci Setelah Selesai (Cleanup)
          ansible.builtin.file:
            path: "{{ lock_file_path }}"
            state: absent
          when: current_lock_status is defined and not current_lock_status.stat.exists
          # Kita hanya menghapus lock file jika kitalah yang membuatnya di awal. 
          # Jika playbook berhenti karena mendeteksi lock file orang lain, kita tidak boleh menghapusnya.

Dalam implementasi di atas, blok always di dalam struktur penanganan error Ansible menjamin bahwa file kunci akan dihapus secara bersih bahkan jika proses pencadangan di tengah jalan mengalami kegagalan (crash). Ini mencegah kluster kita berada dalam status terkunci selamanya (deadlock).


Orkestrasi Tingkat Enterprise dengan AWX dan Ansible Tower #

Meskipun penjadwalan berbasis Linux CLI (cron dan systemd) sangat andal untuk infrastruktur skala kecil, kita akan menemui keterbatasan saat mengelola ribuan server dengan tim operasional yang besar. Beberapa keterbatasannya adalah:

  • Tidak adanya antarmuka grafis (UI) untuk melihat status keberhasilan tugas secara cepat.
  • Kredensial (seperti SSH key dan password database) harus disimpan secara fisik di kontrol node.
  • Sulit membagi peran akses (siapa yang boleh mengedit jadwal vs siapa yang hanya boleh melihat).

Untuk menjawab kebutuhan kelas enterprise, kita harus menggunakan AWX (versi open-source) atau Red Hat Ansible Automation Platform (AAP / dahulu Ansible Tower).

AWX bertindak sebagai control plane terpusat yang menyediakan:

  1. Centralized Logging: Semua output playbook dicatat dalam database terpusat dan dapat dikirim ke sistem eksternal seperti Elasticsearch atau Splunk.
  2. Credential Vault: AWX menyimpan SSH key, token cloud, dan password Vault secara terenkripsi. Runner AWX akan menyuntikkan kredensial ini ke dalam memori kontainer saat eksekusi dan menghapusnya seketika setelah selesai.
  3. Enterprise Scheduler: Antarmuka berbasis web yang ramah pengguna untuk mengatur jadwal eksekusi (cron-like expression) lengkap dengan kalender libur kerja nasional untuk menunda eksekusi otomatis.
  4. Drift Detection: Menjalankan playbook secara berkala dengan opsi --check (dry-run). Jika ada perubahan konfigurasi manual di server target, AWX akan mendeteksi perbedaan tersebut (drift) dan mengirimkan alert atau otomatis melakukan rekonsiliasi ke kondisi semula.

Desain Playbook Defensif untuk Tugas Rutin #

Tugas rutin seperti pembaruan keamanan (patch management) menuntut desain playbook yang ekstra defensif. Kita tidak boleh memperbarui semua server secara acak dan bersamaan karena jika paket pembaruan mengalami masalah kompatibilitas, seluruh layanan kita akan mati total secara bersamaan.

Orkestrasi Patching Bergilir (Rolling Patching) #

Kita harus memanfaatkan parameter serial pada Ansible untuk mengatur pembaruan secara bertahap. Misalnya, dengan menyetel serial: "25%", Ansible hanya akan memperbarui 25% server dari total inventory dalam satu waktu. Jika pembaruan pada kelompok pertama gagal, Ansible akan menghentikan jalannya seluruh playbook, sehingga membatasi radius kerusakan hanya pada 25% infrastruktur kita, sementara 75% sisanya tetap aman berjalan melayani pengguna.

Berikut adalah playbook patch management defensif mingguan:

# playbooks/weekly-patching.yml
---
- name: Penerapan Pembaruan Keamanan Mingguan secara Rolling
  hosts: web_servers
  become: true
  serial: "25%" # Proses 25% server dalam satu kelompok (Rolling update)
  vars:
    reboot_timeout_seconds: 300

  pre_tasks:
    - name: Catat versi kernel saat ini sebelum patching
      ansible.builtin.command: uname -r
      register: kernel_before
      changed_when: false

  tasks:
    - name: Terapkan pembaruan hanya untuk paket keamanan (Debian/Ubuntu)
      ansible.builtin.apt:
        upgrade: dist
        update_cache: true
        only_upgrade: true
      when: ansible_os_family == "Debian"
      register: apt_update_result

    - name: Cek apakah sistem operasi memerlukan reboot
      ansible.builtin.stat:
        path: /var/run/reboot-required
      register: reboot_status

    - name: Lakukan Reboot Server Jika Diperlukan
      ansible.builtin.reboot:
        msg: "Reboot otomatis oleh Ansible untuk menyelesaikan instalasi kernel patch."
        reboot_timeout: "{{ reboot_timeout_seconds }}"
        post_reboot_delay: 15
      when: reboot_status.stat.exists

    - name: Verifikasi ketersediaan port aplikasi pasca-reboot
      ansible.builtin.wait_for:
        port: 80
        delay: 5
        timeout: 60
        state: started

  post_tasks:
    - name: Dapatkan versi kernel pasca-patching
      ansible.builtin.command: uname -r
      register: kernel_after
      changed_when: false

    - name: Laporkan transisi versi kernel
      ansible.builtin.debug:
        msg: "Server {{ inventory_hostname }} berhasil diupdate. Kernel: {{ kernel_before.stdout }} -> {{ kernel_after.stdout }}"
      when: kernel_before.stdout != kernel_after.stdout

Notifikasi dan Monitoring Kegagalan Tugas Terjadwal #

Salah satu bahaya terbesar dari tugas penjadwalan otomatis tanpa pengawasan adalah kegagalan senyap (silent failure). Kita merasa aman karena telah membuat skrip backup otomatis setiap malam. Namun, setelah enam bulan berjalan, saat server kita mengalami kerusakan disk dan kita membutuhkan data cadangan tersebut, kita baru menyadari bahwa skrip backup telah gagal berjalan sejak bulan pertama karena kehabisan ruang disk.

Kita harus mengonfigurasi mekanisme pelaporan otomatis ke platform komunikasi tim seperti Slack atau Microsoft Teams setiap kali eksekusi tugas terjadwal mengalami kegagalan.

Berikut adalah contoh playbook Ansible yang mengirimkan notifikasi instan ke Slack webhook jika mendeteksi kegagalan pada proses backup:

# playbooks/backup-with-alert.yml
---
- name: Backup Database Terjadwal dengan Notifikasi Slack
  hosts: db_servers
  become: true
  vars:
    slack_webhook_url: "https://hooks.slack.com/services/T00/B00/X00" # Simpan di Ansible Vault untuk keamanan
    backup_file_path: "/var/backups/db-latest.sql"

  tasks:
    - name: Alur Utama Eksekusi Backup
      block:
        - name: Eksekusi dump database
          ansible.builtin.command: "/usr/bin/mysqldump --all-databases > {{ backup_file_path }}"
          register: mysqldump_result
          changed_when: true

        - name: Validasi ukuran file backup (Harus > 1MB)
          ansible.builtin.stat:
            path: "{{ backup_file_path }}"
          register: file_status

        - name: Pastikan file backup valid dan tidak kosong
          ansible.builtin.assert:
            that:
              - "file_status.stat.size > 1048576"
            fail_msg: "File cadangan terlalu kecil, terdeteksi kerusakan dump data!"

      rescue:
        - name: Kirim notifikasi kegagalan ke Slack
          delegate_to: localhost
          become: false
          ansible.builtin.uri:
            url: "{{ slack_webhook_url }}"
            method: POST
            body_format: json
            body:
              text: "🚨 *KEGAGALAN BACKUP TERJADWAL* 🚨\n*Server:* {{ inventory_hostname }}\n*Waktu:* {{ ansible_date_time.iso8601 }}\n*Detail Error:* {{ ansible_failed_result.msg | default('Proses dump database mengalami kegagalan runtime.') }}"
            status_code: 200
          ignore_errors: true

        - name: Teruskan kegagalan agar pipeline mencatat status gagal
          ansible.builtin.fail:
            msg: "Playbook backup dihentikan karena kegagalan internal."

Melalui metode penanganan error di atas, kita dapat memastikan bahwa setiap masalah yang terjadi pada malam hari akan langsung dilaporkan ke saluran siaga tim operasi pada pagi harinya, sehingga penanganan dapat segera dilakukan sebelum berdampak buruk.


Ringkasan #

  • Gunakan Systemd Timer untuk Fleksibilitas — Systemd timer menawarkan pencatatan log terintegrasi via journald, pemulihan pasca-boot otomatis, dan manajemen alokasi resource yang jauh lebih unggul dibandingkan cron tradisional.
  • Terapkan Proteksi Lock File — Selalu gunakan file kunci (lock file) di awal playbook Anda untuk mencegah terjadinya eksekusi ganda yang tumpang tindih dan memicu race condition.
  • Implementasi Block-Always untuk Cleanup — Bungkus alur eksekusi di dalam struktur block-always agar proses penghapusan file kunci tetap berjalan meskipun tugas utama mengalami kegagalan.
  • Batasi Dampak dengan Serial Patching — Saat menjadwalkan update sistem, gunakan parameter serial: "25%" untuk memperbarui server secara bertahap dan membatasi radius kerusakan jika paket update bermasalah.
  • Hindari Silent Failure dengan Alerting — Pasang blok rescue yang memicu pemanggilan API Slack/Teams untuk mengirimkan pemberitahuan instan jika mendeteksi kegagalan pada skrip terjadwal.
  • Evaluasi Fitur AWX/Tower — Pertimbangkan migrasi penjadwalan ke platform terpusat seperti AWX untuk kebutuhan logging terpusat, manajemen hak akses berbasis RBAC, dan audit kepatuhan konfigurasi kluster berskala besar.

← Sebelumnya: CI/CD Integration   Berikutnya: Error Handling →

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