인그레스로 노출한 애플리케이션이 어떤 때는 응답하고 어떤 때는 멈춘다. 백엔드 파드를 인그레스 컨트롤러가 떠 있는 노드로 옮기면 안정적으로 되고, 다른 노드로 가면 다시 간헐적으로 끊긴다. 노드 안에서 curl 로 확인해도 결과가 들쭉날쭉하다.
노드 안에서 NodePort 를 두드릴 때 호스트 이름이나 127.0.0.1 을 쓰면 안 된다.
curl http://$(hostname):30080 # 신뢰할 수 없는 테스트
curl http://127.0.0.1:30080 # 신뢰할 수 없는 테스트
호스트 이름이 /etc/hosts 에서 127.0.0.1 로 풀리면 패킷이 루프백으로 들어가고, NodePort 를 구현하는 iptables·IPVS 규칙은 루프백 입력을 기본 대상으로 잡지 않는다. kube-proxy 가 route_localnet=1 을 설정한다는 로그를 남기지만 이것은 조건부 허용이라 환경에 따라 동작이 갈린다.
반드시 노드의 실제 인터페이스 주소로 확인한다.
ip -4 -o addr show scope global | awk '{print $2, $4}'
curl -sv http://<NODE_IP>:30080
getent hosts $(hostname)
getent hosts 가 루프백을 돌려주면 그 이름으로 한 테스트는 의미가 없다.
간헐적 실패의 전형적인 원인은 인그레스 컨트롤러와 백엔드 파드가 서로 다른 노드에 있을 때 노드 간 파드 통신이 깨져 있는 것이다. 엔드포인트가 여러 개면 요청마다 다른 노드로 분산되므로 "한 번은 되고 한 번은 멈추는" 모양이 된다.
어디에 무엇이 떠 있는지부터 본다.
kubectl get pod -n ingress-nginx -o wide
kubectl get pod -n <NS> -l app=<APP> -o wide
kubectl get endpointslice -n <NS> -l kubernetes.io/service-name=<SVC> -o wide
그다음 Kubernetes 를 빼고 파드 대 파드로 직접 확인한다. 이 테스트가 실패하면 인그레스 설정을 아무리 만져도 소용없다.
kubectl run t1 --image=busybox --restart=Never --overrides='{"spec":{"nodeName":"<NODE1>"}}' -- sleep 3600
kubectl run t2 --image=busybox --restart=Never --overrides='{"spec":{"nodeName":"<NODE2>"}}' -- sleep 3600
kubectl get pod -o wide
kubectl exec t1 -- ping -c3 <T2_POD_IP>
kubectl exec t1 -- wget -qO- --timeout=3 http://<T2_POD_IP>:80
노드를 넘을 때만 실패한다면 오버레이 네트워크 경로가 막힌 것이다. 클라우드에서는 보안 그룹이나 네트워크 정책이 파드 대역이나 오버레이 포트를 막고 있는 경우가 대부분이고, 온프레미스에서는 노드 방화벽이다. "보안 그룹이 똑같다" 는 것은 두 노드의 설정이 같다는 뜻일 뿐, 오버레이가 쓰는 프로토콜과 포트가 허용됐다는 뜻은 아니다.
백엔드를 인그레스 컨트롤러와 같은 노드에 고정하면 증상은 사라진다. 요청과 응답이 한 노드 안에서 끝나기 때문이다. 그러나 원인은 그대로 남아 있고, 파드가 다시 스케줄되거나 복제본을 늘리는 순간 되돌아온다.
급히 고정해야 한다면 nodeName 보다 nodeSelector 나 affinity 를 쓴다. nodeName 은 스케줄러를 건너뛰므로 그 노드에 자원이 없으면 파드가 영원히 Pending 으로 남고, 노드를 비울 때(drain) 다시 배치되지도 않는다.
spec:
nodeSelector:
kubernetes.io/hostname: <NODE>
인그레스 컨트롤러 쪽을 DaemonSet 으로 두어 모든 노드에 하나씩 띄우는 것도 같은 성격의 회피책이다.
Service 의 externalTrafficPolicy 가 Local 이면 그 노드에 해당 서비스의 파드가 없을 때 NodePort 로 들어온 요청이 그대로 버려진다. 로드밸런서가 상태 점검으로 걸러 주지만, 노드 IP 로 직접 두드리는 테스트에서는 "어떤 노드는 되고 어떤 노드는 안 되는" 모양으로 보인다.
kubectl get svc -n ingress-nginx ingress-nginx-controller \
-o jsonpath='{.spec.externalTrafficPolicy}{"\n"}'
Cluster 는 어느 노드로 들어와도 처리되지만 다른 노드로 전달될 때 출발지 주소가 바뀐다. 클라이언트 IP 가 필요하면 Local 로 두고 컨트롤러를 DaemonSet 으로 배치하는 조합을 쓴다.
kube-proxy 가 규칙을 만들었는지 노드에서 직접 본다.
iptables -t nat -L KUBE-NODEPORTS -n --line-numbers | head -20
ipvsadm -Ln | head -20 # IPVS 모드일 때
kubectl logs -n kube-system ds/kube-proxy --tail=50
규칙이 없으면 kube-proxy 가 그 노드에서 제대로 돌지 않은 것이다. 규칙이 있는데도 닿지 않으면 그 앞단(호스트 방화벽, 클라우드 보안 그룹)이다.