# Dockerfile
FROM ubuntu:20.04
WORKDIR /recipe-book
COPY . .
RUN apt-get update -y
RUN apt-get install -y python3
RUN apt install -y python3-pip
RUN pip install wheel
RUN pip install -r requirements.txt
ENV FLASK_APP=app
ENV FLASK_DEBUG=true
ENV APP_CONFIG_FILE=/recipe-book/config/docker.py
RUN chmod +x app.sh
CMD [ "./app.sh" ]
# app.sh
flask db init
flask db migrate
flask db upgrade
gunicorn --bind 0.0.0.0:5000 --timeout 90 "app:create_app()"
- Flask + Gunicorn (WSGI)로 구성된 웹 애플리케이션 서버
- Ubuntu 20.04 베이스
- 빌드 시 필요한 패키지와 의존 관계 설치
- Gunicorn WSGI가 Flask 애플리케이션을 인식할 수 있도록 Dockerfile에 환경 변수를 삽입하고, Flask 애플리케이션에 도커 전용 Config 파일을 작성
- 실행 시 app.sh 스크립트 실행
- app.sh 스크립트를 통해 데이터베이스를 설정하고, WAS를 실행
- 이전에 Lightsail에 배포했을 땐 Unix Socket을 통해 웹 서버와 통신했는데, 도커화 후에는 포트에 바인딩해 서비스
- Unix Socket은 같은 로컬 머신 안에서 빠르고 효울적으로 통신이 가능하지만, 본 프로젝트에서는 웹 서버와 WAS를 다른 컨테이너에 격리했기 때문에 포트로 서비스해야 함
# docker.py
from config.default import *
from dotenv import load_dotenv
import os
load_dotenv(os.path.join(BASE_DIR, '.env'))
SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://{user}:{pw}@{url}:{port}/{db}'.format(
user=os.getenv('DB_USER'),
pw=os.getenv('DB_PASSWORD'),
url=os.getenv('DB_HOST'),
port=os.getenv('DB_PORT'),
db=os.getenv('DB_NAME'))
SQLALCHEMY_TRACK_MODIFICATIONS = False
- 위와 같은 애플리케이션의 config/docker.py를 작성해 Docker 환경에서 설정을 자동화
# Dockerfile
FROM postgres:15.4
ENV LANG C.UTF-8
ENV DB_USER=postgres
ENV DB_PASSWORD=password
ENV DB_HOST=postgres
ENV DB_PORT=5432
ENV DB_NAME=recipe
COPY ./data /var/lib/postgresql/data
- PostgreSQL 데이터베이스 서버
- Dockerfile에서 기본 환경 변수를 설정
- 빌드 시 기본 데이터베이스 데이터를 COPY
# Dockerfile
FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf
# nginx.conf
events {
worker_connections 1000;
}
http {
upstream was {
server recipe-book-was:5000;
}
server {
listen 80;
location / {
proxy_set_header Connection "";
proxy_pass http://was/;
}
}
}
- Nginx 웹 서버
- 빌드 시 Nginx 설정 파일을 COPY
- 설정 파일에서 proxy_pass를 통해 스트림되는 요청을 라운드 로빈으로 부하 분산
# docker-compose.yaml
version: "3.3"
services:
recipe-book-postgres:
image: seungwonbae/recipe-book-postgres
container_name: postgres
volumes:
- postgres:/var/lib/postgresql/data
expose:
- "5432"
environment:
- POSTGRES_PASSWORD=password
networks:
- net
recipe-book-was:
depends_on:
- recipe-book-postgres
image: seungwonbae/recipe-book-was
expose:
- "5000"
networks:
- net
recipe-book-ws:
depends_on:
- recipe-book-was
image: seungwonbae/recipe-book-ws
container_name: ws
restart: always
ports:
- "80:80"
networks:
- net
volumes:
postgres: {}
networks:
net:
driver: bridge
- docker-compose 파일에 세 개의 서비스 등록
- DB: recipe-book-postgres
- WAS: recipe-book-was
- Web Server: recipe-book-ws
- DB에 볼륨을 설정해 재시작하더라도 데이터가 유지됨
- 웹 서버만 호스트와 포트를 매핑해 나머지는 백엔드에서 구동되도록 함
- 웹 서버를 통해서만 서비스 접근 가능
- 웹 서버가 죽으면 다시 재시작하도록 설정
- Compose default 네트워크를 사용해도 되지만, net 네트워크를 생성해 네트워크 구성을 명확하게 함
- 세 개의 WAS 컨테이너에 라운드 로빈으로 부하 분산이 되는 것을 확인
✅: 해결 이슈 ❓: 미해결 이슈
- WAS 컨테이너를 띄울 때 CMD 명령을 통해 Flask 애플리케이션을 실행
- 이 때, DB 설정을 docker exec -it was /bin/bash 명령을 통해 직접 붙어서 수동으로 해줘야 함
✅ 해결: 초기 설정 쉘 스크립트를 작성하고, 컨테이너 실행 시 CMD 명령이 이를 수행하도록 함
- 애플리케이션이 동작은 하나, 기능, 즉 로그인이나 글쓰기 처리가 매우 느림
- 로그를 확인해보니 WAS에서 Timeout 에러가 계속 나고 있었음
✅ 해결: 로그를 확인해보니 Timeout 속도가 너무 짧았던 것이 문제여서 WAS를 띄울 때 --timeout=90으로 설정
- 처음에는 PostgreSQL의 기본 이미지로 컨테이너를 띄움
- 이 때, 초기 DB 생성을 docker exec -it postgres /bin/bash 명령을 통해 직접 붙어서 수동으로 해줘야 함
✅ 해결: 초기 설정을 마친 DB 데이터를 로컬 머신에 저장해두고 이미지 빌드 시 해당 데이터를 COPY하도록 함, 이후에는 Named Volume에 저장된 해당 데이터를 읽기 때문에 자동으로 설정됨
- 웹 서버에서 정적 리소스를 서빙하도록 하고 싶어 location /static 설정을 해봤지만 정적 리소스 불러오기 불가능
❓미해결: 애플리케이션의 Static 경로를 웹 서버에서 읽어들일 수 있도록 설정해봐야겠음
⚠️ 해결 방안: Flask 애플리케이션에 있는 Static 리소스를 아예 Nginx의 컨테이너로 옮기던가, 아니면 지금처럼 그냥 WAS가 서빙하도록 하던가 하면 됨, 요새는 WAS가 웹 서버 기능도 거의 담당하고, 로드 밸런싱을 위해서라면 그냥 로드 밸런서를 쓰는 것이 나음
- 스케일링을 하지 않고 docker compose up을 했을 때는 애플리케이션이 정상적으로 실행됨
- --scale 옵션을 통해 WAS를 스케일링하면 WAS가 종료됨
✅ 해결: docker-compose 파일에서 recipe-book-was의 container_name을 명시적으로 준 것이 문제, 스케일링을 하려면 Docker가 컨테이너 이름을 관리하도록 해야 함