Fuwari Banner
지니제스트Tech Archive
Docker7분 소요

Docker Pool overlaps 에러 해결

ggeniezst

Docker Compose 실행 시 발생하는 Pool overlaps with other one on this address space 에러의 커널 IPAM 원인을 분석하고, daemon.json default-address-pools 설계를 통해 사내망 및 VPN 충돌을 영구적으로 방지하는 가이드입니다.

Sponsored

Docker 환경에서 docker compose up 또는 docker network create 명령을 내렸을 때 Pool overlaps with other one on this address space 에러가 출력되며 컨테이너 구동이 멈추는 현상이 자주 발생합니다. 특히 사내 인트라넷이나 VPN(Virtual Private Network)이 연동된 호스트 서버에서 이 오류가 나타나면, 신규 컨테이너 배포 실패에 그치지 않고 호스트의 기존 사내 데이터베이스 접속이나 내부 API 호출까지 마비되는 복합 네트워크 장애로 번집니다.

Docker 데몬의 기본 IPAM(IP Address Management) 할당 드라이버가 호스트 네트워크 환경을 고려하지 않고 RFC 1918 사설 IP 대역을 선점하면서 발생하는 충돌 메커니즘을 규명하고, daemon.json 전역 설정을 통해 사내망과 절대 겹치지 않는 서브넷 풀을 설계하는 해결 절차를 다룹니다.


장애 재현 상황과 시스템 에러 로그 분석

복수의 마이크로서비스를 docker compose로 기동하거나 독립된 브리지 네트워크를 수동으로 프로비저닝할 때 커널 라우팅 테이블과 Docker IPAM 간의 서브넷 충돌이 표면화됩니다.

신규 서비스를 배포하기 위해 Compose 명령어를 실행하면 다음과 같은 에러가 표준 에러 출력으로 반환됩니다:

BASH
docker compose -f docker-compose.infra.yml up -d
TEXT
[+] Running 0/1
 ✗ Network infra_default  Error
Error response from daemon: Pool overlaps with other one on this address space

이 오류는 Docker 데몬이 새 브리지 네트워크에 할당하려는 서브넷 대역이 이미 호스트 시스템의 다른 인터페이스나 기존 Docker 네트워크에 할당되어 있어 IP 할당 요청을 거부했음을 의미합니다.

호스트 서버의 systemd 저널 로그를 확인하면 데몬 내부에서 실패한 트랜잭션의 상세 로그를 확인할 수 있습니다:

BASH
# Docker 데몬의 최근 네트워크 관련 시스템 로그 추적
journalctl -u docker.service --no-pager -n 50 | grep -E 'level=(error|warning)|bridge|ipam'
TEXT
level=error msg="[resolver] failed to allocate network IP for container web_api: address already in use"
level=error msg="Handler for POST /v1.45/networks/create failed: Pool overlaps with other one on this address space"
kernel: br-e2a8b941d3c1: port 1(veth3a9b1c2) entered disabled state

호스트 운영체제의 라우팅 테이블(ip route)을 출력해 보면 충돌 원인이 명확히 드러납니다:

BASH
# 호스트 커널의 IPv4 라우팅 테이블 확인
ip route show
TEXT
default via 192.168.1.1 dev eth0 proto dhcp src 192.168.1.150 metric 100
10.8.0.0/24 dev tun0 proto kernel scope link src 10.8.0.42           # 사내 VPN 터널 인터페이스
172.16.0.0/16 dev eth1 proto kernel scope link src 172.16.10.25      # 사내 DB 전용 내부망
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1     # 기본 Docker 브리지
172.18.0.0/16 dev br-3c81fa72d110 proto kernel scope link src 172.18.0.1
172.19.0.0/16 dev br-88a2c10b4f32 proto kernel scope link src 172.19.0.1

사내 내부망(172.16.0.0/16)이 eth1에 바인딩되어 있는 상황에서, Docker 데몬이 추가 브리지 네트워크에 172.16.0.0 대역이나 인접 대역을 할당하려고 시도하면서 커널 네트워크 스택 수준에서 충돌이 감지된 것입니다.


Linux 커널 네트워크와 Docker IPAM 동작 원리

Docker 컨테이너가 호스트 외부 및 다른 컨테이너와 통신할 수 있는 이유는 Linux 커널의 Network Namespace(netns), 가상 이더넷 페어(veth), 소프트웨어 브리지(bridge) 구조 덕분입니다.

