Skip to content

Commit 5eaeb90

Browse files
committed
관측성 selector 검증 보강
1 parent 86702e9 commit 5eaeb90

3 files changed

Lines changed: 76 additions & 2 deletions

File tree

docs/content/developer/infra/observability-improvement-plan/_index.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,11 @@ P1은 `group_wait=30s`, `repeat_interval=1h`로 전달합니다.
203203
- Grafana dashboard JSON parse와 dashboard shape check: uid/title/refresh/panel/target expr.
204204
- PrometheusRule shape check: P0/P1 interval, alert priority/severity, summary/description, duplicate alert namespace label.
205205
- AlertmanagerConfig shape check: groupBy, P0/P1 Discord receivers, webhook Secret reference.
206+
- kube-prometheus-stack values shape check: Prometheus selector policy, AlertmanagerConfig selector, Loki datasource, dashboard sidecar label.
207+
- scrape resource shape check: ServiceMonitor/PodMonitor selector label, scrape path/port/interval.
206208
- Monitoring kustomization shape check: email fallback example이 기본 적용에 섞이지 않는지 확인.
207209
- Live cluster smoke check: `bash kubernetes/monitoring/smoke-check.sh`
208-
- Prometheus Operator CRD, monitoring namespace resource, webhook Secret, kube-prometheus-stack Pod readiness, optional Loki/Alloy, app namespace backend readiness를 읽기 전용으로 확인합니다.
210+
- Prometheus Operator CRD, monitoring namespace resource, selector label, webhook Secret, kube-prometheus-stack Pod readiness, optional Loki/Alloy, app namespace backend service/port/readiness를 읽기 전용으로 확인합니다.
209211

210212
`promtool`이 있는 환경에서는 `promtool check rules kubernetes/monitoring/prometheus-rules.yaml`도 실행합니다.
211213
`helm`이 있는 환경에서는 Loki/Alloy values를 `helm template`로 렌더링 검증합니다.

kubernetes/monitoring/smoke-check.sh

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,22 @@ resource_exists() {
2121
kubectl -n "$1" get "$2" "$3" >/dev/null 2>&1
2222
}
2323

24+
resource_count() {
25+
kubectl -n "$1" get "$2" -l "$3" -o jsonpath='{.items[*].metadata.name}' 2>/dev/null | wc -w | tr -d ' '
26+
}
27+
28+
require_label() {
29+
local namespace="$1"
30+
local resource="$2"
31+
local name="$3"
32+
local key="$4"
33+
local expected="$5"
34+
local actual
35+
actual="$(kubectl -n "$namespace" get "$resource" "$name" -o "jsonpath={.metadata.labels['${key//./\\.}']}")"
36+
[ "$actual" = "$expected" ] || fail "$resource/$name label $key expected=$expected actual=$actual"
37+
ok "$resource/$name label $key=$expected"
38+
}
39+
2440
require_cmd kubectl
2541

2642
echo "==> checking Prometheus Operator CRDs"
@@ -49,6 +65,14 @@ for resource in \
4965
ok "$resource"
5066
done
5167

68+
require_label "$NAMESPACE" prometheusrule codeplace-fast-alerts release kube-prometheus-stack
69+
require_label "$NAMESPACE" servicemonitor backend release kube-prometheus-stack
70+
require_label "$NAMESPACE" podmonitor postgres release kube-prometheus-stack
71+
require_label "$NAMESPACE" podmonitor redis release kube-prometheus-stack
72+
require_label "$NAMESPACE" alertmanagerconfig codeplace-alert-routing alertmanagerConfig codeplace
73+
require_label "$NAMESPACE" configmap grafana-dashboard-codeplace grafana_dashboard 1
74+
require_label "$NAMESPACE" configmap grafana-dashboard-codeplace-logs grafana_dashboard 1
75+
5276
if resource_exists "$NAMESPACE" secret alertmanager-contact-points; then
5377
kubectl -n "$NAMESPACE" get secret alertmanager-contact-points \
5478
-o jsonpath='{.data.webhook-url}' | grep -q . \
@@ -69,7 +93,7 @@ for selector in \
6993
done
7094

