티스토리 뷰

Graylog란?

  • GraylogELK Stack, Grafana와 경쟁하는 오픈 소스 로그 관제 솔루션이다. 애플리케이션이 전송한 로그 메시지의 적재와 조회, 시각화 등의 기본 기능 외 많은 기능을 제공한다. 크게 Graylog Server(Java 기반), Elasticsearch(로그 데이터 저장소), MongoDB(환경설정 저장소)로 구성된다. 이를 통해 애플리케이션으로부터 전송되는 로그를 거의 실시간으로 구조화하여 적재할 수 있다.

구조화된 로그가 필요한 이유

  • 그렇다면 외부 애플리케이션을 써서라도 어째서 구조화된 로그 수집이 필요한 것일까? AI의 등장과 함께 이제 애플리케이션 로그는 사람이 보는 정보가 아니라 기계가 분석하는 정보가 되었다. 기계가 로그를 분석하려면 기존의 전통적인 라인 단위의 텍스트 로그로는 부족하다. 잘 설계된 구조화된 로그를 전송하면 기계에 의해 분석되어 유의미한 정보로 가공할 수 있다. Graylog가 개발한 구조화된 GELF 로그 포맷은 그러한 목적으로 탄생하였다. [관련 링크]
  • 단, 한 번이라도 본인이 개발하지 않은 코드에서 발생한, 원인도 모르겠는 예외를 추적하기 위해 식은 땀을 흘리며 콘솔에서 grep 명령어를 사용하여 시간을 할애한 기억이 있다면 본 솔루션을 강력히 추천한다. 실제로 디버깅의 트렌드 또한 IDE에서 한 문장씩 문제의 원인을 찾아가는 것보다 적절한 시점에 상세하게 적재된 로그를 확인하는 것으로 옮겨가고 있다.

Graylog 특징

  • Graylog ServerSyslog TCP/UDP, GELF TCP/UDP 스트림을 바로 수신할 수 있다. 실제 로그를 생성하는 애플리케이션에서는 Graylog Server를 수신지로 바로 로그를 전송하면 된다. 즉, 애플리케이션에서는 로그 데이터를 1차원의 Key-Value 구조로 GELF 포맷으로 전송하기만 하면 Graylog에 의해 Elasticsearch에 적재하고 시각화된 정보로 가공하여 즉시 이용할 수 있다.
  • 애플리케이션에서 바로 스트림을 전송하지 않고 파일로 로그를 저장할 경우 각 애플리케이션 서버에서 Logstash, Graylog Collector Sidecar를 이용하여 주기적으로 Graylog Server로 전송하는 방법이 있다. 서버 수가 많을수록 아키텍처가 복잡해진다는 단점이 있다. 또한, 상황에 따라 유연한 오토 스케일링이 요구되는 마이크로서비스 아키텍쳐에서는 각 애플리케이션 노드에 일시적으로 남아 미처 전송되지 못한 로그 또한 상태(Stateful)로 간주될 수 있다. 내 경우 고객의 결제 정보와 같이 일말의 데이터 유실 가능성도 허용되지 않는 성격의 엄격한 데이터를 제외한 모든 데이터를 UDP로 실시간 전송하여 수집하는 방법으로 로그 수집 정책을 세웠고, 결과적으로 아키텍쳐를 단순화할 수 있었다.
  • Graylog Server는 1개로도 운영이 가능하지만 n개의 클러스터 구성도 가능하다. 실제 운영 환경에서는 무중단 로그 수집을 위해 이 구성이 필수이다. 각 서버는 HTTP 헬스 체크 기능을 제공하므로 앞에 위치한 로드 밸런서에서 적절한 분기가 가능하다. [관련 링크] 로드 밸런서는 NGINX를 이용할 수 있다. TCP, UDP 트래픽에 대한 프록시 및 로드 밸런싱 기능을 제공한다. [관련 링크]
  • Graylog Web Interface는 앞서 설명한 로드 밸런서 역할의 서버에 같이 위치할 수 있다.
  • MongoDBGraylog의 환경설정 등의 메타데이터를 저장하는 역할을 수행한다.
  • Elasticsearch는 실제 로그 데이터가 수집될 저장소 역할을 한다. 역시 n개의 클러스터 구성이 권장된다. Graylog의 로그 백업 정책은 Elasticsearch의 아키텍처에 의존적이다.(Snapshot, Restore)

