분산환경은 로깅을 어떻게 할까
개요
slueth-zipkin을 통해서 하나의 Request가 어떻게 어떤서비스에서 얼마의 시간을 소요하고 흘러가는지 확인할 수 있었다.
전통적으로 서비스는 개별로 로그를 보관한다. 로그를 확인하기 위해서 개별 서비스의 로그를 찾아가면서 확인하는 비효율적인 작업을 타개하기 위해 ELK Stack
이 도입되었다. 각각 Elasticsearch
, Logstash
, Kibana
의 약어이다. 분산된 로그데이터들을 중앙화하고 분석과 검색, 시각화에 용이하게 수집 및 정리하는데 사용된다.
ELK Stack
Elasticsearch
로그를 저장하는 저장소 역할을 한다.
실시간으로 대용량 데이터를 검색하고 분석하는데 사용되는 오픈소스 검색 엔진이다. 기본적인 full-text 검색 뿐 아니라, 구조화된 데이터와 비구조화된 데이터를 확장 가능하게 인덱싱하는데 사용된다. Elasticsearch는 분산 시스템으로 설계되어서 강력한 확장성을 보유하며, 복잡한 검색 작업을 매우 빠르게 처리할 수 있다.
Logstash
로그를 수집해오고 파싱하는 역할을 한다.
다양한 소스에서 데이터를 수집하여(input
) 설정에 맞춰 변환(filter
)한 뒤 ElasticSearch와 같은 저장소에 저장(output
)을 하는 서버사이드 데이터 처리 파이프라인이다.
기본적으로 in-memory를 사용하여 파이프라인 단계(input->filter->output) 사이에 in-memory queues를 사용해 이벤트를 버퍼링한다.
Kibana
키바나는 단순하게 생각하면 수집된 데이터를 탐색 및 분석하는 수단이라고 할 수 있다. Elasticsearch로 저장된 데이터를 시각화하고, 데이터를 쉽게 분석할 수 있도록 도와준다.
ELK(+zipkin) 연동
zipkin을 올릴때 사용했던 docker-compose에 내용을 추가하면서 진행한다. 가장 간단한 세팅으로 ELK 구동을 목적으로 yml을 작성한다.
ELK는 동일한 버전을 사용하는것을 권장한다.
logstash 연결을 위한 logback 설정
먼저 logstash-logback-encoder 의존성을 추가해준다.
그리고 logback을 생성해주는데 가장 중요한건 logstash로 전달해주는 구문이다.
<!-- logback.xml -->
...
<appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>${YOUR_LOGSTASH_HOST}:5000</destination>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<pattern>
<pattern>
{
"date":"%date",
"traceId":"%X{traceId:-}",
"spanId":"%X{spanId:-}",
"level": "%level",
"project": "${projectName}",
"pid": "${PID:-}",
"thread": "%thread",
"class": "%logger{36}",
"message": "%message"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
...
zipkin
이전글에서 생성한 zipkin은 in-memory에 데이터를 들고있다가 컨테이너가 멈춤과 동시에 사라지게된다. elasticsearch를 추가하면서 storage를 elasticsearch로 연결해준다.
zipkin:
image: openzipkin/zipkin
container_name: zipkin
environment:
- STORAGE_TYPE=elasticsearch
- "ES_HOSTS=http= http://elasticsearch:9200"
ports:
- "9411:9411"
networks:
- elk
depends_on:
- elasticsearch
logstash
logstash:
image: docker.elastic.co/logstash/logstash:7.12.0
container_name: logstash
command: logstash -f /etc/logstash/conf.d/logstash.conf
volumes:
- ./config:/etc/logstash/conf.d
ports:
- "5000:5000"
networks:
- elk
depends_on:
- elasticsearch
실행순서를 조정을 위해 elasticsearch에 depends_on
두어 es가 켜진뒤에 실행되도록 했다.
logstash는 -f
옵션을 통해 설정파일을 불러오도록 한다.
volumes를 보면 상위 config폴더 를 컨테이너 내 /etc/logstash/conf.d
로 마운팅했다.
아래와 같은 트리로 보면 config 폴더 내부에 설정파일을 작성해둔걸 확인 할 수 있다.
├── config
│ └── logstash.conf
└── docker-compose.yml
# ./config/logstash.conf
input {
tcp {
port => 5000
codec => json_lines
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
}
}
config file의 속성에 대해서는 공식 Document의 Input/Filter/Output plugins 들을 확인하면 된다. 위 같은 경우에는 index
패턴을 별도로 지정하지 않았기 때문에 logstash-{now/d}-00001
와 같은 형식으로 전달된다.
elasticsearch
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.12.0
container_name: elasticsearch
environment:
- node.name=elasticsearch
- discovery.type=single-node
- cluster.name=docker-cluster
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
ports:
- "9200:9200"
- "9300:9300"
networks:
- elk
elasticsearch에 2개의 포트포워딩이 들어간다.
기본적으로 9200포트는 HTTPS를 통한 클라이언트 트래픽에 사용되는 포트다.(eg.curl -XGET 'http://localhost:9200/_count?pretty'
)
9300포트는 elasticsearch 클러스터 내부의 노드들간의 통신에 사용된다.(지금 설정은 single-node이지만..)
kibana
kibana:
image: docker.elastic.co/kibana/kibana:7.12.0
container_name: kibana
environment:
# ELASTICSEARCH_URL: "http://elasticsearch:9200"
ELASTICSEARCH_HOSTS: "http://elasticsearch:9200"
ports:
- "5601:5601"
depends_on:
- elasticsearch
networks:
- elk
kibana 6.x.x버전 까지는 ELASTICSEARCH_URL
로 ES 인스턴스의 URL을 명시하였으나 7.0.0 이후로는 ELASTICSEARCH_HOST
로 변경되었다.
이렇게 모두 종합한 docker-compose.yml
은 아래와 같다.
# docker-compose.yml
name: elkz-container
services:
zipkin:
image: openzipkin/zipkin
container_name: zipkin
ports:
- "9411:9411"
logstash:
image: docker.elastic.co/logstash/logstash:7.12.0
container_name: logstash
command: logstash -f /etc/logstash/conf.d/logstash.conf
volumes:
- ./config/logstash.config:/etc/logstash/conf.d/logstash.conf
ports:
- "5000:5000"
networks:
- elk
depends_on:
- elasticsearch
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.12.0
container_name: elasticsearch
environment:
- node.name=elasticsearch
- discovery.type=single-node
- cluster.name=docker-cluster
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
ports:
- "9200:9200"
- "9300:9300"
networks:
- elk
kibana:
image: docker.elastic.co/kibana/kibana:7.12.0
container_name: kibana
environment:
ELASTICSEARCH_HOSTS: "http://elasticsearch:9200"
ports:
- "5601:5601"
depends_on:
- elasticsearch
networks:
- elk
volumes:
elasticsearch-data:
driver: local
networks:
elk:
driver: bridge
데이터 저장을 위한 volumes와 networks를 선언하고 저장한다.
depends_on의 실행순서에 관하여
depends_on
을 실행순서를 조정하기 위해 위와 같이 사용하였는데 실제로는 내 생각대로 실행되지 않았다. 이 명령어는 단순히 실행에 대한 순서만 조정이 될뿐이었다.
의존을 갖고있는 서비스가 healthy
한 상태에서 나머지 서비스가 실행되길 원했고 공식문서에서 답을 찾을수 있었다.
healthcheck
Docker document.healthcheck
healthcheck가 선언된 서비스에서 test속성에 정의된 명령어를 통해 자기 자신에게 HTTP요청을 보내면서 healthcheck를 시도한다. 이 명령이 성공상태 코드를 반환하면 서비스가 정상적으로 작동한다고 간주하고 healthy 라는 상태를 부여한다.
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:9200" ]
interval: 30s
timeout: 20s
retries: 3
healtcheck는 condition
과 함께 쓰이며 예시는 아래와 같다.
zipkin:
...
depends_on:
elasticsearch:
condition: service_healthy
그 외 inverval
,timeout
,retries
는 프로퍼티명부터 직관적으로 어떤 의미인지 설명하지 않아도 모두가 알것이라고 생각한다. druation에 대한 정의는 아래와 같이 docs에 써있다.
The supported units are
`us` (microseconds),
`ms` (milliseconds),
`s` (seconds),
`m` (minutes) and `h` (hours).
Values can combine multiple values without separator.
10ms
40s
1m30s
1h5m30s20ms
최종 docker-compose.yml
name: elkz-container
services:
zipkin:
image: openzipkin/zipkin
container_name: zipkin
environment:
- STORAGE_TYPE=elasticsearch
- "ES_HOSTS=http://elasticsearch:9200"
ports:
- "9411:9411"
networks:
- elk
depends_on:
elasticsearch:
condition: service_healthy
logstash:
image: docker.elastic.co/logstash/logstash:7.12.0
container_name: logstash
command: logstash -f /etc/logstash/conf.d/logstash.conf
volumes:
- ./config:/etc/logstash/conf.d
ports:
- "5000:5000"
networks:
- elk
depends_on:
elasticsearch:
condition: service_healthy
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.12.0
container_name: elasticsearch
environment:
- node.name=elasticsearch
- discovery.type=single-node
- cluster.name=docker-cluster
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
ports:
- "9200:9200"
- "9300:9300"
networks:
- elk
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:9200" ]
interval: 30s
timeout: 20s
retries: 3
kibana:
image: docker.elastic.co/kibana/kibana:7.12.0
container_name: kibana
environment:
ELASTICSEARCH_HOSTS: "http://elasticsearch:9200"
ports:
- "5601:5601"
networks:
- elk
depends_on:
elasticsearch:
condition: service_healthy
volumes:
elasticsearch-data:
driver: local
networks:
elk:
driver: bridge
구동
이전 이미지를 사용하지 않고 다시 이미지를 만들어 올리기 위해 --force-recreate
옵션과 --build
옵션을 같이 사용한다.
> docker compose up --build --force-recreate -d
[+] Running 5/5
Network elkz-container_elk Created
Container elasticsearch Started
Container logstash Created
Container kibana Created
Container zipiin Created
여기서 우린 healthcheck
옵션이 잘 들어갔다는걸 확인 할 수 있다.
healthcheck가 정상응답을 주기 전까지 elasticsearch를 제외한 나머지 컨테이너는 Created
상태로 기다리다가 elasticsearch 컨테이너가 Healthy
상태가 되면 나머지도 Started로
변경된다.
http://YOUR_HOST:5601 를 통해 Kibana의 UI로 들어갈 수 있는데 일단 탐색할 대상데이터를 위해 index pattern
을 먼저 생성해줘야한다.
처음 화면에서 'Kibana' 섹션을 클릭 한 뒤 'Add your data'를 통해 아래 화면으로 진입한다.
index pattern을 별도로 지정하지 않았으니 logstash-*
를 입력하여 진행하면 생성이 된다.
Discovery 탭으로 이동해 위 인덱스의 로그들을 탐색 할 수 있다.
수집된 로그데이터를 토대로 분석을 할수있는 대시보드도 아래같이 사용할수 있다.