Fluentd 의 활용. ElasticSearch, Kibana 을 사용한 Nginx Log 수집
앞선 글에서 LogStash 와 Fluentd 의 차이를 간단히 알아봤다. 이번엔 Fluentd 를 활용해 로그를 취합하고, 통계를 볼 수 있는 구조를 만들어보자. ElasticSearch, Fluentd, Kibana 조합으로 Nginx 의 Access Log 를 시각화 하는것이 목적이다.
로그 분석에 ElasticSearch를 사용하는 이유에 대한 질문이 많았다. 물론, 로그 데이터를 MySQL 이나 MongoDB에 입력하는것도 방법이나, 정형화된 형식을 넣어야 하고, 값이 바뀔 때 마다 SQL 구문을 수정해야 한다. 더 중요한 것은 이들은 시각화 도구를 제공하지 않고 통신을 위한 API 설계가 필요하다. 즉, 손이 많이 간다는 이야기다.
ElasticSearch의 분석 능력이 뛰어나고, 실시간 검색 능력도한 훌륭하다. 떼이터를 REST 형태로 쉽게 처리할 수 있고 요청할 수 있다. 함께 사용할 수 있는 Kibana 는 훌륭한 시각화 도구 이다.
Access Log 가 이런 형태로 저장된다면.
1.211.40.28 - - [11/Jun/2019:08:56:30 +0900] "GET /api/v4/users/me/teams/9kztki3nz7foi81je6cy9t1nqr/channels HTTP/1.1" 200 926 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) MTWORKS/4.1.4 Chrome/66.0.3359.181 Electron/3.0.13 Safari/537.36" 1.211.40.28 - - [11/Jun/2019:08:56:30 +0900] "GET /api/v4/channels/wqjqexrdibgapmr69t5381wsdc/posts?page=0&per_page=60 HTTP/1.1" 200 430 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) MTWORKS/4.1.4 Chrome/66.0.3359.181 Electron/3.0.13 Safari/537.36" 1.211.40.28 - - [11/Jun/2019:08:56:35 +0900] "GET /api/v4/users/me/teams/unread HTTP/1.1" 200 128 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) MTWORKS/4.1.4 Chrome/66.0.3359.181 Electron/3.0.13 Safari/537.36" 10.10.101.150 - - [11/Jun/2019:08:57:25 +0900] "GET / HTTP/1.1" 200 1262 "-" "ELB-HealthChecker/2.0"
ElasticSearch 와 Kibana 는 팬시한 화면과 생산성을 높혀줄 것이다.
아키텍쳐
인터넷에서 유입된 트래픽은 복수의 Reverse Proxy. 즉 Nginx를 통해서 Application Server 로 전달된다. 분산된 Nginx 의 Access Log 를 한곳에 모아 ElasticSearch 로 전달하고, Kibana 가 이쁘게 그려줄 것이다.
이 포스트에서는 N 개의 Nginx 가 아닌 1개의 Nginx 를 대상으로, S3 로의 백업은 생략했다. 즉, 점선 안 내용을 설명하고자한다. 점선 안 내용을 다시 드로잉하면 다음과 같이 표현할 수 있다.
포스트는 ‘수집(집계)’를 처리하는 왼쪽(STEP #1)과, ‘분석, 처리’하는 오른쪽(STEP #2)으로 나눠 설명하고 있다.
STEP #1
Nginx Access Log
분석 대상은 Nginx 의 Access Log이며 동적으로 변하는 Log 를 대상으로 한다. Ubuntu 는 패키지 관리자로 Nginx 를 설치하면 필요한 부가 설정들이 기본적으로 세팅 되어 있고, 수정하지 않는다면 다음과 같은 규칙을 갖고 있다.
- 로그 저장소는 /var/log/nginx 이다.
- 현재 저장되고 있는 Log 는 access.log 다.
- 하루 지난 Log 는 access.log.1 이다.
ubuntu@ip-10-10-101-43:/var/log/nginx# ls -al total 3228 drwxr-xr-x 2 root adm 4096 Jun 11 06:25 . drwxrwxr-x 13 root syslog 4096 Jun 11 06:25 .. -rw-r----- 1 mtapp mtapp 1214668 Jun 11 20:57 access.log -rw-r----- 1 mtapp mtapp 1284593 Jun 11 06:24 access.log.1 -rw-r----- 1 mtapp mtapp 19863 Jun 10 06:24 access.log.2.gz -rw-r----- 1 mtapp mtapp 0 May 23 06:25 error.log -rw-r----- 1 mtapp mtapp 249173 May 22 18:29 error.log.1 -rw-r----- 1 mtapp mtapp 13798 May 21 20:21 error.log.2.gz
중요한 것은 Access Log 의 Format 이다. 반드시 ‘분석 가능한’ 형태로 저장되어야 한다. Nginx 의 HTTP Log 에 대한 내용은 다음 링크를 참고하자.
- http://nginx.org/en/docs/http/ngx_http_log_module.html
- https://stackoverflow.com/questions/37437153/dictionary-variable-in-log-format-nginx
기본 Log Format. 분석 해야 하는 값이 함께 들어가 있다. $request 부분을 보면 Method 와 URI 그리고 Query String 이 포함되기 때문에 분석하기 쉽지 않다.
1.211.40.28 - - [11/Jun/2019:08:56:30 +0900] "GET /api/v4/users/me/teams/9kztki3nz7foi81je6cy9t1nqr/channels HTTP/1.1" 200 926 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) MTWORKS/4.1.4 Chrome/66.0.3359.181 Electron/3.0.13 Safari/537.36"
변경된 Log Format. 본인이 수집하려는 기준으로 맞추면 되며, 나는 JSON 형태로 만들었다.
log_format main '{"@time":"$time_iso8601",' '"IP":"$remote_addr",' '"Status":$status,' '"Method":"$request_method",' '"RequestTime":$request_time,' '"FileName":"$request_filename",' '"QueryString":"$query_string",' '"SentSize":$bytes_sent,' '"UA":"$http_user_agent",' '"Referer":"$http_referer"}'; # 결과 {"@time":"2019-06-13T20:20:17+09:00","IP":"1.5.6.252","Status":200,"Method":"POST","RequestTime":0.000,"FileName":"/usr/share/nginx/html/api/v4/users/status/ids","QueryString":"-","SentSize":827,"UA":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36","Referer":"-"} # { "@time": "2019-06-13T20:20:17+09:00", "IP": "1.5.6.252", "Status": 200, "Method": "POST", "RequestTime": 0.000, "FileName": "/usr/share/nginx/html/api/v4/users/status/ids", "QueryString": "-", "SentSize": 827, "UA": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36", "Referer": "-" }
Fluentd 설치
Ubuntu 의 기본 패키지 저장소에는 td-agent 가 존재하지 않는다. fluentd.org 제공하는 OS 별 스크립트를 참고하자.
- https://docs.fluentd.org/installation/install-by-deb
ubuntu@ip-10-10-101-43:~$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 18.04.2 LTS Release: 18.04 Codename: bionic ubuntu@ip-10-10-101-43:~$ sudo curl -L https://toolbelt.treasuredata.com/sh/install-ubuntu-bionic-td-agent3.sh | sh . . ubuntu@ip-10-10-101-43:~$ which td-agent /usr/sbin/td-agent ubuntu@ip-10-10-101-43:~$ service td-agent Usage: /etc/init.d/td-agent {start|stop|reload|restart|force-reload|status|configtest}
디스크립터 수를 증가시키자. Ubuntu 는 기본 8096 이다. 늘리자. 권한이 불충분 할 경우 Fluentd 를 실행할 때 LimitNOFILE=65536 옵션으로 사용해도 무관하다.
ubuntu@ip-10-10-101-43:/home/ubuntu# ulimit -n 8096 ubuntu@ip-10-10-101-43:/home/ubuntu# sudo vi /etc/security/limits.conf . . root soft nofile 65536 root hard nofile 65536 * soft nofile 65536 * hard nofile 65536
Fluentd 플러그인 설치
Fluentd 에서 사용할 플러그인을 설치한다. 플러그인은 td-agent 와 함께 설치된 td-agent-gem 을 사용한다.
- td-agent-gem 을 사용하려면 2.5.0 이상의 Ruby 가 먼저 설치 되어 있어야 한다.
- agent 에 종속된 내용들을 갱신(update)하자.
- fluent-plugin-config-expander 는 설정파일 확장 적용을 위한 플러그인이다.
ubuntu@ip-10-10-101-43:/home/ubuntu# sudo td-agent-gem update Updating installed gems Updating aws-partitions Fetching: aws-partitions-1.174.0.gem (100%) Successfully installed aws-partitions-1.174.0 Parsing documentation for aws-partitions-1.174.0 Installing ri documentation for aws-partitions-1.174.0 Installing darkfish documentation for aws-partitions-1.174.0 Done installing documentation for aws-partitions after 0 seconds Parsing documentation for aws-partitions-1.174.0 Done installing documentation for aws-partitions after 0 seconds . . ubuntu@ip-10-10-101-43:/home/ubuntu# sudo td-agent-gem install fluent-plugin-config-expander
Fluentd 수집 환경 설정
Fluentd를 이용해 Access Log (/var/log/nginx/access.log)를 수집하자. td-agent 의 환경 설정을 수정 하면 된다.
- pos_file 은 처리 기준의 위치를 기록하기 위해 필요하다.
- 수집된 정보는 TCP 24224 포트를 통해 10.10.102.203 로 전달된다.
- format 은 JSON 이다.
ubuntu@ip-10-10-101-43:/home/ubuntu# sudo vi /etc/td-agent/td-agent.conf # 소스 (nginx 의 access log) <source> @type config_expander <config> @type tail path /var/log/nginx/access.log pos_file /var/log/nginx/access.log.pos format json tag ${hostname}/nginx.access </config> </source> # 집계된 데이터로 전송 <match *.**> type forward retry_limit 5 flush_interval 5s <server> host 10.10.102.203 # 중앙 서버 IP port 24224 </server> </match>
pos 파일을 만들고 Fluentd 를 시작하자.
ubuntu@ip-10-10-101-43:/home/ubuntu# touch /var/log/nginx/access.log.pos ubuntu@ip-10-10-101-43:/home/ubuntu# sudo service td-agent configtest * td-agent # 색상이 없으면 OK ubuntu@ip-10-10-101-43:/home/ubuntu# sudo service td-agent start td-agent 14620 1 0 Jun11 ? 00:00:11 /opt/td-agent/embedded/bin/ruby /opt/td-agent/embedded/bin/fluentd --log /var/log/td-agent/td-agent.log --daemon /var/run/td-agent/td-agent.pid td-agent 14626 14620 0 Jun11 ? 00:00:21 /opt/td-agent/embedded/bin/ruby -Eascii-8bit:ascii-8bit /opt/td-agent/embedded/bin/fluentd --log /var/log/td-agent/td-agent.log --daemon /var/run/td-agent/td-agent.pid --under-supervisor
STEP #2
AWS ElasticSearch 생성
2015년 10월, AWS Service 에 ElasticSearch 가 도입됐다. Self-Install 하는것과 차이는 명확하다. 감이 오지 않는다면 EC2 Instance 에 MySQL 을 직접 설치하는것과 RDS 의 MySQL 을 사용하는 것의 차이를 생각해 보자.
- RDS 의 MySQL 은 플러그인 설치가 불가능하다.
- 최상위 관리자의 권한이 제한된다.
- 상대적으로 비싸다.
- 버전이 현행버전 대비 한단계 이상 뒤쳐진다.
물론 관리형 서비스가 추가 되었을 때의 장점은 명확하다.
- 전문 지식이 없어도 환경 구축이 가능하다.
- 기본적인 모니터링 및 관리가 편리하다.
개인적으로 AWS ElasticSearch Service는 공식 형태소 분석기 노리(Nori)를 지원하지 않아 불만이 있지만 (5.x 부터 운전한닢을 포함하고 있다. 그나마 다행) 이번 로그 분석에서는 한글 형태소 분석이 불필요하기 때문이 이 요소는 무시하고자 한다. (생각해 보면 RDS Serverless 는 MySQL 5.6 기반이라 N-Gram. Full-Text Search 를 지원하지 않는다. 이게 Cloud의 단점이라면 단점)
AWS ElasticSearch Service 를 생성하자.
ElasticSearch Service 를 생성하는건 간단하다. 본인 인프라에 맞게 환경을 설정하고 IAM 정책을 부여하면 된다.
- 도메인 이름은 ELB와 마찮가지로(ELB보다 더짧다. ELB 32자 제한) 제한 길이가 짧다.
- EC2 등의 다른 서비스와 달리 ElasticSearch는 t2.small 가 Free-tier 대상이다.
- Public Access 는 제한하고. Private Subnet 에 위치함으로서 서비스를 보호하자.
생성하는데 약 10분 내외가 소요된다. Kibana 로 생성 상태를 확인해 보자.
AWS IAM 정책 추가
불특정 다수가 ElasticSearch 에 접근하는 것을 막기 위한 가장 기본적인 조치로 IAM 으로 제어하는 방법이 있다. Cognito 를 사용하는 방법도 있지만, 앞서설치한 플러그인(fluent-plugin-aws-elasticsearch-service)은 IAM 기반 억세스를 지원한다. 추가 방법은 간단하다.
- 정책 편집기로 ElasticSearch 를 위한 정책을 추가한다.
- 다른 서비스와 달리 개발자 및 Fluentd 를 위해서라면 대부분의 권한을 부여하자
- 그리고 삭제 기능만 제한하자.
(자사 정책은 최고 책임자만 서비스 삭제 및 종료가 가능하고, 개발자가 제어하려면 관리 Tag 를 추가해 기록을 남기게 되어 있다.)
생성된 정책을 연결함으로서 완료 접근 준비가 완료됐다.
※ 해당 Group 전체가 아니라 특정 사용자만 접근 가능하게 하려면 ElasticSearch 내 사용자 액세스 수정이 필요하다.
취합용 EC2 Instance 에 Fluentd 설치
Access Log 를 집계한 데이터를 행선지(ElasticSearch)로 전달할 EC2 Instance 다. Fluentd(td-agent) 설치 방법은 동일하며, 플러그인에서 차이가 있다. 플러그인 목록 중 핵심은 fluent-plugin-aws-elasticsearch-service 으로 AWS ElasticSearch 에 맞게 커스터마이징 되어있는데, 사용자 인증을 IAM KEY 로 대신할 수 있게 도와준다. (뿐만 아니라, Region 및 접근 경로를 EndPoint 로 지정할 수 있다)
※ 만약 AWS ElasticSearch Service 를 사용하지 않는다면 fluent-plugin-elasticsearch 를 설치하면 되고, 내용상 큰 차이는 없다.
ubuntu@ip-10-10-102-203:~$ sudo td-agent-gem install fluent-plugin-config-expander ubuntu@ip-10-10-102-203:~$ sudo td-agent-gem install fluent-plugin-filter ubuntu@ip-10-10-102-203:~$ sudo td-agent-gem install fluent-plugin-forest ubuntu@ip-10-10-102-203:~$ sudo td-agent-gem install fluent-plugin-typecast ubuntu@ip-10-10-102-203:~$ sudo td-agent-gem install fluent-plugin-parser ubuntu@ip-10-10-102-203:~$ sudo td-agent-gem install fluent-plugin-aws-elasticsearch-service
Fluentd 의 동작을 위해 td-agent 설정을 변경하자.
ubuntu@ip-10-10-102-203:~$ sudo vi /etc/td-agent/td-agent.conf # 집계 서버로 부터 데이터를 수신한다. Port 는 서로 일치해야 한다. <source> type forward port 24224 </source> # Parse Data의 일부를 Integer로 캐스팅한다. > JSON 형태로 값을 전달하기 때문에 불필요해짐 #<match parsed.*nginx.access**> # type typecast # item_types response_time:integer,size:integer,status:integer # prefix casted #</match> # Parse Data를 ElasticSearch 에 저장하자. <match casted.parsed.*nginx.access**> type_name nginx @type "aws-elasticsearch-service" tag_key @log_name include_tag_key true logstash_format true flush_interval 10s buffer_type file buffer_path /var/log/td-agent/buffer/casted.nginx.access.buffer <endpoint> url {VPC 엔드포인트} region {Region} access_key_id "{Access key ID}" secret_access_key "{Secret access key}" </endpoint> </match>
그리고 두 서버의 Fluentd 를 모두 실행 함으로서 AWS ElasticSearch Service에 Log 전달이 시작된다.
※ Log 의 위치는 /var/log/td-agent 이며, error 가 없다면 무시해도 된다.
# td-agent 시작하기 root@ip-10-10-101-43:/etc/td-agent# service td-agent start # Log 예제 # 수집 2019-06-13 18:27:29 +0900 [info]: #0 starting fluentd worker pid=9135 ppid=9127 worker=0 2019-06-13 18:27:29 +0900 [info]: #0 delayed_commit_timeout is overwritten by ack_response_timeout 2019-06-13 18:27:29 +0900 [info]: #0 following tail of /var/log/nginx/access.log 2019-06-13 18:27:29 +0900 [info]: #0 fluentd worker is now running worker=0 # 취합 2019-06-13 09:26:43 +0000 [info]: #0 starting fluentd worker pid=1545 ppid=1540 worker=0 2019-06-13 09:26:43 +0000 [info]: #0 listening port port=24224 bind="0.0.0.0" 2019-06-13 09:26:43 +0000 [info]: #0 fluentd worker is now running worker=0 2019-06-13 09:27:26 +0000 [warn]: #0 no patterns matched tag="fluent.info"
Kibana 설정
Kibana Web 에 접속하면, 기본적으로 Management Tag 이 선택되어 있다. Index 패턴을 지정해야 하기 때문이다. 우린 앞선 설정에서 logstash_format 를 true 로 했기 때문에 Kibana 는 logstash 의 패턴을 추천할 것이다.
기준 값은 앞서 지정한 @time 이다.
수집되는 데이터를 확인할 수 있다.