Kubernetes 네트워크 트러블슈팅 순서: DNS·TCP·TLS·HTTP를 증거로 좁히기

반응형

Pod에서 특정 domain에 접근하지 못할 때 ping, nc, curl, tcpdump를 무작정 돌리면 결과는 많아져도 원인은 좁혀지지 않는다. 먼저 어느 network namespace에서, 어떤 destination으로, 어느 시각에 실패했는지를 고정하고 DNS → route → TCP → TLS → HTTP 순서로 한 단계씩 확인한다.

명령어를 OSI layer 하나에 정확히 꽂아 넣는 표는 기억에는 편하지만 실제 진단에는 거칠다. 예를 들어 curl 한 번에도 DNS resolution, TCP connect, TLS handshake, HTTP exchange가 연달아 일어난다. 각 도구가 성공·실패를 어디까지 증명하는지를 읽는 편이 중요하다.

0. 증상을 재현 가능한 문장으로 만든다

다음 정보를 먼저 기록한다.

  • source: namespace, Pod, container, node
  • destination: hostname, port, protocol, URL path
  • time: timezone을 포함한 시작·종료 시각
  • symptom: timeout, connection refused, TLS error, HTTP status 중 무엇인지
  • scope: 한 Pod, 한 node, 한 namespace, cluster 전체 중 어디까지인지
  • recent change: deployment, NetworkPolicy, Service, DNS, certificate 변경 여부

접속이 안 된다 대신 payments namespace의 api-xxx Pod에서 09:12 KST부터 api.example.internal:443 TCP connect가 3초 timeout처럼 적으면 다음 검사가 선명해진다.

1. Kubernetes object와 endpoint부터 확인한다

Service를 향한 traffic이라면 client 안으로 들어가기 전에 desired state와 실제 endpoint를 비교한다.

kubectl -n <namespace> get pod <pod> -o wide
kubectl -n <namespace> get service <service> -o yaml
kubectl -n <namespace> get endpointslice -l kubernetes.io/service-name=<service>
kubectl -n <namespace> describe pod <pod>

selector와 Pod label이 맞는지, EndpointSlice에 ready endpoint와 예상 port가 있는지 확인한다. Service가 존재한다는 사실만으로 backend가 준비됐다는 뜻은 아니다. NetworkPolicy, service mesh, egress gateway가 있다면 그 경로도 scope에 포함한다.

2. DNS: 이름이 어느 주소로 풀리는가

애플리케이션과 같은 Pod·container context에서 resolver 설정과 결과를 본다.

cat /etc/resolv.conf
getent ahosts <host>
dig <host> A
dig <host> AAAA

확인할 것은 단순 성공 여부만이 아니다.

  • search domain과 ndots 때문에 예상 밖 query가 먼저 나가는가
  • A와 AAAA 중 application이 어느 주소를 선택하는가
  • 응답의 TTL과 authoritative answer가 무엇인가
  • NXDOMAIN, SERVFAIL, timeout 중 어떤 실패인가

nslookupdig의 성공은 지정한 DNS query가 답을 받았다는 증거다. application runtime의 caching과 search behavior까지 동일하다고 보장하지는 않는다. 가능한 경우 application과 같은 resolver API를 쓰는 getent 결과도 비교한다.

3. Route와 source address를 확인한다

DNS가 반환한 destination IP를 기준으로 kernel이 선택할 route를 본다.

ip address
ip route
ip route get <destination-ip>

ip route get 결과의 outgoing interface, next hop, source address를 확인한다. Pod CNI, node route, NAT, egress gateway가 있는 환경에서는 client Pod에서 보이는 경로와 node·external firewall에서 보이는 경로가 다를 수 있다.

ping은 ICMP echo가 허용될 때 round-trip reachability를 보는 보조 도구다.

ping -c 4 <destination-ip>

ping 실패만으로 TCP service가 죽었다고 판단할 수 없다. ICMP가 filter되면서 TCP 443은 정상인 환경도 많다. 반대로 ping 성공도 application port와 TLS·HTTP 정상 동작을 보장하지 않는다.

4. TCP connect 결과를 분리한다

nc -vz -w 3 <host> <port>
  • succeeded는 해당 시점에 TCP connection이 성립했다는 뜻이다.
  • connection refused는 흔히 destination이 RST를 반환했음을 뜻하며 listener·port·policy를 확인한다.
  • timed out은 packet drop, route, firewall, overloaded endpoint 등 여러 원인이 가능하다.

메시지 하나를 root cause로 번역하지 말고 packet path의 다음 증거와 맞춘다. UDP service는 TCP connect와 다른 방식으로 확인해야 하며, nc -u의 성공 문구만으로 server application이 응답했다고 단정하기 어렵다.

