본문 바로가기

문돌이 존버/ELK 스터디

ELK 기본 명령어 스터디(튜토리얼)

반응형

<출처: Elastic 홈페이지>

ELK(elasticsearch + logstash + kibina) 스터디 내용입니다. ELK를 처음 접하느라 Elastic 홈페이지에 소개하는 튜토리얼 동영상을 보며 따라했습니다. 튜토리얼 동영상에 나오는 github 주소는 여기입니다. 아래 정리한 데는 강사의 설명을 모두 주석으로 처리했습니다. 

➜  bin curl localhost:9200 
{
  "name" : "호스트 이름.local",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "G_3I66A5Ru-gqjWS7GCsVQ",
  "version" : {
    "number" : "7.7.0",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "81a1e9eda8e6183f5237786246f6dced26a10eaf",
    "build_date" : "2020-05-12T02:01:37.602180Z",
    "build_snapshot" : false,
    "lucene_version" : "8.5.1",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}
➜  bin curl -XPUT "http://localhost:9200/my_index/_doc/1" -H 'Content-Type: application/json' -d' {"message":"안녕하세요 Elasticsearch"}' # XPUT: 텍스트 입력 command
{"_index":"my_index","_type":"_doc","_id":"1","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":0,"_primary_term":1}%                                                                                               
➜  bin curl -XGET http://localhost:9200/my_index/_doc/1{"_index":"my_index","_type":"_doc","_id":"1","_version":1,"_seq_no":0,"_primary_term":1,"found":true,"_source": {"message":"안녕하세요 Elasticsearch"}}%

# XGET: 입력이 잘되었는지 확인 
➜  bin curl -XPUT "http://localhost:9200/my_index/_doc/1" -H 'Content-Type: application/json' -d' {"message":"안녕하세요 Elastic Stack"}'
{"_index":"my_index","_type":"_doc","_id":"1","_version":2,"result":"updated","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":1,"_primary_term":1}%                                                                                               
➜  bin curl -XGET http://localhost:9200/my_index/_doc/1{"_index":"my_index","_type":"_doc","_id":"1","_version":2,"_seq_no":1,"_primary_term":1,"found":true,"_source": {"message":"안녕하세요 Elastic Stack"}}%                                                                                                  
➜  bin curl -XPOST "http://localhost:9200/my_index/_doc" -H 'Content-Type: application/json' -d' {"message":"안녕하세요 Kibana"}'

# XPOST: _doc으로만 디렉토리 설정(전과 같이 1을 작성하지 않음 => 결과로 랜덤하게 id가 주어짐)
{"_index":"my_index","_type":"_doc","_id":"qvaiO3IBJ5rsE_UkQUFH","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":2,"_primary_term":1}%                                                                            
➜  bin curl localhost:9200/my_index/_doc/qvaiO3IBJ5rsE_UkQUFH{"_index":"my_index","_type":"_doc","_id":"qvaiO3IBJ5rsE_UkQUFH","_version":1,"_seq_no":2,"_primary_term":1,"found":true,"_source": {"message":"안녕하세요 Kibana"}}%                                                                                       
➜  bin curl -XDELETE "http://localhost:9200/my_index/_doc/1"{"_index":"my_index","_type":"_doc","_id":"1","_version":3,"result":"deleted","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":3,"_primary_term":1}%                                                                                               

# XDELETE: 도큐먼트 삭제
➜  bin curl -XGET http://localhost:9200/my_index/_doc/1     
{"_index":"my_index","_type":"_doc","_id":"1","found":false}%                                                                 
➜  bin curl -XDELETE http://localhost:9200/my_index # 인덱스 단위로도 삭제 가능        
{"acknowledged":true}%                                                                                                        
➜  bin curl -XGET http://localhost:9200/my_index/_doc/1
{"error":{"root_cause":[{"type":"index_not_found_exception","reason":"no such index [my_index]","resource.type":"index_expression","resource.id":"my_index","index_uuid":"_na_","index":"my_index"}],"type":"index_not_found_exception","reason":"no such index [my_index]","resource.type":"index_expression","resource.id":"my_index","index_uuid":"_na_","index":"my_index"},"status":404}%              
---------------------------------------------------------------------------
# Kibana localhost 사이트에 들어가서 작업 시작 
# Kibana 사이트와 Elasticsearch 사이트는 디폴트로 다르게 설정되어 있으나 이후 같은 공간에서 작업 가능 
PUT my_index/_doc/1
{
  "message":"안녕하세요 여러분"
}

# Bulk 색인
# 다량의 도큐먼트를 한꺼번에 색인 할 때는 반드시 bulk API 사용(속도 약 10배 차이)

POST my_index/_bulk
{"index":{"_id":1}}
{"message":"The quick brown fox"}
{"index":{"_id":2}}
{"message":"The quick brown fox jumps over the lazy dong"}
{"index":{"_id":3}}
{"message":"The quick brown fox jumps over the quick dong"}
{"index":{"_id":4}}
{"message":"Brown fox brown dong"}
{"index":{"_id":5}}
{"message":"Lazy jumping dog"}

# 풀텍스트 검색(_search)
# 인덱스의 전체 도큐먼트 검색: match_all
# 기본 디폴트 검색 사이즈는 10개 => "size":3 와 같이 추가 명령어로 조절 가능 
GET my_index/_search
{
  "query":{
    "match_all":{ }
  }
}

#match 쿼리: dog 검색
GET my_index/_search
{
  "query": {
    "match": {
      "message":"dog"
    }
  }
}

# match 쿼리: quick 또는 dog 검색(or)
# 띄어쓰기 = or의 의미
GET my_index/_search
{
  "query": {
    "match": {
      "message": "quick dog"
    }
  }
}

# match 쿼리: quick과 dog 검색(and)
GET my_index/_search
{
  "query": {
    "match": {
      "message": {
        "query": "quick dog",
        "operator": "and"
      }
    }
  }
}

# match_phrase 쿼리: "lazy dog" 구문 검색
GET my_index/_search
{
  "query": {
    "match_phrase": {
      "message": "lazy dog"
    }
  }
}

# 복합 쿼리 - bool 쿼리를 이용한 서브쿼리 조합
# must: 쿼리가 참인 도큐먼트들을 검색
# must_not: 쿼리가 거짓인 도큐먼트들을 검색
# should: 검색 결과중 이 쿼리에 해당하는 도큐먼트의 점수를 높임
# must에 해당하는 도큐먼트들 중 should 조건을 만족하면 랭킹 점수라고 하는 스코어가 올라감(스코어가 높을수록 먼저 가져오게 됨)
# filter: 쿼리가 참인 도큐먼트를 검색하여 조건을 만족하지 않는 도뮤컨트들은 제거하는 것으로 스코어를 계산하지 않음, must보다 검색 속도가 빠르고 캐싱됨

# "quick"과 "lazy dog"가 포함된 모든 문서 검색
GET my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {"message":"quick"}
        },
        {
          "match_phrase": {"message":"lazy dog"}
        }
      ]
    }
  }
}

