Kubernetes 로그 수집의 기본 개념
Kubernetes 클러스터에서 관리해야 하는 로그는 크게 두 가지로 분류됩니다.
- 시스템 및 컴포넌트 로그:
/var/log/messages와 같은 OS 레벨의 로그와kubelet,kube-apiserver등의 컴포넌트 로그. - 애플리케이션 비즈니스 로그: 클라우드 네이티브 방식으로 개발된 앱의 표준 출력(Stdout) 로그와, 기존 레거시 앱이 컨테이너 내부 파일 시스템에 기록하는 파일 기반 로그.
전통적인 ELK(Elasticsearch, Logstash, Kibana) 아키텍처에서 Logstash는 JVM 기반의 무거운 프로세스로, 메모리 점유율이 높고 유지보수 비용이 큽니다. 따라서 Kubernetes 환경에서는 보다 경량화된 로그 수집 스택이 요구됩니다.
EFK 스택을 활용한 표준 로그 수집
Kubernetes 커뮤니티에서 권장하는 EFK(Elasticsearch, Fluentd, Kibana) 스택은 Fluentd를 데몬셋(DaemonSet)으로 배포하여 노드 수준의 로그를 수집합니다.
참고: Fluentd는 기본적으로 컨테이너의 표준 출력(Console) 로그를 수집하는 데 최적화되어 있으며, 컨테이너 내부의 특정 파일에 기록되는 비표준 로그를 직접 수집하는 데는 제약이 있습니다.
EFK 클러스터 배포
사내 프라이빗 레지스트리에 이미지를 푸시하고, 노드 셀렉터를 활용해 로그 수집이 필요한 특정 노드에만 Fluentd를 배포하는 예시입니다.
# 프라이빗 레지스트리에서 이미지 풀링 및 푸시
docker pull harbor.internal.io/infra/elasticsearch:7.14.2
docker pull harbor.internal.io/infra/fluentd:v1.14.3
docker pull harbor.internal.io/infra/kibana:7.14.2
# 로깅 전용 네임스페이스 생성
kubectl create namespace cluster-logging
# Elasticsearch StatefulSet 및 Service 배포
kubectl apply -f es-statefulset.yaml -n cluster-logging
kubectl apply -f es-service.yaml -n cluster-logging
# Kibana Deployment 및 Service 배포
kubectl apply -f kibana-deployment.yaml -n cluster-logging
kubectl apply -f kibana-service.yaml -n cluster-logging
Fluentd는 nodeSelector를 사용하여 특정 노드에만 배포할 수 있습니다.
# fluentd-daemonset.yaml 발췌
spec:
template:
spec:
nodeSelector:
log-collector: "enabled"
containers:
- name: fluentd
image: harbor.internal.io/infra/fluentd:v1.14.3
# 로그 수집 대상 노드에 라벨 부여
kubectl label node worker-node-01 log-collector=enabled
kubectl label node worker-node-02 log-collector=enabled
# Fluentd 데몬셋 배포
kubectl apply -f fluentd-daemonset.yaml -n cluster-logging
Filebeat 사이드카를 통한 파일 로그 수집
Fluentd가 파일 기반 로그를 수집하지 못하는 한계를 극복하기 위해, 애플리케이션 Pod 내에 Filebeat를 사이드카(Sidecar)로 주입하는 방식을 사용할 수 있습니다. 이를 통해 컨테이너 내부의 볼륨을 공유하여 파일 로그를 직접 읽을 수 있습니다.
대규모 트래픽 환경에서 Elasticsearch로의 직접 전송은 병목 현상을 유발할 수 있으므로, Filebeat -> Kafka -> Logstash -> Elasticsearch 파이프라인을 구성하여 버퍼링 및 전처리를 수행하는 것이 일반적입니다.
Kafka 및 Logstash 배포
Helm을 사용하여 Zookeeper와 Kafka 클러스터를 먼저 배포한 후, Logstash를 구성합니다.
# values.yaml 이미지 레지스트리 수정 후 Helm 설치
helm install -n cluster-logging zookeeper ./zookeeper-chart
helm install -n cluster-logging kafka ./kafka-chart
# Logstash ConfigMap 및 Deployment 배포
kubectl apply -f logstash-configmap.yaml -n cluster-logging
kubectl apply -f logstash-deployment.yaml -n cluster-logging
Logstash의 logstash-configmap.yaml에서는 Kafka를 입력 소스로 사용하며, Filebeat와 동일한 토픽을 구독하도록 설정합니다.
Filebeat 사이드카 주입 및 볼륨 공유
애플리케이션 컨테이너와 Filebeat 컨테이너가 동일한 emptyDir 볼륨을 마운트하여 로그 파일을 공유합니다.
# order-service-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 2
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order-app
image: harbor.internal.io/apps/order-service:v2.1.0
volumeMounts:
- name: shared-log-dir
mountPath: /var/log/app/
command: ["sh", "-c", "while true; do echo $(date) >> /var/log/app/access.log; sleep 5; done"]
- name: filebeat-sidecar
image: harbor.internal.io/infra/filebeat:7.14.2
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
- name: shared-log-dir
mountPath: /data/collected-logs/
- name: filebeat-config
mountPath: /usr/share/filebeat/filebeat.yml
subPath: filebeat.yml
volumes:
- name: shared-log-dir
emptyDir: {}
- name: filebeat-config
configMap:
name: filebeat-config
Filebeat 설정 파일(ConfigMap)에서는 공유된 디렉토리를 모니터링하고, 수집한 로그에 파드 메타데이터를 태깅하여 Kafka로 전송합니다.
# filebeat-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-config
data:
filebeat.yml: |-
filebeat.inputs:
- type: log
paths:
- /data/collected-logs/*.log
tail_files: true
fields:
service_name: "order-service"
pod_name: "${POD_NAME}"
namespace: "${POD_NAMESPACE}"
fields_under_root: true
output.kafka:
hosts: ["kafka-headless.cluster-logging.svc:9092"]
topic: "order-service-logs"
required_acks: 1
경량 로그 스택: Loki 아키텍처
Elasticsearch는 전문 검색에는 강력하지만, 단순 로그 저장소로 사용하기에는 리소스 소모가 매우 큽니다. 이를 대체하기 위해 Grafana Labs에서 개발한 Loki는 로그의 전문 인덱싱(Full-text indexing)을 수행하지 않고, 메타데이터(라벨)에 대해서만 인덱싱을 수행하여 매우 경량화된 아키텍처를 제공합니다.
Loki 스택은 크게 세 가지 컴포넌트로 구성됩니다.
- Loki: 로그 스트림을 저장하고 쿼리하는 메인 서버.
- Promtail: 노드 또는 파드의 로그를 수집하여 라벨을 부착하고 Loki로 전송하는 에이전트.
- Grafana: LogQL을 사용하여 수집된 로그를 시각화하고 조회하는 대시보드.
Helm을 통한 Loki Stack 배포
# Grafana Helm 레포지토리 추가 및 차트 풀링
helm repo add grafana https://grafana.github.io/helm-charts
helm pull grafana/loki-stack
# 로깅 네임스페이스 생성 및 Loki 스택 배포
kubectl create namespace loki-stack
helm install loki ./loki-stack \
--set grafana.enabled=true \
--set grafana.service.type=NodePort \
-n loki-stack
# Grafana 초기 admin 비밀번호 확인
kubectl get secret --namespace loki-stack loki-grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo
Loki는 기본적으로 인메모리 또는 로컬 스토리지를 사용하므로, 프로덕션 환경에서는 values.yaml을 수정하여 PVC를 통한 영구 볼륨(Persistent Volume) 마운트를 구성해야 합니다. 배포 후 Grafana 대시보드에서 LogQL을 활용해 {app="order-service"}와 같은 라벨 기반의 로그 필터링이 가능합니다.