Skip to content

fix(nodecompat,chart): 자체 수집기 중복 차단 완성 + 스크레이프 발견 opt-in 화 - #35

Merged
eightynine01 merged 1 commit into
mainfrom
fix/native-collector-dedup
Aug 12, 2026
Merged

fix(nodecompat,chart): 자체 수집기 중복 차단 완성 + 스크레이프 발견 opt-in 화#35
eightynine01 merged 1 commit into
mainfrom
fix/native-collector-dedup

Conversation

@eightynine01

Copy link
Copy Markdown
Contributor

0.9.0 을 운영에 얹으면 조용히 메트릭을 잃는 결함 3건을 고칩니다. 셋 다 파드는 Ready, /metrics 는 200 이라 로그를 보지 않으면 드러나지 않습니다.

1. native collector 5그룹이 임베드 node_exporter 와 중복 등록

nativeCollectors 는 upstream collector 를 둘(loadavg·uname)만 껐지만 nodecompat 은 7그룹을 냅니다. 나머지 5개가 살아남아 같은 메트릭을 양쪽에서 등록했고, client_golang 은 충돌한 family 를 스크레이프에서 빼면서도 200 을 계속 줍니다(sink/metrics.goContinueOnError).

Linux 실측으로 확인한 소실 대상 9종 — 결함을 되살려 가드가 잡아내는 것을 확인했습니다:

upstream collector 소실되던 메트릭
entropy node_entropy_available_bits
filefd node_filefd_allocated
stat node_procs_running, node_procs_blocked
os node_os_version
vmstat node_vmstat_pgfault, pgmajfault, pswpin, oom_kill

차단 목록을 main.go 에 다시 적는 대신 nodecompat 이 자기 수집기 집합에서 파생시킵니다(SupersededCollectors). subCollectorSupersedes() 를 둬서 대체 대상을 밝히지 않은 수집기는 추가 자체가 불가능하게 했습니다 — 두 목록이 어긋난 것이 이 결함의 원인이었습니다. procsstat, osreleaseos 처럼 로컬 이름과 upstream 이름이 다른 두 건이 특히 빠지기 쉽습니다.

회귀 가드는 두 층입니다: 선언↔플래그 정합성(단위) + 실제 수집 결과의 교집합(Linux 통합). 후자에는 "upstream family 20개 이상" 단언을 함께 넣어, upstream 수집이 통째로 실패했을 때 교집합이 비어 통과하는 가짜 초록불을 막습니다.

2. nodevitals_build_info 가 소스에 박힌 버전을 신고

NewHeartbeat(cfg.Node, "0.8.5") 리터럴 탓에 0.9.0 이미지가 version="0.8.5" 를 냈습니다. 이 메트릭은 "이 노드가 어떤 빌드인가"에 답하는 유일한 자기신고 수단이라, 그럴듯한 기본값은 확인 수단 자체를 없앱니다.

-ldflags -X main.version 주입으로 바꾸고 Chart.yamlappVersion 을 단일 출처로 삼습니다(Makefile·Dockerfile·release.yml 이 같은 값을 전달). 미주입 시 "unknown" — 모르는 것을 모른다고 말하는 편이 아닐 수도 있는 릴리스를 자칭하는 것보다 낫습니다.

실증(빌드된 바이너리):

nodevitals_hw_nodevitals_build_info{...,version="0.9.1"} 1

3. 스크레이프 발견 어노테이션이 업그레이드만으로 켜짐

0.9.0 이 파드 템플릿에 prometheus.io/scrape 를 무조건 렌더합니다. 이미 Service / ServiceMonitor 로 수집하던 클러스터는 업그레이드만으로 role:pod 잡이 같은 파드를 한 벌 더 긁게 되어, 모든 시리즈가 job 라벨만 다른 2벌이 됩니다. 오류는 없고 카디널리티와 저장량만 두 배가 됩니다.

serviceMonitor.enabled 와 같은 규칙으로 scrapeAnnotations.enabled 로 분리하고 기본 off 로 뒀습니다 — 발견 경로가 스스로 켜지는 것이 문제이지 기능 자체가 문제는 아닙니다. 어노테이션 정의는 configChecksums helper 에서 분리했습니다(이름과 내용이 어긋나 있었습니다).

compatibility-check.sh 는 문서 전체에서 문자열만 grep 해 위치를 구분하지 못했고, 애초에 Makefile·CI 어디에도 연결돼 있지 않아 한 번도 실행된 적이 없었습니다. 파드 템플릿 안인지까지 검사하도록 고치고 chart-test 에 연결합니다.

검증

  • make fmt vet test 통과 (13 패키지)
  • make chart-lint (kubeconform strict), make chart-test (3종) 통과
  • Linux 컨테이너에서 internal/nodecompat·cmd/nodevitals 통과
  • 회귀 가드 유효성: 결함을 되살려 9개 충돌을 검출하는 것 확인
  • cmd/nodevitals 에 첫 테스트 추가 (기존 0%)

Chart/appVersion 0.9.00.9.1.

0.9.0 을 운영에 얹으면 조용히 메트릭을 잃는 결함 3건을 고친다. 셋 다 파드는
Ready, /metrics 는 200 이라 로그를 보지 않으면 드러나지 않는 종류다.

## 1. native collector 5그룹이 임베드 node_exporter 와 중복 등록