# "fox"를 포함하는 모든 도큐먼트 중 "lazy"가 포함된 결과에 가중치 부여
GET my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "message":"fox"
          }
        }
      ],
      "should": [
        {
          "match": {
            "message":"lazy"
          }
        }
      ]  
    }
  }
}

# "fox"와 "quick"을 포함하는 쿼리의 must & filter 스코어 비교
GET my_index/_search
{
  "query": {
    "match": {
      "message": "fox"
    }
  }
}

GET my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "message": "fox"
          }
        },
        {
          "match": {
            "message": "quick"
          }
        }
      ]
    }
  }
}

GET my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "message":"fox"
          }
        }
      ],
      "filter": [
        {
          "match": {
            "message":"quick"
          }
        }
      ]
    }
  }
}
---------------------------------------------------------------------------
# range 쿼리(숫자나 날짜 쿼리)
# gte(Greater-than or equal to): 이상(같거나 큼)
# gt(Greater-than): 초과(큼)
# lte(Less-than or euqual to): 이하(같거나 작음)
# lt(Less-than): 미만(작음)

POST phones/_bulk
{"index":{"_id":1}}
{"model":"Samsung GalaxyS 5","price":475,"date":"2014-02-24"}
{"index":{"_id":2}}
{"model":"Samsung GalaxyS 6","price":795,"date":"2015-03-15"}
{"index":{"_id":3}}
{"model":"Samsung GalaxyS 7","price":859,"date":"2016-02-21"}
{"index":{"_id":4}}
{"model":"Samsung GalaxyS 8","price":959,"date":"2017-03-29"}
{"index":{"_id":5}}
{"model":"Samsung GalaxyS 9","price":1059,"date":"2018-02-25"}

# price 필드 값이 700 이상, 900 미만인 데이터를 검색
GET phones/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 700,
        "lt": 900
      }
    }
  }
}

# data 필드 날짜가 2016년 1월 1일 이후인 도큐먼트들을 검색
GET phones/_search
{
  "query": {
    "range": {
      "date": {
        "gt": "2016-01-01"
      }
    }
  }
}

# data 필드 날짜가 오늘(2020년 5월 23일)부터 4년 전 이후인 도큐먼트들을 검색
GET phones/_search
{
  "query": {
    "range": {
      "date": {
        "gt": "now-36M" # y(연도) M(월) d(일) H(시간) m(분) s(초) 단위 모두 가능
      }
    }
  }
}
---------------------------------------------------------------------------
# 텍스트 분석 - Analysis(_analyze API)

