[Grafana] Opentelemetry: metric ๊ตฌ์„ฑํ•˜๊ธฐ

otel collector์™€ prometheus๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ธฐ๋ณธ metric์„ ๊ตฌ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ฐ„๋‹จํžˆ ์ •๋ฆฌํ•ด๋ณด๊ฒ ๋‹ค. ๊ธฐ๋ฐ˜ ํ™˜๊ฒฝ์€ docker compose๋‹ค.

Grafana dashboard์— ๋Œ€ํ•ด์„œ ์•ฝ๊ฐ„ ์•ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•œ๋‹ค.




otel collector

Trace์— ์‚ฌ์šฉํ•˜๋Š” Grafana Tempo ๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” ์ž์ฒด์ ์œผ๋กœ opentelemetry API ๊ทœ๊ฒฉ์„ ์ถฉ์กฑํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ถ”๊ฐ€์ ์ธ ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ํ•„์š”ํ•˜์ง€๋Š” ์•Š๋‹ค.

๋ฐ˜๋ฉด metric์—๋Š” ๋ณดํ†ต prometheus ๊ฐ™์€ ์‹œ๊ณ„์—ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, prometheus๋Š” opentelemetry์™€ ์•„๋ฌด๋Ÿฐ ์—ฐ๊ด€์ ๋„ ์—†๋‹ค.
๊ทธ๋ž˜์„œ ์š”์ฆ˜์€ prometheus๋กœ metric์„ ๊ตฌ์„ฑํ•œ๋‹ค๊ณ  ํ•˜๋ฉด otel-collector ๊ฐ™์€ ์ค‘๊ฐ„ ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„์„ ๋‘ฌ์„œ ๊ทœ๊ฒฉํ™”๋œ ํ˜•ํƒœ๋กœ ์ €์žฅํ•˜๋„๋ก ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค.

์ž, ๊ทธ๋Ÿผ ์‹œ์ž‘ํ•ด๋ณด์ž.
otel-collector๋ฅผ ๋„์šฐ๊ธฐ ์œ„ํ•œ config๋ฅผ ๋จผ์ € ๊ตฌ์„ฑํ–ˆ๋‹ค.

receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318

exporters:
  prometheus:
    endpoint: "0.0.0.0:8889"

service:
  pipelines:
    metrics:
      receivers: [otlp]
      exporters: [prometheus]
    traces:
      receivers: [otlp]
      exporters: [otlp]
  telemetry:
    metrics:
      address: 0.0.0.0:8888
      level: detailed

receivers๋Š” ์ด otel-collector๊ฐ€ ์–ด๋–ค ๋ฆฌ์Šค๋„ˆ๋ฅผ ์—ด์ง€๋ฅผ ์ •์˜ํ•˜๋Š” ๋ถ€๋ถ„์ด๋‹ค.
otel-collector๋Š” http์™€ grpc๋ฅผ ๋ชจ๋‘ ์ง€์›ํ•˜๋Š”๋ฐ, http๋Š” 4318, grpc๋Š” 4317 ํฌํŠธ๋ฅผ ์“ฐ๋Š”๊ฒŒ ๊ธฐ๋ณธ์ด๋‹ค.

exporters๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋””๋กœ ์ „์†กํ•˜๊ฒŒ ํ•ด์ค„์ง€๋ฅผ ์ •์˜ํ•˜๋Š” ๋ถ€๋ถ„์ด๋‹ค.
์œ ์˜ํ•  ์ ์€, ์ด๊ฒŒ ๋ฐ”๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ์ „์†ก์‹œํ‚ค๋Š”๊ฒŒ ์•„๋‹ˆ๋ž€ ๊ฒƒ์ด๋‹ค. 8889 ํฌํŠธ๋กœ export API๋ฅผ ์—ด์–ด๋†“๊ณ , ๊ทธ๊ฑธ ๋‹ค๋ฅธ metric ์ €์žฅ์†Œ๊ฐ€ ์Šคํฌ๋žฉํ•˜๊ฒŒ ํ•˜๋Š” ๊ตฌ์กฐ๋ฅผ ๊ฐ–๊ณ  ์žˆ๋‹ค.
๋‚˜๋จธ์ง€๋Š” prometheus ์ ˆ์—์„œ ์ด์–ด์„œ ๋‹ค๋ฃจ๊ฒ ๋‹ค.

์•„๋ฌดํŠผ ์ € ์„ค์ •์„ ๋ฐ›์•„์„œ ์ด๋ฏธ์ง€๋ฅผ ๋ง๊ฒŒ๋” Dockerfile์„ ๊ตฌ์„ฑํ•˜๊ณ 

FROM otel/opentelemetry-collector-contrib:latest

COPY ./otel-collector-config.yaml /etc/otel-collector-config.yaml

docker compose์—๋Š” ์ด๋ ‡๊ฒŒ ์ •์˜ํ–ˆ๋‹ค.

services:
  otel-collector:
    build:
      context: .
      dockerfile: Dockerfile.otel
    command: ["--config", "/etc/otel-collector-config.yaml"]
    ports:
      - "4317:4317"
      - "4318:4318"
      - "8888:8888"
    depends_on:
      - prometheus



Prometheus ๊ตฌ์„ฑ

์ด๋ฒˆ์—” prometheus๋ฅผ ๊ตฌ์„ฑํ•ด๋ณด์ž.
prometheus๊ฐ€ ์ฃผ์ฒด์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ๊ฐ€์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์—ฌ๊ธฐ์„œ๋Š” otel-collector๋ฅผ ์ฝ๋Š” ๋ถ€๋ถ„์ด ์ •์˜๋œ๋‹ค.

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'otel-collector'
    scrape_interval: 10s
    static_configs:
      - targets: ['otel-collector:8889']

