DEV Community

Cover image for Instrumentasi Aplikasi Golang Menggunakan Prometheus
Muhammad Syukur Abadi
Muhammad Syukur Abadi

Posted on

Instrumentasi Aplikasi Golang Menggunakan Prometheus

Foto oleh Daniel Korpai on Unsplash

Mengapa Perlu Monitoring Aplikasi?

Tom Wilkie memperkenalkan metodologi USE (Utilization, Saturation, Error)untuk mengukur penggunaan perangkat keras seperti kecepatan pembacaan oleh disk (I/O), penggunaan memori, dan jaringan. Namun metodologi tersebut tidak menunjukkan performa aplikasi dan bagaimana pengguna berinteraksi dengan aplikasi. Oleh karena itu, ia mengenalkan metodologi RED (Rate, Error, Duration). Dengan menggunakan metodologi RED, pengembang bisa mengetahui bahwa durasi yang tinggi menandakan aplikasi berjalan lemot atau menjawab pertanyaan kapan request terbanyak terjadi dalam rentang waktu tertentu.

Tentang Prometheus

Prometheus adalah perangkat lunak yang dikembangkan oleh SoundCloud. Prometheus digunakan untuk memantau (monitoring) kinerja aplikasi dan memberi peringatan (alerting) jika terjadi kendala pada aplikasi. Prometheus mengumpulkan dan menyimpan data metrik (metrics) dalam format deret waktu (time series). Metrik disimpan menggunakan penanda waktu (timestamp) beserta label berupa pasangan kunci dan nilai (key-value pairs). Prometheus mengumpulkan data metrik dari aplikasi secara terus menerus atau biasa dikenal sebagai pull mechanism.

Berikut ini adalah arsitektur Prometheus.

Arsitektur Prometheus

Prometheus mengambil data metrik dengan cara melakukan pull atau scrape dari Prometheus target, di mana target ini bisa berupa aplikasi HTTP atau database. Prometheus mengetahui target aplikasi yang akan di-scrape melalui service discovery yang bisa dilakukan secara otomatis oleh Kubernetes atau kita mendefinisikan sendiri targetnya menggunakan configuration file. Metrik yang berhasil scrape oleh Prometheus bisa kita oleh menggunakan PromQL.

Metrik Prometheus

Prometheus memiliki 4 buah metrik yang digunakan secara spesifik untuk mengukur kinerja aplikasi

  1. Counter
    Counter merupakan metrik dengan sifat monoton naik, tidak bisa berkurang, namun nilainya bisa berubah menjadi 0. Counter bisa digunakan untuk memantau berapa request yang berhasil diproses dan jumlah galat (error).

  2. Gauge
    Gauge merupakan metrik yang hanya bisa naik atau turun. Gauge bisa digunakan untuk memantau konsumsi memori, konsumsi CPU, serta jumlah request yang terjadi secara konkuren.

  3. Histogram
    Histogram merupakan metrik yang mengumpulkan beberapa nilai hasil observasi dalam rentang tertentu yang bisa dikonfigurasi. Histogram bisa digunakan untuk memantau waktu yang dibutuhkan untuk menyelesaikan sebuah request dalam rentang waktu tertentu atau memantau ukuran request dalam rentang waktu tertentu.

  4. Summary

Instrumen Aplikasi Menggunakan Prometheus

Berikut ini adalah struktur folder yang akan kita gunakan untuk melakukan instrumentasi menggunakan Golang dan Docker.

Sebelum menjalankan docker compose up -d, pastikan struktur folder seperti ini.

|   docker-compose.yml
|   Dockerfile
|   go.mod
|   go.sum
|   main.go
|   prometheus.yml
Enter fullscreen mode Exit fullscreen mode

Sebelum melakukan instrumentasi, kita pasang beberapa package yang dibutuhkan menggunakan

go get github.com/prometheus/client_golang/prometheus/promauto /
github.com/prometheus/client_golang/prometheus /
Enter fullscreen mode Exit fullscreen mode

Misal kita punya HTTP handler sebagai berikut.

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        err := serviceA()
    })
Enter fullscreen mode Exit fullscreen mode

