[0722] Django-postgreSQL-Gunicorn-Docker

nikevapormax·2022년 7월 22일
0

TIL

목록 보기
81/116

Environment Variable

  • 어제 작성했던 docker-compose 파일을 보면 나의 소중한 postgreSQL 아이디와 비밀번호가 적나라하게 적혀있다.
  • 이것을 환경 변수로 변경시켜 활용해보자.
  • 먼저 .env.postgres 파일을 생성한다. 그리고 아래와 같이 docker-compose 파일에 있는 내용을 복사 붙여넣기 해준다.
    • docker-compose에 있는 내용은 없애준다.
# .env 파일에 있는 부분과 동일한 내용이다. django와 postgres는 이 부분을 통해 통신한다. 
POSTGRES_DB="turtle_drf_backend_db"
POSTGRES_USER="turtle_drf_backend_nikevapormax"
POSTGRES_PASSWORD="turtle_drf_backend_password"
  • 그리고 해당 파일이 깃으로 올라가지 않도록 .gitignore에 파일 이름을 넣어주도록 하자.
  • 그리고 docker-compose 파일을 아래와 같이 변경하면 된다.
version: "3.8"

services:
  web:
    # 현재 폴더에 있는 것들을 사용해 빌드하겠다.
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    ports:
      - 8000:8000
    working_dir: /usr/src/app/
    volumes:
      - ./:/usr/src/app/
    env_file:
      - ./.env
    depends_on:
      - db
  db:
    image: postgres:14.4-alpine
    volumes:
      - postgres_db:/var/lib/postgresql/data # named-volume
    env_file:
      - ./.env.postgres
    ports:
      - "5432:5432"

# named-volume을 사용하기 때문에 아래에서 선언해주어야 함
volumes:
  postgres_db:
  • 만약 env 파일을 만들고 혹시 푸시를 했다면 혹은 env.postgres 파일을 만들면서 뭔가 값이 변경되었다면, 이미 노출이 된 아이디와 비밀번호를 바꿔야 한다.
  • 그래서 .env파일과 .env.postgres 파일에서 아이디와 비밀번호를 모두 바꾸었다고 가정하자. 이러고 docker-compose를 빌드하고 실행하려하면 기존에 있던 값들과 충돌해 에러가 나게 된다.
  • 이유는 우리가 named-volume을 만들 때 기존의 정보로 만들었기 때문이다.
  • 이로 인해 로그인 정보만을 바꾼다고 에러가 해결되지 않는다. 에러를 방지하기 위해 기존의 값으로 돌렸던 container를 삭제해주어야 한다. 그리고 기존의 volume도 없애주어야 한다.

gunicorn 사용

  • docker-compose.yaml 파일을 한 번 살펴보자.
services:
  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
  • 보면 나는 현재 개발용 서버에 연결을 해놓은 것을 볼 수 있다. 이는 개발에 사용하기엔 용이하지만, 서비스를 배포하고 나서 대량의 트래픽을 처리하는 등의 일에는 맞지 않다.
  • 따라서 nginx 서버를 사용하게 되는데, python과 nginx를 연결해주는 것이 바로 gunicorn이다. (Web Server Gateway Interface)
  • docker로 gunicorn을 사용하기 위해 나는 기존에 사용하던 docker-compose.yaml이 아닌 배포용 docker-compose 파일을 만들 것이다.
  • 파일명은 docker-compose.prod.yaml로 하고 기존에 사용하던 것을 복사 붙여넣기 했다.
version: "3.8"

services:
  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    ports:
      - 8000:8000
    working_dir: /usr/src/app/
    volumes:
      - ./:/usr/src/app/
    env_file:
      - ./.env
    depends_on:
      - db
  db:
    image: postgres:14.4-alpine
    volumes:
      - postgres_db:/var/lib/postgresql/data # named-volume
    env_file:
      - ./.env.postgres
    ports:
      - "5432:5432"

volumes:
  postgres_db:
  • 그리고 gunicorn을 설치해주었다. 그리고 반드시 requirements.txt에 넣어주도록 하자.
