elastic stack

Why elastic stack?

기업의 서비스의 복잡도가 올라감에 따라 프록시 서버, 인증서버, 데이터베이스, api 서버 등 수많은 서비스들이 동작하고, 각자 다른 배포환경에서 관리되게 되었다. 이에 따라 하나의 서비스를 운영하더라도 여러 도메인에 걸쳐 많은 문제들이 생기게 되고, 여러 서버에 걸쳐 로그 및 데이터 분석의 필요성이 생겼고, 데이터를 관리하고 활용하기 위한 통합 솔루션의 필요성이 대두되었다. Elastic Stack 은 기업이 서비스를 운영함에 따라 나오는 모든 데이터를 한 곳에서 관리하고, 사용자가 직관적으로 이해할 수 있도록 시각화 시켜 가공하고, 수많은 데이터에서 원하는 데이터를 검색 및 분류해주는 플랫폼이다.

쉽게 이야기 하면, 기업이 운영하는 수십대의 서버 및 데이터베이스에서 나오는 모든 데이터를 한곳으로 모으고, 가공하고, 시각화해주는 통합 솔루션이라 할 수 있다.

이러한 기능을 수행하기 위해 elastic stack 을 구성하는 여러 서비스가 있는데, 대표적으로 각 서버에서 생기는 데이터를 한 곳으로 모으는 shipping 을 담당하는 beats(beat 에는 file beat 등 여러 종류의 beat 가 있으며, 기본적으로 file beat 를 사용하면 기업의 데이터 파일을 한곳으로 간편하게 이동시킬 수 있다.)와 데이터를 저장하고 분류하는 일종의 데이터 검색 엔진인 elastic search , 또, 이러한 사용자에게 가시적으로 보여주기 위한 kibana 등이 있다.

본 포스트에서는 이러한 elastic stack 의 다양한 기술을 활용하여 여러 서버의 로그 정보를 한 곳에 모으고 이를 모니터링 하기 위한 환경을 구축해 보도록 하겠다.

File beat

먼저 각 서버에서 나오는 로그들을 한곳으로 shipping 하기 위해서, 각 서버에 file beat 를 설치하고 운영해야 한다.

본 포스트에서는 각 서버와 filebeat 를 하나의 docker-compose 로 관리하여 하나의 서버에서 filefbeat 와 application 서버를 구축하고 이를 elastic search 서버로 전달해 보도록 한다.

어플리케이션 서버와 file beat 를 구성하는 docker-compose 파일은 다음과 같다.

docker-compose.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
version: '3'

services:
filebeat:
image: docker.elastic.co/beats/filebeat:${ELASTIC_VERSION:-6.5.0}
hostname: '{{.Node.Hostname}}-filebeat'
user: root
networks:
- default
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/docker/containers/:/var/lib/docker/containers/:ro
- ./log/application:/var/log/application

command: ['--strict.perms=false']
deploy:
mode: global

server:
image: 'example-server'
build: './example-server'
volumes:
- ./log:/app/log
networks:
- default
ports:
- '3001:3001'

networks:
default:
driver: bridge

파일비트가 수집할 로그가 있는 디렉토리를 docker volume 으로 지정하고 application server 를 3001 번에서 동작시켰다.

또한, filebeat 의 설정을 담당하는 filebeat.yml 파일을 docker 내에 볼륨으로 지정하였으며, 그 내용은 다음과 같다.

filebeat.yaml

