DevOps

[Kubernetes] EFK 적용 과정 (Elasticsearch, Fluentd, Kibana) - 1

네른 2022. 5. 10. 19:53

보통 로깅을 위한 아키텍쳐라고 하면 ELK(Elasticsearch, Logstash, Kibana)의 구조를 생각하게 된다.

가장 유명한? 널리 알려진? 로깅/분석 스택이기 때문이다.

 

 Kubernetes 환경에서도, Kubernetes 자체의 로그 뿐 아니라 내부의 애플리케이션에 대한 로깅과 매니징이 필요하다.

그래서 사내에서 동작중인 앱에 대해 ELK 스택을 적용하고자 하였으나, CNCF stack을 찾아보니 Logstash보다는 Fluentd를 권장하고 있어 이를 사용하게 되었다.

 

Fluentd에 대해 간단히 설명하자면 다음과 같다

 - 오픈소스 기반의 로그 수집기

 - HTTP, TCP, File 등 다양한 데이터 소스로부터 로그를 수집해 원하는 곳으로 전달(elasticseardch, s3)할 수 있다.

 - Logstash와 유사한점이 많으며, 성능이 비교적 좋다는 평가를 받음.

 - 로그에 설정된 태그에 따라 이벤트를 라우팅해서 정리

 - 그 외에도, docker에 fluentd용 로깅 드라이버가 기본적으로 포함되어있기에 별다른 설정없이 바로 동작이 가능함.

    예를들어, 애플리케이션의 로그 중 stdout에 찍히는 로그들을 수집할 때 logstash는 이를 파일에 기록하고 filebeat로 수집하여 logstash로 전달하지만 / fluentd는 stdout에서 곧바로 fluentd로 전송할 수 있음

 - logstash와 마찬가지로 json, nginx, apache 등 다양한 기본 파서를 제공하므로 사용이 용이함

 

전반적으로 logstash와 매우 유사하나 logstash보다 조금 더 kube환경에 맞추어진 로그 수집기라고 보면 된다.

 


아무튼 그래서 EFK를 적용할 때, 다음과 같은 구조로 구성하였다.

 - 기본적으로는 Sidecar 형태로 로깅을 진행

    : Side-car? 본 목적의 컨테이너 이외에, 별도의 목적을 지닌 컨테이너를 함께 붙여 사용하는 패턴

 - 여기서는 로깅용 컨테이너와 애플리케이션 컨테이너를 별도로 생성하고 하나의 Pod로 생성하는 것.

 - 이렇게 되면 애플리케이션 컨테이너의 수정 없이 사이드카 컨테이너만 수정하면 된다.

 

사용중인 애플리케이션들 중 일부는 파일로, 일부는 HTTP로 로그를 전송하도록 하였다.

이 때, 파일로 로그를 수집하는 fluentd들은 사이드카 형태로 구성하였으나

HTTP 통신을 통해 로그를 수집하는 경우에는 사이드카 형태가 아니라, 별도의 fluentd Pod를 구성하였다.

 - 자세한 이유는 밝힐 수 없지만, 클라이언트 측에서 로그를 fluentd로 전송하는 방법을 고민하다가 이렇게 구성하였다.

 

Fluentd는 수집한 로그를 Elasticsearch로 전달하게되며, 이 로그들은 Kibana에 전달되어 시각화되는것이 최종 목표였다.

 


Fluentd conf파일 구성

 fluentd는 config 파일에 적힌 tag 기반 rule에 따라 로그를 수집하고 분류하며 후처리한다.

간단하게 주요 tag에 대해 정리하면 다음과 같다. 마찬가지로 나중에 까먹을까봐 주로 썼던것들만 정리하고자 함

 

 - <source> : input source를 의미하며 @type 문구를 사용하여 input plugin(none, in_tail, apache, nginx 등)를 지정할 수 있음

                 source는 크게 세 정보를 받는데, tag / time / record로 나뉘게 됨.

                 tag는 .을 이용해 구분되는 스트링으로, fluentd의 내부 라우팅 엔진에서 해당 이벤트가 어디로 라우팅될지를 결정하는 데 사용됨

                 time은 주로 unix 포맷이며 input plugin에 따라 차이가 있음

                 record는 JSON 오브젝트로 구성되며, 이 외의 주요 내용들을 포함하고 있음

   - 주로 사용한 플러그인중에는 in_tail이라는 플러그인이 있는데, 주로 사용하는 tail -F와 유사하게 파일을 watch하고 변경내용이 있으면 읽어오는 플러그인.

 

