🚀 이번 포스팅에서 알아갈 키워드
- Docker Network
- Bridge Network (docker0, eth0, veth)
🐛 문제 상황
도커를 이용해서 스프링 부트 컨테이너를 띄우고 MySQL도 마찬가지로 컨테이너로 띄웠다.
그리고 계속해서 Connection Error가 나왔고 원인을 찾아야 했다.
🟩 현재 환경 : EC2, Docker Container
같은 EC2에 두 컨테이너를 띄어 놓았다.
- Spring 컨테이너 Host Port : 8080
- Spring 컨테이너의 Port : 8080
- MySQL 컨테이너 Host Port : 3306
- MySQL 컨테이너의 Port : 3306
🟩 현재 환경 : application.yml
스프링 부트의 application.yml은 다음과 같이 작성했다.
flyway를 꺼놓고 데이터베이스는 mysql로 접속할 수 있도록 했다.
`docker.compose.enabled: off`는 스프링 부트 3 이상에서 제공하는 도커 컴포스 기능인데 미리 띄어놓은 mysql를 이용할 것이기에 자동으로 새로운 컨테이너를 띄우는 것을 막아두었다.
spring:
flyway:
enabled: false
datasource:
url: jdbc:mysql://localhost:3306/testdb
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
jpa:
properties:
hibernate:
format_sql: true
hibernate:
ddl-auto: none
docker:
compose:
enabled: off
🟩 문제 원인 : Container 내부의 localhost는 Container를 가리킨다.
스프링 부트에 설정한 yml에 Database Host를 localhost로 작성해둔것이 문제였다.
스프링부트에서 인식하는 localhost는 자신의 컨테이너 내부 ip주소를 의미하는 것이었다.
한 EC2 내부에서의 localhost라면 EC2를 가리키고 있을 줄 알았지만 Spring Container를 가리키고 있었다.
생각해보면 당연한 이야기였다. 이러한 이유로 MySQL을 인식하지 못한 것이다.
해결하기 위해서는 스프링이 MySQL 컨테이너를 가리키게 하면 된다.
🟩 문제 해결 : Docker Network (docker0, eth0, veth)
현재 문제를 해결하기 위해서는 Docker Network의 개념을 알아야한다.
컨테이너를 생성하게 되면 컨테이너는 NET namespace라는 기술을 이용해서 독립된 네트워크 공간을 할당 받는다.
서로 독립된 네트워크 공간을 가지고 있는 컨테이너가 서로 통신하기 위해서 Default Bridge Network (bridge)가 자동으로 생성된다.
Bridge Network는 호스트 시스템의 Bridge Interface에 컨네이너들을 연결해서 서로 통신할 수 있도록 도와준다.
각 컨테이너들은 eth0(네트워크 인터페이스)를 통해서 veth(가상 이더넷 인터페이스)의 가상 연결을 통해 docker0(기본 브리지 네트워크)와 연결되어 있다.
그림으로 보면 다음과 같다.
도커는 컨테이너를 생성할 때 기본적으로 Docker daemon이 자동으로 생성하는 기본 브리지 네트워크(docker0)에 연결된다.
자동으로 docker0에 연결되는 흐름은 다음과 같다.
- Docker daemon이 실행되면 자동으로 docker0라는 이름의 브리지 네트워크 인터페이스를 생성한다.
- 컨테이너가 생성될 때 도커는 eth0라는 이름을 갖는 네트워크 인터페이스를 생성하고, docker0 브리지에 연결한다.
- 컨테이너들은 같은 브리지 네트워크에 속하게 된다.
- 컨테이너들은 고유한 IP 주소를 할당받고, docker0 브리지를 통해 컨테이너 간의 통신을 하거나 호스트 시스템과도 통신할 수 있다.
🟧 eth0 - Linux 환경의 도커 컨테이너 내부 네트워크 인터페이스
Linux 시스템에서 기본적으로 사용되는 네트워크 인터페이스(Network Interface)의 이름 중 하나이다.
호스트 시스템과 도커 컨테이너들이 네트워크와 통신하기 위해 사용되는 인터페이스이다.
즉, 도커 컨테이너의 eth0는 해당 컨테이너 내부에서 네트워크와 통신하는데 사용된다.
🟧 veth - eth0와 docker0가 서로 통신할 수 있게 가상 연결을 만들어주는 가상 이더넷 인터페이스
veth는 가상 이더넷 인터페이스(Virtual Ethernet Interface)를 의미한다.
veth 인터페이스는 도커 컨테이너 내부의 eth0 인터페이스와 호스트 시스템의 브리지(docker0) 인터페이스 사이에 가상 연결을 만들어준다. 이를 통해서 도커 컨테이너 내부의 eth0와 호스트 시스템의 브리지 인터페이스(docker0)와 서로 통신할 수 있게 된다.
🟧 docker0 - 기본적으로 컨테이너들을 연결해주는 브리지 네트워크 인터페이스
도커가 기본적으로 생성하는 브리지 네트워크(Bridge Network)로 컨테이너들을 연결할 때 사용한다.
도커 데몬(Docker daemon)이 실행되면 호스트 시스템에서 자동으로 docker0라는 브리지 네트워크 인터페이스를 생성한다.
브리지는 기본적으로 컨테이너들이 연결되는 네트워크를 담당한다.
docker network ls
라는 명령어를 치면 네트워크 목록을 볼 수 있다.
그 어디에서도 docker0라는 네트워크 이름을 찾아볼 수 없지만 기본으로 bridge라는 이름을 갖은 상태로 들어가는 것으로 파악했다.
✅ 도커에서 기본으로 제공하는 브리지 네트워크인 bridge의 정보를 더 자세히 봐보자.
docker inspect bridge
라는 명령어를 사용해서 내부 정보를 더 들여다 보자.
아래 부분을 자세히 보면 "com.docker.network.bridge.name" : "docker0"를 찾을 수 있다.
"com.docker.network.bridge.name" : "docker0"
✅ 브리지 네트워크 bridge가 포함하고 있는 컨테이너의 ip 주소를 확인하자.
또한 중간에 "Containers" 를 보면 "mysql-local-container"가 해당 브리지에 포함하고 있는 것을 알 수 있다.
그리고 현재 'bridge'라는 브리지 네트워크 내부에 존재하는 컨테이너 'mysql-local-container'의 ip주소는 '172.17.0.2'라는 것을 알 수 있다.
즉, application.yml에 적은 datasource의 host를 localhost가 아닌 172.17.0.2를 적음으로서 해결할 수 있다.
spring:
flyway:
enabled: false
datasource:
url: jdbc:mysql://172.17.0.2:3306/testdb
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
jpa:
properties:
hibernate:
format_sql: true
hibernate:
ddl-auto: none
docker:
compose:
enabled: off
🟩 [deprecated] docker network 없이 이어주는 방식?
🤔 docker --link 옵션 (deprecated)
docker run -d --name spring-demo --link mysql-local-container -p 8080:8080 demo
가장 간단한 방법은 다음과 같이 컨테이너에 링크를 거는 방법이다.
application.yml에는 host를 ip주소가 아닌 컨테이너명을 적어줌으로서 해결할 수 있다.
spring:
flyway:
enabled: false
datasource:
url: jdbc:mysql://mysql-local-container:3306/testdb
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
jpa:
properties:
hibernate:
format_sql: true
hibernate:
ddl-auto: none
docker:
compose:
enabled: off
다만 이 방법은 이제 레거시로 추천하지 않는 방식이다.
🟩 datasource에 ip 주소를 명시하지 말고 컨테이너 이름을 사용하자.
도커 브리지 네트워크에서 컨테이너에 할당해준 ip주소는 컨테이너가 재실행될 경우 바뀔 수 있는 가능성이 높다.
그렇다면 당연히 연결이 실패할 수 있다.
다음과 같이 datasource host가 변경되어도 연결될 수 있도록 해보자.
spring:
datasource:
url: jdbc:mysql://mysql-local-container:3306/testdb
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
먼저 도커에서 기본적으로 제공하는 브리지 네트워크를 이용할 때는 컨테이너 이름을 통해서 찾을 수 없다.
그러므로 새로운 사용자 정의 브리지 네트워크를 만들어서 MySQL과 Spring가 위치할 수 있도록 해야한다.
01. 브리지 네트워크를 만든다.
docker create network demo-network
아래에서 demo-network가 만들어졌다는 것을 확인할 수 있다.
02. 브리지 네트워크를 기반으로 컨테이너를 생성하자.
MySQL 컨테이너를 먼저 생성하자.
- '--network=demo-network' : 컨테이너가 demo-network에 속하도록 생성한다.
docker run -d --name mysql-local-container -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root --network=demo-network mysql
Spring 컨테이너를 생성하자.
먼저 application.yml을 수정한다.
spring:
datasource:
url: jdbc:mysql://mysql-local-container:3306/testdb
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
다시 이미지를 만들고 container를 생성하는데 MySQL과 같은 브리지 네트워크에 위치하게 옵션을 준다.
- '--network=demo-network' : 컨테이너가 demo-network에 속하도록 생성한다.
docker run -d --name spring-demo -p 8080:8080 --network=demo-network demo
이제 다음과 같이 잘 작동하는 모습을 보면서 포스팅을 마치겠다!