# Tokenizer을 통해 문장을 검색어 텀(term)으로 쪼갬
GET my_index/_analyze
{
  "tokenizer": "standard",
  "text": "Brown fox brown dog"
}

# Filter(토큰필터)를 통해 쪼개진 텀들을 가공
# lowercase - 소문자로 변경
GET my_index/_analyze
{
  "tokenizer": "standard",
  "filter": [
    "lowercase"
  ],
  "text": "Brown fox brown dog"
}

# unique - 중복 텀 제거
GET my_index/_analyze
{
  "tokenizer": "standard",
  "filter": [
    "lowercase",
    "unique"
  ],
  "text": "Brown brown brown fox brown dog"
}

# (standard Tokenizer + lowercase Filter) 대신 Analyzer 사용
GET my_index/_analyze
{
  "analyzer": "standard",
  "text": "Brown fox brown dog"
}

# 분석 과정 이해하기
# 복합적인 문장 분석 - T:standard, F:lowercase
GET my_index/_analyze
{
  "tokenizer": "standard",
  "filter": [
    "lowercase"
  ],
  "text": "THE quick.brown_FOx jumped! $19.95 @ 3.0"
}

# 복합적인 문장 분석 - T:letter, F:lowercase
# 문자를 제외한 기호 모두 제거
GET my_index/_analyze
{
  "tokenizer": "letter",
  "filter": [
    "lowercase"
  ],
  "text": "THE quick.brown_FOx jumped! $19.95 @ 3.0"
}

# Email, URL 분석 - T:standard
GET my_index/_analyze
{
  "tokenizer": "standard",
  "text": "elastic@example.com website: https://www.elastic.co"
}

# Email, URL 분석 - T:uax_url_email
# 이메일 형태, 주소 형태 유지한 채로 변환 
# ex) elastic@example.com / https://www.elastic.co
GET my_index/_analyze
{
  "tokenizer": "uax_url_email",
  "text": "elastic@example.com website: https://www.elastic.co"
}
--------------------------------------------------------------------------------
# 한글 형태소 분석기 nori 설치
# $ bin/elasticsearch-plugin install analysis-nori

# nori_tokenizer를 이용한 한글 분석(standard tokenizer와 비교)
# ex) 동해/물/과/백두/산/이
GET _analyze
{
  "tokenizer": "standard"
  "text": ["동해물과 백두산이"]
}

GET _analyze
{
  "tokenizer": "nori_tokenizer",
  "text": ["동해물과 백두산이"]
}
--------------------------------------------------------------------------------
# 인덱스 생성
# settings: analyzer, 샤드 수, 리프레시 주기 등을 설정
# maapings: 각 필드별 데이터 명세를 정의(텍스트, 숫자 필드? 각 필드는 어떤 애널라이져를 사용할지?)

# 사용자 정의 analyzer
# settings, mappings는 한 번 설정하면 대부분 수정하기 어렵기 때문에 생성할 때 주의하자!
PUT my_index_2
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "letter",
          "filter": [
            "lowercase",
            "stop" # stopwords(is, the)
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "message": {
        "type": "text",
        "analyzer": "my_analyzer"
      }
    }
  }
}

# 사용자 정의 analyzer 필드에 데이터 색인
PUT my_index_2/_doc/1
{
  "message": "THE quick.brown_FOx jumped! $19.95 @ 3.0"
}

# 데이터 검색
# letter tokenizer를 설정했기 때문에 quick.brown_FOx 부분은 잘 분리됨
GET my_index_2/_search
{
  "query": {
    "match": {
      "message": "brown"
    }
  }
}

# stopwords를 설정했기 때문에 the는 검색되지 않음 
GET my_index_2/_search
{
  "query": {
    "match": {
      "message": "the"
    }
  }
}
--------------------------------------------------------------------------------
# 애그리게이션 - 집계(Aggregation)
# metrics: min, max, sum, avg 등의 계산
# bucket: 특정 기준으로 도큐먼트들을 그룹화

