Tracing #
Distributed tracing adalah standar industri untuk memetakan bagaimana sebuah permintaan (request) mengalir melintasi puluhan microservice di lingkungan produksi. Namun, teknik observabilitas ini tidak hanya berguna untuk melacak kode aplikasi kita. Dalam dunia otomatisasi infrastruktur, sebuah proses deployment menggunakan Ansible Playbook sering kali terdiri dari ratusan task yang berjalan di puluhan server target secara paralel atau sekuensial. Ketika proses deployment mendadak melambat, log teks biasa di terminal tidak cukup membantu kita mengidentifikasi task mana yang menjadi bottleneck atau host mana yang mengalami latensi tinggi. Artikel ini membahas integrasi distributed tracing menggunakan OpenTelemetry dan Jaeger untuk memetakan eksekusi task Ansible Playbook secara visual, mengonfigurasi collector agent, serta menganalisis trace untuk mengoptimalkan otomatisasi kita.
Integrasi OpenTelemetry dan Jaeger #
Untuk membangun sistem pelacakan eksekusi, kita perlu memahami dua komponen utama dalam ekosistem distributed tracing modern: OpenTelemetry (OTel) dan Jaeger.
Perbandingan dengan Solusi APM Tradisional #
Sebelum OpenTelemetry menjadi standar industri, pengembang dan tim operasional harus memasang agen APM (Application Performance Monitoring) kepemilikan (proprietary) yang spesifik untuk setiap vendor (seperti New Relic, Dynatrace, atau Datadog). Agen-agen lama ini bersifat tertutup, memakan sumber daya sistem yang tinggi karena melakukan instrumentasi bytecode secara agresif, dan mengunci kita pada satu vendor saja (vendor lock-in).
OpenTelemetry memecah batasan tersebut dengan memisahkan instrumen kode dari sistem backend penyimpanan. Menggunakan API dan SDK OTel yang terstandarisasi secara terbuka, kita hanya perlu menginstrumentasikan aplikasi kita sekali saja. Data yang dihasilkan dapat dikirim ke kolektor lokal dan diteruskan ke berbagai backend visualisasi yang kita inginkan tanpa perlu memodifikasi kode sumber kembali.
Konsep Dasar Distributed Tracing #
Di tingkat dasar, distributed tracing bekerja dengan menghubungkan rantai kejadian melalui metadata trace:
- Trace ID: Pengenal unik sepanjang 128-bit yang mewakili satu alur transaksi utuh. Seluruh komponen yang terlibat dalam transaksi ini harus melampirkan Trace ID yang sama pada log dan telemetri mereka.
- Span: Representasi satu unit kerja tunggal. Span memiliki waktu mulai, waktu selesai, serta serangkaian atribut kustom (tags) yang memberikan konteks tambahan.
- Span ID: Pengenal unik sepanjang 64-bit untuk membedakan satu unit kerja dengan unit kerja lainnya di dalam satu trace yang sama.
- Parent ID: Pengenal yang menunjuk pada Span ID yang memicu unit kerja saat ini. Hubungan parent-child inilah yang memungkinkan sistem visualisasi menyusun diagram pohon (waterfall diagram) yang presisi.
Di dalam ekosistem otomatisasi kita, kita mengintegrasikan keduanya agar Ansible dapat mengekspor detail eksekusi setiap modul langsung ke penampung trace tanpa merusak jalannya playbook:
flowchart TD
subgraph PlaybookRun["Proses Eksekusi Playbook"]
A["Ansible CLI (Playbook Run)"] -->|"Mulai Task"| B["Callback Plugin (OTel)"]
B -->|"Buat root span"| C["Play Span"]
C -->|"Buat child span"| D["Task Span (e.g. Apt Install)"]
end
subgraph TracingPipeline["Jalur Data Tracing"]
B -->|"Kirim OTLP (gRPC)"| E["OpenTelemetry Collector Agent"]
E -->|"Batch & Export"| F["Jaeger Backend"]
F -->|"Visualisasi"| G["Jaeger UI / Grafana"]
end
Ansible controller akan bertindak sebagai produsen data (data producer). Melalui plugin callback, ia mengirimkan span setiap kali sebuah play dimulai, task dijalankan, atau handler dipicu. Agen OpenTelemetry Collector menerima data ini secara lokal, lalu meneruskannya ke server Jaeger pusat untuk diindeks.
Setup Collector Agents dengan Ansible #
Langkah pertama dalam implementasi ini adalah menyebarkan backend Jaeger dan agen OpenTelemetry Collector di server monitoring kita menggunakan otomatisasi Ansible.
1. Deploy Jaeger Backend #
Kita akan menjalankan Jaeger All-in-One menggunakan Docker agar proses instalasi dan konfigurasi database penyimpanan memori lokal berjalan dengan cepat.
# playbooks/deploy_jaeger.yml
---
- name: Deploy Jaeger Tracing Backend
hosts: monitoring_servers
become: true
tasks:
- name: Pastikan Docker sudah terpasang
apt:
name: docker.io
state: present
update_cache: true
- name: Jalankan kontainer Jaeger All-in-One
docker_container:
name: jaeger
image: jaegertracing/all-in-one:1.57
state: started
restart_policy: always
published_ports:
- "16686:16686" # Port HTTP untuk Jaeger Web UI
- "4317:4317" # Port OTLP gRPC receiver
- "4318:4318" # Port OTLP HTTP receiver
env:
COLLECTOR_OTLP_ENABLED: "true"
2. Deploy OpenTelemetry Collector Agent #
Kita memasang OpenTelemetry Collector di sisi server controller atau gateway jaringan untuk mengumpulkan, memproses, dan meneruskan span eksekusi ke Jaeger.
# roles/otel_collector/tasks/main.yml
---
- name: Unduh paket deb OTEL Collector
get_url:
url: "https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.98.0/otelcol-contrib_0.98.0_linux_amd64.deb"
dest: "/tmp/otelcol.deb"
mode: '0640'
- name: Pasang paket deb OTEL Collector
apt:
deb: "/tmp/otelcol.deb"
state: present
- name: Deploy file konfigurasi otelcol
template:
src: otel-collector-config.yaml.j2
dest: /etc/otelcol-contrib/config.yaml
owner: otelcol-contrib
group: otelcol-contrib
mode: '0640'
notify: Restart otelcol-contrib
- name: Pastikan layanan otelcol berjalan dan aktif
systemd:
name: otelcol-contrib
state: started
enabled: true
Berikut adalah file template konfigurasi /etc/otelcol-contrib/config.yaml yang kita gunakan untuk mengarahkan data ke Jaeger. Kita menyertakan beberapa prosesor standar industri seperti memory_limiter untuk mencegah agen kehabisan memori (out-of-memory) saat menangani beban tinggi, serta batch untuk mengelompokkan data sebelum dikirimkan ke jaringan:
{# roles/otel_collector/templates/otel-collector-config.yaml.j2 #}
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 75
spike_limit_percentage: 15
batch:
timeout: 1s
send_batch_size: 256
send_batch_max_size: 512
exporters:
otlp/jaeger:
endpoint: "{{ jaeger_internal_url }}:4317"
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp/jaeger]
Tracing Playbook Tasks: Menelusuri Siklus Ansible #
Bagian terpenting dari panduan ini adalah mengonfigurasi Ansible agar secara aktif mengirimkan data tracing ke kolektor kita. Sejak versi 2.14+, Ansible menyediakan plugin callback native berbasis OpenTelemetry yang dapat kita aktifkan dengan mudah.
1. Konfigurasi ansible.cfg
#
Kita harus mengaktifkan plugin callback tersebut di file konfigurasi proyek kita agar Ansible memuat modul telemetri sebelum mengeksekusi inventaris server. Edit file ansible.cfg proyek kita:
# ansible.cfg
[defaults]
# Mengaktifkan callback otel untuk pengiriman trace
callbacks_enabled = community.general.opentelemetry
stdout_callback = yaml
2. Konfigurasi Environment Variables untuk Callback OTel #
Plugin callback OpenTelemetry di Ansible membaca konfigurasi pengiriman data melalui variabel lingkungan standar W3C. Sebelum menjalankan playbook, kita harus mengekspor variabel-variabel tersebut untuk mengarahkan span ke endpoint OTEL Collector:
# Set endpoint kolektor lokal kita (menggunakan protokol gRPC)
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"
# Berikan nama layanan yang mencerminkan tugas otomatisasi kita
export OTEL_SERVICE_NAME="ansible-infrastruktur-deploy"
# Tambahkan metadata kustom ke seluruh trace
export OTEL_RESOURCE_ATTRIBUTES="environment=production,executor=jenkins-agent-01"
3. Integrasi Pipeline CI/CD (Jenkinsfile) #
Dalam alur kerja modern, eksekusi Ansible Playbook hampir selalu dipicu dari sistem CI/CD terpusat. Kita dapat mengintegrasikan variabel lingkungan OpenTelemetry ini ke dalam pipeline Jenkins kita, menangkap Trace ID dari stdout, dan menampilkan tautan langsung ke dashboard Jaeger agar tim pengembang dapat melakukan penelusuran mandiri saat deployment lambat.
Berikut adalah contoh snippet tahap eksekusi di dalam file Jenkinsfile:
stage('Ansible Deploy') {
environment {
OTEL_EXPORTER_OTLP_ENDPOINT = 'http://otel-collector.internal:4317'
OTEL_SERVICE_NAME = "ansible-pipeline-${env.JOB_NAME}"
OTEL_RESOURCE_ATTRIBUTES = "jenkins.build_number=${env.BUILD_NUMBER},env=production"
}
steps {
script {
// Jalankan playbook dan rekam outputnya
sh 'ansible-playbook -i inventories/production playbooks/deploy_app.yml'
// Catat pesan bantuan untuk tim operasi
echo "----------------------------------------------------------------------"
echo "Deployment trace dikirimkan ke OpenTelemetry Collector."
echo "Untuk melihat analisis visual durasi eksekusi task, buka Jaeger UI:"
echo "http://jaeger.internal.zone:16686/search?service=ansible-pipeline-${env.JOB_NAME}"
echo "----------------------------------------------------------------------"
}
}
}
Memetakan Execution Trace dan Menganalisis Bottleneck #
Dengan tersimpannya trace di Jaeger, kita dapat mulai memetakan eksekusi dan menganalisis mengapa suatu task berjalan lambat. Keuntungan terbesar dari visualisasi timeline adalah kita dapat melihat tumpang-tindih eksekusi task yang berjalan secara paralel ketika kita mengonfigurasi parameter forks di Ansible.
Berikut adalah sequence diagram yang menggambarkan bagaimana trace span dibuat dan dipropagasikan saat Ansible Controller mengeksekusi playbook di dua host target secara paralel:
sequenceDiagram
participant CLI as "Ansible Controller CLI"
participant CB as "OTel Callback Plugin"
participant COL as "OTel Collector"
participant H1 as "Target Host 1 (Web)"
participant H2 as "Target Host 2 (DB)"
CLI->>CB: "Playbook start: deploy.yml"
CB->>COL: "Start root span (deploy.yml)"
CLI->>H1: "Jalankan Task 1 (Setup Nginx)"
CB->>COL: "Start child span (Setup Nginx - Host 1)"
H1-->>CLI: "Task 1 completed"
CB->>COL: "End child span (Setup Nginx - Host 1)"
CLI->>H2: "Jalankan Task 2 (Setup PostgreSQL)"
CB->>COL: "Start child span (Setup DB - Host 2)"
H2-->>CLI: "Task 2 completed"
CB->>COL: "End child span (Setup DB - Host 2)"
CLI->>CB: "Playbook finished"
CB->>COL: "End root span (deploy.yml)"
Atribut Span yang Dihasilkan Ansible #
Setiap span yang dikirimkan oleh callback Ansible memiliki serangkaian metadata standar yang sangat berharga untuk analisis filter query di Jaeger. Berikut adalah daftar atribut kunci yang dapat kita cari:
| Nama Atribut (Tag) | Deskripsi | Contoh Nilai |
|---|---|---|
ansible.playbook.name |
Nama file playbook yang dieksekusi | deploy_app.yml |
ansible.play.name |
Nama blok play di dalam playbook | Setup Web Server |
ansible.task.name |
Deskripsi task yang sedang berjalan | Install Nginx Package via APT |
ansible.task.action |
Nama modul Ansible yang digunakan | apt |
ansible.host |
Alamat IP atau hostname server target | web-server-01.internal |
ansible.result |
Status hasil akhir eksekusi task | changed, ok, failed, skipped |
Menganalisis Bottleneck Tersembunyi #
Saat memeriksa visualisasi trace di UI Jaeger, kita harus mencari pola-pola berikut untuk menemukan bottleneck:
- Gap Waktu Kosong (Blank Gaps): Jika terdapat jeda waktu kosong yang panjang di antara selesainya satu task dan dimulainya task berikutnya, hal ini menunjukkan adanya latensi pemrosesan internal pada Ansible Controller (misalnya, controller lambat dalam melakukan kompilasi template Jinja2 lokal yang sangat kompleks).
- Durasi Modul Tunggal yang Ekstrim: Jika task dengan modul
aptmemakan waktu hingga 3 menit, kita dapat memeriksa tag span untuk melihat apakah server target sedang menunggu penguncian package manager (dpkg lock) atau memiliki koneksi cermin (mirror repository) yang lambat. - Pola Serial yang Tidak Efisien: Jika kita melihat task berjalan secara berurutan (step-by-step sequential) pada 20 server, kita dapat mempertimbangkan untuk menaikkan nilai default
forksdiansible.cfg(misalnya, dari5menjadi20) agar task berjalan secara paralel dan menghemat waktu deployment hingga 75 persen.
Dengan melacak eksekusi playbook secara visual, kita tidak lagi menebak-nebak bagian mana yang menghambat otomatisasi kita.
Anti-Pattern dan Solusi dalam Tracing Playbook #
Implementasi distributed tracing pada alat otomatisasi seperti Ansible memiliki tantangan tersendiri yang berbeda dengan tracing aplikasi microservice.
1. Mengaktifkan Tracing untuk Setiap Eksekusi Ad-Hoc #
- Anti-Pattern: Mengonfigurasi environment variable OTel secara permanen di
/etc/environmentpada mesin lokal tim operasi. Setiap kali tim menjalankan perintah ad-hoc kecil sepertiansible all -m ping, data trace sampah dikirimkan ke Jaeger, memadati database penyimpanan dan mengaburkan trace deployment yang penting. - Solusi: Aktifkan tracing secara kondisional. Kita hanya boleh mengekspor variabel lingkungan OpenTelemetry di dalam skrip pipeline CI/CD (seperti Jenkins, GitLab CI, atau GitHub Actions) saat menjalankan playbook rilis resmi.
2. Hardcode Endpoint Kolektor di ansible.cfg #
- Anti-Pattern: Menuliskan alamat IP server OTEL Collector secara statis di dalam file
ansible.cfgproyek Git. Hal ini membuat konfigurasi menjadi tidak fleksibel ketika kita harus menjalankan playbook di lingkungan jaringan yang berbeda (misalnya, dari laptop lokal via VPN vs dari runner CI/CD internal). - Solusi: Biarkan file
ansible.cfghanya mendefinisikan pemuatan plugin callback, sementara alamat endpoint collector disuntikkan secara dinamis menggunakan variabel lingkunganOTEL_EXPORTER_OTLP_ENDPOINTsaat proses eksekusi dimulai.
3. Mengirimkan Data Tanpa Enkripsi (mTLS) Melintasi Jaringan Publik #
- Anti-Pattern: Mengirimkan trace OTLP secara langsung melalui jaringan WAN publik tanpa enkripsi (menggunakan HTTP biasa atau gRPC insecure). Attacker dapat melakukan sniffing data di tengah jalan dan melihat seluruh struktur internal server, nama task, dan metadata sistem kita.
- Solusi: Konfigurasikan TLS dan otentikasi mTLS pada OpenTelemetry Collector dan Jaeger. Pastikan sertifikat klien di-deploy ke server controller Ansible dan direferensikan dalam parameter konfigurasi transport.
Berikut adalah contoh konfigurasi eksportir yang aman menggunakan enkripsi TLS pada file OpenTelemetry Collector:
# Contoh konfigurasi eksportir terenkripsi di otel-collector-config.yaml
exporters:
otlp/secure-jaeger:
endpoint: "jaeger-prod.internal.zone:4317"
tls:
insecure: false
ca_file: "/etc/otelcol-contrib/certs/ca.crt"
cert_file: "/etc/otelcol-contrib/certs/client.crt"
key_file: "/etc/otelcol-contrib/certs/client.key"
4. Mengabaikan Overhead Jaringan pada Loop Task #
- Anti-Pattern: Menggunakan loop task Ansible (
with_itemsatauloop) untuk memproses ratusan item kecil, di mana setiap iterasi memicu pembuatan span baru. Hal ini menyebabkan ledakan jumlah span (span explosion) yang membebani jaringan controller. - Solusi: Sebisa mungkin gunakan modul Ansible yang mendukung operasi massal (bulk operations), seperti modul
aptdengan daftar paket langsung, alih-alih melakukan loop memanggil modulaptberulang kali. Ini tidak hanya mempercepat eksekusi tetapi juga menjaga ukuran trace tetap bersih dan ringkas.
Berikut adalah contoh perbandingan antara implementasi task loop yang buruk dan optimal:
# ANTI-PATTERN: Loop memicu pembuatan 3 span terpisah yang lambat
- name: Pasang paket utilitas satu per satu
apt:
name: "{{ item }}"
state: present
loop:
- curl
- htop
- git
# ✗ Memicu 3 koneksi OTLP terpisah dari controller ke collector
# BENAR: Operasi massal menghasilkan 1 span ringkas dan cepat
- name: Pasang paket utilitas secara massal
apt:
name:
- curl
- htop
- git
state: present
# ✓ Hanya memicu 1 span tunggal untuk keseluruhan instalasi paket
Dengan mengoptimalkan penulisan task, kita menjaga kestabilan sistem telemetri kita tanpa mengorbankan kedalaman informasi yang kita kumpulkan.
Ringkasan #
- Distributed tracing pada Ansible Playbook memberikan visibilitas visual terhadap alur eksekusi otomatisasi infrastruktur kita, mempermudah pelacakan bottleneck.
- OpenTelemetry menyediakan protokol standar (OTLP) yang menjembatani pengiriman data trace dari Ansible Controller ke berbagai backend visualisasi.
- Jaeger All-in-One dapat kita jalankan dengan cepat menggunakan Docker di server monitoring untuk menyimpan dan menganalisis trace span.
- Plugin Callback community.general.opentelemetry adalah modul native Ansible yang bertugas mencatat siklus hidup eksekusi dan mengekspornya sebagai span.
- Root Span dan Child Span memetakan hubungan hierarkis di Jaeger, memisahkan durasi eksekusi dari level playbook, play, hingga task individu.
- Optimasi Forks dan identifikasi blank gaps di UI Jaeger membantu kita memotong waktu deployment secara drastis melalui paralelisasi task.
- Aktivasi Selektif variabel lingkungan OTel di pipeline CI/CD mencegah database trace dipadati oleh aktivitas pengujian ad-hoc harian.
- Penggunaan Modul Massal (Bulk) menggantikan task loop sangat penting untuk mencegah terjadinya span explosion yang membebani memori controller.