본문 바로가기
EFK

[EFK] EFK란(fluent bit 사용법)

by devjh 2022. 5. 30.
반응형

이번 게시글에서는 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에 대해 알고싶다면 아래 게시글을 참고해주세요)

 

[kubernetes] 쿠버네티스 인증, 인가(rbac)

1. 쿠버네티스 인증이란? 쿠버네티스 사용자가 맞는지 확인하는 작업입니다. 쿠버네티스 마스터노드에는 api서버가 있으며 보통 쿠버네티스 클라이언트인 kubectl을 이용하여 요청을 보냅니다. 이

frozenpond.tistory.com

(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를 이용합니다.

 

Kubernetes - Fluent Bit: Official Manual

To obtain this information, a built-in filter plugin called kubernetes talks to the Kubernetes API Server to retrieve relevant information such as the pod_id, labels and annotations, other fields such as pod_name, container_id and container_name are retrie

docs.fluentbit.io

설치해야되는 리소스는 serviceaccount, role, rolebinding, configmap, daemonset입니다.

fluent-bit은 k8s환경을 위해 미리 준비된 manifest를 제공하므로 가져와 etcd에 넣어주면 설치가 완료됩니다.

공식문서에서 제공하는 yaml을 받아 etcd에 manifest를 올려줍니다.

 

aws와 연계해서 사용해야 한다면 아래 이미지를 이용해 구축합니다(aws 관련 리소스를 사용해야 할때 플러그인이 없다고 나오면 aws에서 제공하는 daemonset을 사용해줍니다.)

다양한 플러그인을 지원하니 원하는 daemonset을 선택하면 되며, 예시에는 stdout으로 결과를 보낼꺼니 아무거나 사용해도 상관없습니다.

 

AWS for Fluent Bit 이미지 사용 - Amazon ECS

AWS for Fluent Bit 이미지 사용 AWS는 CloudWatch Logs와 Kinesis Data Firehose 모두에 대한 플러그인과 함께 Fluent Bit 이미지를 제공합니다. Fluent Bit가 Fluentd보다 리소스 사용률이 낮으므로 Fluent Bit를 로그 라우

docs.aws.amazon.com

 

 

Fluent Bit를 DaemonSet로 설정하여 CloudWatch Logs에 로그 전송 - Amazon CloudWatch

이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.

docs.aws.amazon.com

 

 

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를 적용합니다.

 

Outputs - Fluent Bit: Official Manual

 

docs.fluentbit.io

 

7. es로 보내고 싶다면

efk에서 수집기를 담당하는 fluent bit에서 원하는 데이터를 수집했다면 이제 elastic serch로 데이터를 보내줘야합니다.

아래의 게시글을 참고합니다.

 

[devops] fluent bit을 사용해 로그를 elastic search로보내기

이번 게시글에서는 fluent bit에서 elastic search로 보내는 방법에 대해 정리합니다. fluent-bit 사용법은 이전 게시글을 참고해주세요 1. fluent bit에서 elastic search로 보내는 4가지 방법 fluent-bit에서 원..

frozenpond.tistory.com

 

반응형

댓글