postgresql 사용하는 스프링부트 프로젝트 도커화
https://tose33.tistory.com/1341
Docker
용어 Host OS가 설치된 컴퓨터 Container host 에 설치된 각각의 격리된 실행 환경. 각 컨테이너에는 OS 가 각각 설치되어 있지 않고 어플리케이션을 실행하는데 필요한 lib(라이브러리),bin(실행파일) 만
tose33.tistory.com
https://tose33.tistory.com/1342
Dockerfile
Dockerfile 은 이미지를 만드는 방법이다. 이미지는 여러개의 레이어로 이루어져있다고 했는데 이 레이어들이 뭔지 내가 정의하여 이미지를 만드는 방법이다. FROM ubuntu:20.04 # -y 옵션은 정말 다운로
tose33.tistory.com
https://tose33.tistory.com/1343
Docker-compose
어떤 환경에서 어플리케이션을 실행할때 해당 어플리케이션은 여러개의 또 다른 어플리케이션에 종속적일수 있다. 예를들어 웹서버 어플리케이션 컨테이너가 있는데, 이 웹서버는 정보를 저장
tose33.tistory.com
도커에 대해 충분히 알아봤으니 이제 기존의 내 스프링 프로젝트를 도커화 해보자.
내 스프링 웹 프로젝트는 디비로 postgres 를 사용하고 있다.
따라서 spring app 컨테이너와 postgres 컨테이너 총 두개의 컨테이너를 하나의 네트워크를 구성해 만들것이다.
그리고 docker-compose 를 사용해 네트워크를 구축할것이다.
- host 의 8080 포트로 요청이 오면 스프링앱 컨테이너의 8080 포트로 전달된다
- Postgres 는 5432 포트를 listen 하고 있다
- 스프링 앱 컨테이너가 데이터 저장 등을 위해 postgres 컨테이너의 5432 포트로 소통한다
docker-compose.yml
version: '3.7'
services:
db:
image: postgres:latest
# restart: always
# ports:
# - 5432:5432
environment:
POSTGRES_DB: instawebv2
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- ./postgres_data:/var/lib/postgresql/data
app:
depends_on:
- db
build:
context: .
dockerfile: Dockerfile
restart: always
ports:
- 8080:8080
environment:
DATASOURCE_URL: ${DATASOURCE_URL}
GOOGLE_REDIRECT_URI: ${GOOGLE_REDIRECT_URI}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
NAVER_REDIRECT_URI: ${NAVER_REDIRECT_URI}
NAVER_CLIENT_ID: ${NAVER_CLIENT_ID}
NAVER_CLIENT_SECRET: ${NAVER_CLIENT_SECRET}
KAKAO_REDIRECT_URI: ${KAKAO_REDIRECT_URI}
KAKAO_CLIENT_ID: ${KAKAO_CLIENT_ID}
KAKAO_CLIENT_SECRET: ${KAKAO_CLIENT_SECRET}
app 의 이미지 만드는 Dockerfile
FROM openjdk:17-jdk-slim
# /build/libs/ 디렉토리의 jar 파일을 도커 이미지에 복사하고 app.jar 로 이름 변경
ADD /build/libs/*.jar app.jar
# 도커 컨테이너 실행 시 실행 될 명령어
# java -jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
db
- image
- 이미지는 postgres:latest 로 자동으로 hub 에서 최신버전을 pull 한다
- https://hub.docker.com/_/postgres
- 위 링크가 docker hub 의 postgres official image 인데 버전별로 Docker file 이 나와있다.
- 해당하는 Dockerfile 이 pull 되서 빌드 된다
- ports
- 5432:5432 는 host 의 5432 포트와 db 컨테이너의 5432 포트를 연결하는 것이다.
- 그런데 지금 주석처리 해놨는데 이부분은 필요가 없기 때문이다
- 검색 했을때 이렇게 된 경우가 많아서 처음에 그냥 명시했는데 내 경우에는 app 컨테이너와 db 컨테이너가 하나의 네트워크를 이루고 있다 (docker-compose 에 services 로 정의하면 저절로 하나의 네트워크에 속하게 된다).
- DATASOURCE_URL=jdbc:postgresql://db:5432/instawebv2
- 이런 식으로하면 app 컨테이너에서 환경변수를 지정해주면 docker-compose 에서 서비스의 이름으로 같은 네트워크에 속한 컨테이너의 ip가 자동으로 가르키게된다
- 즉 db 부분이 같은 네트워크의 db 컨테이너의 ip 로 대체되기 때문에 여기서는 db 컨테이너의 5432 포트와 host 의 5432 포트를 연결해줄 필요는 없다.
- environment
- POSTGRES_DB: 명시한 이름이 이미지가 시작될때 만들어지는 기본 db 의 이름이 된다. 명시 하지 않는다면 POSTGRES_USER 의 값으로 db 이름이 만들어진다.
- POSTGRES_USER: 이 값으로 슈퍼유저 권한을 가진 사용자가 생성된다. 이 변수를 지정하지 않으면 postgres의 기본 사용자 (postgres) 가 사용된다.
- POSTGRES_PASSWORD: 슈퍼유저의 비밀번호가 된다. 이 값은 필수고 POSTGRES_DB, POSTGRES_USER 는 명시하지 않아도 된다.
- 이 환경변수 값들은 docker hub 의 postgres official image 에 나와있는 compose 에 입력해야 하는 값들이다. (https://hub.docker.com/_/postgres).
- 즉 postgres 컨테이너가 생성될때 이 환경변수들을 기반으로 db, 사용자를 만들고 슈퍼유저 권한도 부여한다.
- volumes
- ./postgres_data:/var/lib/postgresql/data
- 내 호스트 디렉토리인 ./postgres_data 와 postgres 컨테이너 내부의 디렉토리 /var/lib/postgresql/data 를 바인딩한다
- db 컨테이너에서 /var/lib/postgresql/data 에 디비에 저장되는 값들이 저장되는데, host 의 ./postgres_data 와 바인딩되있기 때문에 host 에도 동일하게 저장된다
- 그말은 즉 컨테이너를 껐다 켜도 host 에서 ./postgres_data 폴더가 건재하는 이상 데이터는 유지된다
app
- depends_on
- 여기 정의된 서비스 이름의 컨테이너가 먼저 생성되고 해당 서비스 컨테이너가 생성된다
- 즉 여기서는 app 컨테이너는 db 서비스가 먼저 생성된 후에 생성된다
- 하지만 이 말이 db 컨테이너의 db 가 준비완료 된 후에 app 컨테이너 시작이 보장된다는 말은 아니다
- 직접 docker-compose 로 빌드해보면 알 수 있는데 db 컨테이너 생성 후 -> app 컨테이너가 생성되는 것이지
- postgres db 자체가 준비완료되서 5432 포트를 리스닝하는 상태가 된 후에 app 컨테이너가 실행되는 것이 아니다
- 따라서 app 은 restart:always 로 재시작하게 해줘야 db 가 준비완료 되기 전에 spring boot 가 실행되도 다시 시작할수 있다
- build
- 현재 디렉토리 (.) 의 Dockerfile 을 기반으로 이미지를 만들고 빌드해서 컨테이너를 생성한다
환경변수
docker-compose 파일에 ${DATASOURCE_URL} 이런식으로 환경변수를 사용했다.
url 같은 경우 환경에 따라 바뀔수도 있고 무엇보다 secret key 때문에 환경변수를 사용했다.
docker에서는 docker-compose 파일이 있는 경로에 .env 파일에 환경변수를 저장할수 있고
${} 이런식으로 바로 사용할수 있다.
.env 파일
DATASOURCE_URL=jdbc:postgresql://db:5432/instawebv2
GOOGLE_REDIRECT_URI=http://localhost:8080/login/oauth2/code/google=value
물론 깃 같은 버전컨트롤 시스템을 사용한다면 이 .env 파일은 올리면 안된다. (시크릿키가 있다면)
흐름
스프링앱에 클라이언트로부터 요청이 왔을때 흐름을 생각해보자
- 웹 브라우저에 localhost:8080 으로 접근한다
- 이 요청은 host 에 8080 포트로 전달된다
- host의 8080 포트는 app 컨테이너의 8080 포트와 연결되어 있기 때문에 app 컨테이너에게 전달된다
- app 컨테이너는 db:5432 에 쿼리를 보낸다
- db:5432 에서 db 는 같은 도커 네트워크의 db 의 ip 를 뜻한다
- db 의 5432 포트로 쿼리가 전달된다
결과
docker compose up
compose 파일을 up 커맨드로 실행하면 docker-compose 에 정의된 내용에 따라 컨테이너들이 실행된다.
...
db-1 이 먼저 실행되서 5432 포트를 리스닝하고 있다.
app-1 에서 스프링 부트가 실행된다.
에러
docker could not open directory "pg_notify": No such file or directory
docker-compose 를 up 했는데 db 컨테이너가 위와 같은 에러가 뜨면서 실패하고 종료됐다.
호스트에 volumes 로 매핑한 ./postgres_data 디렉토리 내부에 pg_notify 디렉토리가 없다는 의미다.
이 에러는 집에서 윈도우로 작업할때는 없다가 깃에 올리고 다른 곳에서 pull 받아서 작업할때 생겼다.
아마 git 에 올라가는 과정에서 안올라갔거나 에러가 있었던것 같다.
./postgres_data 디렉토리는 어처피 postgres:latest 이미지 에 의해 자동으로 생성되기 때문에 디렉토리를 전부 지우고 다시 doker-compose 를 up 하니 해결됐다.
물론 지금은 테스트 단계라 postgres_data 디렉토리를 지웠지만, 이걸 지우면 db 데이터도 다 날라간다.
아마 그냥 postgres_data 디렉토리 내부에 pg_notify 디렉토리를 생성해주면 될듯 하다.