NYO_O

Docker Container 간 통신 원리(DNS)와 docker-compose 라이프사이클 분리 전략 본문

DevOps/Docker

Docker Container 간 통신 원리(DNS)와 docker-compose 라이프사이클 분리 전략

NYO_O 2026. 5. 26. 15:56
반응형

도커(Docker)의 기본적인 명령어와 실행 방법을 익히고 나면, 자연스럽게 다음 단계의 고민이 찾아옵니다. "데이터베이스 컨테이너와 스프링 부트 컨테이너를 각각 띄웠는데, 얘네들은 서로 어떻게 통신하는 걸까?"

일반적인 물리 서버 환경에서는 고정된 IP 주소를 통해 통신하지만, 도커 컨테이너는 껐다 켤 때마다 내부 IP가 동적으로 변하는 특징을 가지고 있습니다. 오늘은 도커가 이 문제를 어떻게 해결하는지 내부 DNS와 브릿지 네트워크의 원리를 알아보겠습니다.

이미지(Image)와 컨테이너(Container)

본격적인 네트워크 이야기에 앞서, 도커의 뼈대가 되는 두 가지 핵심 개념을 짚고 넘어가겠습니다. 도커는 무거운 OS 전체를 띄우는 가상 머신(VM)과 달리, 호스트 OS의 커널을 공유하며 프로세스 단위로 격리하기 때문에 훨씬 가볍고 빠릅니다. 이 기술을 완성하는 것이 바로 이미지와 컨테이너입니다.

  • Docker 이미지 (Image): 애플리케이션을 실행하는 데 필요한 자바 빌드 파일, 런타임(JDK), 시스템 라이브러리, 환경 설정을 모두 포함하는 '읽기 전용(Read-only) 패키지'입니다. 디스크에 저장된 정적인 상태의 파일 묶음이라고 볼 수 있습니다.
  • Docker 컨테이너 (Container): 정적인 이미지를 기반으로 메모리에 올라가 생성된 '실행 중인 격리 프로세스'입니다. 독자적인 네트워크 환경과 파일 시스템을 가지며, 애플리케이션이 실제로 살아서 동작하는 동적인 상태를 의미합니다.

2026.05.26 - [Tech/Docker] - 도커 이미지와 컨테이너의 차이, 그리고 Docker Compose

 

도커 이미지와 컨테이너의 차이, 그리고 Docker Compose

백엔드 개발을 하다 보면 가장 많이 듣는 기술 중 하나가 바로 도커(Docker)입니다. "도커로 띄워주세요", "이미지 말아주세요" 같은 실무 용어들을 처음 접하면 다소 막막하게 느껴질 수 있습니다.

ddangnyo.tistory.com

Dockerfile 해부하기: 자바 앱은 어떻게 이미지가 될까?

자바 애플리케이션을 도커 이미지로 만들기 위해서는 일종의 설계도인 Dockerfile이 필요합니다. 도커 이미지는 여러 개의 '계층(Layer)'이 차곡차곡 쌓여서 만들어지며, 명령어 한 줄이 실행될 때마다 새로운 계층이 추가됩니다.

