Skip to content
Published on

OTel의 Kubernetes 속성이 stable이 됐다 — k8sattributes 기본값이 뒤집히기 전에 할 일

공유하기
Authors

들어가며 — stable 승급은 조용히 지나갔다

2026년 6월 12일, semconv v1.42.0이 나오면서 OpenTelemetry의 Kubernetes 속성 한 묶음이 stable로 승급했습니다. 체인지로그의 한 줄은 이렇게 적혀 있습니다 — "Promote a selection of k8s and container registry attributes to stable".

그리고 아무 일도 일어나지 않았습니다.

정확히 말하면, 아무 일도 일어나지 않도록 설계돼 있습니다. 당신이 Collector의 k8sattributes 프로세서를 돌리고 있다면, 지금 이 순간에도 옛 스키마(이하 v0)를 그대로 내보내고 있을 겁니다. 최신 릴리스인 collector-contrib v0.156.0(2026-07-07) 기준으로도 관련 피처 게이트는 여전히 alpha이고, alpha에서는 기본값이 v0이기 때문입니다.

이건 유예이지 면제가 아닙니다. Collector의 RFC가 정한 롤아웃대로라면 게이트가 beta로 올라가는 순간 기본 동작이 v1 전용으로 뒤집힙니다. 그리고 그 변경의 성질이 고약합니다 — 에러가 나지 않습니다. 대시보드가 그냥 빕니다.

이 글은 실제로 무엇이 바뀌는지, 언제 뒤집히는지, 지금 열려 있는 창구에서 무엇을 해야 하는지를 정리합니다. 그리고 마지막에, 이게 생각보다 훨씬 좁은 문제라는 이야기도 하겠습니다.

4개월간의 승급 타임라인

먼저 사실관계를 시간순으로 놓겠습니다. 전부 semconv 저장소의 릴리스와 머지된 PR에서 확인할 수 있는 것들입니다.