nativeCollectors 는 upstream collector 를 두 개(loadavg·uname)만 껐다. nodecompat
은 7그룹을 내므로 entropy·filefd·stat·vmstat·os 가 그대로 살아 같은 메트릭을
양쪽에서 등록했다. client_golang 은 충돌한 family 를 스크레이프에서 빼면서 200 을
계속 주므로(sink/metrics.go 가 ContinueOnError), 손실이 에러로 나타나지 않는다.

Linux 실측으로 확인된 소실 대상: node_entropy_available_bits, node_filefd_allocated,
node_procs_running, node_procs_blocked, node_os_version, node_vmstat_pgfault,
node_vmstat_pgmajfault, node_vmstat_pswpin, node_vmstat_oom_kill.

차단 목록을 main.go 에 다시 적는 대신 nodecompat 이 자기 수집기 집합에서
파생시킨다(SupersededCollectors). subCollector 인터페이스에 Supersedes() 를 두어
새 수집기가 대체 대상을 밝히지 않으면 아예 추가되지 못하게 했다 — 두 목록이
어긋나는 것이 이 결함의 원인이었다. procs→"stat", osrelease→"os" 처럼 로컬
이름과 upstream 이름이 다른 두 건이 특히 빠지기 쉽다.

회귀 가드는 선언과 플래그의 정합성(단위)과 **실제 수집 결과의 교집합**(Linux
통합) 두 층이다. 후자는 결함을 되살려 9개 메트릭을 잡아내는 것으로 유효성을
확인했다. upstream 수집이 통째로 실패해도 교집합이 비어 통과하는 것을 막으려
"upstream family 20개 이상" 을 함께 단언한다.

## 2. nodevitals_build_info 가 소스에 박힌 버전을 신고

NewHeartbeat(cfg.Node, "0.8.5") 리터럴 탓에 0.9.0 이미지가 version="0.8.5" 를
냈다. 이 메트릭은 "이 노드가 어떤 빌드인가" 에 답하는 유일한 자기신고 수단이라,
그럴듯한 기본값은 확인 수단 자체를 없앤다. -ldflags -X main.version 주입으로
바꾸고 Chart.yaml 의 appVersion 을 단일 출처로 삼는다(Makefile·Dockerfile·
release.yml 이 같은 값을 흘려보낸다). 미주입 시에는 "unknown" — 모르는 것을
모른다고 말하는 편이 아닐 수도 있는 릴리스를 자칭하는 것보다 낫다.

## 3. 스크레이프 발견 어노테이션이 업그레이드만으로 켜짐

0.9.0 이 파드 템플릿에 prometheus.io/scrape 를 무조건 렌더한다. 이미 Service /
ServiceMonitor 로 수집하던 클러스터는 업그레이드만으로 role:pod 잡이 같은 파드를
한 벌 더 긁게 되어, 모든 시리즈가 job 라벨만 다른 2벌이 된다. 오류는 없고
카디널리티와 저장량만 두 배가 된다.

serviceMonitor.enabled 와 같은 규칙을 적용해 scrapeAnnotations.enabled 로
분리하고 기본 off 로 둔다 — 발견 경로가 스스로 켜지는 것이 문제이지 기능 자체가
문제는 아니다. 어노테이션 정의를 configChecksums helper 에서 떼어냈다(이름과
내용이 어긋나 있었다).

compatibility-check.sh 는 문서 전체에서 문자열만 grep 해 위치를 구분하지 못했고,
애초에 Makefile·CI 어디에도 연결돼 있지 않아 한 번도 실행된 적이 없었다.
파드 템플릿 안인지까지 검사하도록 고치고 chart-test 에 연결한다.

Chart/appVersion 0.9.0 → 0.9.1.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 412f7e4ea9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


// node_procs_running / node_procs_blocked belong to upstream's "stat"
// collector, not to a "procs" one — that collector does not exist.
func (c *procsCollector) Supersedes() string { return "stat" }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don't disable the whole stat collector

When nodeExporter.nativeCollectors is enabled, this Supersedes() value feeds NoCollectorFlags() and appends --no-collector.stat, but the local procsCollector only emits node_procs_running and node_procs_blocked. The upstream stat collector also supplies families such as node_boot_time_seconds, node_context_switches_total, node_forks_total, and node_intr_total, so enabling the embedded node_exporter with native collectors now silently drops those compatibility metrics instead of just deduplicating the two process gauges.

Useful? React with 👍 / 👎.


// node_os_info / node_os_version come from upstream's "os" collector
// (os_release.go), which is not named after the file it reads.
func (c *osReleaseCollector) Supersedes() string { return "os" }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the upstream os collector until parity

With native collectors on, returning os here disables the embedded node_exporter os collector entirely. The native replacement only emits node_os_info with 8 labels plus node_os_version, while node_exporter v1.12.1's os collector also exposes the image_id, image_version, variant, and variant_id labels and emits node_os_support_end_timestamp_seconds when SUPPORT_END is present, so clusters using those labels or support-end metadata lose or change the node_exporter-compatible surface after upgrade.

Useful? React with 👍 / 👎.

@eightynine01
eightynine01 merged commit 7a100fb into main Aug 12, 2026
2 checks passed
@eightynine01
eightynine01 deleted the fix/native-collector-dedup branch August 12, 2026 11:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants