EFK를 정리하자 1일차 - overview

0

EFK

목록 보기
1/8

Overview

fluentd는 log collector로 다양한 source(file, stdout, network etc)에서 발생한 log들을 한 곳으로 모아 필터링과 변환을 거친 후에 원하는 위치로 output을 보내준다.

https://www.youtube.com/watch?v=5ofsNyHZwWE

다음은 fluentd의 대략적인 아키텍처 구조를 보여준다.
fluentd-overview

fluentd는 C언어로 만들어졌으며 사용하기 좋게 Ruby wrapper로 감싸져있다. 이 덕분에 50,000+개의 서버로 부터 log들을 받을 수 있는 scalability를 가지고 있으며, 사용자 logs들을 JSON과 같은 원하는 format으로 쉽게 다룰 수 있다.

Local Installation

필자는 container, kubernetes 환경에서 fluentd를 사용할 것이기 때문에 이를 참고만 남겨두기로 한다.

설치 이전에 설정해야하는 부분들이 있다.

  1. NTP 설정
  2. 최대 file description 수 증가
  3. Network kernel parameter 최적화

먼저 정확한 timestamp를 설정하기 위해서 NTP damon을 설정하도록 하자. NTP가 아니여도 chrony가 설치되어있다면 상관없다. 참고로 Aamazon linux는 chrony가 설정되어있어서 수정할 필요가 없다.

https://www.lesstif.com/lpt/linux-chrony-time-synchronizing-82215032.html

다음으로 file description 수를 늘려주도록 하자.

ulimit -n
1024

1024로 file description 수가 너무 적다면 /etc/security/limits.conf를 통해서 수정할 수 있다.

아래의 line을 추가하고 변경한 이후에는 reboot가 필요하다. 단, fluentd를 systemd를 이용하여 실행한다면 LimitNOFILE=65536 옵션이 이를 대신할 수 있어, 굳이 아래의 line을 추가할 필요가 없다. 또한, td-agent package를 사용하면 default로 이미 설정되어있다.

root soft nofile 65536
root hard nofile 65536
* soft nofile 65536
* hard nofile 65536

다음으로 Network kernel parameter의 최적화인데, EC2에서 적용하면 된다. /etc/sysctl.conf에 다음의 line을 넣어주고, sysctl -p를 통해 reboot를 시켜주어 해당 파라미터를 적용시키면 된다. 해당 kernel parameter는 다음의 slide로 그 이유를 찾아볼 수 있다. https://www.slideshare.net/brendangregg/how-netflix-tunes-ec2-instances-for-performance

net.core.somaxconn = 1024
net.core.netdev_max_backlog = 5000
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_wmem = 4096 12582912 16777216
net.ipv4.tcp_rmem = 4096 12582912 16777216
net.ipv4.tcp_max_syn_backlog = 8096
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 10240 65535
# If forward uses port 24224, reserve that port number for use as an ephemeral port.
# If another port, e.g., monitor_agent uses port 24220, add a comma-separated list of port numbers.
# net.ipv4.ip_local_reserved_ports = 24220,24224
net.ipv4.ip_local_reserved_ports = 24224

이제 본격적으로 fluentd를 설치하도록 하자. fluentd 설치 이전에 fluentd에는 몇가지 파생 production이 있는데, 다음과 같다.
1. fluentd: 일반적으로 말하는 fluentd는 opensource source code파일을 말한다. https://github.com/fluent/fluentd 다음에 링크를 통해 Ruby로 직접 설치가 가능하다.
2. fleunt-package(td-agent): 이전에는 td-agent(treasure data agnet - 여기서 teasure는 fluentd를 maintain하는 회사)라고 불리는 package로 ruby를 이용해 fluentd를 설치하기 어려운 사람들을 위해 만든 패키지이다. 일반적인 fluentd와의 차이는 다음과 같다. https://www.fluentd.org/faqs
3. fluent-bit: fluentd의 경량화 버전으로 IoT나 메모리가 한정된 MSA구조에서 사용된다.
4. calyptia-fluentd: Calyptia 회사에서 운영하고 관리하는 fluentd로 일반적인 fluentd보다 더 사용하고 용이하고 설치하기 쉽다는 장점이 있다. https://calyptia.com/

