저는 원리를 파고 들어가는 진성 개발자보다는,
일단 hello world를 해보고 원리를 다시 공부하는 청개구리 스타일이에요
왜 중요한지를 알지 못하면 하지를 않아요..
그래서 ElasticSearch로 대시보드 만드는 예시를 찾아보던 중
지하철 대시보드를 따라하기로 했어요
Elastic에서 엔지니어로 일하고 계신 갓종민 님이 올려주신 유투브
과할 정도로 친절합니다. 사랑합니다 형님
2016년에 올리셨던거를, 2019년에 다시 올려주셨어요
그리고 코로나 사태가 터졌을 때 코로나 대시보드도 만들어주샸고요
Elk 모두 사용하고 있기 때문에
유투브, Git, Blog
Elastic Stack을 이용한 서울시 지하철 대시보드 다시 만들기 #1
https://www.youtube.com/watch?v=ypsEZXVYLo4&t=890s
20160414 서울시 지하철 대시보드 만들기
https://www.youtube.com/watch?v=xPjNtd8xUZo&t=3663s
기타 (활용 데이터)
https://drive.google.com/file/d/0ByqsUCpttxAGd1VXRU41VmJBNWs
아래와 같은 순서로 진행되고요
저처럼 기초가 없는 상태에서 시작하려는 분들께서 참고하면 좋은 내용도 정리해봣어요
seoul-metro-2018.logs
역별 주소 정보
라고 검색하면 나와요logstash 7.7 사용
윈도우에서는 설정이 달라요
logstash/bin 에 들어가서 실행
$ logstash -f seoul-metro-2018.conf
윈도우에서 하다보니 상대경로가 안맞아서 그냥 conf 파일을 bin에 넣어줘요.
270만건이어서 들어가는데 시간이 좀 걸립니다. 또한 윈도우는 input의 설정이 약간 달라요. sincedb_path => "nul"
//seoul-metro-2018.conf
input {
file {
path => "C:/seoul-metro-2018.logs"
# path => "/Users/kimjmin/git/elastic-demos/seoul-metro-logs/data/seoul-metro-2018.logs"
codec => "json"
start_position => "beginning"
sincedb_path => "nul"
}
}
filter {
mutate {
remove_field => ["host","path","@version"]
}
}
output {
# stdout { }
# 환경변수 설정:
# $LS_HOME/config/startup.options 또는
# $LS_HOME/bin/logstash-keystore
elasticsearch {
hosts => ["192.168.4.103:9200"]
index => "seoul-metro-logs-2018"
pipeline => "hour_and_week"
}
}
GET seoul-metro-logs-2018/_count
계속 때려주면 데이터가 들어가는지 볼수 있어요.stack monitoring
기능을 사용해도되요visualize에서 모듈형식으로 그래프들을 여러개 만들어요
승차/하차 처럼 같은 내용의 경우는 save as new를 체크하고 저장하면 됩니다.
날짜도 처음에는 안나올텐데 2018년으로 설정해줘요
나는 처음에 last 15 minutes
가 @timestamp 기준이 아니라 insert 날짜인줄 알고 조금 헤멨어요
하나씩 집어넣는게 은근 쉬우면서도 귀찮다. 속도도 약간 느려서 버벅거리기도하고. 시간 필터의 경우는 우측 상단의 날짜 설정으로 체크해야해서 매번 바꾸는게 번거로워요. 그래도 ELK 기본적인 내용을 실습했다는 뿌듯함을 느낄 수 있어 기분 좋네요
자세한거는 스터디할 때 설명 예정!
이런식으로 시간/ 일/ 주 등 원하는 양식으로 전처리를 할 수 있습니다.
1. GET 방식으로 확인
2. kibana -> index_patterns -> script fields에서 추가
//hour of day
GET seoul-metro-logs-2017/_search
{
"script_fields": {
"hour_of_day": {
"script":{
"lang": "painless",
"source":"""
def hod=doc['@timestamp'].value.getHour();
return hod;
"""
}
}
},
"query": {
"range":{
"@timestamp": {
"gte": "2017-01-01",
"lte": "2018-12-31"
}
}
}
}
//day of week
GET seoul-metro-logs-2017/_search
{
"script_fields": {
"hour_of_day": {
"script":{
"lang": "painless",
"source":"""
def dow=doc['@timestamp'].value.getDayOfWeekEnum().getValue();
return dow;
"""
}
}
},
"query": {
"range":{
"@timestamp": {
"gte": "2017-01-01",
"lte": "2018-12-31"
}
}
}
}
일일이 넣는건 불편하죠. 그래서 pipline이라는 것을 사용할 수 있어요
여러 방안이 있지만 script processor이라는 것을 유투브에서 설명해주십니다.
https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-ingest-processor-context.html
//파이프라인 만들기, 여기서는 ctx.이 중요!
PUT _ingest/pipeline/hour_and_week
{
"description": "add hour_of_day and day_of_week field from @timestamp",
"processors": [
{
"script":{
"lang": "painless",
"source":"""
def ts=ctx['@timestamp'];
def sdf=new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
def date=sdf.parse(ts);
def cal=Calendar.getInstance();
cal.setTime(date);
ctx.hour_of_day = cal.get(Calendar.HOUR_OF_DAY);
def dowNum=cal.get(Calendar.DAY_OF_WEEK);
def dowEn=["SUN","MON","TUE","Wed","Thu","Fri","Sat"][dowNum];
def dowKr=["일","월","화","수","목","금","토"][dowNum];
ctx.day_of_week = ["num":dowNum, "en":"downEn","kr":dowKr]
"""
}
}
]
}
#재색인으로 테스트!
POST _reindex
{
"source": {
"index": "seoul-metro-logs-2017",
"query": {
"range":{
"@timestamp": {
"gte": "2018-01-01",
"lte": "2018-01-02"
}
}
}
},
"dest": {
"index": "seoul-metro-logs-test",
"pipeline": "hour_and_week"
}
}
#hello world
GET _all
DELETE _template/seoul-metro
GET _template/seoul-metro
#logstash check
# count(*), *
GET seoul-metro-logs-*/_count
GET seoul-metro-logs-*/_search
#mapping check
GET seoul-metro-logs-*/_mapping
DELETE seoul-metro-logs-*
#data check
GET seoul-metro-logs-2018/_search
{
"size":1,
"query": {
"match": {
"station.name": "홍대입구"
}
}
}
GET seoul-metro-logs-2018/_search
{
"size":1,
"query": {
"match": {
"station.name": "홍대"
}
}
}
#nori check
# bin/elasticsearch-plugin install analysis-nori
# elasticsearch restart
GET seoul-metro-logs-2018/_analyze
{
"text":"홍대입구",
"analyzer": "nori"
}
#template check
GET _template
GET _template/seoul-metro
PUT _template/seoul-metro
{
"order": 5,
"index_patterns": [
"seoul-metro-logs*"
],
"settings": {
"number_of_shards": 2,
"analysis": {
"analyzer": {
"nori": {
"tokenizer": "nori_t_discard",
"filter": "my_shingle"
}
},
"tokenizer": {
"nori_t_discard": {
"type": "nori_tokenizer",
"decompound_mode": "discard"
}
},
"filter": {
"my_shingle": {
"type": "shingle",
"token_separator": "",
"max_shingle_size": 3
}
}
}
},
"mappings": {
"properties": {
"@timestamp": {
"type": "date"
},
"code": {
"type": "keyword"
},
"line_num": {
"type": "keyword"
},
"line_num_en": {
"type": "keyword"
},
"location": {
"type": "geo_point"
},
"people": {
"properties": {
"in": {
"type": "integer"
},
"out": {
"type": "integer"
},
"total": {
"type": "integer"
}
}
},
"station": {
"properties": {
"kr": {
"type": "text",
"fields": {
"nori": {
"type": "text",
"analyzer": "nori",
"search_analyzer": "standard"
},
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"fields": {
"nori": {
"type": "text",
"analyzer": "nori",
"search_analyzer": "standard"
},
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
}
GET _template/seoul-metro
#logstash 재색인 후 test
GET seoul-metro-logs-2018/_search
{
"size":1,
"query": {
"match": {
"station.name.nori": "홍대 입구"
}
}
}
# inverted index
# 홍대: 401
# 입구: 401, 402.503
#data check
GET seoul-metro-logs-*/_mapping
GET seoul-metro-logs-*/_search?size=1
GET seoul-metro-logs-2018/_doc/GYU50HIBa3wl0dvSULv2
#pipeline
GET seoul-metro-logs-2018/_search
{
"script_fields": {
"hour_of_day": {
"script":{
"lang": "painless",
"source":"""
def dow=doc['@timestamp'].value.getDayOfWeekEnum().getValue();
return dow;
"""
}
}
},
"query": {
"range":{
"@timestamp": {
"gte": "2018-01-01",
"lte": "2018-01-02"
}
}
}
}
DELETE _ingest/pipeline/hour_and_week
PUT _ingest/pipeline/hour_and_week
{
"description": "add hour_of_day and day_of_week field from @timestamp",
"processors": [
{
"script":{
"lang": "painless",
"source":"""
def ts=ctx['@timestamp'];
def sdf=new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
def date=sdf.parse(ts);
def cal=Calendar.getInstance();
cal.setTime(date);
ctx.hour_of_day = cal.get(Calendar.HOUR_OF_DAY);
def dowNum=cal.get(Calendar.DAY_OF_WEEK);
def dowEn=["SUN","MON","TUE","Wed","Thu","Fri","Sat"][dowNum];
def dowKr=["일","월","화","수","목","금","토"][dowNum];
ctx.day_of_week = ["num":dowNum, "en":"downEn","kr":dowKr]
"""
}
}
]
}
#재색인으로 테스트!
POST _reindex
{
"source": {
"index": "seoul-metro-logs-2018",
"query": {
"range":{
"@timestamp": {
"gte": "2018-01-01",
"lte": "2018-01-02"
}
}
}
},
"dest": {
"index": "seoul-metro-logs-test",
"pipeline": "hour_and_week"
}
}
GET seoul-metro-logs-test/_search?size=1
# logstash, pipeline 있는걸로 재색인
GET seoul-metro-logs-2018/_count
GET seoul-metro-logs-2018/_search?size=1
#inspect test
GET seoul-metro-logs-*/_search
{
"aggs": {
"3": {
"terms": {
"field": "day_of_week.num",
"order": {
"_key": "desc"
},
"size": 7
},
"aggs": {
"4": {
"terms": {
"field": "hour_of_day",
"order": {
"1": "desc"
},
"size": 24
},
"aggs": {
"1": {
"sum": {
"field": "people.in"
}
}
}
}
}
}
},
"size": 0,
"stored_fields": [
"*"
],
"script_fields": {},
"docvalue_fields": [
{
"field": "@timestamp",
"format": "date_time"
}
],
"_source": {
"excludes": []
},
"query": {
"bool": {
"must": [],
"filter": [
{
"match_all": {}
},
{
"range": {
"@timestamp": {
"gte": "2017-12-01T05:30:24.385Z",
"lte": "2018-01-31T06:30:35.395Z",
"format": "strict_date_optional_time"
}
}
}
],
"should": [],
"must_not": []
}
}
}