10분만에 이해하는 컨테이너 네트워크
컨테이어가 사용하는 네트워크는 처음 접할 때는 어렵기 마련이다. 그러나 리눅스 네트워크 네임스페이스를 공부하고 리눅스 명령어로 이를 구현해 보면 이해가 훨씬 쉽다.
Single Network Namespace
구현하고자 하는 목표는 아래 다이어그램과 같다.
red 네트워트 네임스페이스를 만들고 인터페이스를 만들어서 ip 를 할당하고 노드에서 ping 을 통해 통신이 가능한지 확인한다.
아래의 명령어로 컨테이너 네트워크를 만들어 본다.
1. 새로운 네임스페이스 생성 (이름: red)
$ sudo ip netns add red
2. veth pair 생성
$ sudo ip link add veth1-r type veth peer veth2-r
3. veth2-r 을 red 네임스페이스로 이동
$ sudo ip link set veth2-r netns red
4. red 네임스페이스에 존재하는 veth2-r 인터페이스에 ip 주소를 세팅
$ sudo ip netns exec red ip address add 192.168.0.1 dev veth2-r
5. red 네임스페이스에 존재하는 veth2-r 인터페이스 up
$ sudo ip netns exec red ip link set dev veth2-r up
6. 노드에 존재하는 veth1-r 인터페이스 up
$ sudo ip link set dev veth1-r up
7. red 네임스페이스에 존재하는 loopback 인터페이스 up
$ sudo ip netns exec red ip link set lo up
8. 노드의 라우팅 테이블 세팅
subnet mask 가 32 이므로 바로 해당 ip 에 대해서만 veth1-r 로 향하도록 라우팅을 세팅했다. veth1-r 은 veth2-r 과 링크가 연결되어 있으므로 192.168.0.1 을 목적지로 하는 네트워크 패킷들은 red 네임스페이스 안에 존재하는 veth2-r 인터페이스로 향하게 된다.
$ sudo ip route add 192.168.0.1/32 dev veth1-r
9. red 네임스페이스의 디폴트 라우팅을 세팅
red 네임스페이스 안에서 디폴트 게이트웨이를 veth2-r 인터페이스의 ip address 로 세팅
$ sudo ip netns exec red ip route add default via 192.168.0.1 dev veth2-r
10. 네트워크 통신 확인
$ ip netns
red (id: 0)
$ sudo ip netns exec red ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
74: veth2-r@if75: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 52:42:40:a9:86:8b brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.0.1/32 scope global veth2-r
valid_lft forever preferred_lft forever
inet6 fe80::5042:40ff:fea9:868b/64 scope link
valid_lft forever preferred_lft forever
$ ping 192.168.0.1
PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=0.057 ms
64 bytes from 192.168.0.1: icmp_seq=2 ttl=64 time=0.046 ms
64 bytes from 192.168.0.1: icmp_seq=3 ttl=64 time=0.043 ms
Single Network, 2 Namespaces
이번에는 하나의 노드에 2개의 네임스페이스가 있는 경우를 살펴보자. 보통 컨테이너는 하나의 노드에 2개 이상 멀티로 띄는 경우가 많기 때문에 이 번 케이스가 대부분의 컨테이너 네트워크에 맞다고 보면 된다.
1개의 네임스페이스와 다른 점은 ip 대역을 지정하고 (일반적으로 subnet mask 24 혹은 25) 리눅스 브릿지로 이를 연결하는 것이다. 그리고 브릿지에 ip 주소를 할당한 후 네임스페이스의 네트워크에서 사용할 디폴트 게이트웨이로 지정하여, 네임스페이스 내에서는 외부로 나가는 패킷을 리죽스 브릿지로 통하게 한다.
1. 네임스페이스 생성
$ sudo ip netns add red
$ sudo ip netns add blue
2. veth pair 생성
$ sudo ip link add veth1-r type veth peer name veth2-r
$ sudo ip link add veth1-b type veth peer name veth2-b
3. veth pair 를 네임스페이스로 이동
$ sudo ip link set veth2-r netns red
$ sudo ip link set veth2-b netns blue
4. 네트워크 네임스페이스 내에 있는 인터페이스에 ip 주소 세팅
$ sudo ip netns exec red ip address add 192.168.0.2/24 dev veth2-r
$ sudo ip netns exec blue ip address add 192.168.0.3/24 dev veth2-b
5. 네트워크 네임스페이스 내에 있는 인터페이스를 up
$ sudo ip netns exec red ip link set dev veth2-r up
$ sudo ip netns exec blue ip link set dev veth2-b up
6. 리눅스 브릿지 생성
$ sudo ip link add name br0 type bridge
7. 네트워크 네임스페이스 인터페이스를 브릿지에 연결
$ sudo ip link set dev veth1-r master br0
$ sudo ip link set dev veth1-b master br0
8. 네트워크 네임스페이스 인터페이스를 up
$ sudo ip link set dev veth1-r up
$ sudo ip link set dev veth1-b up
9. 브릿지에 ip 주소 할당
$ sudo ip address add 192.168.0.1/24 dev br0
10. 브릿지 up
$ sudo ip link set dev br0 up
red 네임스페이스에서 blue 네임스페이스로 ping 테스트
$ sudo ip netns exec red ping 192.168.0.3
PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.
ping 은 통신이 안되고 있다. 원인이 무엇일까?
위의 통신은 아래의 경로를 지난다.
red 네임스페이스 -> root 1번 프로세스의 노드 네임스페이스 -> blue 네임스페이스
즉, red 네임스페이스의 iptables, 노드 네임스페이스의 iptables, blue 네임스페이스의 iptables 를 모두 지난다. 그러므로 각 iptables 의 filter 체인을 모두 확인해야 한다.
- red 네임스페이스의 iptables 은 밖으로 나가는 출발점 이고, 응답을 받아야 하므로 OUTPUT filter 체인과 INPUT 체인이 열려 있는지를 확인한다.
- 노드 네임스페이스의 iptables 는 네트워크를 경유하는 곳이므로 FORWARD filter 체인이 열려있는지 확인한다. (지금의 경우에만 그렇고 서버에 접속하고 여러 작업을 하므로 INPUT, OUTPUT 이 모두 열려 있어야 한다.)
- blue 네임스페이스는 iptables 는 패킷을 받고 응답을 줘야 하므로 INPUT filter 체인과 OUTPUT 체인이 열려있는지 확인한다.
## red 네임스페이스
$ sudo ip netns exec red iptables -S
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
## 노드 네임스페이스
$ sudo iptables -S
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
...
## blue 네임스페이스
$ sudo ip netns exec blue iptables -S
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
노드 네임스페스의 FOWARD filter 가 DROP 으로 되어 있다. 이를 ACCEPT 로 변경해 준다.
$ sudo iptables -P FORWARD ACCEPT
한가지가 더 있다. 다음과 같이 sysctl 로 forward 를 열어줘야 한다.
아래의 값이 0 일 경우 이를 1로 변경해 준다.
$ sudo sysctl -a | grep -i net.ipv4.ip_forward
net.ipv4.ip_forward = 0
$ sudo vi /etc/sysctl.d/99-sysctl.conf
net.ipv4.ip_forward=1
$ sudo sysctl -p
이제 red 네임스페이스에서 blue 네임스페이스로 ping 테스트를 다시 하면 성공함을 알 수 있다.
$ sudo ip netns exec red ping 192.168.0.3
PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=0.077 ms
64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=0.063 ms
64 bytes from 192.168.0.3: icmp_seq=3 ttl=64 time=0.062 ms
red, blue 네임스페이스에서 외부로의 ping 테스트
노드 내부 간의 ping 테스트는 성공했으나 외부 통신은 여전히 실패한다.
$ sudo ip netns exec red ping 8.8.8.8
ping: connect: Network is unreachable
이유는 간단하다. red 네임스페이스에서 밖으로 나가는 route 경로를 지정하지 않았기 때문이다. 디폴트 게이트웨이를 아래와 같이 각각 추가해 준다.
$ sudo ip netns exec red ip route add default via 192.168.0.1 dev veth2-r
$ sudo ip netns exec blue ip route add default via 192.168.0.1 dev veth2-b
그리고 중요한 내용 하나가 더 있다.
외부에서는 node 까지는 알 수 있지만 실제로 내부의 red 네임스페이스에 대해서는 알지 못한다.
그렇기 때문에 네임스페이스 안에서 노드 밖으로 패킷이 나갈 때는 source address 를 node 의 address 로 변경해줘야 한다. 이를 masquerade 라고 하며 아래와 같이 노드에서 iptables 의 nat 테이블에 등록하면 된다.
$ sudo iptables -t nat -A POSTROUTING -s 192.168.0.0/24 ! -o br0 -j MASQUERADE
이제 다시 외부 통신을 테스트 해보면 성공함을 알 수 있다.
$ sudo ip netns exec red ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=103 time=32.6 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=103 time=32.6 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=103 time=32.6 ms
$ sudo ip netns exec blue ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=103 time=32.5 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=103 time=32.6 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=103 time=32.5 ms
참고: https://www.youtube.com/watch?v=6v_BDHIgOY8&t=1561s (Container Networking From Scratch - Kristen Jacobs)