2026-02-19  semconv v1.40.0   k8s.pod.name 등 -> stability: beta
2026-03-02  PR #3491 오픈      "Promote a selection of k8s/container attributes to RC"
2026-03-09  PR #3491 머지      beta -> release_candidate
2026-03-16  공식 블로그 공지    "Kubernetes attributes promoted to release candidate"
2026-04-28  semconv v1.41.0   42개 속성이 release_candidate 로 릴리스에 반영
2026-05-11  semconv v1.41.1   여전히 release_candidate
2026-06-12  semconv v1.42.0   -> stable  (체인지로그 #3382)
2026-07-03  semconv v1.43.0   stable 유지
2026-07-07  contrib v0.156.0  피처 게이트는 여전히 alpha (v0.145.0부터)

PR #3491의 본문에는 의도가 명확히 적혀 있습니다 — "이것이 stable로 만들기 전 마지막 중간 단계 승급이며, 피드백을 받기 위한 마지막 호출"이라고요. 3월 16일 공식 블로그(Christos Markou/Elastic, David Ashpole/Google)도 "release_candidate에 머무는 동안이 피드백을 줄 때"라고 못박았습니다.

그 창구는 6월 12일에 닫혔습니다. 속성은 이제 stable입니다.

여기서 눈여겨볼 대목은 마지막 두 줄의 간극입니다. semconv는 6월 12일에 stable을 선언했는데, 7월 7일 Collector 릴리스에서도 게이트는 alpha입니다. 뒤에서 보겠지만 RFC는 "semconv가 stable로 표시하는 릴리스와 같은 Collector 릴리스에서 게이트를 beta로 올린다"고 적어 놨습니다. 즉 지금은 RFC가 그린 그림보다 늦어져 있는 상태입니다. 왜 늦어졌는지, 정확히 어느 릴리스에서 뒤집힐지는 제가 확인할 수 있는 자료에 없습니다 — 그러니 날짜를 지어내지 않겠습니다. 다만 방향은 정해져 있고, 당신에게 주어진 건 그 사이의 시간입니다.

실제로 깨지는 7가지

v0과 v1 사이의 breaking change는 k8sattributes 프로세서 README에 정확히 7개로 명시돼 있습니다.

container.image.tag              ->  container.image.tags
k8s.pod.labels.KEY               ->  k8s.pod.label.KEY
k8s.pod.annotations.KEY          ->  k8s.pod.annotation.KEY
k8s.node.labels.KEY              ->  k8s.node.label.KEY
k8s.node.annotations.KEY         ->  k8s.node.annotation.KEY
k8s.namespace.labels.KEY         ->  k8s.namespace.label.KEY
k8s.namespace.annotations.KEY    ->  k8s.namespace.annotation.KEY

여섯 개는 복수형에서 단수형으로 바뀌는 것이 전부입니다. labelslabel이 되고, annotationsannotation이 됩니다. 실제 속성 이름으로 보면 이렇습니다.

v0:  k8s.pod.labels.app       = "checkout"
v1:  k8s.pod.label.app        = "checkout"

글자 하나입니다. 그리고 바로 이 점이 위험합니다. 이름이 완전히 달라지면 쿼리가 에러를 내거나 눈에 띄게 깨지지만, 존재하지 않는 속성으로 필터하는 쿼리는 대개 조용히 빈 결과를 냅니다. 알림은 발화하지 않고(조건에 맞는 시계열이 없으니), 대시보드 패널은 "No data"를 띄우고, 아무도 새벽에 깨지 않습니다. 그게 며칠 뒤 "그러고 보니 이 그래프 언제부터 비어 있었지?"로 돌아옵니다.

일곱 번째는 성격이 다릅니다. container.image.tag에서 container.image.tags로 가는 건 이름만 바뀌는 게 아니라 타입이 바뀝니다. 프로세서 문서에 따르면 전자는 Any Str, 후자는 Any Slice입니다. 문자열 하나에서 배열로 바뀌는 것이라, 이름을 고쳐도 tag = "v1.2.3" 같은 등치 필터는 배열에 대해 원하는 대로 동작하지 않습니다. 이건 문자열 치환으로 끝나지 않고 쿼리 자체를 다시 써야 하는 변경입니다.

두 개의 피처 게이트, 그리고 진리표

Collector는 SDK 쪽의 OTEL_SEMCONV_STABILITY_OPT_IN 환경변수 방식을 일부러 쓰지 않습니다. RFC의 표현을 빌리면 그 방식은 "Collector 네이티브하게 느껴지지 않고", 특히 업그레이드 후 이전 동작으로 롤백하는 걸 지원하지 않기 때문입니다. 대신 짝을 이루는 피처 게이트 두 개를 씁니다. 둘 다 v0.145.0부터 있고, 현재 alpha입니다.

  • processor.k8sattributes.EmitV1K8sConventions — 켜면 새(stable) 스키마를 내보냅니다.
  • processor.k8sattributes.DontEmitV0K8sConventions — 켜면 옛(legacy) 스키마를 끕니다.

핵심은 이 둘이 독립적이라는 점입니다. 그래서 네 가지 조합이 나오고, RFC가 진리표를 이렇게 정의합니다.

EmitV1DontEmitV0결과 동작
꺼짐꺼짐v0만 발행 (현재 기본값)
꺼짐켜짐시작 시 에러 — 아무 텔레메트리도 안 나가므로
켜짐꺼짐v0과 v1 둘 다 발행 (이중 발행)
켜짐켜짐v1만 발행

세 번째 줄이 이 글의 요점입니다. 둘 다 켜는 게 아니라 EmitV1만 켜면 두 스키마가 동시에 나갑니다. 마이그레이션 기간에 옛 대시보드와 새 대시보드가 함께 살아 있을 수 있다는 뜻입니다.

두 번째 줄도 알아둘 만합니다. "새 걸 끄고 옛 걸 끈다"는 조합은 조용히 무시되는 게 아니라 기동 실패입니다. 설정 실수로 텔레메트리가 소리 없이 사라지는 것보다는 훨씬 나은 설계입니다.

게이트는 Collector 실행 시 플래그로 켭니다.

# 이중 발행: v0과 v1을 동시에 내보낸다 (마이그레이션 기간용)
otelcol-contrib --config=config.yaml \
  --feature-gates=+processor.k8sattributes.EmitV1K8sConventions

# 전환 완료: v1만 내보낸다
otelcol-contrib --config=config.yaml \
  --feature-gates=+processor.k8sattributes.EmitV1K8sConventions,+processor.k8sattributes.DontEmitV0K8sConventions

RFC의 4단계 롤아웃 — 기본값은 언제 뒤집히나

semconv-feature-gates RFC는 게이트 한 쌍이 밟아 갈 단계를 이렇게 못박아 놨습니다. 두 게이트는 항상 같은 속도로 함께 움직입니다.

  1. alpha — 둘 다 기본 꺼짐. 기본 동작은 v0만 발행. 사용자는 원하면 이중 발행이나 v1 전용으로 옵트인할 수 있습니다. RFC는 이 단계에서 컴포넌트가 기동 시 경고 로그를 남겨야 한다고 규정합니다("A warning message must be logged by the component at startup indicating the upcoming change").
  2. beta — semconv가 해당 컨벤션을 stable로 표시하는 릴리스가 나오면, 같은 Collector 릴리스에서 게이트가 beta로 승급합니다. 이때 기본 동작이 v1 전용으로 뒤집힙니다. 이제는 반대로 v0을 쓰려면 옵트아웃해야 합니다.
  3. stable — beta에서 마이너 릴리스 4개가 지나면 stable 단계로 올라갑니다. 이 시점부터는 v1만 쓸 수 있습니다.
  4. 제거 — 거기서 마이너 릴리스 4개가 더 지나면 게이트 자체가 사라집니다.

지금 우리는 1단계에 있고, semconv 쪽 조건(v1.42.0의 stable 승급)은 이미 충족됐습니다. 그러니 2단계는 예정된 사건입니다. 위에서 말했듯 정확한 릴리스 번호는 확인되지 않으니 단정하지 않겠습니다. 하지만 계획을 세울 때 기준으로 삼을 사실은 두 가지입니다 — 뒤집힘은 Collector를 업그레이드하는 순간 찾아오고, beta 이후 마이너 릴리스 8개가 지나면 되돌릴 수단 자체가 없어집니다.

Collector를 자동 업그레이드하고 있다면(오퍼레이터의 이미지 태그를 latest로 뒀거나, 렌더링된 차트 버전을 고정하지 않았다면) 이 뒤집힘은 당신이 고른 날이 아니라 아무 날에나 옵니다.

지금 할 일 — 창구가 열려 있는 동안

순서가 중요합니다. RFC가 이중 발행 단계를 굳이 만들어 둔 이유가 이것입니다.

1. 내가 노출돼 있는지부터 확인합니다. Collector 설정에서 k8sattributes 프로세서의 extract 블록을 봅니다. labelsannotations 하위 항목이 있습니까? metadata 목록에 container.image.tag가 있습니까? 둘 다 아니라면 — 뒤에서 설명하듯 — 당신은 이 변경과 무관합니다.

2. 이중 발행을 켭니다. EmitV1K8sConventions만 켜서 v0과 v1이 동시에 나가게 합니다. 이 시점부터 백엔드에는 두 이름이 모두 존재하므로, 옛 대시보드는 계속 살아 있고 새 이름으로 쿼리를 시험해 볼 수 있습니다. 되돌리기도 쉽습니다.

3. 쿼리와 대시보드를 옮깁니다. 복수형에서 단수형으로 가는 여섯 개는 기계적 치환에 가깝습니다. container.image.tag는 앞서 말한 타입 변경 때문에 손으로 봐야 합니다.

4. v0을 끕니다. 새 쿼리가 데이터를 내는 걸 확인한 뒤에 DontEmitV0K8sConventions를 켭니다. 이 시점의 동작이 곧 앞으로의 기본값이므로, 미래를 미리 겪어 보는 셈입니다.

5. 그러고 나서 Collector를 올립니다. 이미 v1 전용으로 돌고 있다면 게이트가 beta로 뒤집혀도 당신에게는 아무 일도 일어나지 않습니다. 그게 목표입니다.

핵심은 2단계와 5단계를 분리하는 것입니다. 아무것도 안 하고 있다가 업그레이드를 하면 스키마 전환과 버전 업그레이드가 한날한시에 겹치고, 무언가 비었을 때 원인이 둘 중 무엇인지 알 수 없게 됩니다.

정직한 트레이드오프 — 이중 발행은 공짜가 아니다

이중 발행 단계를 "그냥 켜두면 안전한 것"처럼 말하고 싶지 않습니다. 대가가 있습니다.

리소스 속성이 두 벌이 됩니다. 레이블 5개를 추출하고 있었다면 이제 10개가 붙습니다. 이건 모든 스팬·메트릭·로그의 리소스에 실려 나가므로 네트워크와 저장 양쪽에 비용이 붙습니다. 정확히 몇 퍼센트가 늘어나는지는 당신이 몇 개의 레이블을 뽑고 있는지에 전적으로 달려 있어서, 여기서 숫자를 지어내지 않겠습니다 — 다만 자기 파이프라인에서 재 보는 건 어렵지 않습니다.

메트릭이라면 카디널리티 쪽이 더 아픕니다. 리소스 속성이 시계열 아이덴티티에 들어가는 백엔드에서는 같은 값을 담은 속성이 두 이름으로 존재하는 게 시계열 수에 영향을 줄 수 있습니다. 백엔드마다 리소스 속성을 다루는 방식이 달라서 일률적으로 말할 수 없으니, 이중 발행을 켜기 전에 작은 네임스페이스 하나에서 먼저 켜 보고 계측값을 보는 편이 안전합니다.

그래서 이중 발행은 종착지가 아니라 다리입니다. RFC의 롤아웃 자체가 그걸 전제합니다 — 이중 발행 조합은 stable 단계가 되면 사라집니다. 켰으면 끝을 봐야 합니다. "일단 켜두고 나중에"가 가장 비싼 선택입니다.

누가 실제로 영향받나 — 그리고 안 받나

여기가 이 글에서 제일 중요한 절일지도 모르겠습니다. 지금까지의 이야기는 "K8s 시맨틱 컨벤션이 깨진다"처럼 들리지만, 깨지는 범위는 소문보다 훨씬 좁습니다.

당신이 매일 쓰는 속성들은 안 바뀝니다. k8s.pod.name, k8s.namespace.name, k8s.deployment.name, k8s.node.name, k8s.container.name, k8s.pod.uid — 전부 이름 그대로입니다. breaking change 목록에 없습니다. 대부분의 대시보드는 이 속성들 위에 서 있고, 그런 대시보드는 아무 일도 겪지 않습니다.

바뀌는 건 레이블/어노테이션 추출과 container.image.tag뿐입니다. 그리고 레이블 추출조차 조건이 붙습니다 — 프로세서 README에 따르면 복수형 이름은 tag_name을 지정하지 않았을 때 쓰이는 기본 포맷입니다. 즉 extract 설정에서 레이블마다 tag_name을 명시해 자기 이름을 붙여 왔다면, 그 이름은 당신 것이고 이번 개명과 무관합니다. 영향을 받는 건 기본 포맷에 기대고 있던 설정입니다.

# tag_name 을 명시한 경우 — 속성 이름은 'l2'. 이번 변경과 무관.
extract:
  labels:
    - tag_name: l2
      key: label2
      from: pod

# tag_name 이 없는 경우 — 기본 포맷을 따르므로 영향을 받는다.
# v0: k8s.pod.labels.label2   ->   v1: k8s.pod.label.label2
extract:
  labels:
    - key: label2
      from: pod

그리고 stable이 된 것도 "일부"입니다. semconv v1.43.0의 k8s 레지스트리를 파싱해 보면 속성 89개 중 stable은 42개이고, 나머지 45개는 아직 development, 2개는 experimental입니다. 3월 블로그의 제목도 "a selection of"였습니다. 그러니 "K8s 컨벤션이 이제 다 안정됐다"고 읽으면 안 됩니다 — 워크로드 관련 상당수는 여전히 바뀔 수 있는 자리에 있습니다. 이번에 안정된 42개는 k8sattributesresourcedetection 같은, 안정화를 목표로 한 Collector 컴포넌트가 실제로 쓰던 것들입니다.

정리하면 이렇습니다.

지금 움직여야 하는 경우

  • k8sattributesextract에서 tag_name 없이 레이블/어노테이션을 뽑고 있다.
  • container.image.tag로 필터하거나 그룹핑하는 쿼리가 있다.
  • Collector를 자동으로 따라 올리고 있어서 업그레이드 시점을 당신이 고르지 않는다.

신경 안 써도 되는 경우

  • k8s.pod.name / k8s.namespace.name / k8s.deployment.name 정도만 쓴다 — 대다수가 여기 해당합니다.
  • 레이블 추출에 항상 tag_name을 명시해 왔다.
  • Collector 버전을 고정해 두고 업그레이드를 의도적으로 계획한다 — 그렇다면 이 글은 다음 업그레이드 티켓에 붙일 메모입니다.

마치며

이 이야기의 교훈은 "OTel이 또 이름을 바꿨다"가 아닙니다. 오히려 반대에 가깝습니다. semconv는 beta → release_candidate → stable을 4개월에 걸쳐 밟았고, 그때마다 공지했고, 마지막 호출까지 붙였습니다. Collector 쪽은 진리표가 명확한 게이트 한 쌍과 이중 발행 창구, 그리고 8개 마이너 릴리스에 걸친 유예를 설계해 뒀습니다. 조용한 실패를 막기 위해 갖출 수 있는 건 거의 다 갖춘 셈입니다.

그럼에도 이런 변경이 사람을 다치게 하는 지점은 언제나 같습니다 — 기본값이 뒤집히는 순간과 당신이 업그레이드를 누르는 순간이 같기 때문입니다. 지금은 그 두 순간 사이에 시간이 남아 있는, 흔치 않은 상태입니다. semconv는 이미 stable을 선언했고 Collector 기본값은 아직 안 뒤집혔습니다. 이 틈이 이중 발행을 켜고, 쿼리를 옮기고, 확인하고 넘어가기에 정확히 좋은 자리입니다.

그리고 다시 말하지만, 확인해 봤더니 extract 블록에 레이블이 없더라 — 라면 그걸로 끝입니다. 그게 이 글의 가장 흔한 결말이고, 5분이면 확인됩니다.

참고 자료