Files
project-infra/docs/runbooks/2026-07-31-observability-access-cutover.md

10 KiB

관측성 운영 접근·알림 전환 Runbook

목적

공용 관측성 플랫폼의 검증된 dashboard·rule·Alertmanager 설정을 적용한 뒤, 가장 마지막에 Grafana private HTTPS 경로를 여는 절차와 rollback 경계를 고정한다.

이 문서는 Secret 값, bearer token, Cookie, 사용자 ID, authorization code/state, webhook 원문을 기록하지 않는다. 실행하지 않은 검사는 성공으로 기록하지 않는다.

운영 alert의 runbook_url로 사용할 공개 위치는 다음 경로다. 이 파일이 해당 저장소에 실제 게시되어 HTTPS 200으로 도달하기 전에는 rules-alerts 적용을 시작하지 않는다.

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-oidcclient-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인 도구는 실행하지 않는다.

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 외부 /metrics404다.
  • 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을 별도 절차로 회전한다.