Tantangan Observabilitas Pipeline CI/CD
Sebagian besar pipeline CI/CD beroperasi secara opaque. Tim platform engineering sering kesulitan melacak regresi durasi build, efisiensi alokasi runner, hingga cache miss tanpa memasang agent APM berat yang memperlambat runtime. Pendekatan intrusif seperti membungkus setiap perintah CLI dengan wrapper profiler menambahkan latency eksekusi dan meningkatkan risiko kegagalan pipeline akibat kesalahan internal tools monitoring.
Solusinya adalah CI Telemetry Harvester pasif. Pola ini memisahkan proses harvesting menjadi proses background mandiri yang membaca data runtime dari platform metadata API, file sistem, atau stream log runner, lalu mengirimkan payload metrik secara asinkron tanpa menahan proses exit pipeline utama.
Arsitektur CI Telemetry Harvester Pasif
Harvester dirancang dengan prinsip zero-impact terhadap status build:
- Non-blocking lifecycle: Harvester berjalan di background via daemon process atau dijalankan khusus pada step pasca-eksekusi (post-job hook) menggunakan status flag
always(). - Fault tolerance: Kesalahan fatal pada koneksi jaringan, parsing metrik, atau API ingest tidak boleh menghasilkan exit code non-zero yang menggagalkan build runner.
- Deterministic payload: Menghasilkan skema JSON yang konsisten untuk metrik eksekusi, cache status, dan alokasi memori.
Implementasi Harvester CLI dengan Go
Go dipilih karena menghasilkan single binary portabel tanpa dependensi runtime tambahan. Tool berikut membaca penggunaan memori Linux via /proc/meminfo dan status cache dari environment variable GitHub Actions, lalu mengirimkan metrik dengan batas timeout ketat.
package main
import (
"bytes"
"context"
"encoding/json"
"net/http"
"os"
"strconv"
"strings"
"time"
)
type Payload struct {
WorkflowRunID string `json:"workflow_run_id"`
Repository string `json:"repository"`
DurationSec float64 `json:"duration_sec"`
MemoryUsedKB uint64 `json:"memory_used_kb"`
CacheHit bool `json:"cache_hit"`
StepStatuses map[string]string `json:"step_statuses"`
}
func getMemoryUsage() uint64 {
data, err := os.ReadFile("/proc/meminfo")
if err != nil {
return 0
}
var memTotal, memAvailable uint64
lines := strings.Split(string(data), "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
if fields[0] == "MemTotal:" {
memTotal, _ = strconv.ParseUint(fields[1], 10, 64)
}
if fields[0] == "MemAvailable:" {
memAvailable, _ = strconv.ParseUint(fields[1], 10, 64)
}
}
if memTotal > memAvailable {
return memTotal - memAvailable
}
return 0
}
func main() {
startTime := time.Now()
// Simulasi parsing waktu dari file trigger atau env
if startStr := os.Getenv("HARVESTER_START_TIME"); startStr != "" {
if epoch, err := strconv.ParseInt(startStr, 10, 64); err == nil {
startTime = time.Unix(epoch, 0)
}
}
payload := Payload{
WorkflowRunID: os.Getenv("GITHUB_RUN_ID"),
Repository: os.Getenv("GITHUB_REPOSITORY"),
DurationSec: time.Since(startTime).Seconds(),
MemoryUsedKB: getMemoryUsage(),
CacheHit: os.Getenv("CACHE_HIT_STATUS") == "true",
StepStatuses: map[string]string{
"build": os.Getenv("STEP_BUILD_STATUS"),
"test": os.Getenv("STEP_TEST_STATUS"),
},
}
body, err := json.Marshal(payload)
if err != nil {
os.Exit(0) // Silent exit untuk menghindari build failure
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, os.Getenv("TELEMETRY_ENDPOINT"), bytes.NewBuffer(body))
if err != nil {
os.Exit(0)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-CI-Token", os.Getenv("TELEMETRY_TOKEN"))
client := &http.Client{}
_, _ = client.Do(req) // Error diabaikan secara sengaja
os.Exit(0)
}
Integrasi pada GitHub Actions Workflow
Gunakan execution hook pada tahap akhir workflow. Dengan directive if: always(), step harvester tetap berjalan meski unit test atau tahapan kompilasi sebelumnya mengalami error.
name: CI Pipeline
on:
push:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Mark Pipeline Start
run: echo "HARVESTER_START_TIME=$(date +%s)" >> $GITHUB_ENV
- name: Checkout Repository
uses: actions/checkout@v4
- name: Restore Build Cache
id: cache-restore
uses: actions/cache@v4
with:
path: ~/.cache
key: ${{ runner.os }}-build-${{ hashFiles('**/go.sum') }}
- name: Build Application
id: step_build
run: make build
- name: Run Tests
id: step_test
run: make test
- name: Run Telemetry Harvester
if: always()
env:
CACHE_HIT_STATUS: ${{ steps.cache-restore.outputs.cache-hit }}
STEP_BUILD_STATUS: ${{ steps.step_build.outcome }}
STEP_TEST_STATUS: ${{ steps.step_test.outcome }}
TELEMETRY_ENDPOINT: ${{ secrets.INTERNAL_TELEMETRY_ENDPOINT }}
TELEMETRY_TOKEN: ${{ secrets.INTERNAL_TELEMETRY_TOKEN }}
run: |
chmod +x ./bin/harvester
./bin/harvester || true
Mitigasi Risiko Kegagalan Jaringan
Salah satu kesalahan fatal dalam telemetri CI adalah pipeline berwarna merah hanya karena backend ingest down atau mengalami timeout latensi. Tiga aturan mitigasi wajib:
- Strict Client-Side Timeout: Terapkan batas waktu maksimal 2 detik pada HTTP client. Koneksi dialer, TLS handshake, dan payload transmission harus selesai dalam rentang tersebut atau dihentikan paksa (fail-fast).
- Explicit Exit Code Masking: Pastikan script shell membungkus CLI dengan pipe
|| trueagar runner mengabaikan error exit code apa pun dari harvester. - UDP atau Fire-and-Forget Ingest: Untuk skala load pengujian yang masif, pertimbangkan pengiriman paket metrik via StatsD (UDP) ke sidecar runner agar transmisi I/O sama sekali tidak terpengaruh oleh state TCP handshake.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!