이번 게시글에서는 container가 생성되었을 때, 어떻게 host server와 완전히 분리된 network를 사용할 수 있게되는지, 또 container에서 외부 network와 어떻게 통신할 수 있는지 확인해보자..!
Docker Network ??
일반적으로 흔히 사용하는 container 생성/관리 오픈소스인 docker는 다음과 같은 container network을 지원한다.
- host - host의 network를 그대로 사용하는 방식
- bridge - host network와 bridge를 생성하여 통신하는 방식
- overlay - 물리적으로 독립된 docker network를 구성하여 통신하는 방식
- none - network에 연결되지 않는 방식
이번 게시글에서는 가장 흔히 사용되는 bridge network를 중점적으로 확인할것이다.
Bridge Network ?
bridge network는 linux에서 virtual networking을 위해 software로 구현한 network 방식이며 하나의 물리적인 network interface로부터 다리(bridge)를 연결한 interface를 추가로 생성하여 통신을 가능하게 하는 방식이다.
위 그림과 같은 구조를 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을 통해 확인할 수 있다.
외부에서 들어온 패킷의 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 변경된 채 외부로 내보내진다.
'docker' 카테고리의 다른 글
container가 생성되는 과정 (2) - file system (0) | 2021.11.24 |
---|---|
container가 생성되는 과정 (1) - computing resource (0) | 2021.11.22 |
set -e 와 exec "$@" (0) | 2021.07.19 |