Notification & Reporting

Notification & Reporting #

Pipeline yang selesai tanpa memberitahu siapa pun adalah pipeline yang berjalan di kekosongan. Tim perlu mengetahui saat deployment berhasil, dan perlu mengetahui lebih cepat lagi saat deployment gagal. Tetapi notifikasi yang berlebihan adalah noise yang diabaikan — sama buruknya dengan tidak adanya notifikasi. Artikel ini membahas cara membangun sistem notifikasi yang informatif tanpa menjadi spam, laporan yang memberikan visibilitas nyata atas aktivitas deployment, dan integrasi yang menyambungkan deployment event ke sistem yang sudah kita gunakan (Slack, Jira, Datadog, observability stack). Pembahasan melengkapi fondasi observability di section lain — di sini fokusnya adalah apa yang dikirim, kepada siapa, kapan, dan bagaimana formatnya saat sebuah deployment selesai.

Anatomi Sistem Notifikasi Deployment #

Sebelum masuk ke channel dan integrasi, pikirkan terlebih dahulu arsitektur umumnya. Setiap notifikasi melewati tiga lapisan: trigger (apa yang memicu), service (siapa yang memformat dan merouting), dan channel (di mana penerima melihat). Trigger adalah event dari pipeline — deployment dimulai, sukses, gagal, atau rollback. Service adalah task Ansible yang menerima event, memformat payload, dan mengirim ke channel yang sesuai. Channel adalah tempat penerima berinteraksi: Slack channel, inbox email, PagerDuty rotation, atau webhook ke sistem internal lainnya.

flowchart TD
    A["Deployment Pipeline<br/>(Ansible/AWX/GitHub Actions)"] --> B["Event Trigger<br/>play started / success / failed / rollback"]
    B --> C["Notification Service<br/>roles/notify/"]
    C --> D{"Format & Route<br/>berdasarkan severity<br/>dan audience"}
    D -- "info" --> E["Slack #deployments<br/>(broadcast publik)"]
    D -- "warning" --> F["Slack #dev-team<br/>(tim developer)"]
    D -- "critical" --> G["PagerDuty Rotation<br/>(on-call)"]
    D -- "audit" --> H["Email Log Harian<br/>(manager)"]
    D -- "metric" --> I["Datadog Event<br/>(annotation)"]
    D -- "ticket" --> J["Jira Comment<br/>(issue terkait)"]
    C --> K["Deployment Log<br/>(/var/log/deployments.log)"]
    K --> L["Dashboard HTML<br/>(self-hosted)"]

Kuncinya adalah memisahkan event generation (yang terjadi sekali per deployment) dari routing (yang berbeda untuk setiap audience). Jika kita menulis kode yang mengirim langsung ke Slack di setiap playbook, kita akan berakhir dengan copy-paste notification di puluhan tempat. Role notify terpusat menyelesaikan masalah ini: setiap playbook memanggil role yang sama dengan parameter yang berbeda, dan role itulah yang memutuskan channel serta formatnya.


Memilih Channel yang Tepat untuk Setiap Audiens #

Tidak semua notifikasi harus masuk ke channel yang sama. Keputusan channel tergantung pada tiga faktor: urgency (seberapa cepat seseorang harus bertindak), audience (siapa yang perlu tahu), dan content richness (berapa banyak konteks yang perlu disertakan). Slack cocok untuk broadcast tim yang real-time, email cocok untuk ringkasan periodik, PagerDuty cocok untuk hal yang memerlukan respons on-call dalam hitungan menit, dan webhook cocok untuk integrasi programatik dengan sistem lain.

Channel Kekuatan Kelemahan Cocok untuk Hindari untuk
Slack Real-time, rich formatting, mudah di-thread, integrasi bot Mudah tenggelam di channel sibuk, tidak untuk alert 3 AM Update deployment sukses, broadcast ke tim, status ringkas Notifikasi yang harus diproses saat off-hours (gunakan PagerDuty)
Email Audit trail permanen, agregat multi-recipient, lampiran Tidak real-time, mudah di-ignore, penuh dengan email lain Summary harian/mingguan, laporan ke manajemen, bukti audit Alert yang butuh respons dalam 5 menit (terlalu lambat)
PagerDuty On-call rotation otomatis, escalation, ack/resolve tracking, phone call fallback Mahal, bisa alert-fatigue kalau threshold salah, perlu setup awal Deployment gagal production, error yang membakar SLO budget Deployment sukses, info perubahan minor, notifikasi massal
Webhook Integrasi programatik ke sistem internal, bisa di-filter/transform Receiver harus bisa HTTP, debugging lebih sulit Integrasi ke Jira/Datadog/internal dashboard, audit log terstruktur Broadcast ke manusia (kecuali diformat jadi notifikasi di UI lain)
Microsoft Teams Mirip Slack, banyak dipakai enterprise Ekosistem bot lebih kecil dari Slack Perusahaan yang sudah pakai Teams, share ke channel project Kalau tim sudah pakai Slack — duplikasi channel = noise