$ pip install gunicorn
$ pip freeze > requirements.txt
  • 위에서 복사해온 docker-compose.prod.yamlcommand: python manage.py runserver 0.0.0.0:8000를 다음과 같이 변경해주도록 하자.
command: gunicorn turtle_drf_backend.wsgi:application --bind 0.0.0.0:8000
  • 위에 있는 wsgi는 우리 프로젝트 폴더 안에 있는 wsgi.py이다.
  • 변경된 파일로 docker-compose를 빌드하도록 하자.
    • 사용하는 파일이 변경되었으므로 -f를 붙여주고 바로 뒤에 변경된 파일의 이름을 작성해주면 된다.
$ docker-compose -f docker-compose.prod.yaml build 

  • 그리고 up을 진행해보자.
$ docker-compose -f docker-compose.prod.yaml up 
  • psycopg2가 실행되지 않는다는 에러가 또 났다. 그래서 requirements.txt를 확인했더니 없어 추가해 주었고, 다시 위의 과정을 진행했고 실행이 잘 되는 것을 볼 수 있었다.
  • 이렇게 하고 브라우저로 가서 localhost:8000을 입력해 실행이 잘 되는지 보고, admin 페이지로 들어가보자.
    • css가 적용되지 않고, 아래와 같은 에러가 뜬 것을 볼 수 있다.

  • terminal 창에서도 css를 찾을 수 없다 등의 에러가 난 것을 볼 수 있다.
  • 해당 문제는 gunicorn을 실행하면서 app 폴더의 static 파일을 직접 연결하지 못하게 되면서 일어나게 되는 것이다. 따라서 이에 대한 설정을 진행하도록 하겠다.
  • settings.py로 이동해 아래와 같이 static 설정을 해주도록 하자.
STATIC_ROOT = BASE_DIR / 'static'
STATIC_URL = '/static/'
  • 그리고 전체 urls.py 폴더를 아래와 같이 변경해준다.

    from xml.etree.ElementInclude import include
     from django.contrib import admin
     from django.urls import path, include
    
     from django.conf import settings
     from django.conf.urls.static import static
    
     urlpatterns = [
         path('admin/', admin.site.urls),
         path('user/', include('user.urls')),
         path('articles/', include('articles.urls')),
     ] 
    
     urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
     urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
  • 그리고 다시 빌드를 진행하고 서버를 돌리도록 하자.

$ docker-compose -f docker-compose.prod.yaml up --build
  • 하지만 나는 되지 않았다. 이유는 static 파일이 흩어져 있기 때문이다. 그러므로 이를 모아주도록 하겠다.
  • 먼저 docker desktop으로 가 빨간 박스를 눌러 terminal 창을 열어준다.
  • 그리고 terminal 창이 열리면 아래와 같이 진행해 static 파일을 모아줄 수 있다.
$ python manage.py collectstatic

  • 이렇게 하면 css가 적용된 화면을 볼 수 있다.

production을 위한 env와 settings 설정

  • 로컬에서 사용하는 .env와 배포에 사용하는 .env를 구분지어 생성해보도록 하겠다.
  • 먼저 원래 있던 .env에서 내용을 복사해와 새로 만든 .env.prod에 붙여넣기 해준다.
  • 그리고 새로운 SECRET_KEY를 발급받도록 한다.
$ python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
  • .env.prod
SECRET_KEY = 'bdfy7i5#vxn39k$nk)u+4__v2fjd^@u^=km41sy&z*+0x+km$s'

SQL_ENGINE='django.db.backends.postgresql'
SQL_DATABASE='turtle_drf_backend_db'
SQL_USER='turtle_drf_backend_nikevapormax'
SQL_PASSWORD='turtle_drf_backend_password'
SQL_HOST='db'
SQL_PORT='5432'
  • db 또한 배포를 위한 파일을 하나 생성해준다.
  • .env.postgres.prod
    • 아이디와 비밀번호를 원하는 것으로 원래 있던 파일과 다르게 생성해준다.
