본문 바로가기

개발

[Docker] SpringBoot, Mysql 도커로 띄우고 연동하기

https://aws.amazon.com/ko/compare/the-difference-between-docker-images-and-containers/

Docker의 이미지컨테이너에 대한 개념을 정립할 수 있는 AWS의 글이다.

 

Docker desktop을 설치하고, 배포를 위해 새로운 브랜치를 생성해준다.

deploy/docker-V1 브랜치를 새로 생성했다.

**가장 중요한 0순위**

application.yml 파일의 민감한 정보들은 환경변수로 따로 저장해두자.

 

1. 프로젝트 루트 디렉토리에 .env 파일을 생성한다.

 

2.  .env 파일안에는 기존에 application.yml 파일 안에 있던 민감한 정보들 ( DB, JWT_SECRET_KEY 등 ) 을 환경변수로 저장해 두고, application.yml 파일을 다음과 같이 수정하자.

application.yml 파일

이렇게 두면 로컬에서는 어플리케이션을 실행할 수 없다.

혹시라도 배포 이전에 로컬에서 실행해보고 싶다면 .env 파일을 intellij 로컬 환경변수로 저장해두자

 

-> 우측 상단에 run / debug configuration 클릭

-> edit configurations

-> Build and run의 modify options

-> environment variables에서 오른쪽 작은 폴더 클릭

-> .env 파일이 있는 프로젝트 루트 디렉토리로 이동

-> cmd + shift + . (mac os) , ctrl + alt + . (windows, linux)

-> 숨겨져있던 .env 파일을 선택

-> Apply

 

이제 민감한 정보들은 모두 환경변수 처리해둔 상태고, 어플리케이션 빌드 - docker image 생성 - docker container 생성의 절차를 거쳐야 한다.

 

**1. 어플리케이션 빌드**


아래 첫번째 방법을 수행해보고 빌드가 안되면 -x test 를 추가해 시도하자.

./gradlew build
./gradlew build -x test

 

빌드 과정은 다음과 같다.

 

- 소스 코드 컴파일 과정 : src/main/java 와 src/main/resources 폴더의 내용을 컴파일하여 .class 파일 생성

 

- build/libs 폴더에 실행 가능한 JAR파일 ( ‘app.jar’ 혹은 '프로젝트이름-0.0.1-SNAPSHOT.jar’ )을 생성

 

여기서 jar파일 앞에서 생성한 .class, build.gradle에 정의된 의존성 라이브러리들, application.yml파일등이 들어간다. 

 

db정보, api 키, jwt secret key등의 민감한 정보를 application.yml파일에 넣어둔다면 그대로 jar파일에 포함되기에 앞서 

.env파일에 민감한 정보들을 넣어둔 것이다. 

 

**2. Dockerfile 생성 후 Docker image 생성**

# Dockerfile

FROM openjdk:21-jdk-slim
ADD /build/libs/생성된jar파일 app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

Dockerfile을 프로젝트 루트 디렉토리에 생성하자. '생성된jar파일' 은 좀 전에 build 결과로 나온 .jar파일을 넣어주면 된다.

이후 아래 명령어를 터미널에 입력하면 Docker image가 생성된다.

docker build -t 도커이미지이름 .

도커이미지이름에는 원하는 image 이름을 넣어주자. ex) courtnet-app1

 

마지막의 . 이 현재 디렉토리에서 Dockerfile을 찾아 빌드를 수행한다는 뜻이다. 

 

만들어진 image는 터미널에서 docker images를 입력하거나, Docker Desktop에서 확인할 수 있다.

docker images입력
Docker Desktop으로 확인

**문제발생**

나는 여기서 아래 명령어를 통해 컨테이너를 생성한 후 백그라운드로 어플리케이션을 실행하면 문제 없이 돌아가는줄 알았는데, 큰 착각이었다.

docker run -d -p 8080:8080 --env-file .env --name springboot-container courtnet-app1

 

어플리케이션은 실행되지 않았고,

Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. 
The driver has not received any packets from the server.

에러 로그를 가져와보면, 

어플리케이션이 Mysql 데이터베이스와의 연결에 실패했음을 알 수 있다.

 

앞서 .env에 입력해두었던 DB_URL 을 잠시가져와 보면,

DB_URL=jdbc:mysql://localhost:3306/courtnetdb

여기서 localhost는 Host OS(내 컴퓨터)를 가리키는 것이 아닌 컨테이너 내부의 localhost를 가리켜 SpringBoot 어플리케이션과 Mysql이 연결되지 않은 것이다. 

 

그렇다면 SpringBoot 어플리케이션을 감싸고 있는 컨테이너 내부에 Mysql을 같이 담으면 안될까?

 

이 방법이 권장되지 않는 이유는

 

첫째, 컨테이너의 단일 책임 원칙에 위배되기 때문이다. 서로 독립적인 SpringBoot와 Mysql은 개별 컨테이너로 관리해야 한다.

 

둘째, 관리 및 업데이트 문제가 있다. 예를 들어. SpringBoot를 업데이트해야 하는데, Mysql까지 포함된 이미지를 새로 빌드해야 한다면 불필요한 작업이 추가된다.

 

