Files
keycloak-pattern/docs/evidence/d4-certificate-renewal/08-inflight-artifact.txt
T
DongHyeonkaandClaude Opus 5 0d84921a56 docs(d4): the renewal unit has no reload, and nginx has not reloaded in 22 hours
The forced renewal did not change the certificate being served — 161 serial
samples over 13 minutes, all identical, notBefore still Sep 3.

Two things were readable without sudo and both matter.

nginx has never been reloaded: master 585 and worker 586 share a start time
and both are 80529 seconds old. A reload keeps the master and replaces the
workers, so the original fork still being there means no reload has happened
since Sep 3 19:00. nginx reads the certificate at startup and holds it in
memory; without a reload it serves the old one no matter what is on disk.

And the unit that is supposed to renew has nothing that would reload it:

  [Service]
  Type=oneshot
  ExecStart=/usr/bin/certbot -q renew
  PrivateTmp=true

No ExecStartPost, no --deploy-hook. The timer runs twice a day and exited
0/SUCCESS both times today, which is precisely why this stays invisible —
the renewal is not due for 88 days, so nothing has failed yet.

What remains undetermined is whether the user's --force-renewal failed, or
succeeded and left new files that nginx never picked up. Telling those apart
needs /etc/letsencrypt, which is root-only. If it is the latter, that is the
most valuable thing this lab has produced: "renewal succeeded" and "the new
certificate is being served" are different events.

CT logs looked like a way around the permission problem and were not: the
served certificate carries two valid SCTs, yet crt.sh returns zero rows for
auth.hyeonworks.com. Same shape as A-2's `up` metric — the observing tool
sees a subset of the truth.

Also recorded: 76 in-flight failures at 08:15:04 are a local artifact, not a
server event. The concurrent 0.2s poll was clean across the same second,
num_connects was 0, and time_total was 50 microseconds. Not reproducible in
100 retries. The monitor now records curl's exit code so a recurrence is
diagnosable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 17:28:01 +09:00

67 lines
3.2 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
D-4 — in-flight 감시에서 나온 76건 실패: 서버 탓이 아니다
============================================================
수집: 2026-09-04 · dev 머신에서 실행한 감시 로그
무엇이 보였나
-------------
42초짜리 in-flight 요청을 연달아 돌리는 감시에서, 91건 중 76건이 실패했다.
08:13:40 코드=200 바이트=845361 시간=41.041830 연결수=1
08:14:22 코드=200 바이트=845361 시간=42.338482 연결수=1
08:15:04 코드=000 바이트=0 시간=0.001148 연결수=0 ← 여기부터
08:15:04 코드=000 바이트=0 시간=0.001140 연결수=0
08:15:04 코드=000 바이트=0 시간=0.000055 연결수=0
... (76건, 전부 08:15:04)
08:15:04 코드=200 바이트=845361 시간=42.236496 연결수=1 ← 곧바로 복귀
08:15:47 코드=200 바이트=845361 시간=42.327129 연결수=1
이걸 "갱신 중 진행 중이던 요청이 끊겼다"로 읽으면 안 되는 이유
----------------------------------------------------------------
1. **같은 순간 폴링은 멀쩡했다.** 0.2초 간격 폴링의 08:15:0008:15:20 구간
표본 49건이 전부 200이다. 같은 호스트, 같은 TLS, 같은 머신이다.
08:15:00.24 200 0.092491
08:15:01.15 200 0.099177
08:15:02.38 200 0.078729
... 49건 / 비200 0건
2. **연결수=0 이다.** curl 이 TCP 연결을 시도조차 못 했다는 뜻이다.
서버가 연결을 끊었다면 연결수는 1이고 바이트는 0보다 크다.
3. **시간이 0.00005초다.** 50마이크로초. DNS 조회조차 이보다 오래 걸린다.
요청이 나가기 전에 로컬에서 실패했다.
4. **76건이 1초 안에 몰렸다.** 실패가 즉시 반환되니 while 루프가 폭주한 것이다.
시간축에 퍼진 장애가 아니라 순간의 스파이크다.
5. **재현되지 않는다.**
같은 URL 100회 연속 → 실패 0/100
DNS 해석 200회 → 실패 0/200
URL 유효성 → 코드=200 크기=845361, 콘솔이 참조하는 경로와 동일
6. **nginx 는 그 시각에 아무 일도 하지 않았다.** 마스터·워커가 22.4시간째
같은 프로세스다(07 참조). reload 도 재시작도 없었다.
결론
----
**로컬(dev 머신) 쪽 일시적 원인이며, 서버 가용성 사건이 아니다.**
정확한 원인은 특정하지 못했다 — curl 을 `-s` 로 돌려 오류 메시지를 버렸고,
종료 코드도 기록하지 않았기 때문이다.
고친 것
-------
감시를 종료 코드까지 남기는 형태로 교체했다. 다음에 같은 일이 생기면
curl 의 종료 코드(6=DNS, 7=connect, 35=TLS ...)로 원인이 바로 나온다.
실패 시 1초 쉬게 해 루프 폭주도 막았다.
R=$(curl -s --limit-rate 20k -o /dev/null -w "..." "$URL" 2>/dev/null); E=$?
echo "$T $R curl종료=$E"
[ $E -ne 0 ] && sleep 1
남기는 교훈
-----------
측정 장치가 실패했을 때 **왜 실패했는지 남기지 않으면, 그 실패를 대상 탓으로
돌릴지 장치 탓으로 돌릴지 판단할 근거가 없다.** 여기서는 대조 폴링이 같은
시각에 멀쩡했다는 사실 하나로 겨우 갈랐다. 대조군이 없었다면 이 76건은
"갱신 중 대규모 요청 실패"라는 그럴듯한 오보가 됐을 것이다.