POSTGRES_DB="turtle_drf_backend_db"
POSTGRES_USER="turtle_drf_backend_nikevapormax1"
POSTGRES_PASSWORD="turtle_drf_backend_password1"
  • 주의할 점은 방금 .env.postgres.prod의 USER와 PASSWORD를 바꾸었기 때문에 .env.prod의 USER와 PASSWORD 또한 바꿔주어야 하는 것이다.
SECRET_KEY = 'bdfy7i5#vxn39k$nk)u+4__v2fjd^@u^=km41sy&z*+0x+km$s'

SQL_ENGINE='django.db.backends.postgresql'
SQL_DATABASE='turtle_drf_backend_db'
SQL_USER='turtle_drf_backend_nikevapormax1'
SQL_PASSWORD='turtle_drf_backend_password1'
SQL_HOST='db'
SQL_PORT='5432'
  • 배포와 개발용을 구분하기 위해 파일 이름을 아래와 같이 변경해주자.
    • .env -> .env.dev
    • .env.postgres -> .env.postgres.dev
  • manage.py에서 env 파일을 읽어오는 부분을 삭제해준다.
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
    """Run administrative tasks."""
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'turtle_drf.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__":
    main()
  • 개발 및 배포는 docker 환경에서 진행할 예정이지만, runserver를 그대로 하고 싶다면 아래와 같이 settings.py의 설정을 변경해주어야 한다.
    • DATABASES는 예전에 설정한 값이지만, 다시 한 번 작성하는 이유는 이곳에도 default 값이 적용되어 있고 이는 runserver를 실행할 때는 sqlite3를 사용할 수 있도록 하는 코드이다.
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get("SECRET_KEY", "somesecret")

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = int(os.environ.get("DEBUG", 1))

...

DATABASES = {
    'default': {
        'ENGINE': os.environ.get('SQL_ENGINE',"django.db.backends.sqlite3"),
        'NAME': os.environ.get('SQL_DATABASE', BASE_DIR / "db.sqlite3"),
        'USER': os.environ.get('SQL_USER', 'user'),
        'PASSWORD': os.environ.get('SQL_PASSWORD', 'password'),
        'HOST': os.environ.get('SQL_HOST', 'localhost'),
        'PORT': os.environ.get('SQL_PORT', '5432'),
    }
}
  • .env.dev.env.prod에도 아래와 같이 DEBUG를 추가해준다.
  • .env.dev
DEBUG = '1'
SECRET_KEY = 'django-insecure-5ofxcce2mfgazkn2w(o16j2i_rj6w^7%)xeilm$8go4qezt^ng'

SQL_ENGINE='django.db.backends.postgresql'
SQL_DATABASE='turtle_drf_backend_db'
SQL_USER='turtle_drf_backend_nikevapormax'
SQL_PASSWORD='turtle_drf_backend_password'
SQL_HOST='db'
SQL_PORT='5432'
  • .env.prod
DEBUG = '0'
SECRET_KEY = 'bdfy7i5#vxn39k$nk)u+4__v2fjd^@u^=km41sy&z*+0x+km$s'

SQL_ENGINE='django.db.backends.postgresql'
SQL_DATABASE='turtle_drf_backend_db'
SQL_USER='turtle_drf_backend_nikevapormax1'
SQL_PASSWORD='turtle_drf_backend_password1'
SQL_HOST='db'
SQL_PORT='5432'
  • 위의 설정이 의미하는 것은 아래와 같다.
    • 우리는 settings.py에서 DEBUG를 설정했다.
    • 현재 .env.dev.env.prod에서 DEBUG를 문자열 1 또는 0으로 설정했는데
      • 개발 환경에서는 DEBUG가 int('1')이 되어 디버깅이 활성화되고
      • 배포에서는 DEBUG가 int('0')이 되어 디버깅이 실행되지 않는다.
  • gitignore에도 아래와 같이 수정 또는 추가하도록 하자.
.env.dev
.env.prod
.env.postgres.dev
.env.postgres.prod
  • docker-compose.prod.yaml 또한 아래와 같이 현재 바뀐 파일 명으로 변경해준다.
version: "3.8"

