노트

컨테이너 네트워킹

Container Networking

인프라#docker#network · 연결된 개념 8개

쉽게 말하면

도커 네트워크는 아파트 단지 인터폰처럼 같은 네트워크에 붙은 컨테이너끼리 'db' 같은 이름만으로 서로를 부르게 해 줘요. 컨테이너가 다시 떠서 IP가 바뀌어도 이름은 그대로라 헤매지 않아요.

비유가 깨지는 곳 단지 안과 밖은 주소가 달라요. 컨테이너 안의 localhost는 호스트가 아니라 그 컨테이너 자신이고 PC의 hosts 파일도 없어요. 그래서 이미지에 박힌 주소는 환경변수로 덮어 서비스 이름을 가리키게 해요.

컨테이너는 각자 격리된 네트워크를 가지므로, 서로 통신하려면 같은 도커 네트워크에 붙인다. 같은 사용자 정의 네트워크(user-defined network) 안에서는 도커 내장 DNS(Domain Name System)가 컨테이너 이름(또는 별칭)을 IP로 풀어 주어, IP 대신 이름으로 접속한다.

docker network create app-net
docker run -d --network app-net --network-alias db \
  -e POSTGRES_PASSWORD=secret postgres:17
docker run -d --network app-net -e DB_HOST=db -p 3000:3000 app
  • 앱은 db:5432로 DB에 닿는다. 컨테이너가 다시 떠서 IP가 바뀌어도 이름은 그대로다
  • 문제 해결용으로 nicolaka/netshoot 같은 이미지를 같은 네트워크에 띄워 dig db로 이름 해석을 확인할 수 있다
  • Docker Compose는 프로젝트마다 네트워크를 자동으로 만들고, 서비스 이름이 곧 호스트 이름이 된다

localhost가 가리키는 곳

  • 컨테이너 안의 localhost는 그 컨테이너 자신이다. 호스트의 서비스가 아니다
  • 컨테이너에서 호스트로 가려면 Docker Desktop에서는 host.docker.internal을 쓴다
  • 호스트에서 컨테이너로 가려면 -p 호스트포트:컨테이너포트로 포트를 게시(publish)해야 한다

이 "안에서 보는 주소와 밖에서 보는 주소가 다르다"는 점이 Kafka 같은 서비스에서 함정이 된다(Kafka advertised listeners 함정). 쿠버네티스에서는 Service가 같은 역할(고정 이름 + 로드밸런싱)을 한다(쿠버네티스 핵심 개념). 이름 해석의 일반 원리는 DNS와 CNAME를 본다.

컨테이너로 옮기면 깨지는 주소

개발자 PC에서 localhost:8080이나 hosts 파일에 적은 local.example.test 같은 이름으로 다른 서버를 부르던 설정은 컨테이너 안에서 거의 다 깨진다. localhost는 그 컨테이너 자신이고, PC의 hosts 파일은 컨테이너에 없기 때문이다. 이름을 못 풀면 연결 단계에서 실패하고, 앱에 따라 503 같은 오류로 드러난다. 이미지에 박힌 주소는 고치지 말고 환경변수로 바깥에서 덮어 서비스 이름(http://api:8080)을 가리키게 한다. 같은 이미지를 환경마다 설정만 바꿔 쓰는 원칙은 저장소 역할 분담 (DB·캐시·큐·검색)에도 이어진다.

여러 Compose 프로젝트를 한 네트워크에

Compose는 프로젝트마다 기본 네트워크를 따로 만든다. 인프라(DB·캐시·브로커) Compose와 앱 Compose를 따로 띄우면 서로 이름으로 찾지 못한다. 미리 만든 네트워크를 양쪽에서 external로 선언하면 같은 네트워크에 붙는다.

networks:
  default:
    name: shared-net
    external: true   # Compose가 만들지 않는다. 없으면 에러
docker network create shared-net 2>/dev/null || true   # 이미 있으면 넘어가기
docker compose -f infra.yml up -d
docker compose -f app.yml up -d

external 네트워크는 Compose 밖에서 수명을 관리한다는 뜻이라 docker compose down이 지우지 않는다.

비밀값

예제에서는 -e로 비밀번호를 넘기지만, 환경변수는 docker inspect나 로그로 새기 쉽다. 운영에서는 비밀 파일 마운트나 시크릿 관리 기능을 쓴다.

출처: Docker 네트워킹: User-defined networks · DNS services · Published ports · Compose networks: external · Networking in Compose: Use an existing network

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • Dockerfile: 레이어 캐시와 멀티 스테이지

    Dockerfile은 이미지를 만드는 명령을 순서대로 적은 파일이다. 파일시스템을 바꾸는 명령(RUN·COPY·ADD)이 각각 레이어(layer)가 되고, 바뀌지 않은 레이어는 캐시를 재사용한다. 그래서 자주 바뀌는 것을 뒤에 두는 순서가 빌드 속도를 좌우한다.

  • 도커 볼륨과 바인드 마운트

    컨테이너의 파일시스템은 이미지 위에 얹힌 얇은 쓰기 레이어라, 컨테이너를 지우면 그 안에 쓴 데이터도 사라진다. 데이터를 남기거나 호스트와 파일을 공유하려면 컨테이너 밖의 저장 공간을 마운트한다. 방법은 named volume과 bind mount 두 가지다.

  • kubectl 디버깅 치트시트

    kubectl은 쿠버네티스 클러스터를 조회·조작하는 CLI(Command-Line Interface)다. 앱 개발자가 가장 자주 쓰는 건 "내 Pod가 떠 있나, 왜 죽었나"를 확인하는 명령들이다.

  • Tailscale Serve와 Funnel

    Tailscale은 WireGuard 위에 만든 메시 VPN(mesh Virtual Private Network)으로, 내 기기들을 하나의 사설망(tailnet)으로 묶는다. tailscale serve는 내 기기의 로컬 서비스를 tailnet 안에서만 HTTPS로 열어 주고, tailscale funnel은 같은 서비스를 공개 인터넷에 연다.

  • 환경변수 스코프와 source

    셸에서 만든 변수는 기본적으로 그 셸 안에서만 보이고, export해야 그 셸이 띄우는 자식 프로세스(child process)에 전달된다. 그리고 스크립트를 실행하면 자식 프로세스에서 돌기 때문에, 스크립트가 바꾼 환경은 부모 셸로 돌아오지 않는다.

보기 옵션