15์ดˆ๋งˆ๋‹ค ํ•œ๋ฒˆ์”ฉ ๋Œ๋ฉด์„œ ์•„๊นŒ exporter๋กœ ์—ฐ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ฐ”๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ์œผ๋Š” ๊ฒƒ์ด๋‹ค.

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Dockerfile ๋ง๊ณ 

FROM prom/prometheus:latest

COPY ./prometheus.yaml /etc/prometheus.yaml

docker compose๋กœ ๋„์šฐ๋„๋ก ํ–ˆ๋‹ค.

services:
  prometheus:
    build:
      context: .
      dockerfile: Dockerfile.prometheus
    command:
      - --config.file=/etc/prometheus.yaml
      - --web.enable-remote-write-receiver
      - --enable-feature=exemplar-storage
      - --enable-feature=native-histograms
    volumes:
      - prometheus_data:/prometheus
    ports:
      - "9090:9090"

volumes:
  prometheus_data: 
    driver: local

๊ทธ๋ฆฌ๊ณ  ๊ทธ๋ผํŒŒ๋‚˜๋„ ํฌํ•จํ•ด์„œ ํ•จ๊ป˜ ๋„์›Œ๋ณด์ž.

  grafana:
    environment:
      - GF_PATHS_PROVISIONING=/etc/grafana/provisioning
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
    entrypoint:
      - sh
      - -euc
      - |
        mkdir -p /etc/grafana/provisioning/datasources
        cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
        apiVersion: 1
        datasources:
        - name: Prometheus
          type: prometheus
          uid: prometheus
          access: proxy
          orgId: 1
          url: http://prometheus:9090
          basicAuth: false
          isDefault: false
          version: 1
          editable: false
          jsonData:
            httpMethod: GET
        EOF
        /run.sh
    image: grafana/grafana:latest
    ports:
      - "13000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
      - grafana_etc:/etc/grafana

volumes:
  grafana_data:
    driver: local
  grafana_etc: 
    driver: local



์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ agent ๊ตฌ์„ฑ (go)

๊ทผ๋ฐ ์ €๊ฒƒ๋งŒ ๋œ๋  ์žˆ์œผ๋ฉด ๋ญํ•˜๊ฒ ๋‚˜? ๊ฐ„๋‹จํ•œ ์„œ๋ฒ„ ํ•˜๋‚˜ ๋„์›Œ์„œ ๋ฉ”ํŠธ๋ฆญ์„ ์Œ“๊ฒŒ ํ•ด๋ณด์ž.

๊ธฐ๋ณธ metric์€ ์„œ๋ฒ„์™€ ์—ฐ๊ด€์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ํ”„๋ ˆ์ž„์›Œํฌ์— ์ข…์†์ ์ด์ง€ ์•Š๋‹ค.
๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ดˆ๊ธฐํ™” ์ฝ”๋“œ๋งŒ ์‹ฌ์–ด์„œ ์ง„์ž…์ ์—์„œ ์‹คํ–‰ํ•˜๊ฒŒ ๋‘๋ฉด ๋์ด๋‹ค.

func initMetric() *metric.MeterProvider {
	ctx := context.Background()

	metricExporter, err := otlpmetrichttp.New(ctx, otlpmetrichttp.WithEndpointURL(
		"http://localhost:4318",
	), otlpmetrichttp.WithInsecure())
	if err != nil {
		panic(err)
	}

	meterProvider := metric.NewMeterProvider(
		metric.WithReader(metric.NewPeriodicReader(metricExporter, metric.WithInterval(3*time.Second))),
		metric.WithResource(resource.NewWithAttributes(
			semconv.SchemaURL,
			semconv.ServiceNameKey.String("go-server"),
			semconv.ServiceNamespaceKey.String("dev"),
		)),
	)
	if err != nil {
		panic(err)
	}
	otel.SetMeterProvider(meterProvider)

	err = runtime.Start(runtime.WithMinimumReadMemStatsInterval(time.Second))
	if err != nil {
		panic(err)
	}

	return meterProvider
}

์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๊ธฐ๋‹ค๋ ค๋ณด๋ฉด ๋ญ๊ฐ€ ์Œ“์ผ ๊ฒƒ์ด๋‹ค.

์‹œ๊ณ„์—ด ํŒ๋‚  ๊ฐ€์ ธ๋‹ค๊ฐ€ ์กฐํšŒํ•ด๋ณด๋ฉด

go ๋Ÿฐํƒ€์ž„ ๊ด€๋ จ๋œ ์ž์งˆ๊ตฌ๋ ˆํ•œ ์˜ต์…˜๋“ค์ด๋‚˜, up ๊ฐ™์€ ์ƒ์กด์‹ ํ˜ธ๋“ค์ด ์Œ“์—ฌ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

์ถ”๊ฐ€์ ์ธ ๊ตฌ์„ฑ์ด ์—†๋‹ค๋ฉด ๊ธฐ๋ณธ ๋ฉ”ํŠธ๋ฆญ์€ ์ด๋Ÿฐ ๋‹จ์ˆœํ•œ ์ •๋ณด๋“ค๋งŒ ๋‚ด๋ ค์ค€๋‹ค.

trace, API์— ๊ธฐ๋ฐ˜ํ•œ ์ˆ˜์น˜ ํ†ต๊ณ„๋“ค์ด ํ•„์š”ํ•˜๋‹ค๋ฉด spanmetric ๊ฐ™์€ ์ถ”๊ฐ€ ๊ตฌ์„ฑ์ด ํ•„์š”ํ•˜๋‹ค.
๊ทธ๊ฑด ๋‹ค์Œ ํฌ์ŠคํŠธ์—์„œ ๋ฐ”๋กœ ๋‹ค๋ฃจ๊ฒ ๋‹ค.