di dalam handler kita punya fungsi serviceA, di mana fungsi tersebut akan menjalankan logika bisnis, berinteraksi dengan koneksi eksternal seperti memanggil gRPC dari servis atau melakukan HTTP call ke aplikasi lain, hingga melakukan query ke database. Untuk mengetahui berapa request yang gagal atau berhasil diolah serta berapa lama waktu yang diperlukan untuk mengolah request oleh handler tersebut, kita melakukan instrumentasi menggunakan fungsi setMeter().

// main.go

var opsProcessed *prometheus.CounterVec

var opsDuration *prometheus.HistogramVec

func setMeter() {
    opsProcessed = promauto.NewCounterVec(
        prometheus.CounterOpts{
            Name: "app_request_total",
            Help: "Number of total request",
        },
        []string{"URL", "status"},
    )

    opsDuration = promauto.NewHistogramVec(
        prometheus.HistogramOpts{
            Name: "app_request_duration",
            Help: "Duration of a request",
        },
        []string{"URL", "status"},
    )

    prometheus.Register(opsProcessed)

    prometheus.Register(opsDuration)
}
Enter fullscreen mode Exit fullscreen mode

Pada fungsi di atas, kita mendeklarasikan opsProcessed dan opsDuration sebagai metrik yang akan digunakan untuk menginstrumentasi HTTP Server. opsProcessed digunakan untuk mengukur berapa banyak request yang diproses dan opsDuration digunakan untuk mengukur berapa lama waktu yang dibutuhkan untuk menyelesaikan sebuah request. Ketika menginisialisasi kedua metrik tersebut, terdapat argumen dengan tipe data []string. Argumen ini disebut sebagai label yang berguna untuk membedakan metrik dan akan kita gunakan untuk melakukan query di Prometheus nanti. Kita bisa mendefinisikan label sesuai kebutuhan, dan dalam contoh ini kita melakukan labeling untuk path dan HTTP response code. Panduan lengkap penggunaan label bisa dilihat di sini.

Berikut keseluruhan kode yang digunakan dalam main.go

// main.go

package main

var opsProcessed *prometheus.CounterVec

var opsDuration *prometheus.HistogramVec

func setMeter() {
    opsProcessed = promauto.NewCounterVec(
        prometheus.CounterOpts{
            Name: "app_request_total",
            Help: "Number of total request",
        },
        []string{"URL", "status"},
    )

    opsDuration = promauto.NewHistogramVec(
        prometheus.HistogramOpts{
            Name: "app_request_duration",
            Help: "Duration of a request",
        },
        []string{"URL", "status"},
    )

    prometheus.Register(opsProcessed)

    prometheus.Register(opsDuration)
}

func main() {
    setMeter()

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()

        respCode := 200

        defer func() {
            opsDuration.WithLabelValues("/", fmt.Sprint(respCode)).Observe(time.Since(start).Seconds())
        }()

        failQuery := r.URL.Query().Get("fail")

        if failQuery != "" {
            respCode = 500

            isFail, err := strconv.ParseBool(failQuery)
            if err != nil {
                w.WriteHeader(http.StatusInternalServerError)
                w.Write([]byte("error parse query"))

                log.Println("error parse query")

                opsProcessed.WithLabelValues("/", fmt.Sprint(http.StatusInternalServerError)).Inc()

                return
            }

            if isFail {
                w.WriteHeader(http.StatusInternalServerError)
                w.Write([]byte("encounter fail"))

                log.Println("encounter fail")

                opsProcessed.WithLabelValues("/", fmt.Sprint(http.StatusInternalServerError)).Inc()

                return
            }
        }

        w.Write([]byte("Hello, World!"))

        opsProcessed.WithLabelValues("/", fmt.Sprint(http.StatusOK)).Inc()
    })
    http.Handle("/metrics", promhttp.Handler())

    log.Println("Starting server on :9320")
    if err := http.ListenAndServe(":9320", nil); err != nil {
        log.Fatalf("could not start server: %v\n", err)
    }
}
Enter fullscreen mode Exit fullscreen mode

Perhatikan method WithLabelValues() ketika memanggil opsProcessed maupun opsDuration. Kita mengisi argumen pada method tersebut dengan / dan response code, karena ketika kita menginisiasi opsProcessed dan opsDuration, kita mendefinisikan label URL dan status. Jika jumlah argumen method tersebut tersebut kurang atau lebih dari 2, maka akan terjadi galat (error).

