🔪

docker-compose로 Grafana, Loki 요리해보기

Created
Mar 25, 2024 10:02 AM
Tags
loki
grafana
HTTP App에서 발생한 로그 이벤트를 loki로 전달해 grafana에서 확인해보자
HTTP App의 로그는 stdout으로 발생시키고, 이를 docker는 capture해서 json-file 로깅 드라이버로 파일 로깅을 하는데, 이 로그를 읽어갈 예정이다.
 

프로젝트 구성

  • HTTP App
  • Promtail
  • Loki
  • Grafana
 
프로젝트는 총 4개의 컴포넌트로 구성된다. HTTP Request를 처리하는 App이 메인이며, Promtail을 통해 로그를 Loki로 포워딩하고, Grafana에서 Loki를 data source로 사용해 visualization한다.
 
graph TD A[App] -->|logs to| B[Docker Socket] B -->|reads logs from| C[Promtail] C -->|forwards logs to| D[Loki] E[Grafana] -->|queries logs from| D
 
 

웹 어플리케이션 서버

Go를 이용해 매우 간단한 서버를 만들었다.
/로 GET 요청이 들어오면 user-agent, request method, uri, remote-addr을 로깅한다.
 
먼저 go mod init loki 명령어로 Go 프로젝트 환경을 세팅하고, main.go 파일에 아래 코드를 추가한다.
 
