ReferenceKubernetes

OpenTelemetry, ClickStack과 SeaweedFS cold tier 운영 참조

외부 OTLP 인증, node agent 재수집 차단, gateway 내구성 queue, ClickHouse cold S3 보존과 SeaweedFS S3 audit 경로를 조회합니다.

업데이트 검증 근거 자료이 페이지 편집

핵심 요약

  • 외부 OTLP는 otel.jamie.kr:443에서만 받고 Gateway API ExternalAuth가 Bearer token을 검증한 뒤 내부 otel-gateway로 전달함
  • node agent는 clickstack-ingest 자신의 container log만 제외해 순환 수집을 차단하고 나머지 workload 로그 수집을 유지함
  • otel-gateway는 OTLP 4317·4318과 Fluent Forward 8006을 수신하고 file-backed queue로 downstream 장애를 흡수함
  • ClickHouse는 2일 재압축, 3일 cold_s3 이동, 14일 삭제 TTL로 hot storage와 SeaweedFS S3 cold tier를 분리함
  • SeaweedFS S3 access audit은 Fluent Forward로 gateway에 전달되고 seaweedfs.s3.access dataset으로 일반 telemetry와 구분됨

수집 경로와 책임을 분리함

외부 Codex·SDK                         Kubernetes node
   │ HTTPS :443 + Bearer token            │ container·host·kubelet telemetry
   ▼                                      ▼
Gateway API ── ExternalAuth ──► otel-gateway ◄── otel-node DaemonSet
   │                                   │
   │ OTLP/gRPC :4317, HTTP :4318         ├── OTLP sink ──► clickstack-ingest
   │                                     │                    │
SeaweedFS S3 ── Fluent Forward :8006 ───┘                    ▼
                                                            ClickHouse
                                                              ├── hot disk
                                                              └── SeaweedFS S3 cold tier
  • Gateway API는 인터넷 노출, TLS 종료, hostname과 OTLP path allowlist를 소유함
    • otel.jamie.kr은 Gateway의 HTTPS listener에 연결됨
    • Collector Service와 Pod의 4317, 4318은 외부에 직접 노출하지 않음
  • otel-gateway는 인증된 signal, node telemetry와 S3 audit을 하나의 exporter 경계에서 처리함
    • gateway는 sink retry와 disk-backed sending queue를 소유함
    • clickstack-ingest는 gateway의 OTLP sink만 ingress로 허용함
  • ClickHouse는 telemetry 조회와 TTL 실행을 소유하고 SeaweedFS는 cold object와 S3 access audit을 소유함
    • SeaweedFS Master·Volume data PVC는 object-rwo StorageClass로 10.25.140.6:/s3_data에 배치됨
    • object data의 물리 저장소와 ClickHouse hot disk를 분리해 NFS 용량 압박의 원인을 구분함

외부 OTLP를 인증 후 gateway로 전달함

  • gRPC와 OTLP/HTTP는 path가 다르므로 같은 HTTPS endpoint에서 별도 backend port로 분기함
요청 종류공개 경로내부 backend
OTLP/gRPC logs·metrics·traces/opentelemetry.proto.collector.*.v1.*Service/Exportotel-gateway:4317
OTLP/HTTP logs/v1/logsotel-gateway:4318
OTLP/HTTP metrics/v1/metricsotel-gateway:4318
OTLP/HTTP traces/v1/tracesotel-gateway:4318
  • 각 route rule은 ExternalAuthotel-external-auth:8080을 먼저 호출함
    • auth Service는 정확한 Authorization: Bearer <token>에만 200을 반환함
    • 토큰이 없거나 일치하지 않는 요청은 gateway에서 401으로 종료됨
    • route는 Authorization만 auth backend로 전달하고 인증 성공 표시는 X-OTLP-Authenticated로 제한함
  • token 원문은 Git에 두지 않고 SOPS ciphertext에서 Kubernetes Secret으로 materialize함
    • 클라이언트는 표준 OTEL_EXPORTER_OTLP_HEADERSAuthorization=Bearer%20<token>을 주입함
    • Codex 설정에는 /v1/logs, /v1/metrics, /v1/traces endpoint만 유지함
  • 공개 ingress와 내부 backend는 NetworkPolicy로 별도 제한함
    • Gateway ingress entity는 gateway의 4317, 4318에만 접근 가능해야 함
    • auth Service는 ingress, host와 remote node의 8080만 허용함
거부·허용과 route 상태 확인
curl --silent --output /dev/null --write-out '%{http_code}\n' \
  --request POST --header 'Content-Type: application/x-protobuf' \
  --data-binary '' https://otel.jamie.kr/v1/logs

kubectl -n observability get httproute otel-external -o wide
kubectl -n observability rollout status statefulset/otel-gateway
kubectl -n observability rollout status deployment/otel-external-auth

