본문 바로가기

DevOps/쿠버네티스

쿠버네티스 로그 아키텍쳐 개요 및 방법



Logging Architecture

로그들은 에러를 디버깅하거나 클러스터의 status에 대해 자세히 알 수 있다. 앞에서 말한 목적때문에 대부분의 애플리케이션들은 standard output으로 로그파일을 남기고 있다.


그러나, 컨테이너 엔진 혹은 이전의 애플리케이션의 로깅 방법은 쿠버네티스에서의 로그 확인에 적절하지 않다. 사용자는 컨테이너 crash, pod 삭제, node VM이 죽는 경우에도 로그를 확인을 원할 수 있다.


그러므로, 로그는 모니터링 하고자 하는 쿠버네티스와는 무관한(별개의) storage와 lifecycle을 가져야한다. 

이러한 컨셉을 cluster-level-logging 이라고 부른다. cluster-level-logging각각의 분리된 저장소, 분석 솔루션, 대시보드가 필요하다. 쿠버네티스는 로그 데이터를 위한 storage solution을 제공해주지 않는다. 쿠버네티스에서는 여러 로그 처리 방법을 추천하므로, 이 중 필요한 것을 적절히 써야 한다.


Basic logging in Kubernetes

가장 기본적인 쿠버네티스 native log 확인 방법은 아래와 같이 실행하는 것이다.
apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox
    args: [/bin/sh, -c,
            'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']

$ kubectl create -f https://k8s.io/examples/debug/counter-pod.yaml
pod "counter" created
$ kubectl logs counter
0: Mon Jan  1 00:00:00 UTC 2001
1: Mon Jan  1 00:00:01 UTC 2001
2: Mon Jan  1 00:00:02 UTC 2001
...
counter라는 pod을 deploy하고 log를 확인.

Logging at the node level

Node level logging

컨테이너화 된 앱들은 stdout 혹은 stderr을 통해 어딘가로 로그를 떨어트리게 된다. 예를들어 Docker container engine은 두개의 stream을 logging driver로 redirect 시킨다.

만약 컨테이너가 재시작되면, kubelet은 이 로그를 삭제시키게 된다. 만약 pod이 node로 부터 삭제되면, 컨테이너 뿐만아니라 로그들도 사라지게 된다.

node의 storage가 다 차면 위험하므로, node-level-logging에서 log rotation은 매우 중요하다. 쿠버네티스에서는 log rotation에 대해 강제 하지 않지만, kube-up.sh 스크립트를 제공하여, 1시간 단위의 logrotate tool을 제공한다. 혹은 Container에서 제공하는 log rotation을 사용해도 된다. Docker에는 log-opt를 제공하는데, 이를 활용하여 log file이 10MB가 넘으면 log rotate 되도록 설정 가능하다.

Cluster-level logging architecture

쿠버네티스는 cluster-level logging에 대해 공식적인 솔루션을 제공하지는 않지만, 몇가지 추천하는 방법들이 있다.


방법1. 모든 node에서 동작하는 node-level logging agent 활용(바로가기)

방법2. 각각의 application pod에 로깅을 위한 sidecar 합치기(바로가기)

방법3. application이 특정 로깅 db에 직접 log를 푸시(바로가기)


각각의 방법에 대해 자세히 살펴보자.

방법 1. 모든 node에서 동작하는 node-level logging agent 활용
Using a node level logging agent

Logging agent는 각각의 node에 저장된 log file들에 대해서 수집하는 장치이다. 이런 방식은 각 node마다 한개의 logging agent pod이 띄어지기 때문에 가장 보편적이고 쉬운 접근방식일 것이다. 하지만, node-level logging은 오직 애플레케이션의 standard output과 standard error에 대해서만 확인 가능하다.

node-level logging agent에 대해 쿠버네티스는 표준을 정하지 않았지만 두가지 옵션을 추천한다.

1. Stack driver logging for use with GCP(Google Cloud Platform) 

두가지 방법 모두 각각의 node에 fluentd logging collection솔루션을 설치하는 방식.



방법 2. 각각의 application pod에 로깅을 위한 sidecar 합치기
Sidecar container with a streaming container
Side car container 방식은 로그를 확인하고자 하는 application의 다른 format에 대해 각각의 stdout, stderr stream을 생성한다. 이는, application의 로깅 특성에 맞추어서 분리 수집 가능하다. 

사이드카 컨테이너 로깅은 이미 node-level logging agent가 심어져 있는 경우에 효과적이다.
예를들자면 아래와 같이 두가지의 다른 format의 로그가 쌓인다고 가정해보자. 

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox
    args:
    - /bin/sh
    - -c
    - >
      i=0;
      while true;
      do
        echo "$i: $(date)" >> /var/log/1.log;
        echo "$(date) INFO $i" >> /var/log/2.log;
        i=$((i+1));
        sleep 1;
      done
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  volumes:
  - name: varlog
    emptyDir: {}
