EFK
1. EFK
- EFK는 Elasticsearch, Fluentd, Kibana의 조합으로 구성된 로깅 시스템입니다.
- 분산 환경에서 발생하는 로그 데이터를 효율적으로 수집, 저장, 분석하고 시각화하는 데 사용되는 오픈 소스 솔루션들의 통합 스택입니다.
- 이번 글에서는 EFK를 쿠버네티스 환경에서 구축하는 방법에 대해 알아보겠습니다.
1.1 Elastic Search
- Elasticsearch는 분산형 검색 및 분석 엔진으로서 다음과 같은 특징을 가지고 있습니다:
- 대규모 로그 데이터를 효율적으로 저장하고 검색할 수 있는 기능을 제공합니다
- 실시간 데이터 처리가 가능하며, 강력한 검색 기능을 제공합니다
- 쿠버네티스의 Cluster Level Logging Architecture에서 logging backend 역할을 담당합니다
1.2 Fluentd
- Fluentd는 로그 데이터를 수집하고 분석하는 데 사용되는 오픈 소스 데이터 수집기다.
- 다양한 소스로부터 로그 데이터를 수집하고 처리할 수 있습니다
- 수집한 데이터를 여러 출력 대상으로 전달할 수 있습니다
- 쿠버네티스 환경에서는 각 노드에 배포되어 logging agent 역할을 수행합니다
1.3 Kibana
- Kibana는 Elasticsearch의 데이터를 시각화하고 탐색하는 도구로서 다음과 같은 기능을 제공합니다:
- 직관적인 대시보드를 통해 로그 데이터를 시각적으로 표현합니다
- Elasticsearch에 저장된 데이터를 다양한 방식으로 분석하고 탐색할 수 있습니다
- 사용자 친화적인 인터페이스를 통해 복잡한 쿼리와 분석을 수행할 수 있습니다
1.4 EFK 적용을 위한 사전 준비
- EFK를 효과적으로 구축하기 위해서는 다음과 같은 사항들을 먼저 이해해야 합니다:
- 쿠버네티스의 cluster-level logging 아키텍처의 기본 개념을 파악합니다
- Elasticsearch, Fluentd, Kibana 각 컴포넌트의 역할과 상호 작용 방식을 이해합니다
- 로깅 시스템 구축에 필요한 리소스 요구사항을 검토합니다
2 Logging Architecture
- EFK를 적용하기에 앞서 먼저 kubernetes의 cluster-level logging 아키텍처를 이해하는 것이 중요합니다.
- 이는 로그 관리의 기본이 되는 핵심 개념이기 때문입니다.
2.1 컨테이너 로그 관리 필요성
- 우리가 EFK를 사용하는 이유는 컨테이너 런타임에서 제공하는 기본 기능으로 로그를 저장하고 분석 및 조회하기가 어렵기 때문입니다.
- 다음과 같은 상황에서도 애플리케이션의 로그를 확인할 수 있어야 합니다:
- 컨테이너가 예기치 않게 종료된 경우
- 파드가 노드에서 퇴출된 경우
- 노드 자체가 장애를 겪은 경우
- 하지만 현재 실행 중인 컨테이너와 직전 컨테이너의 로그만 보관되어 과거 이력 추적이 어렵습니다.
- 추가적으로 로드 밸런싱 환경에서는 여러 파드에 분산된 로그를 통합적으로 분석해야 할 필요가 있습니다.
- 디버깅을 위해 모든 파드의 로그를 일일이 찾아야 하는 번거로움이 있습니다.
- 즉 컨테이너 런타임에서 제공하는 로깅 기능만으로는 로그 관리가 어렵다는 것을 알 수 있습니다
- 따라서 cluster-level logging을 적용하면 중앙화된 로그 저장소에 모든 로그를 저장하고 관리된다면 이러한 문제를 해결할 수 있습니다.
2.2 노드가 컨테이너 로그를 다루는 방법
- 쿠버테니스의 모든 노드에는 Kubelet과 컨테이너 런타임 작동하고 있습니다.
- kubelet이 CRI를 통해 컨테이너 런타임에게 명령을 전달하면 컨테이너 런타임이 주어진 위치에 컨테이너 로그를 기록하는 방식으로 작동합니다.
- 컨테이너 런타임은 컨테이너에서 만들어지는 stdout and stderr streams을 처리하고 리다이렉트 합니다.
- 즉 컨테이너에서 생성되는 로그를 캡처해서 지정된 로그 파일에 기록합니다.
- 노드에서 동작 중인 컨테이너의 로그는 노드의 어느 위치에 위치한 로그 파일에 쓰이고 있습니다.
- Kubelet은 기본적으로 컨테이너를 다시 시작하면 종료된 컨테이너 하나를 로그와 함께 보관합니다.
- 만약 파드가 노드에서 퇴출당하면 포함된 모든 컨테이너와 로그가 함께 제거됩니다.
- 로그의 생명 주기가 파드와 같기 때문에 파드가 퇴출되면 cluster-level logging이 없으면 더 이상 로그를 보는 것이 불가능합니다.
- cluster-level logging 솔루션을 적용하면 애플리케이션의 로그는 노드, 파드, 컨테이너와 별개로 독립적인 backend에 저장되기 때문에 로그를 계속해서 볼 수 있습니다.
Container Runtime Interface (CRI)
Kubelet과 컨테이너 런타임은 긴밀한 협력 관계를 통해 작동한다. Kubelet이 각 노드의 컨테이너 런타임을 관리하는 역할을 하기 때문이다. 그런데 컨테이너 런타임에는 여러 가지 종류가 존재하는데 Kubelet은 어떻게 서로 다른 컨테이너 런타임과 협력할 수 있을까? 이유는 Kubelet과 컨테이너 런타임 사이의 표준 프로토콜 CRI가 존재한다. 따라서 CRI를 만족시키는 컨테이너 런타임들은 교체해도 Kubelet은 무리 없이 협력이 가능하다.
2.3 Log Rotation
- 파드를 계속해서 실행하게 되면 로그 파일에 쌓이는 로그의 양이 많아질 텐데 쿠버네티스는 이를 어떻게 관리할까?
- kubelet의 Log Rotaion 설정을 통해 로그 파일을 일정한 크기로 관리할 수 있습니다.
- kubelet이 CRI를 통해 컨테이너 런타임에게 명령을 전달하면 컨테이너 런타임이 주어진 위치에 컨테이너 로그를 기록하는 방식으로 작동합니다.
- kubelet의 containerLogMaxSize, containerLogMaxFiles 설정해서 로그 파일을 관리합니다.
- containerLogMaxSize: 로그 파일 하나의 최대 크기(Default: "10Mi")
- containerLogMaxFiles: 컨테이너 하나가 가질 수 있는 최대 로그 파일의 개수(Default: 5)
- kubelet의 Log Rotation을 직접 설정하고 싶다면 configuration settings, kubelet configuration file 를 참고하자.
2.3 Cluster-level logging architectures
- 쿠버네티스는 자체적인 로깅 시스템을 제공하지 않기 때문에 cluster-level logging을 직접 구현해야 합니다.
- 3가지 방식으로 cluster-level logging을 구현할 수 있습니다.
- 모든 노드에 logging agent를 놓는 방식
- logging을 위한 사이드카 컨테이너를 pod에 포함시키는 방식
- 애플리케이션에서 logging backend로 직접 로그를 전송하는 방식
2.3.1 모든 노드에 logging agent를 놓는 방식
- logging agent를 각각의 노드에 하나씩 배치시키는 방법입니다.
- logging agent가 로그를 logging backend로 보내 는 역할을 합니다.
- EKF 스택에서는 Fluent Bit를 logging agent로 사용합니다.
- 일반적으로 logging agent는 해당 노드에서 작동하는 모든 컨테이너의 로그 파일에 직접 접근이 가능합니다.
- 노드 당 하나의 logging agent가 필요하기 때문에 DaemonSet을 이용해 agent를 배포하는 것이 좋습니다.
- 컨테이너마다 생성하는 로그의 형식이 다를 수 있기 때문에, logging agent는 이를 파싱하여 통일된 형식으로 변환한 후 logging backend로 전송하는 기능이 필요할 수 있습니다
2.3.2 logging을 위한 사이드카 컨테이너를 pod에 포함시키는 방식
- 만약 노드 레벨의 logging agent가 적합하지 않은 상황이라면 파드에 사이드카로 logging agent를 추가할 수 있습니다.
- logging agent를 사이드카로 사용하는 것은 엄청난 양의 리소스를 소비할 수 있으니 주의가 필요합니다.
2.3.3 애플리케이션에서 logging backend로 직접 로그를 전송하는 방식
- 애플리케이션 컨테이너가 직접 logging backend로 로그를 전달하는 방식입니다.
2.4 EFK의 아키텍처
- logging agent를 사이드카로 사용하는 것은 엄청난 양의 리소스를 소비할 수 있으니 우선적으로 모든 노드에 logging agent를 놓는 아키텍처로 진행하겠습니다.
- 즉 모든 노드에 하나씩 logging agent(Fluent Bit)을 배치하고 logging backend(ElasticSearch)로 로그를 전달하는 방식으로 EFK를 구축해 보겠습니다.
- Kibana는 ElasticSearch에 저장된 로그를 시각화하는 도구로 사용하겠습니다.
3 데모 애플리케이션 만들기
- EFK 구축에 앞서 로깅을 생성하는 HTTP API 애플리케이션을 구현해보겠습니다.
- 이 데모 애플리케이션의 개발 배경은 다음과 같습니다.
- 실제 프로젝트 진행 중 클라이언트의 HTTP API 호출에서 INTERNAL SERVER ERROR가 발생했을 때, 관련 로그를 찾는 데 어려움이 있었 습니다.
- 쿠버네티스의 기본 로깅 기능만으로는 특정 API 요청과 관련된 로그만을 조회하기가 매우 불편했기 때문입니다.
- 이러한 문제를 해결하기 위해, 각 요청마다 고유한 ID를 부여하고 모든 로그에 이 요청 ID를 포함시키는 방식을 적용하기로 했습니다.
- 이렇게 하면 특정 요청과 관련된 모든 로그를 쉽게 검색하고 추적할 수 있습니다.
- 요청별 고유 식별자를 할당하는 기능은 Servlet Filter를 통해 직접 구현할 수도 있지만, Spring Cloud Sleuth를 사용하면 의존성 추가만으로도 동일한 기능을 손쉽게 구현할 수 있습니다.
- 따라서 이번 데모 프로젝트에서는 Spring Cloud Sleuth를 활용하여 요청 ID를 관리하도록 하겠습니다.
3.1 의존성 설정
3.2 Logback 포맷 설정
- 로그 출력 형식을 커스터마이징하기 위해 Logback 설정을 수정하겠습니다.
- Logback은 기본 포맷을 제공하지만, 우리의 요구사항에 맞게 커스터마이징할 수 있습니다.
- logback-spring.xml 파일에 Logback에 대한 설정을 추가하겠습니다.
- logback-spring.xml은 src/main/resources/logback-spring.xml에 위치합니다.