# 03 — 호스트 nginx 라우팅 ## 이 단계가 끝나면 밖에서 보낸 요청이 **nginx → Traefik → 파드**로 닿는다. 아직 TLS 는 없다. ## 전제 [02](../02-k3s/) 가 끝나 두 노드가 `Ready`. ## 왜 프록시가 두 겹인가 nginx 와 Traefik 이 하는 일이 다르다. | | 맡는 것 | |---|---| | 호스트 nginx | 바깥세상과의 접점 — TLS 종단 · 인증서 · `X-Forwarded-*` | | Traefik | 클러스터 안의 동적 라우팅 — Ingress 를 보고 서비스를 고른다 | **이 2홉이 운영 구조와 같다는 것이 이 배치의 핵심**이고, 동시에 B-4 의 헤더 실험이 성립하는 이유다. 1홉을 가정하고 쓴 계약이 2홉에서도 유효한지를 재려면 두 겹이 있어야 한다. --- ## 1. 설정을 쓴다 원본은 [`deploy/lab/host/nginx-keycloak-lab.conf`](../../../deploy/lab/host/nginx-keycloak-lab.conf). **하기** ```bash 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](../04-tls/) 에서 만든다. 그전까지는 443 > 블록을 주석 처리하고 80 만 `proxy_pass` 로 두면 이 단계를 먼저 확인할 수 있다. Arch 는 `sites-available` 관례가 없다. 직접 만들고 `nginx.conf` 의 `http` 블록 안에서 include 한다. ```bash 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. 문법을 보고 적용한다 **하기** ```bash sudo nginx -t && sudo systemctl reload nginx ``` **어디를 봐야 하는가** — `nginx -t` 가 내놓는 **마지막 줄**이다. `syntax is ok` 와 `test is successful` 두 마디가 다 나와야 통과다. 앞에 나오는 `[warn]` 줄(예: `types_hash_max_size`)은 통과를 막지 않는다 — 04 에서 이 경고를 실패로 오독하는 일이 실제로 벌어지므로, 여기서 **경고와 오류를 구분하는 눈**을 들여 둔다. 실패면 `[emerg]` 줄에 파일과 줄 번호가 찍힌다. **이 결과가 의미하는 것** — 통과했으면 `&&` 뒤의 reload 가 이어서 돌고, `systemctl reload` 는 아무 말 없이 끝난다(무소식이 좋은 소식이다). 실패했으면 `&&` 가 reload 를 **막아 준 것**이고, 지금 돌고 있는 nginx 는 옛 설정 그대로 멀쩡하다. 설정이 깨진 상태에서 reload 하면 새 워커를 못 띄운다 — `-t` 를 먼저 통과시키고 그때만 reload 하는 이유다. reload 가 정말 반영됐는지는 워커가 갈렸는지로 본다. 같은 판정 방법을 04 에서 인증서 갱신에 그대로 쓴다. ```bash systemctl status nginx --no-pager | head -20 ``` **어디를 봐야 하는가** — `Active:` 줄이 `active (running)` 인가, 그리고 그 아래 프로세스 트리에 `nginx: master process` 하나와 `nginx: worker process` 여럿이 붙어 있는가. 워커 줄의 **PID** 를 눈에 담아 둔다. **이 결과가 의미하는 것** — reload 는 마스터를 그대로 두고 **워커만** 갈아 끼운다. 그래서 reload 전후로 워커 PID 가 바뀌면 새 설정이 실제로 적용된 것이고, 안 바뀌었으면 `-t` 는 통과했는데 reload 가 안 간 것이다. 04 에서 인증서 갱신이 서빙까지 닿았는지를 정확히 이 방법으로 판정한다. ## 3. 층별로 확인한다 — 아래에서 위로 한 번에 밖에서 치지 말고, **가까운 층부터** 본다. 어디서 끊겼는지가 바로 나온다. > **`curl` 을 두 형태로 쓴다.** 처음 볼 때는 `-I`(헤더까지 읽는 형태)로 > **응답을 눈으로 읽고**, 같은 것을 여러 번 재거나 두 노드를 나란히 비교할 > 때만 `-w '%{http_code}'`(값만 뽑는 형태)로 바꾼다. 값만 뽑는 형태는 > 골라 놓은 한 칸 말고는 전부 버리므로, **무엇이 잘못됐는지 모르는 상태** > 에서는 쓸 것이 못 된다. **확인 ①** Traefik 이 듣고 있나 (nginx 를 건너뛴다) ```bash curl -I http://192.168.122.11 ``` **형태** (이 실험대에서 캡처해 두지 않았다 — 봐야 할 줄만) ``` HTTP/1.1 404 Not Found ... ``` **어디를 봐야 하는가** — 첫 줄의 상태 줄 하나. 그리고 그 앞에 **아무 오류도 없이** 헤더가 나왔다는 사실. `curl: (7) Failed to connect` 이면 응답 자체가 없는 것이라 상태 코드를 볼 일도 없다. **이 결과가 의미하는 것** — **`404` 가 성공 신호다.** 게스트의 80 을 누가 듣고 있고(Traefik), 그가 요청을 받아 「매칭되는 Ingress 규칙이 없다」고 답한 것이다. 이 층은 통과. `502` 면 Traefik 은 떴는데 그 뒤 백엔드가 없는 것이고, 연결 거부·타임아웃이면 Traefik 이 안 떴거나 게스트가 죽은 것이라 **02 로 돌아간다.** 두 노드가 같은지 볼 때는 값만 뽑는 형태가 낫다. 두 줄을 나란히 놓고 눈으로 비교하는 것이 목적이기 때문이다. ```bash curl -s -o /dev/null -w '%{http_code}\n' http://192.168.122.11 curl -s -o /dev/null -w '%{http_code}\n' http://192.168.122.12 ``` **실측** — `.11` 에서 잰 값이다. ``` 404 ``` **어디를 봐야 하는가** — 두 줄이 **같은 코드**인가. **이 결과가 의미하는 것** — 둘이 같으면 nginx 가 어느 쪽으로 보내도 같은 결과가 나온다. 한쪽만 다르면 upstream 두 개 중 하나가 죽은 것이고, 그 상태에서는 **요청의 절반만 실패**해서 「가끔 안 된다」로 보인다. **확인 ②** nginx 가 80 에서 리다이렉트하나 ```bash curl -I http://auth.hyeonworks.com ``` **형태** (봐야 할 두 줄만) ``` HTTP/1.1 301 Moved Permanently Location: https://auth.hyeonworks.com/ ... ``` **어디를 봐야 하는가** — 상태 줄과 **`Location:` 헤더 한 줄**. `Location` 이 `https://` 로 시작하고 원래 호스트명을 그대로 들고 있는가. `$host` 대신 설정에 이름을 박아 두면 여기서 엉뚱한 호스트가 나온다. **이 결과가 의미하는 것** — 301 이 나왔다는 것은 **바깥 요청이 호스트 nginx 까지 닿았다**는 뜻이다(①은 게스트에 직접 친 것이므로 nginx 를 안 거쳤다). DNS·방화벽·80 리스너가 전부 살아 있다. 응답이 아예 없으면 nginx 가 안 떴거나 80 이 막힌 것이다. 리다이렉트를 따라가 끝까지 보려면 `-L` 을 붙인다. 값을 반복해서 잴 때의 형태는 이것이고, 아래가 이 실험대의 실측이다. ```bash curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://auth.hyeonworks.com ``` ``` 301 https://auth.hyeonworks.com/ ``` **확인 ③** 끝까지 닿나 (TLS 이후) ```bash curl -s -o /dev/null -w '%{http_code}\n' https://auth.hyeonworks.com/realms/master ``` ``` 200 ``` **어디를 봐야 하는가** — 코드 한 칸. 여기서만 값만 뽑는 형태를 바로 쓰는 이유는, 이 200 이 **04·05 에서 매번 같은 명령으로 다시 잴 기준값**이기 때문이다. 처음 한 번은 `curl -I https://auth.hyeonworks.com/realms/master` 로 헤더까지 보고, 그다음부터 이 형태로 줄인다. **이 결과가 의미하는 것** — `200` 이면 nginx → Traefik → 파드까지 2홉이 다 이어졌다. `502` 는 nginx 는 살아 있는데 upstream 을 못 잡은 것(4번의 로그를 본다), `curl: (60)` 같은 인증서 오류는 아직 04 를 안 한 것이다. TLS 단계에서 막히면 코드만 보지 말고 `curl -v` 로 협상 과정을 읽는다 — 04 에서 그렇게 한다. ## 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](../04-tls/) · 아래 로그 | **로그를 볼 때** — 실무자가 치는 형태다. ```bash journalctl -u nginx -p err -n 5 # 최근 에러만 journalctl -u nginx -f # 지금 벌어지는 것 ``` **어디를 봐야 하는가** — 각 줄의 **괄호 안 errno**(`113`·`111`)와 그 뒤의 `upstream: "http://192.168.122.1x:80/..."` 부분. 어느 upstream 이 문제인지가 거기 적혀 있다. 그리고 **타임스탬프** — 방금 친 요청 시각과 안 맞으면 지금 보고 있는 것은 옛 사고다. **이 결과가 의미하는 것** — `-p err` 로 걸러도 아무것도 안 나오면 nginx 는 정상이고 문제는 더 위(Traefik·파드)에 있다. 두 번째 명령은 **띄워 놓은 채로 다른 창에서 요청을 치는** 용도다 — 요청과 로그 줄을 눈으로 짝지으면 「이 요청이 어느 upstream 으로 갔나」가 바로 보인다. 끝내려면 Ctrl+C. **★ nginx 에러 로그는 2048바이트에서 잘린다.** 긴 URL 이 끝에서 단어 중간에 끊겨 보이면 그것이다. 되찾으려면 **access 로그를 본다** — 거기엔 제한이 없다. ```bash grep oauth2/callback /var/log/nginx/access.log | tail -1 ``` **어디를 봐야 하는가** — 그 한 줄의 **끝**이다. 요청 URL 이 `"` 로 제대로 닫혀 있으면 온전한 줄이고, 단어 중간에서 멈춰 있으면 잘린 것이다. 길이가 궁금하면 세어 본다. ```bash grep oauth2/callback /var/log/nginx/access.log | tail -1 | wc -c ``` **이 결과가 의미하는 것** — error 로그와 access 로그의 같은 요청 길이가 다르면 **error 쪽이 잘린 것**이지 요청이 잘린 것이 아니다. 이 실험대에서 B-7 의 502 원인이 error 로그에 있었는데 잘려 있었고, access 로그에는 3492자로 온전히 남아 있었다. 잘린 문자열을 놓고 원인을 추측하면 없는 문제를 좇게 된다.