Jalankan server menggunakan go run main.go, buka browser, dan arahkan ke http://localhost:9320/ atau http://localhost:9320?fail=true, setelah itu coba akses http://localhost:9320/metrics. Di endpoint metrics kita bisa melihat metrik dan label yang telah kita definisikan di fungsi setMeter() sebagai berikut.

Endpoint Metrics

Kita akan menggunakan Docker dan Prometheus untuk melakukan visualisasi dan query metrik yang telah kita buat.

Menggunakan Prometheus di Docker

Sebelum kita melakukan query dan membuat visualisasi, kita akan membuat Dockerfile dari aplikasi Go yang telah kita buat. Berikut Dockerfilenya.

# Dockerfile

FROM golang:1.22-alpine AS build
ARG APP_VERSION=latest

WORKDIR /src/

COPY . /src/.

RUN CGO_ENABLED=0 go build -o /bin/go-loki ./main.go

# final - service
FROM alpine as api-service

COPY --from=build /bin/go-loki /bin/go-loki

# HTTP Port
EXPOSE 9320

ENTRYPOINT ["/bin/go-loki"]
Enter fullscreen mode Exit fullscreen mode

Kemudian buatlah configuration file untuk Prometheus dengan nama prometheus.yml. Kita akan mendefinisikan aplikasi apa saja yang akan di-scrape oleh Prometheus menggunakan configuration file ini. Berikut isi prometheus.yml.

# prometheus.yml

global:
  scrape_interval: 3s

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: 
        - "localhost:9090"

  - job_name: app 
    metrics_path: /metrics
    static_configs:
      - targets:
        - host.docker.internal:9320
Enter fullscreen mode Exit fullscreen mode

Perhatikan bagian scrape_configs.static_configs.targets. Pada bagian tersebut, kita menuliskan nilai host.docker.internal:9320, di mana nilai tersebut merujuk pada aplikasi yang telah kita Dockerize menggunakan Dockerfile. Karena kita akan menggunakan Docker untuk menjalankan Prometheus dan aplikasi, kita tidak menggunakan localhost:9320 sebagai nilai pada bagian scrape_configs.static_configs.targets, melainkan host.docker.internal:9320.

Berikutnya, buatlah file docker-compose.yml dengan isi sebagai berikut.

# docker-compose.yml

version: "3"

volumes:
  prometheus_data:

networks:
  observability:

services:
  prometheus:
    image: prom/prometheus:v2.24.0
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/usr/share/prometheus/console_libraries'
      - '--web.console.templates=/usr/share/prometheus/consoles'
    ports:
      - 9090:9090
    restart: always
    networks:
      - observability

  app:
    container_name: app
    build:
      context: .
      dockerfile: ./Dockerfile
    ports:
      - 9320:9320
    networks:
      - observability
Enter fullscreen mode Exit fullscreen mode

Jalankan docker compose up -d, kemudian cobalah akses http://localhost:9090/targets. Kita akan melihat app sebagai salah satu target seperti yang telah kita definisikan di bagian scrape_configs.static_configs.targets.

Target Prometheus

Kemudian, cobalah akses tab Graph yang berada di navigation bar, kita akan melihat tampilan seperti ini.

Graph Prometheus

Di kolom Expression, cobalah isi app_request_total, kita akan mendapatkan hasil seperti ini.

Prometheus Search Bar

Kita akan mendapatkan total request yang masuk ke endpoint localhost:9320 dengan response code yang berbeda. Response code 500 didapat ketika query fail pada endpoint bernilai true, dan 200 ketika bernilai false atau tidak diisi sama sekali. Kemudian, untuk mendapatkan rata-rata waktu yang dibutuhkan untuk menyelesaikan request, kita menggunakan query app_request_duration_sum / app_request_duration_count atau histogram_quantile(0.5, rate(app_request_duration_bucket[10m])). Kita akan mendapatkan hasil yang kurang lebih seperti ini di Prometheus jika kita menjalankan query di atas.

Histogram Quantile

Referensi

Image of Docusign

Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more