Web/Docker

postgresql 사용하는 스프링부트 프로젝트 도커화

tose33 2023. 12. 31. 13:36

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 파일은 올리면 안된다. (시크릿키가 있다면) 

 

 

 

흐름

스프링앱에 클라이언트로부터 요청이 왔을때 흐름을 생각해보자

 

  1. 웹 브라우저에 localhost:8080 으로 접근한다
  2. 이 요청은 host 에 8080 포트로 전달된다
  3. host의 8080 포트는 app 컨테이너의 8080 포트와 연결되어 있기 때문에 app 컨테이너에게 전달된다 
  4. app 컨테이너는 db:5432 에 쿼리를 보낸다 
  5. db:5432 에서 db 는 같은 도커 네트워크의 db 의 ip 를 뜻한다
  6. 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 디렉토리를 생성해주면 될듯 하다.