package main import ( "fmt" "log/slog" "net/http" "os" ) func main() { // Stdout 설정 logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { ua := r.UserAgent() // 로깅 logger.Info("request", "user-agent", ua, "method", r.Method, "path", r.URL.Path, "remote-addr", r.RemoteAddr) // 응답 반환 fmt.Fprintf(w, "Hello, World!") }) fmt.Println("server is running on port 8080...") // 8080 포트 listen http.ListenAndServe(":8080", nil) }
 
Dockerfile을 추가해 빌드 설정을 해준다.
멀티 스테이지 빌드를 했다.
FROM golang:1.22 as builder WORKDIR /go/src/app COPY . . RUN go mod tidy && \ go mod vendor && \ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o bin/app . FROM scratch WORKDIR /go/src/app COPY --from=builder /go/src/app/bin/app ./app ENTRYPOINT ["./app"]
 

docker-compose

Grafana, Loki, Promtail, App 총 4개의 컴포넌트를 모두 실행하고 서로 연결해야 하기 때문에 docker-compose를 이용해 간단히 구성했다.

app: build: . ports: - "8080:8080"
위에서 만든 go HTTP 서버가 8080으로 서빙하고 있기 때문에, 8080 포트를 외부에서 접근 가능하게 open해줬다.
 
build는 앞서 만든 Dockerfile을 이용하며, 별도로 이미지를 생성해 push하지는 않았다.

Loki

loki: image: grafana/loki:latest ports: - "3100:3100"
Loki도 별로 설정이 없다. port만 외부에서 접근 가능하게 open했는데, 브라우저에서 접속해 loki가 잘 동작하는지, 로그는 잘 적재되는지 체크할 목적으로 오픈했다.
 

Promtail

promtail은 몇가지 설정이 필요하다.
promtail: image: grafana/promtail:latest ports: - "9080:9080" volumes: - /var/run/docker.sock:/var/run/docker.sock - ./promtail-config.yml:/etc/promtail/promtail-config.yml command: -config.file=/etc/promtail/promtail-config.yml depends_on: - loki
 
  • port: Loki와 동일하게 브라우저에서 웹콘솔에 접근해 작동을 체크하기 위해 오픈
  • volumes
    • /var/run/docker.sock: 소켓통신을 위해 오픈했다. Docker 데몬 소켓을 호스트로부터 container로 mount한다.
    • promtail-config.yml: 로컬 디렉토리에 생성한 promtail 설정파일이다. 추후 다시 설명한다.
  • command: promtail-config를 읽어 실행하도록 설정했다.
  • depends_on: promtail이 시작되기 전에 loki가 시작되었는지 확인하게 한다.
 

Grafana

Grafana에서는 시작하면서 Loki를 data source로 등록하도록 설정을 좀 더 추가했다.
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: Loki type: loki access: proxy orgId: 1 url: http://loki:3100 basicAuth: false isDefault: true version: 1 editable: false EOF /run.sh image: grafana/grafana:latest ports: - "3000:3000"
  • environment
    • GF_PATHS_PROVISIONING=/etc/grafana/provisioning: 프로비저닝 설정을 어디에서 참조하면 되는지 정의하는 path
    • GF_AUTH_ANONYMOUS_ENABLED=true: 익명 access를 가능하게 설정
    • GF_AUTH_ANONYMOUS_ORG_ROLE=Admin: 익명 사용자를 Admin으로 지정해서 모든 설정을 할 수 있게 했다. (간단한 demo이므로)
  • grafana 이미지의 default entrypoint를 override하는 속성이다.
    • /etc/grafana/provisioning/datasources 라는 디렉토리를 생성한다.
    • ds.yaml을 위 디렉토리에 추가한다.
    • yaml 파일에 설정을 추가해서 data source로 loki를 사용하게 한다.
    • url은 http://loki:3100이다. 앞서 loki라는 서비스를 정의했기 때문에 docker의 default network를 이용하면 이렇게 컨테이너 간 연결이 가능하다.
 

전체 코드

docker-compose.yml
version: '3.7' services: app: build: . ports: - "8080:8080" loki: image: grafana/loki:latest ports: - "3100:3100" promtail: image: grafana/promtail:latest ports: - "9080:9080" volumes: - /var/run/docker.sock:/var/run/docker.sock - ./promtail-config.yml:/etc/promtail/promtail-config.yml command: -config.file=/etc/promtail/promtail-config.yml depends_on: - loki 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: Loki type: loki access: proxy orgId: 1 url: http://loki:3100 basicAuth: false isDefault: true version: 1 editable: false EOF /run.sh image: grafana/grafana:latest ports: - "3000:3000"
 

promtail 설정

promtail에서 docker의 로그를 읽어와 loki로 포워딩할 수 있게 promtail-config를 작성해줬다.
server: http_listen_port: 9080 grpc_listen_port: 0 positions: filename: /tmp/positions.yaml clients: - url: http://loki:3100/loki/api/v1/push scrape_configs: - job_name: 'docker' docker_sd_configs: - host: unix:///var/run/docker.sock relabel_configs: - source_labels: [__meta_docker_container_name] regex: '/(.*)' target_label: container - source_labels: [__meta_docker_container_log_path] target_label: __path__
  • server: promtail의 HTTP/gRPC 서버에 대한 설정이다. HTTP만 사용할 것이므로 gRPC는 비활성화했다.
  • position: promtail이 로그파일을 읽고 어디까지 읽었는지 저장할 때 사용하는 위치를 지정한다.
  • clients: Loki와의 커넥션 관련 설정을 한다. 로그가 포워딩될 API를 지정한다.
  • scrape_configs: 로그를 발견하고 수집하는 설정이다.
    • docker_sd_configs: 도커의 service discovery를 활성화한다. Promtail이 도커 컨테이너 로그를 자동으로 찾도록 한다.
      • - host: unix:///var/run/docker.sock: 도커 소켓 파일을 지정해서 Promtail이 Docker와 연결하고 컨테이너와 로그 파일을 찾을 수 있게 한다.
    • relabel_configs: 발견된 데이타를 의미있는 레이블로 변경하도록 설정한다.
 
docker 레이블 설정 관련
- source_labels: [__meta_docker_container_name] regex: '/(.*)' target_label: container
docker 컨테이너의 이름을 container라는 레이블로 지정한다.
 
- source_labels: [__meta_docker_container_log_path] target_label: __path__
docker의 service discovery로 발견한 log path를 사용하며,__path__ 라는 새 엔트리로 관리한다.
 

실행

컨테이너들을 띄워보자.
$ docker-compose up --build
 
http://localhost:3000 에 접속해 explore로 들어간 다음, label을 지정하면 아래처럼 로그를 조회할 수 있다.
notion image