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
: 프로비저닝 설정을 어디에서 참조하면 되는지 정의하는 pathGF_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을 지정하면 아래처럼 로그를 조회할 수 있다.