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
@@ -8325,6 +8325,21 @@ source ~/.bashrc
|
||||
virsh uri
|
||||
```
|
||||
|
||||
libvirt 쪽 설정도 같다. 디렉터리는 명령으로 만들고 파일은 편집기로 연다.
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/libvirt
|
||||
nano ~/.config/libvirt/libvirt.conf
|
||||
```
|
||||
|
||||
그 파일에 이 줄을 더한다.
|
||||
|
||||
```text
|
||||
uri_default = "qemu:///system"
|
||||
```
|
||||
|
||||
이 나눈 형태는 이 실험대에서 치지 않았다(unknown).
|
||||
|
||||
**확인** — 지금 셸이 어느 하이퍼바이저를 보고 있는가
|
||||
|
||||
```bash
|
||||
@@ -8414,16 +8429,68 @@ virsh list --all
|
||||
|
||||
- (observed) libvirt `12.7.0` · `QEMU emulator version 11.1.1` · 그룹 세 개 ·
|
||||
`default` 네트워크 `active`/autostart `yes` · `virsh uri` 가 `qemu:///system`
|
||||
- (unknown) `lsmod | grep kvm` 의 출력은 캡처해 두지 않았다. 가이드도 줄 모양만
|
||||
적고 값을 싣지 않았다
|
||||
- (unknown) 가이드의 실측 줄은 「이 실험대의 호스트는 16 코어 전부에서
|
||||
지원한다」인데 §178 의 대상 환경은 **논리 코어 8**(i5-1135G7)이다. 두 값이
|
||||
어긋나고, 어느 쪽이 이 호스트의 값인지는 재지 않았다
|
||||
- (observed) `lsmod | grep kvm` 의 출력을 2026-09-17 에 받았다. 세 줄이고 마지막
|
||||
칸이 그 모듈을 쓰는 수다
|
||||
|
||||
```text
|
||||
kvm_intel 524288 11
|
||||
kvm 1490944 6 kvm_intel
|
||||
irqbypass 16384 1 kvm
|
||||
```
|
||||
|
||||
- (observed) **이 호스트는 논리 코어 8 이다.** 가이드의 「16 코어 전부」가 아니라
|
||||
§178 의 값이 맞다. 2026-09-17 에 `nproc` 이 `8`, `lscpu` 가
|
||||
`11th Gen Intel(R) Core(TM) i5-1135G7`, 물리 4 코어에 코어당 스레드 2 라고 답했다
|
||||
|
||||
```bash
|
||||
nproc
|
||||
lscpu | grep -E "^Model name|^CPU\(s\):|^Core\(s\)|^Thread\(s\)"
|
||||
```
|
||||
|
||||
- (observed) `systemctl status libvirtd.socket` 도 같은 날 쳤다. 보는 줄은 `Active`
|
||||
와 `Listen` 과 `Triggers` 셋이고, `Triggers` 가 `libvirtd.service` 를 가리키는
|
||||
것이 소켓 활성화가 걸렸다는 뜻이다
|
||||
|
||||
```text
|
||||
● libvirtd.socket - libvirt legacy monolithic daemon socket
|
||||
Loaded: loaded (/usr/lib/systemd/system/libvirtd.socket; enabled; preset: disabled)
|
||||
Active: active (running) since Thu 2026-09-03 19:00:35 KST; 1 week 6 days ago
|
||||
Triggers: ● libvirtd.service
|
||||
Listen: /run/libvirt/libvirt-sock (Stream)
|
||||
```
|
||||
|
||||
- (observed) 볼륨 일곱이 실제로 먹는 양도 그날 쟀다. **선언한 크기와 실제 할당이
|
||||
크게 다르다** — 오버레이라 쓴 만큼만 먹는다. 철거 절의 「엣지 디스크는 재 두지
|
||||
않았다」와 거기서 역산한 「1GB 안팎」이 이 값으로 다시 보인다
|
||||
|
||||
```bash
|
||||
virsh vol-info --pool default kc-lab-edge.qcow2
|
||||
```
|
||||
|
||||
```text
|
||||
base.qcow2 Capacity=3.00 GiB Allocation=323.25 MiB
|
||||
kc-lab-1.qcow2 Capacity=20.00 GiB Allocation=5.40 GiB
|
||||
kc-lab-2.qcow2 Capacity=20.00 GiB Allocation=3.85 GiB
|
||||
kc-lab-edge.qcow2 Capacity=10.00 GiB Allocation=316.26 MiB
|
||||
seed-kc-lab-{1,2,edge}.iso Capacity=370.00 KiB Allocation=372.00 KiB
|
||||
```
|
||||
|
||||
- (observed) `cloud-init status --long` 의 마지막 줄이 시드를 `bus=virtio` 로 붙이는
|
||||
이유를 그대로 뒷받침한다. `seed=/dev/vdb` 이므로 CD-ROM(`sr0`)이 아니라 디스크로
|
||||
잡혔고, cloud-init 이 거기서 데이터소스를 찾았다
|
||||
|
||||
```text
|
||||
status: done
|
||||
boot_status_code: enabled-by-generator
|
||||
last_update: Thu, 17 Sep 2026 04:24:22 +0000
|
||||
detail:
|
||||
DataSourceNoCloud [seed=/dev/vdb][dsmode=net]
|
||||
```
|
||||
|
||||
- (unknown) **원본 가이드에 되돌리는 절차가 없다.** 패키지·그룹·유닛·`.bashrc`·
|
||||
네트워크 autostart 를 걷어내 본 적이 없다
|
||||
- (unknown) `sudo modprobe kvm_intel` 과 `systemctl status libvirtd.socket` 은
|
||||
막혔을 때 치라고 가이드가 적어 둔 것이고, 이 실험대에서는 막히지 않아 치지
|
||||
않았다
|
||||
- (unknown) `sudo modprobe kvm_intel` 은 막혔을 때 치라고 가이드가 적어 둔
|
||||
것이고, 이 실험대에서는 막히지 않아 치지 않았다
|
||||
- (external) Debian/Ubuntu 패키지 이름은 가이드가 참고로 적어 둔 것이고 이
|
||||
실험대는 Arch 로 세웠다
|
||||
|
||||
@@ -8548,6 +8615,24 @@ cat ~/.ssh/id_ed25519.pub
|
||||
openssl rand -base64 18
|
||||
```
|
||||
|
||||
**따라 하는 사람은 ① 을 둘로 나눈다.** `[ -f … ] || …` 는 「키가 있으면 두고
|
||||
없으면 만든다」를 한 줄에 접어 넣은 형태라, 배울 것보다 단축 평가를 먼저 읽게
|
||||
된다. 먼저 꺼내 보고, 없을 때만 만든다.
|
||||
|
||||
```bash
|
||||
cat ~/.ssh/id_ed25519.pub
|
||||
```
|
||||
|
||||
`ssh-ed25519 …` 한 줄이 나오면 키가 이미 있는 것이고 아래를 건너뛴다.
|
||||
`No such file or directory` 면 키가 없다.
|
||||
|
||||
```bash
|
||||
ssh-keygen -t ed25519 -N '' -f ~/.ssh/id_ed25519
|
||||
```
|
||||
|
||||
만든 뒤 다시 `cat` 해서 공개키 줄을 꺼낸다. 이 나눈 형태는 이 실험대에서 치지
|
||||
않았다(unknown).
|
||||
|
||||
**이 실험대는 이렇게 했다**(원본 01 본문) — 자리표시자 셋을 `sed` 로 한 번에
|
||||
바꿨다.
|
||||
|
||||
@@ -9023,9 +9108,20 @@ sudo journalctl -u cloud-init -n 50
|
||||
제7부 §198 이 따로 잰다
|
||||
- (unknown) `kc-lab-1.yaml` 을 템플릿에서 어떻게 만드는지가 원본에도 없다.
|
||||
`kc-lab.yaml.example` 이 `source/` 에 없어 대조하지 못했다
|
||||
- (unknown) cloud-init 의 `packages` 에 certbot 이 들어 있었는지가 가이드 안에서
|
||||
갈린다. 01 의 예시와 03 의 본문은 `curl`·`nftables` 뿐이라 적고, 04 는
|
||||
`kc-lab.yaml.example` 의 `packages` 에 certbot 이 있다고 적는다
|
||||
- (observed) **cloud-init 의 `packages` 에 certbot 은 없다** — 2026-09-17 에
|
||||
갈렸다. 뜬 게스트가 실제로 받은 user-data 가 `packages: [curl, nftables]` 한
|
||||
줄이고, `kc-lab.yaml.example` 에는 `nginx`·`certbot`·
|
||||
`python3-certbot-dns-cloudflare` 셋이 **주석으로 막혀** 있다. 04 의 표에
|
||||
`kc-lab-edge` 가 `nginx · certbot` 으로 적힌 것은 그 게스트가 끝나면 맡을
|
||||
역할이지 cloud-init 이 깔아 준 것이 아니다
|
||||
|
||||
```bash
|
||||
sudo grep -n "packages" /var/lib/cloud/instance/user-data.txt
|
||||
```
|
||||
|
||||
```text
|
||||
19:packages: [curl, nftables]
|
||||
```
|
||||
- (unknown) `scp` 다섯 줄 형태, `virsh vol-list`·`vol-info`·`net-dumpxml
|
||||
--inactive`·`cloud-init status --long`·`virsh console` 은 가이드가 적어 둔
|
||||
명령이고 이 실험대가 캡처한 출력이 없다
|
||||
@@ -9230,7 +9326,31 @@ ssh kc-lab-1 'sudo openssl x509 -in /var/lib/rancher/k3s/server/tls/serving-kube
|
||||
```
|
||||
|
||||
`IP Address:127.0.0.1` 과 `IP Address:192.168.122.11` 이 둘 다 보인다. 8번의 SSH
|
||||
터널이 `127.0.0.1` 로 붙을 수 있는 것도 같은 까닭이다.
|
||||
터널이 `127.0.0.1` 로 붙을 수 있는 것도 같은 까닭이다. **2026-09-17 에 그 출력을 실제로
|
||||
받았다**(observed).
|
||||
|
||||
```text
|
||||
X509v3 Subject Alternative Name:
|
||||
DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc,
|
||||
DNS:kubernetes.default.svc.cluster.local, DNS:localhost, DNS:kc-lab-1,
|
||||
IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1,
|
||||
IP Address:192.168.122.11, IP Address:10.43.0.1, IP Address:192.168.122.11
|
||||
```
|
||||
|
||||
`192.168.122.11` 이 두 번 들어 있는데 중복이고 동작에는 영향이 없다. `10.43.0.1` 은
|
||||
클러스터 안에서 API 서버를 부르는 Service 주소다.
|
||||
|
||||
**그리고 `journalctl -u k3s-agent` 는 정상일 때도 `E` 로 시작하는 줄이 가득하다**(observed).
|
||||
이것을 모르면 「막혔을 때 여기를 보라」는 안내대로 열었다가 멀쩡한 노드를 고장으로 읽는다.
|
||||
같은 순간 `kubectl get nodes` 는 둘 다 `Ready` 였다.
|
||||
|
||||
```text
|
||||
E0917 07:42:16.919786 conn.go:353] "Error on socket receive" err="read tcp 127.0.0.1:10250->127.0.0.1:55428: use of closed network connection"
|
||||
```
|
||||
|
||||
`kubectl exec` 나 `logs` 로 연결했다가 끊을 때마다 한 줄씩 남는다. **봐야 할 것은 `E` 인지가
|
||||
아니라 문구다** — 토큰이나 주소가 틀렸으면 `failed to get CA certs` 나 `401 Unauthorized` 가
|
||||
나온다.
|
||||
|
||||
**3. 토큰을 꺼낸다**
|
||||
|
||||
@@ -9360,6 +9480,32 @@ rm ~/node-token
|
||||
이 여덟 줄 형태는 2026-09-17 에 쳤다 — 설치 자체는 이 판으로 다시 돌리지 않았고,
|
||||
유닛을 고쳐 다시 시작하는 쪽만 확인했다(observed).
|
||||
|
||||
**따라 하는 사람은 유닛을 편집기로 연다.** `sed -i` 는 패턴이 안 맞아도 조용히 성공하고,
|
||||
여기 패턴에는 `$HOME` 이 들어 있어서 설치할 때와 다른 계정으로 치면 아무 줄도 안 바뀐
|
||||
채 `daemon-reload` 와 `restart` 가 이어서 돈다. 그 뒤 노드는 지금 떠 있으니 정상으로
|
||||
보이고, 다음 재부팅에서야 `Waiting for file` 로 멎는다.
|
||||
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/k3s-agent.service
|
||||
```
|
||||
|
||||
`ExecStart=` 아래에서 `'--token-file'` 다음 줄의 경로를 `/etc/rancher/node-token` 으로
|
||||
고친다. 고친 뒤 그 두 줄이 이렇게 보인다.
|
||||
|
||||
```
|
||||
'--token-file' \
|
||||
'/etc/rancher/node-token' \
|
||||
```
|
||||
|
||||
저장하고 나와서 유닛을 다시 읽힌다.
|
||||
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl restart k3s-agent
|
||||
```
|
||||
|
||||
이 편집기 형태는 이 실험대에서 치지 않았다(unknown).
|
||||
|
||||
**확인** — agent 가 클러스터에 들어왔는가, 그리고 **제 주소로** 들어왔는가
|
||||
|
||||
```bash
|
||||
@@ -10186,11 +10332,34 @@ curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://auth.hyeonworks.
|
||||
301 https://auth.hyeonworks.com/
|
||||
```
|
||||
|
||||
**★ 이 `301` 도 04 이후의 값이다.** 03 까지만 한 상태라면 ② 와 같은 `404` 가
|
||||
정상이고, 이 층이 묻는 것은 코드값이 아니라 **「밖에서 친 것이 엣지까지
|
||||
닿았는가」**다 — ② 와 같은 응답이 밖에서도 오면 DNAT 과 구멍이 살아 있다는 뜻이다.
|
||||
**★ 이 `301` 은 04 이후의 값이다.** 03 까지만 했을 때 나오는 것은 `302` 이고 보내는
|
||||
곳도 다르다. 2026-09-17 에 구멍을 뚫고 밖에서 재 보니 이랬다(observed).
|
||||
|
||||
2026-09-17 에 밖에서 재 보니 **닿지 않았다**(observed).
|
||||
```
|
||||
302 https://auth.hyeonworks.com/admin/
|
||||
```
|
||||
|
||||
**리다이렉트를 내는 것이 둘이다.** 04 를 끝내면 엣지 nginx 의
|
||||
`return 301 https://$host$request_uri` 가 먼저 답해 `301 https://auth.hyeonworks.com/`
|
||||
이 되고, 03 까지만 했으면 그 블록이 없어 요청이 Keycloak 까지 가서 **Keycloak 이** `/`
|
||||
를 `/admin/` 으로 보낸다. 코드도 `301` 이 아니라 `302` 다. `Location` 이 `https://` 인
|
||||
것도 Keycloak 이 정한다 — `KC_HOSTNAME=https://auth.hyeonworks.com` 과
|
||||
`KC_HOSTNAME_STRICT=true` 가 그렇게 시키고, **요청이 `http` 로 들어와도 자기가 아는
|
||||
주소로 답한다.**
|
||||
|
||||
**같은 엣지에 어떻게 치느냐가 코드를 가른다**(observed).
|
||||
|
||||
| 어떻게 치나 | 코드 |
|
||||
|---|---|
|
||||
| Host 없이 엣지 IP 로 (층 ②) | `404` — Traefik 에 매칭되는 Ingress 가 없다 |
|
||||
| `Host: auth.hyeonworks.com` 으로 엣지 IP 에 | `302 https://auth.hyeonworks.com/admin/` |
|
||||
| 밖에서 도메인으로 (층 ③) | `302 https://auth.hyeonworks.com/admin/` |
|
||||
| 밖에서 도메인 + `/realms/master` | **`200`** |
|
||||
|
||||
마지막 줄이 중요하다 — **TLS 를 얹기 전에도 `/realms/master` 는 `200` 이다.** 층 ④ 의
|
||||
`200` 은 「TLS 가 섰다」가 아니라 「끝까지 이어졌다」를 재는 값이다.
|
||||
|
||||
구멍을 뚫기 전에는 같은 `curl` 이 이렇게 끝났다(observed).
|
||||
|
||||
```
|
||||
curl: (7) Failed to connect to auth.hyeonworks.com port 80 after 54 ms
|
||||
@@ -10204,7 +10373,8 @@ curl: (7) Failed to connect to auth.hyeonworks.com port 80 after 54 ms
|
||||
**확인 ④ 끝까지 닿나** (TLS 이후)
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
```
|
||||
@@ -10216,7 +10386,7 @@ curl -s -o /dev/null -w '%{http_code}\n' https://auth.hyeonworks.com/realms/mast
|
||||
번은 헤더까지 보고, 그다음부터 이 형태로 줄인다.
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
**이 결과가 의미하는 것** — `200` 이면 nginx → Traefik → 파드까지 2홉이 다
|
||||
@@ -10309,6 +10479,10 @@ error 로그에 있었는데 잘려 있었고, access 로그에는 **3492자**
|
||||
구멍이 필요한지는 재지 않았다(§183)
|
||||
- **되돌리기는 가이드 7편 가운데 이 편에만 있다.** 위 네 줄이 원문 그대로이고,
|
||||
그 네 줄을 실제로 쳐서 걷어내 본 기록은 없다(unknown)
|
||||
- (unknown) 2026-09-17 에 더한 CoreDNS 매니페스트(`coredns-custom`)는 가이드의
|
||||
되돌리기 표에 없다. 걷어내려면 `kubectl -n kube-system delete configmap coredns-custom`
|
||||
하고 `kubectl -n kube-system delete pod -l k8s-app=kube-dns` 인데 이 실험대에서
|
||||
치지는 않았다
|
||||
|
||||
**배포된 것과 적어 둔 것이 다르다**
|
||||
|
||||
@@ -10341,11 +10515,250 @@ grep -c ExecStartPost /etc/systemd/system/lab-edge-dnat.service
|
||||
써서 `sudo systemctl daemon-reload && sudo systemctl restart lab-edge-dnat.service`
|
||||
로 올린다.
|
||||
|
||||
**그런데 고쳤는지는 이번에 확인하지 못했다**(unknown). 이 호스트의 `sudo` 는
|
||||
비밀번호를 묻고 이번 작업에는 그 비밀번호가 없었다. 그래서 `nft list table ip
|
||||
lab_edge` 도 `guest_input` 체인 조회도 못 돌렸고, 두 파일을 다시 배포하지도
|
||||
않았다. 위 표는 **파일 내용과 타임스탬프만으로** 적은 것이고, `reject` 줄의
|
||||
카운터가 실제로 올라갔는지는 안 봤다.
|
||||
**2026-09-17 늦게 그 `sudo` 가 열려 셋을 마저 봤다**(observed). 결론은 **절반만
|
||||
고쳐져 있다**이다.
|
||||
|
||||
```text
|
||||
grep -c 'chain forward' /etc/nftables.d/lab-edge-dnat.nft → 1 (0 이어야 한다)
|
||||
grep -c ExecStartPost /etc/systemd/system/lab-edge-dnat.service → 1 (맞다)
|
||||
```
|
||||
|
||||
유닛에는 `ExecStartPost` 가 들어갔는데 `.nft` 는 아직 옛 판이다 — 효과 없는
|
||||
`forward` 체인을 아무도 걷어내지 않았다. `.nft` 를 다시 쓰고
|
||||
`sudo systemctl restart lab-edge-dnat.service` 로 올려야 §180 이 싣는 판이 된다.
|
||||
|
||||
```bash
|
||||
sudo nft list chain ip libvirt_network guest_input
|
||||
```
|
||||
|
||||
```text
|
||||
oif "virbr0" ip daddr 192.168.122.10 tcp dport { 80, 443 } ct state new counter packets 44 bytes 2640 accept
|
||||
oif "virbr0" ip daddr 192.168.122.10 tcp dport { 80, 443 } ct state new counter packets 0 bytes 0 accept
|
||||
oif "virbr0" ip daddr 192.168.122.0/24 ct state established,related counter packets 141705 bytes 2375116597 accept
|
||||
oif "virbr0" counter packets 5 bytes 300 reject
|
||||
oif "virbr0" ip daddr 192.168.122.10 tcp dport { 80, 443 } ct state new counter packets 10 bytes 600 accept
|
||||
```
|
||||
|
||||
**구멍이 하나가 아니라 셋이다.** `ExecStartPost` 가 `nft insert` 로 맨 앞에 끼워
|
||||
넣으므로 유닛을 다시 시작할 때마다 같은 규칙이 한 줄씩 는다 — **여러 번 쳐도 안전한
|
||||
형태가 아니다.** 기능은 멀쩡하다. 맨 앞 하나가 통과시키고 나머지는 카운터가 안
|
||||
오르거나(둘째 `0`) 아예 닿지 않는다(다섯째는 `reject` 뒤다). `reject` 의 `5` 는
|
||||
구멍이 들어가기 전에 막힌 것이고 그 뒤로는 안 오른다.
|
||||
|
||||
**그리고 출발지를 덮는 것이 무엇인지도 그때 갈렸다 — Tailscale 이다**(observed).
|
||||
|
||||
```bash
|
||||
sudo nft list ruleset | grep -nE 'masquerade|snat'
|
||||
sudo iptables -t nat -S | grep ts-postrouting
|
||||
sudo tailscale debug prefs | grep -i snat
|
||||
```
|
||||
|
||||
```text
|
||||
chain ts-forward { iifname "tailscale0" counter ... xt target "MARK" }
|
||||
-A ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE
|
||||
"NoSNAT": false
|
||||
```
|
||||
|
||||
`tailscale0` 으로 들어와 전달되는 패킷에 Tailscale 이 표식 `0x40000` 을 찍고,
|
||||
`ts-postrouting` 이 그 표식이 붙은 것을 전부 `MASQUERADE` 한다. 나가는 인터페이스가
|
||||
`virbr0` 이라 출발지가 `192.168.122.1` 이 된다. `NoSNAT` 이 `false` 인 것이 그 동작이
|
||||
켜져 있다는 뜻이고 그것이 Tailscale 의 기본값이다.
|
||||
|
||||
**그래서 §180 의 경고는 옳았는데 엉뚱한 테이블을 지켰다.** 「masquerade 를 붙이면
|
||||
엣지가 모든 클라이언트를 `192.168.122.1` 로 보게 된다」는 그대로 일어났고, 다만 그
|
||||
`masquerade` 를 붙인 것이 우리 테이블이 아니라 Tailscale 이다. libvirt 의 마스커레이드는
|
||||
무관하다 — `ip saddr 192.168.122.0/24 ip daddr != 192.168.122.0/24` 이라 게스트에서 밖으로
|
||||
나가는 것만 고르고, 밖에서 들어오는 이 패킷은 목적지가 그 대역 안이라 안 걸린다.
|
||||
|
||||
**클러스터 안에서도 공개 이름에 못 닿는다**(2026-09-17, observed).
|
||||
|
||||
랩 호스트만의 일이 아니다. 파드 안에서도 같은 이름이 같은 이유로 막힌다.
|
||||
|
||||
```bash
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- sh -c "getent hosts auth.hyeonworks.com"
|
||||
```
|
||||
|
||||
```text
|
||||
100.83.212.4 auth.hyeonworks.com
|
||||
```
|
||||
|
||||
같은 파드에서 `100.83.212.4` 의 80 과 443 을 두드리면 둘 다 닫혀 있고, 엣지 게스트의
|
||||
`192.168.122.10:80` 은 열려 있다. **길은 있는데 이름이 그 길을 안 가리킨다.** 클러스터
|
||||
DNS 어디에도 그 매핑이 없다 — CoreDNS 의 `NodeHosts` 에는 노드 둘뿐이고 노드의
|
||||
`/etc/hosts` 에도 없으며, `deploy/lab/k8s/` 의 매니페스트 여덟 장에도 없다.
|
||||
|
||||
nginx 가 호스트에 있던 동안에는 파드가 `100.83.212.4:443` 을 치면 그 nginx 에 닿았다.
|
||||
03 이 옮기면서 그 경로가 끊겼고, 대신 놓아 줄 것을 어느 문서도 안 놓는다.
|
||||
|
||||
고치는 한 단계는 CoreDNS 에 서버 블록을 하나 더 주는 것이다. **이 실험대는 매니페스트를
|
||||
힙독으로 흘려 넣었다**(observed).
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'YAML'
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: coredns-custom
|
||||
namespace: kube-system
|
||||
data:
|
||||
hyeonworks.server: |
|
||||
hyeonworks.com:53 {
|
||||
hosts {
|
||||
192.168.122.10 auth.hyeonworks.com
|
||||
192.168.122.10 app1.hyeonworks.com
|
||||
192.168.122.10 app2.hyeonworks.com
|
||||
fallthrough
|
||||
}
|
||||
forward . /etc/resolv.conf
|
||||
}
|
||||
YAML
|
||||
kubectl -n kube-system delete pod -l k8s-app=kube-dns --wait=false
|
||||
```
|
||||
|
||||
**따라 하는 사람은 매니페스트를 파일로 쓴다.** 힙독은 이 ConfigMap 을 터미널 명령 안에
|
||||
묻어 버려서, 나중에 서버 블록을 고치려면 같은 힙독을 처음부터 다시 친다. 파일로 두면
|
||||
`kubectl apply` 를 다시 쳐서 고칠 수 있고 무엇이 들어갔는지도 열어서 본다.
|
||||
|
||||
```bash
|
||||
nano coredns-custom.yaml
|
||||
```
|
||||
|
||||
파일에 위 힙독 안의 `apiVersion:` 부터 마지막 `}` 까지를 그대로 쓴다. 그리고 적용한 뒤
|
||||
CoreDNS 파드를 다시 띄운다.
|
||||
|
||||
```bash
|
||||
kubectl apply -f coredns-custom.yaml
|
||||
kubectl -n kube-system delete pod -l k8s-app=kube-dns --wait=false
|
||||
```
|
||||
|
||||
이 나눈 형태는 이 실험대에서 치지 않았다(unknown).
|
||||
|
||||
**파일 이름이 `.server` 인 것이 핵심이다.** k3s 는 `*.override` 를 기본 서버 블록 안에
|
||||
끼워 넣는데 그 블록은 이미 `hosts /etc/coredns/NodeHosts` 로 `hosts` 를 한 번 쓴다.
|
||||
`.override` 에 `hosts` 를 또 쓰면 CoreDNS 가 `CrashLoopBackOff` 로 안 뜬다.
|
||||
|
||||
```text
|
||||
plugin/hosts: this plugin can only be used once per Server Block
|
||||
```
|
||||
|
||||
`.server` 는 별개의 서버 블록으로 들어가므로 그 안에서 `hosts` 를 처음 쓰는 것이 된다.
|
||||
뜨고 나면 로그 머리에 듣는 영역이 둘로 찍히고, 파드에서 이름이 엣지로 풀린다.
|
||||
|
||||
```text
|
||||
.:53
|
||||
hyeonworks.com.:53
|
||||
```
|
||||
|
||||
```text
|
||||
192.168.122.10 auth.hyeonworks.com
|
||||
```
|
||||
|
||||
`192.168.122.10:80` 이 파드에서 열린다. **443 은 여전히 닫혀 있다** — 그것은 04 가
|
||||
인증서를 얹어야 열린다.
|
||||
|
||||
**03 을 밟고 나면 랩 호스트에서 공개 URL 을 못 친다**(2026-09-17, observed).
|
||||
|
||||
nginx 가 호스트에 있던 동안에는 호스트에서 `curl https://auth.hyeonworks.com` 이
|
||||
그냥 됐다. 03 이 nginx 를 엣지 게스트로 옮기면서 그것이 끊긴다. 80 도 443 도
|
||||
안 열린다.
|
||||
|
||||
```bash
|
||||
curl -s -m 10 -o /dev/null -w "%{http_code}\n" https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/certs
|
||||
```
|
||||
|
||||
```text
|
||||
000
|
||||
Failed to connect to auth.hyeonworks.com:443 after 22 ms: Could not connect to server
|
||||
```
|
||||
|
||||
이유는 두 줄이다.
|
||||
|
||||
```bash
|
||||
getent hosts auth.hyeonworks.com
|
||||
ss -lnt | grep -E ":(80|443) "
|
||||
```
|
||||
|
||||
```text
|
||||
100.83.212.4 auth.hyeonworks.com
|
||||
```
|
||||
|
||||
두 번째 줄은 아무것도 안 낸다. `auth.hyeonworks.com` 은 호스트 자신의 tailnet
|
||||
주소로 풀리는데 호스트에는 그 포트를 듣는 것이 없고, 03 의 DNAT 은 `iifname
|
||||
"tailscale0"` 만 매칭하므로 호스트가 스스로 낸 패킷은 그 규칙을 안 탄다. §190 이
|
||||
자기 4번 확인을 엣지가 아닌 다른 머신으로 보낸 것과 같은 이유다.
|
||||
|
||||
**이 전제가 실험 기록 14편 · 명령 55줄에 걸려 있다**(observed). `[lab host]` 라벨이
|
||||
붙은 코드블록 안에서 `auth`·`app1`·`app2` 의 공개 URL 을 치는 줄을 센 것이고,
|
||||
많은 쪽부터 B-6(12) · B-5(10) · D-1(9) 순이다. 그 줄들은 nginx 가 호스트에 있던
|
||||
때의 배치로 쓰였고 지금 배치에서는 `000` 을 낸다.
|
||||
|
||||
랩 호스트에서 꼭 쳐야 하면 이름은 그대로 두고 주소만 엣지로 못박는다.
|
||||
|
||||
```bash
|
||||
curl -s -m 10 --resolve auth.hyeonworks.com:80:192.168.122.10 \
|
||||
http://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/certs
|
||||
```
|
||||
|
||||
이 형태는 응답을 받는다(observed). 다만 DNAT 을 안 거치므로 **층 ③ 을 재는 것이
|
||||
아니다.** 층 ② 를 도메인 이름으로 재는 것이고, 4·5 번이 제대로 들어갔는지는
|
||||
여전히 밖에서 쳐야 갈린다.
|
||||
|
||||
**`X-Forwarded-For` 계약은 이 실험대에서 성립하지 않는다**(2026-09-17, observed).
|
||||
|
||||
위 4번이 SNAT 을 안 거는 이유로 「masquerade 를 붙이면 엣지가 모든 클라이언트를
|
||||
`192.168.122.1` 로 보게 된다」고 적었고, §215 의 표도 그 줄을 감수한 비용으로
|
||||
싣는다. 그런데 밖에서 한 번 치고 엣지의 로그를 열었더니 **이미 그 상태였다.**
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null http://app1.hyeonworks.com/api/echo
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo tail -3 /var/log/nginx/access.log
|
||||
```
|
||||
|
||||
```text
|
||||
192.168.122.1 - - [17/Sep/2026:07:10:45 +0000] "GET /api/echo HTTP/1.1" 200 617 "-" "curl/8.5.0"
|
||||
```
|
||||
|
||||
`lab_edge` 테이블에는 `masquerade` 도 `snat` 도 없다 — 위에 실은 `.nft` 파일
|
||||
그대로다. 그러므로 출발지를 덮는 것은 이 테이블이 아니고, **무엇이 덮는지는 가리지
|
||||
못했다**(unknown). 그것을 보려면 호스트에서 `nft list ruleset` 을 쳐야 하는데 그
|
||||
`sudo` 가 비밀번호를 묻는다. 결과만 놓고 보면 `proxy_set_header X-Forwarded-For
|
||||
$remote_addr` 는 시키는 대로 동작하고 있고 `$remote_addr` 가 이미 호스트의 브리지
|
||||
주소다. §259 가 적은 신뢰 경계 논의는 그대로 유효하지만, **이 실험대에서 그 헤더로
|
||||
클라이언트를 가릴 수는 없다.**
|
||||
|
||||
**Traefik 이 그 헤더를 한 번 더 덮는다 — 03 에 없던 한 단계**(2026-09-17, observed).
|
||||
|
||||
2번이 세운 헤더가 앱까지 가는지를 처음으로 쟀다. 안 간다. Traefik 은 자기가
|
||||
신뢰하지 않는 곳에서 온 `X-Forwarded-*` 를 자기 연결 기준으로 다시 쓰고, 기본값의
|
||||
신뢰 목록은 비어 있다.
|
||||
|
||||
```bash
|
||||
curl -s http://app1.hyeonworks.com/api/echo
|
||||
```
|
||||
|
||||
| 헤더 | 이 한 단계를 안 밟았을 때 | 밟은 뒤 |
|
||||
|---|---|---|
|
||||
| `x-real-ip` | `10.42.0.1` — flannel 게이트웨이. 엣지가 보낸 값이 사라졌다 | `192.168.122.1` — 엣지가 보낸 값 그대로 |
|
||||
| `remoteAddr` | `10.42.0.1` | `192.168.122.1` |
|
||||
| `x-forwarded-for` | 없다 | 없다 |
|
||||
|
||||
빠진 한 단계는 두 줄이고, 저장소에 파일(`deploy/lab/k8s/traefik-forwarded-headers.yaml`)
|
||||
이 있는데 **setup 아홉 편 어디에도 이 명령이 없었다.**
|
||||
|
||||
```bash
|
||||
kubectl apply -f deploy/lab/k8s/traefik-forwarded-headers.yaml
|
||||
kubectl -n kube-system rollout status deploy/traefik --timeout=180s
|
||||
```
|
||||
|
||||
신뢰 목록에는 파드 대역 `10.42.0.0/16` 하나만 담는다. Traefik 의 Service 가
|
||||
`externalTrafficPolicy: Cluster` 라 svclb 가 출발지를 덮고, 그래서 Traefik 에 닿는
|
||||
주소는 엣지가 아니라 파드 대역에서 온다. 노드·호스트 대역을 같이 넣어서도 재
|
||||
봤는데 위 표의 값이 하나도 안 바뀌었다 — 나타날 수 없는 대역이라 넣어도 소용이
|
||||
없다(observed). `x-forwarded-for` 는 이 단계를 밟아도 앱까지 안 오고, **왜
|
||||
사라지는지는 안 가렸다**(unknown).
|
||||
|
||||
## 190. 단계 04 — Let's Encrypt 와 인증서 갱신
|
||||
|
||||
@@ -10749,6 +11162,49 @@ server {
|
||||
| 443 블록 | 없었다 | 인증서와 함께 새로 |
|
||||
| `X-Forwarded-Proto` | `http` | **`https`** |
|
||||
|
||||
**★ 위 블록의 인증서 경로가 틀렸다**(2026-09-17, observed). `live/hyeonworks.com/` 이라고
|
||||
적혀 있는데 certbot 이 만드는 디렉터리는 **`live/auth.hyeonworks.com/`** 이다. 이름을
|
||||
여럿 담은 인증서라도 디렉터리 이름은 `-d` 로 처음 준 이름 하나를 쓴다. 그대로 치면
|
||||
`nginx -t` 가 막힌다.
|
||||
|
||||
```text
|
||||
[emerg] cannot load certificate "/etc/letsencrypt/live/hyeonworks.com/fullchain.pem":
|
||||
BIO_new_file() failed (SSL: error:80000002:system library::No such file or directory
|
||||
nginx: configuration file /etc/nginx/nginx.conf test failed
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo ls /etc/letsencrypt/live/
|
||||
sudo certbot certificates | grep 'Certificate Path'
|
||||
```
|
||||
|
||||
```text
|
||||
README
|
||||
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/` 쪽이다.
|
||||
|
||||
**그리고 이 실험대의 certbot 은 DNS-01 로 등록되어 있다**(observed). 두 문서가 갈렸던
|
||||
대목이 닫혔다.
|
||||
|
||||
```bash
|
||||
sudo sh -c 'grep -H "authenticator\|dns_cloudflare_credentials\|server =" /etc/letsencrypt/renewal/*.conf'
|
||||
```
|
||||
|
||||
```text
|
||||
authenticator = dns-cloudflare
|
||||
dns_cloudflare_credentials = /etc/letsencrypt/cloudflare.ini
|
||||
server = https://acme-v02.api.letsencrypt.org/directory
|
||||
```
|
||||
|
||||
**`sudo grep ... /etc/letsencrypt/renewal/*.conf` 로 치면 안 된다**(observed). 글로브를 일반
|
||||
사용자 셸이 먼저 펼치는데 그 디렉터리를 못 읽어서 `no matches found` 로 끝나고 `grep` 은
|
||||
시작도 안 한다. `sudo sh -c '...'` 로 sudo 안에서 펼쳐야 한다.
|
||||
|
||||
**`cert.pem` 이 아니라 `fullchain.pem`.** 서버 인증서만 보내면 중간 인증서가 빠져
|
||||
체인이 끊긴다. 브라우저는 대개 캐시나 AIA 로 보완해서 **정상으로 보이고**, 캐시가
|
||||
없는 클라이언트에서만 깨진다. 그래서 발견이 늦다. 2번에서 certbot 이 찍어 준 경로와
|
||||
@@ -10944,6 +11400,31 @@ curl -v https://auth.hyeonworks.com/ -o /dev/null
|
||||
**`subject` 가 `hyeonworks.com` 인 것이 맞다** — 와일드카드 인증서라 CN 은 apex
|
||||
이름이고 `auth.hyeonworks.com` 은 SAN 의 `*.hyeonworks.com` 에 걸린다.
|
||||
|
||||
**★ 그런데 이 실험대에서는 CN 이 `auth.hyeonworks.com` 이다**(2026-09-17,
|
||||
observed). 가이드대로 와일드카드를 받으면 위 문단처럼 보인다. 이 실험대는
|
||||
와일드카드를 안 받았고 계보도 `auth.hyeonworks.com` 하나뿐이라, 앞에서 적은
|
||||
「이 실험대는 처음에 와일드카드를 안 쉈고」와 같은 사실을 가리킨다. 그 둘이
|
||||
같은 문서 안에서 서로 어긋나 있었다.
|
||||
|
||||
```bash
|
||||
echo | openssl s_client -connect 192.168.122.10:443 -servername auth.hyeonworks.com 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName -dates
|
||||
```
|
||||
|
||||
```text
|
||||
subject=CN=auth.hyeonworks.com
|
||||
X509v3 Subject Alternative Name:
|
||||
DNS:app1.hyeonworks.com, DNS:app2.hyeonworks.com, DNS:auth.hyeonworks.com
|
||||
notBefore=Sep 4 11:29:18 2026 GMT
|
||||
notAfter=Dec 3 11:29:17 2026 GMT
|
||||
```
|
||||
|
||||
그래서 CN 을 합격 조건으로 읽으면 안 된다. 와일드카드를 받았으면
|
||||
`hyeonworks.com`, 이름을 나열해 받았으면 첫 번째 `-d` 의 이름이 CN 이 된다.
|
||||
보는 것은 `SSL certificate verify ok.` 한 줄과, 지금 친 이름이 SAN 에 있는가다.
|
||||
이름 셋 밖은 TLS 단계에서 끝나 `curl` 이 종료 코드 `60` 을 내고 `%{http_code}` 가
|
||||
`000` 이 되는데, 화면에서는 「서버가 죽었다」와 같아 보인다. A-4 의 Grafana
|
||||
탐침이 그렇게 쓸모없어졌다.
|
||||
|
||||
**이 결과가 의미하는 것** — 네 줄이 다 나오면 인증서가 붙었고 체인이 클라이언트
|
||||
기준으로 검증됐다. **인증서를 처음 붙인 직후에는 코드 한 칸이 아니라 이 화면을
|
||||
본다.** `verify` 줄 대신 `unable to get local issuer certificate` 가 나오면 중간
|
||||
@@ -10955,6 +11436,14 @@ curl -v https://auth.hyeonworks.com/ -o /dev/null
|
||||
curl -s -o /dev/null -w '%{http_code} tls=%{ssl_verify_result}\n' https://auth.hyeonworks.com/
|
||||
```
|
||||
|
||||
**랩 호스트에서 칠 때만 `--resolve` 를 붙인다**(2026-09-17, observed). 거기서는 이 이름이 호스트
|
||||
자신의 tailnet 주소로 풀리고 그 주소에는 443 을 듣는 것이 없다.
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w '%{http_code} tls=%{ssl_verify_result}\n' --resolve auth.hyeonworks.com:443:192.168.122.10 \
|
||||
https://auth.hyeonworks.com/
|
||||
```
|
||||
|
||||
**실측**(observed) — 2026-09-11, tailnet 클라이언트에서
|
||||
|
||||
```
|
||||
@@ -11204,7 +11693,8 @@ ls deploy/lab/k8s/
|
||||
[04] 의 확인 ① 을 그대로 다시 친다.
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w '%{http_code} tls=%{ssl_verify_result}\n' https://auth.hyeonworks.com/
|
||||
curl -s -o /dev/null -w '%{http_code} tls=%{ssl_verify_result}\n' --resolve auth.hyeonworks.com:443:192.168.122.10 \
|
||||
https://auth.hyeonworks.com/
|
||||
```
|
||||
|
||||
**어디를 봐야 하는가** — 04 에서 잰 `404 tls=0` 이 그대로인가. **앞의 코드를 같이
|
||||
@@ -11692,7 +12182,8 @@ vendor_cluster_size{cache_manager="keycloak",node="keycloak-0-46674"} 2.0
|
||||
**확인 ④ 밖에서 닿나** — 2홉을 다 지나 파드까지
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
```
|
||||
@@ -11709,7 +12200,7 @@ Service 뒤에 파드가 있는지(2-4), 파드가 Ready 인지(2-2) 순서다.
|
||||
헤더가 필요하면 값만 뽑는 형태를 버리고 읽는 형태로 바꾼다.
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
**★ 이 `200` 은 04 를 끝냈을 때의 값이다.** 04 를 건너뛰고 05 만 했다면 443 을 듣는
|
||||
@@ -12103,7 +12594,8 @@ kubectl -n observability exec deploy/prometheus -- \
|
||||
응답을 받아 보는 것이 가장 짧다.
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
**어디를 봐야 하는가** — 코드 한 칸. 여기서 값만 뽑는 형태를 쓰는 것은 `up` 의 1/0 과
|
||||
@@ -12219,10 +12711,9 @@ kubectl -n observability port-forward svc/grafana 3000:3000
|
||||
돌고 있는 실험대가 없어지기 때문이다 — **가이드대로 쳐서 이 상태가 다시 서는지**
|
||||
는 확인된 적이 없다 (unknown)
|
||||
- 호스트의 코어 수가 가이드의 「16 코어 전부」와 §178 의 「논리 코어 8」로
|
||||
갈린다. 어느 쪽이 이 실험대인지 (unknown)
|
||||
- cloud-init 의 `packages` 에 certbot 이 있었는가. 01·03 은 `curl`·`nftables`
|
||||
뿐이라 적고 04 는 들어 있다고 적는다. `kc-lab.yaml.example` 이 `source/` 에
|
||||
없어 대조하지 못했다 (unknown)
|
||||
갈렸다 → **8 이다.** 2026-09-17 에 `nproc` 과 `lscpu` 로 갈랐다 (observed)
|
||||
- cloud-init 의 `packages` 에 certbot 이 있었는가 → **없다.** 2026-09-17 에
|
||||
게스트의 user-data 와 저장소 템플릿을 둘 다 열어서 갈랐다 (observed)
|
||||
- `lsmod | grep kvm` 의 실제 출력, 03 의 `curl -I http://192.168.122.11` 출력,
|
||||
04 의 `curl -v` 협상 출력 — 셋 다 캡처해 두지 않았다 (unknown)
|
||||
- k3s 토큰 108자는 판올림에 따라 달라진다. 다른 판에서 몇 자인지는 재지 않았다
|
||||
@@ -12759,6 +13250,39 @@ tar 하나에 30초고, 답을 알고 나면 지우면 된다.
|
||||
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` 로
|
||||
지운다 — 인증서는 그 안에서 같이 사라지고, 위 백업에는 안 들어간다.
|
||||
|
||||
```bash
|
||||
ls -la /etc/letsencrypt/
|
||||
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` | **한다** |
|
||||
|
||||
묶어야 할 것은 게스트 쪽이다.
|
||||
|
||||
```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 ~/
|
||||
```
|
||||
|
||||
**「철거해도 남는 것」 표의 패키지 줄도 같은 이유로 틀렸다.** 그 줄은 `nginx` 와
|
||||
`certbot` 을 남는 쪽에 적어 두었는데 둘 다 게스트 안에 있으므로 게스트와 함께
|
||||
사라진다. 랩 호스트에 남는 것은 `libvirt`·`qemu`·`kubectl` 과, 쓰이지 않는 호스트
|
||||
쪽 `certbot` 이다.
|
||||
|
||||
**철거 전 네 값은 2026-09-17 에 다시 받았다**(observed). 도메인 3 대에 볼륨 7 개,
|
||||
예약 3 줄까지는 2026-09-10 과 같고 **디스크만 11G 가 아니라 18G 다** — 그사이에
|
||||
k3s 와 컨테이너 이미지가 쌓였다. 이 네 값은 이 호스트의 정답이 아니라 매 실행에서
|
||||
받아 두는 대조군이다.
|
||||
|
||||
복원은 반대로 한 줄이다.
|
||||
|
||||
```bash
|
||||
@@ -17514,6 +18038,20 @@ k3s가 컨테이너에 SIGTERM을 보낸다. 유예 시간이 짧으면 **Postgr
|
||||
강제 종료되어 다음 기동에 crash recovery가 돈다.** 미리 내려두면 그 위험이
|
||||
없다.
|
||||
|
||||
**따라 하는 사람은 3번의 두 게스트를 `&&` 로 잇지 않고 한 줄씩 친다.**
|
||||
|
||||
```bash
|
||||
virsh shutdown kc-lab-1
|
||||
virsh shutdown kc-lab-2
|
||||
```
|
||||
|
||||
두 게스트는 서로 앞뒤가 없고, `A && B` 는 A 가 성공했을 때만 B 를 실행한다.
|
||||
`kc-lab-1` 이 이미 `shut off` 면 `virsh shutdown` 은 `domain is not running` 으로
|
||||
실패하므로 `kc-lab-2` 는 켜진 채로 남고, 바로 다음 줄의 `systemctl poweroff` 가
|
||||
그 게스트를 강제로 끈다 — 이 절차가 막으려던 바로 그 상태다. §226 의
|
||||
`net-start && net-autostart` 와 같은 자리다. 이 나눈 형태는 이 실험대에서 치지
|
||||
않았다(unknown).
|
||||
|
||||
**clean shutdown 확인**
|
||||
|
||||
```bash
|
||||
@@ -17537,6 +18075,18 @@ kubectl -n keycloak-lab scale statefulset/keycloak --replicas=2
|
||||
|
||||
**스케일을 0으로 내려두면 자동으로 복구되지 않는다.** 명시적으로 올려야 한다.
|
||||
|
||||
여기 첫 줄도 `&&` 로 잇지 않고 한 줄씩 친다.
|
||||
|
||||
```bash
|
||||
virsh start kc-lab-1
|
||||
virsh start kc-lab-2
|
||||
```
|
||||
|
||||
`kc-lab-1` 이 이미 `running` 이면 `virsh start` 가 `domain is already active` 로
|
||||
실패하고 `kc-lab-2` 는 안 뜬다. 그러면 `kubectl get nodes` 에 노드가 하나만 나오는데,
|
||||
증상이 게스트를 안 띄운 것과 노드가 안 붙은 것 사이에서 갈리지 않는다.
|
||||
이 나눈 형태는 이 실험대에서 치지 않았다(unknown).
|
||||
|
||||
## 335. qcow2 파일을 다른 물리 서버로 옮기면 무엇이 따라가나
|
||||
|
||||
1층의 「qcow2와 backing store」가 **오버레이 구조**를, 「qcow2 파일 내부는
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
=== 03 층별 확인 — 이제 밖에서 닿는다 ===
|
||||
--- 층 ③ 밖에서 도메인으로 (문서: 04 이후엔 301, 이 단계에선 404) ---
|
||||
HTTP/1.1 302 Found
|
||||
Server: nginx/1.22.1
|
||||
Date: Thu, 17 Sep 2026 07:07:31 GMT
|
||||
Connection: keep-alive
|
||||
--- 값만 뽑는 형태 ---
|
||||
302 https://auth.hyeonworks.com/admin/
|
||||
--- 층 ④ TLS 이후 (아직 인증서가 없다) ---
|
||||
000
|
||||
=== app1 도 닿나 (B층·C층이 쓸 경로) ===
|
||||
app1 200
|
||||
app1 root 200
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
=== 같은 엣지에 세 가지로 친다 — 무엇이 코드를 가르나 ===
|
||||
① Host 없이 엣지 IP 로 : 000
|
||||
404
|
||||
② Host: auth 로 엣지 IP 에 : 302 https://auth.hyeonworks.com/admin/
|
||||
③ 밖에서 도메인으로 : 302 https://auth.hyeonworks.com/admin/
|
||||
④ 밖에서 도메인 + /realms/master: 200
|
||||
|
||||
=== Keycloak 이 무엇을 보고 그 주소를 만드나 ===
|
||||
KC_HOSTNAME=https://auth.hyeonworks.com
|
||||
KC_HOSTNAME_STRICT=true
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
=== 01 의 미검증: cloud-init packages 에 certbot 이 있었나 ===
|
||||
--- 저장소 템플릿
|
||||
10:hostname: kc-lab-__NODE__
|
||||
14:users:
|
||||
15: - name: donghyeon
|
||||
22: # (22.4.2 on the guests) rejects it and prints the whole users.0 block with
|
||||
40:packages:
|
||||
|
||||
--- 엣지 게스트가 실제로 받은 user-data
|
||||
19:packages: [curl, nftables]
|
||||
---
|
||||
#cloud-config
|
||||
hostname: kc-lab-edge
|
||||
fqdn: kc-lab-edge
|
||||
manage_etc_hosts: true
|
||||
|
||||
users:
|
||||
- name: donghyeon
|
||||
groups: [sudo]
|
||||
shell: /bin/bash
|
||||
sudo: ['ALL=(ALL) NOPASSWD:ALL']
|
||||
ssh_authorized_keys:
|
||||
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINW0f8garKmfO93vd66yl1t1JtTKN68eRh8K6NuMpBzk test-server -> kc-lab
|
||||
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGhGllPm3iLvB93ITk36Ep0TtAXgBqZZtzvhIwLK8T4j donghyeon@donghyeon-960XGK
|
||||
|
||||
ssh_pwauth: false
|
||||
package_update: true
|
||||
packages: [curl, nftables]
|
||||
=== 저장소 템플릿의 packages 절 ===
|
||||
ssh_pwauth: false
|
||||
package_update: true
|
||||
packages:
|
||||
- curl
|
||||
- nftables
|
||||
# kc-lab-edge only. The k3s nodes do not need these, and the edge does not need
|
||||
# anything else — nginx terminates TLS and certbot renews the certificate, both
|
||||
# inside this disposable guest.
|
||||
# - nginx
|
||||
# - certbot
|
||||
# - python3-certbot-dns-cloudflare
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
=== 01 확인 ① ===
|
||||
Id Name State
|
||||
-----------------------------
|
||||
13 kc-lab-1 running
|
||||
15 kc-lab-edge running
|
||||
16 kc-lab-2 running
|
||||
|
||||
|
||||
kc-lab-1
|
||||
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
|
||||
|
||||
=== 01 확인 ② ===
|
||||
kc-lab-edge
|
||||
enp1s0 UP 192.168.122.10/24 metric 100
|
||||
status: done
|
||||
|
||||
=== 01 확인 ③ 스크린샷 ===
|
||||
Screenshot saved to /tmp/kc1.ppm, with type of image/png
|
||||
/tmp/kc1.ppm: PNG image data, 720 x 400, 8-bit/color RGB, non-interlaced
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
=== 08 「지우기 전에 먼저 본다」 네 값 ===
|
||||
Id Name State
|
||||
-----------------------------
|
||||
13 kc-lab-1 running
|
||||
15 kc-lab-edge running
|
||||
16 kc-lab-2 running
|
||||
|
||||
--- 볼륨
|
||||
Name Path
|
||||
----------------------------------------------------------------------
|
||||
base.qcow2 /var/lib/libvirt/images/base.qcow2
|
||||
kc-lab-1.qcow2 /var/lib/libvirt/images/kc-lab-1.qcow2
|
||||
kc-lab-2.qcow2 /var/lib/libvirt/images/kc-lab-2.qcow2
|
||||
kc-lab-edge.qcow2 /var/lib/libvirt/images/kc-lab-edge.qcow2
|
||||
seed-kc-lab-1.iso /var/lib/libvirt/images/seed-kc-lab-1.iso
|
||||
seed-kc-lab-2.iso /var/lib/libvirt/images/seed-kc-lab-2.iso
|
||||
seed-kc-lab-edge.iso /var/lib/libvirt/images/seed-kc-lab-edge.iso
|
||||
|
||||
--- 예약
|
||||
<dhcp>
|
||||
<range start='192.168.122.2' end='192.168.122.254'/>
|
||||
<host mac='52:54:00:aa:bb:10' name='kc-lab-edge' ip='192.168.122.10'/>
|
||||
<host mac='52:54:00:aa:bb:11' name='kc-lab-1' ip='192.168.122.11'/>
|
||||
<host mac='52:54:00:aa:bb:12' name='kc-lab-2' ip='192.168.122.12'/>
|
||||
</dhcp>
|
||||
</ip>
|
||||
</network>
|
||||
|
||||
--- 디스크
|
||||
/dev/nvme0n1p3 226G 18G 197G 9% /
|
||||
|
||||
=== 08 이 지키려는 /etc/letsencrypt 가 어느 기계에 있나 ===
|
||||
--- lab host
|
||||
ls: cannot open directory '/etc/letsencrypt/': Permission denied
|
||||
certbot: /usr/bin/certbot
|
||||
--- kc-lab-edge (03·04 가 nginx·certbot 을 옮겨 둔 곳)
|
||||
total 16
|
||||
drwxr-xr-x 3 root root 4096 Sep 17 07:11 .
|
||||
drwxr-xr-x 66 root root 4096 Sep 17 04:33 ..
|
||||
-rw-r--r-- 1 root root 207 Nov 12 2021 cli.ini
|
||||
drwxr-xr-x 5 root root 4096 Sep 17 04:33 renewal-hooks
|
||||
certbot: /usr/bin/certbot
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
=== 09 · lsmod | grep kvm ===
|
||||
kvm_intel 524288 11
|
||||
kvm 1490944 6 kvm_intel
|
||||
irqbypass 16384 1 kvm
|
||||
|
||||
=== 09 · 코어 수가 16 인가 8 인가 ===
|
||||
8
|
||||
CPU(s): 8
|
||||
Model name: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz
|
||||
Thread(s) per core: 2
|
||||
Core(s) per socket: 4
|
||||
|
||||
=== 09 · systemctl status libvirtd.socket ===
|
||||
● libvirtd.socket - libvirt legacy monolithic daemon socket
|
||||
Loaded: loaded (/usr/lib/systemd/system/libvirtd.socket; enabled; preset: disabled)
|
||||
Active: active (running) since Thu 2026-09-03 19:00:35 KST; 1 week 6 days ago
|
||||
Invocation: e330532b350d4d79ad7f31f7c6512a6c
|
||||
Triggers: ● libvirtd.service
|
||||
Listen: /run/libvirt/libvirt-sock (Stream)
|
||||
|
||||
Sep 03 19:00:35 test-server systemd[1]: Listening on libvirt legacy monolithic daemon socket.
|
||||
|
||||
=== 01 · virsh vol-info 와 net-dumpxml --inactive ===
|
||||
Name: kc-lab-1.qcow2
|
||||
Type: file
|
||||
Capacity: 20.00 GiB
|
||||
Allocation: 5.40 GiB
|
||||
|
||||
---
|
||||
Name: seed-kc-lab-1.iso
|
||||
Type: file
|
||||
Capacity: 370.00 KiB
|
||||
Allocation: 372.00 KiB
|
||||
|
||||
---
|
||||
<bridge name='virbr0' stp='on' delay='0'/>
|
||||
<range start='192.168.122.2' end='192.168.122.254'/>
|
||||
<host mac='52:54:00:aa:bb:10' name='kc-lab-edge' ip='192.168.122.10'/>
|
||||
<host mac='52:54:00:aa:bb:11' name='kc-lab-1' ip='192.168.122.11'/>
|
||||
<host mac='52:54:00:aa:bb:12' name='kc-lab-2' ip='192.168.122.12'/>
|
||||
|
||||
=== 01 · cloud-init status --long ===
|
||||
status: done
|
||||
boot_status_code: enabled-by-generator
|
||||
last_update: Thu, 17 Sep 2026 04:24:22 +0000
|
||||
detail:
|
||||
DataSourceNoCloud [seed=/dev/vdb][dsmode=net]
|
||||
=== 볼륨 일곱의 용량·할당 ===
|
||||
base.qcow2 Capacity=3.00 GiB Allocation=323.25 MiB
|
||||
kc-lab-1.qcow2 Capacity=20.00 GiB Allocation=5.40 GiB
|
||||
kc-lab-2.qcow2 Capacity=20.00 GiB Allocation=3.85 GiB
|
||||
kc-lab-edge.qcow2 Capacity=10.00 GiB Allocation=316.26 MiB
|
||||
seed-kc-lab-1.iso Capacity=370.00 KiB Allocation=372.00 KiB
|
||||
seed-kc-lab-2.iso Capacity=370.00 KiB Allocation=372.00 KiB
|
||||
seed-kc-lab-edge.iso Capacity=370.00 KiB Allocation=372.00 KiB
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
=== 03 ① 배포된 두 파일이 문서 판인가 ===
|
||||
1
|
||||
1
|
||||
(문서는 0 과 1 이 나와야 맞다고 적는다)
|
||||
|
||||
=== 03 ② lab_edge 테이블 실물 ===
|
||||
table ip lab_edge {
|
||||
chain prerouting {
|
||||
type nat hook prerouting priority dstnat; policy accept;
|
||||
iifname "tailscale0" tcp dport { 80, 443 } dnat to 192.168.122.10
|
||||
}
|
||||
|
||||
chain forward {
|
||||
type filter hook forward priority filter - 10; policy accept;
|
||||
ip daddr 192.168.122.10 tcp dport { 80, 443 } ct state new accept
|
||||
}
|
||||
}
|
||||
|
||||
=== 03 ③ guest_input 체인 — 구멍이 들어가 있나 ===
|
||||
table ip libvirt_network {
|
||||
chain guest_input {
|
||||
oif "virbr0" ip daddr 192.168.122.10 tcp dport { 80, 443 } ct state new counter packets 44 bytes 2640 accept
|
||||
oif "virbr0" ip daddr 192.168.122.10 tcp dport { 80, 443 } ct state new counter packets 0 bytes 0 accept
|
||||
oif "virbr0" ip daddr 192.168.122.0/24 ct state established,related counter packets 141705 bytes 2375116597 accept
|
||||
oif "virbr0" counter packets 5 bytes 300 reject
|
||||
oif "virbr0" ip daddr 192.168.122.10 tcp dport { 80, 443 } ct state new counter packets 10 bytes 600 accept
|
||||
}
|
||||
}
|
||||
|
||||
=== 03 ④ 출발지를 덮는 것이 무엇인가 ===
|
||||
# Warning: table ip filter is managed by iptables-nft, do not touch!
|
||||
# Warning: table ip nat is managed by iptables-nft, do not touch!
|
||||
# Warning: table ip6 filter is managed by iptables-nft, do not touch!
|
||||
# Warning: table ip6 nat is managed by iptables-nft, do not touch!
|
||||
# Warning: table ip mangle is managed by iptables-nft, do not touch!
|
||||
# Warning: table ip6 mangle is managed by iptables-nft, do not touch!
|
||||
122: meta l4proto tcp ip saddr 192.168.122.0/24 ip daddr != 192.168.122.0/24 counter packets 308 bytes 18480 masquerade to :1024-65535
|
||||
123: meta l4proto udp ip saddr 192.168.122.0/24 ip daddr != 192.168.122.0/24 counter packets 39 bytes 2964 masquerade to :1024-65535
|
||||
124: ip saddr 192.168.122.0/24 ip daddr != 192.168.122.0/24 counter packets 0 bytes 0 masquerade
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
=== tailscale 이 만든 체인 ===
|
||||
# Warning: table ip filter is managed by iptables-nft, do not touch!
|
||||
# Warning: table ip nat is managed by iptables-nft, do not touch!
|
||||
# Warning: table ip6 filter is managed by iptables-nft, do not touch!
|
||||
# Warning: table ip6 nat is managed by iptables-nft, do not touch!
|
||||
# Warning: table ip mangle is managed by iptables-nft, do not touch!
|
||||
# Warning: table ip6 mangle is managed by iptables-nft, do not touch!
|
||||
2: chain ts-input {
|
||||
4: iifname "tailscale0" counter packets 1168848 bytes 1390274528 accept
|
||||
6: ip saddr 100.115.92.0/23 iifname != "tailscale0" counter packets 0 bytes 0 return
|
||||
7: ip saddr 100.64.0.0/10 iifname != "tailscale0" counter packets 530 bytes 29680 drop
|
||||
10: chain ts-forward {
|
||||
11: iifname "tailscale0" counter packets 360 bytes 33127 xt target "MARK"
|
||||
13: ip saddr 100.64.0.0/10 oifname "tailscale0" counter packets 0 bytes 0 drop
|
||||
14: oifname "tailscale0" counter packets 265 bytes 95483 accept
|
||||
19: counter packets 3492404 bytes 4065830972 jump ts-input
|
||||
24: counter packets 590543 bytes 5494319628 jump ts-forward
|
||||
28: chain ts-postrouting {
|
||||
34: counter packets 332016 bytes 24132162 jump ts-postrouting
|
||||
38: chain ts-input {
|
||||
40: iifname "tailscale0" counter packets 1 bytes 104 accept
|
||||
44: chain ts-forward {
|
||||
45: iifname "tailscale0" counter packets 0 bytes 0 xt target "MARK"
|
||||
47: oifname "tailscale0" counter packets 0 bytes 0 accept
|
||||
52: counter packets 435 bytes 159766 jump ts-input
|
||||
57: counter packets 0 bytes 0 jump ts-forward
|
||||
61: chain ts-postrouting {
|
||||
67: counter packets 81 bytes 6580 jump ts-postrouting
|
||||
152: iifname "tailscale0" tcp dport { 80, 443 } dnat to 192.168.122.10
|
||||
|
||||
=== iptables-nft 쪽 nat POSTROUTING ===
|
||||
-P PREROUTING ACCEPT
|
||||
-P INPUT ACCEPT
|
||||
-P OUTPUT ACCEPT
|
||||
-P POSTROUTING ACCEPT
|
||||
-N ts-postrouting
|
||||
-A POSTROUTING -j ts-postrouting
|
||||
-A ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE
|
||||
|
||||
=== tailscale 의 SNAT 설정 ===
|
||||
"AdvertiseTags": null,
|
||||
"AdvertiseRoutes": null,
|
||||
"AdvertiseServices": null,
|
||||
"NoSNAT": false,
|
||||
"Advertise": false
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
=== 02 · API 서버 인증서 SAN ===
|
||||
X509v3 Subject Alternative Name:
|
||||
DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, DNS:localhost, DNS:kc-lab-1, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1, IP Address:192.168.122.11, IP Address:10.43.0.1, IP Address:192.168.122.11
|
||||
|
||||
=== 02 · journalctl -u k3s (서버) 끝 6줄 ===
|
||||
Sep 17 07:37:32 kc-lab-1 k3s[1024]: I0917 07:37:32.358989 1024 reconciler_common.go:299] "Volume detached for volume \"kube-api-access-49x4f\" (UniqueName: \"kubernetes.io/projected/b2a26eb4-c671-4681-b67f-77e47966b53b-kube-api-access-49x4f\") on node \"kc-lab-1\" DevicePath \"\""
|
||||
Sep 17 07:37:32 kc-lab-1 k3s[1024]: I0917 07:37:32.501306 1024 scope.go:122] "RemoveContainer" containerID="21a239d442f2d69328ebf52e5b29e6706a93f62c622ae201284313a983839424"
|
||||
Sep 17 07:37:32 kc-lab-1 k3s[1024]: I0917 07:37:32.962363 1024 kubelet_volumes.go:161] "Cleaned up orphaned pod volumes dir" podUID="b2a26eb4-c671-4681-b67f-77e47966b53b" path="/var/lib/kubelet/pods/b2a26eb4-c671-4681-b67f-77e47966b53b/volumes"
|
||||
Sep 17 07:40:08 kc-lab-1 k3s[1024]: time="2026-09-17T07:40:08Z" level=info msg="COMPACT compactRev=9376 targetCompactRev=9685 currentRev=10685"
|
||||
Sep 17 07:40:08 kc-lab-1 k3s[1024]: time="2026-09-17T07:40:08Z" level=info msg="COMPACT deleted 380 rows from 309 revisions in 9.152746ms - compacted to 9685/10685"
|
||||
Sep 17 07:40:08 kc-lab-1 k3s[1024]: time="2026-09-17T07:40:08Z" level=info msg="COMPACT compacted from 9376 to 9685 in 1 transactions over 9ms"
|
||||
|
||||
=== 02 · journalctl -u k3s-agent (에이전트) 끝 6줄 ===
|
||||
Sep 17 07:42:16 kc-lab-2 k3s[2644]: E0917 07:42:16.919786 2644 conn.go:353] "Error on socket receive" err="read tcp 127.0.0.1:10250->127.0.0.1:55428: use of closed network connection"
|
||||
Sep 17 07:42:17 kc-lab-2 k3s[2644]: E0917 07:42:17.005337 2644 conn.go:353] "Error on socket receive" err="read tcp 127.0.0.1:10250->127.0.0.1:55436: use of closed network connection"
|
||||
Sep 17 07:42:17 kc-lab-2 k3s[2644]: E0917 07:42:17.092765 2644 conn.go:353] "Error on socket receive" err="read tcp 127.0.0.1:10250->127.0.0.1:55450: use of closed network connection"
|
||||
Sep 17 07:42:24 kc-lab-2 k3s[2644]: E0917 07:42:24.627959 2644 conn.go:353] "Error on socket receive" err="read tcp 127.0.0.1:10250->127.0.0.1:40132: use of closed network connection"
|
||||
Sep 17 07:42:25 kc-lab-2 k3s[2644]: E0917 07:42:25.072360 2644 conn.go:353] "Error on socket receive" err="read tcp 127.0.0.1:10250->127.0.0.1:40148: use of closed network connection"
|
||||
Sep 17 07:42:26 kc-lab-2 k3s[2644]: E0917 07:42:26.574554 2644 conn.go:353] "Error on socket receive" err="read tcp 127.0.0.1:10250->127.0.0.1:40158: use of closed network connection"
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
=== 04 3단계 ④ 권한과 바이트 수 (값은 안 본다) ===
|
||||
ls: cannot access '/etc/letsencrypt/cloudflare.ini': No such file or directory
|
||||
wc: /etc/letsencrypt/cloudflare.ini: No such file or directory
|
||||
|
||||
=== 04 3단계 ⑤ 토큰이 살아 있고 권한 범위가 맞는지 (응답의 상태 문자열만) ===
|
||||
awk: cannot open /etc/letsencrypt/cloudflare.ini (No such file or directory)
|
||||
{"success":false,"errors":[{"code":6003,"message":"Invalid request headers","error_chain":[{"code":6111,"message":"Invalid format for Authorization header"}]}],"messages":[],"result":null}
|
||||
@@ -0,0 +1,19 @@
|
||||
=== 랩 호스트의 인증서 ===
|
||||
Saving debug log to /var/log/letsencrypt/letsencrypt.log
|
||||
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Found the following certs:
|
||||
Certificate Name: auth.hyeonworks.com
|
||||
Serial Number: 6f3e0ef4d1bb03de58130eaad1176101373
|
||||
Key Type: ECDSA
|
||||
Identifiers: auth.hyeonworks.com app1.hyeonworks.com app2.hyeonworks.com
|
||||
Expiry Date: 2026-12-03 11:29:17+00:00 (VALID: 77 days)
|
||||
Certificate Path: /etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem
|
||||
Private Key Path: /etc/letsencrypt/live/auth.hyeonworks.com/privkey.pem
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
=== renewal 설정의 authenticator (04 의 오랜 미검증) ===
|
||||
zsh:1: no matches found: /etc/letsencrypt/renewal/*.conf
|
||||
|
||||
=== 토큰이 살아 있나 (상태 문자열만) ===
|
||||
{"success":false,"errors":[{"code":6003,"message":"Invalid request headers","error_chain":[{"code":6111,"message":"Invalid format for Authorization header"}]}],"messages":[],"result":null}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
=== 04 의 오랜 미검증: authenticator 가 무엇인가 ===
|
||||
--- 문서 형태 (글로브가 sudo 앞에서 펼쳐진다)
|
||||
zsh:1: no matches found: /etc/letsencrypt/renewal/*.conf
|
||||
--- sudo 안에서 펼치게 고친 형태
|
||||
/etc/letsencrypt/renewal/auth.hyeonworks.com.conf:authenticator = dns-cloudflare
|
||||
/etc/letsencrypt/renewal/auth.hyeonworks.com.conf:dns_cloudflare_credentials = /etc/letsencrypt/cloudflare.ini
|
||||
/etc/letsencrypt/renewal/auth.hyeonworks.com.conf:server = https://acme-v02.api.letsencrypt.org/directory
|
||||
|
||||
=== 토큰 검증 (값은 안 찍는다) ===
|
||||
토큰 길이 0자
|
||||
{"success":false,"errors":[{"code":6003,"message":"Invalid request headers","error_chain":[{"code":6111,"message":"Invalid format for Authorization header"}]}],"messages":[],"result":null}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
=== 호스트 → 엣지로 인증서를 옮긴다 (값은 화면에 안 나온다) ===
|
||||
Found the following certs:
|
||||
Certificate Name: auth.hyeonworks.com
|
||||
Serial Number: 6f3e0ef4d1bb03de58130eaad1176101373
|
||||
Key Type: ECDSA
|
||||
Domains: auth.hyeonworks.com app1.hyeonworks.com app2.hyeonworks.com
|
||||
Expiry Date: 2026-12-03 11:29:17+00:00 (VALID: 77 days)
|
||||
Certificate Path: /etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem
|
||||
Private Key Path: /etc/letsencrypt/live/auth.hyeonworks.com/privkey.pem
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
=== 엣지에서 파일 권한 확인 ===
|
||||
total 4
|
||||
-rw-r--r-- 1 root root 692 Sep 3 01:47 README
|
||||
lrwxrwxrwx 1 root root 43 Sep 4 12:29 cert.pem -> ../../archive/auth.hyeonworks.com/cert3.pem
|
||||
lrwxrwxrwx 1 root root 44 Sep 4 12:29 chain.pem -> ../../archive/auth.hyeonworks.com/chain3.pem
|
||||
lrwxrwxrwx 1 root root 48 Sep 4 12:29 fullchain.pem -> ../../archive/auth.hyeonworks.com/fullchain3.pem
|
||||
lrwxrwxrwx 1 root root 46 Sep 4 12:29 privkey.pem -> ../../archive/auth.hyeonworks.com/privkey3.pem
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
=== live 아래에 무엇이 있나 ===
|
||||
README
|
||||
auth.hyeonworks.com
|
||||
|
||||
=== 04 5단계를 문서 그대로 (live/hyeonworks.com) 쳤을 때 ===
|
||||
2026/09/17 07:47:15 [emerg] 2530#2530: cannot load certificate "/etc/letsencrypt/live/hyeonworks.com/fullchain.pem": BIO_new_file() failed (SSL: error:80000002:system library::No such file or directory:calling fopen(/etc/letsencrypt/live/hyeonworks.com/fullchain.pem, r) error:10000080:BIO routines::no such file)
|
||||
nginx: configuration file /etc/nginx/nginx.conf test failed
|
||||
@@ -0,0 +1,13 @@
|
||||
=== 경로를 live/auth.hyeonworks.com 으로 고친다 ===
|
||||
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
|
||||
nginx: configuration file /etc/nginx/nginx.conf test is successful
|
||||
RELOADED
|
||||
|
||||
=== 엣지가 443 을 듣나 ===
|
||||
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=2547,fd=5),("nginx",pid=1065,fd=5))
|
||||
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=2547,fd=10),("nginx",pid=1065,fd=10))
|
||||
|
||||
=== 밖에서 ===
|
||||
https://auth.hyeonworks.com/realms/master 400
|
||||
https://app1.hyeonworks.com/ 400
|
||||
http://auth.hyeonworks.com/ 301
|
||||
@@ -0,0 +1,22 @@
|
||||
=== 400 의 본문과 머리 ===
|
||||
HTTP/2 400
|
||||
server: nginx/1.22.1
|
||||
date: Thu, 17 Sep 2026 07:47:28 GMT
|
||||
content-type: text/plain; charset=utf-8
|
||||
|
||||
400 Bad Request: malformed Host header
|
||||
=== TLS 협상 자체는 되나 ===
|
||||
http_version=2 ssl_verify=0 code=400
|
||||
--- HTTP/1.1 로 강제하면
|
||||
code=400
|
||||
|
||||
=== 엣지 로그 ===
|
||||
192.168.122.1 - - [17/Sep/2026:07:47:28 +0000] "GET /realms/master HTTP/2.0" 400 38 "-" "curl/8.5.0"
|
||||
192.168.122.1 - - [17/Sep/2026:07:47:28 +0000] "GET /realms/master HTTP/2.0" 400 38 "-" "curl/8.5.0"
|
||||
192.168.122.1 - - [17/Sep/2026:07:47:28 +0000] "GET /realms/master HTTP/1.1" 400 49 "-" "curl/8.5.0"
|
||||
---
|
||||
2026/09/17 04:27:39 [notice] 1065#1065: using inherited sockets from "5;6;"
|
||||
2026/09/17 05:39:03 [error] 1106#1106: *31 connect() failed (113: No route to host) while connecting to upstream, client: 192.168.122.1, server: _, request: "GET /realms/master HTTP/1.1", upstream: "http://192.168.122.12:80/realms/master", host: "auth.hyeonworks.com"
|
||||
2026/09/17 05:40:37 [error] 1106#1106: *38 connect() failed (113: No route to host) while connecting to upstream, client: 192.168.122.1, server: _, request: "GET /realms/master HTTP/1.1", upstream: "http://192.168.122.12:80/realms/master", host: "auth.hyeonworks.com"
|
||||
2026/09/17 05:46:21 [error] 1106#1106: *47 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.122.1, server: _, request: "GET /realms/master HTTP/1.1", upstream: "http://192.168.122.12:80/realms/master", host: "auth.hyeonworks.com"
|
||||
2026/09/17 05:48:45 [error] 1106#1106: *54 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.122.1, server: _, request: "GET /realms/master HTTP/1.1", upstream: "http://192.168.122.12:80/realms/master", host: "auth.hyeonworks.com"
|
||||
@@ -0,0 +1,15 @@
|
||||
=== 층 ① 엣지에서 Traefik 에 직접 (Host 헤더를 손으로) ===
|
||||
200
|
||||
|
||||
=== 층 ② 엣지의 443 에 엣지 자신이 (localhost) ===
|
||||
400
|
||||
|
||||
=== 층 ② 엣지의 80 (리다이렉트 확인) ===
|
||||
301 -> https://\auth.hyeonworks.com\/realms/master
|
||||
|
||||
=== 지금 깔린 443 블록의 Host 관련 줄 ===
|
||||
7: listen 80 default_server;
|
||||
13: listen 443 ssl http2 default_server;
|
||||
21: proxy_pass http://k3s_traefik;
|
||||
23: proxy_set_header Host \$host;
|
||||
24: proxy_set_header X-Forwarded-Host \$host;
|
||||
@@ -0,0 +1,11 @@
|
||||
23: proxy_set_header Host $host;
|
||||
24: proxy_set_header X-Forwarded-Host $host;
|
||||
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
|
||||
nginx: configuration file /etc/nginx/nginx.conf test is successful
|
||||
RELOADED
|
||||
|
||||
=== 밖에서 ===
|
||||
https://auth.hyeonworks.com/realms/master 400
|
||||
https://app1.hyeonworks.com/ 200
|
||||
https://app2.hyeonworks.com/ 302
|
||||
http://auth.hyeonworks.com/ 301
|
||||
@@ -0,0 +1,23 @@
|
||||
=== auth 의 400 본문 ===
|
||||
HTTP/2 200
|
||||
server: nginx/1.22.1
|
||||
date: Thu, 17 Sep 2026 07:47:56 GMT
|
||||
content-type: application/json;charset=UTF-8
|
||||
content-length: 602
|
||||
cache-control: no-cache
|
||||
referrer-policy: no-referrer
|
||||
strict-transport-security: max-age=31536000; includeSubDomains
|
||||
x-content-type-options: nosniff
|
||||
x-frame-options: SAMEORIGIN
|
||||
x-robots-tag: none
|
||||
|
||||
|
||||
=== 다른 경로도 400 인가 ===
|
||||
/ 302
|
||||
/realms/master 200
|
||||
/realms/keycloak-patterns 200
|
||||
/admin/ 302
|
||||
|
||||
=== 엣지에서 Traefik 에 Host 를 주고 직접 (대조군) ===
|
||||
http 200
|
||||
http+XFP-https 200
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
=== certbot 의 진짜 종료 코드 ===
|
||||
certbot exit=0
|
||||
11
|
||||
|
||||
=== 인증서만 옮기면 따라오지 않는 둘을 마저 옮긴다 ===
|
||||
ls: cannot access '/etc/letsencrypt/accounts/*/*/*': No such file or directory
|
||||
81 /etc/letsencrypt/cloudflare.ini
|
||||
@@ -49,3 +49,11 @@
|
||||
43-dominfo-vs-config.txt 같은 순간의 dominfo(5242880) 와 dumpxml --inactive(4194304). 바꾼 것이 들어갔는지는 --inactive 가 답한다
|
||||
44-host-prep-checks.txt 00 의 확인 명령 전부 — vmx, kvm 모듈 셋, 저장소, virsh 12.7.0, libvirtd.socket, libvirt 그룹, default 네트워크, sudo 없는 virsh list
|
||||
45-libvirt-uri-mechanism.txt rc 파일은 비대화형 ssh 에서 안 읽힌다. 그런데도 되는 까닭은 ~/.config/libvirt/libvirt.conf 의 uri_default
|
||||
48 cloud-init 의 packages 에 certbot 이 없다는 것 (게스트 user-data + 저장소 템플릿)
|
||||
49 01 의 확인 ①②③ 을 다시 친 결과
|
||||
50 08 의 철거 전 네 값과, 인증서가 어느 기계에 있는지
|
||||
51 (철거는 실행하지 않았다 — 파일 없음)
|
||||
52 09 의 lsmod·코어 수·libvirtd.socket, 01 의 vol-info·net-dumpxml·cloud-init status
|
||||
53 sudo 가 열린 뒤 doc 03 의 미검증 셋 — 배포 파일 상태·guest_input 중복·masquerade 찾기
|
||||
54 출발지를 덮는 것이 Tailscale 이라는 근거 (ts-forward MARK → ts-postrouting MASQUERADE)
|
||||
55 doc 02 의 API 서버 SAN 과 두 journalctl
|
||||
|
||||
Reference in New Issue
Block a user