services:
  web:
    # 현재 폴더에 있는 것들을 사용해 빌드하겠다.
    build: .
    command: gunicorn turtle_drf.wsgi:application --bind 0.0.0.0:8000
    ports:
      - 8000:8000
    working_dir: /usr/src/app/
    volumes:
      - ./:/usr/src/app/
    env_file:
      - ./.env.prod
    depends_on:
      - db
  db:
    image: postgres:14.4-alpine
    volumes:
      - postgres_db:/var/lib/postgresql/data # named-volume
    env_file:
      - ./.env.postgres.prod
    ports:
      - "5432:5432"

# named-volume을 사용하기 때문에 아래에서 선언해주어야 함
volumes:
  postgres_db:
  • 그리고 현재 up 되어있는 docker-compose를 down 해주고, 만약 volume이나 image가 있다면 삭제해준다.
$ docker-compose down

nginx 서비스 작성

  • docker-compose.prod.yaml로 와 nginx 서비스를 작성해보도록 하겠다.
version: "3.8"

services:
  nginx:
    build: ./nginx
    volumes:
      - static_volume:/usr/src/app/static
      - media_volume:/usr/src/app/image
    ports:
      - 80:80
    depends_on:
      - web
  web:
    # 현재 폴더에 있는 것들을 사용해 빌드하겠다.
    build: .
    command: gunicorn turtle_drf.wsgi:application --bind 0.0.0.0:8000
    ports:
      - 8000:8000
    working_dir: /usr/src/app/
    volumes:
      - ./:/usr/src/app/
      - static_volume:/usr/src/app/static
      - media_volume:/usr/src/app/image
    expose:
      - 8000
    env_file:
      - ./.env.prod
    depends_on:
      - db
  db:
    image: postgres:14.4-alpine
    volumes:
      - postgres_db:/var/lib/postgresql/data # named-volume
    env_file:
      - ./.env.postgres.prod
    ports:
      - "5432:5432"

# named-volume을 사용하기 때문에 아래에서 선언해주어야 함
volumes:
  postgres_db:
  static_volume:
  media_volume:
  • 이제 nginx 설정 파일들을 만들도록 하자.
  • nginx/ 폴더 안에 아래의 파일들을 생성한다.
  • nginx.conf
 upstream turtle_drf_backend {
    server web:8000;  # docker-compose.prod.yaml의 web에 설정한 8000과 연결되게 된다.
}                      # nginx와 gunicorn이 통신할 때 여기로 함 (내부 통신)