GELF 로그 포맷

  • GraylogGELF라는 자체 로그 포맷을 사용하여 로그를 전송할 수 있다. [관련 링크]
  • GELF는 여러 장점을 가진다. 로그를 UDP로 전송할 경우 건 당 8,192바이트로 메시지 크기가 제한되는데 GELF는 사용자가 신경쓰지 않아도 라이브러리가 분할 전송함으로서 용량 제한을 극복했다. 또한, 기본적으로 GZIP으로 로그를 압축하여 전송하기 때문에 네트워크 대역폭을 절약할 수 있다. 이런 여러 장점으로 인해 Graylog를 사용하지 않는 툴(대표적으로 Docker)에서도 GELF 포맷만 따로 사용할 정도다.
  • GELF는 단순한 Key-Value의 집합으로 Value에는 숫자 또는 문자열만 담을 수 있다.
  • GELF는 필수 필드와 사용자 커스텀 필드로 구분된다. 필수 필드에는 host, short_message, full_message, timestamp, level이 있다. 커스텀 필드명 앞에는 구분을 위해 _ 문자를 붙인다.
  • GELF는 기본적으로 JSON 형식을 지원하지 않는다. 다만, 특정 KeyJSON 문자열을 담아 보내면 Graylog Server의 해당 InputJSON Extractor를 설정하여 Key로 변환할 수 있다. 예를 들면 http.method라는 JSON 문자열에 속한 2차원 필드 구조는 http_method라는 1차원 필드명으로 변환된다. [관련 링크]
  • Spring Boot 환경에서 GraylogGELF UDP 로그를 전송하는 방법은 본 블로그의 이 글을 참고한다.

인덱스

  • Graylog는 모든 로그 데이터를 Elasticsearch 클러스터에 저장한다. 기본 설정은 2,000만개 로그가 쌓일 때마다 인덱스를 새로 생성하며 총 인덱스 개수가 20개를 초과하면 오래된 인덱스는 삭제한다.
  • Elasticsearch 클러스터에 생성되는 인덱스 이름은 graylog_0부터 시작하여 뒷자리 숫자가 순차적으로 증가하며 새로 생성된다.
  • 특정 로그를 인덱스에서 삭제하려면 ElasticsearchREST API를 이용하면 된다. [관련 링크]
  • Graylog가 생성하는 모든 인덱스는 graylog-internal이라는 이름의 인덱스 템플릿을 적용하여 로그를 저장한다. [관련 링크]
  • 로그 저장시 특정 필드(검색 대상으로 필요하지 않은 긴 문자열)를 인덱스 대상에서 제외하려면 Elasticsearch에 저장하기 전 커스텀 인덱스 템플릿을 생성하여 no로 설정을 해야 한다. (analyzed, not_anlayzed 설정된 필드만 검색 대상이 된다.) [관련 링크]

