Name
Last commit
Last update
..
.gitkeep docker swarm index init
README.md docker network init

logo

Docker Network 검토

Docker의 네트워크 개념을 검토하고, 서비스에 적용한다.

Docker의 Network 방식

Docker는 크게 Overlay, Bridge, host, none네 가지 Network 방식이 존재하며, 다음과 같이 개념과 역할에 대해 알아보자.

Overlay Network [ Pick ]

  • Overlay NetworkDocker Swarm에 참여하는 Docker Daemon 간의 통신을 관리한다.
  • 독립 실행형 컨테이너의 Network를 생성하는 방법과 동일한 방식으로 Overlay Network를 생성할 수 있다.
  • 기존에 생성된 Overlay Network에 Service를 연결시켜, Service 간 통신을 활성화할 수 있다.
  • Overlay Network는 Overlay Network Driver를 사용한다.

www.hongsnet.net 서비스는 다음과 같이,

Front-end(HA-Proxy) -> Internal Network -> Backend(Docker) -> Internal Network -> DBMS 의 구성이므로, Overlay Network를 이용하여 구성한다.

Ingress Network ( Overlay Network Sub mode )

  • Ingress Network는 Service의 Node 들간에 Load Balancing을 하는 Overlay Network 이다.
  • Docker Swarm의 모든 Node가 노출된 Port로 요청을 받게되면, 해당 요청을 IPVS라는 모듈로 전달한다.
  • IPVS는 해당 Service에 참여하는 모든 IP 주소를 추적하고, 그 중 하나를 선택한 뒤 요청을 해당 경로로 Routing 한다.
  • Igreess Network는 Docker Swarm을 Init하거나 Join할 때 자동으로 생성된다.

host

Host 모드는 docker0 interface를 사용하지 않고 Host OS의 네트워크 자원을 사용한다. bridge 형식으로 사용하지 않기 때문에 brctl show docker0 명령으로 검색해도 interfaces에 나타나지 않으며, Container에서 Port를 노출하지 않는 경우 사용할 수가 없으며, 보안상 권장하지 않는 방법이다.

None

컨테이너가 LoopBack(127.0.0.1)을 제외한 어떠한 네트워크도 사용하지 않는다. 이는 격리가 필요할 경우 사용된다.

Bridge

Docker에서 기본적으로 설정되는 Network 방식이다. 호스트에서 다음과 같이 docker network inspect bridge 명령을 통해 상세정보를 확인해본다.

# docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "43e5b193049b1f29fa48819a0cf137f433cb3e4f512efccaedfa6f034e69e62d",
        "Created": "2020-01-21T05:35:46.711742895+09:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Containers": {},
        "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": {}
    }
]

Bridge라는 특성에 맞게, 같은 네트워크(Layer 2)에서 통신이 가능하다. 즉, Ingress와 같이 외부로 노출하지 않을 경우에는 기본적으로 외부와의 통신이 불가능하다.

docker0 Network ( Bridge Network Sub mode )

Docker Host를 설치한 후 host의 Network Interface를 보면, docker0라는 Virtual Interface가 존재하는 것을 볼 수 있다. 아래는 Docker를 설친한 host의 Interface를 확인한 정보이다.

