개발

Fluent-bit를 통한 Kubernetes 로그환경 구축

데이비드___ 2023. 4. 3. 23:50
목적: Fluentbit를 적용하여 쿠버네티스 기반 로그환경 마련 자체 로그환경 구성

EFK 소개


EFK

EFK 스택을 활용한 Kubernetes환경에서의 통합 로깅체계 구축 및 로깅 중앙화

  • Pod가 삭제 되어도 삭제된 Pod 내의 컨테이너 로그를 남길 수 있음
  • 로그를 중앙 집중식으로 저장하고 분석해 MSA 환경에서의 로그 관리의 어려움을 해결
  • 고가용성을 제공하고 많은 양의 데이터를 저장할 수 있고 실시간으로 검색, 분석, 저장 가능
  • 저장된 로그를 웹으로 시각화 해서 차트나 그래프로 볼 수 있음
  • 보내지는 데이터의 유형에 따라 로그 대쉬보드 + Metric 대쉬보드 구현 가능
  • cluster내에 container log 수집 가능
  • cpu, memory, disk 현황 데이터 수집 가능

Fluent-bit 소개


INPUT -> (PARSER) -> FILTER -> BUFFER -> (ROUTER) -> OUTPUT
fluent bit은 위와같은 파이프라인을 가지고 log를 지정한 목적지로 보내쥽니다
 
Fluent-bit Config

Fluent Option설명기타
INPUTfluent bit이 수집한 로그, 메트릭은 event 또는 record라고 합니다.

해당 파이프라인에 어떤 record를 수집할지를 정하게 됩니다.
(공식) Document 
PARSERparser에서 비 정형데이터를 정형 데이터로 변경시킬 수 있습니다. 
정규표현식 기반입니다.
docker 파서는 json형태로 변경해서 log라는 key에 stdout을 담아줍니다.
공식) Document 
FILTERFILTER에 적절한 옵션을 지정하면 원하는 정보만 목적지로 보낼 수 있습니다.
혹은 설정에 따라 원하는 데이터만 추가적인 Parser를 적용할 수도 있습니다.
 
BUFFER버퍼는 입출력의 속도차이가 생기는것을 고려해 잠시 저장하는 공간입니다.

memory(ram)이나 file에 저장할수 있으며 tag단위로 chunk가 생성됩니다.
 
OUTPUT결과를 어디로 보낼지 지정하는 곳입니다.

cloudwatch, s3, firehose, htt, ES, Logstash 등 원하는 OUTPUT으로 지정한 RECORD를  보낼 수 있습니다.
공식) Document 

 

Kubernetes Cluster 배포


배포 파일 리스트

배포시에 아래의 파일들이 필요합니다. 각 파일 순서대로 배포 해야 하며 저희 환경기준에서는 Logging Namespace에 구성 하였습니다.
각각의 로그들은 Configmap에서 설정한 옵션에 따라 데이터를 수집하고 필터하고 파씽하여 최종적으로 보내는 작업까지 거치게 됩니다.

Service-account.yaml

  • 서비스 계정을 만들어 준다고 생각하면 됩니다
  • 우리는 서비스 계정을 통해 Role을 지정하고 바인딩 해줄 생각입니다.
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluent-bit
  namespace: logging

Role.yaml

  • fluent-bit-read라는 Role을 생성하고 읽는 권한만 추가(RBAC)하였습니다 (except Update)
  • RBAC 를 통해 Role을 사용하는 주된 목적은 보완적인 측면때문 입니다. (아무 리소스나 접근 하면 안됨) 
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: fluent-bit-read
rules:
- apiGroups: [""]
  resources:
  - namespaces
  - pods
  verbs: ["get", "list", "watch"]

Role-binding.yaml

  • 위에서 생성한 Role을 기준으로 service-account를 바인딩하여 줍니다.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: fluent-bit-read
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: fluent-bit-read
subjects:
- kind: ServiceAccount
  name: fluent-bit
  namespace: logging

 
ConfigMap.yaml

  • 실제 Fluent-bit의 각 레이어별(수집 → 전송) 설정을 할 수 있습니다.
  • Krakend Log 수집을 위해 Input 옵션을 사용하고 있습니다.
  • Kubernetes 자원들의 정보들을 수집하기 위해서는 Filter 옵션을 사용합니다.
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  namespace: logging
  labels:
    k8s-app: fluent-bit
