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) 기능을 이해해야 한다.
네임스페이스
리눅스 네임스페이스는 프로세스가 특정 리소스를 독립적으로 사용할 수 있도록 하나의 운영체제 안에서 여러 개의 가상화된 “독립된” 시스템 환경을 제공하는 기술이다.
리눅스에서의 네임스페이스는 크게 아래와 같은 여섯 가지 종류로 나뉘며, 그중 네트워크 네임스페이스는 컨테이너 네트워크의 독립성을 제공하는 핵심 요소이다.
- NET 네임스페이스: 네트워크 인터페이스와 라우팅 테이블을 분리하여 각 네임스페이스마다 별도의 네트워크 스택을 구성
- PID 네임스페이스: 프로세스 ID를 독립하여 관리
- IPC 네임스페이스 : Inter-process communication. 프로세스 간 통신 격리
- NS 네임스페이스: 파일 시스템 마운트 지점을 독립적으로 관리
- UTS 네임스페이스: 호스트 이름과 도메인 이름을 독립적으로 관리
- 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' 카테고리의 다른 글
Docker로 Sonarqube 설치 및 실행(Spring & react.js + ts) (0) | 2024.10.28 |
---|
댓글