1.log, 2.log라고 하는 두개의 다른 format(혹은 목적이 다른)의 log가 쌓이고 있다. 만약 이 상태에서 node-level logging 수집을 한다면 두개의 로그가 짬뽕되므로 분석시 어려움을 겪을 수 있다. 이때는 두개의 로그 stream을 분리하여 node-level logging agent에 던져줘야한다.

아래와 같이 두개의 side car container를 목적에 따라 각각 다른 이름의 stdout log stream으로 분리한다면 더 효과적으로 로그를 분리, 확인 가능하다.

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox
    args:
    - /bin/sh
    - -c
    - >
      i=0;
      while true;
      do
        echo "$i: $(date)" >> /var/log/1.log;
        echo "$(date) INFO $i" >> /var/log/2.log;
        i=$((i+1));
        sleep 1;
      done
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  - name: count-log-1
    image: busybox
    args: [/bin/sh, -c, 'tail -n+1 -f /var/log/1.log']
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  - name: count-log-2
    image: busybox
    args: [/bin/sh, -c, 'tail -n+1 -f /var/log/2.log']
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  volumes:
  - name: varlog
    emptyDir: {}
상기 pod을 띄운다면 각각의 log를 아래와 같이 따로 로그가 남겨지는 것을 확인 할 수 있다.

$ kubectl logs counter count-log-1
0: Mon Jan  1 00:00:00 UTC 2001
1: Mon Jan  1 00:00:01 UTC 2001
2: Mon Jan  1 00:00:02 UTC 2001
...
$ kubectl logs counter count-log-2
Mon Jan  1 00:00:00 UTC 2001 INFO 0
Mon Jan  1 00:00:01 UTC 2001 INFO 1
Mon Jan  1 00:00:02 UTC 2001 INFO 2
...
이미 설치된 node-level agent는 별다른 설정없이 해당 log stream들을 잘 파싱할 것이다.

하지만, 이러한 방식은 file로 로그도 떨어뜨리고 stdout으로도 로그를 떨어트리므로 disk를 두배로 사용하게 될것이다.
만약 애플리케이션이 하나의 파일에 로그를 생성한다면,  사이드카 컨테이너 방식보다는 /dev/stdout 방식으로 로그를 찍는 것을 추천한다.

만약 애플리케이션이 로그파일 rotate기능이 없다면, 사이드카 컨테이너가 로그파일의 rotate기능을 제공해주기도 한다. 그러나, 여전히 쿠버네티스에서는 stdout 혹은 stderr을 바로 찍는 것을 추천한다.



방법 3. application이 특정 로깅 db에 직접 log를 푸시
Sidecar container with a logging agent
만약 node-level logging agent가 node에 flexible하게 작동하지 않는다면? 사이드카 컨테이너가 직접 로그를 로그 수집기에 전달하는 방식도 있다. 

사이드카 컨테이너 로그 에이전트 방식은 리소스 낭비가 심하다. 또한, kubectl logs 명령어를 사용해서 로그를 볼 수도 없다. 이미 사이드카 컨테이너가 사용하고 있기 때문이다!!
Stackdriver와 fluentd의 logging agent를 활용하는 방법이 아래에 나와있다.

(1) fluentd config 설정

apiVersion: v1
data:
  fluentd.conf: |
    

type tail format none path /var/log/1.log pos_file /var/log/1.log.pos tag count.format1

type tail format none path /var/log/2.log pos_file /var/log/2.log.pos tag count.format2 type google_cloud kind: ConfigMap metadata: name: fluentd-config


(2) pod의 sidecar 설정


apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox
    args:
    - /bin/sh
    - -c
    - >
      i=0;
      while true;
      do
        echo "$i: $(date)" >> /var/log/1.log;
        echo "$(date) INFO $i" >> /var/log/2.log;
        i=$((i+1));
        sleep 1;
      done
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  - name: count-agent
    image: k8s.gcr.io/fluentd-gcp:1.30
    env:
    - name: FLUENTD_ARGS
      value: -c /etc/fluentd-config/fluentd.conf
    volumeMounts:
    - name: varlog
      mountPath: /var/log
    - name: config-volume
      mountPath: /etc/fluentd-config
  volumes:
  - name: varlog
    emptyDir: {}
  - name: config-volume
    configMap:
      name: fluentd-config

상기와 같이 설정하게 되면 stackdriver interface에서 로그를 확인 가능하다.



그외 방법. application이 직접 로그 수집기에 로그 푸시

Exposing logs directly from the application

단순하게 application이 직접 로그를 푸시하는 경우가 있을 것이다. 그러나 쿠버네티스 외부의 로깅 api를 application 내부에서 써야한다는 점이 있다.



출처 : Kubernetes logging architecture(https://kubernetes.io/docs/concepts/cluster-administration/logging/)