fix(setup): 실험대에서 35편을 끝까지 밟고 어긋난 명령·결과 31건을 고친다
test-server 를 비우고 다시 세운 뒤 Setup 기록 35편(virtualization 9 ·
keycloak-session-store 26)을 문서에 적힌 명령 그대로 쳤다. 어긋난 자리를
기록과 SSOT 양쪽에 실측과 함께 넣었다.
막히던 것
- 04 의 인증서 경로가 live/hyeonworks.com 이라 nginx 가 [emerg] 로 안 떴다.
실제 계보는 live/auth.hyeonworks.com 이고 「문제가 생기면」은 진단이 거꾸로였다
- 인증서가 와일드카드가 아니다. SAN 이 auth·app1·app2 셋뿐이라 그 밖의 이름은
TLS 에서 끊기고 curl 이 exit 60 · %{http_code} 000 을 낸다. SSOT 안에서
두 문단이 서로 어긋나 있었다
- A-7 14번 ①이 kc-lab-1 에서 여섯 줄 다 실패하는데 마지막 date 만 「차단」을 찍는다
검사가 실패할 수 없던 자리
- B-1 의 세션 키 고르기는 앞 단계가 $KEY 를 채워 둬서 루프가 한 건도 못 맞혀도
통과한다. KEY= 로 비우고 키마다 1/0 을 찍게 바꿨다
- k3s-agent 유닛의 sed -i 는 패턴에 $HOME 이 들어 있어 아무 줄도 안 바꾼 채 성공한다
certbot
- renew --dry-run 의 종료 코드는 성공도 0, 실패도 0, 다른 사유의 실패는 1 이다.
본문의 renew failure(s) 로만 판정할 수 있다
- --dry-run 은 staging 서버를 쓰는데 renewal/*.conf 의 account= 는 운영 계정을
가리킨다. 실패한 dry-run 이 staging 계정을 하나 더 만들어 다음 실행이 계속 멎는다
- 훅을 755 로 놓고 시뮬레이션이 성공해도 Running deploy-hook command 는 안 나온다.
certbot 2.1.0 에는 --run-deploy-hooks 도 없다
- 강제 갱신은 실제로 쳤고 서빙까지 닿았다. serial 06F3E0EF…1373 → 065547…3DF1,
notAfter Dec 3 → Dec 16, nginx worker 2629 4712 → 4745 4754
독자가 칠 수 있는 형태로
- 안 되는 형태가 번호 붙은 단계에 앉아 있던 8곳을 뒤집고, 되는 형태를 ①로 올렸다
- 랩 안에서 공개 이름을 치는 curl 65줄에 --resolve 를 붙였다. 붙인 형태를 실제로
쳐서 문서가 적은 값과 같은지 확인했다
- 힙독·sed -i·echo >>·&&·|| 를 편집기 + 파일 리스팅 + 분할 형태로 바꿨다
- 닫는 코드펜스가 빠져 뒤 200여 줄의 블록 종류가 뒤집혀 있던 곳을 포함해 3곳을 고쳤다
관문: check_body PASS · check_prose error 0 · check_evidence 두 프로젝트 문제 없음 ·
verify-tech-log-tree error 0 · verify-project-layout error 0 · 코드펜스 전수 0건
남은 것: B-0 주입은 keycloak-pattern 저장소의 소스를 고치고 이미지를 다시 구워야
해서 안 했다(unknown).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4a457afde9
commit
32e39e20aa
+15
-5
@@ -59,6 +59,8 @@ sourceRevision: cdac9b8178391311d8eca1ebc6cac15bb62d79af
|
||||
|
||||
**이 절차에는 스크립트가 없다.** 원래 실행은 백업·파괴·복구를 스크립트 하나로 돌렸고, 그래서 증거 파일의 줄에는 `realms|clients|users|sessions|authclients = 2|15|2|3|1` 처럼 이름표가 붙어 있다. 사람이 치는 형태가 아니다. 그리고 이 실험에서 스크립트는 특히 위험하다 — `DROP SCHEMA` 와 복구가 한 파일에 있으면 중간에서 멈췄을 때 무엇이 실행됐는지 알 수 없다. 파괴를 손으로 치고, 눈으로 확인하고, 복구도 손으로 친다.
|
||||
|
||||
**공개 이름은 랩 안에서 안 풀린다.** `auth.hyeonworks.com` 같은 공개 이름이 랩 호스트에서도 게스트에서도 호스트 자신의 tailnet 주소 `100.83.212.4` 로 풀리는데 그 주소에는 443 을 듣는 것이 없다. 이름만 치면 `curl` 이 `000` 을 낸다(2026-09-17, 랩 호스트와 `kc-lab-1` 양쪽에서 쳐서 확인했다, observed). 그래서 랩 안에서 치는 `curl` 에는 `--resolve <이름>:443:192.168.122.10` 을 붙여 엣지 게스트를 짚었고, 그 형태로는 정문이 `200` 이다(observed). tailnet 에 붙은 다른 기계에서 치면 이름 그대로 닿으므로 `--resolve` 가 필요 없다.
|
||||
|
||||
## 이 실험이 가르는 것
|
||||
|
||||
「백업이 있다」와 「복구해 봤다」는 다른 문장이다. 백업 스크립트가 매일 도는 것과 그 파일로 실제로 서비스를 되살리는 것 사이에는 시험되지 않은 가정이 여러 개 있고, 이 절차는 그중 둘을 판정한다.
|
||||
@@ -194,14 +196,16 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
**무엇을 보는가** — 정문과 app1 의 응답. 처음 한 번은 응답을 읽는다.
|
||||
|
||||
```bash label="[lab host] ① 헤더를 통째로 본다"
|
||||
curl -I https://auth.hyeonworks.com/realms/master
|
||||
curl -I --resolve auth.hyeonworks.com:443:192.168.122.10 https://auth.hyeonworks.com/realms/master
|
||||
```
|
||||
|
||||
헤더가 통째로 나온다. `HTTP/2 200`, `content-type: application/json` 을 본다. 같은 것을 반복해서 재고 비교할 때만 코드만 뽑는다.
|
||||
|
||||
```bash label="[lab host] ② 코드만 뽑아 둘을 잰다"
|
||||
curl -s -o /dev/null -w '%{http_code}\n' https://auth.hyeonworks.com/realms/master
|
||||
curl -s -o /dev/null -w '%{http_code}\n' https://app1.hyeonworks.com/
|
||||
curl -s -o /dev/null -w '%{http_code}\n' --resolve auth.hyeonworks.com:443:192.168.122.10 \
|
||||
https://auth.hyeonworks.com/realms/master
|
||||
curl -s -o /dev/null -w '%{http_code}\n' --resolve app1.hyeonworks.com:443:192.168.122.10 \
|
||||
https://app1.hyeonworks.com/
|
||||
```
|
||||
|
||||
**어디를 보나** — 실측은 이렇다(observed, `02-destruction.txt`). 이것도 파괴 직후 값이고, 그게 결과다.
|
||||
@@ -400,8 +404,10 @@ LINE 1: select count(*) from realm
|
||||
|
||||
```bash label="[lab host] ③ 파드와 밖에서 본 상태를 다시 잰다"
|
||||
kubectl -n keycloak-lab get pods -o wide
|
||||
curl -s -o /dev/null -w '%{http_code}\n' https://auth.hyeonworks.com/realms/master
|
||||
curl -s -o /dev/null -w '%{http_code}\n' https://app1.hyeonworks.com/
|
||||
curl -s -o /dev/null -w '%{http_code}\n' --resolve auth.hyeonworks.com:443:192.168.122.10 \
|
||||
https://auth.hyeonworks.com/realms/master
|
||||
curl -s -o /dev/null -w '%{http_code}\n' --resolve app1.hyeonworks.com:443:192.168.122.10 \
|
||||
https://app1.hyeonworks.com/
|
||||
```
|
||||
|
||||
실측은 이렇다(observed, `02-destruction.txt`).
|
||||
@@ -441,8 +447,10 @@ kubectl -n keycloak-lab exec -i deploy/postgres -- psql ... < dump.sql # ✔
|
||||
|
||||
```bash label="[lab host] ① 두 경로를 잰다"
|
||||
curl -s -o /dev/null -w 'certs %{http_code}\n' \
|
||||
--resolve auth.hyeonworks.com:443:192.168.122.10 \
|
||||
https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/certs
|
||||
curl -s -o /dev/null -w 'well-known %{http_code}\n' \
|
||||
--resolve auth.hyeonworks.com:443:192.168.122.10 \
|
||||
https://auth.hyeonworks.com/realms/keycloak-patterns/.well-known/openid-configuration
|
||||
```
|
||||
|
||||
@@ -458,6 +466,7 @@ curl -s -o /dev/null -w 'well-known %{http_code}\n' \
|
||||
|
||||
```bash label="[lab host] ② 토큰 발급을 잰다"
|
||||
curl -s -o /dev/null -w '토큰 %{http_code}\n' -X POST \
|
||||
--resolve auth.hyeonworks.com:443:192.168.122.10 \
|
||||
https://auth.hyeonworks.com/realms/master/protocol/openid-connect/token \
|
||||
-d grant_type=password -d client_id=admin-cli -d username=admin \
|
||||
-d "password=$(kubectl -n keycloak-lab get secret keycloak-lab-secrets \
|
||||
@@ -594,6 +603,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -tA
|
||||
|
||||
```bash label="[lab host] ① 15초 뒤에 다시 잰다"
|
||||
curl -s -o /dev/null -w 'well-known %{http_code}\n' \
|
||||
--resolve auth.hyeonworks.com:443:192.168.122.10 \
|
||||
https://auth.hyeonworks.com/realms/keycloak-patterns/.well-known/openid-configuration
|
||||
kubectl -n keycloak-lab get pods -o wide | grep keycloak
|
||||
```
|
||||
|
||||
+6
-3
@@ -60,6 +60,8 @@ databasechangelog 행 수를 먼저 세고 이미지 태그를 정방향·롤백
|
||||
|
||||
**실측이 두 실행에서 나온다**(observed). 처음 실행은 `15:00–15:10` 에 역방향 `26.0` 을 쳤고, 후속 실행은 `15:22–15:26` 에 `26.7.3` 정방향과 롤백을 쳤다. 아래에서도 어느 쪽인지 매번 적는다.
|
||||
|
||||
**공개 이름은 랩 안에서 안 풀린다.** `auth.hyeonworks.com` 같은 공개 이름이 랩 호스트에서도 게스트에서도 호스트 자신의 tailnet 주소 `100.83.212.4` 로 풀리는데 그 주소에는 443 을 듣는 것이 없다. 이름만 치면 `curl` 이 `000` 을 낸다(2026-09-17, 랩 호스트와 `kc-lab-1` 양쪽에서 쳐서 확인했다, observed). 그래서 랩 안에서 치는 `curl` 에는 `--resolve <이름>:443:192.168.122.10` 을 붙여 엣지 게스트를 짚었고, 그 형태로는 정문이 `200` 이다(observed). tailnet 에 붙은 다른 기계에서 치면 이름 그대로 닿으므로 `--resolve` 가 필요 없다.
|
||||
|
||||
## 이 실험이 가르는 것
|
||||
|
||||
「문제가 생기면 이미지 태그를 되돌린다」는 거의 모든 배포 계획서에 적혀 있다. 그 계획이 언제 동작하고 언제 동작하지 않는지를 가른다.
|
||||
@@ -278,7 +280,7 @@ curl -s "https://quay.io/api/v1/repository/keycloak/keycloak/tag/?limit=40&onlyA
|
||||
```bash label="[lab host] ① 폴링을 뒤에서 돌린다"
|
||||
( for i in $(seq 1 150); do
|
||||
printf '%s ' "$(curl -s -o /dev/null -w '%{http_code}' --max-time 3 \
|
||||
https://auth.hyeonworks.com/realms/master)"
|
||||
--resolve auth.hyeonworks.com:443:192.168.122.10 https://auth.hyeonworks.com/realms/master)"
|
||||
sleep 1
|
||||
done > /tmp/d2-avail.txt ) &
|
||||
```
|
||||
@@ -462,7 +464,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -tA
|
||||
```bash label="[lab host] ⓪ 폴링 창에서 다시 띄운다"
|
||||
( for i in $(seq 1 150); do
|
||||
printf '%s ' "$(curl -s -o /dev/null -w '%{http_code}' --max-time 3 \
|
||||
https://auth.hyeonworks.com/realms/master)"
|
||||
--resolve auth.hyeonworks.com:443:192.168.122.10 https://auth.hyeonworks.com/realms/master)"
|
||||
sleep 1
|
||||
done > /tmp/d2-avail.txt ) &
|
||||
```
|
||||
@@ -582,7 +584,8 @@ kubectl -n keycloak-lab logs keycloak-1 --previous
|
||||
### 7. 그런데 서비스는 살아 있다
|
||||
|
||||
```bash label="[lab host] 밖과 Service 와 StatefulSet 을 함께 본다"
|
||||
curl -s -o /dev/null -w '%{http_code}\n' https://auth.hyeonworks.com/realms/master
|
||||
curl -s -o /dev/null -w '%{http_code}\n' --resolve auth.hyeonworks.com:443:192.168.122.10 \
|
||||
https://auth.hyeonworks.com/realms/master
|
||||
kubectl -n keycloak-lab get endpointslice -l kubernetes.io/service-name=keycloak \
|
||||
-o "custom-columns=NAME:.metadata.name,ADDR:.endpoints[*].addresses,READY:.endpoints[*].conditions.ready"
|
||||
kubectl -n keycloak-lab get statefulset keycloak
|
||||
|
||||
+29
-5
@@ -407,6 +407,21 @@ ssh test-server "ps -eo pid,ppid,etimes,lstart,args | grep 'nginx:' | grep -v gr
|
||||
|
||||
**문제가 생기면** — 출력이 비면 `grep 'nginx:'` 의 콜론을 빠뜨렸거나 nginx 가 떠 있지 않다. `systemctl status nginx` 부터 본다.
|
||||
|
||||
**이 단계도 기계가 틀렸다**(2026-09-17, observed). 위 명령은 `test-server` 에서 nginx 를 찾는데, 기반 가이드 03 이 nginx 를 `kc-lab-edge` 로 옮겼다. 그래서 랩 호스트에서는 **한 줄도 안 나온다** — 5·6 절이 certbot 을 엣지에서 찾는 것과 같은 이유다. 찾는 곳을 엣지로 바꾼다.
|
||||
|
||||
```bash label="[kc-lab-edge] nginx 프로세스 두 줄을 본다"
|
||||
ssh kc-lab-edge "ps -eo pid,ppid,etimes,lstart,args | grep 'nginx:' | grep -v grep"
|
||||
```
|
||||
|
||||
```text label="2026-09-17 의 두 줄"
|
||||
1065 1 11145 Thu Sep 17 04:27:39 2026 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
|
||||
1106 1065 11135 Thu Sep 17 04:27:49 2026 nginx: worker process
|
||||
```
|
||||
|
||||
**이 두 줄은 위 예시와 한 군데가 다르고, 그 다름이 판정표를 그대로 보여 준다.** 위 예시는 마스터와 워커의 `lstart` 와 `etimes` 가 같아서 「기동 이후 reload 가 없었다」였다. 여기서는 워커가 10초 늦게 떴고 `etimes` 도 10 작다 — 03 을 밟으면서 `systemctl reload nginx` 를 친 흔적이다. 마스터는 그대로 두고 워커만 갈아 끼운 것이 숫자로 남는다.
|
||||
|
||||
또 하나. 엣지의 마스터 명령줄은 `/usr/sbin/nginx -g daemon on; master_process on;` 이고 위 예시의 `/usr/bin/nginx` 와 경로가 다르다. Arch 호스트와 Debian 게스트의 패키징 차이다.
|
||||
|
||||
### 8. 두 기계의 시계 차를 지금 잰다
|
||||
|
||||
**목적** — 주입 후에는 되짚을 수 없는 값을 확보한다. SSH(Secure Shell, 원격 셸 접속) 왕복에 걸리는 시간까지 함께 본다. 이 실험은 이걸 나중에 하는 바람에 공백 수치를 한 번 틀렸다.
|
||||
@@ -670,11 +685,11 @@ sudo: a password is required
|
||||
ssh -t test-server 'sudo certbot renew --dry-run'
|
||||
```
|
||||
|
||||
**예상 결과** — 끝의 `simulated renewals` 요약이 나온다. 훅을 넣었다면 `Running deploy-hook command` 줄도 나오는데, 이 실험대는 훅이 없는 상태에서 쟀으므로 그 줄은 미검증이다(unknown).
|
||||
**예상 결과** — 끝의 `simulated renewals` 요약이 나온다. **훅을 넣어도 `Running deploy-hook command` 줄은 안 나온다**(2026-09-17, observed) — certbot 2.1.0 의 dry-run 은 deploy 훅을 건너뛴다. 근거는 D-4a 의 「인증서를 세우고 훅까지 놓아도」 절에 있다. 그리고 같은 명령이 DNS 전파 때문에 한 번 실패하고 다음 번에 성공하기도 한다.
|
||||
|
||||
**왜 필요한가** — dry-run 은 인증서를 발급하지 않고 한도도 안 깎는다. 절차가 도는지, 검증이 통과하는지까지만 말해 준다. 파일이 실제로 바뀌었을 때 nginx 가 그것을 집는지는 dry-run 으로 알 수 없다.
|
||||
|
||||
**문제가 생기면** — 여기서 실패하면 강제 갱신도 실패한다. 오류 문구를 읽고 고친 뒤 다시 친다.
|
||||
**문제가 생기면** — **여기서 실패했다고 강제 갱신도 실패하는 것은 아니다**(2026-09-17, observed). `--dry-run` 은 Let's Encrypt 의 **staging 서버**로 붙는데 `renewal/*.conf` 의 `account =` 은 **운영 계정**을 가리킨다. 그래서 staging 쪽 ACME 계정이 둘 이상이면 dry-run 만 `Please choose an account` 로 멈는다 — 운영 경로에는 그 계정이 하나뿐이라 같은 일이 안 일어난다. 오류 문구에 `account` 가 들어 있으면 D-4a 의 「`--dry-run` 이 보는 계정」 절을 먼저 읽는다. 자격증명 파일이 없거나 DNS-01 검증이 안 되는 경우는 두 경로가 같은 것을 쓰므로 강제 갱신에서도 그대로 날 것으로 보는데, 이 실험대에서 그렇게 재 보지는 않았다(unknown).
|
||||
|
||||
### 13. 강제 갱신을 한 번 친다
|
||||
|
||||
@@ -698,6 +713,15 @@ ssh -t test-server 'sudo certbot renew --force-renewal'
|
||||
|
||||
**문제가 생기면** — 발급 한도에 걸렸으면 주당 중복 인증서 5장을 이미 썼다는 뜻이다. 다음 주까지 기다린다.
|
||||
|
||||
**★ 2026-09-17 에 다시 치는 곳은 `test-server` 가 아니라 `kc-lab-edge` 다**(observed). 인증서와 nginx 가 엣지 게스트로 옵기면서 명령을 치는 기계도 바뀜었고, 거기는 NOPASSWD sudo 라 `-t` 가 필요 없다.
|
||||
|
||||
```bash label="[kc-lab-edge] 이 실험대가 2026-09-17 에 친 형태"
|
||||
sudo certbot renew --force-renewal
|
||||
```
|
||||
|
||||
결과는 `Congratulations, all renewals succeeded:` 였고 `certbot exit=0` 이었다. 배포 훅이 돌아 서빙하는 인증서까지 바뀌었는데, 그 대조는 D-4a 의 「엣지에서 다시 치고」 절에 있다 — 일련번호가 `06F3E0EF4D1BB03DE58130EAAD1176101373` 에서 `065547991777D11A408CEA90D945DDA03DF1` 로, `notAfter` 가 `Dec 3` 에서 `Dec 16` 으로 바뀜다.
|
||||
|
||||
|
||||
## 주입 검증
|
||||
|
||||
「갱신 실패」와 「갱신은 됐는데 안 집었다」를 가르는 절이다. 이 실험은 처음에 이 둘을 구별하지 못해 두 갈래로 적어 뒀었다.
|
||||
@@ -1106,8 +1130,8 @@ crt.sh 에 관한 곁다리도 실측이다. 발급 사실은 Certificate Transp
|
||||
|
||||
2026-09-17 에 기반 가이드로 실험대를 새로 세우고 이 편을 어디까지 밟고 멈췄는지 적는다. **못 밟은 것을 밟은 것처럼 적지 않으려고 남긴다.**
|
||||
|
||||
- **남은 것** — 인증서 발급과 갱신·reload 판정 전부. Cloudflare API 토큰이 있어야 DNS-01 이 돈다. 지금까지 밟은 것 — 기계·유닛 이름 대조, 기본 유닛에 훅이 없다는 확인.
|
||||
- **막는 것** — `https://auth.hyeonworks.com` 이 서지 않는다. 와일드카드 인증서(Cloudflare API 토큰이 필요한 DNS-01)와, 밖에서 실험대에 닿는 길(호스트의 libvirt `guest_input` 구멍 — A-4 에서 확인한 `ExecStartPost` 누락)이 둘 다 있어야 한다.
|
||||
- **남은 것** — 인증서 발급과 갱신·reload 판정 전부(9~22 절). Cloudflare API 토큰이 있어야 DNS-01 이 돈다. 지금까지 밟은 것 — 기계·유닛 이름 대조(5 절), 기본 유닛에 훅이 없다는 확인(6 절 셋 다), nginx 워커 PID 판정 기준(7 절, 기계를 엣지로 고쳐서), 두 기계의 시계 차(8 절).
|
||||
- **막는 것** — `https://auth.hyeonworks.com` 이 서지 않는다. **남은 것은 인증서 하나다**(2026-09-17 기준). 같이 적어 두었던 다른 둘은 그날 해결됐다 — 밖에서 닿는 길은 호스트의 libvirt `guest_input` 구멍과 유닛의 `ExecStartPost` 가 들어가면서 열렸고(`http` 가 밖에서 `200`), 클러스터 안에서 그 이름이 엣지를 안 가리키던 것은 기반 가이드 03 의 CoreDNS 한 단계로 놓았다. 인증서는 Cloudflare API 토큰이 필요한 DNS-01 로만 받을 수 있다 — 이름 셋이 tailnet 주소로 풀려 HTTP-01 은 성립하지 않는다.
|
||||
- **그때까지 이 편의 실측 가운데 `(observed)` 로 적힌 2026-09-17 값은 위 「지금까지 밟은 것」 범위뿐이다.** 나머지는 원래 실행의 값이다.
|
||||
|
||||
## 무엇이 관측이고 무엇이 아닌가
|
||||
@@ -1116,7 +1140,7 @@ crt.sh 에 관한 곁다리도 실측이다. 발급 사실은 Certificate Transp
|
||||
- (observed, 호스트 확인) `nginx.service` 의 유효 설정 여덟 값. 증거 파일이 아니라 이 호스트에서 확인한 값이라 가이드가 실측(호스트)으로 따로 표시했다.
|
||||
- **이 편이 잰 reload 는 사람이 쳤다** — `08:58:52` 의 `nginx -s reload` 는 복구 절에서 사람이 `ssh -t` 로 붙어 친 한 줄이다. 훅이 부르는 자동 reload 는 이 실험대에 아직 없었고(그것이 이 편의 진단이다) D-4a 에서 넣어 따로 쟀다. 무중단 판정의 `8856건` 과 `845361` 바이트는 사람이 건 reload 를 잰 값이다.
|
||||
- **`2199초` 는 이 실험이 스스로 정정했다** — 처음에 `archive/cert2.pem` 의 mtime(test-server 시계)과 일련번호 관측(dev 시계)을 그대로 빼서 `2199초` 로 적었고, 시계 왜곡 106초를 보정한 뒤 `2305초` 로 고쳤다. 틀린 값과 맞는 값을 둘 다 적어 둔 까닭은 어느 쪽이 왜 틀렸는지가 이 편의 교훈이기 때문이다. 보정을 자기 검증한 것은 D-4a 이고, 거기서는 같은 106초가 결과를 뒤집는다.
|
||||
- (unknown) `certbot renew --dry-run` 에서 `Running deploy-hook command` 줄이 나오는지 — 이 실험대는 훅이 없는 상태에서 쟀다. `openssl … -checkend 2592000` 감시 한 줄, `systemctl reload nginx` 형태. 가이드가 전부 미검증으로 표시했다.
|
||||
- (observed, 2026-09-17) `certbot renew --dry-run` 은 훅을 놓아도 `Running deploy-hook command` 를 안 낸다 — certbot 2.1.0 의 dry-run 이 deploy 훅을 건너뛰고 `--run-deploy-hooks` 플래그도 없다. `openssl … -checkend 2592000` 감시 한 줄, `systemctl reload nginx` 형태는 여전히 미검증이다(unknown).
|
||||
- **비밀은 옮기지 않았다** — 이 편이 다루는 파일 중 비밀인 것은 `privkey2.pem` 하나이고 크기(`241`)와 권한(`-rw-------`)만 적었다. 내용은 열지 않았고 가이드도 열지 않는다. 일련번호·`Log ID`·호스트명은 식별자라 그대로 적었다.
|
||||
- **이 실험이 재지 않은 것** — 훅이 진짜로 실패했을 때 certbot 이 무엇을 찍는지, 전송 중 아티팩트 76건의 원인, 타이머가 스스로 갱신하는 경로(만료 30일 전에야 조건이 성립한다). 전송 중 감시는 전체 50건이었고 그 이상 반복하지 않았다.
|
||||
|
||||
|
||||
+222
-4
@@ -394,14 +394,232 @@ nginx: configuration file /etc/nginx/nginx.conf test is successful
|
||||
|
||||
**문제가 생기면** — `nginx -t` 가 실패하면 `&&` 뒤가 안 돌고 워커도 안 바뀐다. `nginx.conf` 를 고친 뒤 다시 친다.
|
||||
|
||||
certbot 이 훅을 부르는지 먼저 보는 형태도 있는데, 이 실험대는 곧바로 강제 갱신을 했다(observed). 아래는 가이드가 미검증으로 표시한 줄이다(unknown).
|
||||
**5~8 절은 2026-09-17 에 실제로 밟았다. 다만 기계가 `test-server` 가 아니다**(observed). 기반 가이드 03 이 nginx 를, 04 가 certbot 을 `kc-lab-edge` 로 옮겼으므로 `/etc/letsencrypt/renewal-hooks/deploy/` 도 `nginx` 도 그 게스트 안에 있다. 1 절의 `ps` 줄을 랩 호스트에서 치면 **한 줄도 안 나온다.** 아래는 엣지에서 친 것이다 — **따라 하는 순서가 아니라 그날의 기록이다.** 첫 줄의 훅 파일은 5 절 ②의 `nano /tmp/reload-nginx.sh` 로 연다. `printf` 로 찍으면 `\n` 과 `>` 를 먼저 해독한 뒤에야 정작 읽어야 할 두 줄에 닿는데, 그 두 줄은 나중에 열어서 고칠 파일이다.
|
||||
|
||||
```bash label="[test-server] dry-run 으로 훅 호출만 본다 (unknown)"
|
||||
```bash label="[kc-lab-edge] 5~8 절을 엣지에서 이어 친 형태 (observed)"
|
||||
printf '#!/bin/sh\nnginx -t && nginx -s reload\n' > /tmp/reload-nginx.sh
|
||||
sudo install -m755 /tmp/reload-nginx.sh /etc/letsencrypt/renewal-hooks/deploy/
|
||||
sudo ls -l /etc/letsencrypt/renewal-hooks/deploy/
|
||||
sudo /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
|
||||
```
|
||||
|
||||
```text label="7 절의 실측 — 파일 크기가 40 이 아니라 38 이다"
|
||||
total 4
|
||||
-rwxr-xr-x 1 root root 38 Sep 17 07:34 reload-nginx.sh
|
||||
```
|
||||
|
||||
```text label="8 절의 실측 — 위 예상 결과에 없는 셋째 줄이 나온다"
|
||||
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
|
||||
nginx: configuration file /etc/nginx/nginx.conf test is successful
|
||||
2026/09/17 07:34:08 [notice] 2361#2361: signal process started
|
||||
```
|
||||
|
||||
셋째 줄은 `nginx -s reload` 가 신호를 보내는 프로세스를 띄웠다고 알리는 것이고, 실패가 아니다. 위 예상 결과는 `nginx -t` 의 두 줄만 실어서 이 줄을 보면 잠깐 멈추게 된다.
|
||||
|
||||
**그리고 이 절이 세우려는 판정 기준이 여기서 그대로 작동했다**(observed). 훅을 손으로 돌리기 전과 뒤의 두 줄이 이렇다.
|
||||
|
||||
```text label="훅 실행 전후"
|
||||
전 1065 1 11186 Thu Sep 17 04:27:39 2026 nginx: master process
|
||||
1106 1065 11177 Thu Sep 17 04:27:49 2026 nginx: worker process
|
||||
뒤 1065 1 11189 Thu Sep 17 04:27:39 2026 nginx: master process
|
||||
2362 1065 0 Thu Sep 17 07:34:08 2026 nginx: worker process
|
||||
```
|
||||
|
||||
마스터는 `1065` 로 그대로이고 워커가 `1106` 에서 `2362` 로 갈렸으며 새 워커의 `etimes` 가 `0` 이다. 판정표의 「마스터 그대로 · 워커 바뀜 = reload 됐다」 그 칸이다. 10 절이 이 방법으로 훅의 효과를 판정하는데, **인증서가 없어도 이 판정 기준 자체는 여기서 검증된다.**
|
||||
|
||||
certbot 이 훅을 부르는지 먼저 보는 형태도 있는데, 이 실험대는 곧바로 강제 갱신을 했다(observed). 아래 줄은 오래 미검증이었는데 **2026-09-17 에 쳤다**(observed). 인증서가 한 장도 없는 상태에서도 돌고, 종료 코드는 `0` 이다.
|
||||
|
||||
```bash label="[kc-lab-edge] dry-run 으로 훅 호출만 본다"
|
||||
sudo certbot renew --dry-run
|
||||
```
|
||||
|
||||
```text label="인증서가 없을 때의 출력"
|
||||
Saving debug log to /var/log/letsencrypt/letsencrypt.log
|
||||
No simulated renewals were attempted.
|
||||
```
|
||||
|
||||
**갱신할 것이 없으면 훅도 안 불린다.** `Running deploy-hook command` 줄은 안 나오고 `No simulated renewals were attempted.` 한 줄로 끝나며, 그래도 종료 코드는 `0` 이다. 그래서 **이 명령의 성공은 훅이 도는지에 대해 아무 말도 안 한다**.
|
||||
|
||||
**★ 그런데 인증서를 세우고 훅까지 놓아도 dry-run 은 훅을 안 부른다**(2026-09-17, observed). 오래 미검증으로 남겨 둔 줄이라 이번에 재 봤다. `/etc/letsencrypt/renewal-hooks/deploy/` 에 실행 권한까지 준 훅을 놓고, 시뮬레이션이 **성공한** 판에서도 `Running deploy-hook command` 는 안 나온다.
|
||||
|
||||
```bash label="[kc-lab-edge] 훅을 놓고, 성공한 dry-run 출력에서 그 낱말을 센다"
|
||||
sudo ls -l /etc/letsencrypt/renewal-hooks/deploy/
|
||||
sudo grep -c deploy-hook /tmp/dr.txt
|
||||
certbot --version
|
||||
```
|
||||
|
||||
```text label="이 실험대의 값"
|
||||
-rwxr-xr-x 1 root root 38 Sep 17 09:23 reload-nginx.sh
|
||||
0
|
||||
certbot 2.1.0
|
||||
```
|
||||
|
||||
`certbot --help all` 에 `--run-deploy-hooks` 도 없다(2.1.0 기준, 센 값 `0`). **그러므로 이 판의 certbot 에서는 dry-run 으로 훅을 확인할 방법이 없다.** 이 절의 사전 점검은 「갱신 절차가 도는가」까지만 말한다. 훅이 도는 것은 다음 절의 진짜 갱신에서만 보인다.
|
||||
|
||||
**★ 그리고 같은 명령이 성공하기도 실패하기도 한다**(2026-09-17, observed). 세 번을 연달아 쳐는데 성공 · 실패 · 성공이었다. 실패한 판은 앞의 둘과 또 다른 사유다.
|
||||
|
||||
```text label="세 번째 실패 사유 — DNS 전파"
|
||||
Certbot failed to authenticate some domains (authenticator: dns-cloudflare). The Certificate Authority reported these problems:
|
||||
Domain: auth.hyeonworks.com
|
||||
Type: dns
|
||||
Detail: During secondary validation: DNS problem: NXDOMAIN looking up TXT for _acme-challenge.auth.hyeonworks.com - check that a DNS record exists for this domain
|
||||
|
||||
Hint: The Certificate Authority failed to verify the DNS TXT records created by --dns-cloudflare. Ensure the above domains are hosted by this DNS provider, or try increasing --dns-cloudflare-propagation-seconds (currently 30 seconds).
|
||||
certbot exit=1
|
||||
```
|
||||
|
||||
기본값 30초가 이 도메인에서는 아슬아슬하다. **한 번 실패했다고 설정이 틀린 것이 아니다** — 다시 쳐 보고, 계속 실패하면 `--dns-cloudflare-propagation-seconds` 를 올린다. 이 판의 종료 코드는 `1` 이었다 — 앞에서 본 대로 종료 코드는 사유마다 다르다.
|
||||
|
||||
|
||||
**★ 더 나쁜 것은, 종료 코드가 실패를 일관되게 알려 주지 않는다는 점이다**(2026-09-17, observed). 같은 「전부 실패」 본문을 두 번 받았는데 한 번은 `0`, 한 번은 `1` 로 끝났다.
|
||||
|
||||
```bash label="[kc-lab-edge] 종료 코드를 따로 잡아서 본다"
|
||||
sudo certbot renew --dry-run >/tmp/dr.txt 2>&1; echo "certbot exit=$?"
|
||||
```
|
||||
|
||||
```text label="첫 번째 — 본문은 실패, 종료 코드는 0"
|
||||
Failed to renew certificate auth.hyeonworks.com with error: You should register before running non-interactively, …
|
||||
All simulated renewals failed. The following certificates could not be renewed:
|
||||
/etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem (failure)
|
||||
1 renew failure(s), 0 parse failure(s)
|
||||
certbot exit=0
|
||||
```
|
||||
|
||||
조금 뒤에 같은 명령을 다시 치니 실패 사유가 바뀜었고, 종료 코드도 같이 바뀜었다.
|
||||
|
||||
```text label="두 번째 — 본문은 같은 실패, 종료 코드는 1"
|
||||
Failed to renew certificate auth.hyeonworks.com with error: Missing command line flag or config entry for this setting:
|
||||
Please choose an account
|
||||
Choices: ['test-server@2026-09-03T01:50:44Z (66d5)', 'kc-lab-edge@2026-09-17T08:16:31Z (5df4)']
|
||||
|
||||
All simulated renewals failed. The following certificates could not be renewed:
|
||||
/etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem (failure)
|
||||
1 renew failure(s), 0 parse failure(s)
|
||||
certbot exit=1
|
||||
```
|
||||
|
||||
**두 번 다 `All simulated renewals failed` 이고 `1 renew failure(s)` 인데 종료 코드만 갈렸다.** `0` 을 성공으로 읽으면 첫 번째를 놓치고, 그렇다고 `1` 을 기다려도 두 번째에서만 맞는다. 이 명령에 대해 종료 코드는 판정 근거가 못 된다.
|
||||
|
||||
**그러므로 `&&` 로 뒤를 잇거나 `$?` 로 갈라선 안 된다.** 판정은 본문의 `renew failure(s)` 수와 `All simulated renewals failed` 줄로 한다. 이 편이 재려는 것이 「성공했다고 보고하는데 실제로는 안 된 일」인데, 사전 점검 명령 자체가 그 성질을 갖고 있다.
|
||||
|
||||
```bash label="[kc-lab-edge] 실패를 실패로 읽는 형태"
|
||||
sudo certbot renew --dry-run >/tmp/dr.txt 2>&1
|
||||
grep -E 'Failed to renew|renew failure' /tmp/dr.txt
|
||||
```
|
||||
|
||||
두 줄이다. 먼저 파일로 받아 두고, 그 파일에서 판정에 쓰는 줄만 골라 눈으로 읽는다. `renew failure(s)` 앞의 숫자가 `0` 이고 `Failed to renew` 줄이 없으면 통과고, 위 실측처럼 `1 renew failure(s)` 와 `Failed to renew` 가 같이 나오면 실패다. 갱신할 인증서가 한 장도 없으면 `grep` 은 아무것도 안 찍는다 — 그건 통과가 아니라 시뮬레이션할 것이 한 장도 없었다는 뜻이라 `/tmp/dr.txt` 를 그대로 열어 본다.
|
||||
|
||||
판정을 `&&` 나 `||` 로 이어 붙이지 않는 까닭은 바로 위 문단과 같다. 종료 코드가 거짓말하는 명령을 살피는 절에서 분기를 셸에 맡기면 같은 함정을 다시 판다.
|
||||
|
||||
**★ 그리고 인증서를 옮겨도 갱신 능력은 따라오지 않는다**(observed). 다른 기계에서 `live/` · `archive/` · `renewal/` 만 가져오면 위 오류가 난다. `renewal/*.conf` 가 가리키는 **ACME(Automatic Certificate Management Environment, 인증서 발급을 주고받는 프로토콜) 계정**(`/etc/letsencrypt/accounts/`)과 **DNS 자격증명 파일**이 없기 때문이다. 오류 문구가 「등록부터 하라」여서 계정을 새로 만들라는 말로 읽히는데, 실제로 빠진 것은 옮겨 오지 않은 디렉터리다.
|
||||
|
||||
```text label="/etc/letsencrypt/ 아래에서 함께 와야 하는 것"
|
||||
live/ 인증서 심볼릭 링크
|
||||
archive/ 실제 파일
|
||||
renewal/ 갱신 설정 — authenticator 와 자격증명 경로를 적는다
|
||||
accounts/ ★ ACME 계정 키. 없으면 "You should register…"
|
||||
cloudflare.ini ★ renewal 이 가리키는 자격증명. 없으면 DNS-01 이 못 돈다
|
||||
```
|
||||
|
||||
출력에 `Running deploy-hook command` 계열의 줄이 나오는가, 그리고 `simulated renewals` 요약을 본다. dry-run 은 인증서를 발급하지 않고 한도도 안 깎는다. 훅이 호출되는지까지만 말해 주고, 호출된 훅이 nginx 를 정말 갈아 끼웠는지는 dry-run 으로 알 수 없다. 그래서 관찰 절이 필요하다.
|
||||
|
||||
**★ 그리고 `--dry-run` 이 보는 계정은 `renewal/*.conf` 가 가리키는 계정이 아니다**(2026-09-17, observed). `--dry-run` 은 Let's Encrypt **staging 서버**로 붙는데, `renewal/*.conf` 의 `account =` 은 **운영 계정**의 id 를 적어 둔다. 그래서 staging 쪽 계정이 둘 이상이면 certbot 이 고르지 못하고 앞서 본 `Please choose an account` 로 멈는다.
|
||||
|
||||
```bash label="[kc-lab-edge] 두 서버의 계정을 각각 센다"
|
||||
sudo find /etc/letsencrypt/accounts -mindepth 3 -maxdepth 3 -type d
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-edge] renewal 이 어느 계정을 박아 뇌는가"
|
||||
sudo grep -E '^(account|server) ' /etc/letsencrypt/renewal/auth.hyeonworks.com.conf
|
||||
```
|
||||
|
||||
```text label="이 실험대의 값"
|
||||
/etc/letsencrypt/accounts/acme-staging-v02.api.letsencrypt.org/directory/66d5d86599378a0b07936733587017a6
|
||||
/etc/letsencrypt/accounts/acme-v02.api.letsencrypt.org/directory/8d53f9312e2a4c9cdd13620122cd8272
|
||||
account = 8d53f9312e2a4c9cdd13620122cd8272
|
||||
server = https://acme-v02.api.letsencrypt.org/directory
|
||||
```
|
||||
|
||||
`account =` 이 가리키는 `8d53…` 은 `acme-v02`(운영) 아래에만 있다. `--dry-run` 은 `acme-staging-v02` 아래를 보고, 거기 계정이 하나면 그것을 쓰고 둘이면 묻는다. 앞서 고르라고 나온 `66d5` 와 `5df4` 는 **둘 다 staging 계정**이었다 — `5df4` 는 실패한 dry-run 이 그때 새로 만든 것이라, **한 번 실패하고 나면 다음 dry-run 이 계속 실패한다.**
|
||||
|
||||
고르라고 나오는 이름은 계정 폴더의 `meta.json` 에서 온다.
|
||||
|
||||
```bash label="[kc-lab-edge] staging 계정을 누가 언제 만들었나"
|
||||
sudo sh -c "cat /etc/letsencrypt/accounts/acme-staging-v02.api.letsencrypt.org/directory/*/meta.json"
|
||||
```
|
||||
|
||||
```text label="이 실험대의 값"
|
||||
{"creation_dt": "2026-09-03T01:50:44Z", "creation_host": "test-server"}
|
||||
```
|
||||
|
||||
`Choices:` 에 찍힌 `test-server@2026-09-03T01:50:44Z (66d5)` 가 바로 이 두 칸과 폴더 이름 앞 네 글자다. 둘 이상 나오면 나중에 생긴 쪽의 폴더를 `sudo rm -rf` 로 지워 하나만 남긴다. 이 실험대에서는 `kc-lab-edge` 가 만든 쪽을 지워 `test-server` 가 만든 하나만 남겨 두었다(observed). **지우는 것은 staging 계정이라 운영 인증서에는 영향이 없다.**
|
||||
|
||||
위 명령에 `sudo sh -c` 가 붙은 까닭은 경로에 `*` 가 들어 있기 때문이다. `sudo cat …/*/meta.json` 은 셀이 먼저 `*` 를 푸는데, 그 셀은 root 가 아니라 `accounts/` 안을 못 읽고 `No such file or directory` 로 끝난다.
|
||||
|
||||
**그래서 `--dry-run` 의 실패와 진짜 갱신의 실패는 같은 것이 아니다.** 다만 이 실험대에서 진짜 갱신을 다시 치는 데까지 가지는 않았다(unknown). 확인한 것은 운영 계정이 하나뿐이고 그것이 `account =` 이 가리키는 바로 그 id 라는 것까지다.
|
||||
|
||||
**중복 계정을 지우고 다시 치니 dry-run 이 통과했다**(2026-09-17, observed).
|
||||
|
||||
```text label="staging 계정을 하나만 남긴 뒤"
|
||||
certbot exit=0
|
||||
Saving debug log to /var/log/letsencrypt/letsencrypt.log
|
||||
Processing /etc/letsencrypt/renewal/auth.hyeonworks.com.conf
|
||||
Simulating renewal of an existing certificate for auth.hyeonworks.com and 2 more domains
|
||||
Waiting 30 seconds for DNS changes to propagate
|
||||
Congratulations, all simulated renewals succeeded:
|
||||
/etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem (success)
|
||||
```
|
||||
|
||||
**★ 2026-09-17 에 엣지에서 다시 치고 갱신이 서빙까지 닿는 것을 봤다**(observed). 이번엔 `[test-server]` 가 아니라 `[kc-lab-edge]` 에서 쳤다 — 인증서와 nginx 가 거기 있기 때문이다.
|
||||
|
||||
```text label="[kc-lab-edge] sudo certbot renew --force-renewal"
|
||||
Renewing an existing certificate for auth.hyeonworks.com and 2 more domains
|
||||
Hook 'deploy-hook' ran with error output:
|
||||
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
|
||||
nginx: configuration file /etc/nginx/nginx.conf test is successful
|
||||
2026/09/17 10:36:31 [notice] 4744#4744: signal process started
|
||||
|
||||
Congratulations, all renewals succeeded:
|
||||
/etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem (success)
|
||||
certbot exit=0
|
||||
```
|
||||
|
||||
**판정은 이 출력이 아니라 서빙하는 인증서로 한다.** 같은 소켓을 갱신 전후로 두 번 열어 일련번호와 날짜를 견준다.
|
||||
|
||||
```bash label="[kc-lab-edge] 서빙하는 인증서가 바뀜는가"
|
||||
echo | openssl s_client -connect 127.0.0.1:443 -servername auth.hyeonworks.com 2>/dev/null | openssl x509 -noout -dates -serial
|
||||
```
|
||||
|
||||
```text label="갱신 전"
|
||||
notBefore=Sep 4 11:29:18 2026 GMT
|
||||
notAfter=Dec 3 11:29:17 2026 GMT
|
||||
serial=06F3E0EF4D1BB03DE58130EAAD1176101373
|
||||
```
|
||||
|
||||
```text label="갱신 후"
|
||||
notBefore=Sep 17 09:37:58 2026 GMT
|
||||
notAfter=Dec 16 09:37:57 2026 GMT
|
||||
serial=065547991777D11A408CEA90D945DDA03DF1
|
||||
```
|
||||
|
||||
일련번호가 바뀌었으므로 **훅이 돌았고 nginx 가 새 파일을 집었다.** 같은 순간 worker 프로세스도 바뀜다 — 갱신 전 `2629 4712`, 뒤 `4745 4754`. 디스크에는 `privkey4.pem` 이 `Sep 17 10:36` 으로 생겼다.
|
||||
|
||||
**원래 실행과 다른 데가 둘 있다**(observed). 첫째, 훅 출력에 `types_hash` 경고 줄이 없다 — 엣지의 nginx 설정이 호스트의 것과 달라서다. 둘째, 원래는 worker 하나가 그대로 남았는데 이번에는 둘 다 교체됐다. **`ran with error output` 이 실패가 아니라는 것은 그대로다** — stderr 로 나간 세 줄이 전부 성공 메시지다.
|
||||
|
||||
새 인증서의 `notBefore` 가 훅이 도는 시각(`10:36:31`)보다 약 한 시간 앞이다. 왜 그런지는 이 실험대에서 가르지 않았다(unknown) — 발급자가 앞당긴 것인지 시계 차인지는 안 재 봤다.
|
||||
|
||||
세 이름 모두 갱신 뒤에도 검증을 통과한다.
|
||||
|
||||
```text label="[lab host] 갱신 뒤 세 이름"
|
||||
auth 302 verify=0
|
||||
app1 200 verify=0
|
||||
app2 302 verify=0
|
||||
```
|
||||
|
||||
|
||||
**그리고 이것이 종료 코드 이야기를 닫는다.** 성공도 `0` 이고 앞의 첫 번째 실패도 `0` 이었다. 같은 명령이 돼을 때와 안 돼을 때 같은 값을 내므로, `$?` 로는 둔 경우를 가를 수 없다. 본문을 읽는 수밖에 없다.
|
||||
|
||||
|
||||
## 관찰
|
||||
|
||||
### 9. 강제 갱신을 친다
|
||||
@@ -650,8 +868,8 @@ echo | openssl s_client -connect auth.hyeonworks.com:443 -servername auth.hyeonw
|
||||
|
||||
2026-09-17 에 기반 가이드로 실험대를 새로 세우고 이 편을 어디까지 밟고 멈췄는지 적는다. **못 밟은 것을 밟은 것처럼 적지 않으려고 남긴다.**
|
||||
|
||||
- **남은 것** — 훅을 설치하고 강제 갱신으로 워커 PID 가 바뀌는지 보는 구간 전부. 인증서가 있어야 갱신할 것이 생긴다. 지금까지 밟은 것 — 훅 디렉터리가 빈 것, 두 기계 시계 차 94초.
|
||||
- **막는 것** — `https://auth.hyeonworks.com` 이 서지 않는다. 와일드카드 인증서(Cloudflare API 토큰이 필요한 DNS-01)와, 밖에서 실험대에 닿는 길(호스트의 libvirt `guest_input` 구멍 — A-4 에서 확인한 `ExecStartPost` 누락)이 둘 다 있어야 한다.
|
||||
- **남은 것** — 강제 갱신부터(9~13 절). 인증서가 있어야 갱신할 것이 생긴다. 지금까지 밟은 것 — 훅 디렉터리가 빈 것(3 절), 두 기계 시계 차 94초(4 절), **훅 파일 작성·설치·확인·손으로 실행(5~8 절)과 그 실행이 워커 PID 를 갈아 끼우는 것(10 절의 판정 기준)**. 기계는 전부 `kc-lab-edge` 로 바꿔서 쳤다.
|
||||
- **막는 것** — `https://auth.hyeonworks.com` 이 서지 않는다. **남은 것은 인증서 하나다**(2026-09-17 기준). 같이 적어 두었던 다른 둘은 그날 해결됐다 — 밖에서 닿는 길은 호스트의 libvirt `guest_input` 구멍과 유닛의 `ExecStartPost` 가 들어가면서 열렸고(`http` 가 밖에서 `200`), 클러스터 안에서 그 이름이 엣지를 안 가리키던 것은 기반 가이드 03 의 CoreDNS 한 단계로 놓았다. 인증서는 Cloudflare API 토큰이 필요한 DNS-01 로만 받을 수 있다 — 이름 셋이 tailnet 주소로 풀려 HTTP-01 은 성립하지 않는다.
|
||||
- **그때까지 이 편의 실측 가운데 `(observed)` 로 적힌 2026-09-17 값은 위 「지금까지 밟은 것」 범위뿐이다.** 나머지는 원래 실행의 값이다.
|
||||
|
||||
## 무엇이 관측이고 무엇이 아닌가
|
||||
|
||||
Reference in New Issue
Block a user