쌩로그

[TroubleShooting] 도커 이미지로 만든 애플리케이션을 localhost에서 도커 컴포즈로 실행시 local(hostPC)의 DB와 Connection 되지 않던 문제 해결 - 컨테이너 개념 부재 본문

TroubleShooting & 고민/기타

[TroubleShooting] 도커 이미지로 만든 애플리케이션을 localhost에서 도커 컴포즈로 실행시 local(hostPC)의 DB와 Connection 되지 않던 문제 해결 - 컨테이너 개념 부재

.쌩수. 2025. 3. 13. 00:05
반응형

목록

  1. 문제 발생 배경
  2. 문제 해결 핵심
  3. 문제 해결 과정
  4. 윈도우와 리눅스
  5. 요약

참고

  • 문제가 발생할 때 따로 기록하지 않았기 때문에 GPT가 준 파일들을 예시를 든 점 양해바란다.
  • DB는 MariaDB다.

참고로 해당 포스팅은 localhost에서의 환경이므로 호오오옥시나!! 운영과 연관지어 생각한다면 큰 일이 날 수도 있다는 점 참고하자.

3월 13일 수정

  • 이번 포스팅을 하고 다음날인 바로 오늘(3월13일)에 적용을 했지만, 잘 못된 내용이 있어서 수정한다.
  • 잘 못 말한 내용은 줄긋기()로 표시하고 정정한 내용을 작성한다.
  • 참고로 리눅스와 윈도우의 환경 차이가 있다.
    • 정확하게는 윈도우와 리눅스보다는 Docker Desktop이냐 아니냐의 차이다.

1. 문제 발생 배경

최근 도커 컴포즈를 이용해서 로컬호스트(localhost)에서 애플리케이션을 실행하려고 했다.
배포를 하기 전에 localhost에서 테스트를 해야했다.

늘 그래왔던 것처럼 도커 이미지로 감싸진 애플리케이션의 yml 에는 DB 정보(datasource, username, userpassword)들을 환경변수로부터 가져오도록 했다.

도커 이미지로 만들어 해당 이미지를 도커 허브에 올렸다.
이후 이미지를 내려받고 도커 컴포즈 yaml 파일을 작성한 후에
docker-compose up 명령어를 실행했다.

그런데, 애플리케이션이 실행되지 않고 있었다.

로그를 확인해보니 DB와 connection이 되지 않고 있던 문제가 발생했다.
최근에 다뤘던 트러블슈팅 포스팅처럼 환경 변수가 문제가 있는지 알았다.
스프링 부트의 로그를 들여다보았다.
그런데 이상하게 port 번호, DB의 연결할 host 정보가 잘 나오고 있었다.

예를 들면, datasource 부분에 jdbc:mysql://localhost:3306/testdb?useSSL=false&characterEncoding=UTF-8 와 같은 값을 환경변수로 받아오도록 했었는데,

스프링 애플리케이션 실행시 DB 와 연결하는 로그에서 localhost와 포트 번호 3306이 보란듯이 잘 나오고 있었다.

그런데 왜!! DB와 커넥션이 되지 않던걸까...??

도커 컴포즈 yaml 파일에는 다음과 같이 환경변수를 정의해놓을 수 있다.


...
...    
ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://localhost:3306/testdb?useSSL=false&characterEncoding=UTF-8
      SPRING_DATASOURCE_USERNAME: user
      SPRING_DATASOURCE_PASSWORD: password
...
...

위와 같이 YML을 작성했는데, DB 커넥션이 되지 않았다.

2. 문제 해결 핵심

결론을 먼저 말하자면, 컨테이너 개념을 이해하면 되는 부분이다.
사실 컨테이너 개념에 대한 부재로 인해서 시간을 잡아먹었다.

다시 말하자면 컨테이너 개념이다.

3. 문제 해결 과정

먼저 내가 생각한 그림은 다음과 같다.

컨테이너 내부에 있는 애플리케이션이 직접 DB에 접근하는 것으로 생각했다.
사실 그림은 지금 이해하고 그려서 저렇게 그렸지만, 처음에는 컨테이너와 애플리케이션을 동일시 생각했다.

그런데,, datasourcejdbc:mysql://localhost:3306/testdb?useSSL=false&characterEncoding=UTF-8 이런 식으로 하게 되면, 그림은 아래와 같이 된다.

그니깐 컨테이너 내부에 있는 애플리케이션이 localhost에서 DB를 찾는데, 컨테이너 내부에서 연결할 DB가 있는지를 찾게 되는 것이다.

그림에 표시는 했지만, 컨테이너 내부에서 DB를 찾는데 찾을 수가 없다.
(찾을 수 없다는 것을 물음표로 표시했다..)

애플리케이션 실행 로그를 보면 time out 로그가 보였었다.

찾지 못해서 생기는 문제였다.

그렇다면 답은 간단하다.