정리하자면 source code 자체로 ruby를 통해 실행하는 방법이 fluentd이고, rpm, debian에서 설치하는 방법이 fluent-package(이전 td-agent)이다.

source: https://docs.fluentd.org/installation/install-from-source
debian: https://docs.fluentd.org/installation/install-by-deb
rpm: https://docs.fluentd.org/installation/install-by-rpm

Docker installation

먼저 docker를 사용해서 fluentd를 설치하는 방법에 대해서 알아보도록 하자. 먼저 docker가 설치되었다는 것을 가정하도록 한다.

docker -v
Docker version 23.0.1, build a5ee5b1

fluentd의 docker image들은 다음을 참고하면 된다. (https://hub.docker.com/r/fluent/fluentd/)

docker image로 debian, alpine이 제공되는데 debian에서 jemalloc을 공식적으로 지원하기 때문에 debian으로 다운받기를 추천한다.

docker pull fluent/fluentd:edge-debian

image를 pull받았다면 다음으로 configuration을 설정하고, fluentd container의 volume으로 넘겨주어 실행시키도록 하자.

touch /tmp/fluentd.conf
vi /tmp/fluentd.conf

<source>
  @type http
  port 9880
  bind 0.0.0.0
</source>

<match **>
  @type stdout
</match>

0.0.0.0(localhost)9880 port에서 http로 stdout으로 찍힌 로그를 가져오겠다는 것이다.

이제 fluentd container를 실행시켜보도록 하자.

docker run -p 9880:9880 -v /tmp:/fluentd/etc fluent/fluentd:edge-debian -c /fluentd/etc/fluentd.conf

2023-10-19 07:23:14 +0000 [info]: init supervisor logger path=nil rotate_age=nil rotate_size=nil
2023-10-19 07:23:14 +0000 [info]: parsing config file is succeeded path="/fluentd/etc/fluentd.conf"
2023-10-19 07:23:14 +0000 [info]: gem 'fluentd' version '1.16.2'
2023-10-19 07:23:14 +0000 [warn]: define <match fluent.**> to capture fluentd logs in top level is deprecated. Use <label @FLUENT_LOG> instead
2023-10-19 07:23:14 +0000 [info]: using configuration file: <ROOT>
  <source>
    @type http
    port 9880
    bind "0.0.0.0"
  </source>
  <match **>
    @type stdout
  </match>
</ROOT>
2023-10-19 07:23:14 +0000 [info]: starting fluentd-1.16.2 pid=7 ruby="3.1.4"
2023-10-19 07:23:14 +0000 [info]: spawn command to main:  cmdline=["/usr/local/bin/ruby", "-Eascii-8bit:ascii-8bit", "/usr/local/bundle/bin/fluentd", "-c", "/fluentd/etc/fluentd.conf", "--plugin", "/fluentd/plugins", "--under-supervisor"]
2023-10-19 07:23:14 +0000 [info]: #0 init worker0 logger path=nil rotate_age=nil rotate_size=nil
2023-10-19 07:23:14 +0000 [info]: adding match pattern="**" type="stdout"
2023-10-19 07:23:14 +0000 [info]: adding source type="http"
2023-10-19 07:23:14 +0000 [warn]: #0 define <match fluent.**> to capture fluentd logs in top level is deprecated. Use <label @FLUENT_LOG> instead
2023-10-19 07:23:14 +0000 [info]: #0 starting fluentd worker pid=16 ppid=7 worker=0
2023-10-19 07:23:14 +0000 [info]: #0 fluentd worker is now running worker=0
2023-10-19 07:23:14.733535827 +0000 fluent.info: {"pid":16,"ppid":7,"worker":0,"message":"starting fluentd worker pid=16 ppid=7 worker=0"}
2023-10-19 07:23:14.734397152 +0000 fluent.info: {"worker":0,"message":"fluentd worker is now running worker=0"}

성공적으로 실행된 것을 볼 수 있다.

다음으로, 다른 shell session에 접속해서 fluentd의 test endpoint로 curl요청을 전달해 log를 찍도록 만들어보자. 로그는 fluentd가 가져오게 된다.

2023-10-19 07:24:28.479116319 +0000 sample.test: {"json":"message"}

성공적으로 배포된 것을 확인할 수 있다.

Docker Logging Driver

docker v1.6부터 logging을 위한 logging drivers(짧게 'log-driver')가 추가되었다. 기본적으로 docker는 json-file logging-dvier를 사용하는데, 이는 container들의 stdout, stderr log를 JSON으로 internal하게 캐싱하는 것이다. 해당 log-driver 덕분에 docker는 /var/lib/docker/containers/[Container-ID]/[Container-ID]-json.log로 log 파일을 생성한다.

단, 한 가지 조심해야할 것은 'local'환경에서 logging driver를 사용하면 disk 포화 상태가 발생할 수 있는데, 이는 loggin driver가 rotation되지 않기 때문이다. 즉, 오래된 정보를 삭제하지 않고 쌓기만 한다. 따라서, 'local'에서 logging driver를 사용한다면 configuration으로 rotation 설정을 하는 것을 추천한다.

먼저 fluentd configuration 파일을 만들도록 하자.

  • /tmp/demo.conf
<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

<match *>
  @type stdout
</match>

localhost(0.0.0.0)의 24224 port로부터 온 log를 collect하겠다는 것이다.

다음으로 fluentd를 실행해보도록 하자.

docker run -it -p 24224:24224 -v /tmp/demo.conf:/fluentd/etc/demo.conf -e FLUENTD_CONF=demo.conf fluent/fluentd:edge-debian

현재 container 내부에서 24224 port로 source를 받으니, local PC에서는 24224 port를 fluentd container와 맵핑시켜준다.

24224로 실행한 이유는 fluentd logging driver의 default port가 24224이기 때문이다. (https://docs.docker.com/config/containers/logging/fluentd/) 다음을 참고하면 fluentd-address를 설정해야하는데 localhost:24224가 default이다.

이제 또 다른 container를 실행하여 logging driver를 fluentd로 설정하고 log를 찍게 만들면, fluentd container에 log가 들어올 것이다.

docker run --log-driver=fluentd ubuntu echo "Hello Fluentd!"

ubuntu container를 실행시켜 Hello Fluentd를 찍도록 만들었다. 다음의 로그가 fluentd conatiner에 찍히는 지 확인하도록 하자.

2023-10-19 10:17:28.000000000 +0000 d31455f309b2: {"log":"Hello Fluentd!","container_id":"d31455f309b28153d8239ce8c22dfdf5f7112d37764bc7ff103f8af0f34d0877","container_name":"/zen_bose","source":"stdout"}

예쁘게 포맷팅되어 log msg가 전달된 것을 확인할 수 있다.

이 밖에 다양한 방법들이 존재하므로 확인해보도록 하자. (https://docs.fluentd.org/container-deployment/docker-logging-driver)

Docker Compose

fluentd docs에 나온대로 하면 안타깝게도 현재 실행이 되지 않는다. (https://github.com/fluent/fluentd-docs-gitbook/issues/391) 그래서 약간의 수정을 하였다.

시작 전에 dockerdocker-compose가 설치되었는지 확인이 필요하다.

  • docker-compose 설치
sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

sudo chmod +x /usr/local/bin/docker-compose
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

docker-compose -v
docker-compose version 1.27.4, build 40524192

다음은 docker compose를 사용해서 EFK 스택을 구축하는 방법에 대해서 알아보도록 하자. EFK stack은 elasticsearch, fluentd, kibana를 함께쓰는 software stack을 말한다.

  1. Elasticsearch: fluentd에 의해 log가 수집되고 통합되면, elasticsearch를 통해서 검색을 하고 정렬할 수 있다.
  2. Fluentd: fluentd는 여러 source에서 발생한 log들을 하나로 모으고 변환하고 필터링하는 역할을 한다.
  3. kibana: 모아진 로그를 토대로 시각화가 이루어지며, 다양한 그래프와 차트를 볼 수 있다.

주의할 것은 기본적으로 elasticsearch와 kibana는 opensource license가 아니다.

EFK stack은 docker를 사용해서 쉽게 배포가 가능한데, 우리는 apache http server(httpd), fluentd, elasticsearch, kibana를 배포하여 httpd의 모든 로그들을 fluentd를 통해 elasticsearch와 kibana에 통합시킬 것이다. docker-compose를 사용하여 함께 배포해보도록 하자.

  • docker-compose.yaml
version: "3"
services:
  web:
    image: httpd
    ports:
      - "80:80"
    links:
      - fluentd
    logging:
      driver: "fluentd"
      options:
        fluentd-address: localhost:24224
        tag: httpd.access

  fluentd:
    build: ./fluentd
    container_name: fluentd
    volumes:
      - ./fluentd/conf:/fluentd/etc
    links:
      - "elasticsearch"
    ports:
      - "24224:24224"
      - "24224:24224/udp"

  elasticsearch:
    container_name: elasticsearch
    image: docker.elastic.co/elasticsearch/elasticsearch:8.10.2
    ports:
      - 9200:9200
    environment:
      - http.host=0.0.0.0
      - transport.host=127.0.0.1
      - xpack.security.enabled=false

  kibana:
    image: docker.elastic.co/kibana/kibana:8.10.2
    links:
      - "elasticsearch"
    ports:
      - "5601:5601"

주목할 것은 httpd image의 logging부분인데, 위에서 본 docker logging driver를 적용한 부분이다. 이렇게 써주면 모든 httpd container log들이 localhost:24224로 TCP전송된다. 단, 여기서의 localhost는 container가 아니라 host pc의 localhost를 말하는 것임을 오해하지 말자. 따라서 fluentd container에서 24224 port를 열어 이를 받아낼 수 있다.

재밌는 것은 fluentd부분에 보면 build./fluentd라는 것을 확인할 수 있다. 이는 ./fluend directory의 Dockerfile을 사용해서 로컬 빌드하겠다는 것인데, 이렇게 만든 이유는 우리가 fluentd-debian image를 들고와서 elasticsearch 연동에 필요한 plugin들을 설치해야하기 때문이다.

  • ./fluentd/Dockerfile
FROM fluent/fluentd:v1.16.2-debian-1.1
USER root
RUN ["gem", "install", "fluent-plugin-elasticsearch", "--no-document", "--version", "5.3.0"]
USER fluent

gem으로 original source fluentd에 plugin들을 설치하는 것을 볼 수 있다.

다시 docker-compose.yaml 파일을 보면 ./fluentd/conf:/fluentd/etc로 되어있는데, ./flientd/conf directory에 configuration file을 넣겠다는 것이다.

다음의 configuration 파일을 ./fluentd/conf에 만들도록 하자. 다만 조심할 것은 fluentd.conf가 아니라 fluent.conf이다. 또한, 아래 conf의 {your-elastic-search-IP}부분은 elasticsearch가 설치되는 로컬 PC를 말한다.

  • ./fluentd/conf/fluent.conf
<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

<match *.**>
  @type copy

  <store>
    @type elasticsearch
    host {your-elastic-search-IP}
    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
    flush_interval 1s

  </store>

  <store>
    @type stdout
  </store>
</match>

0.0.0.0:24224로 들어오는 log들을 @type elasticsearch로 보내겠다는 것이다. 자세한 configuration 값은 다음을 참고하도록 하자. https://docs.fluentd.org/output/elasticsearch#parameters

이제 docker compose로 컨테이너를 실행해보도록 하자.

docker-compose up --detach

실행에 성공하였다면, curlhttpd를 실행시켜 log를 발생시키도록 하자.

curl http://localhost:80/
<html><body><h1>It works!</h1></body></html>

이제 kibana에 접속해 index name을 fluentd-*로 설정해주면된다. 참고로 elasticsearch8은 license가 있어야 데이터를 확인할 수 있다.

마지막으로 docker-compose로 컨테이너를 종료시키도록 하자.

docker-compose down

Kubernetes

먼저 kubernetes cluster가 설치되어있어야 한다. 즉, 적어도 한개의 node는 있어야 한다는 것이다.

fleuntd는 kubernetes에서 DamonSet으로 배포된다. DamonSet은 지정한 node에 반드시 한 개의 pod를 실행하도록 하고, node가 사라지면 해당 pod를 삭제한다. 즉, 한 개의 node당 하나씩 pod를 만들어주는 것이다.

datamonset은 다음의 링크에서 설치할 수 있다. https://github.com/fluent/fluentd-kubernetes-daemonset

단, fluentd는 output log들을 어디에 전달할 지를 결정해야하는데 어디에 전달할 지가 결정되어야 지정한 damonset을 설치할 수 있다.

git clone https://github.com/fluent/fluentd-kubernetes-daemonset

다음의 gitub에서 elasticsearch, gcs, azureblob 등 다양한 output 플랫폼들을 볼 수 있다. 우리는 elasticsearch를 사용해보도록 하자.

docker pull docker.elastic.co/elasticsearch/elasticsearch:7.17.14
docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.17.14

curl localhost:9200
{
  "name" : "7aa2e4d282ed",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "bPKBiw41RZ2VMf5YJmjLHA",
  "version" : {
    "number" : "7.17.14",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "774e3bfa4d52e2834e4d9d8d669d77e4e5c1017f",
    "build_date" : "2023-10-05T22:17:33.780167078Z",
    "build_snapshot" : false,
    "lucene_version" : "8.11.1",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

elasticsearch를 로컬에 실행시켰고 제대로 동작하는 지 확인하였다. 이제 fluentd-daemonset-elasticsearch-rbac.yaml에 elasticsearch host ip를 localhost로 바꾸어주도록 하자. custom하게 채워주어야 할 곳은 다음과 같다.

  1. {your-cluster-namespace}: cluster namespace
  2. {your-elasticsearch-host}: elasticsearch host ip, 만약 kubernetes pod안에 구동시켰다면 service DNS로도 접근이 가능하다.
  • fluentd-daemonset-elasticsearch-rbac.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluentd
  namespace: {your-cluster-namespace}

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: fluentd
rules:
- apiGroups:
  - ""
  resources:
  - pods
  - namespaces
  verbs:
  - get
  - list
  - watch

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: fluentd
roleRef:
  kind: ClusterRole
  name: fluentd
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
  name: fluentd
  namespace: {your-cluster-namespace}
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: {your-cluster-namespace}
  labels:
    k8s-app: fluentd-logging
    version: v1
spec:
...
      containers:
      - name: fluentd
        image: fluent/fluentd-kubernetes-daemonset:v1-debian-elasticsearch
        env:
          - name: K8S_NODE_NAME
            valueFrom:
              fieldRef:
                fieldPath: spec.nodeName
          - name:  FLUENT_ELASTICSEARCH_HOST
            value: {your-elasticsearch-host}
          - name:  FLUENT_ELASTICSEARCH_PORT
            value: "9200"
...

이제 실행시켜보도록 하자.

kubectl create -f ./fluentd_docs/fluentd-kubernetes-daemonset/fluentd-daemonset-elasticsearch-rbac.yaml

실행 후 로그가 모아지고 있다면 elasticsearch API를 통해서 검색이 가능하다.

curl {your-elasticsearch-host}:9200/_search | jq

뭔가 와르르 나왔다면 성공이고, 다음과 같이 나왔다면 default값만 나온 것이므로 연결된 것이 아니다.

{
  "took": 0,
  "timed_out": false,
  "_shards": {
    "total": 0,
    "successful": 0,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 0,
      "relation": "eq"
    },
    "max_score": 0,
    "hits": []
  }
}

지금은 어떠한 index도 설정하지 않았기 때문에 검색할 수 있는 것이 거의없다. kubectl logsfluentd pod에 들어가 elasticsearch와 잘 연동되었는 지를 확인하도록 하고, 종료하도록 하자.

kubectl delete -f ./fluentd_docs/fluentd-kubernetes-daemonset/fluentd-daemonset-elasticsearch-rbac.yaml
serviceaccount "fluentd" deleted
clusterrole.rbac.authorization.k8s.io "fluentd" deleted
clusterrolebinding.rbac.authorization.k8s.io "fluentd" deleted
daemonset.apps "fluentd" deleted

이제 기본적인 설치는 완료되었고, 더 나아가서 커스텀하고 configuration을 어떻게 할 수 있는 지 알아보도록 하자.

0개의 댓글