도커 컴포즈로 도커 네트워킹 톺아보기

Ahra YiAhra Yi
16 min read

원문: Kesara Karannagoda, "Diving Deeper into Docker Networking with Docker Compose"

지난 글에서 도커 네트워킹을 분석하겠다는 말씀을 드렸는데 이제 그 약속을 지키려고 합니다. 이번 글에서는 도커 네트워킹을 심도 있게 살펴 보며 그 작동 원리를 더 잘 이해할 수 있도록 여러분을 도우려 합니다

네트워크 모드

도커에서는 다음과 같은 여러 가지 네트워크 유형을 사용할 수 있습니다.

  1. 브리지(Bridge) : 도커의 기본 네트워크 유형입니다. 컨테이너 간 격리를 제공하면서 컨테이너들이 서로 통신할 수 있도록 합니다.

  2. 호스트(Host) : 컨테이너가 호스트 머신의 네트워크 스택에 직접 연결할 수 있도록 하는 특별한 네트워크 유형입니다.

  3. 오버레이(Overlay) : 컨테이너가 동일한 호스트 머신에서 실행되고 있지 않더라도 서로 통신하게 해주는 더 복잡한 네트워크 유형입니다.

  4. 맥브이랜(Macvlan): 컨테이너의 MAC 주소를 지정할 수 있도록 하여 컨테이너가 네트워크의 물리적 장치로 기능하게 해주는 네트워크입니다.

  5. 네트워크 플러그인(Network plugins): 제3자 네트워크 플러그인을 설치하여 사용할 수 있습니다.

  6. 논(None): 새로운 네트워크 환경을 생성하지 않습니다. 논(None) 드라이버가 사용되면 컨테이너는 오직 로컬 네트워크 (사용자의 컴퓨터 네트워크)만 사용할 수 있습니다.

이번 글에서는 브리지(Bridge) 네트워크 모드와 호스트(Host) 네트워크 모드를 다루고자 합니다.

기본 브리지 네트워크

도커를 로컬 컴퓨터에 설치하면 도커 브리지(Docker Bridge)라는 새로운 네트워크가 생성됩니다. 터미널에서 다음 명령어를 실행하여 이를 확인해 볼 수 있습니다.

리눅스 터미널에서 다음 명령어를 입력합니다.

ip address ls

docker0 네트워크 인터페이스를 확인할 수 있습니다.

또한 다음 도커 명령어를 실행하여 (윈도우와 리눅스에서) 컴퓨터의 모든 도커 네트워크 리스트를 볼 수 있습니다.

docker network ls

아래와 같은 출력 결과를 얻을 수 있습니다.

도커를 어느 정도 사용해 본 경험이 있다면 몇몇 추가적인 네트워크도 함께 나타날 수 있습니다. 이들에 대해서도 이번 글에서 추후 더 자세히 다룰 것입니다.

NETWORK ID          NAME                DRIVER              SCOPE
17c1d0d4956b        bridge              bridge              local
f04d3c0691be        host                host                local
cea56a468e79        none                null                local

간단히 말하면 위의 출력 결과는 "브리지 네트워크""브리지 드라이버"라는 두 개체 간의 연결을 나타냅니다. 네트워크와 드라이버는 연결되어 있지만 동일한 구성 요소는 아니라는 점을 이해해야 합니다. 또한 출력은 브리지 네트워크가 로컬 스코프임을 나타내며 이는 해당 네트워크가 이 도커 호스트에서만 존재한다는 것을 의미합니다.

모든 브리지 네트워크는 단일 호스트 네트워크를 제공합니다. 즉 동일한 호스트에서 실행되는 컨테이너 간의 통신 및 연결을 가능하게 하는 데 네트워크가 제한되는 것입니다. 해당 네트워크는 다른 호스트나 컴퓨터로 확장되지 않습니다. 이러한 접근 방식은 컨테이너의 격리를 제공하고 보안을 강화하여 컨테이너가 동일 시스템 내에서만 상호작용할 수 있도록 합니다.

그러나 서로 다른 호스트에서 실행되는 컨테이너의 경우 브리지 네트워크를 통해서는 직접 통신할 수 없습니다. 다중 호스트 통신을 위해 도커는 오버레이(overlay) 네트워크와 같은 대체 네트워크 드라이버를 제공합니다. 오버레이 네트워크는 여러 호스트에 걸쳐 있는 컨테이너들이 분산 네트워크로 서로 통신하게 하여 클러스터 및 분산 도커 환경에서의 원활한 통신을 지원합니다.

이제 간단한 튜토리얼로 기본 브리지 네트워크의 동작 원리를 살펴보겠습니다.


도커 컴포즈: 기본 브리지 네트워크 튜토리얼

사전 준비

컴퓨터에 도커를 설치했다고 가정하겠습니다. 아직 설치하지 않았다면 공식 문서를 참조하세요. https://docs.docker.com/engine/install/