7195
echo "==> checking optional logs stack"
72-
if kubectl -n "$NAMESPACE" get pod -l app.kubernetes.io/name=loki >/dev/null 2>&1; then
96+
if [ "$(resource_count "$NAMESPACE" pod app.kubernetes.io/name=loki)" -gt 0 ]; then
7397
kubectl -n "$NAMESPACE" wait --for=condition=Ready pod -l app.kubernetes.io/name=loki --timeout=30s
7498
ok "loki pods ready"
7599
else
@@ -91,6 +115,9 @@ for app_namespace in $APP_NAMESPACES; do
91115
ok "namespace $app_namespace"
92116
kubectl -n "$app_namespace" get svc backend >/dev/null
93117
ok "$app_namespace service/backend"
118+
kubectl -n "$app_namespace" get svc backend -o jsonpath='{.spec.ports[*].name}' | grep -qw api \
119+
|| fail "$app_namespace service/backend missing api port"
120+
ok "$app_namespace service/backend api port"
94121
kubectl -n "$app_namespace" wait --for=condition=Ready pod -l app=backend --timeout=30s
95122
ok "$app_namespace backend pods ready"
96123
done

kubernetes/monitoring/validate.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,51 @@ for receiver_name in ("p0-discord", "p1-discord"):
119119
raise SystemExit(f"{receiver_name} must use alertmanager-contact-points/webhook-url")
120120
print("ALERTMANAGER SHAPE OK")
121121
122+
stack_values = yaml.safe_load((root / "kube-prometheus-stack-values.yaml").read_text())
123+
prometheus_spec = stack_values.get("prometheus", {}).get("prometheusSpec", {})
124+
for key in (
125+
"serviceMonitorSelectorNilUsesHelmValues",
126+
"podMonitorSelectorNilUsesHelmValues",
127+
"ruleSelectorNilUsesHelmValues",
128+
):
129+
if prometheus_spec.get(key) is not False:
130+
raise SystemExit(f"kube-prometheus-stack prometheusSpec.{key} must be false")
131+
alertmanager_selector = (
132+
stack_values.get("alertmanager", {})
133+
.get("alertmanagerSpec", {})
134+
.get("alertmanagerConfigSelector", {})
135+
.get("matchLabels", {})
136+
)
137+
if alertmanager_selector.get("alertmanagerConfig") != "codeplace":
138+
raise SystemExit("AlertmanagerConfig selector must match alertmanagerConfig=codeplace")
139+
grafana_values = stack_values.get("grafana", {})
140+
datasources = grafana_values.get("additionalDataSources") or []
141+
if not any(ds.get("name") == "Loki" and ds.get("type") == "loki" for ds in datasources):
142+
raise SystemExit("Grafana values must provision Loki datasource")
143+
dashboard_sidecar = grafana_values.get("sidecar", {}).get("dashboards", {})
144+
if dashboard_sidecar.get("label") != "grafana_dashboard":
145+
raise SystemExit("Grafana dashboard sidecar must select grafana_dashboard label")
146+
print("KUBE-PROMETHEUS-STACK VALUES SHAPE OK")
147+
148+
backend_sm = yaml.safe_load((root / "backend-service-monitor.yaml").read_text())
149+
if backend_sm.get("metadata", {}).get("labels", {}).get("release") != "kube-prometheus-stack":
150+
raise SystemExit("backend ServiceMonitor must keep release=kube-prometheus-stack label")
151+
endpoint = (backend_sm.get("spec", {}).get("endpoints") or [{}])[0]
152+
if endpoint.get("port") != "api" or endpoint.get("path") != "/metrics" or endpoint.get("interval") != "15s":
153+
raise SystemExit("backend ServiceMonitor must scrape api /metrics every 15s")
154+
if backend_sm.get("spec", {}).get("selector", {}).get("matchLabels", {}).get("app") != "backend":
155+
raise SystemExit("backend ServiceMonitor must select app=backend")
156+
157+
pod_monitors = list(yaml.safe_load_all((root / "datastore-pod-monitors.yaml").read_text()))
158+
for monitor in pod_monitors:
159+
name = monitor.get("metadata", {}).get("name")
160+
if monitor.get("metadata", {}).get("labels", {}).get("release") != "kube-prometheus-stack":
161+
raise SystemExit(f"{name} PodMonitor must keep release=kube-prometheus-stack label")
162+
endpoint = (monitor.get("spec", {}).get("podMetricsEndpoints") or [{}])[0]
163+
if endpoint.get("interval") != "30s":
164+
raise SystemExit(f"{name} PodMonitor must scrape every 30s")
165+
print("SCRAPE RESOURCE SHAPE OK")
166+
122167
kustomization = yaml.safe_load((root / "kustomization.yaml").read_text())
123168
if "./alertmanager-config-email.example.yaml" in kustomization.get("resources", []):
124169
raise SystemExit("email fallback example must not be included in default kustomization")

0 commit comments

Comments
 (0)