이전 포스트에서 local Docker Container에 띄워져 있는 MariaDB와 FastAPI를 연동하였습니다. 그런데 Project를 Heroku(또는 AWS)에 배포 한다고 생각했을 때 docker-compose로 배포하는 것이 낫다고 생각하였습니다. 문제는 제가 docker-compose를 사용한 적 이 없다는 것이었고 직접 연동해 보았습니다.
docker-compose 적용을 결정하면서 docker-compose.yml 파일 하나로 DB, Backend, Frontend, nginx 세팅이 가능하도록 프로젝트 구조를 잡았습니다. 또한, Backend 내부적으로 보았을 때 유지보수성을 확보할 수 있는 구조를 고민하였습니다. 토이 프로트 처럼 작은 규모의 프로젝트의 구조를 정하는 작업은 언제나 두 가지 고민이 떠오릅니다.
'Project 규모가 작은데 굳이 모듈화(분리) 시킬 필요가 있을까?'
실제로 규모가 작은 경우에는 FastAPI 공식문서의 예제들 처럼 main.py에서 관리하는 것이 훨씬 편합니다. 굳이 이 파일 저 파일 왔다갔다 하지 않고도 작업 할 수 있기 때문입니다.
'언제 규모가 커질 지 모르니 귀찮더라도 유지보수성을 향상 시키자'
그럼에도 불구하고 저는 프로젝트를 계층화 시켜서 유지보수성을 향상 시키는 쪽을 선택 하였습니다. 경험이 많다고는 말씀드릴 수 없지만 프로젝트 규모는 언제나 확장되는 방향으로 갔었고 계층화 시키지 않은 프로젝트의 경우, 회고를 하면서 항상 후회 했던 기억이 있습니다.
1. Project 구조
Backend, DB를 docker-compose로 연동하여 배포하기 위해 다음과 같이 프로젝트 구조를 세팅하였습니다.
1) docker-compose.yml
docker-compose.yml 파일과 동등한 위치에 backend, db, frontend, nginx 를 배치
project_root
│
├── docker-compose.yml
├── backend
│ └── app
│ ├── main.py
│ ├── Dockerfile
│ ├── requirements.txt
│ │
│ ├── business # 비즈니스 로직 구현 (여러 Service 통합 지점)
│ ├── config # db연결
│ ├── controllers # API Router
│ ├── models # Table Schema
│ ├── schemas # Request, Response Schema
│ └── services # CRUD 구현
│
└── db # DB 설정
│ ├── conf.d
│ ├── data
│ └── initdb.d
│
├── frontend # 추후 반영
├── nginx # 추후반영
2) Backend 구조
API Collection별로 API router 역할을 하는 Controllers에 작성하고 각각 Business Logic을 담당하는 계층과 연결 됩니다. 이 계층은 DB Data를 불러오는 Services와 연결되어 결과 값을 불러오고 API의 목적에 맞는 output으로 변형되어 Controllers로 전달합니다. (해당 내용은 docker-compose 와는 관련이 없습니다. Project 구조를 고민하시는 분들 참고만 하시기 바랍니다.)
2. Dockerfile
Backend 서버가 될 Docker Image를 만들고 Docker Container 환경을 정의하기 위해 Dockerfile을 생성합니다. (backend/app/Dockerfile)
# backend/Dockerfile
# pull official base image
FROM python:3.10.6-slim-buster
# set working directory
WORKDIR /usr/src/app
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# install system dependencies
RUN apt-get update \
&& apt-get -y install netcat gcc \
&& apt-get -y install default-libmysqlclient-dev \
&& apt-get clean
# install python dependencies
RUN pip install --upgrade pip \
&& pip install --upgrade setuptools
COPY requirements.txt .
RUN pip install -r requirements.txt
# add app
COPY app .
3. docker-compose.yml
version: '3.8'
services:
db:
image: mariadb:10
container_name: db
ports:
- 3306:3306
volumes:
- ./db/conf.d:/etc/mysql/conf.d
- ./db/data:/var/lib/mysql
- ./db/initdb.d:/docker-entrypoint-initdb.d
env_file: .env
environment:
TZ: Asia/Seoul
networks:
- backend
restart: always
backend:
build: ./backend
container_name: backend
command: uvicorn app.main:app --reload --workers 1 --host 0.0.0.0 --port 8000
volumes:
- ./backend:/usr/src/app
ports:
- 8004:8000
environment:
- ENVIRONMENT=dev
- TESTING=0
networks:
- backend
depends_on:
- db
networks:
backend:
작성한 docker-dompose.yml의 내용을 하나하나 살펴보겠습니다.
- version: docker-compose format 버전 입니다. '3.8'은 최신 버전입니다. 최신 docker-compose 문법을 사용하지 않은 상태에서 굳이 사용할 필욘은 없습니다. 통상 '3'을 많이 쓴다고 합니다.
- services: docker container 설정(생성)
- db
- image (mariadb:10)
- Docker Image를 pull, Local에 해당하는 name의 Image가 없는 경우 DockerHub의 Image를 pull
- container_name
- 원하는 이름으로 container를 생성하기 위해 사용
- DB 연동 시 DB_HOST를 ip주소가 아닌 container name으로 사용 가능 (networks 참고)
- ports
- Container와 communication을 위한 포트 포워딩
- volumes
- Host와 Container의 디렉토리를 연결
- [Host 저장소]:[Container 저장소]
- db 디렉토리 생성 후 하위에 3개의 디렉토리(conf.d, data, initdb.d)를 추가 생성함
- conf.d/my.cnf : mysql 기본 설정
# conf.d/my.cnf [client] default-character-set = utf8mb4 [mysql] default-character-set = utf8mb4 [mysqld] character-set-client-handshake = FALSE character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci
- initdb.d
- create_table.sql
- load.data.sql
- conf.d/my.cnf : mysql 기본 설정
- env_file
- docker-compose는 .env 파일을 그대로 읽어와 사용할 수 있음 (docker-compose.yml과 동등한 위치에 생성)
- DB 설정과 같은 민감 정보를 .env 파일을 활용해 보호함과 동시에 간편하게 연결 할 수 있음
- .env
MYSQL_HOST=<host> MYSQL_PORT=<port> MYSQL_ROOT_PASSWORD=<root_password> MYSQL_DATABASE=<database_name> MYSQL_USER=<user_name> MYSQL_PASSWORD=<user_password>
- environment:
- TZ: 생성된 DB의 Timezone
- restart (중요)
- always: Docker 재 시작 등의 상황에서 자동으로 Container를 재 시작하는 option
- networks : 별도 설명 참고
- image (mariadb:10)
- backend
- command: container 생성 후 실행 할 명령어
- depens_on
- 여러 Container를 생성하여 연결하는 docker-compose의 경우, 생성 시점에 따라 문제가 발생하는 경우가 있을 수 있음 (DB보다 Backend Container가 먼저 생성되어 구동될 경우 'db connecetion fail error' 발생)
- 먼저 생성 되어야하는 service 명을 입력하여 순서를 제어 할 수 있음
- db
- Networks
- Container 간 Communication을 위해 custome network를 생성 하여 사용
- 각각의 service에 생성한 networks 부분에 생성한 custom networks 값으로 묶어줌
- network 명칭은 [어플리케이션이름]_[정의한 network 명칭] 으로 정해짐
- 본 프로젝트의 경우, docker-compose.yml 파일 최하단에 'backend' 라는 이름으로 custom network를 새성하였고 각각의 service에 이를 명시하여 사용 중
- network를 inspect 해보면, Containers에 service에서 정의한 db와 backend가 포함되어 있음을 확인 가능
docker network inspect [application name]_[custom network name]
[ { "Name": "life_sports_backend_backend", "Id": "216757e7ee77c080a2d871ab149bbb864a0819dd0272836c40522ab4aec237c5", "Created": "2022-09-21T05:32:32.016644426Z", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "0.0.0.0/16", "Gateway": "0.0.0.1" } ] }, "Internal": false, "Attachable": true, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": { "2ec77560d21e55c99d8faa5b475d9db169302c46796f27d26c6392c9e001822f": { "Name": "backend", "EndpointID": "15fba2e62138ec6f746d55fb3aeda95377ddde1705f6038bd8c1169bb21fddc0", "MacAddress": "", "IPv4Address": "0.0.0.0/16", "IPv6Address": "" }, "ee1803657f04e7bda712f865c17c0b11f29f83a3d2f4f53d4b8abf910f5ed4ea": { "Name": "db", "EndpointID": "b274b7ddb234b7f96e28329ee4584b50e48afd270d1908158accfd2eaf006575", "MacAddress": "", "IPv4Address": "0.0.0.0/16", "IPv6Address": "" } }, "Options": {}, "Labels": { "com.docker.compose.network": "backend", "com.docker.compose.project": "application_name", "com.docker.compose.version": "1.29.2" } } ]
4. 실행
- FastAPI 내부 DB 연결 정보 변경
- 기존에 IP 주소로 연결 해놓았던 DB Host 정보를 생성한 Container이름인 'db'로 변경
# /backend/app/conofig/secrets.json { "DB": { "user": "{ DB-USER-NAME }", "password": "{ DB-PASSWORD }", "host": "db", # container 명칭 "port": { DB-PORT }, "database": "{ DB-NAME } " } }
- 기존에 IP 주소로 연결 해놓았던 DB Host 정보를 생성한 Container이름인 'db'로 변경
- docker-compose 실행
docker-compose build docker-compose up -d
5. 맺음말
docker-compose로 FastAPI와 DB 연동하는 내용만 작성하려다 보니 Docker Networks를 잘 몰라서 에러를 정말 많이 냈던 기억 때문에 docker-compose.yml 파일을 좀 상세히 다뤄보았습니다. 특히, Networks가 가장 어려웠다 보니 이 부분을 docker inspect를 통해 확인 까지 해보았습니다. 모두 작성하고 보니 제목에 비해 내용이 비대해진 것 같습니다. 부족한 글 이오니 궁금하신 점 댓글로 남겨주시면 성심 껏 답변 드리겠습니다. 감사합니다.
'old > WebProject' 카테고리의 다른 글
[FastAPI] 3. FastAPI - MariaDB(MySQL) Restful API / CRUD (0) | 2022.09.26 |
---|---|
[FastAPI] 0. 프로젝트 개요 (0) | 2022.09.15 |