데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      AKS 상용 서비스 적용기 (5) - EFK 설정

      dragonpipe 24.09.24
      1,364 5 0
      DEVOTEE 요약
      AKS 환경에서 로그 통합 관리를 위해 EFK(ElasticSearch, Fluentd, Kibana) 기술을 적용한 후기를 다룹니다. 다양한 구성 옵션 중 Fluentd를 데몬셋으로 설치하여 리소스를 절약하고 유연한 필터링 기능을 활용했습니다. 설정 과정과 함께 Spring 애플리케이션 로그를 JSON 포맷으로 변환하고, Kibana를 통해 로그를 모니터링하는 방법을 소개합니다.
      DEVOTEE 추천 블로그

      안녕하세요, dragonpipe입니다.

      이 번에는 AKS(Azure Kubernetes Service) 상용 서비스에 적용한 후기 다섯 번째 내용으로, k8s 환경에서 로그를 통합 관리하게 해주는 EFK 기술에 대해 소개드리려고 합니다.


      1. EFK 아키텍처 선정

      우선 EFK는 ElasticSearch, Fluentd, Kibana의 줄임말인데요. 이 세 가지 기술을 사용하는 아키텍처는 여러가지가 있습니다.

      그 중에서 서비스에 맞는 아키텍처를 선정하여 진행하면 됩니다.

      Use Case #1: 각 Node에 Fluentd를 데몬셋으로 설치하고 개별적으로 ElasticSearch로 전송

      efk(2).png

      Use Case #2: 각 Pod에 Fluentbit을 Sidecar로 설치하고 Fluentd에서 통합 수집하여 ElasticSearch로 전송efk(3).png

      Use Case #3: 각 Pod에 Fluentbit을 Sidecar로 설치하고 Kafka를 Fluentd와의 중간에 배치하여 Log손실을 방지efk(4).png

      Use Case #4: 각 Application Server에 쉐어드 볼륨을 마운트하고 Fluentd에서 Log적재 파일을 직접 읽어 드림

      efk(5).png

      그 중에서 일반적으로 Use Case 1번과 Use Case 2번을 많이 적용하는데요. Fluentd를 데몬셋으로 설치하는 방안(1번 케이스)의 장단점에 대해 알아보겠습니다.

      1. 장점

        • 개별 Pod에 Sidecar로 설치하는 것 보다 리소스 경량화 가능 (Pod 마다 Sidecar를 설치하는데 소요되는 자원 절약)

        • fluentbit은 경량이긴 하나 fluentd에서 제공하는 다양한 필터링 기능 사용 불가 (특정 namespace 혹은 pod 로그만 수집 등)

      2. 단점

        • 콘솔에 출력되는 stdout/stderr를 container 로그로 저장하므로 콘솔 로그를 수집에 맞게 Json 형식으로 출력해야 함

        • Daemonset resource 관리가 어려움. (Log생성이 많은 Pod가 특정 노드에 몰릴경우 발생.)

      각 아키텍처의 장단점을 비교 분석한 결과, 저희 서비스는 아래와 같이 "Fluentd를 데몬셋으로 설치"하는 아키텍처를 구성하게 되었습니다.

      efk(1).png


      2. Fluentd 설치

      저희는 ElasticSearch와 kubernetes에 맞춤형 image인 "fluentd-kubernetes-daemonset-debian-elasticsearch"를 사용하여 쿠버네티스에 데몬셋으로 설치하였습니다.

      설치하는 방법은 아래와 같습니다.

      1) ServiceAccount, ClusterRole, ClusterRoleBinding 생성 (한번 해두면 수정할 일은 거의 없음)

      1. yaml 파일 작성

        apiVersion: v1
        kind: ServiceAccount
        metadata:
          labels:
            k8s-app: fluentd
          name: fluentd
          namespace: kube-system
        
        ---
        kind: ClusterRole
        apiVersion: rbac.authorization.k8s.io/v1
        metadata:
          name: fluentd
        rules:
          - apiGroups:
              - ""
            resources:
              - "namespaces"
              - "pods"
            verbs:
              - "list"
              - "get"
              - "watch"
        
        ---
        kind: ClusterRoleBinding
        apiVersion: rbac.authorization.k8s.io/v1
        metadata:
          name: fluentd
        roleRef:
          apiGroup: rbac.authorization.k8s.io
          kind: ClusterRole
          name: fluentd
        subjects:
        - kind: ServiceAccount
          name: fluentd
          namespace: kube-system
      2. ServiceAccount, ClusterRole, ClusterRoleBinding 생성

        k apply -f fluentd-service.yaml

      3. ServiceAccount, ClusterRole, ClusterRoleBinding 생성 확인

        k get serviceaccount fluentd -n kube-system

        k get clusterrole fluentd -n kube-system

        k get clusterrolebinding fluentd -n kube-system

      2) Configmap 생성

      1. yaml 파일 작성

        • namespace : kube-system으로 설정 필요

        • data : file 형태로 저장

          • data 하위에 key로 적은 문자열 (예 : fluent.conf) : 파일명

          • | 이후 내용 : txt 형태로 파일의 내용이 됨

          • <filter> 설정 : filter란 이벤트 처리를 위한 중간 pipeline으로서, 생성할 경우 정의한 문자열 규칙과 동일하게 tag를 지정한 이벤트가 들어오면 로직을 처리 (예 : kubernetes.var.log.containers.**이면 tag로 kubernetes.*로 지정할 경우 filter에 걸림)

            • type : parser로 설정 (json parse를 위해)

            • <parse> 설정

              • type : json으로 설정

            • key_name : log로 설정

          • key_name을 log로 설정하는 이유

            • 데몬셋 parse 규칙 : /^(?<time>.+) (?<stream>stdout|stderr)( (?<logtag>.))? (?<log>.*)$/

            • 마지막 (?<log>.*)$에 해당하는 부분이 실제 application에서 쌓는 로그이며 json 형태로 되어 있음

            • <filter></filter> 없이 ES로 발송할 경우 json 전체가 문자열로 넘어감

            • 해당 json을 ES에서 관리하는 key:value로 인식시키려면 parse 해서 전달해야 함

            • kubernetes.conf를 보면 id가 "in_tail_container_logs"인 소스를 "kubernetes.xxx"로 보내고 있음 (설정하지 않을 경우 기본값이며 기본값이 "kubernetes.*"임)

            • filter로 받아서 log라는 key_name으로 json 문자열이 넘어오므로 key_name : log를 넣어서 json 문자열을 parsing 할 수 있도록 함

          • 환경변수 주입하지 않고 config-map으로 생성한 이유

            • filter를 환경변수 주입으로 생성할 수는 없으므로 config map에 정의하여 config 파일을 생성

            • fluent.conf 파일에서 conf.d/xxx.conf 파일을 include 하도록 설정되어 있어서 daemonset.yaml 에서 volume mountpath를 conf.d/*.conf로 지정

          • filter 조건을 다른 문자열이 아닌 kubernetes.var.log.containers.** 로 설정한 이유

            • source의 tag를 임의의 환경변수로 주입하고 (예 : spring_log) 그 것으로 filter 조건으로 잡아도 걸림

            • 하지만 kubernetes.**로 시작해야만 kubernetes.conf 에서 filter 되며 kubernetes 관련 수집 정보를 add 해주기 때문에 반드시 kubernetes로 시작하는 필터 조건을 잡아야 함

          • retry_count 설정 추가

            • 기본적으로 4회 retry 하도록 되어 있으나 여러번 무한히 반복하여 retry 하는 경우가 탐지되어 3회로 지정하는 부분 추가

        apiVersion: v1
        kind: ConfigMap
        metadata:
          name: fluentd-config
          namespace: kube-system
        data:
          fluent.conf: |
            <filter kubernetes.var.log.containers.**>
              @type parser
              <parse>
                @type json
                json_parser json
              </parse>
              replace_invalid_sequence true
              emit_invalid_record_to_error false
              key_name log
              reserve_data true
              retry_count 3
              retry_limit 3
            </filter>
      2. config-map 생성

        k apply -f fluentd-config.yaml

      3. config-map 생성 확인

        k get configmap -n kube-system

      3) Daemonset 생성

      1. yaml 파일 작성

        • image : fluent/fluentd-kubernetes-daemonset:v1.11.5-debian-elasticsearch7-amd64-1.0

        • namespace : kube-system으로 설정 필요

        • 환경변수 설정 (env)

          • TZ : Timezone 설정 (Asia/Seoul)

          • FLUENT_ELASTICSEARCH_HOST : ES의 주소 (현재는 STG ES로 설정)

          • FLUENT_ELASTICSEARCH_PORT : 통신할 포트 (9200이 기본값)

          • FLUENT_ELASTICSEARCH_SCHEME : HTTPS/HTTP 중 택1

          • FLUENTD_SYSTEMD_CONF : systemd.conf 파일을 사용할 것인지 여부

          • FLUENT_CONTAINER_TAIL_EXCLUDE_PATH : 로그 수집에서 제외할 경로 (현재는 fluentd 로그를 제외시킴)

          • FLUENT_CONTAINER_TAIL_PARSER_TYPE : CONTAINER LOG를 수집할 때 parse 규칙 (없으면 json이 기본). 로그가 json 형식이면 parse에 문제가 없지만 spring log를 json 포맷으로 바꿔도 "pattern not matchced" 에러 발생.

          • 원인은 컨테이너 로그가 "time + stdout/stderr + F + JSON" 형태로 구성되어 있음. 따라서 이 형식에 맞게 아래와 같이 parse 규칙을 지정해줘야 "pattern not mathced" 에러 발생하지 않음

          • /^(?<time>.+) (?<stream>stdout|stderr)( (?<logtag>.))? (?<log>.*)$/

        • volume mount 설정 (volumeMounts, volumes)

          • varlog 설정 : Node에 저장되는 컨테이너 로그의 위치가 /var/log/containers/에 쌓이므로 /var/log 추가 필요(volume 설정에서 hostpath로 설정하는 이유는 실제 로그가 쌓이는 위치가 pod 내부가 아니라 node의 hostpath에 쌓이기 때문)

          • dockercontainerlogdirectory 설정 : /var/log/pods 위치에 저장되는 로그를 ReadOnly로 읽기 위해 추가

          • config path 설정 : fluentd를 구동하면 자체적으로 생성해주는 conf 파일이 있지만 그 외 추가로 필요한 conf 파일이 있을 경우 configmap을 volume으로 추가하여 conf 파일 생성(자동 생성되는 fluent.conf를 보면 conf.d/*.conf를 include 하고 있음. 따라서 mountpath를 conf.d/로 하여 해당 경로에 우리가 직접 설정한 conf 파일이 생성되도록 설정)

        apiVersion: apps/v1
        kind: DaemonSet
        metadata:
          name: fluentd
          namespace: kube-system
          labels:
            k8s-app: fluentd-logging
            version: v1
            kubernetes.io/cluster-service: "true"
        spec:
          selector:
            matchLabels:
              k8s-app: fluentd-logging
          template:
            metadata:
              labels:
                k8s-app: fluentd-logging
                version: v1
                kubernetes.io/cluster-service: "true"
            spec:
              serviceAccount: fluentd
              serviceAccountName: fluentd
              tolerations:
              - key: node-role.kubernetes.io/master
                effect: NoSchedule
              containers:
              - name: fluentd
                image: fluent/fluentd-kubernetes-daemonset:v1.11.5-debian-elasticsearch7-amd64-1.0
                env:
                - name: TZ
                  value: Asia/Seoul
                - name: FLUENT_ELASTICSEARCH_HOST
                  value: "20.214.65.78"
                - name: FLUENT_ELASTICSEARCH_PORT
                  value: "9200"
                - name: FLUENT_ELASTICSEARCH_SCHEME
                  value: "http"
                - name: FLUENTD_SYSTEMD_CONF
                  value: disable
                - name: FLUENT_CONTAINER_TAIL_EXCLUDE_PATH
                  value: /var/log/containers/fluent*
                - name: FLUENT_CONTAINER_TAIL_PARSER_TYPE
                  value: /^(?<time>.+) (?<stream>stdout|stderr)( (?<logtag>.))? (?<log>.*)$/
                resources:
                  limits:
                    memory: 200Mi
                  requests:
                    cpu: 100m
                    memory: 200Mi
                volumeMounts:
                - name: varlog
                  mountPath: /var/log
                - name: dockercontainerlogdirectory
                  mountPath: /var/log/pods
                  readOnly: true
                - name: config
                  mountPath: /fluentd/etc/conf.d
              terminationGracePeriodSeconds: 30
              volumes:
              - name: varlog
                hostPath:
                  path: /var/log
              - name: dockercontainerlogdirectory
                hostPath:
                  path: /var/log/pods
              - name: config
                configMap:
                  name: fluentd-config
      2. kubernetes 배포

        k apply -f fluentd-daemonset.yaml

      3. daemonset 확인

        k get damonset fluentd -n kube-system

        efk(8).png

        k get pods -n kube-system | grep 'fluentd*'

        efk(9).png


      3. Application 설정 추가

      Fluentd를 데몬셋으로 설치하면 노드에 설치되므로 노드에 저장되는 container.log를 ElasticSearch로 이관해 줍니다.

      container.log에는 was에서 쌓는 stdout/stderr 로그들이 저장되는데요. ElasticSearch가 읽을 수 있도록 json으로 formatting 해주어야 합니다.

      이번에는 Spring에서 로그를 json으로 변환하는 방법에 대해 알아보겠습니다.

      1) logstash-logback-encoder 의존성 추가

      물론 logback.xml 설정에서 json 포맷을 만들어 줄 수 있습니다. 하지만 logstash-logback-encoder라는 좋은 라이브러리가 있어서 pom.xml에 의존성을 추가하였습니다.

      <!-- https://mvnrepository.com/artifact/net.logstash.logback/logstash-logback-encoder -->
      <dependency>
          <groupId>net.logstash.logback</groupId>
          <artifactId>logstash-logback-encoder</artifactId>
          <version>6.3</version>
      </dependency>

      2) logback-spring.xml 추가 (/src/main/resources 하위에 생성)

      logback.xml에 아래와 같이 encoder를 활용하도록 appender를 만들어줍니다.

      만약 local 환경에서는 EFK 환경이 아니므로 console log를 line으로 쌓고 싶다고 하면 profile별로 console 방식 또는 json 방식을 아래와 같이 선택하여 사용할 수 있습니다.

      <?xml version="1.0" encoding="UTF-8"?>
      <configuration>
      
          <!-- line format -->
          <property name="CONSOLE_LOG_PATTERN"
                    value="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) [%X{traceId}] [%X{userIdx}] [%X{nftLog}] %magenta(%-4relative) --- [ %thread{10} ] %cyan(%logger{20}) : %msg%n"/>
      
          <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
              <layout class="ch.qos.logback.classic.PatternLayout">
                  <Pattern>
                      ${CONSOLE_LOG_PATTERN}
                  </Pattern>
              </layout>
          </appender>
      
          <!-- json format -->
          <appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
              <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
          </appender>
      
          <root>
              <springProfile name="local">
                  <appender-ref ref="CONSOLE" />
              </springProfile>
      
              <springProfile name="dev,stg,live">
                  <appender-ref ref="JSON" />
              </springProfile>
          </root>
      
      </configuration>

      3) 설정 후 Spring Console Log 확인

      Spring console log가 json 형태로 수정되었는지 확인

      efk(10).png


      4. Kibana 설정

      이제 로그를 ElasticSearch에 잘 쌓았으니 Kibana로 확인할 일만 남았는데요.

      Kibana로 로그가 잘 쌓이고 있는지 확인하는 방법과 로그를 모니터링할 때 사용하는 몇가지 팁을 소개드리겠습니다.

      1. indices 확인

        • kibana 접속 → 좌측 상단 햄버거 아이콘 클릭 → Dev Tools 클릭 → GET _cat/indices 명령어 수행 → logstash-YYYY.MM.DD(오늘날짜) 확인

      2. index pattern 설정

        • Discover 접속 → index pattern이 logstash-*로 되어 있는지 확인

        • logstash-*로 설정하는 이유 : fluentd에서 logstash_format을 true로 할 경우 index_name 파라미터가 무시되며 logstash-YYYY.MM.DD 형식으로 자동 부여 됨

        • index명을 수정하고 싶을 경우 daemonset.yaml 파일에 환경변수를 추가하여 logstash_format을 false로 주입하고 index_name으로 변경하면 됨

      3. 로그 발생 유도

      4. Refresh 버튼 클릭하여 로그 데이터 조회

      5. 좌측 "Available Fileds"에서 원하는 필드를 추가

        • 추천하는 필드

          1. level - ERROR/WARN/INFO/DEBUG/...

          2. kubernetes.labels.app - app label 명

          3. message - 실제 로그

      6. 로그 이력 기간 조건 및 refresh 조건 추가

        • Refresh every 설정 할 경우 자동으로 n초/분 마다 refresh 할 수 있다. (주기가 빠른 경우 ES와 kibana에 부하를 줄 수 있으므로 주의!)


      참고. Kibana 활용 꿀팁

      1. 자주 사용하는 쿼리 저장

        1. kubernetes.labels.app: "kpop-api"로 검색하면 label에 "kpop-api"가 포함된 것을 조회할 수 있음 (kpop-api의 label은 "ifland-kpop-api"임)

        2. kubernetes.namespace_name: 환경(env)을 선택할 때 사용 (개발계-dev, 검수계-stg, 상용계-live)

        3. 보고 싶은 데이터 설정: 아래 예시에서는 시간(Time), 로그레벨(level), 로그내용(message), API call ID(dd.trace_id or traceId)로 구성

        4. 저장: 우측 상단 Save 버튼 클릭 → 제목과 설명 입력하고 저장 → 키바나 좌측 상단 세줄 아이콘 클릭 → Recently viewed에서 조회 가능 → 클릭 시 자동으로 쿼리 출력eks(11).pngeks(12).png

      2. Filter 기능 사용

        1. 필터 걸기 원하는 데이터에 마우스를 갖다 대면 Filter for value 아이콘 생성

        2. "+" 버튼 클릭 시 filter에 추가 되며 해당 값과 동일한 로그 데이터만 검색 됨

        3. 추가된 필터는 검색 쿼리 입력창과 데이터 사이 공간에서 볼 수 있으며 x 아이콘을 클릭하여 제거할 수 있다. (아래 이미지 참고)

          eks(13).png

          eks(14).png


      마치는 글

      이번 글에서는 k8s에서 로그를 통합 관리하기 위해 사용한 EFK 기술 스택에 대해 알아보았습니다.

      k8s 환경에서 log를 통합 관리 및 모니터링하기 위해 사용하는 기술 스택은 매우 다양한데요. (예: PLG, APM log trace 기능 등) 그 중에 상황과 선호에 맞게 사용하면 될 것 같습니다.

      저희 경우에는 ElasticSearch가 검색 기능을 위해 이미 설치되어 있어서 EFK가 가장 현실적으로 좋은 기술스택이라고 판단하였습니다.


      긴 글 읽어주셔서 감사합니다 : )

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      dragonpipe 님의 최신 블로그

      더보기

      DEVOTEE 추천 블로그

      동영상 기고하기