다음은 이 글에서 자주 언급되는 용어입니다.

  1. 화성(Mars): 우리 태양계의 행성. 네트워크 이름으로 사용합니다.

  2. 포보스(Phobos): 화성의 위성 중 하나. 네트워크 노드 (컨테이너 서비스)로 사용합니다.

  3. 데이모스(Deimos): 화성의 또 다른 위성. 네트워크 노드 (컨테이너 서비스)로 사용합니다.

  4. 지구(Earth): 우리 행성. 네트워크 이름으로 사용합니다.

  5. 달(Moon): 우리의 위성. 네트워크 노드 (컨테이너 서비스)로 사용합니다.

시작하려면 docker-compose.yaml 파일이 필요합니다.
이 튜토리얼 과정을 완료하면 다음과 같은 "화성(Mars)" 네트워크를 생성할 것입니다.

화성(Mars) 네트워크

  1. 먼저 로컬 컴퓨터에 새 프로젝트 폴더를 생성합니다.

  2. 프로젝트 폴더 내에 docker-compose.yaml이라는 새 파일을 생성합니다.

  3. 이제 docker-compose.yaml 파일에 다음의 코드 스니펫을 복사하여 붙여넣습니다.

docker-compose.yaml

version: "3"
services:
  phobos:
    container_name: phobos
    image: busybox
    command: sleep infinity
  deimos:
    container_name: deimos
    image: busybox
    command: sleep infinity

이 튜토리얼에서는 'Busybox' 도커 이미지를 사용할 것입니다. Busybox는 디스크 크기가 1에서 5MB 사이인 리눅스 도커 이미지입니다. 크기가 작고 'iputils-ping' 패키지가 들어 있어 ping 명령어를 사용할 수 있습니다.

ping 명령어로 원격 목적지 IP가 활성인지 비활성인지 알아낼 수 있습니다. 또한 목적지와의 통신에서 왕복 지연(round-trip delay)을 찾아 패킷 손실 여부를 검토할 수 있습니다. 기본적으로 두 목적지 (컴퓨터나 노드) 간의 연결성을 확인할 수 있습니다.

이제 docker-compose.yaml 파일을 살펴보겠습니다.

version: "3": 사용 중인 도커 컴포즈 파일의 버전을 지정합니다. 이 경우는 버전 3입니다.

services: 이 섹션은 애플리케이션을 구성하는 개별 서비스를 정의합니다.

phobos: 첫 번째 서비스의 이름으로 "phobos"라는 컨테이너에 해당합니다. 포보스(phobos)는 화성의 위성 중 하나입니다.

container_name: phobos: "phobos" 서비스에 대해 생성될 도커 컨테이너의 이름을 지정합니다. 컨테이너의 이름은 "phobos" 입니다.

image: busybox: "phobos" 서비스가 기본 이미지로 "busybox" 이미지를 사용할 것임을 나타냅니다. BusyBox는 흔히 디버깅 목적으로 사용되거나 다른 컨테이너의 기본 이미지로 사용되는 경량의 최소 버전 컨테이너 이미지입니다.

command: sleep infinity: "phobos" 컨테이너가 시작될 때 실행되는 명령어입니다. 이 경우 "sleep infinity" 명령어로 컨테이너가 아무것도 하지 않고 실행 상태를 무한히 유지하도록 합니다. 기본적으로 백그라운드 프로세스로 작동합니다.

  • 데이모스(deimos) 서비스도 포보스와 동일합니다.

이제 프로젝트 디렉터리에서 터미널을 열고 docker compose up -d 명령어를 실행하여 두 서비스를 빌드하고 실행합니다.

docker compose up -d

명령어 끝에 -d를 추가하여 모든 컨테이너를 분리(detach) 모드로 실행합니다. docker-compose ps 명령어를 사용하여 실행 중인 컨테이너를 확인할 수 있습니다.

$ docker compose ps
NAME                IMAGE               COMMAND             SERVICE             CREATED             STATUS              PORTS
deimos              busybox             "sleep infinity"    deimos              17 hours ago        Up 17 hours         
phobos              busybox             "sleep infinity"    phobos              17 hours ago        Up 17 hours

이제 두 개의 실행 중인 컨테이너를 볼 수 있어야 합니다.

docker network ls 명령어를 실행하면 <your-project-folder>_default라는 새로운 네트워크가 생성된 것을 확인할 수 있습니다. 제 경우에는 프로젝트 폴더 이름을 docker-networking으로 설정했기 때문에 docker-networking_default 네트워크가 생성되었습니다.

$ docker network ls
NETWORK ID     NAME                        DRIVER    SCOPE
17c1d0d4956b   bridge                      bridge    local
eeadb6a2dc43   docker-networking_default   bridge    local
f04d3c0691be   host                        host      local
cea56a468e79   none                        null      local

이전 글에서 말했듯이

도커 컴포즈는 하나의 네트워크에서 하나의 애플리케이션을 위한 서비스를 실행한다는 개념을 이해합니다.

