포너포닉스

OpenShift(OCP) 환경 Filebeat DaemonSet 로그 수집 경로 완벽 분석: Container, Audit, auditd, journald 수집 및 Logstash 파싱 아키텍처 본문

IT

OpenShift(OCP) 환경 Filebeat DaemonSet 로그 수집 경로 완벽 분석: Container, Audit, auditd, journald 수집 및 Logstash 파싱 아키텍처

PONERponix 2026. 11. 12. 00:52

1. 개요: Filebeat DaemonSet 수집 경로에 대한 흔한 오해

엔터프라이즈 환경 및 금융권의 Red Hat OpenShift Container Platform(OCP) 환경에서 대용량 로그 통합 아키텍처를 설계할 때, 로그 에이전트로 Filebeat DaemonSet을 자주 채택합니다. 이때 현업 엔지니어들이 흔히 마주치는 설계 질문 중 하나가 있습니다.

"Filebeat DaemonSet의 paths 설정에 /var/log/pods/default_*/*/*.log, /var/log/pods/kube*_*/*/*.log, /var/log/pods/openshift*_*/*/*.log 를 지정하면 Pod 컨테이너 로그뿐만 아니라 Audit 로그, Linux auditd, systemd journald 로그까지 모두 수집할 수 있는가?"

결론부터 명확히 짚고 넘어가면, "그렇지 않다"입니다.

/var/log/pods/ 경로는 오직 Kubernetes Pod 내부 컨테이너의 표준 출력(stdout) 및 표준 오류(stderr) 로그만 적재되는 위치입니다. 위 세 가지 경로는 수집 대상 로그의 '종류'를 나눈 것이 아니라, Pod가 속한 Kubernetes Namespace 범위를 화이트리스트 형태로 한정한 것에 불과합니다.

따라서 OCP 클러스터의 전사적인 감사(Audit) 추적 및 노드 인프라 모니터링을 완벽하게 달성하려면 로그 유형별 물리적 저장 경로와 적절한 Filebeat Input 타입을 정확히 분리하여 설계해야 합니다.


2. /var/log/pods 디렉터리 구조 및 CRI 로그 포맷 분석

2.1 디렉터리 구조 및 네임스페이스 매핑

OCP Worker 및 Control Plane(Master) 노드의 로컬 디스크 상에서 /var/log/pods 디렉터리는 다음과 같은 정형화된 계층 구조를 갖습니다.

/var/log/pods/
  └── <namespace>_<pod-name>_<pod-uid>/
        └── <container-name>/
              ├── 0.log
              ├── 1.log
              └── ...
경로 세그먼트 의미 및 설명
/var/log/pods/ 해당 노드에 스케줄링되어 실행 중인 모든 Pod의 컨테이너 로그 루트 디렉터리
<namespace> Pod가 배포된 Kubernetes 네임스페이스 (예: default, kube-system, openshift-apiserver)
<pod-name> Kubernetes Pod 리소스 이름
<pod-uid> K8s 클러스터 내에서 Pod를 식별하는 고유 UUID
<container-name> Pod 스펙 내 정의된 컨테이너 이름 (Multi-container Pod인 경우 컨테이너별 디렉터리 분리)
0.log, 1.log 컨테이너 로그 파일. 인덱스 번호는 컨테이너 재시작(Restart) 횟수와 직결됨

2.2 CRI 로그 레코드 포맷 (CRI-O / containerd)

OCP의 기본 컨테이너 런타임인 CRI-O는 로그를 표준 CRI 포맷으로 기록합니다.

2026-11-11T10:20:30.123456789Z stdout F Application started on port 8080
2026-11-11T10:20:31.987654321Z stderr F [ERROR] Failed to establish database connection
  • Timestamp: RFC3339 나노초 정밀도 타임스탬프
  • Stream: stdout 또는 stderr
  • Tag: 로그 레코드의 완결성 플래그 (F: Full line - 개행으로 끝난 완전한 라인, P: Partial line - 버퍼 크기 초과로 다음 줄로 이어지는 분할 라인)
  • Log Message: 실제 애플리케이션 출력 문자열

