docs: publish observability access runbook
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
# 관측성 운영 접근·알림 전환 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을 별도 절차로 회전한다.
|
||||
Reference in New Issue
Block a user