Ubuntu에서 네트워크 네임스페이스 활용하기

Docker 컨테이너를 쓰다 보면 컨테이너마다 독립된 IP와 라우팅 테이블을 갖는다는 걸 당연하게 받아들이게 되지만, 정작 그 격리를 커널이 어떤 메커니즘으로 제공하는지 직접 만들어본 적은 드물다. Docker의 기본 브리지 네트워크는 사실 리눅스 네트워크 네임스페이스와 veth pair, 브리지, NAT를 조합한 것에 지나지 않는다. 이 구조를 직접 손으로 구성해보면 컨테이너 네트워킹 문제를 디버깅할 때(왜 컨테이너가 인터넷에 안 나가는지, 왜 두 컨테이너가 서로 통신이 안 되는지) 훨씬 수월하게 원인을 좁힐 수 있다. 이 글에서는 Ubuntu에서 ip netns로 네트워크 네임스페이스를 만들고, veth pair와 브리지로 서로 연결한 뒤, NAT로 외부 인터넷까지 연결하는 과정을 정리한다.

네트워크 네임스페이스란

네트워크 네임스페이스는 리눅스 커널이 제공하는 네임스페이스 중 하나로, 네트워크 인터페이스·라우팅 테이블·방화벽 규칙·포트 바인딩을 프로세스 그룹별로 완전히 분리한다. 기본 네임스페이스(호스트)와 별도로 만들어진 네임스페이스는 서로 다른 컴퓨터인 것처럼 동작하며, 같은 포트를 여러 네임스페이스에서 동시에 바인딩할 수도 있다. Docker, LXC, Kubernetes의 pod 네트워킹 모두 이 기능 위에서 구현된다.

네임스페이스 생성과 기본 명령

# 네임스페이스 생성
sudo ip netns add ns1
sudo ip netns add ns2

# 목록 확인
ip netns list

# 네임스페이스 안에서 명령 실행
sudo ip netns exec ns1 ip addr show

# 삭제
sudo ip netns del ns1

새로 만든 네임스페이스는 루프백 인터페이스(lo)조차 기본적으로 꺼져 있다. 이후 예제에서 각 네임스페이스에 인터페이스를 붙일 때 lo도 함께 올려야 한다.

veth pair로 두 네임스페이스 연결하기

veth(virtual ethernet)는 항상 쌍으로 생성되며, 한쪽으로 들어간 패킷이 반대쪽으로 그대로 나오는 가상 케이블이다. 두 네임스페이스를 직접 연결하는 가장 단순한 구성은 다음과 같다.

# veth pair 생성
sudo ip link add veth1 type veth peer name veth2

# 각각 다른 네임스페이스로 이동
sudo ip link set veth1 netns ns1
sudo ip link set veth2 netns ns2

# ns1 쪽 설정
sudo ip netns exec ns1 ip addr add 10.0.0.1/24 dev veth1
sudo ip netns exec ns1 ip link set veth1 up
sudo ip netns exec ns1 ip link set lo up

# ns2 쪽 설정
sudo ip netns exec ns2 ip addr add 10.0.0.2/24 dev veth2
sudo ip netns exec ns2 ip link set veth2 up
sudo ip netns exec ns2 ip link set lo up

# 통신 테스트
sudo ip netns exec ns1 ping -c 3 10.0.0.2

브리지로 여러 네임스페이스 묶기

네임스페이스가 셋 이상이거나 나중에 늘어날 가능성이 있다면 point-to-point veth 대신 브리지를 허브처럼 두는 구조가 관리하기 편하다. Docker의 docker0 브리지도 같은 방식이다.

# 호스트에 브리지 생성
sudo ip link add br0 type bridge
sudo ip addr add 172.16.0.1/24 dev br0
sudo ip link set br0 up

# ns1을 브리지에 연결
sudo ip link add veth-ns1 type veth peer name veth-ns1-br
sudo ip link set veth-ns1 netns ns1
sudo ip link set veth-ns1-br master br0
sudo ip link set veth-ns1-br up
sudo ip netns exec ns1 ip addr add 172.16.0.11/24 dev veth-ns1
sudo ip netns exec ns1 ip link set veth-ns1 up
sudo ip netns exec ns1 ip link set lo up
sudo ip netns exec ns1 ip route add default via 172.16.0.1

