ELK Stack으로 Nginx 로그 수집하기
kindof
·2024. 3. 15. 23:17
이번 글에서는 ELK Stack을 활용해서 애플리케이션의 Nginx 로그를 수집해보는 프로젝트를 실습해보려고 합니다.
ELK Stack
ElasticSearch(es)는 Elastic Stack의 핵심인 분산 검색, 분석 엔진입니다. 고성능에 스키마 없는 JSON 기반 document로 다양한 데이터에 대한 검색과 분석에 용이합니다.
Logstash는 데이터를 수집, 집계하고 원하는 곳에 전송할 수 있도록 하는 도구입니다. 특히 es에 데이터를 로드할 때 편리하고 우수한 성능의 인덱싱을 가능하게 하여 es를 사용한다면 Logstash를 동시에 사용하는 것이 보편적입니다.
Kibana는 데이터 시각화 및 탐색 도구입니다. 이 역시 es에 저장된 데이터를 시각화할 때 기본적으로 많이 사용되고 있습니다.
결국 ELK Stack은 [1] Logstash를 통해 데이터를 수집 및 변환해서 es에 전송하고, [2] es는 데이터를 인덱싱하고 검색하는 기능을 제공, [3] Kibana는 데이터의 분석 결과를 시각화하는 일련의 프로세스를 스택화한 것이라고 볼 수 있습니다.
개발 환경 구축하기 - Springboot + Nginx
Spring Initializer를 통해 간단한 Springboot 프로젝트를 생성합니다. Springboot 3.2.3, Java17 환경에서 Lombok, Spring Web 의존성 정도만 추가했습니다. 환경은 크게 중요하지 않습니다.
아래와 같이 TestController를 작성하고 동작을 확인합니다.
@RestController
public class TestController {
@GetMapping("/v1/elk/test")
public String test() {
return "OK!";
}
}
$ curl http://localhost:8080/v1/elk/test
OK!
Nginx는 여러 용도로 사용될 수 있지만 Reverse Proxy 서버 역할을 하는 데 많이 사용됩니다.
간단한 예시로 /api/xxx와 같은 요청에서 /api를 제거하고 서버에 요청을 보내는 기능을 nginx.conf에 작성하고 실행해보겠습니다.
# nginx.conf
// .. 생략
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
access_log /Users/josh/Desktop/projects/elk/logs/nginx/access.log;
error_log /Users/josh/Desktop/projects/elk/logs/nginx/error.log;
server {
listen 80;
server_name localhost;
location /api/v1/ {
rewrite ^/api/(.*) /$1 break;
proxy_pass http://localhost:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
}
}
}
$ nginx -t
$ brew services start nginx
$ brew services list
nginx started josh ~/Library/LaunchAgents/homebrew.mxcl.nginx.plist
이제 크롬을 켜고 Nginx가 떠 있는 localhost:80/api/v1/elk/test에 요청을 보내면 Springboot 애플리케이션의 /v1/elk/test API 요청으로 프록시되고, 아래와 같이 nginx 로그가 남습니다.
$ cat /Users/josh/Desktop/projects/elk/logs/nginx/access.log
127.0.0.1 - - [15/Mar/2024:21:53:17 +0900] "GET /api/v1/elk/test HTTP/1.1" 200 3 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
이러한 Nginx 로그는 서버의 상태를 모니터링하는 데 중요합니다. 또한 클라이언트의 요청에 대한 세부 정보를 포함하기 때문에 보안 측면에서도 수집할 가치가 매우 큽니다.
위에서 설명한 ELK Stack을 이용해서 Nginx 로그를 수집해보겠습니다.
개발 환경 구축하기 - ElasticSearch, Logstash, Kibana 설치
개발 환경은 Mac M1 Monterey 12.5 입니다.
구글에 download elasticsearch, download logstash, download kibana를 검색하고 macOS aarch64 플랫폼의 압축 파일을 다운받으시면 됩니다.
프로젝트 실습 폴더에 해당 파일을 옮기고, 압축을 해제합니다.
~/Desktop/projects/elk > ls -l
total 0
drwxr-xr-x@ 12 josh staff 384 2 19 19:08 elasticsearch-8.12.2
drwxr-xr-x@ 17 josh staff 544 2 20 00:57 kibana-8.12.2
drwxr-xr-x@ 19 josh staff 608 2 20 00:04 logstash-8.12.2
ElasticSearch 실행
elasticsearch를 실행합니다.
$ ~/Desktop/projects/elk/elasticsearch-8.12.2 > ./bin/elasticsearch
만약 아래와 같이 "received plaintext http traffic on an https channel, closing connection Netty4HttpChannel" 로그가 출력되면서 localhost:9200에 연결되지 않는다면 config > elasticsearch.yml 파일의 security 옵션을 false로 수정해줍니다.
정상적으로 실행되면 아래와 같이 Password 등에 대한 정보가 로그로 출력됩니다.
Kibana 실행
kibana를 실행해줍니다.
~/Desktop/projects/elk/kibana-8.12.2 > ./bin/kibana
// .. 생략
[2024-03-10T21:11:39.911+09:00][INFO ][root] Holding setup until preboot stage is completed.
i Kibana has not been configured.
Go to http://localhost:5601/?code=444859 to get started.
로그에 'Kibana has not been configured.' 라는 메시지가 보이며 'http://localhost:5601/?code=444859'에 접속하라는 말이 보입니다.
es를 실행했을 때 로그에 출력됐던 토큰을 입력해줍니다.
Logstash 실행
logstash를 실행합니다.
$ ~/Desktop/projects/elk/logstash-8.12.2 > ./bin/logstash
//.. 생략
ERROR: Pipelines YAML file is empty. Location: /Users/josh/Desktop/projects/elk/logstash-8.12.2/config/pipelines.yml
usage:
bin/logstash -f CONFIG_PATH [-t] [-r] [] [-w COUNT] [-l LOG]
bin/logstash --modules MODULE_NAME [-M "MODULE_NAME.var.PLUGIN_TYPE.PLUGIN_NAME.VARIABLE_NAME=VALUE"] [-t] [-w COUNT] [-l LOG]
bin/logstash -e CONFIG_STR [-t] [--log.level fatal|error|warn|info|debug|trace] [-w COUNT] [-l LOG]
bin/logstash -i SHELL [--log.level fatal|error|warn|info|debug|trace]
bin/logstash -V [--log.level fatal|error|warn|info|debug|trace]
bin/logstash --help
[2024-03-11T20:36:59,999][FATAL][org.logstash.Logstash ] Logstash stopped processing because of an error: (SystemExit) exit
하지만 "ERROR: Pipelines YAML file is empty. Location: .../logstash-8.12.2/config/pipelines.yml" 라는 에러 로그를 찍으며 실행에 실패합니다.
logstash에서 사용할 파이프라인이 정의되지 않았다는 에러입니다. 아래와 같이 logstash.conf 파일을 작성해주고 실행합니다.
# ~/Desktop/projects/elk/logstash-8.12.2/config/logstash.conf
input {
file {
path => "/Users/josh/Desktop/projects/elk/logs/nginx/access.log"
start_position => "beginning"
sincedb_path => "/dev/null"
}
}
filter {
grok {
match => { "message" => "%{IPORHOST:client_ip} - %{DATA:user_name} \[%{HTTPDATE:timestamp}\] \"%{WORD:http_method} %{URIPATHPARAM:request} HTTP/%{NUMBER:http_version}\" %{NUMBER:response_code} %{NUMBER:body_bytes_sent} \"%{DATA:referrer}\" \"%{DATA:user_agent}\"" }
}
date {
match => [ "timestamp", "dd/MMM/yyyy:HH:mm:ss Z" ]
remove_field => [ "timestamp" ]
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "nginx_logs"
}
stdout { codec => rubydebug }
}
Input에는 Nginx 액세스 로그 파일의 경로를 지정하고, 파일의 처음부터 로그를 읽습니다. grok을 이용해서 Nginx 기본 로그 형식을 파싱합니다. Output에는 파싱된 로그를 ES에 인덱싱하는 내용을 지정합니다. 현재 로컬에서 ES를 띄운 상태이기 때문에 localhost:9200입니다. stdout은 파싱된 로그를 터미널에 출력합니다.
아래와 같이 logstash를 실행하고 localhost에 요청을 보내면 로그가 출력됩니다.
$ ~/Desktop/projects/elk/logstash-8.12.2 > ./bin/logstash -f ./config/logstash.conf
// ..
"http_method" => "GET",
"client_ip" => "127.0.0.1",
"event" => {
"original" => "127.0.0.1 - - [15/Mar/2024:22:04:22 +0900] \"GET /api/v1/elk/test HTTP/1.1\" 200 3 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36\""
},
"request" => "/api/v1/elk/test",
"http_version" => "1.1",
"message" => "127.0.0.1 - - [15/Mar/2024:22:04:22 +0900] \"GET /api/v1/elk/test HTTP/1.1\" 200 3 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36\"",
"response_code" => "200",
"referrer" => "-",
"@version" => "1",
"log" => {
"file" => {
"path" => "/Users/josh/Desktop/projects/elk/logs/nginx/access.log"
}
},
"@timestamp" => 2024-03-15T13:04:22.000Z,
"body_bytes_sent" => "3",
"user_name" => "-",
"host" => {
"name" => "AL02284225.local"
},
"user_agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
Logstash -> ElasticSearch -> Kibana 플로우 확인
Kibana로 돌아와서 Stack Management > Index Management를 보면 로그가 정상적으로 수집되는 것을 확인할 수 있습니다.
위 화면에서 Discover index를 통해 실제 데이터를 확인할 수 있습니다.
Reference
https://medium.com/jimmyberg-kim/spring-boot-nginx-c8e87f7f8376
https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/
'Data Engineering' 카테고리의 다른 글
Kafka Leader Election에 대한 정리 (1) | 2023.11.23 |
---|---|
Kafka Consumer의 close(), wakeup() 메서드 들여다보기 (0) | 2023.07.13 |
Kafka Producer Message Batching과 몇 가지 성능 튜닝 옵션들 (0) | 2023.07.01 |
Kafka Consumer, ConsumerGroup 그리고 Rebalancing (0) | 2023.06.11 |
자주 사용하는 Kafka CLI 정리하기 (0) | 2023.02.24 |