개발일기/Docker

도커와 도커 사이의 통신(도커 네트워크)

ignuy 2024. 11. 4.

Spring boot 프로젝트를 로컬에서 바로 실행하지 않고 docker로 프로젝트 디렉터리를 마운트하여 컨테이너로 실행하면서 이슈를 하나 마주쳤다. postgre SQL 을 실행하고 있는 또 다른 Docker 컨테이너와 Spring boot 프로젝트가 커넥션을 맺지 못한다.

 

무슨 일이었는지 알아보자.

일단 해결부터

문제의 원인과 해결은 정말 간단하다. 우선 같은 로컬 환경에서 실행 중인 Docker 컨테이너끼리는 localhost:{port}로 직접 통신할 수 없다. Docker는 각 컨테이너가 독립적인 네트워크 네임스페이스를 가지므로, localhost는 각 컨테이너의 내부 주소를 의미하게 되어 서로를 인식하지 못하게 된다. 따라서, 브릿지 네트워크를 사용하여 두 컨테이너가 같은 사용자 정의 브릿지 네트워크에 있을 경우 컨테이너 이름으로 서로를 참조할 수 있도록 하자.

docker network create [network 이름]
docker run --network [network 이름] --name container1 ...
docker run --network [network 이름] --name container2 ...

위 설정으로 container1에서는 container2:{port}로 접근할 수 있게 된다. Spring의 application.yml에서 datasource 설정 부분도 container의 이름으로 설정한다면 connection을 맺을 수 있다.

Docker Compose를 사용하면 자동으로 같은 네트워크를 사용하도록 설정되므로 이 방법을 이용해도 괜찮다.

Docker Network

그렇다면 docker에서 network가 무엇이길래 이런 이슈를 만나고, 위 방법으로 해결이 되는 걸까?

 

Docker 컨테이너에서는 각 컨테이너가 독립적인 네트워크 네임스페이스를 가지기 때문에, localhost가 각기 다른 주소로 해석된다. localhost는 각 컨테이너 내부에서 자신의 루프백 주소(127.0.0.1)를 참조하게 되어, 외부 네트워크나 다른 컨테이너에 접근하려 할 때에도 자기 자신으로만 통신하려고 한다.

이 때문에, 하나의 로컬 환경에서 서로 다른 Docker 컨테이너가 통신하기 위해선 아래 규칙을 따라야 한다.

  • 같은 네트워크에 속하게 해서 각 컨테이너의 이름으로 접근
  • 컨테이너 외부에서 노출된 IP나 포트를 통해 접근

사용자 정의 네트워크를 이용해 컨테이너들이 같은 네트워크에 속하게 하면, 각 컨테이너의 이름을 호스트네임처럼 사용하여 네트워크 상에서 서로를 인식하게 되므로 통신이 가능하다.

네트워크는 오직 하난데..?

네트워크(네트워크 인터페이스, 라우팅 테이블)는 오직 하나만 존재할 수 있는데 도커가 네트워크를 따로 가지는 기술적인 차이는 어디서 나오는 것일까? 이 의문점을 해결하기 위해선 먼저 Docker가 컨테이너를 서로 분리된 환경에서 실행할 수 있도록 해주는 핵심 기술 중 하나인 리눅스 커널의 네임스페이스(namespace) 기능을 이해해야 한다.

네임스페이스

리눅스 네임스페이스는 프로세스가 특정 리소스를 독립적으로 사용할 수 있도록 하나의 운영체제 안에서 여러 개의 가상화된 “독립된” 시스템 환경을 제공하는 기술이다.

리눅스에서의 네임스페이스는 크게 아래와 같은 여섯 가지 종류로 나뉘며, 그중 네트워크 네임스페이스는 컨테이너 네트워크의 독립성을 제공하는 핵심 요소이다.

  1. NET 네임스페이스: 네트워크 인터페이스와 라우팅 테이블을 분리하여 각 네임스페이스마다 별도의 네트워크 스택을 구성
  2. PID 네임스페이스: 프로세스 ID를 독립하여 관리
  3. IPC 네임스페이스 : Inter-process communication. 프로세스 간 통신 격리
  4. NS 네임스페이스: 파일 시스템 마운트 지점을 독립적으로 관리
  5. UTS 네임스페이스: 호스트 이름과 도메인 이름을 독립적으로 관리
  6. USER 네임스페이스: user와 group ID를 분할 격리

 

Docker는 컨테이너를 생성할 때, 기본적으로 각 컨테이너마다 별도의 네트워크 네임스페이스를 할당한다. 네트워크 네임스페이스가 독립되어 떨어져 나오면, 컨테이너는 자신만의 네트워크 인터페이스, IP 주소, 라우팅 테이블을 가지게 되며, 기본적으로 다른 컨테이너의 네트워크와 완전히 격리된다.

더 구체적으로 리눅스는 어떻게 네임스페이스 격리를 제공하지?

리눅스 커널은 프로세스와 네임스페이스를 연결하여, 해당 네임스페이스 내에서만 리소스를 사용할 수 있도록 한다. 예를 들어, 네트워크 네임스페이스에서는 컨테이너 내부에서 생성된 가상 네트워크 인터페이스와 IP 주소가 호스트의 다른 네임스페이스에는 보이지 않게 되는 상황처럼 말이다. 이렇게 네임스페이스가 리소스를 격리해 주기 때문에, 각 컨테이너는 서로 독립된 네트워크 환경을 사용할 수 있게 된다.

네? 저는 윈도우를 쓰는데요?

본인의 OS가 윈도우이니까 뜬금없이 리눅스 커널의 핵심 기능을 설명하는 필자를 컴공도 아닌 돌팔이처럼 보는 독자도 있을 것 같다…

 

Windows에서 Docker를 사용할 때도, 컨테이너 환경은 기본적으로 Linux 커널을 사용한다. Windows에서 실행되는 Docker는 내부적으로 가상화 기술을 사용하여 리눅스 커널이 포함된 환경을 만들어주고, 이 환경에서 Docker 엔진이 리눅스 네임스페이스 등의 기능을 사용하게 된다.

좀 더 구체적으로 설명하자면 Windows에서 Docker Desktop을 설치하면, Docker는 WSL 2와 함께 작동하도록 설정된다. WSL 2는 경량화된 가상 머신(VM) 위에 리눅스 커널을 실행하여 리눅스 시스템 호출과 기능들을 Windows에서 사용할 수 있게 해주고 Docker는 이 WSL 2 환경에서 리눅스 네임스페이스와 네트워크 격리 기능을 사용할 수 있으며, 이를 통해 Windows에서도 컨테이너 간 독립적인 네트워크 환경 구축이 가능하다.

docker docs(https://docs.docker.com/desktop/wsl/)

 

'개발일기 > Docker' 카테고리의 다른 글

Docker로 Sonarqube 설치 및 실행(Spring & react.js + ts)  (0) 2024.10.28

댓글