만자의 개발일지

[Docker] Docker Network (docker0와 veth) 본문

Docker

[Docker] Docker Network (docker0와 veth)

박만자 2022. 8. 1. 20:33

Docker Network

컨테이너를 생성하게 되면 컨테이너는 NET namespace라는 기술을 통해 구현된 가상화 기법을 사용하여 각자 독립된 네트워크 공간을 할당 받습니다. 그렇다면 이 독립된 네트워크 공간을 가진 컨테이너는 어떻게 외부와 통신되고 컨테이너간의 통신이 가능한 걸까요? 

NET namespace
Network interface, iptables 등 네트워크 리소스와 관련된 정보를 분할하여 각각 다른 namespace에 할당한다.

 

Docker Network 구조

도커의 동작을 이해하기 위해서는 우선 도커 네트워크의 구조부터 이해해야 합니다.

도커 네트워크는 다음과 같은 구조로 이루어져 있습니다.

컨테이너를 생성하면 컨테이너는 호스트와 통신하기 위한 eth0라는 네트워크 인터페이스를 할당받습니다. 이때 동시에 호스트에도 veth(virtual ethernet)라는 네트워크 인터페이스가 할당되고 컨테이너에 할당된 eth0 인터페이스와 통신하게 됩니다. 호스트에 할당된 veth 인터페이스는 docker0와 바인딩되고 docker0는 호스트의 eth0 인터페이스와 연결되어 외부로부터 들어온 요청을 처리합니다.

 

docker0와 veth

대충 구조는 알았으니 이제 도커가 구체적으로 어떻게 외부와 통신할 수 있는지에 대해 알아보도록 합시다. 그 중심에는 바로 docker0veth가 있습니다. 

docker0

docker0는 호스트의 eth0 네트워크 인터페이스와 직접적으로 연결되는 네트워크 인터페이스입니다.

도커를 설치하면 도커 내부 로직에 의해 자동으로 IP를 할당받게 되는데, 이는 172.17.x.x로 시작하며 netmask는 255.255.0.0으로 설정됩니다.

docker0 인터페이스는 호스트의 eth0 인터페이스와 컨테이너의 eth0 인터페이스 사이의 중재자 역할을 하는 가상의 브릿지이며, 컨테이너가 생성되면 컨테이너 내부에 할당되는 eth0 인터페이스와 호스트에 할당되는 veth 인터페이스가 연결되고 docker0에 veth 인터페이스가 바인딩됩니다.

 

도커를 설치한 후 ifconfig를 통해 확인해보면 docker0라는 이름의 네트워크 인터페이스가 생성된 것을 확인하실 수 있습니다.

$ ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:1ff:fe49:e168  prefixlen 64  scopeid 0x20<link>
        ether 02:42:01:49:e1:68  txqueuelen 0  (Ethernet)
        RX packets 1946  bytes 770155 (752.1 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 564  bytes 44678 (43.6 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

 

또한 도커를 설치하면 기본적으로 생성되는 브릿지 네트워크의 정보를 확인해보면 bridge.name docker0로 되어있고, docker0의 IP와 기본 브릿지 네트워크의 Gateway IP가 일치하는 것을 보실 수 있습니다. 이를 통해 기본 브릿지 네트워크가 Gateway로 docker0 인터페이스를 사용한다는 사실을 확인할 수 있습니다.

$ docker network inspect bridge
[
    {
	    "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        .
        .
        .
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

따라서 컨테이너가 기본 브릿지 네트워크를 사용하는 경우 외부로 통신할 때에는 무조건 docker0 인터페이스를 거쳐야 합니다.

 

그렇다면 docker0는 외부에서 들어오는 요청을 어떻게 처리할까요?

 

도커는 호스트의 iptables를 통해 외부에서 들어오는 요청을 관리합니다. 예시를위해 포트 포워딩이 활성화 되있는 컨테이너와 비활성화 되있는 컨테이너를 각각 한개씩 실행시켜주었습니다.

$ docker ps
CONTAINER ID   IMAGE                             COMMAND                  CREATED         STATUS                   PORTS                                       NAMES
37cca9fb3b3a   nginx                             "/docker-entrypoint.…"   5 minutes ago   Up 5 minutes             80/tcp                                      nginx
05d19d18ceab   gcr.io/cadvisor/cadvisor:latest   "/usr/bin/cadvisor -…"   4 hours ago     Up 6 minutes (healthy)   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   cadvisor

 

iptables을 확인해보면 다음과 같이 규칙이 추가된 것을 볼 수 있습니다. 포트 포워딩이 활성화된 컨테이너에 대해서는 iptables에 규칙이 추가되었지만 비활성화된 컨테이너는 규칙이 추가되지 않은 것을 보실 수 있습니다.

$ iptables -S
.
.
.
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A FORWARD -o docker_gwbridge -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker_gwbridge -j DOCKER
-A FORWARD -i docker_gwbridge ! -o docker_gwbridge -j ACCEPT
-A FORWARD -i docker_gwbridge -o docker_gwbridge -j DROP
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8080 -j ACCEPT      <-- 이 부분!
.
.
.

위 규칙을 쉽게 해석해보자면 호스트에 tcp, 8080번 포트로 요청이 들어오는 경우 docker0 인터페이스를 거쳐서 172.17.0.2로 요청을 전달해! 라는 뜻입니다.

 

이처럼 도커는 포트 포워딩이 활성화된 컨테이너를 생성하면 iptables에 규칙을 추가하고 해당 규칙을 통해 외부에서 들어오는 요청을 처리합니다.

veth

컨테이너를 실행하게 되면 호스트에 veth 인터페이스가 할당됩니다. veth(virtual ethernet) 인터페이스는 가상의 네트워크 인터페이스로, 자신과 연결된 컨테이너의 네트워크 인터페이스와 패킷을 주고받는 식으로 동작합니다.

이는 사용자가 직접 생성할 필요없이 도커가 자동으로 생성해주며, veth 인터페이스는 항상 쌍으로(pair)로 생성됩니다.

하나는 vethxxxx라는 이름으로 호스트에 생성되며 docker0에 바인딩 되는 형식이고,  하나는 컨테이너 내부에 eth0 라는 이름으로 생성되어 veth 인터페이스와 연결되는 방식입니다.

 

호스트에 컨테이너가 한개도 실행중이지 않은 경우 실제로 docker0에 veth가 바인딩되지 않는 것을 볼 수 있습니다. 

$ brctl show docker0
bridge name     bridge id               STP enabled     interfaces
docker0         8000.02420149e168       no

그렇다면 호스트에 컨테이너가 실행중이라면 어떻게 될까요?

 

임시로 컨테이너 하나를 실행시켜 주었습니다.

$ docker ps
CONTAINER ID   IMAGE                             COMMAND                  CREATED       STATUS                            PORTS                                       NAMES
05d19d18ceab   gcr.io/cadvisor/cadvisor:latest   "/usr/bin/cadvisor -…"   4 hours ago   Up 2 seconds (health: starting)   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   cadvisor

 

그리고 다시 docker0를 확인해보면 veth 인터페이스 하나가 바인딩된 것을 보실 수 있습니다.

$ brctl show docker0
bridge name     bridge id               STP enabled     interfaces
docker0         8000.02420149e168       no              veth9159bb2

하지만 veth 인터페이스는 쌍으로 생성된다고 했습니다. 컨테이너 내부에도 네트워크 인터페이스가 할당되었는지 확인해 봅시다.

 

컨테이너 내부에서 ifconfig 명령어로 네트워크 인터페이스 목록을 출력해보니 eth0 이라는 이름의 네트워크 인터페이스가 할당된것을 보실 수 있습니다.

또한 eth0 인터페이스의 IP주소는 호스트의 내부 IP로, 기본 브릿지 네트워크의 Subnet CIDR 대역에 포함되는 것을 보실 수 있습니다.

$ ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:38 errors:0 dropped:0 overruns:0 frame:0
          TX packets:91 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:3146 (3.0 KiB)  TX bytes:7512 (7.3 KiB)

 

이처럼 같은 브릿지 네트워크에서 생성된 컨테이너들은 같은 CIDR 대역에 포함되는 IP를 할당받기 때문에 마치 하나의 네트워크에 있는 것처럼 동작하는 것입니다. 따라서 같은 기본 브릿지 네트워크에서 생성된 컨테이너들은 docker0를 통해 서로간의 통신이 가능하게 됩니다.

 

참고

Comments