사용자 정의 브리지 네트워크를 생성하면 Linux 커널은 br-<network-id> 형태의 가상 스위치 인터페이스를 생성하고, 컨테이너가 구동될 때 veth 인터페이스의 한쪽 끝을 컨테이너 네임스페이스(eth0)에, 반대쪽 끝을 호스트 브리지에 연결합니다.

Docker IPAM의 기본 풀 할당 알고리즘

문제는 Docker 데몬 내부의 기본 IPAM(IP Address Management) 드라이버가 서브넷을 선택하는 메커니즘에서 비롯됩니다:

  1. Docker는 사설 IP 표준 규격인 RFC 1918 대역(172.16.0.0/12, 10.0.0.0/8, 192.168.0.0/16) 중에서 기본 주소 풀을 미리 정의해 둡니다.
  2. 기본 브리지인 docker0에는 전통적으로 172.17.0.1/16이 고정 할당됩니다.
  3. 이후 사용자가 docker network create나 Compose 실행으로 신규 브리지를 만들 때마다, IPAM 드라이버는 172.18.0.0/16, 172.19.0.0/16 순서로 거대한 /16(호스트 65,534개) 블록을 통째로 소비하며 순차 할당합니다.
  4. 172.31.0.0/16까지 모두 소진되면 192.168.0.0/20 단위 블록으로 넘어갑니다.

사내망 및 VPN 패킷 탈취 현상

호스트가 기업 내부망(172.16.0.0 ~ 172.31.255.255)이나 가정용 공유기 대역(192.168.0.0/16), 또는 VPN 가상 인터페이스(tun0, wg0)를 사용하고 있을 때 심각한 부작용이 발생합니다.

Docker IPAM이 호스트 물리 인터페이스가 이미 점유하고 있는 서브넷 대역을 고려하지 않고 동일한 대역의 브리지(br-*)를 생성하면, 커널 라우팅 테이블에 더 구체적인 링크 로컬 경로가 등록됩니다. 이로 인해 사내 DB 서버(예: 172.20.5.10)로 전송되어야 할 패킷이 게이트웨이 대신 Docker 가상 브리지 인터페이스로 라우팅되어 패킷이 유실되고 통신이 끊어집니다.

과거 다루었던 시놀로지 NAS에서 Docker Compose로 네트워크 격리 환경 구축하기에서도 언급했듯이, 브리지 네트워크 간의 서브넷 격리와 명시적인 IP 대역 지정은 안정적인 서비스 운영의 필수 전제조건입니다.


현재 충돌 중인 네트워크 인터페이스 진단

설정 파일을 수정하기에 앞서, 현재 시스템에서 어떤 Docker 네트워크가 비정상적으로 생성되어 IP 대역을 점유하고 있는지 확인해야 합니다.

점유 중인 Docker 네트워크 서브넷 전수 조사

터미널에서 다음 명령어를 실행하여 등록된 모든 Docker 네트워크의 이름, 드라이버, 실제 할당된 서브넷을 일괄 조회합니다:

BASH
# 등록된 모든 Docker 네트워크의 이름과 서브넷 대역 추출
docker network inspect $(docker network ls -q) --format '{{.Name}} [{{.Driver}}]: {{range .IPAM.Config}}{{.Subnet}}{{end}}'

출력 결과 예시는 다음과 같습니다:

TEXT
bridge [bridge]: 172.17.0.0/16
host [host]: 
none [null]: 
infra_backend [bridge]: 172.18.0.0/16
monitoring_net [bridge]: 172.20.0.0/16
company_vpn_sim [bridge]: 172.16.0.0/16

물리 인터페이스 IP와 겹치는 대역 교차 검증

호스트의 실제 네트워크 인터페이스 정보와 Docker 서브넷을 비교합니다:

BASH
# 호스트의 모든 활성 네트워크 인터페이스 요약 출력
ip -brief address show
TEXT
lo               UNKNOWN        127.0.0.1/8 ::1/128 
eth0             UP             192.168.1.150/24 
eth1             UP             172.16.10.25/16 
docker0          DOWN           172.17.0.1/16 
br-88a2c10b4f32  UP             172.20.0.1/16 
tun0             UNKNOWN        10.8.0.42/24

eth1의 물리 대역인 172.16.10.25/16과 Docker의 브리지 대역이 명백히 겹치거나 인접해 있음을 파악할 수 있습니다.


daemon.json 전역 서브넷 풀 설계 및 영구 해결

개별 docker-compose.yml 파일마다 수동으로 서브넷을 지정하는 방식은 신규 프로젝트가 추가될 때마다 개발자가 대역을 일일이 기억하고 관리해야 하므로 실무에서 누락되기 쉽습니다.

