EKS – Docker Container 설정 주의사항 (Spring)

Spring 애플리케이션을 Docker 컨테이너로 실행할 때, 데이터베이스(MySQL)나 캐시(Redis)와 같은 외부 서비스에 접근할 때 application.yml 파일의 네트워크 설정을 정확하게 구성해야 한다. 특히 localhost 사용에 주의해야 한다

문제의 원인: localhost의 의미 변화

Spring 애플리케이션이 호스트(내 PC)에서 직접 실행될 때

  • Spring 애플리케이션은 호스트 PC 위에서 직접 실행된다
  • application.yml에서 localhost는 호스트 PC 자체를 의미한다
  • MySQL(3306), Redis(6379)와 같은 서비스가 호스트 PC 또는 Docker 컨테이너로 호스트 PC의 특정 포트(예: -p 3306:3306)에 바인딩되어 실행되고 있다면, Spring 애플리케이션은 localhost:3306이나 locahlost:6379를 통해 이 서비스들에 문제없이 접근할 수 있다

Spring 애플리케이션이 Docker 컨테이너 내부에서 실행될 때

  • Spring 애플리케이션은 Docker 컨테이너 내부의 격리된 환경에서 실행된다. 이 컨테이너는 그 자체로 하나의 독립적인 “가상 컴퓨터”와 같다
  • 이때, 컨테이너 내부에서 localhost는 해당 Spring 컨테이너 자기 자신을 의미힌다
  • 만약 MySQL이나 Redis 서비스가 호스트 PC에서 실행 중이라면, Spring 컨테이너 내부에는 이 서비스들이 없으므로 localhost:3306이나 localhost:6379로 접근을 시도하면 컨테이너 내부에서 해당 포트를 찾게 되어 연결에 실패하고 에러가 발생한다

나는 존 폰 노이만이 아니기 때문에 뇌(논리적)가 아닌 눈(물리적)으로 확인해보자

<> Bash

docker run -d --name my-spring -p 8080:8080 my-spring:v1.0
  • Docker Desktop에서 확인해보니 실행되지 않는다. 로그도 확인해 보자
  • status가 running이 아닌 exited(1)이고 Logs를 확인해보니 에러가 한 바가지이다. 핵심은 포트를 찾지 못하여 연결이 되지 않았다는 것이다

해결책: host.docker.internal 사용

Docker 컨테이너 내부에서 호스트 PC의 서비스에 접근하려면 특별한 호스트 이름을 사용해야 한다

  • host.docker.internal: 이 특별한 호스트 이름은 Docker (macOS, Windows) 환경에서 컨테이너가 호스트 PC에 접근할 수 있도록 해준다. host.docker.internal을 사용하면 컨테이너는 호스트 PC의 IP 주소를 통해 서비스에 접근할 수 있다
<> Yaml

spring :
  config:
    activate:
      on-profile: local
  data:
    redis:
      host: host.docker.internal # localhost 대신 host.docker.internal
      port: 6379
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://host.docker.internal:3306/ordersystem?useSSL=false&allowPublicKeyRetrieval=true # localhost 대신 host.docker.internal

Docker 이미지 빌드 및 컨테이너 실행

<> Bash
# 이미지 빌드
docker build -t my-spring:v1.1 .

# 컨테이너 실행
docker run -d --name my-spring -p 8080:8080 my-spring:v1.1
  • 주의: 만약 이전에 my-spring 이나를 컨테이너가 실행 중이거나 중지된 상태라면, docker rm -my-spring으로 기존 컨테이너를 제거하거나 다른 이름으로 실행해야 한다. 그렇지 않으면 “컨테이너 이름 중복” 에러로 인해 새로운 컨테이너가 실행되지 않는다

외부에서 Spring 컨테이너로 접근 시: localhost 사용 가능

Postman과 같은 외부 클라이언트(호스트 PC에서 실행)에서 Spring 컨테이너로 요청을 보낼 때는 여전히 http://localhost:8080/member/login과 같은 localhost를 사용할 수 있다

  • 외부 클라이언트의 localhost: 외부 클라이언트 입장에서는 localhost가 호스트 PC 자체를 의미한다
  • 포트 포워딩 (-p 8080:8080): docker -run 명령에서 -p 8080:8080 옵션은 호스트 PC의 8080 포트로 들어오는 요청을 Spring 컨테이너의 8080포트로 전달(포워딩)하도록 설정한다
  • 따라서, 외부 클라이언트 호스트 PC의 localhost:8080으로 요청을 보내고, 이 요청은 Docker에 의해 Spring 컨테이너 내부의 8080 포트로 성공적으로 전달된다

핵심은, localhost가 사용되는 “컨텍스트(어디서 localhost를 참조하는가)”에 따라 그 의미가 달라진다는 점이다

  • 컨테이너 내부에서 외부 서비스 접근 시: host.docker.internal
  • 컨테이너 외부에서 컨테이너 서비스 접근 시: localhost (포트 포워딩 전제)

추가적인 실수: 오타 주의…

host.docker.internal과 같은 중요한 설정 값은 오타가 발생하기 쉽다. 실제로 host.dcoker.internal과 같이 오타를 발생한 상태에서 이미지 빌드 및 컨테이너 실행은 잘 되었지만 http://localhost:8080/member/login로 요청을 보냈을 때 연결 오류로 인해서 로그인 처리가 안 되고 오류 메시지만 한 가득 나왔다. 오류를 해결하는 과정도 개발의 한 부분이라지만, 특히 공부를 하는 도중에 이러한 오류를 만나면 시간 낭비라는 생각이 안 들 수가 없다…

출처 – eks를 활용한 spring 운영서버 배포(feat. devops의 모든것)