FROM amazoncorretto:17-al2023-jdk
WORKDIR /app
COPY build/libs/*SNAPSHOT.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
  • FROM amazoncorretto:17-al2023-jdk 베이스 이미지(Base Image)를 지정합니다. 컨테이너의 가장 밑바닥이 되는 리눅스 운영체제와 자바 17 실행 환경(JDK) 계층을 형성합니다.
  • WORKDIR /app 컨테이너 내부의 작업 디렉토리를 /app으로 설정합니다. 이후 실행되는 명령어들은 모두 이 폴더를 기준으로 작동하는 환경 설정 계층이 추가됩니다.
  • COPY build/libs/*SNAPSHOT.jar app.jar 호스트 컴퓨터(개발 PC)에서 빌드된 애플리케이션 파일(.jar)을 컨테이너 내부로 복사합니다. 개발 중 코드가 수정되어 다시 빌드할 경우, 보통 이 계층부터 변경 사항이 새로 적용됩니다.
  • ENTRYPOINT ["java", "-jar", "app.jar"] 컨테이너가 켜질 때 최초로 수행할 프로세스 명령어를 지정합니다. 복사해 온 자바 애플리케이션을 실행하라는 메타데이터 지시어입니다.

인프라와 애플리케이션의 라이프사이클

프로젝트 설정을 보면 데이터베이스를 띄우는 docker-compose.infra.yaml 파일과, 애플리케이션을 띄우는 docker-compose.yaml 파일이 분리된 경우가 많습니다. 하나로 합치면 편할 텐데, 왜 굳이 분리하는 걸까요?

가장 큰 이유는 각 서비스의 '생명 주기(Lifecycle)'가 다르기 때문입니다.

  • 인프라 (PostgreSQL, Redis 등) 데이터베이스는 한 번 실행해 두면 설정이 거의 변하지 않습니다. 데이터 영속성(Volume)을 유지하며 안정적으로 지속 실행되어야 합니다.
  • 애플리케이션 (Spring Boot 등) 개발 및 유지보수 과정에서 코드가 빈번하게 수정되므로 잦은 이미지 빌드와 컨테이너 재시작이 필요합니다.

만약 하나의 yaml 파일에 두 서비스를 모두 묶어둔다면, 애플리케이션 코드를 수정하고 재빌드할 때마다 멀쩡히 돌아가고 있는 데이터베이스 컨테이너의 상태까지 신경 써야 하는 번거로움이 생깁니다. 파일을 분리하면 DB는 묵묵히 띄워둔 상태에서, 애플리케이션만 독립적으로 재빌드(docker-compose up --build)하고 배포할 수 있어 훨씬 안정적이고 효율적입니다.

도커 브릿지 네트워크

파일이 분리되어 물리적으로 떨어져 있는 두 컨테이너는 어떻게 통신할까요? 비밀은 도커의 브릿지 네트워크(Bridge Network)와 내장 DNS에 있습니다. 브릿지 네트워크는 도커 엔진이 컴퓨터 내부에 만들어주는 '가상의 프라이빗 공유기'라고 생각하시면 됩니다.

네트워크 분리와 연결 원리 인프라 파일(infra.yaml) 하단에 driver: bridge 옵션으로 my-network라는 공유기를 최초로 생성해 둡니다. 이후 애플리케이션 파일(app.yaml) 하단에서 external: true 옵션을 주어, 새로 네트워크를 만들지 않고 기존에 만들어둔 인프라 네트워크에 그대로 합류하도록 설정합니다.

도커 내장 DNS

도커 컨테이너는 재시작될 때마다 IP가 바뀝니다. 애플리케이션 설정 파일에 DB의 특정 IP 주소를 하드코딩하면, DB가 껐다 켜질 때마다 IP가 어긋나 Connection Refused 에러가 발생하게 됩니다.

이를 해결하기 위해 도커는 동일한 브릿지 네트워크 내에서 작동하는 자체 내장 DNS 서버를 제공합니다. 컨테이너가 켜질 때 할당받은 새로운 IP 주소와 컨테이너 이름(예: my-db)을 실시간으로 연결해 줍니다.

따라서 스프링 부트 설정에 jdbc:postgresql://my-db:5432/... 처럼 목적지를 컨테이너 이름으로만 적어두어도, 도커 DNS가 알아서 정확한 IP로 길을 찾아주는 완벽한 통신망이 완성됩니다.

네트워크 환경이 다를 때의 통신 방법

도커 네트워크를 공부하다 보면 맞닥뜨리는 궁금증 두 가지를 명쾌하게 정리해 보았습니다.

Q1. 서로 다른 도커 네트워크에 속한 컨테이너끼리 통신해야 할 때는 어떻게 하나요?

같은 네트워크로 묶여 있지 않다면, 도커의 내장 DNS 서버는 상대방의 이름을 해석(Resolve)할 수 없습니다. 철저히 격리된 상태이기 때문입니다. 이럴 때는 두 가지 해결책이 있습니다.

  1. 호스트 IP 주소 우회: 타겟 컨테이너가 호스트 PC로 포트 포워딩(예: -p 5432:5432)을 열어둔 상태라면, 로컬 PC의 IP(또는 host.docker.internal)를 경유하여 외부를 돌아서 접속할 수 있습니다.
  2. 동일 네트워크 병합(권장): docker network connect 명령어를 사용하거나, compose 파일 설정을 수정하여 두 컨테이너를 같은 네트워크에 합류시킵니다. DNS를 활용한 가장 깔끔한 방법입니다.

Q2. 반대로 네트워크 설정을 아예 적지 않고, 같은 docker-compose 파일에 두 컨테이너를 올리면 어떻게 되나요? IP로 접속해야 하나요?

아닙니다! 여전히 컨테이너 이름(서비스 이름)으로 다이렉트 접속이 가능합니다. 우리가 명시적으로 networks 옵션을 적지 않더라도, docker-compose up 명령어를 실행하는 순간 도커는 디렉토리 이름을 딴 '기본 네트워크(Default Network, 예: myapp_default)'를 자동으로 생성합니다.

해당 파일 안에 적힌 모든 컨테이너는 이 기본 네트워크에 자동으로 가입되며, 동시에 내장 DNS 서버가 활성화됩니다. 따라서 별다른 네트워크 설정이 없어도 jdbc:postgresql://db:5432/ 와 같이 서비스 이름을 그대로 주소로 사용할 수 있습니다.

마무리

오늘은 도커 이미지와 컨테이너의 근본적인 차이부터, Dockerfile의 빌드 계층, 라이프사이클에 따른 아키텍처 분리, 그리고 네트워크와 DNS의 통신 원리까지 다루어 보았습니다.

반응형