이것이 정확히 도커 컴포즈를 사용하여 컨테이너를 빌드하고 실행할 때 일어나는 일입니다. 도커 컴포즈의 표준 동작은 해당 컨테이너 전용의 새 브리지 네트워크를 생성하는 과정을 포함합니다. 이 브리지 네트워크는 docker-compose.yaml 파일로 정의된 컨테이너 간의 원활한 통신과 연결을 가능하게 합니다. 이를 통해 잘 조직되고 격리된 애플리케이션 실행 환경을 구축할 수 있습니다.

연결

먼저 다음 명령어로 docker compose가 생성한 새로운 네트워크를 검사해 봅시다.

⚠️ 참고: 이 튜토리얼을 따라 진행하면서 컨테이너의 IP 주소가 다를 수 있습니다. 복사 및 붙여넣기 사용을 원하시는 경우 튜토리얼을 계속 진행하면서 적절한 IP 주소를 사용하도록 주의를 기울여 주시기 바랍니다. IP 주소는 시스템과 구성에 따라 상이하게 나타날 수 있으므로 성공적인 튜토리얼 구현을 위해 IP 주소를 신중하게 처리하는 것이 중요합니다.

docker network inspect <newly-created-network>
$ docker network inspect docker-networking_default
[
    {
        "Name": "docker-networking_default",
        "Id": "1bdb95b66268640cf272174afa966913db2ad40ac38553dc6511028b51952652",
        "Created": "2023-07-22T10:34:07.181685944+05:30",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "687a21b31cf4715391f3858e86c4c43c4a254359faa4aa05bf002194e036e5d0": {
                "Name": "phobos",
                "EndpointID": "8039fdaa2c7bdb87b2afbb2fad48bed9aa1b42d9e1c3f7ce1f31773a70c32e31",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            },
            "9fa4b494b685eb3557cbeca86c40cb4dbabf9a6bd4b8a6e62e36f9f6fe2b50ea": {
                "Name": "deimos",
                "EndpointID": "e529ce9c375d41d7c9717f9abb3820a553d39da327f3b79126f0eafc0ef81cd3",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "default",
            "com.docker.compose.project": "docker-networking",
            "com.docker.compose.version": "2.18.1"
        }
    }
]

위의 스니펫과 유사한 JSON 출력을 받을 것입니다. "Containers" 섹션에서 볼 수 있듯이 "phobos"와 "deimos" 컨테이너가 있으며 각각 동일한 네트워크의 개별 IP 주소를 할당받았습니다. 이는 컨테이너가 성공적으로 연결되었고 지정된 네트워크를 사용하여 서로 통신할 수 있음을 보여줍니다.


이제 두 컨테이너 간의 연결을 확인해 봅시다.

docker exec -it <container> /bin/sh 명령어를 실행하여 대화형 셸 세션을 시작합니다.

docker exec -it phobos /bin/sh

도커의 exec 서브 명령어는 실행 중인 컨테이너 내부에서 명령어를 실행하는 데 사용됩니다.

-it는 두 개의 개별 옵션을 함께 제공한 것입니다.

-i--interactive를 나타내며 연결되지 않은 경우에도 표준 입력(stdin)을 열어 둡니다. -t--tty를 나타내며 가상 터미널(pseude-terminal)을 할당하여 대화형 셸 세션을 활성화합니다.

해당 컨테이너 내부에서 명시된 명령어 /bin/sh를 실행하여 대화형 셸 세션을 시작합니다. 이 튜토리얼에서는 Busybox 이미지에 기본적으로 "sh"가 설치되어 있으므로 /bin/sh를 사용합니다. 이를 통해 마치 가상 머신이나 원격 서버에 직접 로그인한 것처럼 컨테이너 내부에서 직접 명령줄 인터페이스(CLI)로 작업할 수 있습니다.

편의를 위해 이전 출력 결과의 아래 부분을 추출했습니다. 결과는 다르게 나타날 수 있다는 점을 명심해 주세요.

"Containers": 
{
"687a21b31cf4715391f3858e86c4c43c4a254359faa4aa05bf002194e036e5d0": 
  {
    "Name": "phobos",
    "EndpointID": "8039fdaa2c7bdb87b2afbb2fad48bed9aa1b42d9e1c3f7ce1f31773a70c32e31",
    "MacAddress": "02:42:ac:12:00:03",
    "IPv4Address": "172.18.0.3/16",
    "IPv6Address": ""
  },
"9fa4b494b685eb3557cbeca86c40cb4dbabf9a6bd4b8a6e62e36f9f6fe2b50ea": 
  {
    "Name": "deimos",
    "EndpointID": "e529ce9c375d41d7c9717f9abb3820a553d39da327f3b79126f0eafc0ef81cd3",
    "MacAddress": "02:42:ac:12:00:02",
    "IPv4Address": "172.18.0.2/16",
    "IPv6Address": ""
  }
}

포보스(Phobos)에서 데이모스(Deimos)로 핑(ping) 보내기

