Docker 네트워킹 문제 해결: 연결 문제를 효과적으로 해결하는 방법
컨테이너 DNS, 사용자 정의 네트워크, 포트 게시, 호스트 액세스, DNS 및 방화벽을 통해 Docker 네트워킹 문제를 해결합니다.
Docker 네트워킹 문제 해결: 연결 문제를 효과적으로 해결하는 방법
Docker 네트워킹 문제는 실패한 연결의 방향을 명확히 할 때 훨씬 쉽게 해결할 수 있습니다. "컨테이너가 연결할 수 없습니다"는 너무 모호합니다. 호스트가 컨테이너에 연결하려는 것인가요? 한 컨테이너가 다른 컨테이너에 연결하려는 것인가요? 컨테이너가 인터넷에 연결하려는 것인가요? 다른 머신에서 트래픽이 들어오는 것인가요? 각 경로는 서로 다른 Docker 동작을 사용합니다.
먼저 경로를 일반 언어로 작성하세요:
호스트의 브라우저 -> localhost:8080 -> 컨테이너 포트 80
api 컨테이너 -> db 컨테이너 -> 포트 5432
worker 컨테이너 -> 공용 인터넷 -> api.example.com:443
원격 노트북 -> 서버 공용 IP -> 게시된 컨테이너 포트
경로를 알게 되면 무작위로 네트워크를 변경하는 대신 각 홉을 테스트할 수 있습니다.
기본 Docker 네트워크 모드 이해하기
대부분의 단일 호스트 Docker 설정은 브리지 네트워킹을 사용합니다. Docker는 호스트에 가상 네트워크를 만들고, 컨테이너에 개인 IP 주소를 할당하며, 선택한 컨테이너 포트를 호스트에 게시할 수 있습니다.
기본 bridge 네트워크도 작동하지만, 사용자 정의 브리지 네트워크는 컨테이너 이름으로 내장 DNS를 제공하므로 애플리케이션에 더 좋습니다. 즉, api 컨테이너와 db 컨테이너가 동일한 사용자 정의 네트워크에 연결되어 있으면 db:5432로 db 컨테이너에 접근할 수 있습니다.
다음과 같이 생성하세요:
docker network create appnet
docker run -d --name db --network appnet postgres:16
docker run -d --name api --network appnet my-api
다른 모드도 있습니다. host 네트워킹은 호스트 네트워크 네임스페이스를 공유하고 일반적인 포트 게시 동작을 비활성화합니다. 일부 Linux 경우에 유용하지만 격리성을 낮춥니다. none은 컨테이너에 네트워크를 제공하지 않습니다. overlay는 다중 호스트 Docker Swarm 네트워킹을 위한 것입니다. Compose는 달리 구성하지 않는 한 프로젝트에 대해 자동으로 사용자 정의 네트워크를 생성합니다.
"네트워크를 찾을 수 없음"
이 오류는 일반적으로 네트워크 이름이 잘못되었거나 네트워크가 명령이 예상하는 것과 다른 컨텍스트에 존재함을 의미합니다.
사용 가능한 네트워크 확인:
docker network ls
사용하려는 네트워크 검사:
docker network inspect appnet
존재하지 않으면 생성하세요:
docker network create appnet
Compose를 사용하는 경우 실제 네트워크 이름에 프로젝트 이름이 접두사로 붙을 수 있습니다. compose.yml에서 backend라는 네트워크는 myproject_backend로 표시될 수 있습니다. 다음을 사용하세요:
docker compose ps
docker network ls
외부 Compose 네트워크를 선언한 경우 Compose가 자동으로 생성하지 않습니다:
networks:
appnet:
external: true
이 경우 수동으로 생성하거나 Compose가 관리하도록 external: true를 제거하세요.
컨테이너 간 통신 실패
두 컨테이너가 이름으로 통신하려면 일반적으로 동일한 사용자 정의 네트워크에 있어야 합니다. 먼저 확인하세요:
docker network inspect appnet
Containers 섹션에서 두 컨테이너를 모두 확인하세요.
그런 다음 기본 도구가 있는 컨테이너에서 테스트하세요. 애플리케이션 이미지에 curl, dig 또는 ping이 포함되지 않을 수 있으며 괜찮습니다. 동일한 네트워크에서 임시 디버그 컨테이너를 사용하세요:
docker run --rm -it --network appnet nicolaka/netshoot
내부에서:
dig db
curl -v http://api:8080/health
nc -vz db 5432
DNS가 실패하면 컨테이너가 동일한 사용자 정의 네트워크에 없거나 기본 브리지 네트워크를 사용하면서 동일한 방식으로 이름 확인을 제공하지 않기 때문일 수 있습니다. DNS는 작동하지만 연결이 실패하면 대상 서비스가 예상 포트에서 수신 중인지 확인하세요.
대상 컨테이너 내부:
docker exec -it api sh
ss -ltnp || netstat -ltnp
일반적인 실수는 컨테이너 내부에서 앱을 127.0.0.1에 바인딩하는 것입니다. 이는 해당 컨테이너 내부의 루프백에서만 수신합니다. 다른 컨테이너는 접근할 수 없습니다. 앱이 0.0.0.0에서 수신하도록 구성하세요.
또한 컨테이너 간 트래픽에는 호스트 게시 포트가 아닌 컨테이너 포트를 사용해야 합니다. 데이터베이스가 컨테이너에서 5432 포트를 수신하면 다른 컨테이너는 localhost:15432 또는 게시된 호스트 포트가 아닌 db:5432를 사용해야 합니다.
호스트가 컨테이너에 접근할 수 없음
호스트가 브리지 네트워크 컨테이너의 서비스에 접근하려면 일반적으로 게시된 포트가 필요합니다:
docker run -d --name web -p 8080:80 nginx
이 명령은 호스트 포트 8080을 컨테이너 포트 80에 매핑합니다. 호스트에서 테스트:
curl -v http://localhost:8080
Docker가 게시한 내용 확인:
docker port web
docker ps --format 'table {{.Names}} {{.Ports}}'
포트 매핑이 없으면 Dockerfile의 EXPOSE는 포트를 게시하지 않습니다. EXPOSE는 문서화 및 메타데이터입니다. 여전히 -p 또는 Compose ports:가 필요합니다.
포트가 게시되었지만 연결이 실패하면 네 가지를 확인하세요:
- 애플리케이션이 컨테이너 내부에서 컨테이너 포트로 수신 중입니다.
- 애플리케이션이
127.0.0.1만이 아닌0.0.0.0에서 수신 중입니다. - 호스트 방화벽이 호스트 포트를 차단하지 않습니다.
- 다른 프로세스가 이미 호스트 포트를 사용하고 있지 않습니다.
호스트 포트 충돌 찾기:
sudo lsof -i :8080
# 또는
sudo ss -ltnp 'sport = :8080'
Compose의 경우 구문을 기억하세요:
ports:
- "8080:80"
왼쪽은 호스트 포트, 오른쪽은 컨테이너 포트입니다.
컨테이너가 인터넷에 접근할 수 없음
IP 연결과 DNS를 별도로 테스트하세요:
docker exec -it app sh
ping -c 2 1.1.1.1
ping -c 2 example.com
일부 이미지에는 ping이 포함되지 않습니다. curl을 사용할 수 있으면 사용하세요:
curl -I https://example.com
IP는 작동하지만 이름이 실패하면 DNS 문제입니다. 확인:
cat /etc/resolv.conf
Docker는 일반적으로 리졸버 설정을 주입합니다. 회사 VPN, 사용자 정의 DNS, Docker Desktop 네트워킹이 이를 복잡하게 만들 수 있습니다. Docker 데몬 설정에서 데몬 수준 DNS를 구성하거나 특정 컨테이너에 DNS를 전달할 수 있습니다:
docker run --dns 1.1.1.1 ...
내부 이름을 확인해야 하는 회사 환경에서는 공용 DNS를 무분별하게 사용하지 마세요. 네트워크에 적합한 DNS 서버를 사용하세요.
IP와 DNS 모두 작동하지 않으면 컨테이너가 --network none에 있는지, 호스트 방화벽/NAT 규칙이 손상되었는지, Docker 데몬에 사용자 정의 네트워킹 설정이 있는지, 호스트 자체에 인터넷 접속이 가능한지 확인하세요.
컨테이너가 호스트의 서비스에 접근해야 함
컨테이너에서 localhost는 호스트가 아닌 컨테이너 자체를 의미합니다. 이는 가장 흔한 Docker 네트워킹 놀라움 중 하나입니다.
Docker Desktop에서 host.docker.internal은 일반적으로 호스트로 확인됩니다. 최신 Linux용 Docker Engine에서는 호스트 게이트웨이 항목을 추가할 수 있습니다:
docker run --add-host=host.docker.internal:host-gateway ...
그러면 컨테이너가 다음을 호출할 수 있습니다:
curl http://host.docker.internal:3000
호스트 서비스가 Docker에서 접근 가능한 주소에서 수신 중인지 확인하세요. 환경에서 Docker가 접근할 수 없는 루프백 바인딩만 사용하지 마세요. 로컬 개발 서버가 127.0.0.1에만 바인딩된 경우 OS 및 보안 요구 사항에 따라 0.0.0.0 또는 호스트 인터페이스에 바인딩해야 할 수 있습니다.
원격 머신이 컨테이너에 접근할 수 없음
호스트가 localhost:8080에 접근할 수 있지만 다른 머신이 server-ip:8080에 접근할 수 없다면 Docker는 문제가 아닐 수 있습니다. 호스트 방화벽, 클라우드 보안 그룹, 라우터/NAT, Docker가 루프백에만 게시했는지 확인하세요.
다음은 모든 호스트 인터페이스에 게시합니다:
docker run -p 8080:80 nginx
다음은 localhost에만 게시합니다:
docker run -p 127.0.0.1:8080:80 nginx
루프백 전용 게시는 로컬 개발이나 리버스 프록시 설정에 종종 바람직하지만, 설계상 원격 접근을 차단합니다.
클라우드 서버에서는 제공업체 방화벽도 확인하세요. VM에서 ufw를 열어도 클라우드 보안 그룹이 여전히 포트를 차단하면 소용없습니다.
Compose 네트워킹 실수
Compose는 각 서비스에 서비스 이름을 기반으로 DNS 이름을 제공합니다. 서비스 이름이 db인 경우 다른 서비스는 일반적으로 localhost가 아닌 db에 연결해야 합니다.
예시:
services:
api:
build: .
environment:
DATABASE_URL: postgres://postgres:postgres@db:5432/app
depends_on:
- db
db:
image: postgres:16
depends_on은 시작 순서를 제어하며, 준비 상태를 제어하지 않습니다. 데이터베이스 컨테이너가 Postgres가 연결을 수락할 준비가 되기 전에 시작될 수 있습니다. 애플리케이션은 연결을 재시도하거나 헬스체크 인식 시작 패턴을 사용해야 합니다.
또한 ports와 expose를 구분하세요. ports는 호스트에 게시합니다. expose는 연결된 서비스에 포트를 문서화하거나 노출하지만 동일한 방식으로 호스트에서 접근할 수 있게 하지 않습니다.
패킷 수준 디버깅
일반적인 확인으로 문제가 설명되지 않을 때 네트워크 디버그 이미지를 사용하세요:
docker run --rm -it --network container:<target-container> nicolaka/netshoot
이 명령은 대상 컨테이너의 네트워크 네임스페이스에 조인하여 앱 이미지에 도구를 설치하지 않고도 동일한 관점에서 네트워킹을 검사할 수 있게 합니다.
유용한 명령:
ip addr
ip route
cat /etc/resolv.conf
dig service-name
curl -v http://service-name:port
tcpdump -nn -i any port 8080
패킷이 도착하는지 확인해야 할 때 tcpdump를 사용하세요. 패킷이 전혀 도착하지 않으면 컨테이너 이전을 확인하세요: 게시, 방화벽, 라우팅, 로드 밸런서. 패킷이 도착하고 응답이 나가지 않으면 컨테이너 또는 애플리케이션 내부를 확인하세요.
간단한 문제 해결 흐름
대부분의 Docker 네트워킹 문제에 대해 이 순서를 사용하세요:
- 정확한 경로 정의: 호스트에서 컨테이너로, 컨테이너에서 컨테이너로, 컨테이너에서 인터넷으로, 또는 원격에서 호스트를 거쳐 컨테이너로.
docker network inspect로 네트워크 연결 확인.- 소스 측에서 이름 확인 확인.
- 대상 프로세스가 올바른 인터페이스와 포트에서 수신 중인지 확인.
- 호스트에서 컨테이너로 교차하는 트래픽에 대해서만 포트 게시 확인.
- Docker 외부의 방화벽, VPN, 프록시, DNS, 클라우드 보안 규칙 확인.
대부분의 Docker 네트워킹 문제는 깊은 Docker 버그가 아닙니다. 일반적으로 잘못된 이름, 잘못된 포트, 루프백 바인딩, 누락된 게시 포트, DNS 가정, 또는 경계의 잘못된 측면에서 트래픽을 테스트하는 것입니다.