[K8s] 쿠버네티스 네트워크 (DNS, 트래픽 라우팅, 네트워크 정책)

CKA 준비를 하면서 쿠버네티스 환경에서 파드와 서비스 간의 통신 문제를 풀다 보니 실제 리눅스 시스템 레벨 동작 원리를 이해해야 했다.

클러스터 내부의 DNS 해석, 트래픽 라우팅, 네트워크 보안 정책이 커널 수준에서 어떻게 구현되는지 정리했다.

1. 쿠버네티스 내부 DNS 해석 메커니즘

서비스의 이름만으로 통신이 가능한 이유는 클러스터 내부의 DNS 시스템과 리눅스 컨테이너의 네트워크 설정 격리 덕이다.

Pod와 Node의 resolv.conf 격리

리눅스 시스템은 도메인을 IP로 변환할 때 가장 먼저 /etc/resolv.conf 파일을 참조한다. 쿠버네티스 클러스터에서 파드와 이를 호스팅하는 노드는 서로 다른 DNS 설정 주체를 가진다.

파드가 생성될 때 Kubelet은 컨테이너 내부의 resolv.conf 파일에 클러스터 내부 DNS 서버인 CoreDNS의 IP를 네임서버로 주입한다. 동시에 search 지시자를 통해 클러스터 내부 도메인 접미사 설정도 추가한다.

// 노드(Host)의 /etc/resolv.conf 예시
nameserver 8.8.8.8
nameserver 1.1.1.1

// 파드(Pod) 내부의 /etc/resolv.conf 예시
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

이러한 격리 구조로 인해 파드 내부에서 서비스 이름만으로 요청을 보내면 CoreDNS를 통해 해당 서비스의 가상 IP를 정상적으로 반환받을 수 있다.

반면 파드가 실행 중인 물리적인 노드는 외부 인터넷 네임서버를 바라보므로 쿠버네티스 서비스 도메인을 해석하지 못한다. 통신 테스트를 진행할 때 노드의 쉘이 아닌 클러스터 내부의 디버그 파드를 띄워야 하는 이유가 이거다.

2. 서비스 라우팅과 네트워크 주소 변환

DNS를 통해 서비스의 가상 IP를 획득한 후, 이 패킷이 실제 물리적인 노드를 넘어 대상 파드로 도달하는 과정은 프록시와 네트워크 주소 변환 기술에 의존한다.

kube-proxy와 DNAT 기반의 트래픽 라우팅

쿠버네티스의 서비스는 실제 트래픽을 처리하는 프로세스나 네트워크 인터페이스가 아니라 일종의 라우팅 규칙이다. 각 노드에 배포된 kube-proxy 데몬은 API 서버를 감시하며 서비스와 엔드포인트의 변경 사항을 추적하고 있다.

변경 사항이 감지되면 kube-proxy는 리눅스 커널의 iptables 또는 IPVS에 네트워크 주소 변환 규칙을 작성한다.

파드에서 출발한 패킷이 노드의 네트워크 스택을 통과할 때, 커널의 규칙이 서비스 IP를 목적지로 하는 패킷을 가로챈다. 이후 해당 패킷의 도착지 IP를 실제 트래픽을 처리할 대상 파드의 IP로 조작하는 DNAT를 수행하여 라우팅을 완료한다.

NodePort 환경에서의 SNAT와 비대칭 라우팅 방지

외부 트래픽이 NodePort를 통해 클러스터로 인입될 때는 추가적인 주소 변환 과정이 필요하다. 클라이언트가 특정 노드의 포트로 요청을 보냈지만, 트래픽을 처리할 파드가 다른 노드에 배치되어 있을 경우 비대칭 라우팅 문제가 발생할 수 있다.

패킷을 전달받은 파드가 응답을 보낼 때, 원본 출발지 IP를 참조하여 자신에게 패킷을 전달해 준 노드를 거치지 않고 클라이언트에게 직접 응답을 시도하게 된다.

이를 방지하기 위해 최초로 패킷을 수신한 노드는 다른 노드에 위치한 파드로 패킷을 전달하기 전, 패킷의 출발지 IP를 자신의 노드 IP로 변경하는 SNAT를 수행한다.

[클라이언트] IP: 203.0.113.1
   │
   ▼ 1. 요청 인입 (패킷 헤더: SRC 203.0.113.1 / DST 10.0.0.1:30080)
[Node A] IP: 10.0.0.1
   │
   │ - DNAT 수행: 목적지를 대상 파드 IP(192.168.1.5)로 변경
   │ - SNAT 수행: 출발지를 Node A IP(10.0.0.1)로 변경
   │
   ▼ 2. 노드 간 라우팅 (패킷 헤더: SRC 10.0.0.1 / DST 192.168.1.5:80)
[Node B]
   │
   ▼ 3. 대상 파드 도달
[Pod] IP: 192.168.1.5

위 메커니즘을 통해 파드의 응답이 최초 수신 노드로 정상 회귀할 수 있고, 그래서 클라이언트는 안정적인 TCP 연결을 유지할 수 있다.

3. CNI 플러그인과 라벨 기반 네트워크 제어

클러스터 내부의 트래픽 흐름을 통제하는 방화벽 역할은 네트워크 정책 리소스를 통해 선언되며, 이는 CNI 플러그인에 의해 실제 커널 규칙으로 번역된다.

정책 번역: 라벨 -> IP 규칙

쿠버네티스 매니페스트는 파드의 유동적인 IP 대신 변하지 않는 라벨을 기준으로 방화벽 규칙을 정의한다. 그런데 리눅스 커널의 필터링 도구인 iptables나 eBPF는 라벨의 개념을 이해하지 못하며 오직 IP 주소와 포트만을 식별할 수 있다.

이 간극을 메우는 게 Calico나 Cilium 같은 CNI 플러그인이다. 각 노드의 CNI 에이전트는 API 서버를 주시하며 네트워크 정책에 명시된 라벨을 가진 파드들의 현재 IP 목록을 실시간으로 조회한다. 이후 확보된 동적인 파드 IP 주소들을 바탕으로 커널 공간에 실제 IP 기반의 방화벽 규칙을 동적으로 적용하고 해제한다.

// 1. API 서버에 등록되는 NetworkPolicy (라벨 기준 선언)
spec:
  podSelector:
    matchLabels:
      run: np-test-1
  ingress:
  - ports:
    - port: 80

# 2. CNI 에이전트가 변환한 iptables 실제 규칙 (IP 기준 제어)
# np-test-1 라벨을 가진 파드들의 현재 IP(172.17.1.5, 172.17.1.6)를 추적하여 동적 반영한다.
-A KUBE-NWPLCY -d 172.17.1.5/32 -p tcp -m tcp --dport 80 -j ACCEPT
-A KUBE-NWPLCY -d 172.17.1.6/32 -p tcp -m tcp --dport 80 -j ACCEPT
-A KUBE-NWPLCY -j DROP

이러한 분리 구조 덕분에 K8s 운영자가 IP 변경에 구애받지 않고 선언적인 보안 정책을 유지 가능하고 실제 트래픽 통제는 노드 레벨에서 안전하고 빠르게 처리된다고 한다.


© 2022. All rights reserved.

Powered by Hydejack v9.2.1