아키텍쳐

  • Graylog Server 클러스터의 무중단 운영은 로그의 유실 없는 관리를 위해 필수적이다. 따라서 앞단에 로드 밸런서가 존재해야 한다.
  • NGINX는 기본적으로 TCP/UDP 로드 밸런싱을 지원한다. 하지만 각 서버의 헬스 체크 기능을 활용하려면 NGINX Pro 버전을 구매해야 하는 단점이 있다.
  • HAProxyUDP 로드 밸런싱을 지원하지 않지만 각 서버의 헬스 체크 기능을 제공한다.
  • Keepalived + ipvsadm 조합이 유일하게 TCP/UDP 로드 밸런싱과 헬스 체크 기능을 모두 제공한다. 이 방법이 가장 현실적이다. [관련 링크]
  • 위 언급한 로그 밸런싱에 대한 고려는 물리 서버를 이용했을 경우로, AWS를 이용할 경우는 Amazon Route 53의 가중치 기반 라운드 로빈 정책을 이용하여 UDP 요청 로그를 각 Graylog 서버로 분배할 수 있다.(Amazon ELB는 2019-06-24 부터 NLB를 통해 UDP 로그 밸런싱을 지원한다.)
  • Graylog Server 클러스터의 헬스 체크는 각 노드의 HTTP REST API를 호출하여 응답 코드로 식별이 가능하다. GET /api/system/lbstatus를 호출시 200 코드와 ALIVE 문자열을 리턴하면 정상, 503 코드와 DEAD 문자열을 리턴하면 해당 노드가 죽은 것이다. [관련 링크]
  • 애플리케이션에서 GELF UDP로 로그를 전송하면 로드 밸런서 사용이 불가능하다. 이유는 GZIP 압축된 전체 메시지 크기가 8,192바이트를 초과하면 Chunking이 작동하여 메시지가 분할 전송되는데 한 노드로 전부 가지 못하고 로드 밸런서에 의해 여러 노드로 분산 전송되면서 결국 로그가 깨지기 때문이다. 대부분의 로그는 어지간하면 8,192바이트 안에서 소화되기 때문에 애플리케이션에서 로그 정책을 잘 수립하면 문제가 없지만 항상 감안하고 있어야 한다. GELF UDP를 통해 로그를 쪼개어 전송할 경우, 쪼개진 로그 조각들은 반드시 동일한 Graylog 노드로 전송되어야 하며, 5초 이내에 모든 로그 조각이 전송되어야 한다. 또한 최대 128조각을 넘을 수 없다. 3가지 조건 중 1개만 지켜지지 않아도 로그는 유실된다. 특히, 단일 노드 전송 조건은 UDP 로드 밸런서 설정시 가장 유의해야 하는 부분으로 흔한 라운드 로빈 또는 이와 유사한 가중치 기반 방식의 로드 밸런싱 정책을 적용할 경우 로그가 유실될 확률이 높다.

Graylog 서버 설치

  • Graylog 서버 설치는 본 블로그의 이 글을 참고한다.
  • Apache HTTP Server의 액세스 로그를 Graylog 서버로 전송하는 방법은 본 블로그의 이 글을 참고한다.
  • Spring Boot 기반 프로젝트에서 Graylog로 로그를 전송하는 방법은 본 블로그의 이 글을 참고한다.

스트림

  • 스트림(Stream)은 일종의 필터이다. GraylogInput을 통해 수신되는 모든 로그 메시지에 대해 실시간으로 특정 필드 조건에 따라 경고 알람(Alert) 등의 다양한 동작이 가능하다.
  • 스트림을 별도로 생성하지 않으면 모든 로그는 All messages라는 기본 스트림을 통과하여 ElasticSearch 저장소에 저장된다.
  • 스트림은 기본적으로 저장소의 Default index set에 저장된다. 중복 저장을 예방하기 위해 하나의 로그 메시지는 여러 스트림을 통과해도 저장소에는 단 한 번만 저장된다. 또한, 스트림을 삭제해도 저장소에 저장된 로그 메시지는 그대로 유지된다.
  • 스트림을 생성하면 1개 이상의 규칙(Rule)을 지정할 수 있다. 수신되는 모든 로그 메시지의 특정 필드가 작성한 규칙을 만족하면 해당 스트림으로 분류되어 관리할 수 있다.
  • 스트림 사용에 있어 장점은 사용자 계정 단위로 개별 스트림에 대해 읽기 또는 수정 권한을 부여할 수 있다.
  • 스트림 사용에 있어 단점은 너무 많은 스트림과 규칙을 지정하면 Graylog가 느려질 수 있다는 것이다.

대시보드

  • 대시보드(Dashboard)는 일종의 상황실로 비유할 수 있다. Graylog의 핵심 기능이라고 할 수 있다. 대시보드를 통해 수신된 로그 메시지를 적절히 필터링하여 인간이 한 눈에 알아볼 수 있는 비주얼한 화면을 구성할 수 있다.
  • 스트림과 마찬가지로 사용자 계정 단위로 개별 대시보드에 대해 읽기 또는 수정 권한을 부여할 수 있다.
  • 대시보드 만으로도 직관적인 시각화 정보를 얻을 수 있다. 하지만 추가적으로 Graylog REST API를 이용하여 입맛에 맞게 시각화 정보를 생성하여 각종 보고서나 조기경보 시스템을 구현하는 것이 가능하다. 자세한 사용법은 본 블로그의 이 글을 참고한다.

