
지피티랑 1시간 넘게 싸웠는데 알고 보니 포트 번호 문제;;
Spring Boot 프로젝트에서 Docker로 MySQL을 띄운 뒤 애플리케이션을 실행하려고 했다.
처음 설정은 다음과 같았다.
services:
mysql:
image: mysql:8
container_name: sparta-payment-mysql
environment:
MYSQL_ROOT_PASSWORD: root1234
MYSQL_DATABASE: payment_db
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
volumes:
mysql_data:
spring:
datasource:
url: jdbc:mysql://localhost:3306/payment_db?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul
username: root
password: root1234
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
open-in-view: false
hibernate:
ddl-auto: create
show-sql: true
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.MySQLDialect
하지만 Spring Boot를 실행하면 계속 아래 에러가 발생했다.
Access denied for user 'root'@'localhost' (using password: YES)
최종적으로는 JPA가 DB 커넥션을 얻지 못해 entityManagerFactory 생성에 실패했고, 애플리케이션 실행이 중단됐다.
Failed to initialize JPA EntityManagerFactory
Unable to open JDBC Connection for DDL execution
Application run failed
에러 메시지만 보면 가장 먼저 의심되는 것은 MySQL 비밀번호였다.
Access denied for user 'root'@'localhost'
그래서 처음에는 root 계정의 비밀번호가 잘못되었거나, IntelliJ가 예전 비밀번호를 저장하고 있다고 생각했다.
내가 확인했던 비밀번호 후보는 다음과 같았다.
root1234
dlatjsrn1!
먼저 Docker 컨테이너 안에서 root 계정으로 접속해봤다.
docker exec -it sparta-payment-mysql mysql -uroot -p
비밀번호로 root1234를 입력하니 정상 접속됐다.
Welcome to the MySQL monitor.
그리고 DB도 정상적으로 선택됐다.
USE payment_db;
SHOW TABLES;
결과는 다음과 같았다.
Database changed
Empty set
즉, Docker 컨테이너 내부의 MySQL에서는 root / root1234가 정상적으로 동작했다.
그래도 확실히 하기 위해 명령어로 비밀번호를 직접 검증했다.
docker exec -i sparta-payment-mysql mysql -uroot -proot1234 -e "SELECT 'root1234 OK';"
결과는 성공이었다.
root1234 OK
root1234 OK
반대로 예전에 다른 프로젝트에서 쓰던 비밀번호도 확인했다.
docker exec -i sparta-payment-mysql mysql -uroot -p'dlatjsrn1!' -e "SELECT 'old password OK';"
결과는 실패였다.
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
따라서 현재 Docker MySQL의 root 비밀번호는 root1234가 맞았다.
이상했던 점은 터미널에서는 접속이 되는데 Spring Boot에서는 계속 실패한다는 점이었다.
컨테이너 내부에서는 아래 명령어가 정상 동작했다.
docker exec -it sparta-payment-mysql mysql -uroot -p
하지만 Spring Boot 실행 로그에서는 계속 아래 에러가 발생했다.
Access denied for user 'root'@'localhost' (using password: YES)
처음에는 이 차이를 이해하지 못해서 다음과 같은 것들을 의심했다.
1. application.yaml 비밀번호 오타
2. IntelliJ Database 탭의 비밀번호 캐시 문제
3. MySQL root 계정 권한 문제
4. Docker 볼륨에 예전 비밀번호가 남아있는 문제
그래서 payment_user라는 새 계정도 만들어보고, 권한도 부여해봤다.
CREATE USER 'payment_user'@'%' IDENTIFIED BY 'payment1234';
GRANT ALL PRIVILEGES ON payment_db.* TO 'payment_user'@'%';
FLUSH PRIVILEGES;
하지만 이것도 근본 해결이 아니었다.
나중에 확인해보니 문제는 계정이 아니었다.
IntelliJ 오른쪽 Database 탭에서도 MySQL 연결을 시도했다.
처음에는 다음과 같이 설정했다.
Host: localhost
Port: 3306
User: root
Password: root1234
Database: payment_db
하지만 IntelliJ Database에서도 계속 연결이 실패했다.
Access denied for user 'root'@'localhost' (using password: YES)
이때까지만 해도 계속 비밀번호나 계정 문제라고 생각했다.
하지만 Docker 컨테이너 안에서는 root / root1234가 정상 접속되기 때문에 앞뒤가 맞지 않았다.
설정 파일이 실제로 반영됐는지도 확인했다.
처음에는 파일명을 application.yml로 착각해서 확인했다.
cat src/main/resources/application.yml | grep "jdbc:mysql"
하지만 실제 파일명은 application.yaml이었다.
find . -name "application.yml" -o -name "application.yaml" -o -name "application.properties"
결과는 다음과 같았다.
./build/resources/main/application.yaml
./src/main/resources/application.yaml
그래서 실제 파일을 다시 확인했다.
cat src/main/resources/application.yaml | grep "jdbc:mysql"
cat build/resources/main/application.yaml | grep "jdbc:mysql"
둘 다 정상적으로 같은 설정을 가지고 있었다.
url: jdbc:mysql://127.0.0.1:3306/payment_db?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul
즉, Spring Boot 설정 파일 문제도 아니었다.
혹시 root 계정이 외부 접속을 허용하지 않는 문제일 수도 있어서 MySQL 계정의 host 값을 확인했다.
docker exec -i sparta-payment-mysql mysql -uroot -proot1234 -e "SELECT user, host FROM mysql.user WHERE user = 'root';"
결과는 다음과 같았다.
user host
root %
root localhost
root@%가 있다는 것은 외부 접속도 허용된다는 뜻이다.
따라서 root 계정 권한 문제도 아니었다.
결국 3306 포트를 누가 사용하고 있는지 확인했다.
lsof -iTCP:3306 -sTCP:LISTEN -n -P
결과는 다음과 같았다.
COMMAND PID USER NAME
mysqld 1529 imsungu TCP 127.0.0.1:3306 (LISTEN)
com.docke 52511 imsungu TCP *:3306 (LISTEN)
여기서 핵심은 이 부분이었다.
mysqld ... TCP 127.0.0.1:3306 (LISTEN)
내 Mac에 예전에 설치되어 있던 로컬 MySQL이 이미 127.0.0.1:3306을 사용하고 있었다.
즉, Spring Boot가 아래 주소로 접속할 때,
jdbc:mysql://127.0.0.1:3306/payment_db
내가 의도한 Docker MySQL이 아니라 Mac 로컬 MySQL로 접속하고 있었던 것이다.
그래서 Docker MySQL의 비밀번호인 root1234를 넣어도 Mac 로컬 MySQL에서는 맞지 않기 때문에 계속 로그인 실패가 발생했다.
Access denied for user 'root'@'localhost'
문제의 흐름은 다음과 같았다.
1. Mac 로컬 MySQL이 127.0.0.1:3306 사용 중
2. Docker MySQL도 3306으로 열려고 함
3. Spring Boot는 127.0.0.1:3306으로 접속
4. 실제로는 Docker MySQL이 아니라 Mac 로컬 MySQL에 접속
5. Docker용 비밀번호 root1234를 넣음
6. Mac 로컬 MySQL 입장에서는 틀린 비밀번호
7. Access denied 발생
즉, 원인은 비밀번호가 아니라 포트 충돌이었다.
Docker MySQL의 외부 포트를 3306에서 3307로 변경했다.
ports:
- "3306:3306"
ports:
- "3307:3306"
최종 docker-compose.yml은 다음과 같다.
services:
mysql:
image: mysql:8
container_name: sparta-payment-mysql
environment:
MYSQL_ROOT_PASSWORD: root1234
MYSQL_DATABASE: payment_db
MYSQL_ROOT_HOST: '%'
ports:
- "3307:3306"
volumes:
- mysql_data:/var/lib/mysql
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
volumes:
mysql_data:
그리고 Spring Boot의 application.yaml도 3307로 변경했다.
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3307/payment_db?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul
username: root
password: root1234
driver-class-name: com.mysql.cj.jdbc.Driver
설정 변경 후 Docker 컨테이너를 재실행했다.
docker compose down
docker compose up -d
이미 데이터 초기화가 필요한 상황은 아니었기 때문에 -v는 붙이지 않았다.
docker compose down -v
-v를 붙이면 볼륨까지 삭제되어 DB 데이터가 초기화된다.
IntelliJ Database 탭에서도 기존 localhost:3306 연결은 삭제하고 새로 만들었다.
새 연결 정보는 다음과 같다.
Host: 127.0.0.1
Port: 3307
User: root
Password: root1234
Database: payment_db
URL은 다음과 같이 잡혔다.
jdbc:mysql://127.0.0.1:3307/payment_db
그 결과 Test Connection이 성공했다.
Succeeded
DBMS: MySQL 8.4.9
드디어 IntelliJ Database 탭에서도 payment_db가 정상적으로 보이기 시작했다.
이번 문제는 단순한 비밀번호 문제가 아니었다.
처음에는 root 비밀번호, MySQL 계정 권한, IntelliJ 비밀번호 저장 문제라고 생각해서 한참을 헤맸다.
하지만 실제 원인은 Mac 로컬 MySQL과 Docker MySQL이 같은 3306 포트를 사용하면서 발생한 포트 충돌이었다.
Mac 로컬 MySQL → 127.0.0.1:3306 사용 중
Docker MySQL → 원래 3306으로 열려고 함
Spring Boot → 127.0.0.1:3306으로 접속
결과 → Docker가 아니라 Mac 로컬 MySQL로 접속
해결 후 구조는 다음과 같다.
Mac 로컬 MySQL → 127.0.0.1:3306
Docker MySQL 외부접속 → 127.0.0.1:3307
Docker 내부 MySQL → 3306
즉, 내 Mac에서 Docker MySQL에 접속할 때는 3307을 사용해야 하고, Docker 컨테이너 내부에서는 여전히 3306을 사용한다.
docker exec로 MySQL 접속이 성공한다고 해서 Spring Boot도 같은 MySQL에 접속하고 있다고 단정하면 안 된다.
docker exec -it sparta-payment-mysql mysql -uroot -p
이 명령어는 컨테이너 내부에서 MySQL에 접속하는 것이다.
반면 Spring Boot는 Mac에서 Docker MySQL에 접속한다.
그래서 로컬 PC에 이미 MySQL이 설치되어 있고 3306 포트를 사용 중이라면, Spring Boot가 Docker MySQL이 아니라 로컬 MySQL로 접속할 수 있다.
포트 충돌이 의심될 때는 아래 명령어로 확인하자.
lsof -iTCP:3306 -sTCP:LISTEN -n -P
Docker MySQL 비밀번호 문제가 아니라, Mac 로컬 MySQL이 3306 포트를 이미 사용하고 있어서 Spring Boot와 IntelliJ Database가 Docker가 아닌 로컬 MySQL에 접속하던 문제였다.