# ns2를 브리지에 연결 (같은 패턴)
sudo ip link add veth-ns2 type veth peer name veth-ns2-br
sudo ip link set veth-ns2 netns ns2
sudo ip link set veth-ns2-br master br0
sudo ip link set veth-ns2-br up
sudo ip netns exec ns2 ip addr add 172.16.0.12/24 dev veth-ns2
sudo ip netns exec ns2 ip link set veth-ns2 up
sudo ip netns exec ns2 ip link set lo up
sudo ip netns exec ns2 ip route add default via 172.16.0.1

NAT로 네임스페이스에 인터넷 연결하기

네임스페이스는 기본적으로 외부와 격리돼 있으므로, 인터넷에 나가려면 호스트가 라우터 역할을 하도록 IP 포워딩과 NAT를 설정해야 한다.

# 커널의 IP 포워딩 활성화
sudo sysctl -w net.ipv4.ip_forward=1

# 실제 외부로 나가는 인터페이스 확인 (보통 eth0, ens3 등)
ip route get 8.8.8.8

# NAT 규칙 추가 (아래 eth0은 위에서 확인한 실제 인터페이스로 교체)
sudo iptables -t nat -A POSTROUTING -s 172.16.0.0/24 -o eth0 -j MASQUERADE

# 테스트
sudo ip netns exec ns1 ping -c 3 8.8.8.8

ping은 되는데 도메인 이름 접속이 안 된다면 DNS 설정이 빠진 것이다. /etc/netns/<네임스페이스이름>/resolv.conf 파일을 만들어두면 ip netns exec가 네임스페이스 안에서 /etc/resolv.conf 대신 이 파일을 자동으로 바인드 마운트한다.

sudo mkdir -p /etc/netns/ns1
echo "nameserver 8.8.8.8" | sudo tee /etc/netns/ns1/resolv.conf

sudo ip netns exec ns1 curl -s https://example.com -o /dev/null -w "%{http_code}\n"

실전 활용 예

이 구조는 실제로 VM을 여러 대 띄우지 않고도 다음과 같은 상황을 로컬에서 재현하는 데 쓸 수 있다.

  • 같은 포트(예: 8080)를 쓰는 서비스 여러 개를 충돌 없이 동시에 테스트
  • iptables 방화벽 규칙을 운영 서버에 적용하기 전에 격리된 환경에서 검증
  • 멀티 홉 라우팅이나 정책 기반 라우팅(policy routing)을 가볍게 시뮬레이션
  • Docker 네트워킹 문제(브리지 미연결, NAT 누락, DNS 미설정)의 원인을 계층별로 좁혀가며 재현

주의사항

네임스페이스는 기본적으로 /var/run/netns 아래에 bind mount로 유지되지만 재부팅하면 사라진다. 실습이 아니라 지속적으로 써야 하는 구성이라면 systemd 유닛이나 netplan 후크로 부팅 시 재구성하는 스크립트를 별도로 준비해야 한다.

net.ipv4.ip_forward=1sysctl -w로만 적용하면 재부팅 후 원래대로 돌아간다. 영구 적용하려면 /etc/sysctl.d/ 아래에 설정 파일을 추가해야 한다.

NAT용 iptables 규칙은 호스트 전역에 적용되므로, 실습이 끝나면 iptables -t nat -D POSTROUTING ...로 정리하는 습관을 들이는 게 좋다. 규칙을 남겨두면 나중에 같은 대역을 다른 용도로 쓸 때 예기치 않게 NAT가 걸릴 수 있다.

ip netns exec는 명령을 실행할 때마다 새 네임스페이스로 진입하는 오버헤드가 있다. 네임스페이스 안에서 여러 명령을 연속으로 실행해야 한다면 nsenter로 셸을 하나 띄워두고 그 안에서 작업하는 편이 더 빠르다.

마무리

네임스페이스+veth+브리지+NAT 조합은 Docker 기본 브리지 네트워크가 내부적으로 구성하는 것과 사실상 동일한 구조다. 이 구조를 손으로 한 번 구성해보면 “컨테이너가 왜 인터넷에 안 나가지”, “왜 두 컨테이너가 서로 못 보지” 같은 문제를 만났을 때 IP 포워딩이 꺼졌는지, NAT 규칙이 빠졌는지, 브리지에 붙어있지 않은지를 층위별로 나눠 확인할 수 있게 된다.

참고

답글 남기기