server {
    listen 80;  # web의 80번 포트이자 default http의 포트를 그대로 사용한다. (외부 통신)
    location / {
        # nginx에서 제공하는 default setting
        proxy_pass http://turtle_drf_backend;
        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 /image/ {
        alias /usr/src/app/image/;
    }
}
  • Dockerfile
FROM nginx:1.22.0-alpine
# 실행이 되면 nginx의 기본 설정파일들을 없앤다.
RUN rm /etc/nginx/conf.d/default.conf 
# 없앤 기본 설정파일들 대신 내가 만든 nginx.conf를 복사해 넣어준다.
COPY nginx.conf /etc/nginx/conf.d
  • docker desktop으로 가 기존에 있던 모든 container, image, volime을 모두 정리해준다.

서버

  • 다음 코드를 실행하면, 아래와 같이 서버가 돌아가는 것을 볼 수 있다.
$ docker-compose -f docker-compose.prod.yaml up --build

  • 브라우저에서 localhost로 들어가려 하면 아래와 같이 에러가 나는 것을 볼 수 있다.

    • 이유는 내가 DEBUG 모드를 끝냈기 때문이다.

    • 해당 문제를 해결하기 위해서는 settings.py를 수정해야 한다.

      # SECURITY WARNING: don't run with debug turned on in production!
      DEBUG = int(os.environ.get("DEBUG", 1))
      
      if os.environ.get("DJANGO_ALLOWED_HOSTS"):
         ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(" ")
      else:
         ALLOWED_HOSTS = []
    • .env.prod를 아래와 같이 변경해준다.

      DEBUG = '0'
      SECRET_KEY = 'bdfy7i5#vxn39k$nk)u+4__v2fjd^@u^=km41sy&z*+0x+km$s'
      DJANGO_ALLOWED_HOSTS = localhost 127.0.0.1
      
      SQL_ENGINE='django.db.backends.postgresql'
      SQL_DATABASE='turtle_drf_backend_db'
      SQL_USER='turtle_drf_backend_nikevapormax1'
      SQL_PASSWORD='turtle_drf_backend_password1'
      SQL_HOST='db'
      SQL_PORT='5432'
    • 변경된 코드들의 의미를 살펴보자.

      • DJANGO_ALLOWED_HOSTS = localhost 127.0.0.1를 보면 localhost와 127.0.0.1은 띄어쓰기를 기준으로 나뉘어져 있다.
      • ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(" ")에서는 띄어쓰기를 통해 DJANGO_ALLOWED_HOSTS에 값들을 넣어준다. 따라서 우리는 위의 두 코드를 통해 두 가지의 호스트를 얻을 수 있다.
        • localhost
        • 127.0.0.1
  • .env.dev에는 현재 DEBUG가 1이라 의미는 없지만 같이 넣어주도록 하겠다.

DEBUG = '1'
SECRET_KEY = 'django-insecure-5ofxcce2mfgazkn2w(o16j2i_rj6w^7%)xeilm$8go4qezt^ng'
# 현재 DEBUG가 1이라 쓰이지는 않지만 넣어둠
DJANGO_ALLOWED_HOSTS = localhost 127.0.0.1 

SQL_ENGINE='django.db.backends.postgresql'
SQL_DATABASE='turtle_drf_backend_db'
SQL_USER='turtle_drf_backend_nikevapormax'
SQL_PASSWORD='turtle_drf_backend_password'
SQL_HOST='db'
SQL_PORT='5432'
  • 현재 돌아가고 있는 것들을 멈춘 후 down 시켜주자.
$ docker-compose -f docker-compose.prod.yaml down

  • docker desktop에 있는 volume과 image도 삭제해주자.
  • 그리고 다시 build와 up을 해주자.
$ docker-compose -f docker-compose.prod.yaml up --build

  • 브라우저를 통해 다시 localhost에 들어가면 에러가 나는데, 아까와는 다르게 현재는 보여줄 데이터가 없기 때문에 나는 에러이다.

  • 이제 migration을 진행해주도록 하자.

  • 하지만 아래와 같이 에러가 발생하며 css를 불러오지 못했다.

  • 이유는 명확하지 않지만, 원인은 localhost:8000이었다. localhost만 사용하면 아래와 같이 css가 잘 적용되는 것을 볼 수 있다.

    • 원인을 찾는 도중 docker desktop의 web-1 터미널에서 createsuperuser를 진행하였다.
    • 추측으로는 8000번 포트를 붙이면 gunicorn으로 가게 되고, 현재 gunicorn에는 css를 붙이는 것이 없기 때문에 위의 에러가 난 것일 것이다.

nginx log 보기

  • 아래의 container 중 nginx-1은 nginx에 대한 로그를 자동으로 docker logs로 보내주고 있다. 해당 로그를 보는 법에 대해 작성하도록 하겠다.
$ docker logs turtle_drf_backend-nginx-1
$ docker logs turtle_drf_backend-nginx-1 -f

>>> -f를 붙여 로그를 계속 확인할 수 있다.
  • docker logs nginx-1은 보이는 것과 같이 정확한 이름이 아니다.
  • 정확한 이름을 알고싶다면 어떻게 해야 할까?
    - docker ps로 container를 확인하고 name을 가져와 입력하면 된다.
    - 현재 나의 nginx의 이름은 turtle_drf_backend-nginx-1이다.
  • 실제로 브라우저에서 admin 페이지 안에서 이동하며 로그가 찍히는 것을 확인해보자.
    • 현재 가장 최신 로그이다.
    • 브라우저에서 페이지를 이동하게 되면 아래와 같이 로그가 쌓이는 것을 볼 수 있다.
profile
https://github.com/nikevapormax

0개의 댓글