도커(Docker)의 네트워크 모델 개요
쿠버네티스의 네트워크를 이해하려면 먼저 도커의 기본 네트워크 동작 방식을 파악해야 합니다. 도커는 플러그인 기반의 네트워크 아키텍처를 채택하고 있으며, 컨테이너 실행 시 --network 플래그를 통해 다양한 드라이버를 선택할 수 있습니다.
- bridge: 도커의 기본 네트워크 드라이버입니다. 각 컨테이너에 독립적인 네트워크 네임스페이스와 IP를 할당하고, 가상 브리지에 연결합니다.
- host: 컨테이너가 호스트 머신의 네트워크 스택을 직접 공유하여 사용합니다.
- none: 네트워크 인터페이스를 생성하지 않으며, 루프백(loopback) 장치만 사용할 수 있습니다.
- overlay: 여러 도커 데몬을 연결하여 스위암(Swarm) 클러스터 내에서 컨테이너 간 통신을 가능하게 합니다.
- macvlan: 컨테이너에 고유한 MAC 주소를 할당하여 물리적 네트워크의 독립적인 장치처럼 동작하게 합니다.
- 서드파티 플러그인: 도커 스토어나 외부 벤더에서 제공하는 네트워크 플러그인을 연동할 수 있습니다.
Bridge 네트워크의 구성 및 외부 접근
도커가 설치되면 기본적으로 docker0라는 가상 브리지가 생성됩니다. 이 브리지는 RFC 1918 사설 IP 대역 중 하나를 서브넷으로 할당받습니다. 컨테이너가 실행될 때마다 호스트에는 veth pair 가상 네트워크 인터페이스가 생성됩니다. 이 쌍 중 하나는 컨테이너 내부의 eth0로 연결되고, 다른 하나는 docker0 브리지에 연결됩니다.
가상 브리지는 외부 네트워크와 직접 라우팅되지 않으므로, 외부에서 컨테이너로 접근하려면 포트 포워딩(Port Forwarding)이 필요합니다. 도커는 NAT를 통해 호스트의 포트를 컨테이너의 포트에 매핑합니다.
# 모든 노출된 포트를 호스트의 임의 포트로 매핑
$ docker run --publish-all nginx:latest
# 호스트의 특정 포트를 컨테이너 포트로 매핑
$ docker run --publish 8080:80 nginx:latest
쿠버네티스의 네트워크 요구사항
쿠버네티스 네트워크 모델은 도커의 기본 모델보다 더 복잡한 요구사항을 해결해야 합니다. 크게 다음 네 가지 통신 범위를 지원합니다.
- 클러스터 내부:
- 동일 파드 내 컨테이너 간 통신
- 서로 다른 파드(Pod) 간 통신
- 파드와 서비스(Service) 간 통신
- 클러스터 외부:
- 외부 클라이언트와 서비스 간 통신
쿠버네티스는 모든 파드가 네트워크 주소 변환(NAT) 없이 서로 직접 통신할 수 있어야 한다는 전제를 가집니다. 각 파드는 고유한 IP를 가지며, 이를 통해 물리적 머신이나 가상 머신과 동일한 네트워크 환경을 구성합니다.
동일 파드 내의 컨테이너 통신
쿠버네티스는 파드를 생성할 때 pause라는 인프라 컨테이너를 먼저 실행하여 네트워크 네임스페이스를 초기화합니다. 이후 해당 파드에 속하는 다른 모든 컨테이너는 이 pause 컨테이너의 네트워크 네임스페이스를 공유(--network=container:<pause_id>)하게 됩니다. 따라서 동일 파드 내의 컨테이너들은 localhost를 통해 서로 통신할 수 있습니다.
Flannel을 활용한 파드 간 통신 (CNI)
서로 다른 노드에 분산된 파드 간의 통신을 구현하기 위해 쿠버네티스는 CNI(Container Network Interface) 플러그인을 사용합니다. 그중 Flannel은 CoreOS(현재 Red Hat)에서 개발한 오버레이 네트워크 솔루션으로, L3(Layer 3) 오버레이 모델을 기반으로 합니다.
Flannel은 각 노드에 고유한 서브넷을 할당하고, 패킷을 캡슐화하여 물리적 네트워크를 통해 전송합니다. 주요 백엔드 메커니즘으로는 VXLAN, UDP, host-gw 등이 있습니다.
Flannel의 초기화 및 네트워크 설정 프로세스
1. 클러스터 네트워크 구성 저장
Flannel은 etcd를 데이터 저장소로 사용하여 클러스터 전체의 네트워크 CIDR을 관리합니다. (아래는 etcd v3 API를 사용한 예시입니다)
# 클러스터 네트워크 설정 조회
$ ETCDCTL_API=3 etcdctl get /coreos.com/network/config
2. 노드별 서브넷 할당
각 노드의 flanneld 프로세스는 etcd에서 전체 CIDR을 읽고, 자신의 노드에 할당할 서브넷을 요청하여 리스(lease)를 획득합니다.
# 할당된 서브넷 리스 정보 확인
$ ETCDCTL_API=3 etcdctl get --prefix /coreos.com/network/subnets
3. 로컬 환경 변수 파일 생성
서브넷을 할당받으면 flanneld는 도커 또는 컨테이너 런타임이 사용할 수 있도록 환경 변수 파일을 생성합니다.
$ cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=10.244.1.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true
4. 가상 네트워크 인터페이스 및 브리지 구성
노드에는 flannel.1 (VXLAN 백엔드 사용 시)이라는 가상 인터페이스가 생성되며, 도커 또는 CRI-O의 브리지(예: cni0 또는 docker0)는 할당된 서브넷에 맞게 IP가 재설정됩니다.
# flannel 가상 인터페이스 확인
$ ip -d link show flannel.1
# 컨테이너 브리지 IP 확인
$ ip addr show cni0
VXLAN 기반의 데이터 패킷 전송 흐름
서로 다른 노드에 있는 파드 간에 데이터가 전송될 때, VXLAN 백엔드를 사용하는 경우의 패킷 흐름은 다음과 같습니다.
소스 노드에서의 패킷 처리
- 컨테이너에서 브리지로 전송: 소스 파드에서 목적지 IP로 패킷을 보내면, 컨테이너의 기본 게이트웨이인 로컬 브리지(예:
cni0)로 전달됩니다. - 브리지에서 flannel.1로 라우팅: 목적지 IP가 다른 노드의 서브넷에 속해 있으므로, 호스트의 라우팅 테이블에 따라 패킷은
flannel.1인터페이스로 전달됩니다. - 내부 캡슐화 (VXLAN):
flanneld는 원본 패킷을 VXLAN 헤더로 캡슐화합니다. 이 과정에서 소스 및 목적지flannel.1의 MAC 주소가 사용됩니다. - 외부 캡슐화 (물리적 네트워크 전송): VXLAN 패킷은 물리적 네트워크를 통해 전송되기 위해 다시 UDP 및 외부 IP 헤더로 캡슐화됩니다. 소스 및 목적지 IP는 각 노드의 물리적 네트워크 인터페이스 IP가 됩니다.
목적지 노드에서의 패킷 처리
- 외부 패킷 수신 및 디캡슐화: 목적지 노드의 물리적 NIC가 UDP 패킷을 수신하면, 커널이 VXLAN 터널을 통해 내부 패킷을 추출하여
flannel.1인터페이스로 전달합니다. - 내부 패킷 디캡슐화:
flannel.1은 VXLAN 헤더를 제거하고 원본 이더넷 프레임을 복원합니다. - 로컬 브리지를 통한 최종 전달: 복원된 패킷은 호스트의 라우팅 테이블에 따라 로컬 브리지로 전달되고, 최종적으로 목적지 파드의 네트워크 인터페이스로 도달하여 통신이 완료됩니다.