핑(Ping)은 인터넷 제어 메시지 프로토콜(ICMP, Internet Control Message Protocol) 패킷을 통해 동작합니다. 핑은 대상 호스트로 ICMP 에코 요청을 보내고 ICMP 에코 응답을 기다리는 작업입니다.

네트워킹 예찬론자들을 위해: ICMP 패킷은 게이트웨이 172.18.0.1.를 통과합니다. 위의 그림은 단지 다음 단계와 목표를 이해하는 데 도움을 주고자 한 것입니다.

먼저 명령어 ip address ls를 실행하여 docker network inspect 명령어로 얻은 위의 출력 결과가 유효한지 확인해 봅시다. 터미널 1.0에서 볼 수 있듯이 위의 출력 결과와 동일한 IP 주소가 나타납니다.

phobos: 172.18.0.3/16

docker exec -it phobos /bin/sh (터미널 1.0)

이제 IP 주소 172.18.0.3/16의 "phobos"에서 IP 주소 172.18.0.2/16의 "deimos"를 호출하여 두 컨테이너 간의 연결을 테스트해 봅시다. 아래의 터미널 2.0 그림과 같이 말입니다.

ping 172.18.0.2

ping 172.18.0.2 (터미널 2.0)

터미널 2.0 그림과 같이 지금까지 모든 단계를 올바르게 진행했다면 이제 "phobos"와 "deimos" 두 컨테이너 간에 패킷을 성공적으로 전송할 수 있어야 합니다. 그림은 생성된 브리지 네트워크를 통해 컨테이너 간의 통신이 성공적으로 구축되었음을 보여줍니다.

도커 DNS

앞서 논의한대로 도커 네트워크를 생성하면 도커는 자동으로 브리지 네트워크를 설정합니다. 브리지 네트워크는 해당 네트워크 내의 모든 컨테이너를 연결하는 가상 네트워크 스위치 역할을 수행합니다. 도커 데몬은 이 브리지 네트워크의 DHCP 서버 역할을 하여 컨테이너에 IP 주소를 제공하고 DNS 해석(resolution)을 처리합니다.

도커 데몬(daemon)

"Dockerd"라고도 불리는 도커 데몬은 도커 플랫폼의 핵심 구성 요소입니다. 이미지, 컨테이너, 네트워크 및 볼륨 등의 도커 객체를 관리하고 도커 CLI(명령줄 인터페이스)와 컨테이너 런타임 간의 중개 역할을 수행합니다. 도커 데몬은 호스트 머신에서 백그라운드 프로세스로 실행되어 도커 API 요청을 대기합니다.

도커 개요

DHCP 서버

DHCP 서버는 네트워크의 장치에 자동으로 IP 주소를 할당하는 컴퓨터 프로그램입니다. DHCP 서버로 다른 장치와의 연결 및 통신 과정을 간소화할 수 있습니다.

DNS 서버

DNS 서버는 사람이 읽을 수 있는 도메인 이름 (예: medium.com)을 숫자로 된 IP 주소로 변환하여 장치가 인터넷에서 웹사이트나 서비스를 찾고 접근할 수 있도록 합니다.

터미널에서 ns lookup medium.com 명령어를 실행하면 medium.com에 해당하는 숫자로된 IP 주소를 확인할 수 있습니다. 도메인 이름의 IP 주소를 검사하는 데 해당 명령어를 사용할 수 있습니다.

다시 데이모스(deimos)를 호출해 봅시다!

같은 대화형 터미널에서 "deimos"를 그 이름으로 호출하면 이전과 동일한 IP 주소로 해석되는 것을 확인할 수 있습니다 (터미널 3.0). 이는 도커의 내부 DNS 해석 덕분입니다. 도커 네트워크 내에서 도메인 이름들은 각자의 IP 주소로 매핑되어 있기 때문에 컨테이너가 할당된 이름을 사용하여 서로 통신할 수 있는 것입니다.

ping deimos

우리가 docker-compose.yaml 파일에서 컨테이너 이름(container_name)데이모스(deimos)로 설정했기 때문에 컨테이너를 이름으로 호출할 수 있습니다.

참고: 도커 네트워크의 IP 구성에 대해 걱정할 필요 없습니다. 컨테이너의 이름으로 컨테이너를 호출할 수 있습니다.

ping deimos (터미널 3.0)

exit 명령어를 입력하여 대화형 터미널을 종료할 수 있습니다.

exit

이제 데이모스(deimos)에서 포보스(phobos)를 호출합시다

이전 단계에서 했던 것처럼 아래 명령어를 실행하여 데이모스(deimos)에서 대화형 셸 세션을 생성할 수 있습니다 (터미널 4.0).

docker exec -it deimos /bin/sh

docker exec -it deimos /bin/sh (터미널 4.0)

그런 다음 데이모스(deimos)에서 phobos로 핑을 시도해 봅시다 (터미널 5.0).

ping phobos

ping phobos (터미널 5.0)

패킷이 손실 없이 전송되는 것을 확인할 수 있어야 합니다.


도커 컴포즈: 사용자 정의 네트워크

