From 905392947dbf12d037d41efcc4021174a3451a2a Mon Sep 17 00:00:00 2001 From: DongHyeonka Date: Fri, 4 Sep 2026 18:10:04 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20close=20the=20three=20untested=20items?= =?UTF-8?q?=20=E2=80=94=20all=20three=20are=20now=20measured?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D-2 forward upgrade and B-4 role propagation were filled earlier. D-4 forced renewal was the third, and it needed a person at the keyboard because the host asks for a sudo password. It turned out to hold the largest finding of the three: the renewal succeeded and went unserved for 36 minutes 39 seconds, because nothing in the setup reloads nginx — no ExecStartPost, three empty hook directories, no nginx plugin. And the half of the plan's question that was still an expectation is now a measurement: the reload is graceful, 8856 connections with zero failures and an in-flight request surviving intact. Co-Authored-By: Claude Opus 5 --- docs/experiment-followup-untested-items.md | 101 +++++++++++++++++---- docs/experiment-index.md | 2 +- 2 files changed, 86 insertions(+), 17 deletions(-) diff --git a/docs/experiment-followup-untested-items.md b/docs/experiment-followup-untested-items.md index 3334fc1..8134918 100644 --- a/docs/experiment-followup-untested-items.md +++ b/docs/experiment-followup-untested-items.md @@ -4,8 +4,9 @@ 증거 [`docs/evidence/followup/`](evidence/followup/) · 2026-09-04 17:35–18:20 KST -23개 실험을 마치며 **세 항목을 "못 했다" 로 남겼다.** 그중 둘을 채우고, -셋째(D-4 강제 갱신)는 권한이 필요해 별도로 진행한다. +23개 실험을 마치며 **세 항목을 "못 했다" 로 남겼다. 이제 셋 다 채웠다.** +셋째(D-4 강제 갱신)는 호스트 sudo 가 필요해 사람이 함께 실행했고, +결과적으로 **이 세 항목 중 가장 큰 발견**이 거기서 나왔다. --- @@ -17,6 +18,8 @@ | **그리고 D-2 의 결론이 정밀해졌다** | **"롤백 불가" 는 조건부다** — 스키마가 바뀌었을 때만 | | **B-4 ③ role 변경 반영 시점** | **요청 횟수와 무관하다.** 세션이 새로 만들어져야 한다 | | **B층에 Grafana 증거가 없는 이유** | **관측 대상에 없다.** 안 찍은 것이 아니다 | +| **D-4 강제 갱신** | **★ 갱신은 성공했는데 36분 39초 동안 반영되지 않았다** | +| **D-4 reload 무중단** | **무중단이다.** 8856건 전부 200 · 전송 한가운데였던 요청이 전량 수신 | --- @@ -258,31 +261,97 @@ kubectl -n keycloak-lab exec deploy/redis -- redis-cli --scan --pattern 'oauth2- --- -## 5. 남은 것 — D-4 강제 갱신 +## 5. D-4 강제 갱신 — 셋째 항목, 그리고 가장 큰 발견 + +### 왜 못 했었나 ``` $ sudo -n -l sudo: a password is required ``` -**호스트 sudo 가 비밀번호를 요구해 `certbot renew --force-renewal` 과 -`systemctl reload nginx` 를 실행할 수 없다.** 사람이 함께 있어야 한다. +호스트 sudo 가 비밀번호를 요구해 `certbot renew --force-renewal` 을 실행할 수 +없었다. 게스트(kc-lab-1/2)는 무암호라 A층에서 `conntrack`·`tc` 를 자유롭게 +썼는데 **호스트는 다르다.** 이 사실 자체가 B-7 에서 처음 드러났던 것이다 — +nginx 설정을 읽으려던 시도가 계속 빈 결과였고, 그게 sudo 의 조용한 실패였다. -측정 계획은 준비되어 있다. +### 방법 — 주입보다 대조군을 먼저 -```bash -# 측정 쪽 (내가 실행) -while true; do - printf '%s ' "$(curl -s -o /dev/null -w '%{http_code}' --max-time 2 https://auth.hyeonworks.com/realms/master)" - sleep 0.2 -done +사람이 실행할 명령은 하나뿐이므로, **그 한 번을 헛되게 쓰지 않는 것**이 +관건이었다. 주입 전에 평시를 재두지 않으면 주입 중에 나온 값을 해석할 수 없다. -# 주입 쪽 (사람이 실행) -sudo certbot renew --force-renewal +| 대조군 | 결과 | +|---|---| +| 새 연결 (0.2초 × 900회 / 180초) | **900 전부 200, 오류 0** · 중앙 98ms · p95 195ms | +| 진행 중 요청 (845KB @ 20k/s) | 200 · 845361바이트 · 연결수 1 · **42.3초 완주** | + +두 번째 장치가 따로 필요했던 이유가 중요하다. 첫 번째 폴링은 **TLS +핸드셰이크가 900/900** 이다 — 매 요청이 새 연결이라는 뜻이고, 그래서 +"새 연결을 받아주는가"만 잰다. 계획서가 물은 것은 **"진행 중이던 요청은 +어떻게 되는가"** 이므로, reload 순간에 실제로 전송 중인 요청이 있어야 한다. +845KB 짜리 번들을 일부러 느리게 받아 요청 하나를 42초간 살려 두었다. + +### 결과 ① — 갱신은 성공했는데 인증서가 바뀌지 않았다 + +``` +디스크 cert2.pem 2026-09-04 17:22:13 KST 기록됨 +네트워크 08:10:51 ~ 08:58:47 serial=0520BB…B853 (옛 것) + 08:58:52 serial=06C7CB…EA1D (새 것) ``` -**0.2초 간격으로 재는 이유** — 2절에서 1초 간격으로는 전환을 거의 못 잡았다. -nginx reload 는 그보다 훨씬 짧을 것이므로 해상도를 올려야 한다. +| | 시각 (UTC) | +|---|---| +| 새 인증서 디스크 기록 | 08:22:13 | +| 실제 서빙 시작 (`nginx -s reload`) | 08:58:52 | +| **공백** | **2199초 = 36분 39초** (그 사이 428회 관측) | + +그리고 그 36분은 **우연히 짧았을 뿐이다** — reload 를 시킨 것은 사람이지 +자동화가 아니다. 원인이 셋 겹쳤고 **전부 비어 있었다.** + +| | 상태 | +|---|---| +| `certbot-renew.service` 의 `ExecStartPost` | 없음 | +| `renewal-hooks/{deploy,post,pre}/` | **셋 다 비었음** | +| certbot 의 nginx 플러그인 | 없음 | + +**이 결함은 88일 동안 보이지 않는다.** 타이머는 오늘도 두 번 `SUCCESS` 로 +끝났다. 만료 30일 전까지는 갱신 자체를 하지 않으므로 발현할 기회가 없고, +발현하는 날의 증상은 **인증서 만료**다 — 그날에도 로그는 `SUCCESS` 다. + +### 결과 ② — reload 는 무중단이다 + +**0.2초 간격으로 잰 이유** — 2절에서 1초 간격으로는 전환을 거의 못 잡았다. +nginx reload 는 그보다 짧을 것이므로 해상도를 올렸다. 결과적으로 해상도는 +충분했고, **끊김 자체가 없었다.** + +새 연결 **8856건 전부 200**. p95 는 평시 205.7ms 대 직후 204.3ms. + +``` +08:58:40 845KB 요청 시작 (20k/s) +08:58:52 ← nginx -s reload. 전송 12초째, 한가운데 +08:59:21 코드=200 바이트=845361(전량) 연결수=1 +``` + +연결수가 1이라는 것이 핵심이다 — 끊겨서 다시 붙은 것이 아니라, **옛 워커가 +그 요청을 끝까지 책임졌다.** + +### 곁다리 — 측정 장치가 거짓말할 뻔했다 + +in-flight 감시에서 76건이 실패했다. 그대로 적었으면 "갱신 중 대규모 요청 +실패"라는 오보가 됐을 것이다. 서버 탓이 아니었다. + +| 근거 | 값 | +|---|---| +| 같은 순간 폴링 | 49건 **전부 200** | +| 연결수 | **0** — TCP 연결 시도조차 못 했다 | +| 소요 시간 | **50µs** — DNS 조회보다도 짧다 | +| 재현 | **0/100** | + +**대조군이 오보를 막았다.** 상세는 +[`08-inflight-artifact.txt`](evidence/d4-certificate-renewal/08-inflight-artifact.txt). + +전체 판정은 [D-4 문서](experiment-d4-certificate-renewal.md) 와 +[`13-verdict.txt`](evidence/d4-certificate-renewal/13-verdict.txt). --- diff --git a/docs/experiment-index.md b/docs/experiment-index.md index 49b80e6..0c1c3b5 100644 --- a/docs/experiment-index.md +++ b/docs/experiment-index.md @@ -27,7 +27,7 @@ | **D-2** | 버전 업그레이드 | `...d2-version-upgrade` | **이미지를 되돌려도 스키마는 안 돌아온다** | | **D-3** | 비밀 관리 | `...d3-secret-management` | **RBAC 만 실제로 감춘다** | | **D-4** | 인증서 갱신 | `...d4-certificate-renewal` | **갱신은 됐는데 36분 39초 반영 안 됨** (훅 3경로 전부 비었음). reload 자체는 **무중단**(8856건 0실패) | -| **후속** | 미측정 항목 채우기 | `...followup-untested-items` | 정방향 업그레이드 무중단 · **롤백 불가는 조건부였다** · role 변경은 요청으로 반영 안 됨 | +| **후속** | 미측정 3항목 채우기 | `...followup-untested-items` | **셋 다 완료.** 정방향 업그레이드 무중단 · **롤백 불가는 조건부였다** · role 변경은 요청으로 반영 안 됨 · **D-4 갱신 36분 39초 미반영** | ## 시각 자료