개발 동기
현재 사내 서비스들에 대한 로그는 winston으로 날짜별/레벨별로 관리되고 있다.
export const winstonLogger = WinstonModule.createLogger({
transports: [
new winston.transports.Console({
level: process.env.NODE_ENV === 'production' ? 'http' : 'silly',
format: winston.format.combine(
winston.format.timestamp(),
utilities.format.nestLike(process.env.NODE_ENV, { colors: true, prettyPrint: true }),
),
}),
new winstonDaily(dailyOption('info')),
new winstonDaily(dailyOption('error')),
new winstonDaily(dailyOption('warn')),
new winstonDaily(dailyOption('debug')),
new winston.transports.File({
level: 'info',
filename: `./logs/${moment().format('YYYY-MM-DD')}/${moment().format('YYYY-MM-DD')}.log`,
maxFiles: 30,
zippedArchive: true,
format: winston.format.combine(
winston.format.timestamp(),
utilities.format.nestLike(process.env.NODE_ENV, { colors: true, prettyPrint: true }),
winston.format.printf((info) => {
return `${info.timestamp} - ${info.level} [${process.pid}: ${info.message}]`
})
),
}),
],
format: winston.format.combine(
appendTimestamp({ tz: 'Asia/Seoul' }),
winston.format.json(),
winston.format.printf((info) => {
return `${info.timestamp} - ${info.level} [${process.pid}: ${info.message}]`
})
)
});
또한 서비스의 크리티컬한 에러에 대응하기 위해 웹훅을 이용해 전사 메신저인 팀즈에 에러메세지를 바로 보내게 구현해두었다.
if ((process.env.NODE_ENV === 'staging' || process.env.NODE_ENV === 'production') && status >= 403) {
await this.sendErrorToMessenger(formatMessage);
}
자리에 없을때도 앱을 통해 알림이 오기 때문에 에러 대응에는 적합하다고 생각하지만, 원하는 로그를 찾는데 어려움이 있다.
하지만 근본적으로 모든 로그들을 시각적으로 편하게 수집하여 분석할 수 있다면? 좋을 것 같다는 생각을 수 개월전부터 했고
작년 말 데브페스트의 한 섹션에서 영감을 얻을 수 있었고, 꾸준히 로그 시각화와 관련된 최신 포스팅들을 찾아보려고 했다.
들어가기전에, 필자는 ElasticSearch를 써본 경험이 없고, PLG가 ELK에 비해 가볍다고 알고 있어서 바로 PLG를 사용해보려고 했다.
트러블슈팅
대부분의 어려움은, PLG를 사내 노드 호스트서버 / 프로젝트 컨테이너 중 어디 빌드해야할지에 대한 것이었다.
PLG를 어디 설치하지?, 또 Promtail-Loki는 Promtail이 로그파일들을 수집해서 Loki로 전송한다는 측면에서 같은 곳에다 빌드하는 게 맞는 것 같은데, Grafana는 호스트서버에 설치해야하나???? 이런 고민들을 많이 했고, 이리저리 부딪히고 깨져가며 빌드를 시도했다.
그리고 전반적인 인프라 ( +도커 )에 대한 이해도가 떨어진다고 느꼈던 시간들이었다. 반드시 공부해야할 것 같다.
여러 컨테이너들의 로그를 한번에 받아볼 수 있게
Grafana는 호스트 서버에 설치했고 Promtail, Loki는 컨테이너 마다 빌드해주었다.
Promtail의 config에서, 컨테이너마다 job 네이밍을 다르게하여 각 서비스별로 구분지었다.
Promtail, Loki를 docker-compose에 구성하기
version: '3.3'
services:
test-dev:
image: test-dev
container_name: test-dev
build:
context: .
dockerfile: DockerfileStaging
ports:
- 'port:port'
environment:
- PORT=port
- NODE_ENV=staging
volumes:
- /var/log/test-dev:/app/logs
loki:
image: grafana/loki
container_name: loki
user: "$UID:$GID"
ports:
- "3100:3100"
volumes:
- ./loki/local-config.yaml:/home/nodesvc/loki/local-config.yaml
- ./loki/data:/var/loki
command: -config.file=/home/nodesvc/loki/local-config.yaml
networks:
- loki
promtail:
image: grafana/promtail
container_name: promtail
volumes:
- /var/log/test-dev:/logs
- ./promtail/promtail-config.yml:/home/nodesvc/loki/promtail-config.yaml
command: -config.file=/home/nodesvc/loki/promtail-config.yaml
depends_on:
- loki
networks:
- loki
networks:
loki:
driver: bridge
위의 언급처럼, 프로젝트를 컨테이너로 배포 시에 같이 빌드되어야 하기 때문에, 프로젝트 배포를 위한 docker-compose를 수정하고
이후 action runner를 사용해 배포를 진행했다. 그랬더니 아래와 같이, clean이 실패했다는 에러가 발생했다.
레포에서 해당 커밋 지점을 독립적인 환경으로 구성하는 체크아웃 작업 시 추적되지 않는 구성요소들에 대한 제거를 수행하는 와중에 권한이 없어 발생한 에러로 이해했고, 체크아웃 수행 전 해당 워크스페이스를 강제로 initialize할 수 있게 cleanup을 추가해주었다.
checkout:
runs-on: [self-hosted, withlight_nodeapi_action]
steps:
- name: 🧹 cleanup runner workspace
run: |
echo $GITHUB_WORKSPACE
sudo rm -rf $GITHUB_WORKSPACE
mkdir $GITHUB_WORKSPACE
shell: bash
# if: ${{ failure() && steps.checkout.conclusion == 'failure' }}
- name: 🛒 Checkout
id: checkout
uses: actions/checkout@v3
Loki와 Promtail config파일도 구성해주었다.
Loki는 따로 변경해줄 부분이 없어서 그대로 사용했고,
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
common:
instance_addr: 127.0.0.1
path_prefix: /tmp/loki
storage:
filesystem:
chunks_directory: /tmp/loki/chunks
rules_directory: /tmp/loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
schema_config:
configs:
- from: 2020-10-24
store: tsdb
object_store: filesystem
schema: v12
index:
prefix: index_
period: 24h
ruler:
alertmanager_url: http://localhost:9093
# By default, Loki will send anonymous, but uniquely-identifiable usage and configuration
# analytics to Grafana Labs. These statistics are sent to https://stats.grafana.org/
#
# Statistics help us better understand how Loki is used, and they show us performance
# levels for most users. This helps us prioritize features and documentation.
# For more information on what's sent, look at
# https://github.com/grafana/loki/blob/main/pkg/analytics/stats.go
# Refer to the buildReport method to see what goes into a report.
#
# If you would like to disable reporting, uncomment the following lines:
#analytics:
# reporting_enabled: false
promtail은, 로그를 수집하는 역할을 하기 때문에, 알맞은 경로와 원하는 Job_name을 적어주었다.
# promtail-config.yml
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: info
static_configs:
- targets:
- localhost
labels:
job: info
__path__: ./logs/*/info/*.log
- job_name: warn
static_configs:
- targets:
- localhost
labels:
job: warn
__path__: ./logs/*/warn/*.log
- job_name: error
static_configs:
- targets:
- localhost
labels:
job: error
__path__: .logs/*/error/*.log
Grafana 설치하기
그라파나를 시스템파일로 실행할지, nohup을 사용해 백그라운드에서 실행할지, pm2를 활용할지 여러 옵션이 있었는데
실행한 뒤 따로 서버에서 관리할 필요는 없다고 판단되어서, nohup를 사용해서 실행시켰다.
wget https://dl.grafana.com/enterprise/release/grafana-enterprise-10.1.2.linux-amd64.tar.gz
tar -zxvf grafana-enterprise-10.1.2.linux-amd64.tar.gz
nohup grafana-10.1.2/bin/grafana-server &
그라파나를 실행하면, 디폴트인 3000번 포트를 사용하여 그라파나 페이지에 접속할 수 있고,
1. Datasources에서 Loki선택
2. URL에 ip:3100(디폴트)를 입력한 후 Save&test
3. 2번 작업이 성공한다면, Explore탭에서 Job/Filename 선택 후 확인가능하며 대시보드 구성도 가능하다.
목표
위처럼 대시보드를 생성해 시각화에는 성공했고, 이제 본편을 시작해볼 수 있게 되었다.
로그 시각화를 접목시켜보려 했던 가장 큰 숙제들을 서비스에 녹여낼 수 있게 시간이 오래걸리더라도 꼭 풀어내보자.
반드시 포스팅해야할 주제가 하나 더 늘어나서 기분이 좋아졌다.
끝!
2023.04 ~ 백엔드 개발자의 기록
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!