The experiment documents record what was found. These record what to type to
reproduce it, in folders per stage.
Two kinds of command are kept apart. 하기/확인 is what somebody actually types
at a terminal — short, one at a time. 근거를 재려면 is the long measuring form
this lab used to put evidence in a document, marked as not needed day to day.
The same split applies to curl: -I to look once, -w '%{http_code}' only when
comparing across repetitions.
No placeholders. Where a value is needed the command that produces it is
given, and secrets are checked by length rather than printed:
TOKEN=$(ssh kc-lab-1 'sudo cat /var/lib/rancher/k3s/server/node-token')
echo "${#TOKEN} 자"
Stage 05 verifies resources in layers, because a Secret existing and a pod
having received it are different facts: keys, then length, then the value
inside the container, then which env var came from which Secret. Same for
workloads — Deployment to ReplicaSet to Pod, with the seven ReplicaSets this
cluster actually carries as the worked example.
Two commands were wrong and re-running them caught it. kubectl get endpoints
prints a deprecation warning on v1.33+, so the guide uses describe svc and
EndpointSlice. And the Keycloak image has no curl, so reading metrics from
inside the container fails with exit 127 — the guide asks Prometheus instead,
or runs a throwaway curl pod.
Read-only checks were executed against the running lab and their output is
quoted verbatim. Creating commands could not be re-run without destroying the
lab, so they are the ones used at build time; the README says which is which.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
03 — 호스트 nginx 라우팅
이 단계가 끝나면
밖에서 보낸 요청이 nginx → Traefik → 파드로 닿는다. 아직 TLS 는 없다.
전제
02 가 끝나 두 노드가 Ready.
왜 프록시가 두 겹인가
nginx 와 Traefik 이 하는 일이 다르다.
| 맡는 것 | |
|---|---|
| 호스트 nginx | 바깥세상과의 접점 — TLS 종단 · 인증서 · X-Forwarded-* |
| Traefik | 클러스터 안의 동적 라우팅 — Ingress 를 보고 서비스를 고른다 |
이 2홉이 운영 구조와 같다는 것이 이 배치의 핵심이고, 동시에 B-4 의 헤더 실험이 성립하는 이유다. 1홉을 가정하고 쓴 계약이 2홉에서도 유효한지를 재려면 두 겹이 있어야 한다.
1. 설정을 쓴다
원본은 deploy/lab/host/nginx-keycloak-lab.conf.
하기
sudo tee /etc/nginx/sites-available/keycloak-lab > /dev/null <<'EOF'
upstream k3s_traefik {
server 192.168.122.11:80;
server 192.168.122.12:80;
}
server {
listen 80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl default_server;
http2 on;
server_name _;
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 / {
proxy_pass http://k3s_traefik;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
EOF
인증서 경로는 아직 없다. 04 에서 만든다. 그전까지는 443 블록을 주석 처리하고 80 만
proxy_pass로 두면 이 단계를 먼저 확인할 수 있다.
Arch 는 sites-available 관례가 없다. 직접 만들고 nginx.conf 의 http 블록
안에서 include 한다.
sudo mkdir -p /etc/nginx/sites-{available,enabled}
sudo ln -s /etc/nginx/sites-available/keycloak-lab /etc/nginx/sites-enabled/
# nginx.conf 의 http { } 안에: include /etc/nginx/sites-enabled/*;
2. 문법을 보고 적용한다
하기
sudo nginx -t && sudo systemctl reload nginx
&& 가 중요하다. 설정이 깨진 상태에서 reload 하면 nginx 가 새 워커를
띄우지 못한다. -t 를 먼저 통과시키고 그때만 reload 한다.
3. 층별로 확인한다 — 아래에서 위로
한 번에 밖에서 치지 말고, 가까운 층부터 본다. 어디서 끊겼는지가 바로 나온다.
확인 ① Traefik 이 듣고 있나 (nginx 를 건너뛴다)
curl -s -o /dev/null -w '%{http_code}\n' http://192.168.122.11
404
404 가 성공 신호다. Traefik 까지 닿았는데 매칭되는 Ingress 규칙이 없다는
뜻이다. 502 나 연결 거부면 그 아래에서 끊긴 것이다.
확인 ② nginx 가 80 에서 리다이렉트하나
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://auth.hyeonworks.com
301 https://auth.hyeonworks.com/
확인 ③ 끝까지 닿나 (TLS 이후)
curl -s -o /dev/null -w '%{http_code}\n' https://auth.hyeonworks.com/realms/master
200
4. upstream 이 둘인 이유
upstream k3s_traefik {
server 192.168.122.11:80;
server 192.168.122.12:80;
}
두 노드 모두 Traefik 이 뜨므로 어느 쪽으로 보내도 된다. nginx 는 기본 라운드로빈으로 번갈아 보내고, 한쪽이 죽으면 자동으로 뺀다.
그 「빼는」 동작이 로그에 이렇게 남는다.
connect() failed (113: No route to host) ← 호스트에 못 닿는다
connect() failed (111: Connection refused) ← 포트에 아무도 없다
no live upstreams ← 둘 다 죽었다고 판단
113 과 111 은 대응이 다르다. 113 은 네트워크, 111 은 프로세스다. A-4 에서 노드를 잃었을 때 이 세 줄이 1분 안에 순서대로 나왔다.
막히면
| 증상 | 어디서 끊겼나 | 확인 |
|---|---|---|
| ① 이 연결 거부 | Traefik 이 안 떴거나 게스트가 죽음 | kubectl get pods -n kube-system |
① 이 502 |
Traefik 은 떴는데 백엔드가 없음 | Ingress 확인 |
| ② 가 응답 없음 | nginx 가 안 떴거나 방화벽 | systemctl status nginx |
③ 이 502 |
인증서 문제 또는 upstream 다운 | 04 · 아래 로그 |
로그를 볼 때 — 실무자가 치는 형태다.
journalctl -u nginx -p err -n 5 # 최근 에러만
journalctl -u nginx -f # 지금 벌어지는 것
★ nginx 에러 로그는 2048바이트에서 잘린다. 긴 URL 이 끝에서 단어 중간에 끊겨 보이면 그것이다. 되찾으려면 access 로그를 본다 — 거기엔 제한이 없다.
grep oauth2/callback /var/log/nginx/access.log | tail -1
이 실험대에서 B-7 의 502 원인이 error 로그에 있었는데 잘려 있었고, access 로그에는 3492자로 온전히 남아 있었다.