EFK 로깅 스택 구축

파드가 교체되거나 노드가 사라지면 로컬 컨테이너 로그도 함께 잃을 수 있습니다. 운영 로그는 노드·파드와 수명을 분리해 중앙 저장소로 모아야 합니다. EFK는 Elasticsearch에 저장하고 Fluentd로 수집하며 Kibana에서 검색하는 구성입니다.

로그 흐름

애플리케이션은 가능한 한 stdoutstderr로 로그를 내보냅니다. 컨테이너 런타임이 CRI 형식으로 /var/log/pods에 기록하고 /var/log/containers에 심볼릭 링크를 만듭니다. 각 노드의 Fluentd DaemonSet이 이 파일을 읽어 Elasticsearch로 전송합니다.

구성 요소역할운영 확인
Fluentd노드별 로그 수집·파싱·전송buffer, retry, dropped record
Elasticsearch색인·검색·보존cluster health, shard, disk
Kibana검색·대시보드data view, 접근 권한

Kubernetes 자체는 중앙 로그 저장소를 제공하지 않으므로 저장 용량, 보존 기간, 접근 제어는 운영자가 정해야 합니다.

Elasticsearch와 Kibana

Elasticsearch를 StatefulSet으로 직접 조립할 수도 있지만 인증서, 롤링 업데이트, 클러스터 상태 관리까지 고려하면 Elastic Cloud on Kubernetes(ECK) Operator가 편합니다. ECK를 공식 절차로 설치한 뒤 Elasticsearch 리소스를 생성합니다.

apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: logging
  namespace: logging
spec:
  version: 9.2.3
  nodeSets:
    - name: default
      count: 1
      config:
        node.store.allow_mmap: false
      podTemplate:
        spec:
          containers:
            - name: elasticsearch
              resources:
                requests:
                  cpu: 500m
                  memory: 2Gi
                limits:
                  memory: 4Gi
      volumeClaimTemplates:
        - metadata:
            name: elasticsearch-data
          spec:
            accessModes: ["ReadWriteOnce"]
            storageClassName: nfs-client
            resources:
              requests:
                storage: 50Gi

버전은 ECK compatibility matrix에서 지원 여부를 확인하고 고정합니다. node.store.allow_mmap: false는 테스트 환경을 단순화하지만 성능상 불리하므로 운영에서는 노드의 vm.max_map_count를 설정하는 방식을 검토하세요.

Kibana는 Elasticsearch를 참조하도록 만듭니다.

apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: logging
  namespace: logging
spec:
  version: 9.2.3
  count: 1
  elasticsearchRef:
    name: logging
  podTemplate:
    spec:
      containers:
        - name: kibana
          resources:
            requests:
              cpu: 250m
              memory: 1Gi
kubectl create namespace logging
kubectl apply -f elasticsearch.yaml
kubectl apply -f kibana.yaml
kubectl -n logging get elasticsearch,kibana,pod,pvc

Elasticsearch 연결 확인

ECK는 기본적으로 TLS와 elastic 사용자 비밀번호를 생성합니다.

PASSWORD=$(kubectl -n logging get secret logging-es-elastic-user \
  -o go-template='{{.data.elastic | base64decode}}')

kubectl -n logging port-forward service/logging-es-http 9200:9200

다른 터미널에서 ECK가 생성한 CA를 사용해 상태를 확인합니다. 테스트 외 환경에서 curl -k로 인증서 검증을 끄지 않습니다.

kubectl -n logging get secret logging-es-http-certs-public \
  -o jsonpath='{.data.ca\.crt}' | base64 -d > elastic-ca.crt

curl --cacert elastic-ca.crt -u "elastic:$PASSWORD" \
  https://localhost:9200/_cluster/health?pretty

단일 노드에서 replica shard가 할당되지 않아 yellow가 될 수 있습니다. 운영 HA 구성이면 데이터 노드를 여러 장애 영역에 배치하고 replica 상태까지 green인지 확인합니다.

Fluentd 수집 설정

Fluentd는 모든 노드에 하나씩 실행해야 하므로 DaemonSet으로 배포합니다. 핵심은 CRI 로그 파싱, Kubernetes 메타데이터 추가, 디스크 버퍼입니다.

<source>
  @type tail
  @id kubernetes-containers
  path /var/log/containers/*.log
  pos_file /var/log/fluentd-containers.pos
  tag kubernetes.*
  read_from_head false
  <parse>
    @type cri
  </parse>
</source>

<filter kubernetes.**>
  @type kubernetes_metadata
</filter>

<match kubernetes.**>
  @type elasticsearch
  host "#{ENV['ELASTICSEARCH_HOST']}"
  port 9200
  scheme https
  ssl_verify true
  ca_file /fluentd/certs/ca.crt
  user elastic
  password "#{ENV['ELASTICSEARCH_PASSWORD']}"
  logstash_format true
  logstash_prefix kubernetes

  <buffer tag,time>
    @type file
    path /fluentd/buffer
    timekey 1h
    flush_interval 5s
    retry_type exponential_backoff
    retry_forever true
  </buffer>
</match>

DaemonSet에는 다음 항목이 필요합니다.

  • /var/log/containers/var/log/pods read-only 마운트
  • CA와 비밀번호 Secret 마운트
  • buffer용 writable volume
  • 수집 노드에 배치될 toleration
  • CPU·메모리 requests/limits

비밀번호를 ConfigMap이나 이미지에 넣지 않습니다. Fluentd가 재시작돼도 같은 파일을 중복 수집하지 않도록 pos_file과 buffer 경로를 보존해야 합니다.

Kibana에서 확인

kubectl -n logging port-forward service/logging-kb-http 5601:5601

Kibana에서 kubernetes-* data view를 만들고 다음 필드를 확인합니다.

  • kubernetes.namespace_name
  • kubernetes.pod_name
  • kubernetes.container_name
  • log 또는 message

새 로그가 보이지 않으면 순서대로 확인합니다.

kubectl -n logging get daemonset,pod
kubectl -n logging logs daemonset/fluentd --tail=200
curl --cacert elastic-ca.crt -u "elastic:$PASSWORD" \
  'https://localhost:9200/_cat/indices?v'

Fluentd 로그에 401·TLS·buffer overflow가 없는지, Elasticsearch에 인덱스가 생성됐는지 분리해서 확인하면 원인을 빨리 좁힐 수 있습니다.

보존과 용량 관리

로그를 무기한 보관하면 디스크가 먼저 가득 찹니다. Index Lifecycle Management 정책으로 일정 기간 뒤 삭제하거나 hot-warm-cold 계층으로 이동하도록 설정합니다. 보존 기간은 장애 분석·감사 요구와 저장 비용을 함께 고려해 정합니다.

또한 kubelet의 containerLogMaxSizecontainerLogMaxFiles를 설정해 Fluentd가 보내기 전 노드 디스크가 로그로 가득 차지 않게 해야 합니다. Elasticsearch 클러스터 상태와 PVC 사용량에도 별도 알림을 둡니다.

Loki를 선택할 때

본문 전체 검색과 복잡한 분석이 중요하면 Elasticsearch가 강력합니다. 이미 Grafana를 사용하고 있고 라벨 중심 검색으로 충분하다면 Loki가 더 가볍습니다. 어떤 제품을 쓰든 중앙 저장소, 보존 정책, 접근 권한, 수집 실패 알림은 빠지면 안 됩니다.

참고: Kubernetes 로깅 아키텍처, ECK Elasticsearch 배포 문서