지금까지는 docker-compose.yaml 파일에 네트워크를 지정하지 않았습니다. 포보스(phobos)데이모스(deimos)가 서로 통신할 수는 있지만 아직 화성(Mars) 네트워크에 속하지는 못한 것입니다.

화성(Mars) 네트워크에 연결하기 전에 프로젝트 디렉터리에서 docker compose down 명령어를 실행하여 현재 구성에서 포보스(phobos)데이모스(deimos)를 제거합니다.

$ docker compose down
[+] Running 2/2
 ✔ Container deimos  Removed                                                                                                                                                                      10.3s 
 ✔ Container phobos  Removed

이제 "phobos"와 "deimos"를 화성(mars) 네트워크에 추가해 봅시다. docker-compose.yaml 파일을 다음 코드 스니펫으로 업데이트 해주세요.

docker-compose.yaml

version: "3"
services:
  deimos:
    container_name: deimos
    image: busybox
    command: sleep infinity
    networks:
      - mars
  phobos:
    container_name: phobos
    image: busybox
    command: sleep infinity
    networks:
      - mars
networks:
  mars:
    name: mars
    driver: bridge

그런 다음 프로젝트 디렉터리의 터미널에서 docker compose up -d 명령어를 다시 실행합니다.

$ docker compose up -d
[+] Running 3/3
 ✔ Network mars      Created                                                                                                                                                                       0.1s 
 ✔ Container phobos  Started                                                                                                                                                                       0.6s 
 ✔ Container deimos  Started

이제 docker network ls 명령어를 실행하면 새로 생성된 화성(mars) 네트워크를 볼 수 있습니다 (터미널 6.0).

docker network ls

docker network ls (터미널 6.0)

화성(mars) 네트워크를 docker network inspect mars 명령어로 살펴보면 포보스(phobos)데이모스(deimos)가 해당 네트워크에 속한다는 것을 확인할 수 있습니다.

$ docker network inspect mars
[
    {
        "Name": "mars",
        "Id": "9dc9407778df9b2bc336a9d4c912c869592889aeb46ae08050a09c81d7e1828d",
        "Created": "2023-07-22T13:42:23.084152643+05:30",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.21.0.0/16",
                    "Gateway": "172.21.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "a82fa15fa31548cf3f92e6c90174f1d4a78d7b743186e6f05922c0d1a7d58eb7": {
                "Name": "phobos",
                "EndpointID": "1045e43d6c87e9e245dadaa7d62aed56268320820848b2fd330e9dfe4cd2b4bc",
                "MacAddress": "02:42:ac:15:00:03",
                "IPv4Address": "172.21.0.3/16",
                "IPv6Address": ""
            },
            "aadefe0c9ada96fbf61e1f0f5c3d68dfc4e3685f500f771f147c46bb1e37fb16": {
                "Name": "deimos",
                "EndpointID": "a4dbcb28acc4025438201ce55133a3effe1f6695583d92882a721f6a255dadfa",
                "MacAddress": "02:42:ac:15:00:02",
                "IPv4Address": "172.21.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "mars",
            "com.docker.compose.project": "docker-networking",
            "com.docker.compose.version": "2.18.1"
        }
    }
]

"phobos"와 "deimos" 간의 연결을 확인하면 기본 브리지 네트워크에서 테스트했던 것과 동일하게 작동할 것입니다. 이는 두 컨테이너가 이제 "mars" 브리지 네트워크의 일부가 되어 지정된 해당 네트워크 내에서 서로 통신할 수 있기 때문입니다.

지구(Earth) 네트워크

docker-compose.yaml 파일에 "달(Moon)"과 "지구(Earth)" 네트워크를 추가해 봅시다. 다음은 업데이트된 코드 스니펫입니다.

달(Moon) 네트워크

docker-compose.yaml

version: "3"
services:
  deimos:
    container_name: deimos
    image: busybox
    command: sleep infinity
    networks:
      - mars
  phobos:
    container_name: phobos
    image: busybox
    command: sleep infinity
    networks:
      - mars
  moon:
    container_name: moon 
    image: busybox 
    command: sleep infinity
    networks:
      - earth
networks:
  mars:
    name: mars
    driver: bridge
  earth:
    name: earth 
    driver: bridge
  1. docker compose down 명령어를 사용하여 이전 구성을 중지하고 제거하세요.

  2. docker compose up -d 명령어로 모든 컨테이너를 빌드하고 실행하세요.

  3. docker compose ps 명령어로 실행 중인 컨테이너를 확인할 수 있습니다.

$ docker compose ps
NAME                IMAGE               COMMAND             SERVICE             CREATED             STATUS              PORTS
deimos              busybox             "sleep infinity"    deimos              7 seconds ago       Up 5 seconds        
moon                busybox             "sleep infinity"    moon                7 seconds ago       Up 5 seconds        
phobos              busybox             "sleep infinity"    phobos              7 seconds ago       Up 5 seconds
  1. docker network ls 명령어로 모든 도커 네트워크를 나열하면 화성(mars)지구(earth) 네트워크도 표시될 것입니다.
