본문 바로가기

docker

container가 생성되는 과정 (3) - network

이번 게시글에서는 container가 생성되었을 때, 어떻게 host server와 완전히 분리된 network를 사용할 수 있게되는지, 또 container에서 외부 network와 어떻게 통신할 수 있는지 확인해보자..!

Docker Network ??

일반적으로 흔히 사용하는 container 생성/관리 오픈소스인 docker는 다음과 같은 container network을 지원한다.

  • host - host의 network를 그대로 사용하는 방식
  • bridge - host network와 bridge를 생성하여 통신하는 방식 
  • overlay - 물리적으로 독립된 docker network를 구성하여 통신하는 방식
  • none - network에 연결되지 않는 방식

$ docker network ls 명령어로 docker network 확인 가능

이번 게시글에서는 가장 흔히 사용되는 bridge network를 중점적으로 확인할것이다. 

Bridge Network ? 

bridge network는 linux에서 virtual networking을 위해 software로 구현한 network 방식이며 하나의 물리적인 network interface로부터 다리(bridge)를 연결한 interface를 추가로 생성하여 통신을 가능하게 하는 방식이다.

bridge network (출처 : https://developers.redhat.com/blog/2018/10/22/introduction-to-linux-interfaces-for-virtual-networking#bridge)

위 그림과 같은 구조를 docker network에서 적용하여 사용중인데, docker network inspect bridge 명령어를 통해 확인 가능하다.

$ docker network inspect bridge
[
    {
        "Name": "bridge",
        ...
        ...
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        ...
        "Options": {
		...
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        ...
    }
]
  • IPAM(IP Address Management)
    • docker bridge network을 사용하는 container가 사용할 ip대역(subnet)과 gateway(host의 br interface IP)가 정의되어있다.
  • com.docker.network.bridge.name
    • host server에서 bridge interface의 이름이 정의된다
  • com.docker.network.driver.mtu
    • bridge network interface의 mtu 값을 정의하다.

위 설정에 따라 앞서 보았던 bridge network 그림에서 eth0 interface는 docker0 interface가 된다.
이후 container가 생성되면 host server에 veth1이 생성이 되고 pod에 veth0이 생성될것이다.
이어서 확인해보자!

Container 생성 후 Network 확인

### container 실행, 8080 port를 80 port로 port forwarding 설정
$ docker run -it -d -p 8080:80  ubuntu:16.04 /bin/bash

### bridge 생성 확인. docker0 container로부터 veth* bridge 생성
$ brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242bb5a01a3       no              vetha63f8a1

### container의 pid 확인 (4849)
$ ps -ef | grep /bin/bash
root      4849  4826  0 04:33 pts/0    00:00:00 /bin/bash


### host의 network namespace와 container의 network namespace 분리 확인
$ ls -ltr /proc/1/ns/
total 0
lrwxrwxrwx 1 root root 0 Nov 25 04:33 net -> net:[4026532088]
lrwxrwxrwx 1 root root 0 Nov 25 05:05 uts -> uts:[4026531838]

$ ls -ltr /proc/4849/ns/
total 0
lrwxrwxrwx 1 root root 0 Nov 25 04:33 net -> net:[4026532273]
lrwxrwxrwx 1 root root 0 Nov 25 04:34 uts -> uts:[4026532269]

### host server의 docker0 interface address 확인
$ ifconfig docker0
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255

### 생성된 container에서 어떤 IP를 사용중인지 확인 ( eth0: 172.17.0.2 )
$ nsenter -t 4849 -n ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.2  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:ac:11:00:02  txqueuelen 0  (Ethernet)
        RX packets 16  bytes 1280 (1.2 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

### docker container에서 default route가 host server의 docker0 interface로 생성되어있음
$ nsenter -t 4849 -n route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.17.0.1      0.0.0.0         UG    0      0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

위 command의 결과로 bridge interface 그림에서 veth1은 vetha63f8a1, veth0은 container/eth0 임을 확인할 수 있다.

container의 route 조회결과로 알 수 있듯이 해당 container에서 생성되는 트래픽 중 같은 bridge subnet(172.17.0.0)에 속하는 트래픽은 L2(bridge)로 통신하게되고, 그 외의 트래픽은 host server를 통해 외부로 전달이 된다.

iptables 통한 In/Egress network 확인

Ingress

container의 L2로 통신하기때문에 외부에서 container의 IP로 direct 통신할 수 없다. 이러한 문제는 port forwarding 을 통해 해결할 수 있으며 아래 command와 같이 입력하면 host server의 8080 port로 들어오는 트래픽을 실행되는 container의 80 port로 전달하게된다.

### container 실행, 8080 port를 80 port로 port forwarding 설정
$ docker run -it -d -p 8080:80 ubuntu:16.04 /bin/bash

이러한 설정은 iptables를 통해 확인할 수 있다.

$ iptables -t nat -nvL
Chain PREROUTING (policy ACCEPT 18 packets, 1036 bytes)
 pkts bytes target     prot opt in     out     source               destination
   29  1616 DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT 18 packets, 1036 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 180 packets, 13805 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DOCKER     all  --  *      *       0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT 180 packets, 13805 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0
    0     0 MASQUERADE  tcp  --  *      *       172.17.0.2           172.17.0.2           tcp dpt:80

Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0
    0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 to:172.17.0.2:80

여기서 iptables의 NAPT(Network Address Port Translation) 기능을 사용하는데, 약어의 의미 그대로 address와 port를 변환시켜주는 기능이다.
Ingress 트래픽은 D-NAPT(Destination-NAPT)기능을 통해 local host로 들어온 트래픽을 container의 address:port로 변환한다. 해당 기능은 위 PREROUTING Chain과 DOCKER Chain을 통해 확인할 수 있다.

iptables chain flow ( 참고 : https://www.google.com/url?sa=i&url=https%3A%2F%2Fakhil.io%2Fblog%2Fiptables-explained&psig=AOvVaw1zp7WFq9t-6mYd3V54QcNd&ust=1637910749620000&source=images&cd=vfe&ved=0CAsQjRxqFwoTCJCA_JX7svQCFQAAAAAdAAAAABAD)

외부에서 들어온 패킷의 destination address가 host server의 local address가 아니면 PREROUTING Chain을 타게 되는데, PREROUTING chain에는 모든 트래픽이 Docker Chain을 보도록 되어있다. (nat table)
Docker Chain의 nat table에서 docker0 interface로 부터 들어온 패킷이 아니고, destination port가 8080이라면, container IP:Port인 172.17.0.2:80 으로 DNAPT하도록 rule이 생성되어 있음을 확인할 수 있다.
이러한 rule에 따라 route table 조회 전에 host server에서 해당 traffic이 container IP:Port로 dnapt가 되고, route table에 따라 docker0 interface를 통해 L2 통신으로 container까지 전달된다.

Egress

container에서 외부로 내보내는 패킷의 경우 위에서 언급한바와 같이 default route를 통해 host server의 docker0 interface까지 전달되고, Ingress와 동일하게 iptables chain rule을 적용받는다.

Ingress와 달리 PREROUTING까지 아무런 rule을 거치지 않고 pass하며 POSTROUTING에서 S-NAPT(Source-NAPT)가 된다. POSTROUTING에서 src cidr이 container의 cidr이고, host server에서 해당 packet이 docker0 interface를 통하지 않고 내보내게 되는 경우 MASQUERADE하도록 한다. 이러한 rule에 의해 packet의 source IP:Port는 host server의 route 정책에 맞는 interface의 IP 변경된 채 외부로 내보내진다. 

 

 

반응형