The forward upgrade to 26.7.3 was zero downtime across 87 samples, and since databasechangelog stayed at 210 the rollback to 26.7.0 also succeeded, which narrows D-2's conclusion: rolling back fails when the schema moved, not because of the version number. The row count is the check. Role changes never reach the upstream through request repetition; the session is a snapshot taken at login and only a new session picks up the new claim. Auditing the docs also surfaced that Prometheus scrapes only keycloak, kubelet, node-exporter and itself, so the B-layer experiments have no metrics to screenshot rather than missing screenshots. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6.5 KiB
D-4 — 인증서 갱신
브랜치 feature/keycloak-d4-certificate-renewal ·
증거 docs/evidence/d4-certificate-renewal/ ·
2026-09-04 17:25–17:35 KST
구조
다이어그램 규약은
diagrams/_style.md. 실험대 전체 구조는diagrams/lab-topology.svg.
0. 결론부터
| 확인 | 결과 |
|---|---|
| 인증서 구성 | SAN 3개 (auth/app1/app2), 와일드카드 아님 |
| 체인 완결성 | 정상. Verify return code: 0 (ok), 4단계 |
| 갱신 자동화 | 동작 중. certbot-renew.timer enabled·active, 11시간 전 실행됨 |
| 남은 기간 | 88일 (갱신까지 약 58일) |
| 강제 갱신 실측 | ★ 못 했다. sudo: a password is required |
측정한 것과 못 한 것을 나눠 적는다. 못 한 것을 안 한 것처럼 쓰면 이 기록 전체의 신뢰가 깎인다.
1. 인증서 구성 — B-7 에서 실제로 걸린 제약
X509v3 Subject Alternative Name:
DNS:app1.hyeonworks.com, DNS:app2.hyeonworks.com, DNS:auth.hyeonworks.com
세 이름뿐이고 와일드카드가 아니다.
이 제약이 B-7 에서 실제 비용을 만들었다. oauth2-proxy 를 올릴 호스트명이 없어 Grafana 가 쓰던
app2를 빌려야 했고, 그 때문에 관측 스택의 웹 UI 가 실험 동안 내려가 있었다."인증서에 이름을 몇 개 넣을 것인가" 는 TLS 설정이 아니라 나중에 무엇을 배포할 수 있는가를 정하는 결정이다.
| 이 실험대 | 와일드카드였다면 | |
|---|---|---|
| 새 호스트 추가 | 인증서 재발급 필요 | 바로 가능 |
| DNS-01 검증 | 필요 | 필요 (와일드카드는 DNS-01 만 가능) |
| 노출 | 이름 3개만 | 하위 전체가 한 키에 묶인다 |
2. 체인이 완전한가 — 흔한 실수 확인
0 s:CN = auth.hyeonworks.com ← 리프
1 s:C = US, O = Let's Encrypt, CN = YE2 ← 중간
2 s:C = US, O = ISRG, CN = Root YE
3 s:C = US, O = Internet Security Research Group, CN = ISRG Root X2
Verify return code: 0 (ok)
중간 인증서가 함께 제공된다.
개념 — fullchain.pem vs cert.pem
certbot 은 두 파일을 만든다.
| 파일 | 내용 | nginx 에 넣으면 |
|---|---|---|
cert.pem |
리프만 | 일부 클라이언트에서 검증 실패 |
fullchain.pem |
리프 + 중간 | 정상 |
브라우저는 중간 인증서를 캐시하고 있어 cert.pem 으로도 대개 동작한다.
그래서 실수해도 개발 중에는 안 드러나고, 캐시가 없는 클라이언트
(모바일 앱, curl, 다른 서버)에서만 깨진다.
openssl s_client -connect <host>:443 -servername <host> | grep -E "^ *[0-9] s:"
단계가 2개 이상이면 fullchain 이고, 1개면 cert.pem 을 쓴 것이다. 이 실험대는 4단계로 정상이다.
3. 갱신 자동화는 동작한다
NEXT LEFT LAST PASSED
Fri 2026-09-04 17:03:46 KST 1h 54min Fri 2026-09-04 03:19:39 KST 11h ago
타이머 enabled: enabled / active: active
하루 두 번 돌고, 11시간 전에 실제로 실행됐다.
만료: Dec 2 00:47:22 2026 GMT
남은 일수: 88일
아직 갱신하지 않은 것이 정상이다 — Let's Encrypt 는 90일 발급이고 certbot 은 30일 남았을 때 갱신한다. 지금 실행돼도 아무것도 안 한다.
타이머가 돌았다는 것과 갱신이 됐다는 것은 다르다. "타이머가 active 니까 괜찮다" 는 확인이 아니다. 실제 갱신은 58일 뒤이며, 그때 처음으로 절차가 시험된다.
4. ★ 못 한 것 — 강제 갱신과 무중단 확인
계획서의 D-4 는 이렇게 적혀 있었다.
sudo certbot renew --force-renewal
실행할 수 없었다.
$ sudo -n -l
sudo: a password is required
$ sudo -n systemctl reload nginx
sudo: a password is required
test-server 의 sudo 는 비밀번호를 요구한다. 게스트(kc-lab-1/2)는 무암호라
A층에서 conntrack·tc 를 자유롭게 썼는데, 호스트는 다르다.
이 사실은 B-7 에서 처음 드러났다 — nginx 설정을 읽으려던 시도가 계속 빈 결과였고, 그게 sudo 의 조용한 실패였다. 여기서 다시 확인된다.
그래서 답하지 못한 것
| 계획서의 항목 | 상태 |
|---|---|
| nginx reload 타이밍에 무중단인가 | 미측정 |
| 갱신 중 진행 중이던 요청은 | 미측정 |
certbot-renew.timer 가 실제 갱신을 하는가 |
미측정 (58일 뒤에야 알 수 있다) |
이론적으로는 무엇을 기대하는가
certbot renew → 새 인증서 파일 저장
└─ deploy-hook: nginx -s reload
└─ nginx 는 새 워커를 띄우고 옛 워커는 진행 중 요청을 끝낸 뒤 종료
→ graceful. 진행 중 요청은 옛 인증서로 완결된다
nginx 의 reload 는 설계상 무중단이지만, 확인하지 않았으므로 그렇게
쓰면 안 된다. 이 실험대에서 반복해 배운 것이 바로 그것이다 —
A-1 의 NetworkPolicy, A-3 의 --grace-period=0, B-5 의 AOF 모두
"그럴 것이다" 가 틀렸던 사례다.
5. 재현 절차 (명령어)
# 1. 인증서 내용 — 밖에서 볼 수 있다
echo | openssl s_client -connect auth.hyeonworks.com:443 -servername auth.hyeonworks.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
# 2. 체인 완결성 — 단계가 1개면 cert.pem 을 쓴 것이다
echo | openssl s_client -connect auth.hyeonworks.com:443 -servername auth.hyeonworks.com 2>/dev/null \
| grep -E "^ *[0-9] s:|Verify return code"
# 3. 갱신 자동화
systemctl list-timers certbot-renew.timer --no-pager
systemctl is-enabled certbot-renew.timer
# 4. 강제 갱신 (sudo 필요 — 이 실험대에서는 불가)
sudo certbot renew --force-renewal
# 갱신 중 다른 창에서:
# while true; do curl -s -o /dev/null -w '%{http_code} ' https://auth.hyeonworks.com/realms/master; sleep 1; done
6. 남긴 것
| 강제 갱신 + 무중단 측정 | sudo 권한이 필요하다 |
| SAN 확장 | 새 호스트를 쓰려면 재발급 — B-7 에서 실제로 걸렸다 |
| D-3 과 연결 | 인증서 개인키도 같은 비밀 관리 문제다 |