# 관측성 운영 접근·알림 전환 Runbook ## 목적 공용 관측성 플랫폼의 검증된 dashboard·rule·Alertmanager 설정을 적용한 뒤, 가장 마지막에 Grafana private HTTPS 경로를 여는 절차와 rollback 경계를 고정한다. 이 문서는 Secret 값, bearer token, Cookie, 사용자 ID, authorization code/state, webhook 원문을 기록하지 않는다. 실행하지 않은 검사는 성공으로 기록하지 않는다. 운영 alert의 `runbook_url`로 사용할 공개 위치는 다음 경로다. 이 파일이 해당 저장소에 실제 게시되어 HTTPS `200`으로 도달하기 전에는 rules-alerts 적용을 시작하지 않는다. ```text https://git.learn.hyeonworks.com/donghyeon.kang/project-infra/src/branch/main/docs/runbooks/2026-07-31-observability-access-cutover.md ``` ## 현재 상태 기준 시각: `2026-08-12T07:58:23Z` 상태: **부분 구현 — substrate와 metric target은 live, rules-alerts와 full Nginx cutover는 미실행** - Prometheus active target `30`, healthy `30`, scrape pool `25`, unhealthy `0` - Grafana와 Blackbox Exporter Deployment 각각 `1/1` Ready - target-initial inventory SHA-256 `79688d017d38eec9a6f100f8d0f784a5474e79802046ef1c2c11b30d170b0b0c` - post-substrate inventory SHA-256 `b1c3049206a1a88165ee672ae9aceac7945673a3bb9c3cf3670b7f0d56c3f291` - Host Nginx active config는 Grafana deny-only guard SHA-256 `dbef6d443bcba58b26a5351ea76f6d09f6da8c2ef07a806e22745cf26c88f518` - full Host Nginx candidate SHA-256 `7d2de2a92c3597a0859775da1d2ccbf5a3d72c0b2af5cac2439c82311361f801` - Grafana OIDC Deployment는 exact `grafana-keycloak-oidc`의 `client-id`, `client-secret`만 참조하며 `1/1` Ready - 새 platform dashboard ConfigMap, 새 platform rule, AlertmanagerConfig와 Slack webhook Secret은 아직 live acceptance 전이다. ## 영향 rules-alerts 단계는 다음 object만 생성 또는 수정한다. - dashboard ConfigMap 5개 - platform PrometheusRule 4개 - AlertmanagerConfig 1개 - kube-prometheus-stack Alertmanager/Prometheus owner object - 필요한 observability NetworkPolicy Grafana·Blackbox workload, Probe, Ingress, PVC, Secret과 Host Nginx는 이 단계에서 변경하지 않는다. full Nginx 단계는 rules-alerts acceptance가 끝난 뒤 Grafana upstream을 deny endpoint에서 loopback Traefik으로 전환한다. ## 사전 조건 다음 조건이 모두 통과해야 한다. 1. current context와 API server가 각각 `default`, `https://127.0.0.1:6443`이다. 2. K3s Secret encryption은 `reencrypt_finished`이고 current restore evidence가 유효하다. 3. 같은 rollback ID 아래 root-only ledger root가 `root:root 0700`으로 존재한다. 4. Grafana local credential, Slack webhook, Keycloak의 recovery evidence가 각각 유효하다. 5. Slack Secret은 `observability/alertmanager-slack-webhook`, type `Opaque`, key는 정확히 `url` 하나다. 값은 검사 출력에 포함하지 않는다. 6. 두 inventory directory는 `0700`, 파일은 `0600`, non-symlink, link count 1이며 위 SHA-256과 일치한다. 7. Blackbox private-edge source proof는 같은 rollback ID와 active deny SHA에 결속되고 24시간 이내이며 세 hostname 모두 status `403`이다. 8. 이 runbook의 공개 URL이 HTTPS `200`으로 도달하고 모든 새 alert annotation이 해당 URL을 사용한다. 9. public Grafana A/AAAA는 없고 LAN·Tailscale·Pod resolver만 승인된 private IP를 반환한다. 10. Grafana exact-SAN 인증서, Keycloak discovery, Gitea/Admin endpoint, 내부 Grafana health가 정상이다. ## 절차 — 권위 실행 순서 아래 순서만 허용한다. 각 도구의 no-argument 실행과 focused test가 먼저 통과해야 하며, 현재 독립 검토에서 `Ready: No`인 도구는 실행하지 않는다. ```bash cd /home/donghyeon/workspace/platform OBS_ROLLBACK_ID="$(date -u +%Y%m%dT%H%M%SZ)" [[ "$OBS_ROLLBACK_ID" =~ ^[0-9]{8}T[0-9]{6}Z$ ]] : "${CERTBOT_EMAIL:?set the operator-managed Certbot contact email}" sudo install -d -o root -g root -m 0700 \ "/var/lib/hyeonworks/platform-rollbacks/observability-$OBS_ROLLBACK_ID" export PLATFORM_OBSERVABILITY_ROLLBACK_ID="$OBS_ROLLBACK_ID" bash scripts/validate/observability-core-smoke.sh --execute bash scripts/bootstrap/apply-private-dns.sh --execute bash scripts/bootstrap/apply-host-nginx-observability.sh \ --execute --metrics-guard-only bash scripts/bootstrap/apply-host-nginx-observability.sh \ --execute --certificate-only --certbot-email "$CERTBOT_EMAIL" bash scripts/bootstrap/apply-host-nginx-observability.sh \ --execute --grafana-deny-guard-only bash scripts/validate/validate-blackbox-edge-source.sh --execute --context default bash scripts/validate/k3s-secret-encryption.sh --expect-reencrypted bash scripts/validate/k3s-secret-encryption-restore-evidence.sh --check bash scripts/bootstrap/create-observability-secrets.sh \ --execute --grafana-admin \ --grafana-admin-user-file /home/donghyeon/.secrets/grafana/admin-user \ --grafana-admin-password-file /home/donghyeon/.secrets/grafana/admin-password bash scripts/bootstrap/create-observability-secrets.sh \ --execute --slack-webhook \ --slack-webhook-file /home/donghyeon/.secrets/alertmanager/slack-webhook bash scripts/bootstrap/configure-keycloak-grafana-oidc.sh --execute bash scripts/bootstrap/create-observability-secrets.sh \ --check-grafana-recovery-evidence bash scripts/bootstrap/create-observability-secrets.sh \ --check-slack-recovery-evidence bash scripts/bootstrap/configure-keycloak-grafana-oidc.sh \ --check-recovery-evidence SOURCE_METRIC_ROOT=/tmp/platform-observability-metrics.VUpsZn METRIC_ROOT="$(mktemp -d /tmp/platform-observability-metrics.XXXXXX)" chmod 0700 "$METRIC_ROOT" # Blackbox substrate가 이미 live이므로 target-initial을 현재 상태에서 재수집하면 안 된다. # 실행 당시 보존한 두 authoritative inventory만 새 rules-alerts root로 복제한 뒤 renderer가 # schema, phase, file metadata와 위 두 hash를 다시 검증하게 한다. [[ -d "$SOURCE_METRIC_ROOT/target-initial" ]] [[ -d "$SOURCE_METRIC_ROOT/post-substrate" ]] cp -a -- "$SOURCE_METRIC_ROOT/target-initial" "$METRIC_ROOT/" cp -a -- "$SOURCE_METRIC_ROOT/post-substrate" "$METRIC_ROOT/" chmod 0700 "$METRIC_ROOT/target-initial" "$METRIC_ROOT/post-substrate" chmod 0600 "$METRIC_ROOT/target-initial/"* "$METRIC_ROOT/post-substrate/"* PLATFORM_HELM_BIN=/home/donghyeon/.local/bin/helm \ bash scripts/validate/render-observability-access.sh \ --component rules-alerts --verified-output-dir "$METRIC_ROOT" PLATFORM_HELM_BIN=/home/donghyeon/.local/bin/helm \ bash scripts/bootstrap/apply-observability-access.sh \ --execute --rules-alerts --verified-output-dir "$METRIC_ROOT" bash scripts/bootstrap/apply-host-nginx-observability.sh \ --execute --verified-output-dir "$METRIC_ROOT" ``` 사용자 group membership은 위 transaction과 분리한다. 실제 기존 realm username은 stdin으로 받고 명령 인자·이 문서에 기록하지 않는다. ## 검증 — 자동 수락 조건 rules-alerts 성공 조건: - Prometheus와 Alertmanager controller가 Ready로 수렴한다. - 모든 새 rule이 Prometheus API에서 구문·평가 가능하다. - Alertmanager generated config에 exact `platform-slack` receiver가 있다. - Grafana sidecar가 exact dashboard ConfigMap 5개를 실제로 읽었다. - 기존 active target set은 유지되고 down/error target이 없다. - Grafana·Blackbox·Probe·Ingress·Host Nginx·PVC·Secret의 UID와 계약이 보존된다. - root-only acceptance marker는 위 조건이 모두 끝난 뒤에만 기록된다. full Nginx 성공 조건: - reload 뒤 연속 세 번 안정된 probe가 통과한다. - 승인된 LAN/Tailscale source의 Grafana는 `200` 또는 로그인 redirect다. - 비허용 local/public source는 `403`이다. - Gitea와 Grafana 외부 `/metrics`는 `404`다. - Keycloak·Gitea·storage-admin·db-admin 회귀가 없다. - unknown SNI는 거부되고 LAN의 NodePort `30080/30443`은 계속 refused다. ## 사람 확인 다음은 자동 검사로 대체하지 않는다. - observability admin group 사용자의 Grafana organization `Admin`, server admin `false` - viewer group 사용자의 `Viewer`와 edit/admin 동작 거부 - 두 group이 모두 없는 별도 사용자의 로그인 거부 - local break-glass 계정의 private path 로그인과 server admin 확인 - Slack temporary test alert의 firing/resolved 메시지, cluster/namespace/alertname grouping, severity와 이 runbook URL 확인 - LAN/Tailscale 밖 별도 client에서 public boundary 확인 확인 결과에는 역할·시각·성공/실패만 기록한다. ## 롤백 자동 rollback은 같은 `PLATFORM_OBSERVABILITY_ROLLBACK_ID`의 root-only ledger만 권위 입력으로 사용한다. 1. full Nginx를 exact Grafana deny-only guard payload/hash로 복원하고 `nginx -t`, reload, 연속 probe를 확인한다. 2. rules-alerts ledger를 역순 처리한다. 이번 transaction의 `false/apply`만 UID precondition으로 삭제하고 `true/apply`만 exact sanitized payload와 현재 UID/RV로 복원한다. `controller`는 owner 처리 뒤 수렴만 기다리고 `preserve`는 변경하지 않는다. 3. Alertmanager/Prometheus/Grafana sidecar 수렴과 기존 target set을 다시 확인한다. 4. target metric을 끄고 기존 endpoint 회귀를 확인하기 전에는 metrics guard보다 이전 Nginx 설정으로 돌아가지 않는다. 5. PVC, Secret, CRD, PV, Loki/Tempo object와 bucket은 자동 삭제하지 않는다. UID 변경, API timeout 뒤 결과 불명, payload drift, controller 비수렴 또는 ledger 불일치는 자동 성공으로 처리하지 않는다. 보호 정책과 rollback root를 보존하고 `MANUAL_RECOVERY_REQUIRED`로 에스컬레이션한다. ## 에스컬레이션 - Slack webhook이나 recovery evidence가 없으면 rules-alerts mutation `0`으로 중단한다. - runbook URL이 실제 HTTPS로 도달하지 않으면 rule source를 적용하지 않는다. - Blackbox source가 `200/302`로 보이면 source allowlist를 완화하지 않고 네트워크 경계를 재설계한다. - 외부 client 검증, 세 OIDC 역할 또는 Slack firing/resolved가 하나라도 없으면 전체 Phase 4를 `부분 구현`으로 유지한다. - Secret payload, token, Cookie 또는 원본 Nginx log line이 출력되면 즉시 중단하고 관련 credential을 별도 절차로 회전한다.