$ docker network ls
NETWORK ID     NAME                        DRIVER    SCOPE
1d540324eaff   bridge                      bridge    local
1bdb95b66268   docker-networking_default   bridge    local
ad19e866e8ad   earth                       bridge    local
f04d3c0691be   host                        host      local
31a102c179b7   mars                        bridge    local
cea56a468e79   none                        null      local
  1. 이제 phobos (Mars 네트워크)에서 moon (Earth 네트워크)으로 통신을 시도해 봅시다.

포보스(phobos)에서 대화형 셸 세션을 생성하고 달(moon)ping을 보내 봅시다 (터미널 7.0).

docker exec -it phobos /bin/sh

터미널 7.0

포보스(Phobos)에서 달(Moon)로

"phobos"에서 "moon"으로 통신할 수 없다는 것을 알 수 있을 것입니다. 하지만 "deimos"와는 여전히 통신할 수 있습니다. 이는 "deimos"가 "phobos"와 동일한 "화성(Mars) 네트워크"에 속해 있기 때문입니다. 반면 "moon"은 "지구(Earth) 네트워크"라는 별도의 네트워크에 속해 있기 때문에 "화성(Mars) 네트워크"에서 직접 접근할 수 없습니다.

  1. docker network inspect earth 명령어를 이용하여 "지구(Earth) 네트워크"를 검사해 봅시다.
$ docker network inspect earth
[
    {
        "Name": "earth",
        "Id": "ad19e866e8add2936f047fd8ba6315cbded2b5ba8ca544afbc6df0cb5e79ecf4",
        "Created": "2023-07-22T14:26:47.928073948+05:30",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.23.0.0/16",
                    "Gateway": "172.23.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "7cc84d8f2854565ab2a63d7aee508861b73539494187b504b4b70f520f7d7af3": {
                "Name": "moon",
                "EndpointID": "86f87b3f0c6a79ade3f301d3811ea4c916e9104821bb7636914172a6f1194960",
                "MacAddress": "02:42:ac:17:00:02",
                "IPv4Address": "172.23.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "earth",
            "com.docker.compose.project": "docker-networking",
            "com.docker.compose.version": "2.18.1"
        }
    }
]
  1. "화성(Mars) 네트워크"도 검사해 보겠습니다. 명령어는 docker network inspect mars입니다.
$ docker network inspect mars
[
    {
        "Name": "mars",
        "Id": "31a102c179b70a6d9b855da6422e0797fa527069f0c324bdc622de5f88805e0c",
        "Created": "2023-07-22T14:26:47.842542312+05:30",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.22.0.0/16",
                    "Gateway": "172.22.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "3a20082a7fafca313d04c397ef2389831dc4cf5224e3ca9c0c05bd062e631d9d": {
                "Name": "deimos",
                "EndpointID": "c61ea9206b1f2406402401ee0be8b1ba7e6d540116d21a51cff0c061371a1d10",
                "MacAddress": "02:42:ac:16:00:03",
                "IPv4Address": "172.22.0.3/16",
                "IPv6Address": ""
            },
            "89ebfa3426dd31e6b466a86e8f26796e736f093dbef13f10799c869b242a8202": {
                "Name": "phobos",
                "EndpointID": "ecf6b66097b19985ed8467dc6056911c0fb16e0eb0938c0dfa66b6e2f10bb025",
                "MacAddress": "02:42:ac:16:00:02",
                "IPv4Address": "172.22.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "mars",
            "com.docker.compose.project": "docker-networking",
            "com.docker.compose.version": "2.18.1"
        }
    }
]

두 네트워크가 두 개의 다른 서브넷에 속해 있는 것을 명확히 알 수 있습니다.

화성(Mars) 네트워크: 172.22.0.0/16

지구(Earth) 네트워크: 172.23.0.0/16

화성(Mars)지구(Earth)와 통신하는 방법을 모릅니다. 따라서 포보스(phobos)에서 달(moon)로 통신할 수 없습니다.

화성(Mars)은 지구(Earth)와 통신하는 방법을 모릅니다

포보스(Phobos)화성(Mars) 네트워크를 통해 데이모스(Deimos)에 ICMP 요청을 보냅니다. 지구(Earth) 네트워크화성(Mars) 네트워크 사이에 연결이 없기 때문에 포보스(Phobos)달(Moon)로 ICMP 요청을 보낼 수 없습니다.

두 네트워크 간의 통신을 하려면 오버레이 네트워크 모드를 사용하는 것이 더 낫습니다.

사용자 정의 브리지 네트워크를 사용하여 격리를 달성할 수 있습니다.

사용자 정의 브리지 네트워크에 연결된 컨테이너는 기본적으로 다른 네트워크로부터 격리됩니다. 이러한 격리는 컨테이너 간의 통신을 그들이 속한 네트워크로 제한하므로 보안을 강화하고 의도치 않은 접근을 방지합니다.

호스트 네트워크 설명 (리눅스에서만 작동)