PUT my_stations/_bulk
{"index": {"_id":"1"}}
{"date": "2019-06-01", "line": "1호선", "station": "종각", "passangers": 2314}
{"index": {"_id":"2"}}
{"date": "2019-06-01", "line": "2호선", "station": "강남", "passangers": 5412}
{"index": {"_id":"3"}}
{"date": "2019-07-10", "line": "2호선", "station": "강남", "passangers": 6221}
{"index": {"_id":"4"}}
{"date": "2019-07-15", "line": "2호선", "station": "강남", "passangers": 6478}
{"index": {"_id":"5"}}
{"date": "2019-08-07", "line": "2호선", "station": "강남", "passangers": 5821}
{"index": {"_id":"6"}}
{"date": "2019-08-18", "line": "2호선", "station": "강남", "passangers": 5724}
{"index": {"_id":"7"}}
{"date": "2019-09-02", "line": "2호선", "station": "신촌", "passangers": 3912}
{"index": {"_id":"8"}}
{"date": "2019-09-11", "line": "3호선", "station": "양제", "passangers": 4121}
{"index": {"_id":"9"}}
{"date": "2019-09-20", "line": "3호선", "station": "홍제", "passangers": 1021}
{"index": {"_id":"10"}}
{"date": "2019-10-01", "line": "3호선", "station": "불광", "passangers": 971}

# 전체 passangers 필드값의 합계를 가져오는 metrics aggregation
GET my_stations/_search
{
  "size": 0, # size=0을 하면 우리가 원하는 모든 승객수만 반환 <-> size=0이 없다면 각 지하철역의 정보까지 동시에 반환
  "aggs": {
    "all_passangers": {
      "sum": {
        "field": "passangers"
      }
    }
  }
}

# "station": "강남"인 도큐먼트의 passangers 필드값의 합계를 가져오는 metrics aggregation
GET my_stations/_search
{
  "query": {
    "match": {
      "station": "강남"
    }
  },
  "size": 0,
  "aggs": {
    "gangnam_passangers": {
      "sum": {
        "field": "passangers"
      }
    }
  }
}

# date_histogram으로 date 필드를 1개월 간격으로 구분하는 bucket aggregation
# 해당하는 도큐먼트 개수 반환
GET my_stations/_search
{
  "size":0,
  "aggs": {
    "date_his": {
      "date_histogram": {
        "field": "date",
        "interval": "month"
      }
    }
  }
}

# stations.keyword 필드 별로 passangers 필드의 평균값을 계산하는 bucket & metrics aggregation
GET my_stations/_search
{
  "size":0,
  "aggs": {
    "stations": {
      "terms": {
        "field": "station.keyword"
      },
      "aggs": {
        "avg_psg_per_st": {
          "avg": {
            "field": "passangers"
          }
        }
      }
    }
  }
}
--------------------------------------------------------------------------------
# Geo - 위치 정보
# geo_point : {"lat": 41.12, "lon": -71.34} 같은 형식으로 입력

# geo_point 타입의 location 필드 선언
PUT my_geo
{
  "mappings": {
    "properties": {
      "location": {
        "type": "geo_point"
      }
    }
  }
}

# 예제 데이터 입력
# location 단위로 위도, 경도 입력
# 그냥 위도, 경도를 입력하면 실수 필드 더블 타입으로 들어가기 때문에 geo_point 타입을 통해 location이라는 단위 필드에 속하도록 
PUT my_geo/_bulk
{"index":{"_id":"1"}}
{"station":"강남", "location": {"lon":127.027926, "lat":37.497175}, "line": "2호선"}
{"index":{"_id":"2"}}
{"station":"종로3가", "location": {"lon":126.991806, "lat":37.571607}, "line": "3호선"}
{"index":{"_id":"3"}}
{"station":"여의도", "location": {"lon":126.924191, "lat":37.521624}, "line": "5호선"}
{"index":{"_id":"4"}}
{"station":"서울역", "location": {"lon":126.972559, "lat":37.554648}, "line": "1호선"}

# geo_bounding_box: 두 점을 기준으로 하는 네모 안에 있는 도큐먼트들을 가져옴
GET my_geo/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "bottom_right": {
          "lat": 37.4899,
          "lon": 127.0388
        },
        "top_left": {
          "lat": 37.5779,
          "lon": 126.9617
        }
      }
    }
  }
}

# geo_distance: 한 점을 기준으로 반경 안에 있는 도큐먼트들을 가져옴
GET my_geo/_search
{
  "query": {
    "geo_distance": {
      "distance": "5km",
      "location": {
        "lat": 37.5358,
        "lon": 126.9559
      }
    }
  }
}

참고 : should는 스코어 계산 후 랭킹을 매겨 가장 높은 점수의 도큐먼트를 상위에 출력

{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : 0.94896436,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.94896436,
        "_source" : {
          "message" : "The quick brown fox jumps over the lazy dog"
        }
      }

여기까지가 가장 기본적인 Elasticsearch 활용 방법입니다. 여기서 Kibina에 접속하긴 했지만 저희가 일반적으로 생각하는 시각화 도구는 하나도 사용하지 않았습니다. Kibina UI에서 Dev tools만 사용했습니다. 

728x90
반응형