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.
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
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).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.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.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
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 /
Misal kita punya HTTP handler sebagai berikut.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
err := serviceA()
})
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)
}
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)
}
}
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.
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"]
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
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
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
.
Kemudian, cobalah akses tab Graph yang berada di navigation bar, kita akan melihat tampilan seperti ini.
Di kolom Expression, cobalah isi app_request_total
, kita akan mendapatkan hasil seperti ini.
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.
Top comments (0)