node agent가 ClickStack 재수집을 막음

  • 재수집은 collector가 쓴 로그를 node agent가 다시 읽어 같은 collector로 보내는 순환 구조에서 발생함
    • clickstack-ingest는 OTLP sink의 수신 workload이므로 자신의 container stdout·stderr를 다시 수집하면 volume이 증폭될 수 있음
    • 다른 application 로그를 넓게 제외하면 관측 공백이 생기므로 자기 자신만 정확히 제외해야 함
  • otel-node DaemonSet의 logs pipeline은 namespace와 Pod 이름을 함께 검사하는 filter를 적용함
otel-agent values의 logs processor 순서
processors:
  filter/drop-clickstack-ingest-self:
    error_mode: ignore
    logs:
      log_record:
        - >-
          resource.attributes["k8s.namespace.name"] == "observability" and
          IsMatch(resource.attributes["k8s.pod.name"], "^clickstack-ingest-[0-9]+$")

service:
  pipelines:
    logs:
      processors:
        - memory_limiter
        - resource/role
        - filter/drop-clickstack-ingest-self
        - batch
  • 정규식은 StatefulSet Pod만 대상으로 하므로 clickstack-ingest-*가 아닌 observability workload는 계속 수집됨
    • deployment 이름이나 namespace 단독 match로 확대하면 새 workload가 의도치 않게 제외될 수 있음
    • collector release 이름이나 Pod 이름이 바뀌면 filter와 실제 metadata를 함께 갱신해야 함
  • 검증은 수집량과 제외 대상을 함께 조회해야 함
ClickHouse에서 최근 재수집 여부 조회
SELECT
  ResourceAttributes['k8s.pod.name'] AS pod,
  count() AS records
FROM default.otel_logs
WHERE Timestamp >= now() - INTERVAL 3 MINUTE
  AND ResourceAttributes['k8s.namespace.name'] = 'observability'
  AND match(ResourceAttributes['k8s.pod.name'], '^clickstack-ingest-[0-9]+$')
GROUP BY pod
ORDER BY records DESC;
  • 기대 결과는 filter 적용 뒤 해당 query가 0 record이고 다른 application 로그는 계속 유입되는 상태임

gateway가 신호별 pipeline과 내구성 queue를 제공함

  • otel-gateway는 두 replica StatefulSet으로 실행하고 각 replica에 1Gi의 retained queue PVC를 제공함
    • file_storage extension은 /var/lib/otel/storage에 queue를 저장함
    • replica 축소나 StatefulSet 삭제 때 queue PVC가 Retain이므로 데이터 정리에는 별도 승인과 절차가 필요함
  • 일반 OTLP와 S3 audit은 다른 receiver·resource processor·logs pipeline으로 처리함
pipelinereceiver공통 exporter구분 attribute
logsOTLPotlp/sinkplatform.telemetry.role=gateway
metricsOTLPotlp/sinkplatform.telemetry.role=gateway
tracesOTLPotlp/sinkplatform.telemetry.role=gateway
logs/s3-auditFluent Forward 8006otlp/sinkevent.dataset=seaweedfs.s3.access
  • gateway exporter는 queue 8192, retry 초기 간격 5s, 최대 간격 30s, 무기한 retry를 사용함
    • sink 장애가 장기화되면 PVC와 node storage가 채워질 수 있으므로 queue 사용량, retry와 exporter error를 함께 alert해야 함
    • queue는 delivery resilience를 제공하지만 ClickHouse retention이나 백업 정책을 대체하지 않음
  • gateway ingress는 telemetry namespace, SeaweedFS audit sender와 public ingress의 port별 흐름으로 제한함
    • 4317, 4318은 OTLP에만 사용함
    • TCP와 UDP 8006은 SeaweedFS와 clickhouse-s3-gateway의 Fluent Forward audit에만 사용함
gateway 수신과 queue 확인
kubectl -n observability get svc otel-gateway
kubectl -n observability get pvc -l app.kubernetes.io/instance=otel-gateway
kubectl -n observability logs statefulset/otel-gateway --since=10m

ClickHouse cold S3를 최소 권한 계정으로 보존함

  • ClickHouse는 local default disk와 SeaweedFS S3 cold_s3 disk를 묶은 hot_cold storage policy를 사용함
    • internal clickhouse-s3-gateway:8333이 SeaweedFS filer를 향하는 S3 endpoint임
    • cold data bucket은 clickhouse-observability
    • object-rwo는 SeaweedFS 자체 Master·Volume data의 physical storage 계약이며 ClickHouse S3 client가 NFS를 직접 mount하는 경로가 아님
  • telemetry 테이블 TTL은 hot cost와 조회 기간을 명시적으로 분리함