5. TLS와 HTTP를 curl timing으로 나눈다

curl -sS -o /dev/null \
  --connect-timeout 3 --max-time 10 \
  -w 'code=%{http_code} remote=%{remote_ip} dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} total=%{time_total}\n' \
  https://<host>/<path>

time_namelookup, time_connect, time_appconnect, time_starttransfer를 비교하면 DNS, TCP, TLS, server response 구간을 분리하는 데 도움이 된다. curl -v는 certificate와 header 정보를 자세히 보여 주지만 token·cookie 같은 민감 정보가 terminal log나 ticket에 남을 수 있어 공유 전에 가려야 한다.

curl -I는 HEAD request다. server나 proxy가 GET과 다르게 처리할 수 있으므로 실제 application method를 대신하는 만능 health check가 아니다. DNS를 우회해 특정 IP와 Host/SNI 조합을 시험할 때는 임시 /etc/hosts 변경보다 --resolve를 사용하면 범위를 좁힐 수 있다.

curl --resolve '<host>:443:<ip>' https://<host>/<path>

6. Local socket과 packet을 연결한다

server 또는 같은 network namespace에 접근할 수 있다면 socket 상태를 본다.

ss -lntp
ss -tan state syn-sent
ss -tan state established

ss가 보여 주는 것은 실행한 namespace의 socket이다. host에서 본 결과를 Pod 안의 결과처럼 읽지 않는다. netstat은 기존 환경에 남아 있을 수 있지만 Linux에서는 ss, ip neigh 같은 iproute2 도구를 우선할 수 있다.

packet capture는 앞 단계의 가설을 확인할 때 짧고 좁게 사용한다.

tcpdump -ni any 'host <destination-ip> and port <port>'
  • SYN이 나가고 SYN-ACK이 오는가
  • RST가 어느 방향에서 오는가
  • retransmission이 반복되는가
  • DNS query와 response가 같은 transaction으로 돌아오는가

capture에는 internal address, domain, payload 같은 민감 정보가 포함될 수 있다. 필요한 interface·host·port·시간으로 filter하고, 권한과 보관·공유 범위를 확인한다. TLS여도 endpoint와 timing metadata는 노출된다.

7. Distroless Pod에서는 ephemeral debug container를 쓴다

application image에 shell이나 도구가 없다면 kubectl debug로 running Pod에 ephemeral container를 추가할 수 있다.

kubectl debug -it pod/<pod> \
  -n <namespace> \
  --image=<approved-debug-image>@sha256:<digest> \
  --target=<container> -- sh

--target은 대상 container의 process namespace를 겨냥하며 runtime 지원 여부에 따라 기대한 process가 보이지 않을 수 있다. ephemeral container 추가는 cluster state를 변경하는 작업이다. production에서는 RBAC, admission policy, approved image, audit log를 확인하고 종료 후 남은 상태를 기록한다. 넓은 권한의 ‘만능 image’를 기본값으로 두지 않는다.

결과를 해석하는 빠른 표

관찰 확인된 범위 아직 모르는 것
DNS answer 수신 resolver가 주소를 반환 그 주소의 service가 정상인지
ping 성공 ICMP round trip TCP port·TLS·HTTP 상태
TCP connect 성공 3-way handshake TLS·application response
TLS handshake 성공 certificate·cipher 협상 URL별 business logic
HTTP 200 한 method·path의 응답 전체 dependency와 지속적 정상 상태

라우팅 자체의 원리가 필요하면 라우팅 테이블과 packet 전달 흐름, TCP state의 기초는 TCP·UDP와 socket 상태 정리로 이어갈 수 있다.

자주 묻는 질문

ping이 안 되면 network 장애인가

그럴 수도 있지만 ICMP filtering일 수도 있다. 실제 application protocol의 DNS·route·TCP·TLS 결과를 함께 봐야 한다.

timeout과 connection refused의 차이는 무엇인가

refused는 흔히 RST처럼 명시적인 거절 응답을 받은 경우이고 timeout은 제한 시간 안에 필요한 응답을 못 받은 경우다. 어느 장비가 응답했거나 버렸는지는 route와 packet capture로 확인한다.

tcpdump부터 실행하면 빠르지 않나

가설과 filter 없이 capture하면 noise와 민감 data만 늘기 쉽다. object·DNS·route·connect 결과로 범위를 줄인 뒤 필요한 지점에서 짧게 수집한다.

참고 자료

반응형
KEEP READING
카테고리 전체 보기 →

댓글