[Docker] 장고에 도커 적용하기 (2) - 웹서버 환경 구축

itisny·2024년 3월 26일
0
post-thumbnail
post-custom-banner

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 적용하기

Gunicorn을 사용한 위해 도커 이미지 수정하기

  1. 컨테이너 설정 바꾸기
    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
        
     # 생략..
  2. 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파일을 개발용/배포용으로 분리하기

  1. 개발용/배포용으로 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을 눌러 컨테이너를 중단합니다.

  2. 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을 사용하는 것으로 수정합니다.
  • PostgreSQL 관련 내용은 .env.prod.db파일에서 따로 관리합니다.

환경설정 파일 분리하기

  1. .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_DATABASEeatwell_produced로 바꿨습니다.
      SQL_USERSQL_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() 
    '장고_시크릿_키_생성_됨'
  2. .env.prod.db파일 생성하기
    .env.prod.db를 생성하고 docker-compose.yml에서 삭제한 내용을 붙여넣습니다. 그리고 POSTGRES_DB 값은 eatwell_dev에서 eatwell_prod로 수정했습니다.
    POSTGRES_DBPOSTGRES_DB값은 아까 .env.prod파일에 정의했던 값으로 수정합니다.

  3. .gitignore에 추가하기
    .env.prod.env.prod.db파일은 보안상 이슈로 깃으로 관리하면 안되니, .gitignore에 추가합니다.

  4. 도커에 변화된 내용 반영하기
    앞서 실행되었던 컨테이너를내리고, 이들 컨테이너와 연결되어 있던 볼륨도 다 삭제하기 위해 -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 적용하기

현재 상태에서 웹 사이트를 열어보면 정적파일이 제대로 적용되어 있지 않을 겁니다.
Nginx를 통해 이 문제를 해결하겠습니다.

컨테이너에 Nginx 설정 추가하기

  1. 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:
    • servicesnginx라는 이름의 컨테이너를 추가합니다.
    • build: ./nginx
      nginx 컨테이너를 만들기 위한 파일을 넣을 nginx 폴더를 만듭니다.
    • 컨테이너가 종료되면 컨테이너 안에 저장되어 있던 내용도 함께 사라지는데, Nginx의 volume 기능을 이용해 저장하여 컨테이너를 다시 실행했을 때 바로 불러 올 수 있게 합니다.
      예를 들어 웹사이트 방문자가 정적 파일을 요청하면 장고에서 요청하지 않고, volume에 저장했던 정적 파일을 바로 보내줍니다. volume이름은 static_volumemedia_volume으로 지정했습니다.
    • ports
      도메인이나 IP주소 뒤에 아무것도 쓰지않으면 80이 기본입니다. Nginx를 도입해서 이제부터는 80포트를 사용합니다.
    • depends_on
      이번에 만든 nginx는 web이라는 컨테이너에 의존성을 가집니다.
    • expose
      장고에서 처리한 결과는 8000번 포트로 expose하도록 변경합니다. 8000번 포트로 web 컨테이너와 nginx 컨테이너가 정보를 주고받도록 설정하는 내용은 여기에서 다루지 않아 이는 나중에 더 자세히 알아보겠습니다.
  2. 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
    도커가 제공하는 Nginx 이미지 중 최신 버전 사용
  • rm 명령어로 도커가 제공하는 Nginx의 기본 설정값이 들어 있는 deault.conf 파일 제거
  • 대신 로컬에 새로 만든 Nginx 설정 정보(conf.d)를 Nginx 이미지에 복사
  1. Nginx 설정 파일 만들기
    Nginx 설정 정보를 담을 nginx.conf 파일을 nginx 폴더에 생성해줍니다.
    장고 쪽에서 proxy_pass를 통해 nginx로 오는 내용은 8000번 포트로 받고, 서버 외부에서 오는 요청은 80으로 받도록 설정합니다.
    Nginx에 /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/;
    }
}

컨테이너 다시 시작해서 Nginx 설정 반영하기

터미널에서 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 실행이 되지않는 문제 해결하기

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에서도 확인할 수 있습니다.

post-custom-banner

0개의 댓글