Docker 데몬의 설정 파일인 /etc/docker/daemon.jsondefault-address-pools를 정의하면, 앞으로 생성되는 모든 사용자 정의 브리지 네트워크가 사전에 지정한 안전한 전용 대역 내에서만 잘게 분할되어 자동 할당됩니다.

daemon.json 작성 및 파라미터 구성

/etc/docker/daemon.json 파일을 열어 다음 설정을 반영합니다. 기존 파일이 없다면 새로 생성합니다:

JSON
{
  "bip": "10.200.0.1/24",
  "default-address-pools": [
    {
      "base": "10.201.0.0/16",
      "size": 24
    },
    {
      "base": "10.202.0.0/16",
      "size": 24
    }
  ],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "20m",
    "max-file": "3"
  }
}

설정된 주요 파라미터의 역할은 다음과 같습니다:

  • bip (Bridge IP): 컨테이너에 네트워크를 별도 지정하지 않았을 때 연결되는 기본 docker0 브리지의 IP 및 서브넷입니다. 172.17.0.1/16 대신 10.200.0.1/24를 부여하여 사내망 충돌 가능성을 원천 배제합니다.
  • default-address-pools: docker network create나 Compose가 새 브리지를 만들 때 참조하는 전역 주소 풀입니다.
  • base: Docker가 자유롭게 서브넷을 생성할 수 있는 상위 대역입니다. 여기서는 사내망과 가정용 네트워크에서 거의 쓰이지 않는 10.201.0.0/1610.202.0.0/16을 지정했습니다.
  • size: 상위 대역(base)을 분할하여 각 브리지 네트워크에 배정할 서브넷 마스크 크기입니다. 기본값인 16 대신 24로 지정하면 서브넷 하나당 254개의 컨테이너를 수용하면서, 총 512개(256 + 256)의 독립된 브리지 네트워크를 고갈 없이 생성할 수 있습니다.

Docker Compose 환경에서의 명시적 IPAM 선언

특정 프로덕션 프로젝트에서 고정 IP를 지정하거나 격리된 서브넷이 반드시 필요한 경우에는 docker-compose.yml 내부에서 커스텀 IPAM을 명시적으로 선언할 수 있습니다:

YAML
version: "3.8"

services:
  database:
    image: postgres:16-alpine
    container_name: production_db
    environment:
      POSTGRES_PASSWORD: securepassword
    networks:
      backend_net:
        ipv4_address: 10.205.10.5

networks:
  backend_net:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 10.205.10.0/24
          gateway: 10.205.10.1

전역 daemon.json 풀과 수동 Compose 서브넷 대역이 겹치지 않도록 10.205.x.x와 같이 명확히 분리된 사설 대역을 선택해야 합니다.


단계별 복구 프로세스와 적용 후 검증

daemon.json을 수정한 뒤 기존 충돌 네트워크를 정리하고 데몬을 재시작하여 정상 동작을 확인하는 검증 절차입니다.

1단계: 기존 충돌 네트워크 및 고아 인터페이스 정리

기존에 잘못 생성되어 남아있는 브리지 네트워크를 정리합니다. 컨테이너가 연결되어 있지 않은 네트워크는 일괄 삭제합니다:

BASH
# 실행 중이지 않은 미사용 네트워크 일괄 정리
docker network prune -f

# 특정 충돌 네트워크를 수동 제거해야 하는 경우
docker network rm infra_default company_vpn_sim

만약 Docker 컨테이너는 종료되었으나 커널에 고아 브리지 인터페이스가 남아있다면 링크를 수동으로 내립니다:

BASH
# DOWN 상태의 가상 브리지 인터페이스 수동 삭제
ip link set dev br-3c81fa72d110 down
ip link delete dev br-3c81fa72d110 type bridge

2단계: 데몬 설정 리로드 및 서비스 재시작

수정된 설정을 Docker 엔진에 반영합니다:

BASH
# systemd 설정 리로드 및 Docker 데몬 재시작
systemctl daemon-reload
systemctl restart docker

데몬이 재시작되면 기본 docker0 브리지의 IP가 변경되었는지 확인합니다:

BASH
ip addr show docker0
TEXT
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    inet 10.200.0.1/24 brd 10.200.0.255 scope global docker0
       valid_lft forever preferred_lft forever

기존의 172.17.0.1/16 대신 설정한 10.200.0.1/24로 깔끔하게 교체된 것을 확인할 수 있습니다.

3단계: 신규 Compose 프로젝트 배포 및 할당 서브넷 검증

