OKD 4.7·4.11 노드 journald 로그를 중앙 Syslog로 전달하고 사전 검증하기

|Platform Decision|15분 읽기

OKD 노드의 systemd journal 로그를 외부 로그 서버로 보내야 할 때가 있습니다.

노드 저널이 디스크를 먹는 이야기는 앞서 두 번 썼어요. 로그 몇 줄 남겼을 뿐인데, 왜 Kubernetes 노드의 디스크 Util은 95%가 될까?에서 왜 그런 일이 생기는지 정리했고, OKD 노드 디스크를 저널이 다 먹었습니다에서는 노드 안에서 보존 설정으로 막는 방법을 다뤘습니다. 두 글 모두 "그래서 밖으로 빼야 한다"에서 끝났습니다. 이 글이 그 다음입니다.

처음에는 노드마다 Podman Quadlet이나 별도 Fluentd 컨테이너를 띄우는 쪽을 떠올리기 쉽습니다. 하지만 OKD 4.7과 4.11에는 이미 노드 로그를 수집하고 외부로 전달하는 기능이 들어 있습니다.

두 버전을 함께 관리해야 한다면 다음 구성이 가장 단순합니다.

Fluentd
  + ClusterLogForwarder
  + TCP Syslog
  + RFC5424

이 글에서 다룰 내용입니다.

  • OKD가 바이너리 journal을 어떤 방식으로 읽는지
  • OKD 4.7과 4.11에서 공통으로 사용할 설정
  • 중앙 Syslog 서버 없이 전송 기능을 검증하는 방법
  • 운영 적용 전에 확인해야 할 주의사항

결론부터 보기

OKD 4.7과 4.11 모두 ClusterLogForwarder로 노드 journal 로그를 외부 Syslog 서버에 전달합니다.

두 버전에서 공통으로 쓸 API입니다.

apiVersion: logging.openshift.io/v1
kind: ClusterLogForwarder

로그 흐름은 이렇습니다.

노드의 systemd-journald
        ↓
노드별 Fluentd Collector
        ↓
OKD ViaQ 로그 레코드로 정규화
        ↓
ClusterLogForwarder
        ↓
RFC5424 텍스트 메시지
        ↓
TCP Syslog 서버

별도의 Quadlet 수집기를 각 노드에 설치할 필요가 없습니다.

journal 파일은 바이너리인데 어떻게 읽을까

systemd journal은 일반 텍스트 파일이 아닙니다.