1
2
3
4
5
6
7
8
9
10
11
12
name: example filebeat
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/application/*.log

output.elasticsearch:
hosts: ['host.docker.internal:9200']

setup.kibana:
host: 'host.docker.internal:5601'

위처럼 9200번 포트에서 동작하는 elastic search 서버와 5601번에서 동작하는 kibana가 세팅된다.

deploy with beanstalk

Beanstalk 에서 multidocker deploy를 위한 Dockerrun.aws.json 파일을 작성하여 다음과 같이 배포할 수 있다.

여기서 중요한 점은 image 를 배포함에 있어 반드시 사용자id.dkr.ecr.ap-northeast-2.amazonaws.com/레포이름:latest 의 형태로 이미지 경로를 지정해 주어야 한다는 것이다.

즉, 멀티 도커를 사용할 때 필요한 이미지들을 미리 아마존의 repository 에 배포해 놓아야 한다.

이 과정에서 반드시 아마존에 로그인이 되어야 하며, 다음과 같은 명령어를 통해 ecr 에 별도로 로그인을 수행한다.

아래 명령어는 기본 aws 유저가 아닌 eb cli 접근이 가능한 프로그램 유저를 설정해 두고 해당 유저의 정보로 로그인을 함을 의미한다. 아래 명령어를 입력하면 AWS 에서 로그인을 하기 위한 명령어를 output 으로 제공하는데 해당 문자열을 복사하여 다시 cli에 입력하면 로그인이 완료된다.

1
aws ecr get-login --no-include-email --profile eb-cli

여기서 memory 는 매우 중요한데, 만약 memory 가 부족하다면 도커 컨테이너가 아무런 에러도 출력하지 않고 종료되어 버리니 이점에 유의해야 한다.

Dockerrun.aws.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
{
"AWSEBDockerrunVersion": 2,
"volumes": [
{
"name": "log",
"host": {
"sourcePath": "/var/log"
}
}
],
"containerDefinitions": [
{
"name": "filebeat",
"image": "docker.elastic.co/beats/filebeat:6.5.0",
"essential": true,
"memory": 128,
"mountPoints": [
{
"sourceVolume": "log",
"containerPath": "/var/log"
}
],
"command": ["--strict.perms=false"],
"links": ["server"]
},
{
"name": "server",
"image": "사용자id.dkr.ecr.ap-northeast-2.amazonaws.com/레포이름:latest",
"essential": true,
"memory": 512,
"mountPoints": [
{
"sourceVolume": "log",
"containerPath": "/app/log"
}
],
"portMappings": [
{
"containerPort": 3001,
"hostPort": 80
}
]
}
]
}

how does it works?

File beat 에는 input 과 harvest 라는 두가지 큰 개념이 있다.

먼저 input 이란 filebeat 가 데이터를 읽어오는 리소스를 찾고, 이를 실제로 읽어들이는 harvestor 를 관리하는 역할을 수행한다. 인풋으로 type 이 log 인 인풋이 들어오면 해당하는 패스에서 모든 파일을 찾아 각각에 harvestor 를 작동시킨다.

각 input 은 각자 독자적인 go routine 을 통해 돌아간다.

파일비트는 다음과 같은 종류의 여러 type 의 인풋을 제공한다.

log, stdin, redis, udp, docker, tcp, syslog

example

1
2
3
4
5
filebeat.inputs:
- type: log
paths:
- /var/log/*.log
- /var/path2/*.log

harvestor 란 파일을 하나하나 읽어서 수집하는 harvest 를 수행한다. harvestor는 각 파일을 라인바이 라인으로 읽어서 아웃풋으로 보내며, 파일을 열고 닫으면서 계속 수집을 수행한다. harvestor 가 로그를 수집하는 동안 file을 계속 열어놓는다.

harvestor 를 닫으면 리소스를 할당 해제한다.

파일의 상태관리는 어떻게 하나?

파일 비트는 각 파일의 읽기 상태를 레지스트리 파일 디스크에 기록해 두고 계속 트레킹한다.

이를 통해 어디까지 읽었는지 파악하고 output server 와의 연결상태가 불안정하다면 이를 계속 트레킹 하고 빠짐없이 보내준다. 파일비트는 무조건 최소한 한번은 정보가 전달되는 것을 보장하는 대신 대신에 두번 갈수는 있다. 이는 레지스트리에 저장된 값 덕분이다. 데이터가 두번 전송되는 문제는 shutdown_timeout 옵션을 통해 조절 할 수 있다.

가령, 파일비트가 종료되기 전에 시간을 두고 데이터를 전송을 완료하는 등의 동작을 수행하는 것이다.

또한, output으로의 연결 상태가 좋지 않은데 파일이 삭제된다면 이는 유실될 수 있다.

logstash

logstash 는 data flow engine 으로써 filebeat, dbms, message que 등 여러 데이터 소스로 부터 데이터를 받아 데이터를 가공하고 정제하는 역할을 수행한다.

Logstash 에는 pipeline 이라는 개념이 있으며, 여러 데이터들에 대해 일련의 작업을 수행하는 pipeline의 합집합으로써 동작한다.

elastic stack 내에서 일반적인 활용도는 각 서버의 filebeat 가 보낸 로그 데이터를 받아 각종 메타 정보를 기입하고 분류하고 정제하여 elastic search 서버로 보내주는 역할을 수행한다.

logstash의 기능은 크게 3가지로 이야기 될 수 있는데 바로 input, filter, output 이다.

먼저 input 은 데이터가 유입되는 근원지를 설정함으로 파일, 데이터베이스, 메세지 큐, 파일비트 등 데이터 유입 경로를 지정하고 주기적으로 데이터를 받는다.

filter 단계에서는 받은 데이터를 필터링하고 정제하여 메타데이터를 입히고 데이터를 전처리하는 등을 수행한다.

output 단계에서는 정제된 데이터를 목적으로 되는 서버로 전달하며 주로 검색엔진인 elastic search 등으로 데이터를 전달한다.

logstash 는 데이터를 정제하는 과정에서 codec 을 사용하는데, codec 이란 특정 데이터 소스의 형식에 맞는 데이터를 우리가 원하는 형태로 가공해 주는 역할을 하는 encode decode 를 수행한다고 보면 된다. 가령, nginx 서버에서 생성된 각종 nginx 서버 양식의 로그 데이터를 nginx codec 으로 decode 하여 다시 우리가 원하는 데이터 모델로 재생성 하는 것을 생각하면 된다.

이렇게 재가공된 데이터는 다시 filter 를 통해 분류되어 목적지 output 으로 전달된다.

installation

1
docker pull docker.elastic.co/logstash/logstash:6.6.0

run

1
docker run --rm -it -v ~/pipeline/:/usr/share/logstash/pipeline/ docker.elastic.co/logstash/logstash:6.6.0

여기서 logstash 는 파일비트로 부터 여러번 데이터를 받아 batch size 만큼을 체운 뒤에 elastic search 에 데이터를 전달하기 때문에 즉각적으로 데이터가 전송되지 않아 동작을 확인하기 어려울 수 있다.

가급적 input 에 tcp 를 추가하여 실시간으로 데이터를 확인할 수 있도록 설정해 놓으면 비교적 편리하게 테스트를 진행할 수 있다.

logstash.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 파이프라인 수를 의미한다.
# 더 많은 CPU 를 사용하기 위해 설정한다.
pipeline.workers: 2

# 배치 사이즈를 설정한다.
pipeline.bath.size: 125

# 배치 딜레이는 어떤 간격으로 요청하는지 한다.
pipeline.batch.delay: 5

# config 옵션이 변경되면 보고 감지하는 옵션이다.
config.reload.automatic: false

# config 변경을 감지하는 주기이다.
# 현재 설정에서는 5초에 한번 감지하는 것으로 되어있다.
config.reload.interval: 5s

Elastic Search

Elastic search 는 filebeat 가 수집해온 데이터를 분류하고 서치하는 검색 엔진이다.

installactio on docker

elastic search server 를 설치하고 실행한다.

1
docker pull docker.elastic.co/elasticsearch/elasticsearch:6.5.4
1
docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.5.4

docker 를 사용하여 구성을 할때 max virtual memory areas vm.max_map_count [65530] is too low, increase to at least 와 같은 에러가 나올 수 있다.

이는 도커에서 할당하는 최대 가상 메모리가 모자라서 생기는 문제로, 다음과 같은 명령어를 통해 이를 늘려주면 해결된다.

1
sudo sysctl -w vm.max_map_count=262144

Kibana

키바나는 내부적으로 노드서버를 띄우며, 사용자가 수집한 정보를 사용자에게 보여주기 위해 시각화 시켜주며, 사용자를 위한 관리자 대쉬보드를 제공한다.

또한, kibana 는 elastic search 를 조작하기 위한 dev console 또한 제공한다.

1
docker pull docker.elastic.co/kibana/kibana:6.5.4
1
2
3
4
5
6
version: '2'
services:
kibana:
image: docker.elastic.co/kibana/kibana:6.5.4
volumes:
- ./kibana.yml:/usr/share/kibana/config/kibana.yml

다운로드를 받았다면 설치를 풀어주고

exconfig/kibana.yml 의 설정을 해준다.

키바나를 실행하면 Localhost:5601 로 키바나가 붙는다.

Kibana query

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
POST /inspections/_doc
{
"business_address":"kwan ak goo",
"coordinates":{
"lat":21,
"lon":-123
},
"score":12
}
POST /inspections/_doc/_bulk
{ "index" :{ "_id" : 1 } }
{"business_address":"kwan ak goo"}
{"index":{"_id":2}}
{"business_address":"kwan ak goo"}

GET _search
{
"query": {
"match_all": {}
}
}
GET /inspections/_mapping/_doc
PUT /inspections/_mapping
{
"properties": {
"business_address":{
"type":"test",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"coordinates":{
"type":"geo_point"
}
}
}

GET /inspections/_doc/_search
{
"query": {
"range":{
"score":{
"gte":11
}
}
},
"aggregations":{
"inspection_score":{
"range":{
"field":"score",
"ranges":[
{
"key":"0-80",
"from":0,
"to":80
}
]
}
}
},
"sort":[
{
"_geo_distance":{
"coordinates":{
"lat":34.322345,
"lon":-122.234234
}
},
"order":"asc",
"unit":"km"
}
]
}


GET /inspections/_doc/_search
{
"query": {
"match":{
"business_address":"modified"
}
}
}
GET /inspections/_doc/_search
{
"query": {
"match_phrase":{
"business_address":"kwan"
}
},
"highlight":{
"fields":{
"business_address":{}
}
}
}
GET /inspections/_doc/_search
{
"query": {
"bool":{
"must":[
{
"match_phrase":{
"business_address":"kwan"
}
},
{
"match_phrase":{
"business_address":"ak"
}
}
],
"must_not":[
{
"match_phrase":{
"business_address":"goo"
}
}
]
}
}
}

PUT /inspections/_doc/t061gmgBoJ5OaX9VzeS8
{
"settings":{
"index.number_of_shards":1,
"index.number_of_replicas":0
},
"business_address":"modified"
}

PUT /inspections/_doc/t061gmgBoJ5OaX9VzeS8
{
"settings":{
"index.number_of_shards":1,
"index.number_of_replicas":0
},
"business_address":"modified"
}

POST /inspections/_doc/0k6Ug2gBoJ5OaX9VzuR6/_update
{
"settings":{
"index.number_of_shards":1,
"index.number_of_replicas":0
},
"business_address":"modified"
}

GET /inspections/_analyze
{
"tokenizer": "standard",
"text":"my email address test1234@example.com"
}




GET /inspections/_analyze
{
"tokenizer": "standard",
"filter": ["lowercase", "unique"],
"text":"My my email address test1234@example.com"
}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×