이전에 실패했던 Compose 서비스를 다시 기동하여 신규 서브넷이 정상 배정되는지 검증합니다:

BASH
docker compose -f docker-compose.infra.yml up -d
TEXT
[+] Running 2/2
 ✔ Network infra_default  Created
 ✔ Container infra-web-1  Started

생성된 신규 네트워크의 서브넷을 확인합니다:

BASH
docker network inspect infra_default --format '{{range .IPAM.Config}}{{.Subnet}} (Gateway: {{.Gateway}}){{end}}'
TEXT
10.201.0.0/24 (Gateway: 10.201.0.1)

default-address-pools에 정의한 10.201.0.0/16 대역 내에서 /24 단위로 정확히 분할되어 할당되었음을 확인할 수 있습니다.

호스트의 라우팅 테이블에서도 사내망(172.16.0.0/16)과의 간섭 없이 10.201.0.0/24 dev br-... 경로가 생성되어 사내 DB 및 VPN 트래픽이 정상 복구됩니다.


실무 엔지니어들이 자주 겪는 사이드 이펙트 FAQ

네트워크 대역 수정 후 실무 현장에서 흔히 직면하는 문제들과 그에 대한 실질적인 대처 방안입니다.

Q1. daemon.json을 수정하고 재시작했는데 기존 컨테이너들의 IP 대역이 바뀌지 않습니다.

Docker 데몬은 이미 생성된 네트워크의 메타데이터를 /var/lib/docker/network/files/local-kv.db에 보관합니다. 따라서 데몬을 재기동하더라도 기존에 생성된 브리지 네트워크는 이전 대역을 그대로 유지합니다.

신규 설정을 적용하려면 기존 컨테이너를 중지하고 네트워크를 완전히 삭제한 후 재생성해야 합니다:

BASH
# 컨테이너 및 연관 네트워크 동시 삭제 후 재생성
docker compose down
docker compose up -d

Q2. default-address-pools의 size 파라미터는 어떤 기준으로 결정해야 하나요?

기본값인 size: 16을 그대로 두면 하나의 브리지 네트워크가 65,534개의 IP를 독점하므로 상위 대역이 10.201.0.0/16인 경우 단 하나의 네트워크만 생성되고 풀이 즉시 고갈됩니다.

일반적인 마이크로서비스 아키텍처에서는 한 네트워크에 컨테이너가 수십 개 이상 들어가지 않으므로 size: 24(최대 254개 IP)로 설정하는 것이 가장 이상적입니다. 컨테이너 개수가 아주 적은 단순 개발 환경이라면 size: 26(최대 62개)이나 size: 27(최대 30개)로 더 잘게 쪼개어 수천 개의 격리 네트워크를 운용할 수도 있습니다.

Q3. bip 설정과 default-address-pools를 둘 다 지정해야 하는 이유는 무엇인가요?

두 설정의 적용 대상이 서로 다릅니다.

  • bip: Docker 데몬이 실행될 때 생성되는 기본 docker0 인터페이스 전용입니다. 아무런 네트워크 옵션 없이 docker run을 실행하는 컨테이너들이 이 대역을 사용합니다.
  • default-address-pools: 사용자가 직접 생성하거나 docker compose가 자동으로 생성하는 사용자 정의 브리지 네트워크 전체에 적용됩니다.

따라서 둘 중 하나만 변경하면 나머지 한쪽에서 사내망 충돌이 여전히 발생할 수 있으므로, 두 옵션을 모두 지정하여 전체 환경을 격리하는 것이 표준 구성입니다.

Q4. 프로덕션 환경에서 컨테이너 중단 없이 충돌 네트워크를 변경할 수 있나요?

실행 중인 컨테이너를 끄지 않고 무중단으로 안전하게 대역을 전환하려면 docker network connectdisconnect를 순차적으로 활용할 수 있습니다:

BASH
# 1. 안전한 새 대역의 네트워크 임시 생성
docker network create --subnet 10.201.50.0/24 temp_safe_net

# 2. 실행 중인 프로덕션 컨테이너에 새 네트워크 추가 연결 (통신 유지)
docker network connect temp_safe_net my_production_api

# 3. 기존 충돌 대역 네트워크 연결 해제
docker network disconnect infra_default my_production_api

# 4. 기존 충돌 네트워크 제거
docker network rm infra_default

새 네트워크를 먼저 붙인 뒤 구 네트워크를 분리하면 서비스 다운타임 없이 안전하게 패킷 라우팅 경로를 전환할 수 있습니다.

Sponsored