사용자 계정과 인증

  • Graylog의 가장 큰 장점은 사용자 계정 단위로 별도의 역할(Role)과 권한(Permission)을 부여할 수 있다는 것이다. 기본 계정으로 모든 권한을 가진 Administrator가 존재하는데 단순히 로그를 확인하기 위한 용도의 엔드 유저에게 이 계정을 공유하는 것은 상당히 위험하다. 별도의 사용자 계정을 생성하면 접근을 허용할 스트림과 대시보드를 각각 1개 이상 부여할 수 있어 효과적이고 안전한 통제가 가능하다.
  • 국내에서 가장 널리 쓰이는 ELK Stack으로는 Graylog만큼의 효과적인 사용자 계정 관리가 불가능하다. 대개 Kibana의 앞단에 리버스 프록시로 NGINX을 두어 물리적인 접근을 통제하거나 ElasticSearchShield를 설치하여 인덱스에 대한 접근을 통제하는 방식이다.

로그 메시지 검색

  • Graylog는 검색할 로그 메시지의 시간 범위를 지정할 수 있는데 대표적으로 키워드(Keyword) 방식을 지원한다. 키워드 검색은 제일 자유도가 높아 대시보드 구성에 적절하다. Natty 라이브러리가 이를 담당하며 키워드 문법을 익혀야 한다. [관련 링크] 타임존 오프셋을 지정하지 않으면 UTC 기준 시간으로 인식하여 검색됨에 유의한다.
// 어제 yesterday 00:00:00 +09:00 to yesterday 23:59:59 +09:00  // 오늘 today 00:00:00 +09:00 to today 23:59:59 +09:00  // 2일전 2 days ago 00:00:00 +09:00 to 2 days ago 23:59:59 +09:00  // 7일전~현재 7 days ago 00:00:00 +09:00 to today 23:59:59 +09:00 
  • 다음은 가장 중요한 로그 메시지 검색 부분이다. 사용법이 매우 직관적으로 사용 예는 아래와 같다.
// 가장 단순하고 많이 쓰이는 조회 쿼리 // 특정 필드에 "2019-04-11" 문자열이 포함되어 있는 모든 로그 조회 2019-04-11  // request_path 필드 값이 "/foobar/"로 시작하는 모든 로그 조회 // 이스케이프 문자로 "\"를 사용해야 하는 것에 유의 request_path:\/foobar\/*  // 루씬 정규식 조회, message 필드에 ""숫자.숫자.숫자.1" 문자열이 포함된 모든 로그 조회 message:/..+\..+\..+\1+/ 

IP 주소 기반 위치 정보 로깅하기

  • Graylog는 기본적으로 Geo-Location Processor 플러그인을 내장하고 있는데, 이를 통해 IP 주소에 해당하는 로그 필드에 대해 위치 정보를 담은 필드를 추가할 수 있다. 먼저 서버에 위치 데이터베이스를 다운로드해야 한다. 다운로드 방법은 아래와 같다.
$ cd /opt $ sudo wget https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz $ sudo tar -zxvf GeoLite2-City.tar.gz $ sudo mv ./GeoLite2-City_20191001/GeoLite2-City.mmdb /etc/graylog/server 
  • 설치 완료되었으면 플러그인 활성화가 필요하다. System > Configurations > Plugins: Geo-Location Processor > Update > Enable Geo-Location processor 체크 > Save를 순서대로 클릭하면 이후 수신되는 모든 로그의 IP 주소 필드에 위치 필드가 추가된다. 추가되는 필드는 아래와 같다.
{IP 주소 필드명}_city_name: Seoul {IP 주소 필드명}_contry_code: KR {IP 주소 필드명}_geolocation: 37.5985,126.9783 
  • 위 필드 중 _geolocation 필드는 특별히, World Map 위젯을 생성할 수 있는데, 세계지도에 해당 IP 주소의 위치 정보가 포인트로 출력되어 시각적 지표로 활용하기 좋다.