/var/log/journal/.../*.journal

systemd 전용 바이너리 형식이라 cat이나 tail 같은 명령으로는 직접 읽지 못합니다.

OKD의 Fluentd Collector도 이 파일을 그대로 복사해 보내지는 않습니다. systemd journal API로 각 로그 레코드와 필드를 읽습니다.

journal 레코드는 논리적으로 이런 정보를 담습니다.

{
  "MESSAGE": "Started Kubernetes Kubelet.",
  "_HOSTNAME": "worker-01",
  "_SYSTEMD_UNIT": "kubelet.service",
  "SYSLOG_IDENTIFIER": "kubelet",
  "_PID": "1234",
  "_UID": "0",
  "PRIORITY": "6",
  "__REALTIME_TIMESTAMP": "1787712345000000"
}

Fluentd는 이 레코드를 OKD의 ViaQ 로그 데이터 구조로 정규화합니다. 버전과 로그 종류에 따라 세부 필드는 달라지지만, 큰 틀은 이렇습니다.

{
  "@timestamp": "2026-08-26T12:34:56.123456Z",
  "hostname": "worker-01",
  "message": "Started Kubernetes Kubelet.",
  "level": "info",
  "log_type": "infrastructure",
  "systemd": {
    "u": {
      "_SYSTEMD_UNIT": "kubelet.service",
      "_PID": "1234",
      "_UID": "0",
      "_HOSTNAME": "worker-01"
    },
    "t": {
      "SYSLOG_IDENTIFIER": "kubelet",
      "PRIORITY": "6"
    }
  }
}

바이너리 .journal 파일이 그대로 나가는 게 아닙니다. Fluentd가 journal API로 레코드를 읽고, 구조화된 로그를 RFC5424 텍스트로 바꿔서 보냅니다.

infrastructure 로그의 범위

ClusterLogForwarder에서 다음 입력을 사용하면 노드 journal 로그가 포함됩니다.

inputRefs:
  - infrastructure

infrastructure는 journal만 가리키지 않습니다. 보통 이런 로그가 함께 들어옵니다.

  • OKD 노드의 journald 로그
  • 운영체제와 CRI-O 런타임 로그
  • OKD 인프라 컴포넌트 로그
  • openshift-* 프로젝트의 컨테이너 로그
  • kube-* 프로젝트의 컨테이너 로그
  • default 프로젝트의 인프라 로그

특정 systemd unit 하나만 골라 보내는 설정은 아닌 셈입니다. 서비스 하나만 필요하다면 중앙 로그 서버에서 _SYSTEMD_UNIT, SYSLOG_IDENTIFIER, hostname, message 같은 필드로 걸러내는 편이 낫습니다.

광고

OKD 4.7·4.11 공통 설정

다음 설정은 노드 journal과 인프라 로그를 TCP Syslog 서버로 전달합니다.

apiVersion: logging.openshift.io/v1
kind: ClusterLogForwarder
metadata:
  name: instance
  namespace: openshift-logging
spec:
  outputs:
    - name: central-syslog
      type: syslog
      url: tcp://syslog.example.com:514
      syslog:
        rfc: RFC5424

  pipelines:
    - name: infrastructure-to-syslog
      inputRefs:
        - infrastructure
      outputRefs:
        - central-syslog
        - default
      labels:
        source: okd

적용합니다.

oc apply -f cluster-log-forwarder.yaml

두 버전 모두 이 조건을 지켜야 합니다.

  • 리소스 이름은 instance
  • 네임스페이스는 openshift-logging
  • API는 logging.openshift.io/v1
  • 수집기는 Fluentd 사용 권장

default 출력의 의미

다음 설정은 외부 Syslog와 기존 내부 로그 저장소 양쪽으로 전달합니다.

outputRefs:
  - central-syslog
  - default

내부 Elasticsearch 저장이 필요 없고 외부로만 보낸다면 default를 뺍니다.

outputRefs:
  - central-syslog

다만 ClusterLogForwarder를 생성하면 파이프라인에 정의하지 않은 로그 유형이 기존 내부 저장소에도 전달되지 않을 수 있습니다. 운영 환경에 기존 ClusterLogForwarder/instance가 있다면 반드시 현재 설정을 먼저 백업하고 기존 파이프라인에 병합해야 합니다.

oc -n openshift-logging get clusterlogforwarder instance -o yaml \
  > clusterlogforwarder-backup.yaml

RFC5424로 전송되는 형태

RFC5424 메시지의 구조입니다.

<PRI>VERSION TIMESTAMP HOSTNAME APP-NAME PROCID MSGID STRUCTURED-DATA MESSAGE

실제로는 이런 모양입니다.

<134>1 2026-08-26T12:34:56Z worker-01 kubelet 1234 - - Started Kubernetes Kubelet.

다만 Fluentd가 정규화한 전체 레코드를 보낼지, message 필드만 보낼지는 payloadKey 설정에 따라 달라집니다.

전체 레코드 확인

초기 검증에서는 payloadKey를 지정하지 않는 편이 좋아요.

syslog:
  rfc: RFC5424

그러면 정규화된 레코드가 Syslog 메시지 본문에 직렬화되어 오는지 확인합니다. 개념적인 출력 예시입니다.

<134>1 2026-08-26T12:34:56Z worker-01 fluentd - - -
{"hostname":"worker-01","message":"Started Kubernetes Kubelet.",
"log_type":"infrastructure","systemd":{"u":{"_SYSTEMD_UNIT":
"kubelet.service","_PID":"1234"},"t":{"SYSLOG_IDENTIFIER":
"kubelet","PRIORITY":"6"}}}

원본 메시지만 전달

이렇게 설정하면 로그 레코드의 message 필드만 Syslog 본문으로 씁니다.

syslog:
  rfc: RFC5424
  payloadKey: message

출력은 훨씬 단순해집니다.

<134>1 2026-08-26T12:34:56Z worker-01 fluentd - - - Started Kubernetes Kubelet.

하지만 _SYSTEMD_UNIT, _PID, _UID, SYSLOG_IDENTIFIER, 원래 PRIORITY, systemd cgroup 관련 정보 같은 메타데이터는 본문에서 빠질 수 있습니다.

그래서 최초 검증에서는 전체 레코드를 본 다음 필요한 필드 수준을 정하는 편이 낫습니다.

중앙 서버 없이 OKD 내부에서 검증하기

실제 중앙 Syslog 서버가 준비되지 않았더라도 OKD 내부에 임시 TCP 수신 Pod를 만들어 전송 기능을 검증합니다.

임시 수신기 생성

다음 리소스는 5514/TCP 포트에서 데이터를 받아 그대로 Pod 로그에 출력합니다.

apiVersion: v1
kind: Namespace
metadata:
  name: syslog-test
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tcp-receiver
  namespace: syslog-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tcp-receiver
  template:
    metadata:
      labels:
        app: tcp-receiver
    spec:
      containers:
        - name: receiver
          image: registry.access.redhat.com/ubi8/python-39:latest
          command:
            - python
            - -u
            - -c
          args:
            - |
              import socket
              import sys

              server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
              server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
              server.bind(("0.0.0.0", 5514))
              server.listen(20)

              print("TCP receiver listening on port 5514", flush=True)

              while True:
                  connection, address = server.accept()
                  print("CONNECTED:", address, flush=True)

                  while True:
                      data = connection.recv(65535)
                      if not data:
                          break

                      sys.stdout.write(
                          data.decode("utf-8", errors="replace")
                      )
                      sys.stdout.flush()

                  connection.close()
---
apiVersion: v1
kind: Service
metadata:
  name: tcp-receiver
  namespace: syslog-test
spec:
  selector:
    app: tcp-receiver
  ports:
    - name: syslog
      protocol: TCP
      port: 5514
      targetPort: 5514

적용합니다.

oc apply -f syslog-test.yaml
oc -n syslog-test get pods

수신 로그를 실시간으로 확인합니다.

oc -n syslog-test logs -f deployment/tcp-receiver

ClusterLogForwarder 연결

임시 수신기의 내부 서비스 주소입니다.

tcp-receiver.syslog-test.svc:5514

테스트용 ClusterLogForwarder 설정입니다.

apiVersion: logging.openshift.io/v1
kind: ClusterLogForwarder
metadata:
  name: instance
  namespace: openshift-logging
spec:
  outputs:
    - name: syslog-test
      type: syslog
      url: tcp://tcp-receiver.syslog-test.svc:5514
      syslog:
        rfc: RFC5424

  pipelines:
    - name: infrastructure-syslog-test
      inputRefs:
        - infrastructure
      outputRefs:
        - syslog-test
        - default
oc apply -f clusterlogforwarder-test.yaml
광고
광고

테스트용 journal 로그 발생시키기

노드에서 알아보기 쉬운 journal 메시지를 하나 만듭니다. 먼저 노드를 고릅니다.

NODE=$(oc get nodes -o jsonpath='{.items[0].metadata.name}')
oc debug node/${NODE}

디버그 셸에서 호스트 환경으로 들어갑니다.

chroot /host

logger로 테스트 메시지를 만듭니다.

logger -p local0.info "OKD-SYSLOG-TEST-$(date +%s)"

디버그 셸을 종료합니다.

exit
exit

수신 Pod에서 테스트 메시지를 검색합니다.

oc -n syslog-test logs deployment/tcp-receiver \
  | grep OKD-SYSLOG-TEST

이 세 가지가 확인되면 전송 기능은 정상입니다.

  1. 수신 Pod에 TCP 연결이 표시됩니다.
  2. RFC5424 형태의 텍스트가 수신됩니다.
  3. OKD-SYSLOG-TEST-* 메시지가 검색됩니다.

여기까지 됐다면 운영 적용 때는 URL만 실제 Syslog 서버 주소로 바꾸면 됩니다.

url: tcp://실제-syslog-서버:514

테스트 후 원상 복구

기존 ClusterLogForwarder가 있었다면 백업 설정을 복구합니다.

oc apply -f clusterlogforwarder-backup.yaml

기존 설정이 없었다면 테스트용 리소스를 삭제합니다.

oc -n openshift-logging delete clusterlogforwarder instance

임시 수신기도 삭제합니다.

oc delete namespace syslog-test

운영 적용 시 고려사항

TCP와 TLS

OKD 4.7과 4.11을 동일한 설정으로 관리하기에는 TCP가 가장 단순합니다.

url: tcp://syslog.example.com:514

하지만 로그가 신뢰할 수 없는 네트워크를 통과한다면 TLS를 써야 합니다.

url: tls://syslog.example.com:6514

OKD 4.7과 4.11은 Logging Operator 버전에 따라 TLS Secret의 인증서 키 요구사항이 다를 수 있습니다. 설정을 하나로 단순화하는 게 목적이라면 내부 전용망이나 VPN 구간에서는 TCP를 쓰고 암호화는 네트워크 계층에 맡기는 방법도 있습니다.

원래 severity 보존

다음 설정은 원래 journal의 priority를 자동으로 쓰는 것이 아닙니다.

facility: local0
severity: informational

새로 만들어지는 Syslog 메시지의 facility와 severity를 고정하는 설정입니다. 원래 journald의 PRIORITY와 SYSLOG_FACILITY는 정규화된 레코드 안에 별도 필드로 남아 있을 수 있습니다.

원본 값을 확인해야 한다면 초기 검증 단계에서는 facility, severity, payloadKey를 최소한으로 두는 게 좋아요.

journal 원본 백업과의 차이

Syslog 포워딩은 로그 이벤트를 검색하고 분석하려는 방식입니다. 이런 목적에는 맞지 않습니다.

  • .journal 파일의 원본 보존
  • systemd journal 바이너리의 완전한 복제
  • 디지털 포렌식 목적의 원본 증거 보존

이런 요구사항이 있다면 .journal 파일 백업이나 systemd journal export 형식을 별도로 검토해야 합니다.

광고

최종 정리

OKD 4.7과 4.11의 노드 journal 로그를 한 가지 방식으로 관리하려면 이 구성을 추천합니다.

Fluentd Collector
  + logging.openshift.io/v1 ClusterLogForwarder
  + infrastructure 입력
  + TCP Syslog
  + RFC5424

짚어둘 사항입니다.

  • journal 바이너리 파일을 직접 보내는 것이 아닙니다.
  • Fluentd가 systemd journal API로 레코드를 읽습니다.
  • 읽은 레코드는 OKD ViaQ 데이터 구조로 정규화됩니다.
  • ClusterLogForwarder가 이를 RFC5424 텍스트로 바꿉니다.
  • payloadKey: message를 쓰면 원문만 전송되고 일부 메타데이터가 빠집니다.
  • 최초 검증에서는 payloadKey를 생략해 전체 구조를 확인하는 편이 낫습니다.
  • 중앙 서버가 없어도 OKD 내부 임시 TCP 수신기로 검증합니다.
  • 기존 ClusterLogForwarder/instance가 있다면 덮어쓰지 말고 설정을 병합해야 합니다.

이 글이 도움이 되셨나요?

버튼 하나가 다음 글을 쓰는 힘이 됩니다

광고
#OKD#journald#Syslog#ClusterLogForwarder#Fluentd#RFC5424#로그 수집