온프레미스에서 잘 돌던 같은 매니페스트를 AKS 에 올렸는데 Ingress 로 들어가는 HTTP 요청이 전부 timeout 되는 상황이다. kubectl get svc -n ingress-nginx 에는 EXTERNAL-IP 가 정상적으로 찍히고 파드도 Running 인데, 공인 IP 로 curl 을 때리면 응답이 없다. 404 조차 오지 않는다는 점이 중요하다 — 404 가 온다면 네트워크는 뚫린 것이고 라우팅 규칙 문제이며, timeout 이면 패킷이 백엔드까지 도달하지 못한 것이다.
진단이 어긋나는 가장 큰 이유는 NSG 가 평가되는 지점을 잘못 잡기 때문이다.
클라이언트(공인 IP)
→ Azure Standard LB (프런트엔드 공인 IP)
→ 노드 NIC (사설 IP 10.224.x.x) ← NSG 는 여기서 평가된다
→ ingress-nginx 파드
두 가지가 따라온다.
k8s-azure-lb_allow_IPv4_*)이 그런 상태로 남아 있으면 있으나 마나 한 규칙이 되며, 실제 허용은 다른 규칙이 해 줘야 한다.AzureLoadBalancer 서비스 태그를 허용하는 규칙만으로는 사용자 트래픽이 풀리지 않는다. 그 태그는 상태 프로브용이다. 사용자 트래픽을 열려면 클라이언트 IP 대역을 소스로 하는 규칙이 따로 필요하다.# 내 공인 IP 확인
curl -s https://api.ipify.org; echo
# 그 IP 만 80/443 허용
az network nsg rule create \
-g ${MC_RG} --nsg-name ${NODE_NSG} -n allow-client-http-https \
--priority 310 --direction Inbound --access Allow --protocol Tcp \
--source-address-prefixes ${CLIENT_IP}/32 \
--source-port-ranges '*' \
--destination-address-prefixes '*' \
--destination-port-ranges 80 443
목적지는 * 로 둔다. 노드 IP 가 스케일 이벤트마다 바뀌므로 고정할 수 없다.
externalTrafficPolicy: Local 이면 LB 는 별도의 healthCheckNodePort 로 노드 상태를 확인한다. 이 포트가 응답하지 않으면 해당 노드는 Unhealthy 로 빠지고, 모든 노드가 Unhealthy 가 되면 LB 는 트래픽을 아예 흘리지 않는다 — 증상은 정확히 timeout 이다.
kubectl -n ingress-nginx describe svc ingress-nginx-controller | egrep 'Traffic Policy|HealthCheck NodePort'
# 노드에서 프로브 포트가 응답하는지 직접 확인
curl -v --connect-timeout 2 http://${NODE_IP}:${HEALTHCHECK_NODEPORT}/healthz
# Azure 쪽 프로브 정의 확인
az network lb probe list -g ${MC_RG} --lb-name kubernetes -o table
az network lb rule list -g ${MC_RG} --lb-name kubernetes -o table
Local 정책은 클라이언트 원본 IP 를 파드까지 보존한다는 이점이 있지만, 프로브 의존성이 늘어 장애 지점이 하나 더 생긴다. 원본 IP 보존이 꼭 필요한 것이 아니라면 Cluster 로 두는 편이 단순하다.
helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
-n ingress-nginx --create-namespace \
--set controller.service.type=LoadBalancer \
--set controller.service.externalTrafficPolicy=Cluster \
--set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz
azure-load-balancer-health-probe-request-path 주석은 프로브가 TCP 가 아니라 HTTP 경로를 보게 만든다. ingress-nginx 는 /healthz 를 제공하므로 이 값이 잘 맞는다.
계층을 하나씩 좁힌다. 각 단계에서 어디까지 갔는지를 기록해 두면 다음 단계가 명확해진다.
# 1. 컨트롤러 파드가 실제로 노드에서 듣고 있는가 (클러스터 내부)
kubectl -n ingress-nginx get pod -o wide
kubectl run curltest --rm -it --image=curlimages/curl --restart=Never -- \
curl -sv --connect-timeout 3 http://ingress-nginx-controller.ingress-nginx.svc/
# 2. 같은 VNet 의 다른 VM 에서 노드 사설 IP 로
curl -v --connect-timeout 3 http://${NODE_IP}:${NODEPORT}/
# 3. 같은 VNet 의 VM 에서 LB 공인 IP 로
curl -v --connect-timeout 3 http://${LB_PUBLIC_IP}/
# 4. 사외 PC 에서 LB 공인 IP 로
1 이 되고 2 가 안 되면 노드 NSG, 2 가 되고 3 이 안 되면 LB 규칙 · 프로브, 3 이 되고 4 가 안 되면 소스 IP 허용 범위 문제다.
HTTP 404 가 돌아오기 시작하면 네트워크 구간은 끝난 것이다. 그때부터는 Ingress 리소스의 host · path · ingressClassName 과 서비스 셀렉터를 본다. 도메인을 붙이지 않고 hosts 파일로 임의 이름을 매핑해 테스트하는 경우, Ingress 규칙의 host 와 요청 Host 헤더가 정확히 같아야 매칭된다.
원본 대화에서는 위 조치를 모두 적용한 뒤에도 사외 PC 에서의 접속이 끝내 복구되지 않은 채 기록이 끝난다. 남은 후보는 아래 셋이다.
az network vnet subnet show ... --query routeTable 로 확인한다.controller.hostNetwork=true 로 바꾸며 서비스와 NodePort 매핑이 어긋난 경우. hostNetwork 를 쓰면 컨트롤러가 노드의 80/443 을 직접 점유하므로 LB 백엔드 포트를 80/443 으로 맞춰야 한다.