Nginx의 로그를 Fluentd를 통해 취합하고, 이를 ElasticSearch로 보낸 다음 Kibana에서 Nginx의 Access Logs를 Dashboard로 시각화하여 확인할 수 있다.
실습 환경
- OS : CentOS7
- Container Runtime : Docker
- Tool : docker compose
[도구 소개]
- Nginx
웹 서비스이며 리버스 프록시로써 많이 사용된다.
- FluentD
Log(데이터) 수집기(Collector)라고 생각하면 된다.
Fluentd로 전달된 데이터는 ‘tag, time, record(JSON)으로 구성된 이벤트로 처리되며, 원하는 형태로 가공되어 다양한 목적지 (S3, ElasticSearch, ..)로 전달될 수 있다.
만약 더 적은 메모리를 사용해야 하는 환경이라면 Fluentd forwarder의 경량화 버전인 Fluentd-Bit와 함께 사용할 수 있다.
- Elasticsearch
텍스트, 숫자, 위치 기반 정보, 정형 및 비정형 데이터 등 모든 유형의 데이터를 위한 무료 검색 및 분석 엔진이다.
- Kibana
Elastic Stack을 기반으로 구축된 무료 오픈 소스 프론트엔드 애플리케이션으로, Elasticsearch에서 색인된 데이터를 검색하고 시각화하는 기능을 제공
[아키텍처]
모든 서비스가 Docker Container로 띄워져 있으며, docker compose로 구성되어 있다.
- Nginx Container에는 logging driver로 fluentd가 설치되어 있다.
- Fluentd는 24224 TCP Port로 리스닝하고 있고, 이를 Elasticsearch로 보내주고 있다.
- Elasitcsearch에 수집된 Log들은 Kibana를 통해 웹 대시보드에서 확인할 수 있다.
[과정 정리]
Fluentd
fluentd는 Dockerfile로 Build를 수행하게 되고, fluent.conf 파일을 통해 설정 값을 정의하게 된다.
[Dockerfile]
# fluentd/Dockerfile
FROM fluent/fluentd:v1.12.0-debian-1.0
USER root
RUN ["gem", "install", "fluent-plugin-elasticsearch", "--no-document", "--version", "5.0.3"]
USER fluent
[fluent.conf]
Output 플러그인 - Elasitcsearch
Ref : https://docs.fluentd.org/output/elasticsearch
1. 만약 ES에서 인증 정보가 필요하면 user, password를 기입할 수 있을 것이다.
user fluent password mysecret
2. ES가 여러 Hosts로 구성되어 있을 경우(Cluster) hosts를 통해 설정할 수 있다.
<< 주의 >>
hosts 부분에 es01, es02, ... 이런 식으로 한 칸씩 띄워 쓰면 안된다.
<source>
# forward type
@type forward
port 24224
bind 0.0.0.0
</source>
<match *.**>
@type copy
<store>
@type elasticsearch
hosts es01,es02,es03
port 9200
logstash_format true
logstash_prefix fluentd
logstash_dateformat %Y%m%d
include_tag_key true
type_name access_log
tag_key @log_name
</store>
<store>
@type stdout
</store>
</match>
Elasticsaerch Cluster, Kibana
Elasticsearch는 Cluster mode로 구성하며, Kibana는 단일 컨테이너로 구성한다.
Fluentd는 다양한 구성을 가지고 있지만 심플하게 단일 컨테이너로 구성하였다.
Elasticsearch 설정값 부분은 다음과 같다.
1) Heap Memory 설정
2) cluster.name 설정
클러스터 명 설정
3) discovery.seed_hosts 설정
원격에 있는 노드들을 찾아 바인딩 하는 과정
seed_hosts 옵션은 7.0부터 추가된 기능이다. 6.x 이전 버전에서는 zen.ping.unicast.hosts 옵션을 사용해왔다.
4) initial_master_nodes 설정
클러스터가 최초 실행 될 때 명시된 노드들을 대상으로 마스터 노드를 선출한다.
5) bootstrap.memory_lock 설정
ES가 사용중인 힙메모리 영역을 다른 Java 프로그램들이 간섭 못하도록 미리 점유해놓는 설정이며, true가 권장사항이다.
6) ulimits
ES User를 위한 Openfiles 최대값 설정
memlock 설정
#### docker-compose.yml
version: "3"
services:
##########################################################
################## Elasticsearch #########################
##########################################################
es01:
image: elastic/elasticsearch:7.16.1
container_name: es01
restart: always
environment:
# Node Name
- node.name=es01
# ES ClusterName
- cluster.name=es-docker-cluster
- discovery.seed_hosts=es02,es03
- cluster.initial_master_nodes=es01,es02,es03
# Prevent ES Memory swap
- bootstrap.memory_lock=true
# Heap Memory
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 262144 # maximum number of open files for the Elasticsearch user, set to at least 65536 on modern systems
hard: 262144
volumes:
- data01:/usr/share/elasticsearch/data
ports:
- "9200:9200"
- "9300:9300"
networks:
- elastic
es02:
image: elastic/elasticsearch:7.16.1
container_name: es02
restart: always
environment:
- node.name=es02
- cluster.name=es-docker-cluster
- discovery.seed_hosts=es01,es03
- cluster.initial_master_nodes=es01,es02,es03
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 262144 # maximum number of open files for the Elasticsearch user, set to at least 65536 on modern systems
hard: 262144
volumes:
- data02:/usr/share/elasticsearch/data
networks:
- elastic
es03:
image: elastic/elasticsearch:7.16.1
container_name: es03
restart: always
environment:
- node.name=es03
- cluster.name=es-docker-cluster
:se nonu 1,1 Top
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 262144 # maximum number of open files for the Elasticsearch user, set to at least 65536 on modern systems
hard: 262144
volumes:
- data03:/usr/share/elasticsearch/data
networks:
- elastic
##########################################################
####################### kibana ###########################
##########################################################
kibana:
image: docker.elastic.co/kibana/kibana:7.7.1
container_name: kib01
restart: always
ports:
- 5601:5601
environment:
SERVER_NAME: kibana
ELASTICSEARCH_URL: <http://es01:9200>
ELASTICSEARCH_HOSTS: <http://es01:9200>
depends_on:
- es01
- es02
- es03
networks:
- elastic
##########################################################
######################## fluentd #########################
##########################################################
fluentd:
build: ./fluentd
volumes:
- ./fluentd/conf:/fluentd/etc
links:
- "es01"
- "es02"
- "es03"
restart: always
container_name: fluentd
ports:
- 24224:24224
- 24224:24224/udp
networks:
- elastic
##########################################################
#################### Volume, Network #####################
##########################################################
volumes:
data01:
driver: local
data02:
driver: local
data03:
driver: local
networks:
elastic:
driver: bridge
Nginx
nginx.conf
ES가 인식할 수 있도록 Log Format을 JSON 형식으로 변경해주었다.
# proxy/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
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"}'
'"ClientIp":"$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 55;
include /etc/nginx/conf.d/*.conf;
}
docker-compose.yml
Localhost의 9080 Port로 들어올 경우 Container의 80 Port로 바인딩을 시켜주며,
nginx.conf 파일을 컨테이너의 /etc/nginx/nginx.conf 파일에 적용될 수 있도록 설정,
logging 섹션을 통해 fluentd 드라이버를 사용하겠다고 명시해주었으며, fluentd-address, tag를 설정해주었다.
Ref : https://docs.docker.com/config/containers/logging/fluentd/
version: "3"
services:
nginx:
image: nginx
container_name: nginx
restart: always
ports:
- "9080:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
logging:
driver: "fluentd"
options:
fluentd-address: localhost:24224
tag: fluent
참고로, Docker container에서 사용 가능한 로깅 드라이버는 다음과 같다.
Ref : https://docs.docker.com/config/containers/logging/configure/#supported-logging-drivers
Docker Compose로 컨테이너 한 번에 띄우기
1. EFK 띄우기
[user@example efk_stack]$ pwd
/home/user/docker/efk_stack
[user@example efk_stack]$ ls
docker-compose.yml docker-compose.yml_single elasticsearch fluentd kibana
[user@example efk_stack]$ docker-compose up -d
Creating network "efk_stack_elastic" with driver "bridge"
Creating es01 ... done
Creating es03 ... done
Creating es02 ... done
Creating kib01 ... done
Creating fluentd ... done
[user@example efk_stack]$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
88eb636e6d79 efk_stack_fluentd "tini -- /bin/entryp…" 18 seconds ago Up 16 seconds 5140/tcp, 0.0.0.0:24224->24224/tcp, 0.0.0.0:24224->24224/udp, :::24224->24224/tcp, :::24224->24224/udp fluentd
a34ae5e42dfd docker.elastic.co/kibana/kibana:7.7.1 "/usr/local/bin/dumb…" 19 seconds ago Up 17 seconds 0.0.0.0:5601->5601/tcp, :::5601->5601/tcp kib01
265cf54621b3 elastic/elasticsearch:7.16.1 "/bin/tini -- /usr/l…" 20 seconds ago Up 18 seconds 9200/tcp, 9300/tcp es02
22ec0ba034e3 elastic/elasticsearch:7.16.1 "/bin/tini -- /usr/l…" 20 seconds ago Up 18 seconds 9200/tcp, 9300/tcp es03
58d4a281e6c2 elastic/elasticsearch:7.16.1 "/bin/tini -- /usr/l…" 20 seconds ago Up 18 seconds 0.0.0.0:9200->9200/tcp, :::9200->9200/tcp, 0.0.0.0:9300->9300/tcp, :::9300->9300/tcp es01
[user@example nginx]$ pwd
/home/user/docker/nginx/nginx
[user@example nginx]$ docker-compose up -d
Creating network "nginx_default" with the default driver
Creating nginx ... done
[user@example nginx]$ docker ps | grep nginx
b07cf332f0dc nginx "/docker-entrypoint.…" 6 seconds ago Up 4 seconds 0.0.0.0:9080->80/tcp, :::9080->80/tcp nginx
[user@example nginx]$ curl localhost:9080
0 - Nginx 확인
log를 확인해보면 원했던 대로 JSON 형태로 출력이 된다.
[user@example nginx]$ docker logs -f nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
{"@time":"2021-12-20T07:18:13+00:00","IP":"172.31.0.1","Status":200,"Method":"GET","RequestTime":0.000,"FileName":"/usr/share/nginx/html/index.html","QueryString":"-","SentSize":853,"UA":"curl/7.29.0","Referer":"-"}"ClientIp":"-"
{"@time":"2021-12-20T07:19:34+00:00","IP":"192.168.10.1","Status":304,"Method":"GET","RequestTime":0.000,"FileName":"/usr/share/nginx/html/index.html","QueryString":"-","SentSize":180,"UA":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36","Referer":"-"}"ClientIp":"-"
{"@time":"2021-12-20T07:19:35+00:00","IP":"192.168.10.1","Status":304,"Method":"GET","RequestTime":0.000,"FileName":"/usr/share/nginx/html/index.html","QueryString":"-","SentSize":180,"UA":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36","Referer":"-"}"ClientIp":"-"
{"@time":"2021-12-20T07:19:35+00:00","IP":"192.168.10.1","Status":304,"Method":"GET","RequestTime":0.000,"FileName":"/usr/share/nginx/html/index.html","QueryString":"-","SentSize":180,"UA":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36","Referer":"-"}"ClientIp":"-"
{"@time":"2021-12-20T07:19:35+00:00","IP":"192.168.10.1","Status":304,"Method":"GET","RequestTime":0.000,"FileName":"/usr/share/nginx/html/index.html","QueryString":"-","SentSize":180,"UA":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36","Referer":"-"}"ClientIp":"-"
1 - Fluentd 확인
아래의 명령어를 통해 fluentd가 ES로 정상적으로 Data를 보내고 있는지 확인이 필요하다.
# docker logs -f fluentd
초기에는 아래의 host_unreachable_exceptions 에러가 발생할 것이지만, ES 클러스터가 구성이 모두 완료되면 더 이상 에러는 뜨지 않음
추가로, fluentd는 Log를 통해 사용자에게 다양한 조언?들을 해주고 있다.
Traffic jam을 방지하기 위해서는, flush_thread_count를 설정하길 권고하고 있다. 하지만, Test 용도이기 때문에 별도로 설정해주진 않았음
2 - Kibana 접속
Localhost:5601 로 접속할 경우 Kibana가 정상적으로 접속이 될 것이다.
(필자의 경우에는 VMWare Workstation 내에 CentOS7 VM을 사용하고 있기 때문에 IP는 다름)
[Index Pattern]
‘Management → Index Pattern’으로 접속하면 인덱스 패턴을 지정할 수 있다.
[Discover]
‘Discover’으로 접속하면 지정한 인덱스 패턴에 의해 수집되는 Log들을 확인할 수 있다.
[Stack Monitoring]
Stack Monitoring은 Elasticsearch와 Kibana의 모니터링을 확인할 수 있다.
ES의 Disk 사용 공간, JVM Heap, Uptime, Version, Indicies 등을 확인할 수 있으며
Kibana의 Instances, Connections 등을 확인할 수 있다.
Beat를 사용하여 Stack monitoring 또한 설정이 가능하다.
3 - Local에서 Docker 컨테이너 확인
docker-proxy란 이름으로 각각의 Process를 물고 Port를 리스닝하고 있다.
이렇게 아주 간단하게 Elasticsearch, Fluentd, Kibana를 Docker Compose를 통해 띄워봤으며, Nginx의 Access Log를 ES로 보내 Kibana 웹 대시보드에서 확인해보았다.
물론, AWS 상에서는 관리상 이점을 가져가기 위해 AWS Opensearch 등의 서비스를 많이 사용하곤 한다.
하지만 이렇게 직접 설치해서 사용해보는 경험이 있으면 추후 많은 도움이 될 것이라 생각하여 실습을 진행해보았다.
- Ref
https://grip.news/archives/1344
https://docs.fluentd.org/container-deployment/docker-compose
'Log,Monitorings' 카테고리의 다른 글
[EKS] 아주 가벼운 Loki + Grafana + Promtail 로그 시스템 구성 (2) | 2022.10.31 |
---|---|
[Datadog] EKS에서 운영중인 SpringBoot HikarpCP 모니터링 (Auto Discovery) (0) | 2022.09.09 |
[Datadog] AWS Events 기반으로 Datadog 알람 설정하기 (0) | 2022.06.26 |
[Fluentd] 로그 수집 패턴, Fluentd 개념 정리 (0) | 2021.12.18 |
[ElasticSearch] ElasticSearch 개념 알아보기 (0) | 2021.11.11 |