본문 바로가기

Log,Monitorings

[Elasticsearch] Nginx 로그 Fluentd를 통해 Elasticsearch로 보내기

반응형

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로 구성되어 있다.

  1. Nginx Container에는 logging driver로 fluentd가 설치되어 있다.
  2. Fluentd는 24224 TCP Port로 리스닝하고 있고, 이를 Elasticsearch로 보내주고 있다.
  3. 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

https://soyoung-new-challenge.tistory.com/110

반응형