refactor: 문서 개선 중

This commit is contained in:
donghyeon-ka
2026-09-21 14:30:55 +09:00
parent c93cdea150
commit 805a18f486
1497 changed files with 525837 additions and 59152 deletions
+58 -93
View File
@@ -22,7 +22,7 @@
| 무엇 | 지금 |
|---|---|
| 증거 | **2026-09-17 기준** `evidence/raw` 43 · `evidence/meta` 1. 2026-09-16 까지는 두 폴더에 `README.txt` 한 장씩뿐이고 캡처한 원문 0건이었다. 2026-09-17 실험대를 철거하고 다시 세우며 `raw/lab-state-before-rebuild/`42건을 남겼다 (`rendered`·`browser` 는 여전히 0건이다) |
| 증거 | **2026-09-20 기준** `evidence/raw` 67개(원문 65개 · README 제외) · `evidence/meta` 1개(원문 0) · `evidence/rendered` 1개(원문 0) · `evidence/browser` 1개(원문 0). 2026-09-17 실험대를 철거하고 다시 세우며 `raw/lab-state-before-rebuild/`후속 관측 원문을 남겼다. 이 파일들은 과거 사건의 직접 재현인지, 같은 원인의 후속 관측인지, 단순 현재 상태인지를 claim별로 구분해 쓴다 |
제5~9부의 코드 블록에 있는 명령 출력은 전부 **사람이 문서에 옮겨 적은 것**이다. 실행한 명령·cwd·실행 시각·종료 코드를 짝지어 남긴 `evidence/meta/` 항목이 하나도 없으므로, 그 값들이 언제 어느 상태에서 나왔는지는 **문서가 스스로 밝힌 날짜**(제7부 2026-09-10, §218 2026-09-03)까지만 확인된다. 반입한 `source/` 에도 캡처 원문은 없었다 — 원본 문서들도 출력을 본문에 옮겨 적는 방식이었다.
@@ -9215,7 +9215,7 @@ ssh donghyeon@192.168.122.11 'hostname; cat /etc/os-release | head -1'
**목적** — control-plane 을 세우고 자기 자신을 노드로 등록시킨다.
```bash
ssh kc-lab-1 'curl -sfL https://get.k3s.io | sudo sh -s - server --node-ip 192.168.122.11'
ssh kc-lab-1 "curl -sfL https://get.k3s.io | sudo env INSTALL_K3S_VERSION=v1.36.4+k3s1 sh -s - server --node-ip 192.168.122.11"
```
게스트에 들어가지 않고 원격 실행한다. cloud-init 이 `NOPASSWD:ALL` 을 넣어
@@ -9394,7 +9394,7 @@ ssh kc-lab-1 'sudo ls -l /var/lib/rancher/k3s/server/node-token'
```bash
[ ${#TOKEN} -ge 50 ] || echo "TOKEN 이 비었다 — 3번으로 돌아간다"
ssh kc-lab-2 "curl -sfL https://get.k3s.io | sudo sh -s - agent \
ssh kc-lab-2 "curl -sfL https://get.k3s.io | sudo env INSTALL_K3S_VERSION=v1.36.4+k3s1 sh -s - agent \
--server https://192.168.122.11:6443 \
--token '$TOKEN' \
--node-ip 192.168.122.12"
@@ -9436,7 +9436,7 @@ ssh kc-lab-2
```bash
chmod 600 ~/node-token
curl -sfL https://get.k3s.io | sudo sh -s - agent \
curl -sfL https://get.k3s.io | sudo env INSTALL_K3S_VERSION=v1.36.4+k3s1 sh -s - agent \
--server https://192.168.122.11:6443 \
--token-file ~/node-token \
--node-ip 192.168.122.12
@@ -11071,31 +11071,27 @@ sudo certbot certificates
|---|---|
| `Domains:` | `hyeonworks.com *.hyeonworks.com`**한 줄에** 둘 다 |
| `Expiry Date:` | 오늘 + 90일, `VALID` |
| `Certificate Path:` | `/etc/letsencrypt/live/hyeonworks.com/fullchain.pem` |
| `Private Key Path:` | `/etc/letsencrypt/live/hyeonworks.com/privkey.pem` |
| `Certificate Path:` | `/etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem` — 2026-09-17 현재 실험대 관측 |
| `Private Key Path:` | `/etc/letsencrypt/live/auth.hyeonworks.com/privkey.pem` |
**★ 디렉터리 이름과 인증서가 덮는 이름은 별개다**(observed). 여기서 가장 많이
헷갈린다.
| | 무엇 | 정해지는 방식 |
|---|---|---|
| 디렉터리 이름 `live/hyeonworks.com/` | certbot 이 이 묶음(lineage)을 관리하려고 붙인 **라벨** | **첫 번째 `-d`** 에서 따온다. 서빙과 무관 |
| SAN 목록 | 브라우저가 보는 **실제 유효 호스트명** | `-d` 로 준 이름 전부 |
| 디렉터리 이름 `live/auth.hyeonworks.com/` | **2026-09-17 현재 실험대에서 실제로 존재한 lineage** | `sudo ls /etc/letsencrypt/live/``certbot certificates` 로 확인한다 |
| SAN 목록 | 브라우저가 보는 **실제 유효 호스트명** | `-d` 로 준 이름 전부. lineage 디렉터리 이름과 별개 |
**확인** — 인증서가 실제로 어떤 이름에 유효한가
```bash
sudo openssl x509 -noout -ext subjectAltName -in /etc/letsencrypt/live/hyeonworks.com/fullchain.pem
sudo openssl x509 -noout -ext subjectAltName -in /etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem
```
**어디를 봐야 하는가** — `DNS:hyeonworks.com, DNS:*.hyeonworks.com` 두 개.
`auth.hyeonworks.com` 은 두 번째에 걸린다.
**이 결과가 의미하는 것** — 여기 나온 경로가 곧 nginx 가 읽을 파일이다. 그래서
`auth.hyeonworks.com` 으로 **다시 받을 필요가 없고**, `*.hyeonworks.com` 한 장이
`auth`·`app1`·`app2` 를 전부 덮는다. 다만 3번의 nginx 설정에는 **디렉터리 경로**를
한 글자도 다르지 않게 적어야 한다 — `live/auth.hyeonworks.com/` 이라고 적으면
`cannot load certificate` 로 막힌다.
**이 결과가 의미하는 것** — `certbot certificates` 가 찍은 경로가 곧 nginx 가 읽을 파일이다. 2026-09-17 현재 실험대에서는 그 경로가 `live/auth.hyeonworks.com/` 이었다(observed). SAN 목록은 `hyeonworks.com``*.hyeonworks.com` 이므로 lineage 이름과 인증서가 유효한 호스트명은 같은 개념이 아니다. nginx 설정에는 관측된 디렉터리 경로를 한 글자도 다르지 않게 적는다.
**★ 와일드카드는 한 단계만 덮는다.** `a.b.hyeonworks.com` 은 포함되지 않고, apex 인
`hyeonworks.com` 자신도 `*.hyeonworks.com` 에 들어가지 않는다 — 그래서 `-d` 를 둘
@@ -11135,8 +11131,8 @@ server {
listen 443 ssl http2 default_server;
server_name _;
ssl_certificate /etc/letsencrypt/live/hyeonworks.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/hyeonworks.com/privkey.pem;
ssl_certificate /etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/auth.hyeonworks.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
@@ -11162,10 +11158,7 @@ server {
| 443 블록 | 없었다 | 인증서와 함께 새로 |
| `X-Forwarded-Proto` | `http` | **`https`** |
**★ 위 블록의 인증서 경로가 틀렸다**(2026-09-17, observed). `live/hyeonworks.com/` 이라고
적혀 있는데 certbot 이 만드는 디렉터리는 **`live/auth.hyeonworks.com/`** 이다. 이름을
여럿 담은 인증서라도 디렉터리 이름은 `-d` 로 처음 준 이름 하나를 쓴다. 그대로 치면
`nginx -t` 가 막힌다.
**★ 원본 가이드 04 의 5단계에는 known-wrong 경로가 남아 있었다**(2026-09-17, observed). 가이드는 `live/hyeonworks.com/` 을 적었지만 현재 실험대의 실제 lineage 는 **`live/auth.hyeonworks.com/`** 이다. 위 canonical 설정은 관측된 경로로 바로잡았고, 아래 실패 출력은 원본 가이드 경로를 그대로 쳤을 때의 historical evidence 다.
```text
[emerg] cannot load certificate "/etc/letsencrypt/live/hyeonworks.com/fullchain.pem":
@@ -11184,9 +11177,7 @@ auth.hyeonworks.com
Certificate Path: /etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem
```
경로를 그 이름으로 고치자 `syntax is ok` · `test is successful` 로 통과하고 엣지가 443 을
듣기 시작했다. **§204 의 「문제가 생기면」에 적힌 진단은 방향이 거꾸로다** —
`live/auth.hyeonworks.com/` 이 맞는 경로이고 없는 것은 `live/hyeonworks.com/` 쪽이다.
`live/auth.hyeonworks.com/` 으로 고치자 `syntax is ok` · `test is successful` 로 통과하고 엣지가 443 을 듣기 시작했다. 현재 truth는 하나다 — **실제 lineage는 `auth.hyeonworks.com`, 원본 가이드의 `hyeonworks.com` 경로가 known-wrong**이다.
**그리고 이 실험대의 certbot 은 DNS-01 로 등록되어 있다**(observed). 두 문서가 갈렸던
대목이 닫혔다.
@@ -13200,61 +13191,25 @@ Arch 는 `sites-available` 관례 자체가 없어서 이 함정이 없는 대
## 204. 재구축할 때 무엇이 남아 있나
철거해도 남는 것과 사라지는 것을 구분해 둔다. 이걸 모르면 재구축이 「왜
이건 이미 있지」와 「왜 이건 없지」의 반복이 된다.
철거 남는 것은 **host의 상태**이고, `kc-lab-edge`를 포함한 guest 안의 상태는 guest 디스크와 함께 사라진다. 2026-09-17에 nginx와 certbot을 edge guest로 옮긴 뒤에는 이 구분이 canonical current state다.
| | 상태 | 왜 |
| 무엇 | 철거 뒤 상태 | 왜 |
|---|---|---|
| `base.qcow2` (335MB) | **남는다** | 다음 오버레이의 바닥 |
| 패키지 (libvirt·qemu·nginx·certbot·kubectl) | **남는다** | 재설치가 무의미 |
| host 패키지 (`libvirt`·`qemu`·`kubectl`, 쓰이지 않는 host `certbot`) | **남는다** | guest 삭제와 무관한 host 설치 상태 |
| edge guest의 `nginx`·`certbot` | 사라진다 | `kc-lab-edge` 디스크와 함께 삭제된다 |
| `~/workspace/cloud/kc-lab-{1,2}.yaml` | **남는다** | 키와 비밀번호가 들어 있다 |
| `~/.ssh/config``kc-lab-*` 항목 | **남는다** | 재구축해도 IP 가 같다 |
| libvirt `default` 네트워크 정의 | **남는다** | 예약만 지다 |
| `/etc/letsencrypt/` | **남긴다** (정책) | 아래 참고 |
| `~/.ssh/config``kc-lab-*` 항목 | **남는다** | 재구축해도 IP가 같다 |
| libvirt `default` 네트워크 정의 | **남는다** | DHCP 예약만 지다 |
| edge guest의 `/etc/letsencrypt/` | 사라진다 | guest 삭제 전에 host로 백업한다 |
| host에 복사한 `letsencrypt-backup-<stamp>.tgz` | **남긴다** (정책) | DNS-01 등록은 확인됐지만 credential 유효성과 `certbot renew --dry-run` 성공은 아직 미확인 |
| 게스트 디스크·시드 ISO | 사라진다 | `--remove-all-storage` |
| DHCP 예약 | 사라진다 | `net-update delete` |
| k3s·Keycloak·모든 워크로드 | 사라진다 | 게스트와 함께 |
| k3s·Keycloak·모든 워크로드 | 사라진다 | guest와 함께 |
### 인증서를 지우지 않는 이유
### 현재 서빙 인증서는 edge guest 안에 있다
**한도 때문이 아니다.** Let's Encrypt 의 「같은 이름 조합에 주당 중복 5장」
제한은 가끔 재구축하는 정도로는 근처에도 못 간다.
**진짜 이유는 「지금 재발급이 되는지를 모른다」는 것이다.** 이 실험대의 이름
셋은 tailnet 주소를 가리킨다.
```bash
dig +short auth.hyeonworks.com
```
```
100.83.212.4
```
`100.64.0.0/10` 은 CGNAT 용 예약 대역이라 **공개 인터넷에서 라우팅되지
않는다.** HTTP-01 검증은 Let's Encrypt 가 우리 서버로 들어오는 방식이므로,
이 주소로는 검증이 성립하지 않는다.
| 지금 설정이 | 지우고 나면 |
|---|---|
| `dns-cloudflare` (DNS-01) | 다시 받으면 끝. **백업은 헛수고였던 것** |
| `webroot`·`standalone` (HTTP-01) | 검증 방식부터 손봐야 한다 |
**「못 한다」가 아니라 「일이 하나 생긴다」이다.** 최악의 경우라도 A 레코드를
공인 IP 로 잠깐 돌려 받거나 DNS-01 로 전환하면 된다. 다만 재구축하려고 앉은
자리에서 그 일부터 하게 된다.
**어느 쪽인지 모르는 채로는 지우지 않는다**는 것이 이 정책의 전부다. 백업은
tar 하나에 30초고, 답을 알고 나면 지우면 된다.
```bash
sudo tar czf ~/letsencrypt-backup-$(date +%Y%m%d-%H%M%S).tgz -C /etc letsencrypt
```
**그런데 이 정책이 지키는 디렉터리가 03·04 뒤로 바뀌었다**(2026-09-17, observed).
위 명령은 랩 호스트의 `/etc/letsencrypt/` 를 묶는데, 03 이 nginx 를
`kc-lab-edge` 로 옮겼고 04 가 certbot 을 거기 깔았다. **지금 서빙하는 인증서는
그 게스트 안에 산다.** 그리고 철거 1번은 게스트를 `--remove-all-storage`
지운다 — 인증서는 그 안에서 같이 사라지고, 위 백업에는 안 들어간다.
2026-09-17 두 기계를 나란히 확인했다(observed). host에도 `/etc/letsencrypt/``certbot`이 남아 있지만 80·443을 서빙하지 않고 timer도 없다. 현재 서빙하는 인증서와 `certbot.timer``kc-lab-edge` 안에 있다.
```bash
ls -la /etc/letsencrypt/
@@ -13263,43 +13218,53 @@ ssh kc-lab-edge 'sudo ls -la /etc/letsencrypt/'
| 기계 | `/etc/letsencrypt/` | certbot | 타이머 | 서빙 |
|---|---|---|---|---|
| 랩 호스트 | 있다 (읽으려면 `sudo`) | `/usr/bin/certbot` | 없다 | 안 한다 — 80·443 을 안 듣는다 |
| `kc-lab-edge` | 있다 — `cli.ini` · `renewal-hooks` | `/usr/bin/certbot` | `certbot.timer` | **한다** |
| 랩 호스트 | 있다 (읽으려면 `sudo`) | `/usr/bin/certbot` | 없다 | 안 한다 — 80·443을 안 듣는다 |
| `kc-lab-edge` | 있다 — `cli.ini`·`renewal-hooks` | `/usr/bin/certbot` | `certbot.timer` | **한다** |
묶어야 할 것은 게스트 쪽이다.
따라서 철거 전에 보존할 것은 **edge guest의 `/etc/letsencrypt/`** 이다. 예전 host-local `/etc/letsencrypt/` 백업 명령은 03·04 이전 배치를 전제로 한 historical wrong assumption이라 canonical 절차에서 실행 명령으로 쓰지 않는다.
```bash
ssh kc-lab-edge "sudo tar czf /tmp/letsencrypt-backup-$(date +%Y%m%d-%H%M%S).tgz -C /etc letsencrypt"
scp kc-lab-edge:/tmp/letsencrypt-backup-*.tgz ~/
### DNS-01은 확인됐고, credential 유효성은 아직 확인되지 않았다
2026-09-17 renewal 설정을 읽어 현재 등록 방식이 DNS-01임을 확인했다(observed).
```bash label="[reference] renewal 설정 관측"
sudo sh -c 'grep -H -E "^(authenticator|dns_cloudflare_credentials|server)[[:space:]]*=" /etc/letsencrypt/renewal/*.conf'
```
**「철거해도 남는 것」 표의 패키지 줄도 같은 이유로 틀렸다.** 그 줄은 `nginx`
`certbot` 을 남는 쪽에 적어 두었는데 둘 다 게스트 안에 있으므로 게스트와 함께
사라진다. 랩 호스트에 남는 것은 `libvirt`·`qemu`·`kubectl` 과, 쓰이지 않는 호스트
`certbot` 이다.
관측값은 `authenticator = dns-cloudflare``/etc/letsencrypt/cloudflare.ini`였다. 이로써 HTTP-01인지 DNS-01인지라는 질문은 닫혔다.
**철거 전 네 값은 2026-09-17 에 다시 받았다**(observed). 도메인 3 대에 볼륨 7 개,
예약 3 줄까지는 2026-09-10 과 같고 **디스크만 11G 가 아니라 18G 다** — 그사이에
k3s 와 컨테이너 이미지가 쌓였다. 이 네 값은 이 호스트의 정답이 아니라 매 실행에서
받아 두는 대조군이다.
다만 **DNS-01 등록 사실과 지금 재발급 가능한지는 별개다.** 같은 후속 원문에서 credential 파일의 토큰 길이는 0자로 측정됐고 Cloudflare 검증은 `Invalid request headers`로 실패했다. 남은 미지수는 유효한 Cloudflare credential을 다시 준비했는지와 `certbot renew --dry-run`이 성공하는지다.
복원은 반대로 한 줄이다.
그래서 현재 정책은 **guest 삭제 전 인증서를 백업한다**이다. 재발급 가능성이 검증되면 이 보존 정책을 완화할 수 있다.
### 백업은 edge guest에서 host로 빼낸다
host에서 시각값을 한 번 만들고, 그 값을 원격 tar와 scp가 같이 쓴다.
```bash
sudo tar xzf ~/letsencrypt-backup-<stamp>.tgz -C /etc
STAMP=$(date +%Y%m%d-%H%M%S)
ssh kc-lab-edge "sudo tar czf /tmp/letsencrypt-backup-$STAMP.tgz -C /etc letsencrypt"
scp "kc-lab-edge:/tmp/letsencrypt-backup-$STAMP.tgz" "$HOME/"
```
**미측정** — 이 실험대의 certbot 이 HTTP-01 인지 DNS-01 인지 아직 확인하지
않았다. 호스트의 `sudo` 가 비밀번호를 요구해 비대화식으로 읽지 못했다.
다음 한 줄이 답이다.
이 작업이 끝나면 host의 `$HOME/letsencrypt-backup-$STAMP.tgz`가 guest 밖에 남는다. 그 다음에야 `kc-lab-edge``--remove-all-storage`로 지운다.
복원은 새 edge guest가 다시 만들어진 뒤 그 guest의 `/etc`로 넣는다.
```bash
sudo grep -H authenticator /etc/letsencrypt/renewal/*.conf
BACKUP="$HOME/letsencrypt-backup-<stamp>.tgz"
scp "$BACKUP" kc-lab-edge:/tmp/letsencrypt-backup.tgz
ssh kc-lab-edge 'sudo tar xzf /tmp/letsencrypt-backup.tgz -C /etc'
ssh kc-lab-edge 'sudo certbot certificates && sudo nginx -t'
```
`webroot`·`standalone` 이면 HTTP-01 이라 위 문제가 실재하고, `dns-cloudflare`
면 DNS-01 이라 tailnet 안에서도 재발급·갱신이 된다. 플러그인은 이미 깔려
있다 — `certbot plugins``dns-cloudflare` 가 보인다.
`certbot certificates``nginx -t`가 둘 다 성공해야 복원이 끝난 것이다. 인증서 파일이 존재하는 것과 nginx가 실제 경로를 읽는 것은 별도 조건이다.
### 철거 전 값은 실행마다 다시 받는다
2026-09-17 철거 전에는 도메인 3대, 볼륨 7개, DHCP 예약 3줄을 다시 확인했다(observed). 2026-09-10과 구조는 같았지만 host 디스크 사용량은 11G가 아니라 18G였다. 그 사이 k3s와 컨테이너 이미지가 쌓였기 때문이다.
따라서 이 값들은 이 host의 영구 정답이 아니다. 철거할 때마다 `virsh list --all`, `virsh vol-list default`, DHCP 예약, `df -h /`를 다시 받아 전후 비교의 대조군으로 쓴다.
---