데이터 연령ClickHouse 동작저장 위치
0~2일기본 codec·hot querydefault disk
2일ZSTD(3) 재압축default disk
3일TO DISK 'cold_s3'SeaweedFS S3
14일DELETE삭제됨
  • cold tier는 otel-cold-tier 전용 AccessKey와 SecretKey를 사용함
    • account는 clickhouse-observability bucket에만 Admin, Read, List, Tagging, Write action을 가짐
    • SeaweedFS 전역 admin identity를 ClickHouse 설정에 사용하지 않음
    • credentials와 SeaweedFS identity configuration은 SOPS ciphertext로 Git에 보관함
  • TTL과 storage policy 변경은 모든 replica에서 수렴했는지 확인한 뒤 오래된 part의 disk를 조회해야 함
cold S3 part와 TTL 정의 조회
SELECT disk_name, count() AS parts, formatReadableSize(sum(bytes_on_disk)) AS size
FROM system.parts
WHERE active AND disk_name = 'cold_s3'
GROUP BY disk_name;

SELECT name, create_table_query
FROM system.tables
WHERE database = 'default'
  AND position(create_table_query, "storage_policy = 'hot_cold'") > 0;
  • NFS PVC request 용량은 NFS server quota가 아니므로 physical free space와 S3 object 증가량을 별도로 관측해야 함

SeaweedFS S3 access audit을 Fluent Forward로 수집함

  • 공개 SeaweedFS S3와 internal clickhouse-s3-gateway 모두 auditLogConfig를 사용해 동일 gateway로 audit을 전송함
    • destination은 otel-gateway.observability.svc.cluster.local:8006
    • request_ack: true는 audit receiver의 acknowledgement를 기다려 network failure를 감지함
    • tag prefix는 public S3와 cold tier gateway를 구분하는 보조 정보임
  • gateway의 fluent_forward/s3-audit receiver는 audit record에 표준 resource attribute를 추가함
    • k8s.cluster.name=jamie-kr
    • service.name=seaweedfs-s3-audit
    • event.dataset=seaweedfs.s3.access
  • audit pipeline은 S3 요청의 성공·실패와 actor를 추적할 수 있지만 object payload를 보관하는 data pipeline이 아님
    • audit record의 필드 구조는 SeaweedFS version과 operation에 따라 달라질 수 있으므로 query 전에 raw attribute를 확인해야 함
    • audit retention은 일반 telemetry TTL을 따르므로 장기 보존·규제 요구가 있으면 별도 export와 retention 계약이 필요함
최근 SeaweedFS S3 audit 조회
SELECT
  Timestamp,
  Body,
  ResourceAttributes['service.name'] AS service,
  ResourceAttributes['event.dataset'] AS dataset
FROM default.otel_logs
WHERE Timestamp >= now() - INTERVAL 15 MINUTE
  AND ResourceAttributes['event.dataset'] = 'seaweedfs.s3.access'
ORDER BY Timestamp DESC
LIMIT 100;

운영 검증과 변경 순서를 유지함

  • 변경 전에는 route, receiver, identity, storage policy와 NetworkPolicy의 소유자가 모두 GitOps desired state인지 확인함
    • runtime에서 Secret 또는 collector config를 직접 수정하면 다음 Argo CD sync에서 되돌아갈 수 있음
    • SOPS plaintext를 terminal 출력, commit message, CI log와 debugging artifact에 남기지 않음
  • 배포 후에는 경계별 acceptance check를 순서대로 실행함
    1. 외부 OTLP 무인증 401과 인증 200 확인
    2. gateway OTLP signal 및 exporter retry·queue 상태 확인
    3. node agent의 clickstack-ingest 재수집 0 record 확인
    4. ClickHouse hot_cold 정책·TTL과 cold_s3 part 확인
    5. otel-cold-tier credential로 bucket scoped S3 요청 확인
    6. S3 요청 뒤 seaweedfs.s3.access audit record 확인
  • SeaweedFS PVC migration과 Released PV cleanup은 object integrity와 rollback 보존 기간을 분리해 승인함
    • migration 직후의 이전 /k8s_data PV를 삭제하면 storage path rollback 선택지가 사라짐
    • data copy, SeaweedFS readiness, S3 access, cold part와 audit 수집을 모두 검증한 뒤 rollback PV 보존 종료를 진행함

실행 제안

  • 새 receiver·exporter·TTL 변경은 하나의 GitOps sync에 묶되 acceptance query는 signal, storage와 audit 경계별로 분리해 실행함
  • S3 계정은 workload별로 분리하고 SeaweedFS global admin credentials를 telemetry runtime에 배포하지 않음
  • queue, cold S3 object 증가량, 14일 TTL 삭제와 audit 유입량을 함께 대시보드화해 비용과 누락을 동시에 감시함