이번 게시글에서는 fluent-bit 사용법에 대해 정리합니다.
1. fluent-bit이란
일반적으로 로깅은 Elastic search, LogStatsh, Kibana의 앞자리를 가져온 ELK 스택이 많이 사용됩니다.
그러나 k8s 클러스터 환경에서는 로그를 수집하는 역할을 하는 'L'의 LogStatsh 대신 fluent bit 이라는 로그 수집기를 사용하는 경우도 많습니다(EFK라고 불립니다)
fluent bit은 대표적인 로그 수집기중(LogStash, fluentd, fluentbit) 가장 가볍고(다른 수집기에 비해 10배이상 가볍습니다), 분산한경을 고려하여 만들어졌기에 최근 k8s의 로그 수집기로 각광받고 있습니다.
2. EFK 아키텍쳐
아키텍쳐는 위와 같습니다.
파란색은 쿠버네티스 오브젝트로 전역적인 느낌으로 생각하면 되고 나머지는 모두 물리적 위치로 생각해도 됩니다.
(노란색은 노드입니다.)
(1) fluent-bit-daemonset
k8s 팟에는 docker 컨테이너가 있으며, docker engine은 stdout을 저장하며 노드의 /var/log/containers/ 위치에 로그파일을 심볼릭 링크를 제공해 줍니다.
도커엔진의 로그는 node마다 독립적인 구조이므로(node1에서 생긴 로그를 node2에서 접근하는건 비효율적) 각각의 노드에 로그를 수집해갈 무언가를 만들어야 합니다.
하나의 노드에 하나의 팟을 띄우려면 deployment에 affinity를 걸어줘도 되지만
하나의 노드에 하나의 팟을 띄워주는 쿠버네티스 오브젝트인 daemonset을 사용하여 배포하도록 되어있습니다.
그림상으로는 보라색으로 데몬셋에 의해 노드마다 로그를 수집해갈 fluent-bit pod이 생성됩니다.
(2) rabc관련 object(role, role-binding, service-account)
파란색은 rbac 관련 object들 입니다.
k8s는 pod에 service-account를 부여하는 방식의 인증-인가 구조를 갖고 있습니다.
(role rolebinding은 말그대로 규칙과, 규칙을 계정에 바인딩 해주는 object입니다)
결론은 우리 daemonset이 만들어준 pod에 service-account를 물려주면 됩니다.
이미 fluent-bit 공홈에서 manifest를 제공해주므로 직접 구축할 필요는 없습니다.
aws 환경에 인증이 필요한 경우 fluent-bit pod에 iam을 물려야 한다면 oidc를 만들어 service-account에 iam을 물려줘야 하니 주의합니다
(k8s rbac에 대해 알고싶다면 아래 게시글을 참고해주세요)
(3) fluent-bit-configmap
configmap은 k8s의 config를 클러스터단위로 가져가기 위한 k8s object입니다.
해당 object에 fluent-bit의 동작을 정의해줘야 합니다.
(4) pod
그림에서의 pod는 서비스 application이며
fluent bit을 구축할때는 'container의 stdout은 docker engine이 var/log/container에 심볼릭 링크가 떨어지게 해준다'는 원리를 생각하며 구축해줍니다.
3. 파이프라인 및 구성요소
INPUT -> (PARSER) -> FILTER -> BUFFER -> (ROUTER) -> OUTPUT
fluent bit은 위와같은 파이프라인을 가지고 log를 특정한 목적지로 보내주게 됩니다.
각 파이프라인이 하는 일과 대표적으로 세팅해줘야 하는 것들은 아래와 같습니다.
(1) INPUT
fluent bit이 수집한 로그, 메트릭은 event 또는 record라고 합니다. 해당 파이프라인에 어떤 record를 수집할지를 정하게 됩니다.
(2) PARSER
parser에서 비 정형데이터를 정형 데이터로 변경시킬 수 있습니다.
정규표현식 기반입니다.
docker 파서는 json형태로 변경해서 log라는 key에 stdout을 담아줍니다.
(3) FILTER
FILTER에 적절한 옵션을 지정하면 원하는 정보만 목적지로 보낼 수 있습니다.
(4) BUFFER
버퍼는 입출력의 속도차이가 생기는것을 고려해 잠시 저장하는 공간입니다.
memory(ram)이나 file에 저장할수 있으며 tag단위로 chunk가 생성됩니다.
(5) OUTPUT
결과를 어디로 보낼지 지정하는 곳입니다.
cloudwatch, s3, firehose, http 등 원하는 OUTPUT으로 지정한 RECORD를 보낼 수 있습니다.
3. 설치
공식홈페이지에서 제공하는 manifest를 이용합니다.
설치해야되는 리소스는 serviceaccount, role, rolebinding, configmap, daemonset입니다.
fluent-bit은 k8s환경을 위해 미리 준비된 manifest를 제공하므로 가져와 etcd에 넣어주면 설치가 완료됩니다.
공식문서에서 제공하는 yaml을 받아 etcd에 manifest를 올려줍니다.
aws와 연계해서 사용해야 한다면 아래 이미지를 이용해 구축합니다(aws 관련 리소스를 사용해야 할때 플러그인이 없다고 나오면 aws에서 제공하는 daemonset을 사용해줍니다.)
다양한 플러그인을 지원하니 원하는 daemonset을 선택하면 되며, 예시에는 stdout으로 결과를 보낼꺼니 아무거나 사용해도 상관없습니다.
4. 테스트용 pod 생성
(1) 테스트할 pod 만들기
// 해당 이미지는 linux/amd64 플랫폼으로 빌드하였습니다.
$ docker pull jaeho310/hello-fluent-bit
// aws 환경이라면 ecr에 해당 이미지를 push한 후 사용합니다.
$ kubectl run fluent-bit-test --image=jaeho310/hello-fluent-bit:v1
fluent-bit이 구축된 클러스터에 가서 해당 이미지를 실행합니다.
aws환경이라면 ecr에 해당 이미지를 올려테스트합니다.
(이미지의 내용은 아래와 같으며 3초마다 'hello 현재시간'을 stdout으로 보내 출력합니다)
from datetime import datetime
from time import sleep
if __name__ == '__main__':
while True:
print("hello ",datetime.now())
sleep(3)
========================================
FROM python:3.7.5-slim
WORKDIR /app
ADD . /app
RUN chmod +x /app/app.py
ENTRYPOINT ["python", "-u", "app.py"]
========================================
(k8s는 linux/amd64 환경이라고 가정합니다.)
$ docker buildx build --platform linux/amd64 -t jaeho310/hello-fluent-bit:v1 .
5. config-map 수정
아까 다운받은 config-map은 elastic-search용 configmap입니다.
서비스의 정책에 맞게 수정해서 myfluent-bit-config.yaml을 작성합니다.
수정한 곳에 *을 찍어놨으니 1,2,3 세가지를 수정합니다.
// myfluent-bit-config.yaml
apiVersion: v1
data:
filter-kubernetes.conf: |
[FILTER]
Name kubernetes
Match kube.*
Kube_URL https://kubernetes.default.svc:443
Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token
Kube_Tag_Prefix kube.var.log.containers.
Merge_Log On
Merge_Log_Key log_processed
K8S-Logging.Parser On
K8S-Logging.Exclude Off
fluent-bit.conf: |
[SERVICE]
Flush 1
Log_Level info
Daemon off
Parsers_File parsers.conf
HTTP_Server On
HTTP_Listen 0.0.0.0
HTTP_Port 2020
@INCLUDE input-kubernetes.conf
***** 1. 기존에 elastic search 관련 output을 output-stdout으로 변경해서 include ************
@INCLUDE output-stdout.conf
***********************************************************************************
@INCLUDE filter-kubernetes.conf
*************** 2. include한 이름과 똑같은 이름으로 conf를 만들어줍니다.*************
output-stdout.conf: |
[OUTPUT]
// name은 결과를 어디로 보낼지 선택하는 field 입니다.
// cloudwatch, elasticsearch, s3 등을 지원하지만 test용으로 stdout을 지정합니다.
Name stdout
// Match는 input의 tag와 일치시켜야 합니다.
Match kube.*
*************************************************************************
input-kubernetes.conf: |
[INPUT]
Name tail
Tag kube.*
********** 3. 아까 생성한 pod의 이름은 fluent-bit-test이므로 해당 로그를 INPUT으로 보냅니다 ***********
Path /var/log/containers/fluent-bit-test*.log
***************************************************************************************
Parser docker
DB /var/log/flb_kube.db
Mem_Buf_Limit 5MB
Skip_Long_Lines On
Refresh_Interval 10
parsers.conf: |
[PARSER]
Name apache
Format regex
Regex ^(?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$
Time_Key time
Time_Format %d/%b/%Y:%H:%M:%S %z
[PARSER]
Name apache2
Format regex
Regex ^(?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$
Time_Key time
Time_Format %d/%b/%Y:%H:%M:%S %z
[PARSER]
Name apache_error
Format regex
Regex ^\[[^ ]* (?<time>[^\]]*)\] \[(?<level>[^\]]*)\](?: \[pid (?<pid>[^\]]*)\])?( \[client (?<client>[^\]]*)\])? (?<message>.*)$
[PARSER]
Name nginx
Format regex
Regex ^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$
Time_Key time
Time_Format %d/%b/%Y:%H:%M:%S %z
[PARSER]
Name json
Format json
Time_Key time
Time_Format %d/%b/%Y:%H:%M:%S %z
[PARSER]
Name docker
Format json
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L
Time_Keep On
[PARSER]
# http://rubular.com/r/tjUt3Awgg4
Name cri
Format regex
Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<message>.*)$
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L%z
[PARSER]
Name syslog
Format regex
Regex ^\<(?<pri>[0-9]+)\>(?<time>[^ ]* {1,2}[^ ]* [^ ]*) (?<host>[^ ]*) (?<ident>[a-zA-Z0-9_\/\.\-]*)(?:\[(?<pid>[0-9]+)\])?(?:[^\:]*\:)? *(?<message>.*)$
Time_Key time
Time_Format %b %d %H:%M:%S
kind: ConfigMap
metadata:
creationTimestamp: "2022-05-30T05:43:33Z"
labels:
k8s-app: fluent-bit
name: fluent-bit-config
namespace: logging
resourceVersion: "16447976"
uid: 1fa08e9c-5f4d-41a9-ba5d-9dd520eee2d4
3번에서 소개한 파이프라인은 INPUT -> (PARSER) -> FILTER -> BUFFER -> (ROUTER) -> OUTPUT
입니다.
기존에는 elastic search로 보내는 output이 설정되어 있었지만 예제를 위해서 stdout(콘솔출력)으로 변경했습니다.
myfluenct-bit-config.yaml파일을 작성했다면
기존의 fluent-bit-config를 삭제한후 적용시킵니다.
$ kubectl delete cm fluent-bit-config
$ kubectl apply -f myfluent-bit-config.yaml
5. 결과확인
k9s에 익숙하신 분은 k9s로, 그렇지 않다면 아래의 방법을 사용해 확인합니다.
먼저 fluent-bit-test 팟이 어디노드에 떠있는지 확인합니다.
$ kubectl get pod -o wide
해당 노드에 떠있는 fluent-bit 팟은 아직 변경된 configmap이 적용되지 않은 상태입니다.
해당 pod을 삭제하면 daemonset이 configmap이 적용된 pod을 재구동 해줍니다.
$ kubectl delete pod fluent-bit-xxxxx
재구동된 pod의 이름으로 로그를 확인합니다.
$ kubectl logs -f fluent-bit-xxxxx
아래와 같은 로그가 떨어지는걸 확인할 수 있습니다.
[2022/05/30 07:21:16] [ info] inotify_fs_add(): inode=16350237 watch_fd=1 name=/var/log/containers/fluent-bit-test_logging_fluent-bit-test-6ac66347bfc3ee2ac070c4db77b0edb071eaedd3baa52a83271331374fd53e2d.log [0] kube.var.log.containers.fluent-bit-test_logging_fluent-bit-test-6ac66347bfc3ee2ac070c4db77b0edb071eaedd3baa52a83271331374fd53e2d.log: [1653895801.610305881, {"log"=>"hello 2022-05-30 07:30:01.610221 │ ", "stream"=>"stdout", "time"=>"2022-05-30T07:30:01.610305881Z", "kubernetes"=>{"pod_name"=>"fluent-bit-test", "namespace_name"=>"logging", "pod_id"=>"1a19291d-7eb2-411c-a14a-001d6ca7c1b2", "labels"=>{"run"=>"fluent-bit-test"
inotify_fs_add(): inode=16350237 watch_fd=1 name=/var/log/containers/fluent-bit-test_logging_fluent-bit-test_logging-~~~
-> inotify 인터페이스라는 인터페이스가 파일디스크립션을 본다고 되어있습니다. fd의 1은 standard out을 의미합니다.
아마도 아까 파이썬으로 만든 print('')가 stdout으로 보내주면 파일디스크립션이 연동되어 긁어간다는 내용입니다.
"log"=>"hello 2022-05-30 07:30:01.610221
아까 원하던 결과가 원하는 OUTPUT(stdout(콘솔))에 성공적으로 나왔습니다.
6. 추가적인 설정을 원한다면
fluent-bit의 동작원리와 config-map을 사용하여 fluent-bit의 기본적인 사용법을 확인했다면
fluent-bit 공식홈페이지에 가서 원하는 input output filter parser를 적용합니다.
7. es로 보내고 싶다면
efk에서 수집기를 담당하는 fluent bit에서 원하는 데이터를 수집했다면 이제 elastic serch로 데이터를 보내줘야합니다.
아래의 게시글을 참고합니다.
'EFK' 카테고리의 다른 글
elastic search 트러블 슈팅(로그 적재) (0) | 2022.08.17 |
---|---|
lambda 트러블 슈팅(concurrent execution limit is exceeded) (0) | 2022.08.11 |
fluent bit 트러블 슈팅 (0) | 2022.07.21 |
[EFK] kinesis firehose를 사용하여 fluent-bit 로그를 es로 보내기(datatransfer with lambda) (0) | 2022.06.16 |
[EFK] fluent bit을 사용해 로그를 elastic search 보내기 (0) | 2022.06.16 |
댓글