# ifconfig docker0
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 0.0.0.0
        inet6 fe80::42:7aff:fe09:f1d8  prefixlen 64  scopeid 0x20<link>
        ether 02:42:7a:09:f1:d8  txqueuelen 0  (Ethernet)
        RX packets 5489687  bytes 20588189759 (19.1 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 9576525  bytes 26208253051 (24.4 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

docker0 Interface의 특징은 다음과 같다.

  • IP는 자동으로 172.17.0.0/16 네트워크로 설정된다.
  • 이 IP는 DHCP를 통해 할당받는 것은 아니며, docker 내부 로직에 의해서 자동으로 할당받는 것이다.
  • docker0는 일반적인 Interface가 아니며, Virtual Etherent Bridge이다.

docker0는 Container가 통신하기 위한 가상 Linux Bridge이다. Bridge는 기본적으로 Layer2 통신 기반이며, 만약 Container가 생성되면, 이 Bridge에 Container의 Interface가 하나씩 Binding되는 형태이다. 따라서 Container가 외부로 통신할 때는 무조건 docker0 Interface를 거쳐야 한다.

Container Network ( Bridge Network Sub mode )

Container Network 방식은 같은 호스트에 있는 컨테이너들 끼리의 통신을 제공하는 방법이다. 컨테이너 간의 통신은 내부 도커 네트워크를 통해서 이루어지며, 통신은 호스트의 네트워크로 노출되지 않는다는것을 의미한다.

--link CONTAINER:ALIAS 로 설정하며, CONTAINER 는 링크 컨테이너(연결되는 컨테이너), ALIAS는 마스터 컨테이너(시작되는 컨테이너)의 내부에서 링크 컨테이너를 참조할 때 사용하는 로컬 이름을 설정 한다.

Docker Container의 외부통신 구조

Docker host에 Container가 배포되면, 각 Container에는 격리된 네트워크 환경(namespace)이 제공된다. 이 네트워크 환경은 오로지 각각의 Container 만을 위한 네트워크 환경이다. 각 Container에는 통신을 위한 인터페이스도 새롭게 할당되며, mac 주소와 private IP도 부여받게 된다.

각 Container 들의 인터페이스는 자신들이 상주하고 있는 Docker host와 통신을 위해 Linux Bridge 방식으로 Binding 되어 있다. 따라서 같은 Docker host 내에 배포된 Container 들 사이에는 각자 할당받은 Private IP를 이용해서 자유롭게 통신이 가능하다.

Container Port를 외부로 노출(expose)

Container를 생성하면 기본적으로 외부와 통신이 불가능한 상태이다. 따라서 외부와 통신을 위해서는 Container를 외부로 노출할 Port를 지정해야 한다. 노출할 Port를 지정하는 방법은 Container를 생성할 때 -p 옵션을 사용하면 된다.

# docker run -d -p 8000:80 --name hongsnet-nginx-1.16

만약 docker-compose를 사용할 경우에는 다음과 같이 기술한다.

version: '3'
services:
  hongsnet-nginx-1.16:
       image: hongsnet-nginx-1.16:latest
       #restart: always
       hostname: 'nginx116.hongsnet.net'
       container_name: hongsnet-nginx-1.16
       ports:
        - "8000:80"
        - "8443:443"

위 명령대로 실행하면, 외부에서 Docker 호스트의 8000 포트로 요청되면 컨테이너의 8000번 포트로 해당 요청을 forwarding 하겠다는 의미이다.

아래와 같이 컨테이너 상태를 살펴보면, 8000 -> 80 포트로 forwarding 되어 있는 것을 볼 수 있다.

# docker ps
CONTAINER ID        IMAGE                                                          COMMAND                  CREATED             STATUS                PORTS                                                            NAMES
78f248e1817f        hongsnet-nginx-1.16:latest                                     "nginx -g 'daemon ..."   27 minutes ago      Up 27 minutes         0.0.0.0:8000->80/tcp, 0.0.0.0:8443->443/tcp                      hongsnet-nginx-1.16

# netstat -lnp |grep 8000
tcp6       0      0 :::8000                 :::*                    LISTEN      8182/docker-proxy-c

docker-proxy

그런데 위 8000번 포트를 Listen 하는 프로세스는 docker-proxy 이다. 이 프로세스의 목적은 Docker 호스트로 들어온 요청을 컨테이너로 넘기는 것 뿐이다. docker-proxy는 Kernel이 아닌, userland에서 수행되기 때문에 Kernel과 상관없이 host가 받은 패킷을 그대로 컨테이너의 port로 넘긴다.

컨테이너를 시작할 때 port를 외부로 노출하도록 설정하게 되면, Docker 호스트에는 docker-proxy라는 프로세스가 생성된다.

# tail -f /var/log/messages
...중략
00:00:00 /usr/libexec/docker/docker-proxy-current -proto tcp -host-ip 0.0.0.0 -host-port 8000 -container-ip 172.19.0.2 -container-port 80

docker-proxy 프로세스는 컨테이너의 port를 노출하도록 설정한 수 만큼, 추가로 프로세스가 생성된다(run process per port). 만약 하나의 포트를 오픈하는 두 개의 컨테이너를 생성한다면, docker-proxy는 두 개가 생성된다. 또한 한 개의 컨테이너에 두 개의 포트에 대해서 외부로 노출하도록 설정한다면, 마찬가지로 docker-proxy 프로세스는 두 개가 생성된다.

하지만 실제로는 Docker host로 요청이 들어온 패킷이 컨테이너로 전달되는 것은 docker-proxy와는 무관하게 Docker 호스트iptables에 의해서 동작된다. 즉, docker-proxy 프로세스를 kill 하더라도, 외부에서 들어오는 요청이 컨테이너로 전달되는데는 문제가 없다는 의미이다.

iptables를 이용한 DNAT

먼저 Docker 호스트의 iptables를 확인해 보자.

# iptables -t nat -L -n
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0
MASQUERADE  tcp  --  172.19.0.2           172.19.0.2           tcp dpt:22

Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  0.0.0.0/0            0.0.0.0/0
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:2222 to:172.19.0.2:22

위의 iptables 설정내역을 보면, 먼저 Docker 호스트에 들어온 패킷이 PREROUTING chain을 통해 DOCKER Chain으로 전달되고, Docker chain에서는 DNAT로 2222번 포트로 들어온 요청을 172.19.0.2 IP를 가진, 컨테이너의 22번 포트로 포워딩되는 것을 알 수 있다.

반대로 컨테이너의 외부로 나갈때는 POSTROUTING chain을 거쳐, MASQUERADE 되어 외부로 나간다.