Cloud & Virtualization

GCP의 Kubernetes Cluster의 Logging/Monitoring에 대한 분석

silentbrain 2018. 7. 20. 17:40

Google Cloud Platform의 Kubernetes Cluster에서 구현된 Logging 구조를 들여다봤다.

다른 컨테이너 기반 아키텍쳐에서도 참고할 여지가 있으리라




[분석 방안]


1. Kubernetes Cluster를 생성하고 아무 서비스나 띄운다.


2. 생성된 클러스터의 GCE 인스턴스에 SSH로 접속해서 구동중인 Docker 컨테이너를 sudo docker ps 명령어로 확인해 보았다. 너무 길어서 의미 있는 정보만 표로 뽑았다.


테스트용으로 올린 예제 서비스용 컨테이너 외에도 여러가지 컨테이너들(image 명이 *gcr.io인 것들)이 보인다. 

그중 주의 깊게 보고자 하는건 노란색 2가지,  Fluentd와 Prometheus 컨테이너이다.



3. sudo docker inspect [container_id] 명령어로 2개 컨테이너의 주요 설정값을 확인 해봤다.





[1. Fluentd]


Fluentd 컨테이너의 실행 인자값은  run.sh를 $FLUENTD_ARGS로 실행한다.

"Args": [

            "-c",

            "/run.sh $FLUENTD_ARGS"

        ], 

run.sh는 "/usr/local/bin/fluentd $@"의  bash 쉘로 $FLUENTD_ARGS로 실행인자를 넘겨 받아 Fluentd를 실행하며,  컨테이너  환경 변수 "FLUENTD_ARGS=--no-supervisor -q" 설정되어 있다.  fluentd의 -q는 출력 없이(quiet) 실행하겠다는 것


--no-supervisor 옵션을 잠시 살펴보면, Fluentd는 구동 시 기본적으로 supervisor와 worker 2개의 프로세스가 생성된다. 