⚠️ 참고: 호스트 네트워크 드라이버는 리눅스 호스트에서만 작동합니다.

✅ 1부는 모든 운영 체제에서 작동합니다.

호스트 네트워크를 이야기하기 전에 기본 브리지 네트워크에서 컨테이너 포트를 호스트 머신의 포트에 매핑하는 방법을 살펴보겠습니다.

도커 호스트 네트워크를 이해하기 위해 이번 튜토리얼에서는 nginx 도커 이미지를 사용하겠습니다.

Nginx 이미지

("엔진-엑스"로 발음되는) Nginx는 HTTP, HTTPS, SMTP, POP3, IMAP 프로토콜을 위한 오픈 소스 리버스 프록시 서버입니다. 로드 밸런서, HTTP 캐시 및 웹 서버 (원본 서버)의 역할도 수행합니다.

Nginx 서버는 포트 80에서 들어오는 HTTP 요청을 대기하고 있습니다

기본적으로 Nginx 서버는 포트 80에서 들어오는 HTTP 요청을 대기합니다. (웹 브라우저와 같은) 클라이언트가 서버의 IP 주소나 도메인 이름으로 HTTP 요청을 보내면 Nginx는 포트 80에서 해당 요청을 수신하여 처리하고 적절한 응답을 클라이언트에게 반환합니다.

마찬가지로 Nginx 도커 이미지도 포트 80에서 들어오는 HTTP 요청을 대기합니다. 따라서 도커 브리지 네트워크에서 nginx 컨테이너를 빌드할 때 해당 포트 80을 (여러분의 컴퓨터인) 호스트 머신의 포트에 매핑해야 합니다.

아래 예제에서는 nginx 컨테이너의 포트 80을 호스트 머신의 포트 8000에 매핑하겠습니다.

nginx 포트 80을 호스트 머신 포트 8000으로 매핑합니다

  1. 새 프로젝트 폴더를 생성합니다 (원하는 이름으로 하시면 되고 저는 docker-host-network로 하겠습니다).

  2. docker-compose.yaml 파일을 만들고 아래 코드 스니펫을 추가합니다.

docker-compose.yaml

version: "3"
services:
  web:
    container_name: hello_world
    image: nginx
    ports:
      - 8000:80
  1. docker compose up -d 명령어를 실행하여 새 컨테이너를 빌드하고 실행합니다.

이전에 배운 것처럼 이는 nginx 컨테이너에 대한 기본 브리지 네트워크를 생성합니다. 우리의 컨테이너에 "hello_world"라는 이름을 부여했습니다. 포트 매핑에서는 컨테이너의 포트 80을 호스트 머신의 포트 8000에 노출시키고 있습니다.

터미널에서 docker compose ps를 실행하여 해당 포트 매핑을 확인할 수 있습니다.

$ docker compose ps
NAME                IMAGE               COMMAND                  SERVICE             CREATED             STATUS              PORTS
hello_world         nginx               "/docker-entrypoint.…"   web                 5 seconds ago       Up 3 seconds        0.0.0.0:8000->80/tcp, :::8000->80/tcp

http://localhost:8000를 방문하여 nginx 시작 페이지를 볼 수 있습니다.

nginx 기본 시작 페이지

포트 매핑을 사용하면 (호스트 머신인) 로컬 컴퓨터에서 브리지 네트워크의 컨테이너에 접근할 수 있습니다.

브리지 네트워크 컨테이너에서 호스트 네트워크 컨테이너로

이제 "hello_world" 컨테이너 (Nginx 컨테이너)를 호스트 네트워크로 이동할 때 어떤 일이 발생하는지 살펴보겠습니다.

  1. 먼저 docker compose down 명령어를 실행하여 이전 컨테이너를 중지하고 제거합니다.

  2. 그런 다음 docker-compose.yaml 파일을 아래 코드 스니펫처럼 업데이트해야 합니다.

docker-compose.yaml

version: "3"
services:
  web:
    container_name: hello_world
    image: nginx
    network_mode: host
  1. network_modehost로 설정하여 도커 컴포즈에서 컨테이너를 호스트 네트워크에 추가할 수 있습니다.

  2. docker compose up -d 명령어를 실행하여 "hello_world" 컨테이너를 다시 빌드하고 실행합니다.

docker compose up -d
  1. 이제 docker compose ps 명령어를 실행하면 포트 매핑이 보이지 않을 것입니다.
$ docker compose ps
NAME                IMAGE               COMMAND                  SERVICE             CREATED             STATUS              PORTS
hello_world         nginx               "/docker-entrypoint.…"   web                 31 seconds ago      Up 29 seconds
  1. 그러나 컴퓨터에서 http://localhost/ 또는 http://127.0.0.1/에 접속하면 이전에 포트 8000에서 접근했던 동일한 페이지에 접속할 수 있습니다.

Nginx 기본 시작 페이지 http://127.0.0.1

