chore: 실행 환경 구성 문서 추가 및 수정

This commit is contained in:
DongHyeonka
2026-09-10 15:55:36 +09:00
parent 6f6ab86345
commit 9465582b5d
17 changed files with 1489 additions and 142 deletions
+160 -1
View File
@@ -1661,6 +1661,75 @@ sudo certbot certificates # 발급된 인증서와 도메인 목록
sudo certbot renew --dry-run # 갱신이 실제로 되는지 예행연습
```
### DNS-01 은 언제 쓰는가 — 네 가지 경우
**DNS-01 이 HTTP-01 보다 좋은 방식이 아니다.** HTTP-01 이 못 쓰이는 자리에
쓰는 것이다. 공개 웹서버 한 대라면 HTTP-01 이 옳다 — 토큰도 DNS 연동도
필요 없고, DNS 공급자를 옮겨도 안 깨진다.
**① 와일드카드가 필요할 때** — 선택이 아니라 규칙이다.
`*.example.com` 은 **그 도메인 아래 모든 이름**에 대한 권한이다. 호스트
한 대에 파일을 놓는 것은 **그 이름 하나를 통제한다**는 증명밖에 안 된다.
DNS 존의 TXT 레코드를 고칠 수 있다는 것은 **도메인 전체를 통제한다**는
증명이다. 증명의 급이 다르기 때문에 ACME 명세가 와일드카드를 DNS-01 로만
허용한다.
**② 서버가 공개 인터넷에서 안 보일 때** — 내부망, VPN 뒤, 사설 IP,
CGNAT 뒤. **이 실험대가 여기다.** tailnet 주소 `100.83.212.4` 는
`100.64.0.0/10`(CGNAT 예약 대역)이라 공개 인터넷에서 라우팅 자체가 안 된다.
방화벽을 여는 문제가 아니다 — **그 주소는 인터넷에 존재하지 않는다.**
Let's Encrypt 를 tailnet 에 초대할 방법도 없다.
**③ 80 포트를 못 쓸 때** — 가정용 회선은 ISP 가 80 을 막는 경우가 흔하다.
이미 다른 서비스가 점유한 경우도 같다. 443 이 살아 있으면 TLS-ALPN-01 도
대안이 된다.
**④ 인증서를 쓸 기계와 발급받는 기계가 다를 때** — 실무에서 이 이유가 가장
크다.
| 상황 | HTTP-01 이 곤란한 이유 |
|---|---|
| 로드밸런서 뒤 N 대 | 검증 요청이 **어느 대로 갈지 모른다.** 전부가 같은 토큰에 답해야 하니 공유 스토리지나 challenge 전용 라우팅이 필요하다 |
| CDN 뒤 | 오리진이 직접 응답할 수 없다 |
| CI 에서 발급해 배포 | **서버가 아직 없어도** 발급해야 한다 |
| 쿠버네티스 cert-manager | Ingress 가 여럿이거나 클러스터가 내부망이면 solver 를 DNS-01 로 둔다 |
DNS-01 은 **어디서 돌리든 상관없다.** 인증서를 쓸 기계와 무관하게 발급된다.
**값으로 치르는 것**
| | 내용 |
|---|---|
| **API 토큰이 서버에 있어야 한다** | 유출되면 **도메인 전체의 DNS 를 조작**당한다. 인증서 한 장보다 피해가 크다 — MX 를 바꿔 메일을 가로챌 수도 있다. 그래서 토큰은 **존 하나 + DNS:Edit** 으로 좁힌다. 계정 전역 API Key 를 쓰지 않는다 |
| 공급자에 묶인다 | 플러그인이 공급자마다 다르다. DNS 를 옮기면 재설정 |
| 느리다 | TXT 레코드가 퍼질 때까지 기다려야 한다. certbot 기본 대기 10초, 느린 공급자는 더 준다 |
| 공급자가 API 를 안 주면 못 쓴다 | |
**한 줄 판단**
```
와일드카드가 필요한가? → 예: DNS-01 (다른 선택지 없음)
Let's Encrypt 가 내 서버에 HTTP 로 닿는가? → 예: HTTP-01 아니오: DNS-01
```
> **이 실험대의 문서 두 개가 어긋나 있다.** 위 「HTTP-01 vs DNS-01」이
> 「둘 다 DNS-01 을 가리킨다」로 결론냈는데,
> [`docs/guides/04-tls/README.md`](guides/04-tls/README.md) 는
> `certbot certonly --webroot`(HTTP-01)로 적혀 있고 전제도 「공개 DNS 에
> 이름 셋이 이 호스트를 가리켜야 한다」이다. 지금 DNS 는 tailnet 주소를
> 가리키므로 **그 전제는 성립하지 않는다.** 어느 쪽이 실제인지는 아래로
> 확인한다.
**확인**
```bash
sudo grep -H authenticator /etc/letsencrypt/renewal/*.conf # webroot/standalone = HTTP-01
certbot plugins | grep -E '^\*' # 쓸 수 있는 검증 방식
sudo certbot renew --dry-run # 갱신이 실제로 되는가
dig +short auth.hyeonworks.com # LE 가 올 수 있는 주소인가
```
### `fullchain.pem` / `privkey.pem` / `cert.pem` / `chain.pem`
**무엇인가** — certbot이 만드는 네 파일.
@@ -1739,7 +1808,9 @@ chmod 600 ~/.kube/config
**이 명령은 호스트(test-server)에서 실행한다.** 게스트 안에서 실행하면
`ssh kc-lab-1` 부분이 자기 자신에게 다시 접속하는 꼴이 되고, 애초에
게스트에서는 `sudo k3s kubectl`을 쓰면 되므로 kubeconfig가 필요 없다.
**server 게스트**에서는 `sudo kubectl`이 `/etc/rancher/k3s/k3s.yaml`을
자동으로 집으므로 kubeconfig가 따로 필요 없다. **agent 게스트는 다르다** —
아래 「agent 노드에는 kubeconfig가 없다」를 본다.
**리다이렉션은 셸이 명령보다 먼저 처리한다** — 자주 걸리는 함정이다.
@@ -1761,6 +1832,94 @@ sudo cat 원본 > ~/.kube/config
echo 내용 | sudo tee /root/전용경로 > /dev/null
```
### agent 노드에는 kubeconfig가 없다 — `localhost:8080` 오류
**무엇인가** — kubeconfig는 kubectl에게 **① 어느 API 서버로 ② 어떤 CA로
검증하고 ③ 누구 자격으로** 붙을지 알려주는 파일이다. kubectl은 이 셋을
스스로 알지 못한다.
**왜 여기 나오나** — agent 노드(`kc-lab-2`)에도 `kubectl` **명령은 있다.**
설치 스크립트가 심볼릭 링크를 만들기 때문이다.
```
/usr/local/bin/kubectl -> k3s
```
k3s는 단일 바이너리라 자기가 어떤 이름으로 불렸는지(`argv[0]`)를 보고
동작을 바꾼다. **명령이 있다는 것과 붙을 곳이 있다는 것은 다르다.**
**없으면 무슨 일이 생기나** — agent에서 `sudo kubectl get pods -A`를 치면
이렇게 끝난다.
```
Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused
```
kubectl이 kubeconfig를 찾는 순서는 `--kubeconfig` 플래그 → `$KUBECONFIG`
→ `~/.kube/config`이고, k3s 내장 kubectl은 여기에
`/etc/rancher/k3s/k3s.yaml`을 하나 더 본다. **넷 다 없으면 오류를 내지 않고**
client-go에 하드코딩된 기본값 `http://localhost:8080`으로 넘어간다.
쿠버네티스 1.20 이전 API 서버가 평문으로 열던 레거시 포트인데 지금은
아무도 열지 않는다.
> **`localhost:8080`이 보이면 네트워크 문제가 아니라 설정이 없다는 뜻이다.**
> 이 주소는 어디에도 적혀 있지 않다 — kubectl이 아무것도 못 찾았을 때만
> 나온다. 방화벽이나 k3s를 의심하기 전에 kubeconfig부터 본다.
> **오해 주의 — 「워커라서 파드가 안 보인다」가 아니다.** kubectl은 그냥 HTTP
> 클라이언트라 어디서 실행하든 상관없다(노트북에서도 된다). 필요한 것은
> 주소와 자격증명뿐이고, kc-lab-1의 `k3s.yaml`을 kc-lab-2로 복사해 넣으면
> `get pods -A`는 워커에서도 전부 나온다. 노드 역할이 시야를 가리는 것이
> 아니라 **자격증명 파일이 없을 뿐이다.** 그래서 실패가 「권한 없음(403)」이
> 아니라 「설정 없음(localhost:8080)」으로 나타난다. agent가 가진
> `system:node:kc-lab-2` 자격증명은 kubelet 전용이라 kubectl이 읽는 경로에
> 애초에 없다. 그렇다고 복사해 넣으면 안 되는 이유는 바로 아래에 있다.
**왜 agent에는 주지 않나** — 역할이 다르다.
| | server (`kc-lab-1`) | agent (`kc-lab-2`) |
|---|---|---|
| 도는 것 | API 서버 · 스케줄러 · controller-manager · SQLite | kubelet · kube-proxy · containerd · flannel |
| 6443 LISTEN | O | **X** |
| `/etc/rancher/k3s/k3s.yaml` | 있음 (admin 자격증명, `0600 root`) | **없음** |
| 결정하는 것 | 클러스터 상태 | 없음. 시킨 것을 실행할 뿐 |
agent도 API 서버와 통신은 한다. `127.0.0.1:6444`에 k3s-agent가 자체
로드밸런서를 열고 그걸 통해 server의 6443으로 넘긴다. 하지만 **자격증명의
급이 다르다.**
```
subject=O = system:nodes, CN = system:node:kc-lab-2
```
이 신원은 Node authorizer와 NodeRestriction admission 플러그인이 **자기
노드에 배정된 객체만** 다루도록 제한한다. `get pods -A`(전체 네임스페이스)는
처음부터 권한 밖이다. 워커 한 대가 털려도 클러스터 전체가 털리지 않게 하려는
설계이므로, **편하다고 `k3s.yaml`을 agent로 복사해 넣지 않는다.**
**어디서 치나** — 셋 중 하나다.
```bash
# ① server 게스트에서
ssh kc-lab-1 'sudo kubectl get pods -A'
# ② 호스트(test-server)에서 — 위 「kubeconfig의 127.0.0.1 문제」의 복사를 먼저 한다
export KUBECONFIG=~/.kube/config
kubectl get pods -A
# ③ agent가 클러스터에 붙었는지만 보고 싶을 때 (kubectl 필요 없다)
ssh kc-lab-2 'systemctl is-active k3s-agent'
```
**확인**
```bash
ssh kc-lab-2 'ls -l /usr/local/bin/kubectl' # -> k3s 심볼릭 링크. 명령은 있다
ssh kc-lab-2 'sudo ls /etc/rancher/k3s/ 2>&1' # No such file — 이게 정상이다
ssh kc-lab-1 'sudo ls -l /etc/rancher/k3s/k3s.yaml' # 여기에만 있다
ssh kc-lab-2 'sudo openssl x509 -in /var/lib/rancher/k3s/agent/client-kubelet.crt -noout -subject'
```
### Traefik (k3s 기본 ingress)
**무엇인가** — k3s가 기본으로 배포하는 ingress 컨트롤러.