K8s logging
Kubernetes 클러스터의 각종 컴포넌트, 그리고 클러스터내 파드등 모든 관련된 구성요소에 대한 로깅과 관련된 정보를 정리한 글입니다. 쿠버네티스 공식 문서의 로깅 아키텍처 글을 토대로 실제 사용 가능한 소프트웨어의 예시와 매칭시켜서 정리하였습니다.

로깅 아키텍처
컨테이너화 된 애플리케이션의 가장 쉽고, 널리 적용된 로깅 방법은 표준 출력 및 표준 에러 스트림으로 로그를 내보내는 것입니다.
stdio
라이브러리에 존재하는 stdout, stderr 가 그것에 해당하고. node.js 였다면 console.log
, console.error
에 해당하는 것들 입니다.
하지만 컨테이너 엔진이나, 런타임(node, python, etc..)에서 제공하는 로깅은 완전한 솔루션이라고 부르기에 충분하지 않습니다. 예를들어, 컨테이너가 충돌나거나, 파드가 축출되거나, 노드가 죽어버렸을 때 애플리케이션의 로그에 접근해서 트러블 슈팅을 하고 싶다고 생각해보세요. 표준 출력 / 표준 에러 스트림으로 내보낸 로깅으로는 부족하거나, 휘발되버릴 수 있습니다.
쿠버네티스 클러스터에서 로그는 노드, 파드(컨테이너)와 독립적인 별도의 로그를 저장할 스토리지와 라이프 사이클을 가져야 하고 이 개념을 클러스터 레벨 로깅이라고 부릅니다.
클러스터 수준 로깅 아키텍처는 로그를 저장, 분석 및 쿼리하기 위한 별도의 백엔드가 필요합니다. (ELK 스택 + EKS EBS storage 같이). 쿠버네티스는 로그 데이터를 위한 기본 스토리지 솔루션을 제공하지 않지만, 쿠버네티스와 통합되는 다양한 로깅 솔루션이 있습니다.
파드 및 컨테이너 로그
표준 출력 및 표준 에러로 텍스트를 내보내는 애플리케이션을 파드로 띄운다고 생각해봅시다. 표준 출력 및 표준 에러(로그)는 kubectl logs {podNmae}
을 통해서 확인이 가능합니다.

컨테이너 런타임은 생성된 모든 로그(출력)을 처리하고, stdout, stderr 스트림으로 리다이렉션 합니다. 컨테이너 런타임 별로 다른 방식으로 구현은 되지만 kubelete 과의 통합은 CRI 로깅 포맷으로 표준화 되어있습니다. (CRI = Container Runtime Interface)
여기서 Kubelet 이 파드 및 컨테이너 메타 데이터를 사용해서 모든 컨테이너 로그에 대한 심볼릭 링크를 아래와 같이 생성하게 됩니다.
/var/log/containers/<pod_name>_<pod_namespace>_<container_name>-<container_id>.log

