do it! 장고+부트스트랩 파이썬 웹 개발의 정석 책을 참고하며 작성한 포스팅입니다.
개발할때는 장고에서 기본적으로 제공하는 기능인 runserver
을 사용해도 상관없지만, 실제로 웹 서비스를 운영하다보면 성능, 보안 등에 대한 이슈가 생길 수도 있습니다.
따라서 이런 문제에 대응하기위해 Apache, Nginx 등 전문적인 웹 서버 소프트웨어를 사용해 서버를 실행해야 합니다.
웹 서버 소프트웨어를 사용하면 사용자가 요청했을때 더 빠르게 응답할 수 있고, 많은 사용자가 동시에 접속했을때 여러 대의 서버로 분산하는 로드 밸런싱같은 기능도 제공할 수 있습니다.
요즘 가볍고 빠르게 처리할 수 있다는 Nginx를 더 많이 사용하는데요, 저도 이것을 사용하겠습니다. 그 전에 웹 서버 소프트웨어와 장고를 연결하기 위해 필요한 WSGI(Web Server Gateway Interface) 중 하나인 Gunicorn을 사용하겠습니다.
Nginx를 사용하는 이유?
Nginx는 클라이언트 요청을 처리하는 방식과 정적 컨텐츠 제공에서 Apache보다 효율적입니다.
WSGI를 사용하는 이유?
WSGI은 Python 웹 애플리케이션과 웹 서버 간의 표준화된 통신 규약입니다.
Python 내장 서버는 기본적인 웹 서버 기능만 제공하기 때문에 WSGI를 사용함으로써 외부 웹 서버를 이용할 수 있습니다.
Gunicorn을 사용하는 이유?
Gunicorn은 Python WSGI HTTP 서버로서, 성능과 안정성이 뛰어납니다.
멀티스레드를 구현하여 요청을 효율적으로 처리할 수 있습니다.
컨테이너 설정 바꾸기
Gunicorn을 사용해서 서버를 실행하기 위해 docker-compose.yml
파일의 web
컨테이너 설정 부분을 다음과 같이 설정합니다.
version : '3'
service:
web:
build: .
# command: python manage.py runserver 0.0.0.0:8000 (삭제)
command: gunicorn config.wsgi:application --bind 0.0.0.0:8000
# 생략..
gunicorn 설치 후, 이미지 빌드와 컨테이너 실행하기
그리고 pip로 Gunicorn을 설치한 다음 requirements.txt
를 업데이트하여 도커 이미지에 반영하겠습니다. 그 다음에 docker-compose up --build
라고 입력해 이미지 빌드와 컨테이너 실행을 한 번에 명령합니다.
> (venv) $ pip install gunicorn
> (venv) $ pip freeze > requirements.txt
> (venv) $ docker-compose up --build
정적 파일은 css나 이미지같은 파일입니다. 이 파일들은 서버에서 제공하지 못하고 있기 때문에 화면이 의도대로 안나올 수 있습니다.
이 문제는 서버를 실행할 때 Gunicorn을 사용하면서 더 이상 앱 폴더의 static 폴더에서 직접 파일을 연결하지 않았기 때문입니다.
settings.py
를 열고 STATIC_ROOT
의 경로를 다음과 같이 지정합니다.
STATIC_URL = "static/"
# STATICFILES_DIRS = [
# BASE_DIR / "static",
# ]
# 추가
STATIC_ROOT = BASE_DIR / "static"
STATIC_URL이 프로젝트의 urls.py
에서도 처리되도록 다음과 같이 코드를 추가해줍니다.
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("main.urls")),
path("restaurants/", include("restaurants.urls")),
path("accounts/", include("accounts.urls")),
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# 추가
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
컨테이너가 실행되고 있으면 터미널에서 ctrl + c
를 눌러 종료하고, python manage.py collectstatic
을 입력합니다.
static 폴더를 따로 생성한 경우 아래와 같이 덮어쓰기가 된다고 경고창이 뜨는데,
yes
라고 입력하여 admin 페이지도 정상적으로 볼 수 있도록 새줍니다.
실제 웹 사이트 운영시 보안을 위해 몇 가지 설정이 달라져야합니다.
방문자에게 친절에게 어디서 오류가 났는지 알려준다면 웹 사이트의 소스 코드 일부 및 취약점이 공개되어 버립니다. 그러면 나쁜 의도를 가진 사람들에게 사이트를 공격 당할 수 있습니다.
따라서 개발용 환경과 실제 배포용 환경을 다르게 해야합니다.
개발용/배포용으로 docker-compose
파일 분리하기
지금까지 작업했던 docker-compose.yml
파일을 복사해서 파일명을 docker-compose.dev.yml
으로 바꿔줍니다. 기존 파일은 배포용으로 사용하겠습니다.
개발용인 docker-compose.dev.yml
파일로 도커 컨테이너를 실행시키려면 다음과 같이 -f {활용할 docker-compose 파일}
옵션을 추가하면 됩니다.
> (venv) $ docker-compose -f docker-compose.dev.yml up
웹 브라우저에서 잘 돌아가는 것을 확인했으면 ctrl+c
을 눌러 컨테이너를 중단합니다.
docker-compose.dev.yml
파일 수정하기
services:
web:
build: .
command: gunicorn config.wsgi:application --bind 0.0.0.0:8000
volumes:
- ./:/usr/src/app/
ports:
- 8000:8000
# env_file:
# - ./.env.dev
env_file:
- ./.env.prod
depends_on:
- db
db:
image: postgres:12.0-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
# environment:
# - POSTGRES_USER=eatwell_db_user
# - POSTGRES_PASSWORD=eatwell_db_pwd
# - POSTGRES_DB=eatwell_dev
env_file:
- ./.env.prod.db
.env.dev
대신 .env.prod
을 사용하는 것으로 수정합니다..env.prod.db
파일에서 따로 관리합니다..env.prod
파일 만들기
.env.dev
파일을 복사해서 .env.prod
파일을 만들고 다음처럼 수정합니다.
SECRET_KEY="****장고시크릿키****"
DJANGO_DEBUG=False
DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
SQL_ENGINE=django.db.backends.postgresql
SQL_DATABASE=eatwell_produced
SQL_USER=eatwell_prod
SQL_PASSWORD=eatwell_db_prod
SQL_HOST=db
SQL_PORT=5432
SECRET_KEY
DJANGO_DEBUG
False
로 설정합니다.SQL_
SQL_DATABASE
는 eatwell_produced
로 바꿨습니다.SQL_USER
와 SQL_PASSWORD
는 실제로 웹사이트를 운영할 때 사용하기 위한 내용으로 수정합니다.장고 시크릿키 새로 생성하는 법
시크릿키는 장고가 제공하는 get_random_secret_key() 함수를 이용해서 생성합니다.
무작위로 생성된 값을 복사해서 .env.prod 파일에 넣어주세요(venv) $ py manage.py shell >> from django.core.management.utils import get_random_secret_key >> get_random_secret_key() '장고_시크릿_키_생성_됨'
.env.prod.db
파일 생성하기
.env.prod.db
를 생성하고 docker-compose.yml
에서 삭제한 내용을 붙여넣습니다. 그리고 POSTGRES_DB
값은 eatwell_dev
에서 eatwell_prod
로 수정했습니다.
POSTGRES_DB
와 POSTGRES_DB
값은 아까 .env.prod
파일에 정의했던 값으로 수정합니다.
.gitignore
에 추가하기
.env.prod
와 .env.prod.db
파일은 보안상 이슈로 깃으로 관리하면 안되니, .gitignore
에 추가합니다.
도커에 변화된 내용 반영하기
앞서 실행되었던 컨테이너를내리고, 이들 컨테이너와 연결되어 있던 볼륨도 다 삭제하기 위해 -v 옵션을 추가합니다.
> (venv) $ docker-compose down -v
docker-compose up -d
로 백그라운드에서 도커 이미지를 빌드하고 실행합니다.
웹 브라우저에서 접속을 해보면, Server Error(500)이라고만 나옵니다.
DEBUG=False로 설정했기 때문에 더 이상 오류를 볼 수 없습니다.
왜 이런 문제가 발생했는지 확인하려면 docker-compse logs
를 입력하면 됩니다.
잘 살펴보면 마이그레이션이 되어 있지 않아서 발생한 문제였습니다.
ERROR: relation "auth_user" does not exist at character 280
마이그레이션을 하고, 새로운 슈퍼유저 계정도 만들어 줍니다.
현재 상태에서 웹 사이트를 열어보면 정적파일이 제대로 적용되어 있지 않을 겁니다.
Nginx를 통해 이 문제를 해결하겠습니다.
docker-compose.yml
수정
Nginx를 적용하기 위한 새로운 컨테이너를 준비합니다. docker-compose.yml
을 다음과 같이 수정합니다.
version: '3'
services:
# nignx 추가
nginx:
build: ./nginx
volumes:
- static_volume:/usr/src/app/static
- media_volume:/usr/src/app/media
ports:
- 80:80
depends_on:
- web
web:
build: .
command: gunicorn config.wsgi:application --bind 0.0.0.0:8000
volumes:
# volume 추가
- static_volume:/usr/src/app/static
- media_volume:/usr/src/app/media
- ./:/usr/src/app/
# ports:
# - 8000:8000
# expose 추가
expose:
- 8000
env_file:
- ./.env.prod
depends_on:
- db
db:
image: postgres:12.0-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
env_file:
- ./.env.prod.db
volumes:
postgres_data:
# volume 추가
static_volume:
media_volume:
services
에 nginx
라는 이름의 컨테이너를 추가합니다.build: ./nginx
nginx
컨테이너를 만들기 위한 파일을 넣을 nginx
폴더를 만듭니다.volume
기능을 이용해 저장하여 컨테이너를 다시 실행했을 때 바로 불러 올 수 있게 합니다.volume
에 저장했던 정적 파일을 바로 보내줍니다. volume
이름은 static_volume
과 media_volume
으로 지정했습니다.ports
depends_on
expose
nginx 폴더 생성하기
nginx 폴더를 만들고, 그 안에 Dockerfile을 새로 만듭니다. 도커에서 제공하는 Nginx 이미지를 사용합니다.
# nginx/Dockerfile
FROM nginx:latest
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d
FROM nginx:latest
rm
명령어로 도커가 제공하는 Nginx의 기본 설정값이 들어 있는 deault.conf
파일 제거/static
이나 /media
경로로 접근하는 경우에 파일을 제공할 수 있도록 static 파일과 media 파일이 있는 위치를 작성합니다.upstream eatwell {
server web:8000;
}
server {
listen 80;
location / {
proxy_pass http://eatwell;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
location /static {
alias /usr/src/app/static/;
}
location /media {
alias /usr/src/app/media/;
}
}
터미널에서 docker-compose down
으로 현재 실행 중인 컨테이너를 종료하고 docker-compose up
으로 다시 실행합니다. 그리고 collectstatic으로 static 파일을 정리해줍니다.
> (venv) $ docker-compose down
> (venv) $ docker-compose up
> (venv) $ docker-compose exec web python manage.py collectstatic
웹 브라우저에서 127.0.0.1
을 입력하면 정상적으로 페이지가 나오는 것을 확인할 수 있습니다.
ngnix를 잘 설정했지만, 접속이 안되는 문제가 발생했습니다.
docker desktop에서 확인하여 왜 실행이 안되는지에 대해 살펴보겠습니다.
nginx에서 발생한 오류들인데, 패키지들의 버전이 낮아서 오류가 난 경우입니다.
하나하나 살펴보면 왜 이런 오류가 발생했는지에 대해 상세히 기술되어 있습니다.
장고 버전 문제
3.2.23 이전의 Django 3.2, 4.1.13 이전의 Django 4.1, 4.2.7 이전의 Django 4.2가 보안에 취약하다는 문제로, CVSS 점수가 7.5인 높은 점수를 기록했습니다.
이외에도 다른 패키지의 CVSS 점수가 높아서 pip 패키지를 업그레이드해야 할 필요성이 생겼습니다. 그래서 pip-upgrade
를 통해 패키지들을 업그레이드 해줬습니다.
> (venv) $ pip install pip-upgrader
> (venv) $ pip-upgrade
업그레이드를 했으니까 다시 pip를 설치해야겠죠?!
기존에 있는 nginx 도커 이미지를 docker rmi [이미지 id]
로 삭제해서,
(docker images
로 이미지 id를 확인할 수 있습니다.)
docker-compose up
를 하여 도커 이미지를 다시 생성했습니다.
아래와 같이 정상적으로 실행되는 것을 docker desktop에서도 확인할 수 있습니다.