그래서 이 <source>를 어떻게 쓰냐하면,

    <source>
      @type tail
      path /var/tmp/test.log, /var/tmp/test_err.log
      pos_file /var/tmp/test.log.pos
      tag logging.test
      read_from_head true
      exclude_path /var/log/*.tar
      path_key tailed_path
      rotate_wait 5
      follow_inodes true
      <parse>
       @type nginx
      </parse>
    </source>
    
    
    
      <parse>
        @type multiline
        format_firstline /\d{6} \d{1,2}:\d{1,2}:\d{1,2}/
        format1 /^(?<time>\d{6} \d{1,2}:\d{1,2}:\d{1,2})\s*(?<Id>\d{1,5})\s*(?<command>\w{0,15})\s*(?<argument>.*)/
        time_key time
        time_format %y%m%d %H:%M:%S
        timezone +09:00
      </parse>
      
      
      
    <source>
      @type http
      port 24224
      bind 0.0.0.0
      keepalive_timeout 10s
      cors_allow_origins ["*"]
    </source>

이처럼 config 파일 내에 작성하게 된다.

한 줄씩 설명하자면

 - @type tail   : in_tail 플러그인을 사용해 파일을 읽겠다.

 - path ~~~   : 읽을 파일들의 경로는 다음과 같다

 - pos_file ~~ : 로그 파일들을 어디까지 읽었는지 position을 기록해둘 파일. 수집할 파일이 여러개여도 하나의 position 파일에 기록하므로 하나만 있어도 됨

 - tag logging.tag : 태그

 - read_from_head : pos_file에 기록된 지점 혹은 파일의 처음부터 파일을 이어서 읽어나가겠다는 옵션. fluentd는 기본적으로 파일의 tail의 로그를 읽도록 설정되어있다.

   이 옵션을 true로 하지 않으면 파일이 생성됨과 동시에 작성된 로그의 경우 정상적으로 기록되지 않는 경우가 발생한다.

   추후 기술할 log rotating 관련해서도 꼭 필요한 옵션. 찾아보니 fluentd 1.14.3부터는 in_tail의 경우에는 default가 true로 바뀐듯 보인다.

 - exclude_path : 말 그대로 로깅에서 제외할 파일

 - path_key : 로그를 기록할 때, 해당 로그가 어느 파일에 있던 것인지 기록하기 위한 옵션으로, path_key 뒤에 오는 이름으로 로그의 key값이 설정된다.

      ex) path_key tailed_path 인 경우 -- {"tailed_path":"/path/to/access.log"} 이런식.

 - rotate_wait 5 : 로그 로테이팅 적용 시 필요한 옵션으로, 뒤에 설정된 숫자(초) 만큼 로테이팅 이전의 파일을 마저 바라보고 있는 것(유예 시간). 즉 로그의 손실을 방지하기 위한 옵션

 - follow_inodes true : 로그 로테이션 설정시에, 로그의 내용 중복을 방지하기 위한 옵션.

 - <parse> @type nginx <parse>  :: 새로운 태그! parse 태그는 해당 로그의 포맷을 지정하기 위함.

     이 경우에는 해당 로그의 형태가 nginx의 형태를 띄고 있으니 알아서 파싱해달라는 뜻. 이 ㅗ이에도 json, regexp, none 등의 다양한 type을 사용할 수 있음

 

밑에 별도로 쓰인 parse 태그(multiline)의 경우, 위의 nginx처럼 플러그인이 없는 경우에 직접 라인을 파싱하는 방법.

 - @type multiline : multi-line으로 구성된 로그 파일(자바 로그같이 한 줄이 multi-line인 로그)을 파싱하고자 함

 - format_firtline : multi-line 로그 중 첫 줄을 확인하기 위한 regex

 - formatN : multi-line 로그의 구성이 어떻게 되어있는지 확인하는 regex. 위 예제에서는 모든 multiline의 regex가 동일해서 format1로 끝나지만, 여러 줄의 구성이 다른 경우에는 format2, 3 등을 직접 지정해주어야 함

 - time_key : 시간 값을 별도로 지정할 때 path_key처럼 사용

 - time_format : 시간 값을 파싱하는 regex

 - timezone : 별도로 timezone을 지정해주고자 할 때 사용

 

이 multiline이 생각보다 자주 사용되어서 별도로 정리해두었음. 자세한 예제는 fluentd 공식 도큐에 잘 설명되어있으니 한번 찾아보면 좋을 것으로 생각됨.

 

마지막에 쓰인 http 플러그인은 앞서 설명한 http 요청을 받아 로그를 저장하는 부분.

 - cors_allow_origins : CORS 에러를 방지하기 위해 설정하는 옵션.

 - bind / port : bind가 0.0.0.0 이면 해당 pod 의 24224 port로 들어오는 모든 로그를 수집하겠다는 것.

 


다음으로는, filter 태그가 있다.

    <filter logging.test>
      @type record_transformer
      <record>
        hostname ${hostname}
        log_path ${record["tailed_path"]}
      </record>
    </filter>
    
    
      @type grep
      <record>
        key message
        pattern /cool/
      </record>

filter 태그는 위의 source가 input을 받아 라우팅 할 때 가장 먼저 받는다고 보면 됨.

데이터 필터링이 주 목적이며, record_transformer, filter_stdout, grep 등 다양한 필터 타입이 있음

<filter 태그명>의 구조로, 적혀있는 태그에 해당되는 이벤트는 모두 해당 필터로 전송됨.

 

 - @type : grep은 특정 조건을 만족하는 이벤트만 골라낼 때 사용 / record_transformer는 이름 그대로 record 값을 수정할 때 사용 등등

 - <record> : record_transformer에서 사용하는 태그로, 데이터의 특정 필드를 수정하거나 추가하는 것. 위 예제에서는 hostname과 log_path를 추가하기 위해 사용하였다.

 - key, pattern : grep 플러그인에서 사용하는데, key에 적힌 필드에서 pattern에 해당되는 라인만 수집하고자 함. 위 예제에서는 message 필드에 cool이라는 단어가 포함된 '이벤트만' 수집하기 위함!

 - 이 외에도 exclude 등의 설정값 들이 더 있음.


마지막으로, match 필드이다

    <match logging.test>
      @type elasticsearch
      host 호스트 IP
      logstash_format true
      logstash_prefix backend
    </match>

여기서는 elasticsearch를 사용할 것이므로 해당 플러그인을 사용.

 - host : 연결할 elasticsearch ip  (실제 적용에서는 elasticsearch도 하나의 pod로 동작하므로 kube의 dns를 이용하였음)

 - logstash_format : logstash의 로깅 형식을 따라감. prefix-year.month.day 형태의 index가 지정되고 기록됨

 - logstash_prefix : 앞서 이야하기한 index명 중 prefix에 지정될 이름

 - user/password : elasticsearch에 login할 유저 계정 정보

 - path : 여기에는 쓰여있지않지만, 혹 elasticsearch의 rest api endpoint를 별도로 지정한 경우 이를 이용.

 

 

이렇게 tag들을 config에 설정하고 구동하면, 로그를 읽고 elasticsearch에 전송하는 fluentd를 구성할 수 있다.

물론 이 외에도 태그가 더 있고 필요한 옵션이 더 있으니

https://docs.fluentd.org/   도큐를 꼭 확인.

 

 

 

이 외에 확인할 점은

 - fluentd의 conf 파일인 flunet.conf 는 -c 옵션으로 별도로 지정해주어야 함​

 

fluentd를 실제로 helm을 이용해 deploy 한 내용은 추후에 따로 적어올리는것으로!

elasticsearch와 kibana는 그때 함께 정리하자.

사실 별도로 정리할건 없고 일부 environment값 정도만 지정하면 되므로!