Supervisor의 역할은 몇가지 시그널들을 보내서  Worker process의 라이프 사이클을 제어한다. (https://docs.fluentd.org/v1.0/articles/signals#process-model) 즉, 이 supervisor 프로세스는 사용하지 않고 fluentd를 구동하고 있다. (fluentd 가이드에는 runit과 같은 다른 관리 툴을 사용할 경우 유용하다는데??)



그리고 fluentd 컨테이너가 구동중인 Host GCE VM의 폴더를 다음과 같이 bind로 마운트 하고 있다.

"Binds": [

                "/var/log:/var/log",

                "/var/lib/docker/containers:/var/lib/docker/containers:ro",

                "/usr/lib64:/host/lib:ro",

                "/var/lib/kubelet/pods/블라블라_해쉬값/volumes/kubernetes.io~configmap/config-volume:/etc/fluent/config.d:ro",

                "/var/lib/kubelet/pods/블라블라 해쉬값/volumes/kubernetes.io~secret/fluentd-gcp-token-9rtr4:/var/run/secrets/kubernetes.io/serviceaccount:ro",

                "/var/lib/kubelet/pods/블라블라_해쉬값/etc-hosts:/etc/hosts",

                "/var/lib/kubelet/pods/블라블라_해쉬값/containers/fluentd-gcp/8e11b2f6:/dev/termination-log"

            ], 



핵심으로 보이는 것은 붉은 라인 2개로 /var/log 폴더를 마운트 하고 있는 것은 상식적으로 당연한 듯 하고, 

/var/lib/kubelet/pods/블라블라_해쉬값/volumes/kubernetes.io~configmap/config-volume 이곳에 구동 중인 fluentd의 설정 파일이 위치해 있는 것!

 이 폴더에는 총 4개 파일의 심볼릭 링크가 있는데, 4개 파일 모두 Fluentd의 In/out/filter plugin 셋팅을 포함하고 있다.

lrwxrwxrwx 1 root root   28 Jun 14 06:34 containers.input.conf -> ..data/containers.input.conf

lrwxrwxrwx 1 root root   22 Jun 14 06:34 monitoring.conf -> ..data/monitoring.conf

lrwxrwxrwx 1 root root   18 Jun 14 06:34 output.conf -> ..data/output.conf

lrwxrwxrwx 1 root root   24 Jun 14 06:34 system.input.conf -> ..data/system.input.conf 



설정 파일들의 내용을 대강 살펴본다. 순서대로 따라가면서 처리되는 절차를 이해 가능하다.

system.input.conf  파일

input plugin : tail

(이것들은 해당 파일명을 tag로 단다!)

/var/log/salt/minion

/var/log/startupscript.log

/var/log/docker.log

/var/log/etcd.log

/var/log/kubelet.log

/var/log/kube-proxy.log

/var/log/kube-apiserver.log

/var/log/kube-controller-manager.log

/var/log/kube-scheduler.log

/var/log/rescheduler.log

/var/log/glbc.log

/var/log/cluster-autoscaler.log

input plugin : systemd

(서비스와 밀접한 systemd-journal 정보를 필터로 캐와서 서비스명 tag 달음)

filters [{ "_SYSTEMD_UNIT": "docker.service" }]

filters [{ "_SYSTEMD_UNIT": "kubelet.service" }]

filters [{ "_SYSTEMD_UNIT": "node-problem-detector.service" }]




 containers.input.conf

 <source>

  type tail

  format json

  time_key time

  path /var/log/containers/*.log   

  # /var/log/container/*.log파일들은 host의 /var/log/pods/블라블라_해쉬값/서비스명.log 이하 로그파일들로 심볼릭 링크가 걸려 있고,

  # /var/log/pods/블라블라_해쉬값/서비스명.log는 docker의 json 로그파일로 또 심볼릭 링크가 걸려있다!

  # 이 최종 링크 파일이 바로 docker inspect [CONTAINER_ID]에서 확인되는 컨테이너의 JSON 로그파일이다!! 일단 핵심 의문 해결!

  # 컨테이너의 로그 수집 방식은 http://silentbrain.tistory.com/20  참고

  pos_file /var/log/gcp-containers.log.pos

  time_format %Y-%m-%dT%H:%M:%S.%N%Z

  tag reform.*

  read_from_head true

</source>


# 1. 위 파일에서 생성되는 로그들은 이 parser/formatter를 타고!!

<filter reform.**>

  type parser

  format /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<log>.*)/

  reserve_data true

  suppress_parse_error_log true

  key_name log

</filter>


# 2. record_reformer 플러그인에서 tag를 raw.kubernetes.블라블라로 바꾼다

<match reform.**>

  type record_reformer

  enable_ruby true

  tag raw.kubernetes.${tag_suffix[4].split('-')[0..-2].join('-')}

</match>


# Detect exceptions in the log output and forward them as one log entry.

# 3. detect_exceptions 플러그인은 GCP 전용 Output Plugin(https://github.com/GoogleCloudPlatform/fluent-plugin-detect-exceptions)

# 위로 부터 넘겨 받은 로그 중 Stacktrace exception에 해당하는 로그가 있으면, 이를 단일 라인으로 출력함

해당 없으면, 다른 것과 동일하게 처리를 계속한다. 참고로 여기서 tag 앞에 붙은 raw를 때버린다.

<match raw.kubernetes.**>

  @type detect_exceptions


  remove_tag_prefix raw

  message log

  stream stream

  multiline_flush_interval 5

  max_bytes 500000

  max_lines 1000

</match>




 monitoring.conf 

# Prometheus monitoring

https://docs.fluentd.org/v0.12/articles/monitoring-prometheus 문서에서 Step3에 해당하는 부분이다.

# 아래 output.conf 파일의  <match process_start> @type prometheus 부터 봐야 이해가 가능하다. 

# 이 구절로 인해 Process start 메트릭에 HTTP 접근이 가능해진다.

# 실제로 Fluentd 컨테이너에 들어가서 curl http://localhost:31337/metrics 날려 해당 메트릭의 확인이 가능하다.

# 그런데 이거말고도 여러게 보인다?(어째서? ㅡ.ㅡ??)

<source>

  @type prometheus

  port 31337

</source>


<source>

  @type prometheus_monitor

</source>


# This source is used to acquire approximate process start timestamp,

# which purpose is explained before the corresponding output plugin.

# exec input plugin은 직접 명령을 실행해서 결과값을 Record로 얻어오는 역할로,

# date 명령어로 시간을 얻어서 process_start tag 셋팅

# output plugin이 응답하기 전 process 시작 시간을 측정하기 위한 목적이다! 

<source>

  @type exec

  command /bin/sh -c 'date +%s'

  tag process_start

  time_format %Y-%m-%d %H:%M:%S

  keys process_start_timestamp

</source>


# This filter is used to convert process start timestamp to integer value for correct ingestion in the prometheus output plugin.

# 위의 로그 내용을 Prometheus output plugin에 정상적으로 집어 넣기 위해 Replace

<filter process_start>

  @type record_transformer

  enable_ruby true

  auto_typecast true

  <record>

    process_start_timestamp ${record["process_start_timestamp"].to_i}

  </record>

</filter>




 output.conf

# This match is placed before the all-matching output to provide metric

# exporter with a process start timestamp for correct exporting of

# cumulative metrics to Stackdriver.

# 앞서 monitoring.conf 에서 넘겨 받은 프로세스 스타트 시간을 Prometheus output plugin으로 보내서 record로서 기록한다.

# prometheus output plugin의 사용은 https://docs.fluentd.org/v0.12/articles/monitoring-prometheus 에서 STEP2 참고)

<match process_start>

  @type prometheus


  <metric>

    type gauge

    name process_start_time_seconds

    desc Timestamp of the process start in seconds

    key process_start_timestamp

  </metric>

</match>


# This filter allows to count the number of log entries read by fluentd

# before they are processed by the output plugin. This in turn allows to

# monitor the number of log entries that were read but never sent, e.g.

# because of liveness probe removing buffer.

# Prometheus Filter plugin을 사용해서 output plugin으로 처리되기 직전의 로그에 대한 

# 모니터링용 카운터 metric을 생성(https://docs.fluentd.org/v0.12/articles/monitoring-prometheus 에서 STEP1 참고) , 

# 다만, 위 주석 내용을 보면 보내지는 않는 다고 한다.(????)

# 그런데 이 filter에 해당하는 대응하는 <match **>@type prometheus 따위가 없다?? 그런데 fluentd 컨테이너 들어거가서 

# curl http://localhost:31337/metrics 날려 보면 이 메트릭 보인다. (왜? ㅡㅡ??????)

# Kubernetes의 liveness probe가 버퍼를 제거하기 때문이라는데 이게 또 뭔소리냐; 일단 몰라도 로그 전송 대세에는 크게 상관 없다;;

<filter **>

  @type prometheus

  <metric>

    type counter

    name logging_entry_count

    desc Total number of log entries generated by either application containers or system components

  </metric>

</filter>


# TODO(instrumentation): Reconsider this workaround later.

# Trim the entries which exceed slightly less than 100KB, to avoid

# dropping them. It is a necessity, because Stackdriver only supports

# entries that are up to 100KB in size.

# Stackdriver가 수신가능한 메세지 최대 크기가 100KB이므로, 

# 컨테이너들이 뿜은 로그들 중 이 크기를 넘어가면 내용물을 짜르고 'Trimmed'를 붙인다.

# 해당 플러그인은 내용물을 replace용,  https://docs.fluentd.org/v0.12/articles/filter_record_transformer 참고

<filter kubernetes.**>

  @type record_transformer

  enable_ruby true

  <record>

    log ${record['log'].length > 100000 ? "[Trimmed]#{record['log'][0..100000]}..." : record['log']}

  </record>

</filter>


# We use 2 output stanzas - one to handle the container logs and one to handle

# the node daemon logs, the latter of which explicitly sends its logs to the

# compute.googleapis.com service rather than container.googleapis.com to keep

# them separate since most users don't care about the node logs.

# 위에서 부터 따라왔다면 로그는 아래 2종류로 처리되고 있음을 알 수 있다.

# 1. "kubernetes.*" 태그를 달고 처리중인 container.input.conf에 정의된  컨테이너 어플리케이션의 로그들

# 2. node의 daemon 로그들(system.input.conf에 정의된 kubernetes node daemon 로그들

# 여기서 아래 match로 1번이 먼저 전송되고

<match kubernetes.**>


  # google_cloud는 stackdriver log 전송용 plugin으로 

  # 별도 설정없이 알아서 서비스 어카운트 읽어서 알아서 보내주나보다. 자세한 정보가 없다. ==

  # 참고 https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud

  @type google_cloud


  # Try to detect JSON formatted log entries.

  detect_json true

  # Collect metrics in Prometheus registry about plugin activity.

  enable_monitoring true

  monitoring_type prometheus

  # Set the buffer type to file to improve the reliability and reduce the memory consumption

  buffer_type file

  buffer_path /var/log/fluentd-buffers/kubernetes.containers.buffer

  # Set queue_full action to block because we want to pause gracefully

  # in case of the off-the-limits load instead of throwing an exception

  buffer_queue_full_action block

  # Set the chunk limit conservatively to avoid exceeding the recommended

  # chunk size of 5MB per write request.

  buffer_chunk_limit 1M

  # Cap the combined memory usage of this buffer and the one below to

  # 1MiB/chunk * (6 + 2) chunks = 8 MiB

  buffer_queue_limit 6

  # Never wait more than 5 seconds before flushing logs in the non-error case.

  flush_interval 5s

  # Never wait longer than 30 seconds between retries.

  max_retry_wait 30

  # Disable the limit on the number of retries (retry forever).

  disable_retry_limit

  # Use multiple threads for processing.

  num_threads 2

</match>


# Keep a smaller buffer here since these logs are less important than the user's container logs.

# 남은 나머지 로그들(2번에 해당) 전송된다.

# 참고로 fluentd의 output plugin은 적용 순서에 따라 방화벽 정책 처럼 first match로 처리되므로, 

# 다수의 목적지로 보내고자 한다면 다른 type을 써야 한다.(참고 https://docs.fluentd.org/v0.12/articles/out_copy)

<match **>

  @type google_cloud


  detect_json true

  enable_monitoring true

  monitoring_type prometheus

  detect_subservice false

  buffer_type file

  buffer_path /var/log/fluentd-buffers/kubernetes.system.buffer

  buffer_queue_full_action block

  buffer_chunk_limit 1M

  buffer_queue_limit 2

  flush_interval 5s

  max_retry_wait 30

  disable_retry_limit

  num_threads 2

</match>



대략적인 플로에 대한 이해가 된다.  

다음은 Prometheus 컨테이너를 까볼 예정