애플리케이션이 컨테이너 내부가 아니라, 외부에 있는 DB에 접근할 수 있도록 해주면 되는 것이다.

위의 그림림처럼 컨테이너 외부에서 DB를 찾도록 해주면 되는 것이다.

따라서 datasource 에서

jdbc:mysql://localhost:3306/testdb?useSSL=false&characterEncoding=UTF-8

위의 localhost 부분을 docker.host.external 로 바꿔주면 된다.(이거 아님)
위의 localhost 부분을 host.docker.internal 로 바꿔주면 된다.

아래는 그 예시다.

jdbc:mysql://docker.host.external:3306/testdb?useSSL=false&characterEncoding=UTF-8(이거 아님)
jdbc:mysql://host.docker.internal:3306/testdb?useSSL=false&characterEncoding=UTF-8

이렇게 해주면 애플리케이션이 컨테이너 외부에 있는 DB에 직접 접근한다.

참고

참고로 로컬 PC 말고 다른 PC에 있는 DB에 접근하려면 다른 PC의 IP를 그대로 적어주면 된다.
단, 외부 PC의 컨테이너에 있는지, 그냥 DB가 있는지에 따라 datasource의 host 부분을 달리줘야 할 것이다.

네트워크 관점

위의 그림에서 만나는 접점이 컨테이너 입장에서는 localhost와 연결하는 gateway 다.
따라서 localhost의 IP를 넣으면 어차피 DB가 localhost IP에 포트가 3306이므로,
localhost:3306이 아니라 IP:3306으로 커넥팅을 시도하면 연결되기 때문에 충분히 네트워크 데이터가 DB로 도달되기 때문에 IP를 붙이는 것이 더 나을 수도 있다.

이와 관련된 내용은 아래의 내용과 연결해서 생각하면 충분히 이해가 갈 것 이다.

4. 윈도우와 리눅스

사내에서 도커로 애플리케이션 실행하는 것을 테스트하는 도중인데,
윈도우에서는 되던 것이 VM의 리눅스에서는 되지 않았다.

그래서 GPT의 도움을 받아서 내용을 한번 살펴봤는데, host.docker.internal에 대한 내용은 다음과 같다.

이처럼 Docker Desktop에서 기본으로 제공하는 DNS이름이다.
그런데 리눅스는 제공되지 않는다고 한다.

그래서 윈도우에서는 실행되던 것이 리눅스에선 아무리 해도 안 되던 것이었다.

리눅스에서는 local PC의 IP를 주거나, docker를 설치하면 생기는 docker 네트워크인 docker0 IP의 주소를 datasource의 host주소로 쓰면 된다고 한다.

리눅스에서 ifconfig를 통해 나오는 IP들이 있다.
아래는 그 예시다.

docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255

...
...

enp0s3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.2.1.42  netmask 255.255.255.0  broadcast 10.1.1.255

위의 두 주소를 hostIP로 적으면 컨테이너 외부의 DB와 연결이 된다.

모든 경우의 수를 넣어본 결과

개인적으로 너무 궁금해서 모든 경우의 수를 다 넣고 확인했다.

경우의 수는 다음과 같다.

  • host.docker.internal
  • docker0 IP
  • eth0(혹은 enp0s3, 윈도우에서는 공유기로부터 받아오는 주 IP)

리눅스는 ifconfig 입력 시 출력되는 enp0s3(혹은 eth0)과 docker0 네트워크 주소를 써야 외부 DB와 연결이 되고,

윈도우host.docker.internal 과 ipconfig 입력 시 출력되는 공유기로부터 받은 IP를 설정해주면 된다.

5. 요약

  • 컨테이너 개념 부재로 발생한 이슈였다.
  • jdbc:mysql://localhost:3306/testdb?useSSL=false&characterEncoding=UTF-8jdbc:mysql://docker.host.external:3306/testdb?useSSL=false&characterEncoding=UTF-8 처럼 localhost 부분을 docker.host.external로 변경해주면 된다.(이거 아님)
  • jdbc:mysql://localhost:3306/testdb?useSSL=false&characterEncoding=UTF-8jdbc:mysql://host.docker.internal:3306/testdb?useSSL=false&characterEncoding=UTF-8 처럼 localhost 부분을 host.docker.internal로 변경해주면 된다.
    • 단 리눅스라면 eth0(혹은 enp0s3)과 docker0 를 주면 된다.
  • 그러면 애플리케이션은 DB 연결시 컨테이너 내부가 아닌 외부의 DB에 연결하여 DB와 커넥션을 맺는다.
  • 리눅스는 ifconfig 입력 시 출력되는 enp0s3(혹은 eth0)과 docker0 네트워크 주소를 써야 외부 DB와 연결이 되고,
  • 윈도우host.docker.internal 과 ipconfig 입력 시 출력되는 공유기로부터 받은 IP를 설정해주면 된다.
728x90
Comments