위와 같이 /var/log/pods/monitoring_xxxxx/container_name/*.log
와 /var/log/containers/foobarbaz.log
와 연결이 됩니다.
위와 같이 로그의 위치/네이밍이 표준화 되어서 kubectl logs
를 하는경우 내부적으로 위 파일들을 찾아서 클라이언트에 보여주게 되는 것! 그리고 파드(컨테이너)내에서 만드는 로그는 모두 Node 에 쓰게 되는 것과 마찬가지.
CRI(Container Runtime Interface) 에서 정의한 컨테이너의 로그 위치는 /var/log/containers
다.
/var/log/containers/파드이름_파드네임스페이스이름_컨테이너이름컨테이너ID.log
를
/var/log/pods/파드UID/컨테이너이름/0.log
와 심볼릭 링크를 Kubelet 이 걸어준다.
시스템 컴포넌트 로그
시스템 컴포넌트에는 일반적으로 컨테이너에서 실행되는 구성 요소와, 컨테이너 실행에 직접 관여하는 구성요소 두가지 유형이 있습니다.
Kubelet과 컨테이너 런타임은 컨테이너에서 실행되는게 아닙니다. Kubelet 은 컨테이너를 실행시키는 역할을 합니다. (그리고 컨테이너들은 파드로 그룹화 되고)
쿠버네티스 스케쥴러, 컨트롤러 매니저 API 서버는 파드(보통 static Pod) 안에서 실행됩니다. etcd 컴포넌트는 컨트롤 플레인에서 실행되고, 정적파드로 실행됩니다. 클러스터에서 kube-proxy를 쓰는 경우에는 일반적으로 DaemonSet 으로 실행됩니다.
시스템 컴포넌트 로그 위치
Kubelet 과 컨테이너 런타임은 노드의 운영체제에 따라서 다른 위치에 로그를 저장합니다.
Systemd 를 쓰는 리눅스 노드의 경우 kubelet 과 컨테이너 런타임(우리 케이스의 경우 containerd)은 systemd 서비스로 등록되어있기 때문에 journalctl
을 사용해서 systemd journal 을 읽을 수 있습니다. (journalctl -u kubelet
)
또한 Kubelet 은 컨테이너 런타임이 로그를 /var/log/pods
내에서 쓰도록 강제합니다.
또한 파드에서 실행되는 쿠버네티스 클러스터 컴포넌트의 경우 (쿠버네티스 스케쥴러, 컨트롤러 매니저, API 서버 등) 기본 로깅 정책을 우회해서 /var/log
내 파일에 기록합니다.(컨테이너/파드 기 때문에 systemd 저널에 당연히 기록되지도 않고)
이런 쿠버네티스 클러스터 컴포넌트의 경우 컨트롤 플레인에서 파드 형태로, (특히나 static pod) 떠있다. (static pod 는 kubelet 에서 bootstrapping 하는 파드고, 우리가 직접 생성하지 않아도 kubelet 이 직접 만듦. 만약에 이 파드가 모든 노드에 필요하다고 하면 DaemonSet 으로 사용하게 됨)
EKS 환경에서 컨트롤 플레인 내의 이런 static pod 의 로그를 보려면 CloudWatch 를 통해 봐야하는데. 그 방법은 아래 링크
https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/control-plane-logs.html
클러스터 레벨 로깅 아키텍처
쿠버네티스는 클러스터 레벨 로깅을 위한 네이티브 솔루션을 제공하지는 않지만, 우리가 고려해볼만한 몇가지 접근법은 존재합니다.
- 노드 레벨 로깅 에이전트를 모든 노드에서 돌리는 것 (e.g
Filebeat
,Fluentd
) - 애플리케이션 파드 내에 로깅을 위한 전용 사이드카를 포함해서 돌리는 것
- 애플리케이션 내에서 백엔드로 직접 로그를 푸시 하는 것 (e.g application -Direct-> DataDog)
노드 로깅 에이전트 사용

각 노드에 노드 레벨 로깅 에이전트를 포함시켜서 클러스터 레벨 로깅을 구현 할 수 있습니다. 로깅 에이전트는 로그를 export 하거나, 로그를 백엔드(Elasticsearch, S3, etc…)로 푸시하는 전용 도구입니다. 일반적으로 로깅 에이전트는 해당 노드의 모든 애플리케이션 컨테이너에서 로그 파일이 있는 디렉터리에 (/var/log/containers
혹은 /var/log/pods
) 접근 할 수 있는 컨테이너입니다. 로깅 에이전트는 모든 노드에서 실행되어야 하기 때문에 에이전트를 DaemonSet
으로 동작시키는것을 추천합니다.
노드 레벨 로깅의 경우 노드 별 하나의 에이전트만 생성하고, 노드에 실행되는 애플리케이션에 대한 변경은 필요하지 않습니다. 그도 그럴것이. 애플리케이션의 stdout, stderr 스트림은 노드의 /var/log/containers
위치로 로그가 기록되고 (로그 로테이팅은 뭐 노드레벨에서 하거나 사용자가 설정하거나, 이거는 사용자의 책임.) 이 에이전트는 해당 위치를 바라보고 로그를 로깅 백엔드로 푸시하면 되기 때문입니다.
노드 로깅 에이전트 예시
- Fluentd
- Filebeat
- Promtail
로깅 에이전트 + 사이드카 컨테이너 (비추?)

사이드카 컨테이너가 자체 stdout
, stderr
스트림을 기록하도록 하면, 각 노드에서 이미 실행중인 kubelet 과 노드 로깅 에이전트를 활용 할 수 있다. 사이드카 컨테이너는 파일, 소켓, 또는 journald 에서 로그를 읽는다. 이후에 사이드카 컨테이너는 자체 stdout, stderr 스트림으로 로그를 출력한다.
이 방법을 쓰면 애플리케이션의 다른 부분에서 여러 로그 스트림을 분리 할 수 있다. (뭐 생각해보자면 .. 백엔드 애플리케이션의 어떤 엔드포인트? 경로? API 별 로깅을 분리 할 수 있지 않을까 하는 ..). 보통 로그를 리다이렉션 하는 로직은 최소화 되어있기 때문에 사이드카 컨테이너가 붙는것에 대해서 대부분 케이스에서 심각한 오버헤드가 아니다. 또, stdout ,stderr 가 kubelet 에 의해 처리 되기 때문에 kubectl logs
와 같은 빌트인 도구를 사용 가능한 장점이 있다 !
동일 파드 내에 여러 애플리케이션 컨테이너가(!) 있는 경우, 혹은 그게 아니더라도 여러 컨테이너가 있는 경우에 소스 컨테이너에 따라 로그 라인을 파싱하도록 에이전트를 구성 할 수도 있다.
만약 단일 파일에 로그를 기록해도 되는 케이스라면, 위 이미지와 같은 스트리밍 컨테이너 방식을 사용하기보다는, 그냥 stdout, stderr 로 보내도록 하는게 훨씬 낫다. (스트리밍 컨테이너 + 로그파일 기록을 두번씩 하게 된다면 스토리지 사용량도 두배가 되니까;;)
사이드카 컨테이너를 사용해서 애플리케이션 자체에서 로테이션 할 수 없는 로그파일을 로테이션 할 수도 있다. 그럼에도 stdout, stderr 를 직접 사용하고 로그 로테이션과 유지/ retention 정책을 kubelet 이 하도록 하는게 매우 직관적이고 좋다.
로깅 에이전트가 있는 사이드카 컨테이너

노드 레벨 로깅 에이전트가 상황에 맞게 충분히 유연하지 않은 경우 애플리케이션과 함께 실행하도록 특별히 구성된 별도의 로깅 에이전트를 사용하는것도 방법. 다만 사이드카 컨테이너에서 로깅 컨테이너를 사용하면 상당한 리소스 소비로 이어질 수 있고. 무엇보다 stdout
, stderr
를 통해 로그를 관리하는게 아니기 때문에 kubelet logs
를 사용해서 로그 접근을 못한다는 매우 큰 단점이 있다. (물론 로깅 백엔드가 datadog 같이 훌륭한거라면 문제가 없을수도야 있겠지만. 그래도 우리가 사랑하는 kubectl 을 못쓰는건 아쉽다.)
이런 케이스의 예시로는 fluentd
가 있다.
애플리케이션에서 직접 로그 노출

애플리케이션이 직접 로그를 노출하거나, 원격지/백엔드에 푸시하는 케이스.
쿠버네티스의 범위를 벗어남
관심있게 봐야하는 로그(?)
컨트롤 플레인
EKS 환경에서는 별도로 enable 해서 CloudWatch 를 통해 봐야 한다.
컨트롤 플레인이 돌고있는 어떤 서버(?) 정보에 대해서는 우리가 알 지 못하기 때문에 (EKS 같은 클라우드 공급자 케이스 한정) 컨트롤 플레인 내의 시스템 컴포넌트(컨테이너/파드 형태로 돌고있는)에 대한 아래와 같은 로그는 수집하고, 알 필요가 있다.
- kube-apiserver 로그
- audit 로그
- scheduler 로그
- controller manager 로그
- etcd 로그
워커 노드
워커 노드는 우리가 접속도하고, 조작도 할 수 있다. 여기서도 두가지로 분류해서 생각해봐야한다.
그리고 EC2/서버 자체의 로그/메트릭같은? 쿠버네티스가 아니더라도 잘 확인하고 봐야하는 부분은 생략하고 작성
- 컨테이너/파드 형태로 돌지 않는 컴포넌트
- 컨테이너/파드 형태로 도는 컴포넌트(애플리케이션 파드 같은 것들이 아닌)
- 애플리케이션 로그
컨테이너/파드 형태로 돌지 않는 컴포넌트
Kubelet
Kubelet은 각 노드에서 실행되는 에이전트로. 파드에서 컨테이너가 확실하게 동작하도록 관리하는 역할을 합니다. kube-apiserver
와 통신하며 PodSpec
에 기술된 컨테이너들이 정상적으로 작동되도록 돕습니다. Kubelet 이 파드를 어디에 배치할지 같은건 처리하지 않습니다. (그런건 컨트롤 플레인 내 컴포넌트들이 처리)
Kubelet 은 파드를 배치해야하는 상황에서 컨테이너 런타임에 정확하게 컨테이너/파드를 배치 할 수 있도록 동작을 합니다.
Kubelet 은 Node 에 systemd 서비스로 등록되고, 그 로그는 journald 에서 저장/관리 합니다. systemd 로 등록된 서비스가 만들어낸 로그는 journald 를 통해 저장되고 이 로그는 바이너리라 직접 확인 불가능. journalctl 사용해야만 가능.
rsyslog 같은걸 같이 써서 systemd 서비스 로그를 따로 /var/log
내에 파일로 읽을 수 있게 떨어트리는 방법이 있다면. 그것을 쓰는것도 방법.
Containerd (컨테이너 런타임)
Kubelet 과 동일. systmed 서비스로 등록 됨
컨테이너/파드 형태로 도는 컴포넌트 (static pods)
애플리케이션과 동일하게 로그가 노드에 /var/log/containers
, /var/log/pods
에 저장되기 때문에 크게 신경쓸 부분은 없음
kube-proxy 같은것이 있음.