학교에서 진행한 프로젝트에서 프론트엔드 개발과 배포를 맡아 진행하였고, 도커를 기반으로 온프레미스 환경에 서버를 배포하는 작업을 하였습니다.
제가 해당 프로젝트를 진행하면서 직면했던 가장 큰 문제점은 DB Connection Refused 에러였습니다.
문제 상황
개발이 다 완료된 후 개발 서버에 배포를 진행하였는데, DB와 백엔드의 연동이 되지 않아 발생한 문제였습니다. 기존에 다른 플랫폼이 설정한 레퍼런스를 기반으로 실행해보았지만, 백엔드와 DB가 연동이 되지 않았습니다.
해결 방법
우선 다른 플랫폼의 배포 과정을 확인해보니, 다른 플랫폼 같은 경우에는 개발 서버에 DB 컨테이너가 이미 있다는 가정하에 백엔드 컨테이너를 실행하고 있었다는 점이 저희 플랫폼과 다른점이었습니다. 따라서 해당 레퍼런스를 기준으로 환경을 설정할 수 없어 독자적인 방법으로 배포하고자 하였습니다.
가장 첫 번째이자 중요한 문제인 백엔드-DB 컨테이너 연동이 안되는 것이 문제였기 때문에 개발 서버에서 작업하는 것보다 로컬 서버에서 작업하는 것이 간편하기 때문에 로컬에서 빌드를 시도해보았습니다. 결국 도커는 이미지 기반이기 때문에 로컬에서 동작한다면 개발 서버에서도 동작할 것이고, 그 반대 또한 동작할 것이기 때문입니다. 하지만 예상대로, 로컬에서도 연동이 되지 않았습니다.
그 다음은 하나의 네트워크 안에 있지 않아서 연동할 수 없는 것인가라는 생각이 들어 도커 네트워크를 설정한 뒤, 해당 네트워크 안에 백엔드와 DB 컨테이너가 포함되게 하고 빌드하였습니다. 하지만 이 방법 또한 같은 에러를 반환했는데, 사실 도커 네트워크를 명시적으로 설정하지 않으면 기본 브릿지 네트워크(docker0)에 포함되기 때문에 해당 방법은 원인이 될 수 없었습니다.
그 다음으로 생각한 것이 DB의 생성 과정이었습니다. h2 console에서 mysql로 이전할 때에 로컬에 DB 더미 데이터가 있어야 해서 각 로컬에 더미 데이터를 수동으로 추가해주었습니다. 해당 과정을 통해 DB가 먼저 생성되고 백엔드가 나중에 실행되고 있어서 문제가 생기고 있는 것 같다는 생각을 하였습니다.
기존에는 테이블을 생성해두고, postman에 POST를 통해 데이터를 삽입해주었는데, 도커는 이미지 기반이고, 더미 데이터가 없기 때문에 기존의 방식대로 진행한다면 컨테이너를 실행할 때마다 더미 데이터를 추가해주어야 합니다. 그리하여 dockerfile을 통해 db에 더미 데이터를 COPY한 후 docker compose를 통해 실행해보았습니다. docker compose를 통해 백엔드와 DB 컨테이너를 독립적으로 실행하였는데, DB의 컨테이너가 늦게 생성되고, 연결이 안되는 문제점을 발견하였습니다. 따라서 depends_on을 통해 DB의 상태가 정상적이고 시작할 수 있다면(Healthy), 백엔드 컨테이너를 실행하도록 설정하였더니, 연동을 할 수 있게 되었습니다.
즉, DB 컨테이너가 정상적으로 동작한 뒤에 백엔드 컨테이너를 실행해야 DB connection이 established된다는 것을 알게 되었습니다.
코드로 살펴보겠습니다. 다음은 제가 작성한 docker-compose.yml 파일입니다.
version: '3.8'
services:
db:
build:
context: ./db
container_name: local-siop-db
environment:
MYSQL_ROOT_PASSWORD: 1234
MYSQL_DATABASE: siop-db
MYSQL_USER: dev
MYSQL_PASSWORD: 1105
networks:
- test
ports:
- "4510:3306"
healthcheck: # MySQL 초기화 확인
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "dev", "-p1105"]
interval: 10s
timeout: 5s
retries: 3
backend:
build:
context: ./backend/sucpi
container_name: siop-backend
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://local-siop-db:3306/siop-db
SPRING_DATASOURCE_USERNAME: dev
SPRING_DATASOURCE_PASSWORD: 1105
depends_on:
db:
condition: service_healthy # MySQL이 준비되면 시작
networks:
- test
ports:
- "4502:8080"
frontend:
build:
context: ./frontend/sucpi
container_name: siop-frontend
ports:
- "4501:3000"
networks:
- test
environment:
# 로컬 서버
# VIRTUAL_HOST: frontend.localhost
# 개발 서버
# VIRTUAL_HOST : 사용자가 접근할 도메인 이름 설정
# LETSENCRYPT_HOST : 해당 도메인의 SSL 인증서를 발급받기 위한 도메인 이름
VIRTUAL_HOST: "siop-dev.skku.edu"
LETSENCRYPT_HOST: "siop-dev.skku.edu"
VIRTUAL_PORT: 3000
extra_hosts:
- host.docker.internal:host-gateway
command: ["npm", "start"]
# 로컬 서버
# nginx:
# build:
# context: ./nginx
# container_name: siop-nginx
# ports:
# - "80:80"
# networks:
# - test
# depends_on:
# - backend
networks:
test:
driver: bridge
도커를 기반으로 DB와 백엔드 연동에서 이와 같은 문제를 발견하신다면, depends_on을 통해 DB 컨테이너가 실행된 후 백엔드 컨테이너를 실행하는 방법을 사용해보시면 될 것 같습니다.