개발

[k8s] EFK 스택 구성하기 with helm charts

사무엘 얼터 2023. 9. 27. 11:24

저의 경우 fluentd까지는 필요하지 않다고 판단하여 경량버전인 fluent bit으로 구성하였습니다.
EFK스택을 간단하게 helm chart로 구성하는 방법에 관해 포스팅해보려고 합니다.

 

준비하기

EFK를 구성할 네임스페이스를 생성합니다.

kubectl create ns efk

해당 네임스페이스로 이동합니다.

kubens efk
kubens는 `brew install kubectx` 로 설치 후 사용할 수 있습니다.
참고 : https://blog.secuof.net/37

 

Elastic Search

헬름차트로 elastic search를 설치합니다.

helm install elasticsearch bitnami/elasticsearch

명령어를 실행한 후 조금 기다려주면 elasticsearch라는 이름으로 efk 네임스페이스에 구성 요소들이 설치됩니다.

기본으로 설정된 내용으로 설치되기 때문에 사용량이 더 많을 것 같거나 적을 것 같다면 bitnami/elasticsearch repo의 value.yaml을 확인하고 변경할 내용을 아래와 같이 명령어를 실행하면 됩니다.

helm upgrade elasticsearch --set {설정내용} bitnami/elasticsearch

 

Kibana

헬름차트로 Kibana를 설치합니다.

noglob helm install kibana bitnami/kibana --set elasticsearch.hosts[0]=elasticsearch.efk --set elasticsearch.port=9200

elasticsearch.hosts[0]=실제 설치한 elastic search 서비스의 cluster ip를 입력해 주시면 됩니다.
elasticsearch.port=변경하지 않으셨다면 9200으로 해주시면 됩니다.

대 괄호가 명령어에 들어가서 noglob이라는 명령어를 함께 사용하지 않으면 에러가 발생합니다.

조금 기다려주면 kibana라는 이름으로 efk 네임스페이스에 구성 요소들이 설치됩니다.

kubectl port-forward svc/kibana 8080:5601

위 명령어를 실행한 후 웹브라우저로 localhost:8080에 접속했을 때 잘 나온다면 성공입니다.

 

Fluent Bit

fluent bit은 헬름차트로 설치하지 않고 직접 yaml을 작성합니다.

service-account.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluent-bit
  namespace: efk

role.yaml

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

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: efk

configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  namespace: efk
  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

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

  input-kubernetes.conf: |
    [INPUT]
        Name              tail
        Tag               kube.*
        Path              /var/log/containers/*.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
    
    [FILTER]
        Name                nest
        Match               kube.*
        Operation           lift
        Nested_under        kubernetes
        Add_prefix          Kube.
    
    [FILTER]
      Name                modify
      Match               kube.*
      Remove              Kube.container_hash
      Remove              Kube.docker_id
      Remove              Kube.pod_id
      Remove              Kube.namespace_name
      Remove              Kube.pod_name
      Remove              Kube.host
      Remove              stream
      Remove              logtag
  
    [FILTER]
      Name                nest
      Match               kube.*
      Operation           nest
      Wildcard            Kube.*
      Nested_under        kubernetes
      Remove_prefix       Kube.

  output-elasticsearch.conf: |
    [OUTPUT]
        Name            es
        Match           *
        Host            ${FLUENT_ELASTICSEARCH_HOST}
        Port            ${FLUENT_ELASTICSEARCH_PORT}
        Logstash_Format On
        Replace_Dots    On
        Retry_Limit     False
        Suppress_Type_Name  On

  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

daemonset.yaml

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
  namespace: efk
  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:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 2020
          env:
            - name: FLUENT_ELASTICSEARCH_HOST
              value: "elasticsearch.efk"
            - 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"

ENV값을 유효한 값으로 변경해 줍니다.

위 yaml을 순서대로 apply 합니다.

kubectl apply -f service-account.yaml
kubectl apply -f role.yaml
kubectl apply -f role-binding.yaml
kubectl apply -f configmap.yaml
kubectl apply -f daemonset.yaml

모두 설치 후 성공하였다면 port forwarding 해둔 키바나 페이지로 갑니다.

 

Kibana 기본 설정 하기

키바나의 왼쪽 사이드바 부분을 열어 가장 하단으로 내려보면 Management 섹션에 Stack Management라는 탭이 있습니다.
해당 탭으로 진입한 후 Data -> Index Management 탭으로 진입하면 indies 화면이 나옵니다.

해당 화면에 logstash- 로 시작하는 항목이 존재한다면 fluent-bit가 정상적으로 로그를 수집하여 elastic search에 적재하고 있다는 것입니다. 만약 생성 되지 않았다면 fluent bit의 daemon set 로그를 확인하여 왜 작동되지 않는지 찾아보아야 합니다.

제가 겪었던 문제는 elastic search의 버전과 fluent bit의 버전이 맞지 않아 elastic search에 적재를 하지 못하는 문제가 발생하였고 fluent bit의 이미지 버전을 latest로 변경하여 해결하였습니다.

indies가 존재한다면 view를 생성하면 됩니다.

좌측 메뉴에서 Kibana -> Data Views를 클릭하여 해당 화면으로 진입한 뒤 create data view를 하면 됩니다.

name : 원하시는 대로
index pattern: logstash-*

이렇게 입력하고 생성해 주시면 로그가 잘 나오는 것을 확인할 수 있습니다.

'개발' 카테고리의 다른 글

[NestJS] 슬랙 알람 데코레이터 리팩토링  (0) 2023.05.12
Select marker with drag in Google map  (0) 2023.05.12
Naming Convention  (0) 2023.05.12
REST API Convention  (0) 2023.05.12