The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
126 lines
6.2 KiB
Plaintext
126 lines
6.2 KiB
Plaintext
D-4a — deploy 훅이 실제로 nginx 를 reload 하는가
|
||
==================================================
|
||
수집: 2026-09-04 12:27 UTC (실제) · test-server
|
||
|
||
D-4 가 남긴 것
|
||
--------------
|
||
| 항목 | 상태 |
|
||
|---|---|
|
||
| deploy 훅을 넣으면 자동 반영되는가 | **미측정. 훅은 아직 넣지 않았다** |
|
||
|
||
D-4 는 처방을 적어두고 **검증하지 않았다.** 처방이 듣는지 모르는 채로
|
||
"이렇게 고치면 된다"고 쓰는 것은 이 실험대가 계속 경계해 온 바로 그 실수다.
|
||
|
||
[주입] 훅 하나를 넣고 강제 갱신
|
||
--------------------------------
|
||
/etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
|
||
#!/bin/sh
|
||
# D-4a: certbot deploy hook. 갱신이 실제로 일어났을 때만 실행된다.
|
||
nginx -t && nginx -s reload
|
||
|
||
install -m755 /tmp/reload-nginx.sh /etc/letsencrypt/renewal-hooks/deploy/
|
||
certbot renew --force-renewal
|
||
|
||
[결과] certbot 출력 — 훅이 실행됐다
|
||
------------------------------------
|
||
Processing /etc/letsencrypt/renewal/auth.hyeonworks.com.conf
|
||
Renewing an existing certificate for auth.hyeonworks.com and 2 more
|
||
Hook 'deploy-hook' ran with error output:
|
||
2026/09/04 21:29:36 [warn] 37250#37250: could not build optimal types_hash, …
|
||
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
|
||
nginx: configuration file /etc/nginx/nginx.conf test is successful
|
||
2026/09/04 21:29:37 [warn] 37251#37251: could not build optimal types_hash, …
|
||
2026/09/04 21:29:37 [notice] 37251#37251: signal process started
|
||
|
||
Congratulations, all renewals succeeded:
|
||
/etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem (success)
|
||
|
||
★ 함정 — "Hook 'deploy-hook' ran with error output" 이라고 적혀 있지만
|
||
실패가 아니다. nginx 가 types_hash 경고를 stderr 로 내보내서 certbot 이
|
||
그것을 "error output" 으로 표시한 것뿐이다. 내용은 전부 성공이다:
|
||
· nginx -t → "test is successful"
|
||
· nginx -s reload → "signal process started"
|
||
로그에서 'error' 를 grep 하면 성공한 훅을 실패로 오독하게 된다.
|
||
|
||
[검증 1] nginx 워커가 교체됐다
|
||
-------------------------------
|
||
전 (D-4a 기준선)
|
||
585 1 ... Thu Sep 3 19:00:39 nginx: master process
|
||
28829 585 ... Fri Sep 4 18:00:35 nginx: worker process ← D-4 에서 사람이 reload 한 것
|
||
|
||
후
|
||
585 1 95412 Thu Sep 3 19:00:39 nginx: master process ← 마스터는 그대로
|
||
37252 585 74 Fri Sep 4 21:29:36 nginx: worker process ← ★ 자동 교체됨
|
||
|
||
마스터 PID 는 유지되고 워커만 바뀌었다 = reload 의 서명. D-4 에서 세운
|
||
진단 방법이 그대로 작동한다.
|
||
|
||
[검증 2] 서빙 인증서가 바뀌었다
|
||
--------------------------------
|
||
serial=06F3E0EF4D1BB03DE58130EAAD1176101373
|
||
notBefore=Sep 4 11:29:18 2026 GMT
|
||
notAfter=Dec 3 11:29:17 2026 GMT
|
||
SAN: app1 / app2 / auth .hyeonworks.com
|
||
|
||
D-4 의 인증서(06C7CB…EA1D)에서 바뀌었다.
|
||
|
||
[검증 3] ★ 얼마나 빨랐나 — 시계 보정이 필요하다
|
||
--------------------------------------------------
|
||
훅 로그와 워커 lstart 는 **test-server 시계**다. 그런데 test-server 는
|
||
NTP 동기가 꺼져 있다(NTPSynchronized=no).
|
||
|
||
외부 기준으로 두 시계를 검증했다.
|
||
dev → Google 차이 +0초
|
||
dev → Let's Encrypt ACME 차이 +0초
|
||
test-server → Google 차이 -105초 (즉 test-server 가 105초 빠르다)
|
||
ssh 왕복 왜곡 3회 측정: +106.1 / +106.1 / +106.1초 (안정적)
|
||
|
||
→ **dev 가 정확하고 test-server 가 106초 빠르다.**
|
||
|
||
독립 검증 — 새 인증서의 SCT (CT 로그가 서명한 시각, 자체 정확 시계):
|
||
Signed Certificate Timestamp: Sep 4 12:27:49.054 2026 GMT
|
||
Signed Certificate Timestamp: Sep 4 12:27:49.048 2026 GMT
|
||
|
||
보정한 타임라인 (전부 실제 UTC):
|
||
12:27:49.05 인증서 발급 ← SCT. 외부 권위 기준
|
||
12:27:50 훅 nginx -t ← 로그 21:29:36 KST − 106초
|
||
12:27:50 새 워커 37252 기동 ← lstart 21:29:36 KST − 106초
|
||
12:27:51 훅 nginx -s reload ← 로그 21:29:37 KST − 106초
|
||
|
||
★ 발급 → 서빙까지 **1~2초.**
|
||
★ 그리고 보정이 자기 검증된다 — 독립 시계인 SCT 가 보정한 훅 시각의
|
||
1초 앞에 정확히 놓인다. 보정하지 않으면 훅이 발급보다 104초 먼저
|
||
실행된 것이 되어 물리적으로 불가능해진다.
|
||
|
||
════ D-4 와의 대조 ════
|
||
|
||
훅 없음 (D-4) 훅 있음 (D-4a)
|
||
갱신 → 서빙 2305초 = 38분 25초 1~2초
|
||
무엇이 reload 했나 사람 (`nginx -s reload`) certbot deploy 훅
|
||
안 했다면 다음 nginx 재시작까지 해당 없음
|
||
= 사실상 무기한
|
||
차이 약 1150배
|
||
|
||
════ 부수 정정 — D-4 의 2199초는 틀렸다 ════
|
||
|
||
D-4 에서 적은 **2199초(36분 39초)** 는 `archive/cert2.pem` 의 mtime
|
||
(test-server 시계)과 일련번호 관측 시각(dev 시계)을 **그대로 뺀** 값이었다.
|
||
106초 보정을 적용하면:
|
||
|
||
새 인증서 디스크 기록 08:20:27 UTC (실제) ← mtime 17:22:13 KST − 106초
|
||
실제 서빙 시작 08:58:52 UTC (실제) ← dev 관측, 보정 불필요
|
||
공백 2305초 = 38분 25초
|
||
|
||
관련 문서를 전부 정정했다. **두 시계에서 온 값을 빼면서 그 사실을 적지
|
||
않으면, 자릿수가 아니라 방향까지 틀릴 수 있다** — 여기서는 그 오차가
|
||
100초 단위였고, 1~2초를 재는 D-4a 에서는 결과를 완전히 뒤집었다.
|
||
|
||
════ 결론 ════
|
||
|
||
1. **D-4 의 처방은 듣는다.** 훅 파일 하나로 발급 → 서빙이 38분에서 1~2초가 됐다.
|
||
2. **certbot 출력의 "ran with error output" 을 실패로 읽으면 안 된다.**
|
||
nginx 의 경고가 stderr 로 나갔을 뿐이며, 내용은 전부 성공이다.
|
||
3. **진단 방법이 재확인됐다** — 마스터 PID 유지 + 워커 PID 교체 = reload.
|
||
4. **시계를 섞어 빼면 안 된다.** test-server 는 NTP 가 꺼져 106초 빠르고,
|
||
그 사실을 적지 않은 채 계산한 D-4 의 공백은 106초 짧았다.
|