파이프라인 설정으로 조건에 따라 로그 필드 추가하기

  • 파이프라인 설정을 통해 특정 조건에 따라 내가 원하는 로그 필드와 값을 추가할 수 있다.
  • 파이프라인은 크게 RulePipeline으로 구성된다. Rule에는 특정 조건에 따른 특정 필드의 생성을 Grok 문법으로 설정할 수 있다. Pipeline은 특정 Stream에 앞서 생성한 Rule을 복수개 적용할 수 있다.
  • 가장 먼저 Rule을 작성해보자. System > Pipelines > Manage rules > Create Rule 순서로 실행 후 Rule source에 아래 내용을 입력하고 Save 버튼을 클릭하면 생성된다. 아래는 특정 IP 주소에 대해 필드를 새로 생성하는 룰이다.
rule "foo" when     has_field("ip_address") && to_string($message.ip_address) == "123.123.123.123" then     set_field("node_group", "dev");     set_field("node_name", "dev1"); end 
  • 아래는 요청 User-Agent로부터 IE11 브라우저를 식별하는 예이다.
rule "request_user_agent_ie11" when     contains(to_string($message.request_user_agent), "rv:11.0", true) then     set_field("request_browser", "IE11"); end 
  • 아래는 요청 User-Agent로부터 안드로이드 기기를 식별하는 예이다.
rule "request_user_agent_android" when     contains(to_string($message.request_user_agent), "Android", true) then     set_field("request_device", "Android"); end 
  • 아래는 요청 User-Agent로부터 애플 기기를 식별하는 예이다.
rule "request_user_agent_ios" when     contains(to_string($message.request_user_agent), "iPhone", true) || contains(to_string($message.request_user_agent), "iPad", true) then     set_field("request_device", "iOS"); end 
  • 생성한 파이프라인이 정상적으로 적용되려면 System > Configurations > Message Processors Configuration > Update를 차례로 클릭하고 프로세서의 순서를 아래와 같이 조정해야 한다. (Pipeline ProcessorMessage Filter Chain보다 아래로 오는 것 중요하다.)
AWS Instance Name Lookup GeoIP Resolver Message Filter Chain (*) Pipeline Processor (*) 

특정 조건 만족시 Slack 알람 전송하기

  • 특정 조건 만족시 Slack 알람을 전송할 수 있다. 먼저 각 노드마다 Slack 플러그인을 설치해야 한다. [플러그인 설치 링크] 설치 방법은 아래와 같다.
# Graylog 기본 플러그인 디렉토리로 이동 $ cd /usr/share/graylog-server/plugin  # Slack 플러그인 다운로드 $ sudo wget https://github.com/graylog-labs/graylog-plugin-slack/releases/download/3.1.0/graylog-plugin-slack-3.1.0.jar  # 플러그인 적용을 위해 Graylog 재시작 $ sudo systemctl restart graylog-server.service 
  • Alert은 크게 ConditionNotification으로 구분할 수 있다.
  • Slack Alert 설정 예는 아래와 같다. 입맛에 맞게 출력 메시지를 설정할 수 있다.
${if backlog} ${foreach backlog message} http://graylog.jsonobject.com/search?rangetype=relative&relative=86400&q=${message.fields.request_id}  *message*: ${message.fields.message} *application*: ${message.fields.application} *node_name*: ${message.fields.node_name} *node_instance*: ${message.fields.node_instance} *node_ip_address*: ${message.fields.ip_address}  ${if message.fields.request_id} *request_method*     ${message.fields.request_method}  *request_path*     ${message.fields.request_path}  *request_ip_address*     ${message.fields.request_ip_address}  *email*     ${message.fields.email}  *request_id*     ${message.fields.request_id} ${end} ${if message.fields.schedule_id} *class_name*     ${message.fields.class_name}  *method_name*     ${message.fields.method_name}  *schedule_id*     ${message.fields.schedule_id} ${end}  *error_exception*     ${message.fields.error_exception}  *error_message*     ${message.fields.error_message}  *error_root_exception*     ${message.fields.error_root_exception}  *error_root_message*     ${message.fields.error_root_message}  *error_trace*     ${message.fields.error_trace} ${end} ${end} 
댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
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
글 보관함