호스트 네트워크에서는 도커 컨테이너가 아래 그림처럼 호스트 시스템과 네트워크 네임스페이스를 공유할 수 있습니다. 컨테이너가 호스트 네트워크 모드로 실행되면 도커의 가상 네트워크 인터페이스를 우회하고 호스트 머신의 네트워크 인터페이스를 직접 사용합니다. 이는 컨테이너가 호스트와 동일한 네트워크 스택, IP 주소 및 네트워크 인터페이스를 가진다는 것을 의미합니다.

호스트 네트워크에서의 nginx 컨테이너 (hello_world 컨테이너)

네트워크에 대한 배경 지식이 없거나 익숙하지 않은 분들은 포트 80이 HTTP 프로토콜의 잘 알려진 포트라는 것을 모를 수 있습니다. 네트워킹 영역에서 포트는 번호에 따라 세 가지 주요 유형으로 분류될 수 있습니다.

  1. 잘 알려진 포트 (0–1023)

  2. 등록된 포트 (1024–49151)

  3. 동적 또는 개인 포트 (49152–65535)

잘 알려진 포트(Well-Known ports)는 인터넷 할당 번호 관리 기관(IANA, Internet Assigned Numbers Authority)에 의해 특정 서비스와 애플리케이션에 할당된 표준화된 포트입니다. 전에 언급했던 포트 21(FTP), 포트 22(SSH), 포트 80(HTTP), 포트 443(HTTPS) 등이 있습니다. 이러한 포트는 인터넷에서 널리 인정된 프로토콜과 서비스에 일반적으로 사용됩니다.

이것이 바로 우리가 브라우저에서 http://localhost:80/ 또는 http://127.0.0.1:80/라고 입력할 필요가 없는 이유입니다. 그렇게 입력하면 브라우저에는 자동으로 각각 http://localhost/와 http://127.0.0.1/로 리디렉션합니다.

http://localhost:80을 http://localhost로 리디렉션합니다

docker network inspect host 명령어를 실행하면 "hello_world" 컨테이너가 존재하는 것을 볼 수 있습니다.

$ docker network inspect host
[
    {
        "Name": "host",
        "Id": "f04d3c0691be5113708f0facee709111d1a34567287e0fd13811349ff67fd599",
        "Created": "2023-06-19T00:21:55.411437884+05:30",
        "Scope": "local",
        "Driver": "host",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": []
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "f071b8951047855ab9a3225455e0fd4d459e60a510f41ba921e7277ac886b41c": {
                "Name": "hello_world",
                "EndpointID": "0fc2921ec1503464a153459c470f3a35247a5cdbe323553f44bb1199f099c37f",
                "MacAddress": "",
                "IPv4Address": "",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

이것이 도커 호스트 네트워크의 마법이 작동하는 방식입니다!


Nginx의 기본 시작 페이지를 바꾸고 싶다면 볼륨(volumes)을 사용하여 변경할 수 있습니다.

  1. docker compose down을 실행하여 이전 컨테이너를 중지하고 제거합니다.

  2. docker-compose.yaml 파일을 아래 코드 스니펫처럼 업데이트하여 volumes: 필드를 추가합니다.

docker-compose.yaml

version: "3"
services:
  web:
    container_name: hello_world
    image: nginx
    network_mode: host
    volumes:
      - ./src:/usr/share/nginx/html/
  1. 프로젝트 디렉터리 안에 src 폴더를 만듭니다.

  2. 그 안에 index.html 파일을 추가합니다.

index.html

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Hello, World!</title>
  </head>
  <body>
    <h1>Hello, World</h1>
  </body>
</html>
  1. 이제 프로젝트 디렉터리는 아래와 같을 것입니다.
.
├── src                # source directory. (.html/.js/.css)
│   ├── index.html          # html page 
└── docker-compose.yml
  1. docker compose up -d 명령어를 실행하고 브라우저에서 변경 사항을 확인합니다.

http://localhost/에서 hello, world! 페이지

짠!!! ✨

결론

결론적으로 이 글에서는 두 가지 기본 도커 네트워킹 모드인 브리지 네트워크와 호스트 네트워크에 중점을 두었습니다. 각 네트워크의 고유한 특성을 살펴보며 컨테이너가 이러한 네트워크 구성 안에서 어떻게 통신하고 연결되는지에 대한 귀중한 통찰을 얻었습니다. 브리지 네트워크를 사용하여 격리된 컨테이너 통신을 찾든 직접적인 호스트 네트워크 통합을 선택하든 이러한 개념의 이해는 효율적이고 안전한 컨테이너화된 애플리케이션을 구축에 필수입니다. 이 글이효과적이고 자신 있게 도커 네트워킹을 탐색하는 데 필요한 기본 지식을 제공했기를 바랍니다.

행복한 네트워킹 되세요! 🚀

참고 자료

GitHub 리포지토리: https://github.com/kesaralive/docker-networking

LinkedIn: https://www.linkedin.com/in/kesaralive

3
Subscribe to my newsletter

Read articles from Ahra Yi directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ahra Yi
Ahra Yi