2.3 제시된 세 가지 수집 경로의 실질적 한계

  1. /var/log/pods/default_*/*/*.log: 오직 default 네임스페이스의 컨테이너만 수집합니다. 운영 환경의 비즈니스 앱(예: banking-core, cpm-agent, frontend 등)이 별도 커스텀 네임스페이스에 배포된 경우 전량 누락됩니다.
  2. /var/log/pods/kube*_*/*/*.log: kube-system, kube-public 등 쿠버네티스 코어 네임스페이스에 Pod 형태로 기동된 컴포넌트 로그만 수집합니다.
  3. /var/log/pods/openshift*_*/*/*.log: openshift-apiserver, openshift-ingress, openshift-monitoring 등 OCP 플랫폼 및 오퍼레이터 Pod 로그를 수집합니다. OCP의 모든 로그가 아니라 OCP 네임스페이스 Pod의 컨테이너 로그에 국한됩니다.

3. 4대 핵심 로그 유형별 물리적 저장 위치 및 메커니즘 비교

OCP 클러스터의 전수 감시 체계를 수립하기 위해 반드시 구분해야 하는 4대 로그의 비교표입니다.

로그 유형 물리적 저장 위치 (RHCOS 노드) 로그 성격 및 용도 Filebeat 수집 방식
Container Log /var/log/pods/.../*.log Pod 컨테이너 stdout/stderr filestream (container 파서)
K8s API Audit /var/log/kube-apiserver/audit.log Kubernetes API 호출, 사용자 인증·인가, RBAC 감사 filestream (JSON 파싱)
OCP API Audit /var/log/openshift-apiserver/audit.log OpenShift 전용 API 확장 및 OAuth 로그인 감사 filestream (JSON 파싱)
Linux auditd /var/log/audit/audit.log 호스트 OS(RHCOS) 커널 보안 이벤트, 시스템 콜, SELinux 거부 filestream (auditd 파서)
systemd journald systemd Journal 바이너리 로그 (/run/log/journal) kubelet.service, crio.service 등 노드 레벨 시스템 데몬 로그 journald 전용 input
OCP Node (RHCOS) 파일시스템 구조
│
├── /var/log/
│     ├── pods/                          ← [1] 컨테이너 로그 원본
│     │     ├── default_.../
│     │     ├── kube-system_.../
│     │     └── openshift-monitoring_.../
│     │
│     ├── containers/                    ← pods 디렉터리를 가리키는 심볼릭 링크
│     │
│     ├── kube-apiserver/
│     │     └── audit.log                ← [2] Kubernetes API Audit 로그
│     │
│     ├── openshift-apiserver/
│     │     └── audit.log                ← [3] OpenShift API Audit 로그
│     │
│     └── audit/
│           └── audit.log                ← [4] Linux OS auditd 로그
│
└── systemd Journal (바이너리 저장소)    ← [5] journald input 대상
      ├── kubelet.service
      ├── crio.service
      └── NetworkManager.service

4. 프로덕션 환경 Filebeat DaemonSet 권장 구성

컨테이너, K8s/OCP Audit, OS auditd, 노드 데몬 journald 로그를 단일 Filebeat 에이전트로 통합 수집하기 위한 filebeat.inputs 권장 YAML 템플릿입니다.

filebeat.inputs:
  # =========================================================================
  # 1. Container Logs (Pod stdout/stderr)
  # =========================================================================
  - type: filestream
    id: ocp-container-logs
    paths:
      - /var/log/pods/*/*/*.log
    prospector.scanner.symlinks: false
    parsers:
      - container:
          stream: all
          format: auto
    processors:
      - add_kubernetes_metadata:
          host: "${NODE_NAME}"
          matchers:
            - logs_path:
                logs_path: "/var/log/pods/"

  # =========================================================================
  # 2. Kubernetes & OpenShift API Audit Logs (Control Plane 노드)
  # =========================================================================
  - type: filestream
    id: ocp-api-audit
    paths:
      - /var/log/kube-apiserver/audit.log
      - /var/log/openshift-apiserver/audit.log
      - /var/log/oauth-apiserver/audit.log
    parsers:
      - ndjson:
          target: "audit"
          overwrite_keys: true

  # =========================================================================
  # 3. Linux auditd Logs (Host OS 커널 보안 이벤트)
  # =========================================================================
  - type: filestream
    id: ocp-linux-audit
    paths:
      - /var/log/audit/audit.log
    parsers:
      - auditd: ~

  # =========================================================================
  # 4. Systemd Journal Logs (Kubelet, CRI-O 서비스)
  # =========================================================================
  - type: journald
    id: ocp-systemd-journald
    seek: cursor
    cursor_file:
      path: /var/lib/filebeat/journald.cursor
    units:
      - kubelet.service
      - crio.service
      - NetworkManager.service

5. OCP 운영 시 핵심 체크리스트 & Best Practices

5.1 네임스페이스 하드코딩 지양 및 add_kubernetes_metadata 활용

paths에 네임스페이스 패턴(default_*, kube*_* 등)을 하드코딩하면 신규 서비스 배포 시 수집 누락이 발생합니다. 프로덕션 환경에서는 /var/log/pods/*/*/*.log로 광역 수집하되, Filebeat의 add_kubernetes_metadata 프로세서를 통해 레이블과 네임스페이스를 인덱싱 메타데이터로 부여한 뒤 Elasticsearch/OpenSearch 레벨에서 필터링하거나 라우팅하는 것이 훨씬 유연합니다.

5.2 심볼릭 링크 중복 수집 함정

/var/log/containers/에 존재하는 파일들은 사실 /var/log/pods/... 디렉터리의 실제 로그 파일을 가리키는 심볼릭 링크(Symbolic Link)입니다. 두 디렉터리를 동시에 paths에 등록하면 모든 컨테이너 로그가 2벌씩 중복 수집되어 스토리지 낭비와 인덱싱 부하를 초래합니다. /var/log/pods/ 하나만 지정하는 것을 권장합니다.

5.3 Control Plane 노드 스케줄링 및 권한 (SCC)

Audit 로그(/var/log/kube-apiserver/audit.log)는 Master/Control Plane 노드에만 존재합니다. 따라서 DaemonSet 스펙에 다음 Toleration을 반드시 명시해야 Control Plane 노드에도 Filebeat가 정상 배치됩니다.

tolerations:
  - key: node-role.kubernetes.io/master
    operator: Exists
    effect: NoSchedule
  - key: node-role.kubernetes.io/control-plane
    operator: Exists
    effect: NoSchedule

또한 RHCOS 호스트의 /var/log 및 /run/log/journal을 hostPath로 마운트하여 읽기 위해서는 Filebeat ServiceAccount에 적절한 SCC(SecurityContextConstraints, privileged 또는 hostmount-anyuid) 권한이 부여되어야 합니다.

5.4 노드 현장 검증 실무 명령어

실제 OCP 노드에 접속하여 디렉터리 구조와 로그 생성 여부를 확인할 때 사용하는 유용한 명령어입니다.

# 1. 특정 노드의 디버그 파드 기동 및 호스트 루트 전환
oc debug node/<node-name>
chroot /host

# 2. pods 디렉터리 내 네임스페이스 분포 확인
find /var/log/pods -mindepth 1 -maxdepth 1 -type d | head -n 20

# 3. K8s / OCP Audit 로그 파일 존재 여부 확인
ls -lh /var/log/kube-apiserver/audit.log
ls -lh /var/log/openshift-apiserver/audit.log

# 4. journald 로그 동작 확인
journalctl -u crio.service -n 50 --no-pager


6. 수집 이후의 핵심: Journald 원천 로그의 Logstash 파싱 및 OpenSearch 스키마 매핑

Filebeat가 로그를 수집하여 전달하더라도, 실제 OpenSearch에 적재되는 최종 데이터가 온전한 Key-Value 구조를 갖추려면 반드시 거쳐야 하는 후속 파이프라인 단계가 있습니다. "컨테이너 및 감사 로그는 처음부터 완성된 JSON으로 출력되지 않는다"는 점을 이해하는 것이 핵심입니다.

6.1 원천 로그(Journald)의 실제 구조와 이중 계층 문제

Systemd Journal이 컨테이너 표준 출력(stdout/stderr)을 가로채어 기록할 때, 로그는 메타데이터와 본문(MESSAGE)의 혼합 구조로 저장됩니다.

  • 저널 메타데이터: CONTAINER_NAME (예: k8s_kube-apiserver_kube-apiserver-master-0_openshift-kube-apiserver_...), CONTAINER_ID_FULL, __REALTIME_TIMESTAMP, PRIORITY 등
  • MESSAGE 필드: 실제 로그 본문. 일반 텍스트이거나, OCP API Server 감사(Audit) 로그의 경우 JSON 객체가 통째로 이스케이프된 거대한 단일 문자열(String)로 들어있습니다.

만약 이를 Logstash 파싱 없이 OpenSearch로 그대로 던지면, MESSAGE 필드가 하나의 통짜 텍스트로 색인되어 내부의 verb, user.username, requestURI 등의 핵심 감사 키에 대한 정확한 일치 검색이나 집계가 완전히 불가능해집니다.

6.2 Logstash 파이프라인 설계 (Grok + JSON 디코딩 + Flattening)

Logstash에서는 grok 필터로 컨테이너 메타데이터를 정규화하고, json 필터로 내부 감사 메시지를 해체한 뒤 상위 필드로 끌어올립니다.

filter {
  # 1. OCP 컨테이너 저널 로그 공통 처리: CONTAINER_NAME에서 네임스페이스, 파드, 컨테이너명 추출
  if [CONTAINER_NAME] {
    grok {
      match => { 
        "CONTAINER_NAME" => "k8s_%{DATA:container_name}_%{DATA:pod_name}_%{DATA:namespace}_%{DATA:uid}_%{INT:attempt}" 
      }
      tag_on_failure => ["_ocp_container_name_parse_failure"]
    }
  }

  # 2. 대상 분기: OCP API Server 감사(Audit) 로그 인지 확인 (kube-apiserver 등)
  if [container_name] == "kube-apiserver" or [TAG] =~ /audit/ {
    # MESSAGE 필드 내부에 갇힌 JSON 문자열을 실제 객체로 디코딩
    json {
      source => "message"
      target => "audit_event"
      skip_on_invalid_json => true
    }

    # 3. 감사 로그의 주요 키들을 최상위로 끌어올려 스키마(Schema) 부여
    if [audit_event] {
      mutate {
        add_field => {
          "ocp_audit_level"    => "%{[audit_event][level]}"          # None, Metadata, Request, RequestResponse
          "ocp_audit_verb"     => "%{[audit_event][verb]}"           # get, list, create, update, delete 등
          "ocp_audit_uri"      => "%{[audit_event][requestURI]}"     # API 호출 URI
          "ocp_audit_username" => "%{[audit_event][user][username]}" # 감사 요청 주체
          "ocp_audit_stage"    => "%{[audit_event][stage]}"          # RequestReceived, ResponseComplete 등
        }
        # 불필요하게 거대한 중복 원본 필드 정리로 인덱싱 용량 절감
        remove_field => ["[audit_event][objectRef]", "[audit_event][responseStatus]"]
      }
    }
  } else {
    # 4. 일반 컨테이너 로그 처리 (애플리케이션 자체 JSON 구조 언마샬링)
    json {
      source => "message"
      target => "app_json"
      skip_on_invalid_json => true
    }
  }

  # 5. 저널 나노초 타임스탬프 동기화
  if [__REALTIME_TIMESTAMP] {
    ruby {
      code => "
        ts = event.get('__REALTIME_TIMESTAMP')
        if ts
          event.set('@timestamp', LogStash::Timestamp.at(ts.to_f / 1_000_000))
        end
      "
    }
  }
}

6.3 파싱 실패 시 페일러(Dead Letter Queue) 격리 라우팅 전략

로그 형식이 예기치 않게 변경되거나 손상되었을 때 파이프라인 전체가 중단되거나 OpenSearch 매핑 익셉션으로 로그가 통째로 드랍되는 사고를 막기 위해, Failure 태그 기반 라우팅을 적용합니다.

  • tag_on_failure => ["_ocp_grok_parse_failure"]: Grok 패턴 매칭 실패 시 태그를 부착해 원본 데이터를 보존한 채 다음 파이프로 전달합니다.
  • 조건부 출력 라우팅(if "_ocp_grok_parse_failure" in [tags]): 실패한 원시 로그를 별도의 격리 인덱스(ocp-parse-failures-*)로 격리하여 주 인덱스의 매핑 오염을 방지하고 추후 재생(Replay)할 수 있도록 설계합니다.

6.4 OpenSearch Index Template (OCP 컨테이너 및 감사 로그 매핑 정의)

Logstash가 정제해 넘긴 키들이 OpenSearch에서 어떤 데이터 타입(Type)으로 고정될지 사전 정의하는 인덱스 템플릿입니다.

PUT _index_template/ocp-container-audit-template
{
  "index_patterns": ["ocp-container-logs-*"],
  "template": {
    "settings": {
      "number_of_shards": 3,
      "number_of_replicas": 1,
      "refresh_interval": "5s"
    },
    "mappings": {
      "properties": {
        "@timestamp":         { "type": "date" },
        "namespace":          { "type": "keyword" },
        "pod_name":           { "type": "keyword" },
        "container_name":     { "type": "keyword" },
        "CONTAINER_ID_FULL":  { "type": "keyword" },
        "ocp_audit_level":    { "type": "keyword" },
        "ocp_audit_verb":     { "type": "keyword" },
        "ocp_audit_uri": {
          "type": "text",
          "fields": {
            "keyword": { "type": "keyword", "ignore_above": 256 }
          }
        },
        "ocp_audit_username": { "type": "keyword" },
        "ocp_audit_stage":    { "type": "keyword" },
        "message":            { "type": "text" }
      }
    }
  }
}

기록일: 2026년 11월 12일
작성자: 예브게니 (OpenShift & Cloud Infra Engineering)