https://aws.amazon.com/ko/compare/the-difference-between-docker-images-and-containers/
Docker의 이미지와 컨테이너에 대한 개념을 정립할 수 있는 AWS의 글이다.
Docker desktop을 설치하고, 배포를 위해 새로운 브랜치를 생성해준다.
**가장 중요한 0순위**
application.yml 파일의 민감한 정보들은 환경변수로 따로 저장해두자.
1. 프로젝트 루트 디렉토리에 .env 파일을 생성한다.
2. .env 파일안에는 기존에 application.yml 파일 안에 있던 민감한 정보들 ( DB, JWT_SECRET_KEY 등 ) 을 환경변수로 저장해 두고, 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 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가 생성된 것을 볼 수 있고,
두 컨테이너가 정상적으로 실행중인 모습을 볼 수 있다.
두 컨테이너(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 파이프라인을 구축하는 방법에 대해 다뤄볼 것이다.
'개발' 카테고리의 다른 글
[Java, Spring] 로그인 기능 구현해보기 (세션을 통한 인증) (4) | 2025.02.01 |
---|---|
[Java, Spring] 로그인 기능 구현해보기 (쿠키를 통한 인증) (2) | 2025.01.08 |