fluentd는 log collector로 다양한 source(file, stdout, network etc)에서 발생한 log들을 한 곳으로 모아 필터링과 변환을 거친 후에 원하는 위치로 output을 보내준다.
https://www.youtube.com/watch?v=5ofsNyHZwWE
다음은 fluentd의 대략적인 아키텍처 구조를 보여준다.
fluentd는 C언어로 만들어졌으며 사용하기 좋게 Ruby wrapper로 감싸져있다. 이 덕분에 50,000+개의 서버로 부터 log들을 받을 수 있는 scalability를 가지고 있으며, 사용자 logs들을 JSON과 같은 원하는 format으로 쉽게 다룰 수 있다.
필자는 container, kubernetes 환경에서 fluentd를 사용할 것이기 때문에 이를 참고만 남겨두기로 한다.
설치 이전에 설정해야하는 부분들이 있다.
먼저 정확한 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를 사용해서 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 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)
fluentd docs에 나온대로 하면 안타깝게도 현재 실행이 되지 않는다. (https://github.com/fluent/fluentd-docs-gitbook/issues/391) 그래서 약간의 수정을 하였다.
시작 전에 docker
와 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을 말한다.
주의할 것은 기본적으로 elasticsearch와 kibana는 opensource license가 아니다.
EFK stack은 docker를 사용해서 쉽게 배포가 가능한데, 우리는 apache http server(httpd), fluentd, elasticsearch, kibana를 배포하여 httpd
의 모든 로그들을 fluentd를 통해 elasticsearch와 kibana에 통합시킬 것이다. docker-compose를 사용하여 함께 배포해보도록 하자.
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들을 설치해야하기 때문이다.
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를 말한다.
<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
실행에 성공하였다면, curl
로 httpd
를 실행시켜 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 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하게 채워주어야 할 곳은 다음과 같다.
---
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 logs
로 fluentd
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을 어떻게 할 수 있는 지 알아보도록 하자.