data:
  # Configuration files: server, input, filters and output
  # ======================================================
  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

    [OUTPUT]
        Name          stdout
        Match         *

    [OUTPUT]
        Name          http
        Match         *
        Host          amazonaws.com
        Port          8080
        Format        json

    @INCLUDE input-kubernetes.conf
    @INCLUDE filter-kubernetes.conf

  input-kubernetes.conf: |
    [INPUT]
        Name              tail
        Tag               kube.*
        #Path              /var/log/containers/*.log
        Path              /var/log/containers/hello*.log
        Parser            docker
        DB                /var/log/flb_kube.db
        Mem_Buf_Limit     5MB
        Skip_Long_Lines   On
        Refresh_Interval  10

  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

  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   off

    [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

DeamonSet.yaml

  • Daemonset은 각 노드별로 1개의 파드가 떠서 각 노드별 Contianer에서 발생되는 로그를 수집할 수 있도록 하는게 목적입니다.
  • 각기 다른 노드에서 로그를 읽어 올 수도 있겠지만 비효율적이라 각 노드별로 로그를 수집하도록 하기 위해 DaemonSet을 활용하는 것입니다.
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
  namespace: logging
  labels:
    k8s-app: fluent-bit-logging
    version: v1
    kubernetes.io/cluster-service: "true"
spec:
  selector:
    matchLabels:
      k8s-app: fluent-bit-logging
  template:
    metadata:
      labels:
        k8s-app: fluent-bit-logging
        version: v1
        kubernetes.io/cluster-service: "true"
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "2020"
        prometheus.io/path: /api/v1/metrics/prometheus
    spec:
      containers:
      - name: fluent-bit
        image: fluent/fluent-bit:1.5
        imagePullPolicy: Always
        ports:
          - containerPort: 2020
        env:
        - name: FLUENT_ELASTICSEARCH_HOST
          value: "elasticsearch"
        - name: FLUENT_ELASTICSEARCH_PORT
          value: "9200"
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
        - name: fluent-bit-config
          mountPath: /fluent-bit/etc/
      terminationGracePeriodSeconds: 10
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
      - name: fluent-bit-config
        configMap:
          name: fluent-bit-config
      serviceAccountName: fluent-bit
      tolerations:
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      - operator: "Exists"
        effect: "NoExecute"
      - operator: "Exists"
        effect: "NoSchedule"

 

Fluent-bit 상세 설정


Fluent-bit 가이드에 따른 설정으로는 Kubeernate cluster내에 생성되는 파드의 모든 로그를 보내게 되어 있습니다.
이번에 구축하면서 알게된 사실은 각각의 로그들은 Node Host의 /var/log/container/{POD_NAME}-{UID).log 형식으로 저장하게 됩니다. 또한, 시간에 따라 로그가 삭제될 수 있도록 LogRotate 형식을 취하고 있습니다.


데이터 수집 설정

   @INCLUDE input-kubernetes.conf

  input-kubernetes.conf: |
    [INPUT]
        Name              tail
        Tag               kube.*
		# 특정 서비스 파드에서 발생하는 로그 수집
        Path              /var/log/containers/hello*.log
        Parser            docker
        DB                /var/log/flb_kube.db
        Mem_Buf_Limit     5MB
        Skip_Long_Lines   On
        Refresh_Interval  10
  • 우리가 일반적으로 kubectl logs -f POD_NAME -n logging 을 하게 되면 kubernete를 통해 /var/log/container/{POD_NAME}-{UID).log 파일을 tailing 해주게 됩니다.
  • 하지만 지금 프로젝트에서 필요한 로그는 hello에 관한 로그만 필요하기에 수집되는 데이터를 hello로 한정하여 줍니다.
    • Path /var/log/containers/hello-lg*.log

데이터 전송

 # 단순 확인을 위한 stdout 출력
    [OUTPUT]
        Name          stdout
        Match         *

# Logstash 연동을 위한 전송설정(특이 케이스)
    [OUTPUT]
        Name          http
        Match         *
        Host          amazonaws.com
        Port          8080
        Format        json
  • 수집된 데이터를 출력하여 확인하는 용도로 stdout으로 출력 합니다
  • 최종적으로 Logstash와의 연동을 위해 http로 전송해 주는 옵션을 추가 하였습니다.
    • 해당 설정이 일반적인 경우가 아니라 es와 같은 지정된 프로토콜 전송이 아닌 http전송을 활용 하였습니다. 
    • Logstash에 추가 pipeline configuration 이 필요합니다.

 

결과 및 확인


일전에는 kube-system로그와 다른 파드들에서 나오는 로그들로 인해 제대로 필터 되지 않았지만
Fluent-bit의 Configmap 설정을 프로젝트에 맞게 수정 후 오직 타겟 서비스 로그만, Fluent를 통해 수집되는 것을 확인 할 수 있었습니다.

참고

(공식) Fluent-bit kubernete 
EFK-Fluent-bit 사용법