feat(ksmcompat): 하드코딩 스텁을 실제 K8s API 수집으로 교체 - #36
Conversation
ksmcompat 은 Kubernetes API 를 한 번도 호출하지 않았다. apiURL·token·client· lastFetch 필드를 선언해 두고 어디서도 쓰지 않았고, 방출하던 값은 전부 리터럴 상수였다 — capacity cpu=16, memory=64Gi, kubeproxy_version="v1.28.0", 파드는 자기 자신 하나를 지어낸 것, cluster 모드는 특정 이름의 Deployment/DaemonSet/PVC 세 줄. 켜면 관측 스택이 이 값들을 사실로 받아 저장한다. 이 클러스터의 vmalert 룰은 kube_node_status_condition·kube_daemonset_status_number_ready·kube_deployment_* 를 실제로 쓰므로, 노드마다 제각각인 실제 용량(cpu 12/16/32) 대신 16 이 들어가고 DaemonSet ready=1(실제 11)이 들어간다. 진짜 kube-state-metrics 시리즈 옆에 job 라벨만 다른 거짓 시리즈가 나란히 서게 된다. ## 수집 범위를 노드 스코프로 확정 kube-state-metrics 는 단일 Deployment 로 클러스터 전체를 보고한다. nodevitals 는 DaemonSet 이라 노드마다 한 벌 도는데, 각 파드가 전역 객체를 보고하면 같은 시리즈가 노드 수만큼 생긴다(이 클러스터에서 11벌). 메모리도 KSM 실측 265Mi 급이 노드마다 필요해져 128Mi limit 과 양립하지 않는다. 그래서 이 에이전트가 사실로서 말할 수 있는 것만 낸다 — 자기 노드와 그 노드에 스케줄된 파드. 파드 목록은 서버 쪽 fieldSelector(spec.nodeName)로 좁힌다. mode "cluster" 는 조용히 node 로 강등하지 않고 **거부**한다(helm 렌더 단계 + 에이전트 기동 시점 양쪽). 설정한 사람이 그 사실을 알아야 하기 때문이다. 그 결과 kube_deployment_* · kube_daemonset_* 는 내지 않으므로 **kube-state-metrics 를 은퇴시키지는 못한다**. dcgmCompat·smartctlCompat 이 대체에 성공한 이유는 원본이 애초에 DaemonSet 이었기 때문이고, KSM 은 전제가 다르다. values 주석에 그대로 적었다. ## 실패는 실패로 보이게 토큰 없음 / cluster 모드 / 오타 난 mode 는 기동 시점에 거부한다 — 그대로 두면 증상이 "메트릭이 안 나온다" 하나뿐이라 원인까지 도달하는 데 시간이 걸린다. 스크레이프 중 API 도달 불가·403 은 하드코딩 폴백 대신 아무 시리즈도 내지 않고, InvalidMetric 으로 스크레이프 에러 경로에 실어 보낸다. RBAC 이 모자란 상태가 "건강한 클러스터" 처럼 보이면 안 된다. Quantity 파싱도 같은 원칙이다. 읽을 수 없는 값에 0 을 돌려주면 "이 노드는 용량이 0" 이라는 거짓이 메트릭이 되므로 에러로 올리고 그 리소스만 건너뛴다. Ki 와 k 는 Gi 에서 이미 7% 넘게 벌어지므로 이진/십진 접두사를 구분한다. ## 차트 ksmCompat.enabled 일 때만 ServiceAccount + ClusterRole + Binding 이 렌더되고 파드가 토큰을 마운트한다. 꺼져 있으면 아무것도 생기지 않는다 — 다른 tier 는 /proc·/sys·/dev 와 NVML 만 읽으므로 API 자격이 필요 없다. 권한은 실제로 읽는 두 리소스로 한정했다(nodes:get, pods:list). KSM 의 ClusterRole(20+ 리소스 list/watch)을 복사하지 않는다: 읽지 않는 리소스의 열람 권한은 이 파드가 탈취됐을 때 그대로 클러스터 열람 권한이 된다. 회귀 가드가 secrets·configmaps·deployments·statefulsets 가 섞여 들어오는 것을 막는다. kube_* 표면은 core·singlePod tier 에서만 렌더되므로 SA 도 그 둘에만 붙인다.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4a58bcbf18
ℹ️ 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".
| if err != nil { | ||
| return err | ||
| } | ||
| req.Header.Set("Authorization", "Bearer "+e.token) |
There was a problem hiding this comment.
Reload expiring service-account tokens before requests
On Kubernetes clusters using the default projected service-account token volume, kubelet rotates the token file while the process keeps running. Because each request keeps using the token string captured during startup, once that token expires the API starts returning 401s and the kube_* surface disappears after the cache TTL until the pod is restarted; reread the mounted token or use a token source for each API call.
Useful? React with 👍 / 👎.
| snap, err := e.load() | ||
| if err != nil { | ||
| e.log.Warn("ksmcompat scrape failed — emitting no kube_* series", "node", e.node, "err", err) | ||
| ch <- prometheus.NewInvalidMetric(scrapeErrorDesc, err) |
There was a problem hiding this comment.
Make API failures visible to Prometheus scrapes
When the API or RBAC path fails, this sends an invalid metric and returns, but this app serves /metrics with promhttp.ContinueOnError, so the handler logs the gather error and can still return HTTP 200 with the other nodevitals series. In that failure mode Prometheus will not see up=0 or any kube_state_metrics_scrape_error sample; the kube_* series just vanish, so emit a real error gauge/counter or use an error mode that fails the scrape.
Useful? React with 👍 / 👎.
| case "memory", "ephemeral-storage": | ||
| return "byte" |
There was a problem hiding this comment.
Label hugepage resources with byte units
On Linux nodes that report hugepages-* resources, those capacity and allocatable entries fall through to the default integer unit even though kube-state-metrics exposes hugepage resources with unit="byte". Since unit is part of the series identity, existing KSM-compatible hugepage queries will not match the nodevitals series for those resources.
Useful? React with 👍 / 👎.
릴리스 파이프라인은 이미 발행된 appVersion 을 skip 하므로(멱등), #36 이 착지한 뒤에도 0.9.1 이미지는 재빌드되지 않았다 — 즉 ksmcompat 실구현이 어느 발행 이미지에도 들어 있지 않다. main 의 코드와 발행 아티팩트가 어긋난 채로 두면, 0.9.1 을 받는 쪽은 ksmCompat 을 켰을 때 여전히 하드코딩 스텁을 얻는다. Co-authored-by: phil <phil@keiailab.com>
ksmcompat은 Kubernetes API 를 한 번도 호출하지 않았습니다.apiURL·token·client·lastFetch필드를 선언해 두고 어디서도 쓰지 않았고, 방출하던 값은 전부 리터럴이었습니다:kube_node_status_capacity{resource="cpu"}16kube_node_info{kubeproxy_version}"v1.28.0"kube_pod_infokube_daemonset_status_number_ready1켜면 관측 스택이 이것을 사실로 받아 저장합니다. 이 클러스터의
vmalert룰은kube_node_status_condition·kube_daemonset_status_number_ready·kube_deployment_*를 실제로 사용하므로, 진짜 kube-state-metrics 시리즈 옆에job라벨만 다른 거짓 시리즈가 나란히 서게 됩니다.수집 범위를 노드 스코프로 확정
kube-state-metrics 는 단일 Deployment 로 클러스터 전체를 보고합니다. nodevitals 는 DaemonSet 이라 노드마다 한 벌 도는데, 각 파드가 전역 객체를 보고하면 같은 시리즈가 노드 수만큼 생깁니다(이 클러스터에서 11벌). 메모리도 KSM 실측 265Mi 급이 노드마다 필요해져 128Mi limit 과 양립하지 않습니다.
그래서 이 에이전트가 사실로서 말할 수 있는 것만 냅니다 — 자기 노드와 그 노드에 스케줄된 파드(서버 쪽
fieldSelector=spec.nodeName로 좁힘).mode: cluster는 조용히node로 강등하지 않고 거부합니다(helm 렌더 단계 + 에이전트 기동 시점 양쪽).kube_deployment_*·kube_daemonset_*는 내지 않습니다.dcgmCompat·smartctlCompat이 대체에 성공한 이유는 원본이 애초에 DaemonSet 이었기 때문이고, KSM 은 전제가 다릅니다. values 주석에 명시했습니다.내는 것:
kube_node_info/kube_node_status_condition/kube_node_status_capacity/kube_node_status_allocatable/kube_pod_info/kube_pod_status_phase/kube_pod_container_status_ready/_restarts_total실패는 실패로 보이게
cluster모드 / 오타 난 mode — 그대로 두면 증상이 "메트릭이 안 나온다" 하나뿐이라 원인까지 멀어집니다.InvalidMetric으로 에러 경로에 실어 보냅니다. RBAC 이 모자란 상태가 "건강한 클러스터"처럼 보이면 안 됩니다.Ki와k는Gi에서 이미 7% 넘게 벌어지므로 이진/십진 접두사를 구분합니다.차트 RBAC
ksmCompat.enabled일 때만 ServiceAccount + ClusterRole + Binding 이 렌더되고 파드가 토큰을 마운트합니다. 꺼져 있으면 아무것도 생기지 않습니다.권한은 실제로 읽는 두 리소스로 한정했습니다 —
nodes:get,pods:list. KSM 의 ClusterRole(20+ 리소스list/watch)을 복사하지 않습니다: 읽지 않는 리소스의 열람 권한은 이 파드가 탈취됐을 때 그대로 클러스터 열람 권한이 됩니다. 회귀 가드가secrets·configmaps·deployments·statefulsets가 섞여 들어오는 것을 막습니다.kube_*표면은 core·singlePod tier 에서만 렌더되므로 SA 도 그 둘에만 붙습니다.검증
go test ./...통과.ksmcompat테스트 전면 재작성 —httptest로 API 를 세워 값이 API 에서 온 것인지(cpu=7, kernel=6.17.0-test같은 특이값),fieldSelector가 자기 노드로 좁히는지, 도달 불가/403 에서 메트릭이 0개인지, 잘못된 mode/토큰이 거부되는지 확인make chart-lint,compatibility-check.sh§6·§7 신설(게이트·최소권한·SA 배선·cluster 거부)알려진 무관 이슈
make chart-test의tier-runtime.sh가 macOS 로컬에서 3회 중 1회 간헐 실패합니다(단독 실행·순차 실행·최소 Makefile·CI 전부 통과). 이 PR 과 무관한 기존 flaky 이며, 실패 메시지가 "이미지 태그가 appVersion 과 다르다"로 나오지만helm호출이 빈 출력을 내도 같은 메시지가 나오는 구조라 원인을 가립니다. 별도 처리가 필요합니다.