Decision tree berikut membantu kita memilih channel berdasarkan severity dan audience. Aturan praktisnya: critical incident yang butuh respons di luar jam kerja → PagerDuty. Real-time info ke tim aktif → Slack. Ringkasan periodik → email. Integrasi ke tool lain → webhook. Campur channel sesuai kebutuhan — tidak perlu pilih satu saja.

flowchart TD
    A["Notifikasi deployment"] --> B{"Siapa yang harus<br/>bertindak?"}
    B -- "Tidak ada, info saja" --> C["Slack #deployments"]
    B -- "Tim developer" --> D{"Perlu respons<br/>dalam 5 menit?"}
    D -- "Tidak" --> C
    D -- "Ya" --> E{"Di luar jam kerja<br/>atau weekend?"}
    E -- "Tidak" --> F["Slack #dev-team + mention on-call"]
    E -- "Ya" --> G["PagerDuty"]
    C --> H{"Butuh audit trail<br/>permanen?"}
    H -- "Ya" --> I["Email log harian"]
    H -- "Tidak" --> J(["Selesai"])
    F --> K{"PagerDuty<br/>ack dalam 15 menit?"}
    K -- "Ya" --> L["Slack thread update"]
    K -- "Tidak" --> M["Escalate ke manager"]
    I --> N{"Perlu integrasi<br/>dengan tool lain?"}
    N -- "Ya" --> O["Webhook → Jira/Datadog/dashboard"]
    N -- "Tidak" --> P(["Selesai"])

Aturan yang konsisten di tim tentang “notifikasi level apa masuk ke channel mana” jauh lebih penting daripada pilihan channel itu sendiri. Tanpa konsistensi, orang akan subscribe/unsubscribe channel sesuka hati, dan notifikasi penting akan terlewat. Dokumentasikan routing decision tree di repo kode (bukan wiki terpisah) sehingga routing bisa di-review di PR saat diubah.


Notifikasi Slack yang Informatif #

Slack adalah channel paling umum untuk notifikasi deployment karena latensinya rendah dan kebanyakan tim sudah ada di sana setiap hari. Kunci agar tidak menjadi noise adalah informasi yang cukup untuk bertindak tanpa harus klik ke tempat lain. Sebuah notifikasi deployment yang baik menjawab: apa yang terjadi, di mana, oleh siapa, sukses atau gagal, dan apa selanjutnya.

# roles/notify/tasks/slack.yml
---
- name: Kirim notifikasi Slack
  uri:
    url: "{{ slack_webhook_url }}"
    method: POST
    body_format: json
    body:
      attachments:
        - color: "{{ 'good' if status == 'success' else 'danger' }}"
          title: >
            {{ '✅' if status == 'success' else '❌' }}
            {{ title }}            
          fields:
            - title: Environment
              value: "{{ env | upper }}"
              short: true
            - title: Versi
              value: "{{ app_version }}"
              short: true
            - title: Waktu
              value: "{{ ansible_date_time.iso8601 }}"
              short: true
            - title: Oleh
              value: "{{ deployed_by | default('CI Pipeline') }}"
              short: true
            - title: Detail
              value: "{{ detail | default('') }}"
              short: false
          footer: "Ansible Deployment"
          ts: "{{ ansible_date_time.epoch }}"
  delegate_to: localhost
  run_once: true
  no_log: true
  when: slack_webhook_url is defined

Perhatikan detail-detail kecil yang memiliki dampak besar. delegate_to: localhost memastikan HTTP request dilakukan dari control node, bukan dari managed host (managed host biasanya tidak memiliki akses internet keluar). run_once: true mencegah notifikasi dikirim sekali per host — jika playbook berjalan di 20 server, kita tentu tidak ingin mendapatkan 20 pesan yang identik. no_log: true mencegah URL webhook (yang merupakan secret) muncul di log CI. when: slack_webhook_url is defined membuat role ini aman dipanggil bahkan jika webhook belum dikonfigurasi — role akan menjadi no-op, bukan gagal.

Pemakaian di playbook deployment biasanya terjadi di post_tasks (sukses) dan rescue (gagal):

# Di post_tasks playbook deploy
post_tasks:
  - name: Notifikasi deployment sukses
    include_role:
      name: notify
      tasks_from: slack.yml
    vars:
      status: success
      title: "Deployment Berhasil — {{ app_name }}"
      detail: >
        Versi {{ app_version }} sukses di-deploy ke
        {{ ansible_play_hosts | length }} server.        
    run_once: true

rescue:
  - name: Notifikasi deployment gagal
    include_role:
      name: notify
      tasks_from: slack.yml
    vars:
      status: failure
      title: "Deployment GAGAL — {{ app_name }}"
      detail: >
        {{ ansible_failed_result.msg | default('Error tidak diketahui') }}
        di {{ inventory_hostname }}        
    run_once: true

Rescue block di Ansible menangkap error di task manapun, lalu mengirim notifikasi failure. Detail yang dikirim adalah ansible_failed_result.msg — pesan error mentah dari Ansible. Untuk debugging yang lebih mudah, tambahkan juga ansible_failed_result.task (nama task yang gagal) dan ansible_failed_result.results (kalau ada loop). Penerima tidak perlu membuka log CI hanya untuk mengetahui di mana deploy-nya gagal.

Jangan pernah menyertakan credential, password, atau token API di field detail. Ansible Vault melindungi secret di dalam playbook, tapi ansible_failed_result bisa berisi apa pun dari module — termasuk error message yang menyebut path sertifikat atau URL dengan kredensial. Selalu bersihkan (sanitasi) sebelum dikirim ke channel publik.

ANTI-PATTERN: Notifikasi Spam ke Semua Orang #

Salah satu jebakan paling umum di tim yang baru menerapkan notifikasi Slack adalah mengirim setiap deployment ke channel umum dan menggunakan mention @channel atau @here. Hasilnya: dalam dua minggu, channel tersebut akan di-mute oleh semua orang, dan notifikasi penting (seperti gagal di production) ikut terlewat.

# ANTI-PATTERN: notifikasi broadcast dengan @channel
- name: Notifikasi deployment
  uri:
    url: "{{ slack_webhook_url }}"
    body:
      text: "<!channel> Deployment {{ app_version }} selesai!"
# Masalah:
#   - 30 deployment per hari × @channel = 30 notifikasi yang mengganggu semua orang
#   - Setelah 1 minggu, semua orang mute channel
#   - Notifikasi production failure yang penting ikut terlewat
#   - Sulit filter — tidak ada cara membedakan deployment rutin dari alert

# BENAR: targeted notification dengan severity routing
- name: Kirim notifikasi berdasarkan severity
  include_role:
    name: notify
    tasks_from: "{{ 'slack-critical.yml'
                  if severity == 'critical'
                  else 'slack-routine.yml' }}"
  vars:
    title: "{{ title }}"
    detail: "{{ detail }}"
# Routine deployment → channel #deployments (tanpa mention, tanpa suara)
# Critical failure  → channel #incidents (mention @on-call, suara nyala)

Perbedaan krusial: deployment sukses di staging tidak membutuhkan mention siapa pun. Cukup taruh di channel #deployments (archive-only) tanpa membunyikan notifikasi. Deployment gagal di production membutuhkan perhatian on-call dalam 5 menit — kirim ke channel #incidents dengan mention on-call, dan kirim alert lewat PagerDuty jika tidak ada acknowledgement dalam 5 menit. Langkah ini memerlukan routing logic, tetapi trade-off-nya sepadan: tim kembali mempercayai notifikasi karena tidak ada yang mengganggu tanpa alasan yang valid.


Notifikasi Email untuk Summary Harian #

Untuk lingkungan dengan frekuensi deployment tinggi (staging bisa mencapai 30 deployment per hari), notifikasi per-deployment ke Slack akan menjadi spam. Solusinya: kirim satu email ringkasan harian ke manager atau mailing list tim, berisi agregat dari seluruh deployment hari itu. Email adalah format yang tepat untuk ini karena sifatnya yang asynchronous — manager bisa membaca pada pagi hari berikutnya dan tetap mendapatkan gambaran lengkap.

# playbooks/daily-deployment-summary.yml
---
- name: Generate dan kirim summary deployment harian
  hosts: localhost
  vars:
    log_file: /var/log/deployments.log
    report_date: "{{ ansible_date_time.date }}"

  tasks:
    - name: Baca log deployment hari ini
      shell: "grep {{ report_date }} {{ log_file }} || echo 'Tidak ada deployment hari ini'"
      register: today_deployments
      changed_when: false

    - name: Hitung statistik deployment
      set_fact:
        total_deployments: "{{ today_deployments.stdout_lines | select('search', 'SUCCESS|ROLLBACK|FAILED') | list | length }}"
        successful_deployments: "{{ today_deployments.stdout_lines | select('search', 'SUCCESS') | list | length }}"
        failed_deployments: "{{ today_deployments.stdout_lines | select('search', 'FAILED|ROLLBACK') | list | length }}"

    - name: Kirim email summary
      community.general.mail:
        host: "{{ smtp_host }}"
        port: "{{ smtp_port | default(587) }}"
        username: "{{ smtp_username }}"
        password: "{{ vault_smtp_password }}"
        to: "{{ deployment_report_recipients }}"
        subject: "Deployment Summary {{ report_date }} — {{ successful_deployments }}/{{ total_deployments }} sukses"
        subtype: html
        body: |
          <h2>Deployment Summary {{ report_date }}</h2>
          <table border="1" cellpadding="5">
            <tr><td><b>Total Deployment</b></td><td>{{ total_deployments }}</td></tr>
            <tr><td><b>Sukses</b></td><td style="color:green">{{ successful_deployments }}</td></tr>
            <tr><td><b>Gagal/Rollback</b></td><td style="color:red">{{ failed_deployments }}</td></tr>
          </table>
          <h3>Detail:</h3>
          <pre>{{ today_deployments.stdout }}</pre>          
      no_log: true

Format email di atas sengaja menggunakan HTML (bukan plain text) — supaya manager yang non-teknis bisa langsung melihat angka summary dalam tabel. Namun, kita tetap menyertakan log detail dalam <pre> agar engineer yang menerima email yang sama bisa langsung melakukan inspeksi. Subjek email menggunakan format yang konsisten — Deployment Summary {tanggal} — {sukses}/{total} sukses — sehingga thread email di inbox bisa di-filter dengan mudah.

Untuk tim yang sepenuhnya pindah ke observability dashboard (Grafana, Datadog), email summary bisa diganti dengan posting otomatis ke dashboard. Namun, banyak manager dan stakeholder non-teknis masih lebih menyukai email — mereka tidak masuk (login) ke dashboard setiap hari. Email tetap merupakan format yang efektif untuk komunikasi asinkron antar shift atau lintas zona waktu.

ANTI-PATTERN: Plaintext Credential di Notifikasi #

Kesalahan keamanan yang sering tidak disadari: notifikasi Slack atau email bisa saja membocorkan credential jika pesan error atau output task tidak disanitasi. Ansible tidak otomatis menyembunyikan (redact) nilai variabel di ansible_failed_result, sehingga pesan error yang menyebutkan connection string database, API key, atau path sertifikat bisa masuk ke notifikasi publik.

# ANTI-PATTERN: kirim error mentah ke Slack tanpa sanitasi
- name: Notifikasi failure
  uri:
    url: "{{ slack_webhook_url }}"
    body:
      text: "Deploy gagal: {{ ansible_failed_result }}"
# Output yang bocor:
#   "Deploy gagal: {
#     'msg': 'Failed to connect to postgres://user:s3cr3t@db-01:5432/app',
#     ...
#   }"
# → password database bocor ke channel Slack

# BENAR: redaksi field sensitif sebelum kirim
- name: Sanitasi error message
  set_fact:
    safe_error: >
      {{ ansible_failed_result
         | regex_replace('://[^@]+@', '://***@')
         | regex_replace('(?i)(password|token|api_key|secret)=[\w\-\.]+', '\1=***')
         | truncate(500) }}      

- name: Notifikasi failure (sanitized)
  uri:
    url: "{{ slack_webhook_url }}"
    body:
      text: "Deploy gagal: {{ safe_error }}"
# Output aman:
#   "Deploy gagal: Failed to connect to postgres://***@db-01:5432/app"

