Dockerfile, docker-compose, 환경변수(.env) 설정
가장 먼저 한 것은 docker 관련 설정들 입니다. 웹 개발 공모전 경험을 바탕으로 가장 먼저 해야하는 것은 docker 설정을 통해 개발자들간의 환경을 일치시키는 것이었습니다. 팀원들의 docker에 대한 이해도를 높여주기 위해서 틈틈히 사용방법을 알려주고 docker desktop 설치를 권했습니다.
backend/Dockerfile 코드
# backend/Dockerfile
FROM python:3.10.10
# opencv 설치를 위한 라이브러리 설치
RUN apt-get update && apt-get install -y libgl1-mesa-glx
# 영상 자르기 위한 ffmpeg 설치
RUN apt-get update && apt-get install -y ffmpeg
COPY ./ /usr/src/django
WORKDIR /usr/src/django
EXPOSE 8000
# 필요한 파이썬 패키지 설치
COPY requirements.txt /tmp/requirements.txt
RUN pip install -r /tmp/requirements.txt
먼저 django를 위한 Dockerfile 입니다. 저는 3.10버전을 채택한 이유가 공모전 당시의 FastAPI를 사용할 때 3.9와 3.10 버전의 차이가 타입설정이 있어서 프론트로 Typescript를 쓰는 입장에서 10버전을 쓰는 것 이 맞다는 판단이 들어서 채택했습니다. 만약에 ai용 서버를 따로 뒀었더라면 Base Image를 alpine 버전을 사용했겠지만, 팀원들의 역량을 고려해서 일반 버전을 사용했습니다.
frontend/Dockerfile 코드
# frontend/Dockerfile
FROM node:18-alpine AS base
FROM base AS builder
ARG NEXT_PUBLIC_ENV_API_DOMAIN
ARG NEXT_PUBLIC_ENV_API_URL
ARG NEXT_PUBLIC_ENV_DOMAIN
ENV NEXT_PUBLIC_ENV_API_DOMAIN=${NEXT_PUBLIC_ENV_API_DOMAIN}
ENV NEXT_PUBLIC_ENV_API_URL=${NEXT_PUBLIC_ENV_API_URL}
ENV NEXT_PUBLIC_ENV_DOMAIN=${NEXT_PUBLIC_ENV_DOMAIN}
WORKDIR /usr/src/nextjs
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile --non-interactive --production=false
COPY ./ ./
RUN yarn run build
FROM base AS runner
ARG NEXT_PUBLIC_ENV_API_DOMAIN
ARG NEXT_PUBLIC_ENV_API_URL
ARG NEXT_PUBLIC_ENV_DOMAIN
ENV NEXT_PUBLIC_ENV_API_DOMAIN=${NEXT_PUBLIC_ENV_API_DOMAIN}
ENV NEXT_PUBLIC_ENV_API_URL=${NEXT_PUBLIC_ENV_API_URL}
ENV NEXT_PUBLIC_ENV_DOMAIN=${NEXT_PUBLIC_ENV_DOMAIN}
WORKDIR /usr/src/nextjs
COPY --from=builder /usr/src/nextjs/public ./public
COPY --from=builder /usr/src/nextjs/.next/standalone ./
COPY --from=builder /usr/src/nextjs/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]
Nextjs를 위한 설정입니다. 공모전 당시, Nextjs 컨테이너틀 최적화 하는 과정에서 standalone 옵션에 대해 알게 되었고, Vercel에서 알려주는 방식을 살짝 수정했습니다. builder stage와 runner stage로 나눠져 있으며, 빌드 후에 실행하는 과정이기 때문에 volume 설정을 할 수 없었습니다. 추가로 docker-compose에서 환경 변수를 받아서 적용해주는 것을 했습니다.
docker-compose-back.yml 코드
# docker-compose-back.yml
version: "3.3"
services:
django:
container_name: django
build:
context: ./backend
dockerfile: Dockerfile
volumes:
- ./backend:/usr/src/django
command: >
sh -c "python manage.py makemigrations &&
python manage.py migrate &&
python manage.py flush --no-input &&
python manage.py seed_users &&
python manage.py seed_posts &&
python manage.py seed_comments &&
python manage.py seed_like &&
python manage.py seed_view_history &&
python manage.py seed_video_section &&
python manage.py seed_search &&
python manage.py seed_feedbacks &&
python manage.py runserver 0.0.0.0:8000"
environment:
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY}
DJANGO_TIME_ZONE: ${DJANGO_TIME_ZONE}
DJANGO_LANGUAGE_CODE: ${DJANGO_LANGUAGE_CODE}
DJANGO_NEXTJS_URL: ${DJANGO_NEXTJS_URL}
DJANGO_S3_ACCESS_KEY_ID: ${DJANGO_S3_ACCESS_KEY_ID}
DJANGO_S3_SECRET_ACCESS_KEY: ${DJANGO_S3_SECRET_ACCESS_KEY}
TZ: "Asia/Seoul"
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_ROOT_HOST: ${MYSQL_HOST}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
AWS_DOMAIN: ${AWS_DOMAIN}
ORIGIN_VIDEO_DOMAIN: ${ORIGIN_VIDEO_DOMAIN}
CLOUDFRONT_DOMAIN: ${CLOUDFRONT_DOMAIN}
restart: always
ports:
- "8000:8000"
networks:
- server-network
depends_on:
- mysqldb
mysqldb:
container_name: mysqldb
image: mysql
restart: always
command: mysqld --character-set-server=utf8 --collation-server=utf8_general_ci --default-authentication-plugin=mysql_native_password
volumes:
- ./mysqldb/db:/var/lib/mysql
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_HOST: ${MYSQL_HOST}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
TZ: "Asia/Seoul"
networks:
- server-network
networks:
server-network:
docker-compose.yml은 초반에는 저만 사용했습니다. 그 이유는 백엔드 분들의 진행속도가 느린 점을 감안해서 프론트엔드인 제가 백엔드 서버도 같이 실행해서 개발을 하여 코드통합을 지속적으로 했습니다. 중후반에는 백엔드 분들도 docker 환경을 이용해서 개발을 진행하도록 가르쳐서 실제로 잘 사용되었습니다. (초반에 백엔드 분들이 docker환경을 쓰지 않도록 했던 것은 재빌드하는 과정이 길어지면서 개발 효율이 떨어지기 때문이었습니다.)
docker-compose-front.yml 코드
# docker-compose-front.yml
version: "3.3"
services:
nextjs:
container_name: nextjs
restart: "always"
build:
context: ./frontend
dockerfile: Dockerfile
args:
NEXT_PUBLIC_ENV_API_DOMAIN: ${NEXT_PUBLIC_ENV_API_DOMAIN}
NEXT_PUBLIC_ENV_API_URL: ${NEXT_PUBLIC_ENV_API_URL}
NEXT_PUBLIC_ENV_DOMAIN: ${NEXT_PUBLIC_ENV_DOMAIN}
environment:
TZ: "Asia/Seoul"
networks:
- server-network
nginx:
container_name: nginx
restart: "always"
build:
context: ./nginx
dockerfile: Dockerfile
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
# certbot
- /etc/letsencrypt:/etc/letsencrypt
# letsencrypt
- /var/lib/letsencrypt:/var/lib/letsencrypt
- ./nginx/sites.conf:/etc/nginx/conf.d/sites.conf
- ./nginx/sites-enabled:/etc/nginx/sites-enabled
environment:
TZ: "Asia/Seoul"
depends_on:
- nextjs
networks:
- server-network
networks:
server-network:
driver: bridge
배포하기 직전에만 사용했던 docker-compose-front.yml 파일입니다. Nginx와 Nextjs를 컨테이너로 올리는 코드입니다.
Django 설정
requirements.txt 코드
# requirements.txt
Django==4.2
django-cors-headers==4.0.0
djangorestframework==3.14.0
djangorestframework-jwt==1.11.0
djangorestframework-simplejwt==5.2.2
django-seed==0.3.1
drf-yasg==1.21.5
mysqlclient==2.1.1
django-seed==0.3.1
boto3==1.26.143
flake8==6.0.0
python-dotenv==1.0.0
django-extensions==3.2.1
numpy==1.24.3
mediapipe==0.10.0
mediapipe-rpi4==0.8.8
moviepy==1.0.3
opencv-python==4.7.0.72
protobuf==3.19.6
tensorflow==2.11.0
tensorflow-hub==0.13.0
psycopg2==2.9.6
moviepy==1.0.3
# celery==5.3.1
# redis==4.6.0
# django-celery-results==2.5.1
# django-redis==5.3.0
requirements.txt를 기준으로 venv나 docker 환경을 사용했기에 개발자간의 환경 불일치로 인한 문제는 생기지 않도록 했습니다.
config/manage.py 코드
# manage.py
"""Django's command-line utility for administrative tasks."""
import os
import sys
import dotenv
def main():
"""Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
dotenv.load_dotenv() # dotenv 설정
main()
초기에 venv를 사용하는 백엔드 분들을 위해서 dotenv 설정을 추가로 했었습니다.
.flake8 코드
[flake8]
exclude =
.git,
.gitignore,
*.pot,
*.py[co],
__pycache__,
.venv,
.env
ignore =
E501
파이썬 코드의 가독성을 고려해서 flake8 설정을 해주었습니다.
Nextjs 설정
next.config.js 코드
// next.config.js
const { PHASE_DEVELOPMENT_SERVER } = require("next/dist/shared/lib/constants");
/** @type {import('next').NextConfig} */
const nextConfig = (phase) => {
if (phase === PHASE_DEVELOPMENT_SERVER) {
return {
reactStrictMode: false,
images: {
remotePatterns: [
{
protocol: "https",
hostname: "dancify-bucket.s3.ap-northeast-2.amazonaws.com",
port: "",
pathname: "/**",
},
{
protocol: "https",
hostname: "dancify-bucket2.s3.ap-northeast-2.amazonaws.com",
port: "",
pathname: "/**",
},
],
},
};
}
return {
reactStrictMode: false,
output: "standalone",
images: {
remotePatterns: [
{
protocol: "https",
hostname: "dancify-bucket.s3.ap-northeast-2.amazonaws.com",
port: "",
pathname: "/**",
},
{
protocol: "https",
hostname: "dancify-bucket2.s3.ap-northeast-2.amazonaws.com",
port: "",
pathname: "/**",
},
],
},
};
};
module.exports = nextConfig;
PHASE_DEVELOPMENT_SERVER를 사용해서 개발과 배포를 구분하여 config 설정을 주도록 했지만, s3를 사용하게 되면서 특별히 달라질 필요는 없었습니다. 위 코드는 S3에게 데이터를 요청하기 위해서 따로 설정한 것입니다.
nginx 설정
default.conf 코드
# default.conf
# 캐싱 관리를 위한 설정
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=STATIC:10m inactive=60m use_temp_path=off;
upstream nextjs_upstream {
server nextjs:3000;
}
upstream django_upstream {
server 아이피주소:8000;
}
server {
listen 80;
server_name localhost;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name 도메인명;
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE";
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range";
add_header Access-Control-Expose-Headers "Content-Length,Content-Range";
add_header Access-Control-Allow-Credentials "true";
add_header Access-Control-Max-Age 7200;
# SSL 인증서와 개인 키 파일 경로 설정
ssl_certificate /etc/letsencrypt/archive/도메인명/fullchain1.pem;
ssl_certificate_key /etc/letsencrypt/archive/도메인명/privkey1.pem;
# gzip compression 설정
gzip on;
# 클라이언트에게 gzip으로 응답하는 설정
gzip_proxied any;
# gzip의 강도, cpu 부하 영향
gzip_comp_level 2;
# gzip 압축을 적용할 최소 응답 크기 설정
gzip_min_length 256;
# gzip 압축이 적용된 응답에 Vary 헤더 포함
gzip_vary on;
# gzip을 할 확장자
gzip_types application/javascript text/css text/xml text/plain application/x-javascript application/xml application/xml+rss;
location /api {
proxy_pass http://django_upstream;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_cache_bypass $http_upgrade;
# 클라이언트 요청 바디의 최대 허용 크기 설정
client_max_body_size 200m;
# 클라이언트 요청 바디를 읽는 버퍼 크기 설정. 기본값 8k
client_body_buffer_size 128k;
# 클라이언트와의 연결 유지 시간 설정
keepalive_timeout 240s;
# 클라이언트와의 연결이 끊길 때까지의 대기 시간 설정
send_timeout 240s;
# 프록시 서버와의 연결 시도 시간 초과 설정
proxy_connect_timeout 240s;
# 프록시 서버로의 데이터 전송 시간 초과 설정
proxy_send_timeout 240s;
# 프록시 서버로부터 데이터 수신 시간 초과 설정
proxy_read_timeout 240s;
# 프록시 서버의 버퍼 크기 설정
proxy_buffer_size 4k;
# 프록시 서버의 버퍼 개수와 각 버퍼의 크기 설정
proxy_buffers 4 32k;
# 프록시 서버에서 사용 중인 버퍼의 최대 크기 설정
proxy_busy_buffers_size 64k;
# 프록시 서버에 임시 파일을 쓰는 버퍼의 크기 설정
proxy_temp_file_write_size 64k;
}
location /.well-known/acme-challenge {
root /var/lib/letsencrypt/;
}
location / {
proxy_pass http://nextjs_upstream;
proxy_http_version 1.1;
proxy_set_header Connection "Upgrade";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_cache_bypass $http_upgrade;
# 클라이언트 요청 바디의 최대 허용 크기 설정
client_max_body_size 200m;
# 클라이언트 요청 바디를 읽는 버퍼 크기 설정. 기본값 8k
client_body_buffer_size 128k;
# 클라이언트와의 연결 유지 시간 설정
keepalive_timeout 240s;
# 클라이언트와의 연결이 끊길 때까지의 대기 시간 설정
send_timeout 240s;
# 프록시 서버와의 연결 시도 시간 초과 설정
proxy_connect_timeout 240s;
# 프록시 서버로의 데이터 전송 시간 초과 설정
proxy_send_timeout 240s;
# 프록시 서버로부터 데이터 수신 시간 초과 설정
proxy_read_timeout 240s;
# 프록시 서버의 버퍼 크기 설정
proxy_buffer_size 4k;
# 프록시 서버의 버퍼 개수와 각 버퍼의 크기 설정
proxy_buffers 4 32k;
# 프록시 서버에서 사용 중인 버퍼의 최대 크기 설정
proxy_busy_buffers_size 64k;
# 프록시 서버에 임시 파일을 쓰는 버퍼의 크기 설정
proxy_temp_file_write_size 64k;
}
}
Nextjs와 Nginx를 같은 EC2를 사용하여 ssl, dns 설정을 해주었고, Django는 다른 EC2를 사용하도록 해서 Django의 경우에 외부 아이피 주소를 넣어줬었습니다.
Django에 ssl 설정을 하지 않은 이유는 https로 해줬을 때 cpu 성능에 영향을 끼치기 때문에 동영상 처리기능이 많은 저희 백엔드의 상황을 고려하고, 시간 절약을 위해서 http로 두었습니다.
아쉽게도 Django의 동영상 전처리 기능들이 비동기 처리를 하지 못해서 default.conf에 timeout 설정들을 4분(240s)으로 늘려주었습니다.
'Experience > - KT AIVLE School' 카테고리의 다른 글
KT AIVLE School 빅프로젝트 - 프론트에서 S3의 GET요청을 대기하는 로직 (0) | 2023.07.19 |
---|---|
KT AIVLE School 빅프로젝트 - 배포 과정 정리 (0) | 2023.07.19 |
KT AIVLE School 빅프로젝트 - 프로필 수정 로직에 대한 고민 (0) | 2023.07.19 |
KT AIVLE School 빅프로젝트 - Django와 React 소통하기 (0) | 2023.07.19 |
AIVLE School 17주차 정리 - 미니프로젝트 7차 (0) | 2023.05.27 |
KT AIVLE School 15주차 정리 - SQL (0) | 2023.05.08 |
댓글