셋째, 재사용성이 부족해진다. Mysql은 여러 어플리케이션이 공유해서 사용할 수 있어야 한다. 한 컨테이너에 묶어두면 다른 어플리케이션과 재사용이 어렵다.

 

그래서 SpringBoot와 Mysql을 별도의 컨테이너에 담게되면, 이 둘은 어떻게 통신할까?

**3. docker-compose를 이용해 SpringBoot와 Mysql을 별도의 컨테이너에 담기**

 

docker에는 기본 네트워크 모드가 bridge(브릿지)로 존재하는데, docker-compose를 이용해 bridge 네트워크를 새로 생성하고 SpringBoot와 Mysql을 연동해 볼것이다. 

먼저 프로젝트 루트 디렉토리에 docker-compose.yml 파일을 생성한다.

# docker-compose.yml

# service 는 컨테이너에 올릴 서비스들 ( SpringBoot, Mysql )
services:
  # app - 여기서는 SpringBoot
  app:
  	# 이전에 생성한 image
    image: courtnet-app1
    
    # 왼쪽 : host(나)포트 번호, 오른쪽 : 컨테이너 내부 SpringBoot의 포트 번호
    ports:
      - "8080:8080"
      
     # .env 파일에 적어둔 환경변수들이 사용된다
    environment:
      SPRING_DATASOURCE_URL: ${DB_URL}
      SPRING_DATASOURCE_USERNAME: ${DB_USERNAME}
      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}
      JWT_SECRET_KEY: ${JWT_SECRET_KEY}
      
     # mysql이 실행된 후 SpringBoot가 실행됨
    depends_on:
      - mysql
     
    # bridge 네트워크 사용
    networks:
      - app-network
  
  mysql:
  
  	# docker hub에서 mysql image를 pull 해온다
    image: mysql:8.0
    
    # .env 파일에서 가져온 비밀번호로 초기 비밀번호를 변경해주고, courtnetdb라는 이름의 db를 생성한다
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
      MYSQL_DATABASE: courtnetdb
      
    # 왼쪽 : host(나)포트 번호, 오른쪽 : 컨테이너 내부 Mysql의 포트 번호
    ports:
      - "3306:3306"
      
    # bridge 네트워크 사용
    networks:
      - app-network
      
# bridge 네트워크 생성
networks:
  app-network:

 

docker-compose여러 개의 컨테이너를 정의하고 관리하는 도구로, 어플리케이션이 여러 컨테이너로 구성될 때, 이들을 한꺼번에 설정하고 실행할 수 있게 해준다. 

 

파일 하나로 자체 bridge 네트워크 생성, mysql과 SpringBoot를 각각의 컨테이너에 담아 실행한다.

 

그리고 실행 전 한 가지, .env 파일에서 DB_URL의 localhost:3306 을 <프로젝트이름>-<db서비스이름>-<순번>:3306 으로 바꿔준다.

DB_URL=jdbc:mysql://dbcourtnet-mysql-1:3306/courtnetdb

 

이제 아래 명령어를 통해 컨테이너를 생성하고 bridge 네트워크 안에서 SpringBoot와 Mysql을 연동할 수 있다.

docker-compose up -d

 

mysql image가 docker hub로부터 pull되었다

mysql image가 생성된 것을 볼 수 있고,

두 컨테이너가 정상적으로 실행중인 모습을 볼 수 있다.

 

두 컨테이너(app-1, mysql-1) 를 포함하고 있는 dbcourtnet은 docker compose stack으로, docker-compose로 정의한 서비스들의 집합을 의미한다. 컨테이너를 포함하는 상위 그룹이고, 기본적으로 컨테이너가 공유하는 bridge네트워크를 생성한다.

api도 정상동작한다.

 

아래 명령어를 통해 docker의 mysql 컨테이너에 직접 접속해 비밀번호가 초기화 된 상태인지, docker-compose.yml파일에 입력한대로 변경되었는지 확인해보자.

docker exec -it dbcourtnet-mysql-1 mysql -u root -p

 

터미널에서 docker exec 을 통해 컨테이너에 접근할 수 있다. VM과는 다르게 도커는 호스트와 커널을 공유하기에 호스트에서 직접 접근이 가능하다. 

 

더 나아가서 docker에 생성된 모든 네트워크 목록을 보고 싶다면

docker network ls

사진에서 dbcourtnet_app-network로 bridge 네트워크가 생성돼있는것을 볼 수 있다. 

 

docker network inspect dbcourtnet_app-network

위 명령어를 통해 dbcourtnet_app-network에 대한 세부 정보를 확인할 수 있다.

bridge 타입의 네트워크, 네트워크 서브넷과 게이트웨이의 IP주소, 연결된 컨테이너의 이름과 할당된 IP주소를 확인할 수 있다.

 

모두 172.18.0.0/16 이라는 서브넷 안에 포함되어 있는것을 확인할 수 있다.

 

다음 포스팅에서는 도커를 이용해서 클라우드 환경에 배포해보고, CI/CD 파이프라인을 구축하는 방법에 대해 다뤄볼 것이다.