Pola regex_replace di atas menangani dua jenis kebocoran paling umum: credential di connection string (://user:pass@host) dan query parameter (password=...&token=...). Truncate di 500 karakter memastikan payload Slack tidak overflow. Untuk secret yang lebih panjang, terapkan no_log: true di task yang menghasilkan error, dan jangan pernah masukkan ke vars yang akan di-render ke message.

Pendekatan yang lebih aman secara struktural adalah menggunakan Ansible Vault untuk semua credential, dan mengonfigurasi callback plugin no_log secara global — ini akan otomatis menyembunyikan nilai variabel yang di-tag no_log: true di semua output Ansible, termasuk pesan error. Namun, bahkan dengan callback plugin, sanitasi eksplisit di notifikasi tetap penting sebagai defense in depth.


Deployment Changelog Otomatis dari Git #

Salah satu pertanyaan paling sering saat deployment adalah “apa yang berubah?”. Daripada mengandalkan ingatan developer mengenai commit apa saja yang di-merge, kita bisa men-generate changelog otomatis menggunakan git log di antara dua tag atau commit. Hasilnya adalah daftar commit yang objektif dan bisa diverifikasi, ditambah dengan metadata yang berguna (author, timestamp, hash pendek untuk cross-reference ke GitHub/GitLab).

# tasks/generate-changelog.yml
---
- name: Generate changelog antara versi
  shell: |
    git log \
      v{{ previous_version }}..v{{ current_version }} \
      --oneline \
      --no-merges \
      --format="- %h %s (%an)"    
  args:
    chdir: "{{ app_src_dir }}"
  register: changelog_raw
  changed_when: false
  delegate_to: localhost

- name: Set changelog fact
  set_fact:
    deployment_changelog: "{{ changelog_raw.stdout }}"

- name: Kirim changelog ke Slack
  uri:
    url: "{{ slack_webhook_url }}"
    method: POST
    body_format: json
    body:
      text: "*Changelog v{{ previous_version }} → v{{ current_version }}*"
      attachments:
        - color: good
          text: "{{ deployment_changelog | truncate(2000) }}"
  delegate_to: localhost
  no_log: true
  when:
    - deployment_changelog | length > 0
    - slack_webhook_url is defined

Flag --no-merges menyaring merge commit (yang biasanya hanya berisi pesan seperti “Merge branch ‘main’ into feature/x”), sehingga changelog hanya menampilkan commit yang membawa perubahan kode nyata. Format "- %h %s (%an)" menghasilkan satu baris per commit berisi hash pendek, subject, dan author. Truncate di 2000 karakter dilakukan karena Slack attachment memiliki batas ukuran; untuk changelog yang panjang, kita bisa memisahkannya menjadi beberapa message atau mengunggahnya sebagai file.

Untuk changelog yang lebih terstruktur, kita bisa melakukan parse terhadap subject commit yang mengikuti Conventional Commits (feat:, fix:, chore:, BREAKING CHANGE:) dan mengelompokkannya per kategori. Hal ini memerlukan parsing tambahan menggunakan awk atau script Python, tetapi hasilnya akan jauh lebih mudah dibaca — stakeholder non-teknis bisa langsung mengetahui apakah ada breaking change tanpa harus membaca setiap commit.


Integrasi dengan Sistem Eksternal #

Notifikasi deployment tidak harus berakhir di Slack. Banyak sistem yang lebih bermanfaat jika menerima event deployment sebagai input terstruktur: Jira bisa secara otomatis menambahkan komentar pada issue yang di-deploy, Datadog bisa menambahkan annotation di dashboard metrik, sistem internal bisa memperbarui status board, dan audit log bisa mencatat siapa yang melakukan deploy beserta versinya.

Update Jira Issues yang Ada di Changelog #

Jika tim kita menggunakan Jira dan mengikuti conventional commits (atau menuliskan issue ID di subject commit), setiap deployment bisa otomatis menambahkan komentar ke issue yang relevan. Langkah ini menutup alur komunikasi: developer mengetahui issue-nya sudah di-deploy tanpa harus mengecek pipeline secara manual.

# Catat deployment di Jira
- name: Tambahkan komentar di Jira issues yang ada di changelog
  uri:
    url: "https://company.atlassian.net/rest/api/3/issue/{{ item }}/comment"
    method: POST
    user: "{{ jira_username }}"
    password: "{{ vault_jira_token }}"
    force_basic_auth: true
    body_format: json
    body:
      body:
        type: doc
        version: 1
        content:
          - type: paragraph
            content:
              - type: text
                text: "Deployed to {{ env }} in version {{ app_version }}"
    status_code: [201, 400]   # 400 jika issue tidak ditemukan — tidak masalah
  loop: "{{ jira_issues_in_changelog }}"
  no_log: true
  when: jira_issues_in_changelog | length > 0

status_code: [201, 400] adalah pattern yang sangat berguna untuk integrasi eksternal — task dianggap sukses jika API mengembalikan status 201 (created) atau 400 (issue tidak ditemukan). Ini mencegah satu issue ID yang salah tulis menggagalkan seluruh alur deployment. Loop berjalan untuk setiap issue ID yang terdeteksi di changelog; jika tidak ada issue ID, when clause membuat task ini di-skip tanpa menimbulkan error.

Catat Deployment Event di Datadog #

Datadog dan platform observability modern menerima event API yang akan muncul sebagai annotation di grafik metrik. Ini sangat berguna untuk korelasi: saat metrik latency tiba-tiba naik, kita bisa langsung melihat di grafik “oh, ada deployment 10 menit sebelumnya” — dan mengetahui apakah kenaikan itu terkait dengannya.

# Catat deployment event di Datadog
- name: Catat deployment event di Datadog
  uri:
    url: "https://api.datadoghq.com/api/v1/events"
    method: POST
    headers:
      DD-API-KEY: "{{ vault_datadog_api_key }}"
    body_format: json
    body:
      title: "Deployment: {{ app_name }} v{{ app_version }}"
      text: "Deploy ke {{ env }} oleh {{ deployed_by | default('pipeline') }}"
      tags:
        - "env:{{ env }}"
        - "service:{{ app_name }}"
        - "version:{{ app_version }}"
      alert_type: info
      source_type_name: ansible
  delegate_to: localhost
  no_log: true

Tags di sini mengikuti konvensi observability (env:, service:, version:) sehingga event deployment bisa di-filter dan di-agregasikan di Datadog. alert_type: info (bukan error atau warning) menandai event ini sebagai info rutin. source_type_name: ansible membantu tim observability mengidentifikasi sumber event. Pola yang sama bisa digunakan untuk New Relic deployments API, Grafana annotations, atau Prometheus pushgateway dengan format payload yang sedikit disesuaikan.

Integrasi ke sistem eksternal lain yang umum digunakan:

  • GitHub Deployments API — muncul di PR dan commit sebagai “Environment deployed”
  • GitLab Deployments API — muncul di MR dan halaman environment
  • Statuspage — membuka insiden secara otomatis saat deploy ke produksi gagal
  • Confluence — membuat halaman post-mortem otomatis menggunakan template tertentu

Sequence Diagram: Alur Notifikasi Deployment End-to-End #

Sequence diagram berikut menunjukkan interaksi lengkap antar komponen saat satu deployment selesai. Catat di mana event dihasilkan, di mana diproses, dan di mana diterima — tiga lapisan yang harus dipisahkan agar sistem notifikasi tetap mudah dipelihara (maintainable).

sequenceDiagram
    participant Pipeline as "Ansible Pipeline"
    participant Log as "Deployment Log"
    participant Notify as "roles/notify"
    participant Slack as "Slack"
    participant Email as "SMTP"
    participant Datadog as "Datadog"
    participant Jira as "Jira"
    participant PagerDuty as "PagerDuty"

    Pipeline->>Log: "Tulis event 'DEPLOY success app=v2.3 env=prod'"
    Pipeline->>Notify: "panggil role notify (status=success, severity=info)"

    par Targeted routing
        Notify->>Slack: "POST webhook dengan formatted message"
    and Audit log
        Notify->>Log: "Append structured event dengan metadata"
    and Observability
        Notify->>Datadog: "POST event dengan tags"
    and Issue tracking
        Notify->>Jira: "Loop issue IDs, POST comment"
    end

    Note over Pipeline,PagerDuty: "Untuk failure di production"
    Pipeline->>Notify: "panggil role notify (status=failure, severity=critical)"
    Notify->>PagerDuty: "Trigger incident via Events API"
    Notify->>Slack: "Post ke #incidents dengan @on-call mention"

    Note over Email: "Summary periodik (cron harian)"
    Notify->>Email: "Kirim aggregate report"

Diagram ini menggambarkan bahwa satu event deployment memicu beberapa aksi paralel — bukan satu channel tunggal saja. Pola ini disebut fan-out notification: satu event, banyak listener. Keuntungannya: setiap sistem (Slack, Datadog, Jira) hanya menerima informasi yang relevan dengannya, dan jika salah satu channel down, channel lainnya tetap berfungsi. Risiko: kita harus memastikan setiap listener bersifat idempotent (jika pipeline di-retry, listener tidak akan mengirimkan event duplikat).


Audit Trail: Log Terstruktur untuk Setiap Deployment #

Untuk compliance, debugging, dan capacity planning, kita memerlukan log terstruktur dari setiap deployment — bukan hanya notifikasi real-time saja. Log ini adalah sumber kebenaran (source of truth) yang bisa di-query kapan saja: “berapa deployment kita bulan lalu?”, “siapa yang men-deploy versi X?”, atau “deployment mana yang paling sering gagal?”.

# roles/notify/tasks/log.yml
---
- name: Append deployment event ke log terstruktur
  lineinfile:
    path: /var/log/deployments.log
    line: >-
      {{ ansible_date_time.iso8601 }} |
      event={{ status | upper }} |
      app={{ app_name }} |
      version={{ app_version }} |
      env={{ env }} |
      actor={{ deployed_by | default('ci-pipeline') }} |
      pipeline={{ ci_pipeline_url | default('manual') }} |
      commit={{ git_sha | default('unknown') }} |
      duration={{ deployment_duration | default(0) }}s |
      hosts={{ ansible_play_hosts | default(['localhost']) | length }}      
    create: true
    state: present
  delegate_to: localhost
  run_once: true

Format key=value di setiap baris sangat mudah di-parse dengan awk, grep, atau tool log management (Loki, Elasticsearch). Field actor, pipeline, dan commit wajib disertakan — ini adalah tiga jawaban untuk “siapa, dari mana, commit apa” yang selalu ditanyakan saat insiden terjadi. duration membantu mengidentifikasi deployment yang mulai melambat (bisa menjadi early warning untuk masalah dependency). hosts adalah jumlah server yang di-deploy, berguna untuk keperluan audit (jika deploy ke production hanya berjalan di 1 server padahal biasanya 10, itu tandanya ada sesuatu yang salah).

Log ini kemudian bisa di-parse oleh script Python atau Ansible playbook terpisah untuk men-generate dashboard atau laporan bulanan.


Dashboard Deployment History #

Log terstruktur saja tidak cukup untuk visualisasi. Tim membutuhkan dashboard yang menampilkan tren deployment dari waktu ke waktu: sukses vs gagal, durasi rata-rata, dan aplikasi yang paling sering di-deploy. Dashboard ini membantu dalam dua hal: pertama, retrospective analysis (kenapa bulan lalu banyak terjadi failure?). Kedua, capacity planning (deployment rate meningkat 3x — apakah infrastruktur pipeline masih sanggup menangani?).

# playbooks/generate-deployment-report.yml
---
- name: Generate HTML deployment report
  hosts: localhost
  tasks:
    - name: Baca semua log deployment
      slurp:
        src: /var/log/deployments.log
      register: raw_log

    - name: Parse log deployment
      set_fact:
        deployments: >-
          {{ raw_log.content | b64decode | split('\n')
             | select('match', '.*SUCCESS|.*FAILED|.*ROLLBACK')
             | list }}          

    - name: Generate HTML report
      template:
        src: deployment-report.html.j2
        dest: /var/www/html/deployments/index.html

    - name: Deploy report ke server internal
      copy:
        src: /var/www/html/deployments/index.html
        dest: /var/www/reports/deployments.html
        remote_src: false

Pendekatan di atas menggunakan Jinja2 template untuk me-render HTML statis dari log, kemudian di-host di web server internal. Ini adalah pendekatan yang simpel dan hemat — tidak memerlukan database atau backend dinamis yang rumit. Untuk tim yang sudah memiliki Grafana atau Datadog, dashboard bisa di-generate lebih baik di sana: deploy event dari Datadog Events API bisa di-agregasikan per hari/minggu/bulan, dan metrik seperti success rate dihitung langsung menggunakan query PromQL/LogQL.

Untuk tim yang sudah menjalankan stack observability (lihat artikel Alerting, Health Check, dan SLO & SLA), manfaatkan event deployment yang sudah dikirim ke Datadog/New Relic sebagai annotation. Kita tidak memerlukan dashboard terpisah — cukup buat dashboard yang melakukan overlay metrik aplikasi dengan deployment event, dan kita bisa melihat korelasi secara langsung.

ANTI-PATTERN: Tidak Ada Audit Trail Deployment #

Kesalahan klasik yang baru disadari saat terjadi insiden besar: tim tidak tahu siapa yang melakukan deploy kapan dan apa yang berubah. Slack history sudah terlewat karena batas limit scroll-back, email summary mingguan sudah ditimpa, dan tidak ada cara lain untuk mencari tahu jawabannya. Investigasi menjadi teka-teki misterius yang harus dipecahkan saat production sedang down.

# ANTI-PATTERN: deployment tidak meninggalkan jejak
- name: Deploy
  command: ansible-playbook -i inv/prod site.yml
  # Tidak ada logging, tidak ada tagging, tidak ada metadata
  # Saat ada pertanyaan "kapan versi 2.3 di-deploy?" jawabnya "coba cek git log"

# BENAR: setiap deployment meninggalkan audit trail terstruktur
- name: Pre-deployment: tulis metadata
  copy:
    content: |
      version={{ app_version }}
      deployed_at={{ ansible_date_time.iso8601 }}
      deployed_by={{ lookup('env', 'CI_JOB_URL') | default(lookup('env', 'USER')) }}
      pipeline={{ lookup('env', 'CI_PIPELINE_URL') | default('manual') }}
      git_sha={{ lookup('env', 'CI_COMMIT_SHA') | default(lookup('pipe', 'git rev-parse HEAD')) }}      
    dest: /opt/app/DEPLOYMENT_INFO
    mode: '0644'

- name: Append ke audit log
  lineinfile:
    path: /var/log/deployments.log
    line: "{{ ansible_date_time.iso8601 }} DEPLOY app={{ app_name }} version={{ app_version }} by={{ ansible_user_id }}"
    create: true

Tiga layer audit yang saling melengkapi: (1) runtime metadata di /opt/app/DEPLOYMENT_INFO di setiap server, sehingga kita bisa melakukan SSH ke server mana pun dan mengetahui apa yang sedang berjalan. (2) centralized log di /var/log/deployments.log yang bisa di-agregasikan dan di-query. (3) integrasi dengan observability (Datadog events) yang terlihat langsung di dashboard metrik. Ketiganya memiliki kegunaan yang berbeda — runtime metadata untuk inspeksi per-server, log untuk query historis, dan observability untuk korelasi dengan metrik performa aplikasi.


Kapan Beralih ke Platform Notifikasi Khusus #

Untuk tim kecil (5-10 deployment per minggu), role notify Ansible yang kita bahas di artikel ini sudah lebih dari cukup. Untuk tim yang lebih besar atau yang memiliki compliance requirement ketat, platform khusus bisa memberikan nilai tambah:

Tetap gunakan role notify Ansible jika:
  ✓ Tim kecil-menengah, 1-10 deployment per minggu per aplikasi
  ✓ Channel notifikasi <= 4 (Slack, email, PagerDuty, webhook)
  ✓ Tidak ada compliance requirement untuk immutable audit trail
  ✓ Tim tidak memiliki dedicated platform engineer

Pertimbangkan platform khusus (Spinnaker, Argo CD Notifications, Dispatch) jika:
  ✗ Multi-cloud atau multi-cluster deployment yang kompleks
  ✗ Compliance mengharuskan audit trail dengan cryptographic signing
  ✗ Tim memiliki 50+ microservices dengan notifikasi yang saling terkait
  ✗ Kita membutuhkan orkestrasi lintas pipeline (misal: menggabungkan
    dua event dari pipeline berbeda menjadi satu insiden)

Untuk kebanyakan tim yang menggunakan Ansible, role notify sudah cukup memberikan manfaat yang optimal. Platform khusus baru memberikan ROI positif pada skala yang sangat besar atau jika terikat compliance yang sangat ketat.


Ringkasan #

  • Pisahkan tiga lapisan notifikasi: trigger (event pipeline), service (role notify), channel (Slack/Email/PagerDuty/webhook). Service terpusat mencegah duplikasi dan inkonsistensi.
  • Routing berdasarkan severity dan audience, bukan broadcast ke semua orang. Deployment sukses di staging → Slack #deployments (tanpa mention). Deployment gagal production → PagerDuty on-call + Slack #incidents dengan @on-call.
  • Sanitasi pesan error sebelum kirim ke channel publik. Gunakan regex_replace untuk credential di connection string dan query parameter, serta no_log: true di task yang menghasilkan secret.
  • Log deployment terstruktur (key=value) di /var/log/deployments.log adalah sumber kebenaran historis. Slack history bisa hilang dalam scroll-back, email summary mingguan ditimpa — log terstruktur tetap ada.
  • Integrasikan deployment event ke sistem yang sudah ada: Jira (auto-comment issue), Datadog (annotation), Confluence (post-mortem otomatis), PagerDuty (incident), GitHub Deployments API.
  • Setiap deployment harus meninggalkan jejak: runtime metadata di server, log terstruktur, observability event, dan notifikasi ke audience yang tepat. Saat insiden besar terjadi, jejak ini tidak ternilai harganya.
  • Pilihan channel yang tepat: Slack untuk real-time info, email untuk summary periodik, PagerDuty untuk respons on-call, webhook untuk integrasi programatik. Dokumentasikan routing decision tree di kode.
  • Audit trail bukan opsional — tanpa log deployment yang bisa di-query, investigasi insiden hanya menjadi tebakan belaka. Compliance, debugging, dan capacity planning semuanya membutuhkan data historis yang lengkap.

← Sebelumnya: Artifact Management   Berikutnya: Best Practice →

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