반응형

Django 프로젝트를 배포하는 방법은 서비스 규모나 환경에 따라서 여러가지가 있겠지만..

여기서는 단일 서버에 docker를 이용하여 서비스를 운영한다고 가정을 하고 배포하는 상황을 준비하였습니다.

 

 

서비스 구성

기본적으로 source code는 git을 통해서 관리가 되고 있고, 서버에 docker는 이미 설치되어 있다고 가정하겠습니다. 실제 상황에서는 CI/CD 툴을 이용해서 관리하겠지만, 여기서는 Django 프로젝트를 배포하는 것에만 포커스 하도록 하겠습니다.

 

대략적인 서비스 구성은 아래 그림과 같습니다.

 

실제 서비스라면 database도 준비가 되어야겠지만.. 여기서는 docker에서 nginx와 python만 container를 띄울 예정입니다. 여기서 storage는 static 파일들이 들어갈 위치인데요, Django의 경우 운영환경에서는 django app은 wsgi (혹은 asgi) 형태로 서비스를 제공하며, static 파일들은 웹서버가 직접 처리해 주는 형태로 서비스를 하기 때문에, storage를 별도로 지정해 두었습니다.

외부에서 해당 서버에 요청이 오면, 먼저 nginx가 요청을 받아서 처리를 하게 되는데, static 파일 등은 nginx 가 알아서 처리해주고, 그 외에 django app이 처리해야할 부분만 reverse proxy 형태로 전달해 주게 됩니다.

 

 

Django 서비스 배포 절차

Django 서비스를 배포할 때는 다음과 같은 과정들이 필요합니다.

1. python 가상환경 준비 (처음 한번만 필요)

2. 소스 코드 업데이트

3. 의존 모듈 설치

4. static 파일 collect

5. model migration

6. django app service launch

 

다음에는 각각의 작업을 command로 살펴보겠습니다.

 

 

 

Django 서비스 배포 - 디렉터리 설정

위에서 언급하진 않았지만, 여기서는 아래와 같이 디렉터리를 설정해 보았습니다.

 

기본적으로 /opt/docker 폴더에서 각각의 서비스 컨테이너에 대한 폴더를 생성합니다. (nginx, python)

각 서비스 컨테이너 별로 루트에 docker-compose.yaml 파일들을 위치시키게 됩니다.

nginx 에 대해서는 설정파일들은 conf 폴더에 넣을 예정이고, static 파일들은 static 폴더에 넣어 두면 됩니다.

python 도 역시 설정파일들을 conf 폴더에 넣고, 그 외에 필요한 script들은 scripts 폴더에 보관합니다.

그 외에 python 에는 app 폴더를 생성할 예정인데요, app 폴더는 git clone 후에 rename 해서 생성할 예정입니다. 

 

 

Django 서비스 배포 - 가상환경 준비

가상환경은 application 별로 독립된 python 환경을 구성하기 위해서 준비하는 과정인데, docker image를 빌드하는 경우에는 따로 독립된 환경을 사용하지 않아도 되지만, 여기서는 범용 python 이미지를 사용할 예정이기 때문에, local storage에 가상 환경을 생성해서 사용할 예정입니다. command는 다음과 같은데, 가상환경은 python docker container 안에서 실행할 예정입니다.

 

> python -m venv .venv

 

 

Django 서비스 배포 - 소스 코드 업데이트

소스코드 업데이트는 git pull (혹은 clone) 명령어로 처리가 가능합니다. 물론 먼저 서버에 git이 설치가 되어 있어야 합니다.

 

처음 소스를 받아올 때는 clone 명령어를 사용합니다.

 

> git clone <repository url>

 

 

추후에 업데이트 하는 경우에는 pull 명령어를 이용하면 됩니다.

 

> git pull

 

 

Django 서비스 배포 - 의존 모듈 설치

python에서 필요로 하는 모듈을 설치합니다. 로컬 개발환경에서는 간단히 pip 명령어로 처리가 되지만.. 여기서는 가상환경을 이용하기 때문에 아래와 같이 명령어를 입력합니다. 역시 python docker container 안에서 실행되어야 합니다.

 

> ./.venv/bin/python -m pip intstall -r requirements.txt

 

 

Django 서비스 배포 - static 파일 collect

소스 코드 repository 에 넣어둔 static 파일들을 모아주는 과정입니다.여기서는 가상환경을 이용하기 때문에 아래와 같이 명령어를 입력합니다. 역시 python docker container 안에서 실행되어야 합니다.

 

> ./.venv/bin/python manage.py collectstatic --no-input

 

 

Django 서비스 배포 - model migration

모델을 migration 합니다. 실제로는 database 테이블을 생성 또는 변경 하는 과정입니다. 역시 python docker container 안에서 실행되어야 합니다.

 

> ./.venv/bin/python manage.py migrate

 

 

Django 서비스 배포 - django app service launch

django app 서비스를 실행합니다. 앞에서 잠시 언급한데로 wsgi 혹은 asgi 를 이용할 예정이기 때문에, 로컬 개발환경에서와는 조금 다른 명령어를 사용합니다. 역시 python docker container 안에서 실행되어야 합니다. 그리고 host를 지정하지 않으면 127.0.0.1 로 지정이 되는데, 컨테이너 외부에서 접속을 하기 위해서는 0.0.0.0 으로 바인드를 해줘야 합니다. port는 nginx에서 매핑할 예정이기 때문에 크게 중요하지 않아서 default 포트인 8000으로 지정해 주었습니다. 

 

> ./.venv/bin/python -m uvicorn <asgi applcation> --host 0.0.0.0 --port 8000 --workers 4

 

 

참고로 uvicorn을 이용하기 위해서는 python module 을 미리 설치해 주어야 합니다. 만약 requirements.txt에 해당 모듈이 들어있지 않다면 별도로 설치를 해 줍니다. (2024년 2월 현재 0.27.1 버전이 최신이네요..)

 

> ./.venv/bin/python -m pip install uvicorn==0.27.1

 

 

실제 예시

이번에는 위에서 설명한 내용을 실제로 서버에 반영해 보겠습니다. 소스코드는 예전에 제가 생성해 두었던 기본 django tutorial 코드를 이용하도록 하겠습니다. https://github.com/shineum/django_tutorial.git

 

먼저 /opt/docker/python 폴더에서 소스 코드를 clone 합니다.

 

> git clone https://github.com/shineum/django_tutorial

 

 

그리고 django_tutorial 폴더를 app 으로 rename 합니다.

 

> mv django_tutorial app

 

 

django app에서 사용할 script 들을 https://github.com/shineum/django_scripts.git 에서 다운로드 합니다. 그리고 script 들을 scripts 폴더에 위치시킵니다.

 

docker 에서 가상 환경을 생성합니다. python 은 3.12.2-slim-bullseye 버전을 사용하였는데, 버전은 상황에 맞게 설정하면 됩니다.

 

> docker run -v ./app:/opt/app -v ./scripts/init.sh:/entrypoint.sh -it --rm python:3.12.2-slim-bullseye sh ./entrypoint.sh

 

 

소스 코드는 바로 clone 해서 준비가 되어 있으니 여기서는 업데이트가 필요 없지만, 코드가 변경된 경우에는 git pull 명령어를 이용해서 반영해 주면 됩니다. 

 

 

바로 의존 모듈을 설치해 보겠습니다. 가상 환경 생성과 비슷한데, script 만 init 에서 install 로 변경해 주면 됩니다.

 

> docker run -v ./app:/opt/app -v ./scripts/install.sh:/entrypoint.sh -it --rm python:3.12.2-slim-bullseye sh ./entrypoint.sh

 

 

다음 단계인 collect static 을 실행합니다. 이번에는 볼륨매핑 옵션에 nginx의 static 파일 위치를 지정해 주어야 합니다.

> docker run -v ./app:/opt/app -v /opt/docker/nginx/static:/opt/app/static -v ./scripts/collectstatic.sh:/entrypoint.sh -it --rm python:3.12.2-slim-bullseye sh ./entrypoint.sh

 

제대로 실행이 되었다면 /opt/docker/nginx/static 폴더에 static 파일들이 업데이트 되어야 합니다.

 

 

모델 업데이트는 script만 db로 변경해 줍니다. database를 사용하는 경우에는 db에 필요한 테이블이 생성이 되는데, 여기서는 db.sqlite3 파일이 생성됩니다.

 

> docker run -v ./app:/opt/app -v ./scripts/db.sh:/entrypoint.sh -it --rm python:3.12.2-slim-bullseye sh ./entrypoint.sh

 

 

django app이 잘 실행되는지 테스트를 해 봅니다. 아래 커맨드를 실행하고, 해당 서버로 접속을 해 봅니다.

> docker run -v ./app:/opt/app -v ./scripts/run_test.sh:/entrypoint.sh -p 8000:8000 -it --rm python:3.12.2-slim-bullseye sh ./entrypoint.sh

 

 

아래 화면은 Ubuntu desktop 에서 실행했을 때의 결과인데, django 404 error 페이지가 나오지만 실행은 잘 되는 것을 알 수 있었습니다. 실제로는 localhost 대신 서버 주소를 입력해야합니다.

 

 

그리고, admin 페이지는 아래와 같이 나옵니다.

 

 

이제 정식으로 django app 을 실행하기 위해서 docker-compose.yaml 파일을 아래와 같이 작성합니다.

 

/opt/docker/python/docker-compose.yaml

version: "3.7"
services:
  python:
    image: python:3.12.2-slim-bullseye
    volumes:
      - ./app:/opt/app
      - ./scripts/run.sh:/entrypoint.sh
    command: sh entrypoint.sh
    ports:
      - "8000:8000"

 

 

그리고 아래 명령어를 이용하여 컨테이너를 실행합니다.

> docker compose up -d

 

 

django app 이 asgi 형태로 실행되고 있어서, static 파일들 (css, image 등) 이 화면에 표현되지 않습니다.

 

테스트는 잘 실행이 되었지만, docker compose 실행이 제대로 되지 않는다면, 아마도 asgi 파일의 위치 문제일 가능성이 큽니다. run.sh 파일을 편집에서 해당 프로젝트에서 asgi 파일이 있는 경로를 지정해 주면 됩니다.

 

static 파일 문제를 해결하기 위해서 nginx를 셋업하도록 하겠습니다.

 

 

nginx 셋업

nginx도 docker로 이용할 예정이기 때문에 별다른 설치는 필요하지 않습니다. 

nginx에서는 static 파일에 대한 요청을 처리하고, 나머지 요청은 django app으로 보내 줍니다. 실제로는 ssl 처리 등의  복잡한 기능이 필요하지만, 여기서는 아래와 같이 간단한 설정 파일을 준비합니다. 여기서 proxy_pass에 사용한 172.17.0.1 은 docker의 기본 host 주소 입니다. 원래는 docker network 을 이용해야 하지만, 간단한 예시이기 때문에, docker 기본 host 아이피를 적어주었습니다.

 

/opt/docker/nginx/conf/app.conf

server {
    listen 80;
    listen [::]:80;
    server_name localhost;

    location /static {
        alias /opt/app/static;
    }

    location / {
        proxy_pass http://172.17.0.1:8000/;
    }
}

 

 

그리고 docker-compose.yaml 파일을 아래와 같이 작성합니다.

 

/opt/docker/nginx/docker-compose.yaml

version: "3.7"
services:
  python:
    image: nginx:1.24.0
    volumes:
      - ./static:/opt/app/static
      - ./conf/app.conf:/etc/nginx/conf.d/app.conf
    ports:
      - "80:80"
      - "443:443"

 

 

아래 명령어를 이용하여 컨테이너를 실행합니다.

> docker compose up -d

 

 

그리고 나서 browser에서 확인해 보면.. 아래와 같은 에러가 나옵니다. 

 

 

에러 내용은.. 허용되지 않는 host 라는 건데요.. 해결하기 위해서는 django app의 소스코드를 수정해야 합니다.

 

/opt/docker/python/app/config/settings.py 파일을 열어서 28번째 줄을 다음과 같이 변경합니다. 

 

실제 운영환경에서는 이런 식의 소스 코드 변경을 막기위해서 이러한 설정 값은 모두 환경변수에서 읽어서 처리하게 해야 합니다.

 

 

그리고 python (django app) 의 docker 를 새로 띄우면, 아래와 같이 잘 나오게 됩니다.

 

 

눈치 빠르신 분은 이미 알아채셨겠지만.. 접속 주소가 localhost:8000 에서 localhost 로 변경이 되었습니다. nginx 가 연결을 해주기 때문에 이제는 localhost 로 접속하면 됩니다. 기존의 8000 포트는 iptables 혹은 firewall 등을 이용해서 외부에서 접근하는 것을 막아 두는 것이 좋습니다.

 

반응형

'프로그래밍 > Python - Django' 카테고리의 다른 글

Django URL patterns  (0) 2024.03.17
Django Query Tips - User, Group, Permission  (0) 2024.03.02
Django - Proxy Models  (0) 2023.02.23
Django Tutorial Part 1 - HelloWorld  (0) 2022.11.01
Django - 프로젝트 시작하기  (0) 2022.10.28
반응형

지난 포스트에서는 https 를 적용하기 위해서, 실제 인증기관을 통해서 서명된 인증서를 사용하는 방법에 대해서 적어 보았습니다.

 

하지만, 테스트 용도의 서버나 개발 서버 등 외부로 노출되지 않는 서버까지 유료 인증서를 사용할 필요는 없습니다. 이런 경우 self signed certificate 을 이용해서 적용할 수 있습니다.

 

1. 이전과 마찬가지로 private key 파일과 인증 요청서 파일을 생성하는 것입니다. 이번에는 nodes 옵션을 추가해서 암호 설정이 되지 않은 key 파일을 생성하겠습니다.

openssl req -newkey rsa:2048 -nodes -keyout domain.key -out domain.csr

 

2. 인증서를 생성합니다.

openssl x509 -signkey domain.key -in CSR.csr -req -days 10000 -out domain.crt

days 옵션에 숫자로 날짜를 입력할 수 있습니다. 해당 기간 동안 인증이 유효한 것인데, 테스트 용은 길게 적어 줘도 상관 없습니다.

 

3. key 파일과 인증서 (crt) 파일을 적당한 경로로 옮겨 둡니다.

 

4. nginx 설정에 다음 내역을 추가합니다.

server {
    listen 443 ssl;
    server_name your_domain.com;
    
    ssl_certificate '/etc/ssl/certs/domain.crt';
    ssl_certificate_key '/etc/ssl/certs/domain.key';
    
    ...
}

 

5. nginx 서버를 재시작 합니다.

 

 

위의 내용은 아래 사이트에서 참조하여 작성하였습니다.

https://www.baeldung.com/openssl-self-signed-cert#:~:text=A%20self%2Dsigned%20certificate%20is,the%20certificate%20isn't%20trusted

 

Creating a Self-Signed Certificate With OpenSSL | Baeldung

A quick and practical guide to creating self-signed certificates with OpenSSL.

www.baeldung.com

 

반응형

'프로그래밍' 카테고리의 다른 글

[docker] min.io 사용하기 - 활용편  (0) 2022.07.20
[docker] min.io 사용하기 - 기초편  (0) 2022.07.20
NGINX SSL 설정 (https) - part 1  (0) 2022.06.11
반응형

nginx 서버에 ssl을 설정하는 방법입니다.

 

ssl이란.. Secure Sockets Layer 의 약자로.. 쉽게 이야기해서 Security가 강화된 프로토콜입니다.

 

요즘 대부분의 웹사이트들은 https를 기본으로 적용하고 있습니다. https 를 사용하는 이유는.. 여러가지가 있겠지만.. 그 중에서도 가장 중요한 것은 보안 문제입니다. http 를 사용하게 되면.. 해당 사이트가 제대로 된 서버로 접속이 된건지.. 피싱 사이트인지 구분이 힘들지만, 제대로 된 https 인증서를 세팅한 사이트라면, 실제 회사의 사이트로 접속했다는 것을 인증서 발급 업체가 검증을 해주기 때문입니다. 그 외에 유저 전송 데이터를 암호화 하는 것도 반드시 필요하기 때문에 https가 권장되고 있습니다.

 

대부분의 웹서버들에서 ssl 설정을 할 수 있지만.. 오늘은 nginx 서버에 설정해 보겠습니다.

 

ssl 설정을 하기 위해서는 최종적으로 private key 파일과 인증서 파일이 필요합니다. 

 

먼저 private key 파일과 인증서 파일을 준비하는 과정입니다.

private key 파일이나 인증서 파일은 여러가지 형태(확장자)로 존재하기 때문에, 가장 기본이 되는 파일로 설명해 보겠습니다.

 

1. 암호화 된 private key 파일과 인증서 요청 파일을 생성합니다.

아래 명령어를 이용해서 생성합니다.

openssl req -newkey rsa:2048 -keyout PRIVATEKEY.key -out CSR.csr

private key 파일 (PRIVATEKEY.key) 과 인증서 요청 파일 (CSR.csr) 을 생성합니다. 여기서는 rsa 2048bits 로 생성했지만, 더 강화된 보안을 위해서 rsa:4096 등의 옵션을 사용할 수 있습니다.

생성시 private key 파일에 password 를 설정해야하며, 인증 요청을 위한 정보들 (국가, 지역 등등..) 을 입력해야 합니다.

private key 파일과 password 를 잘 보관해 둬야 합니다.

 

2. 인증서 요청 파일을 인증기관에 보내서 인증서를 발급 받습니다.

원래 인증기관은 인증을 해주는 기관이지만, 여기서는 ssl 인증서를 발급해 주는 회사라고 하겠습니다. GeoTrust 같은 곳이 유명하고, 그 외에도 cloudflare, sectigo(comodo) 등의 인증 기관에서 인증서를 발급 받을 수 있습니다. 인증 기관에 따라 서비스가 크게 차이가 나진 않지만 인증서 발급 가격은 다를 수 있습니다.

인증서는 전체 도메인에 대해서 받을 수도 있고, 특정 호스트 도메인에 대해서도 받을 수 있으며, 인증 기간에 따라서도 비용이 다르니 필요에 따라 발급 받으면 됩니다.

위에서 생성한 인증 요청 파일 (csr 파일)을 보내고, 비용을 내면, 인증서 파일 (pem, crt 등) 을 보내줍니다.

보통 인증기관에서는 요청 도메인에 대한 인증서와 체인 인증서등을 보내줍니다.

요청 도메인에 대한 인증서는 위에서 생성한 csr 에 대한 인증서입니다.

체인 인증서는 csr에 대한 인증서를 인증해준 인증기관들에 대한 정보라고 생각하면 됩니다. 즉, A라는 인증기관이 B를 인증해주고, B에서 csr에 대한 인증서를 발급해 준 것이라면, A-B 인증기관에 대한 인증서가 됩니다.

참고로 인증서는 하나의 파일에 여러 개의 인증 정보가 포함될 수 있습니다.

 

만약 체인 인증서에 요청한 인증서가 포함되지 않았다면, 아래 명령어로 인증서를 하나로 합칩니다.

(domain.crt 가 요청해서 받은 인증서, bundle.crt 가 인증 기관의 체인 인증서)

cat domain.crt bundle.crt > domain.chained.crt

 

3. 암호화된 private key 파일을 복호화합니다.

아래 명령을 이용하여 복호화합니다.

openssl rsa -in PRIVATEKEY.key -out domain.key

처음에 설정한 password 를 입력하면 domain.key 라는 복호화된 키 파일이 생성됩니다.

 

4. 파일을 적당한 위치에 위치시킵니다.

리눅스 서버를 기준으로 하면.. /etc/ssl/certs 같은 폴더에 인증서 파일을 보관합니다.

인증서 (domain.chained.crt) 와 private key (domain.key) 파일을 /etc/ssl/certs 폴더에 위치시킵니다.

 

파일을 읽기 전용으로 변환합니다. (optional)

chmod 400 domain.key
chomd 400 domain.chained.crt

 

nginx 설정 파일을 열어 다음과 같이 설정합니다.

# http 요청을 https URL로 redirect 시킨다.
server {
    listen 80;
    server_name your_domain.com;
    return 301 https://your_domain.com$request_uri;
}

server {
    listen 443 ssl;
    server_name your_domain.com;
    
    ssl_certificate      /etc/ssl/certs/domain.chained.crt;
    ssl_certificate_key  /etc/ssl/certs/domain.key;
    
    # 필요에 따라 ssl protocol 이나 cipher 등을 설정할 수 있음
    # ssl_protocols TLSv1.2 TSLv1.3;    
    # ssl_ciphers ...;
    # ssl_prefer_server_ciphers on;
    
    # 나머지 nginx 옵션
    ...
}

설정 후 nginx를 재시작 합니다.

 

재시작 후에는 아래 명령어로 nginx 설정을 확인해 봅니다.

nginx -t

 

위와 같이 설정하면 your_domain.com 으로 오는 요청에 대해서, http 요청은 https 로 리다이렉트 되고, https 요청은 브라우저의 루트 인증서와 사이트 인증서를 확인하고 나서, 브라우저에서 안전한 연결인지 알려주게 됩니다.

 

브라우저에서 자물쇠 표시가 제대로 나오고, 에러 메시지가 없으면 잘 처리 된 것입니다. 

(아래는 네이버 사이트 https 연결 체크)

 

요약

1. key 파일과 인증서 요청 파일을 생성한다.

2. 인증서 요청 파일을 가지고 인증기관에서 인증서를 받아온다.

3. key 파일 (decrypt 필요), 인증서 파일 (체인 인증서) 을 준비해서 적당한 위치에 둔다.

4. nginx 설정에 key와 인증서 등을 세팅하고 리스타트 한다.

 

 

많이 사용되는 파일 확장자를 간략히 정리하면 다음과 같습니다.

  • .key : private key. (.p8 혹은 .pkcs8을 쓰기도 함) encrypted 된 파일도 있고, decrypted 된 파일도 있습니다. encrypted 파일의 경우 decrypt 하기 위해서는 암호가 필요합니다.
  • .csr : 인증 요청서 파일. (.req 혹은 .p10을 쓰기도 함) 이것을 인증 기관으로 보내서 인증서를 받습니다. (보통은 파일 형태로 보내지 않고, 그 내용만 복사해서 폼에 붙여 넣습니다.)
  • .crt : 인증서 파일. (.cer 을 쓰기도 함) 체인 인증서일 수도 있음.
  • .der : 바이너리 형태의 인증서. (.cer 을 쓰기도 함)
  • .pem : 컨테이너 파일. 컨테이너 라는 것은 안에 무언가 내용을 담을 수 있다는 의미이며, base64로 인코딩된 내용이 들어갑니다. 내용은 key가 될 수도 있고, 인증서가 될 수도 있습니다. (즉, 어떤 내용이 담겨있는지는 생성한 쪽에서 알려줘야 합니다.)
  • .p7b 혹은 .p7c : PKCS#7/CMS 메세지. private key 를 제외한 인증서 혹은 체인 인증서를 담기 위해 사용됩니다.
  • .pfx 혹은 .p12 : PKCS#12 파일. 키 스토어 컨테이너로 보통은 암호화 된 private key 와 인증서를 같이 담기 위해 사용됩니다.

 

각 포맷간 변환 참조 (소스: https://myonlineusb.wordpress.com/2011/06/19/what-are-the-differences-between-pem-der-p7bpkcs7-pfxpkcs12-certificates/)

# PEM to DER
openssl x509 -outform der -in certificate.pem -out certificate.der

# PEM to P7B
openssl crl2pkcs7 -nocrl -certfile certificate.cer -out certificate.p7b -certfile CAcert.cer

# PEM to PFX
openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt -certfile CAcert.crt

# DER to PEM
openssl x509 -inform der -in certificate.cer -out certificate.pem

# P7B to PEM
openssl pkcs7 -print_certs -in certificate.p7b -out certificate.cer

# P7B to PFX
openssl pkcs7 -print_certs -in certificate.p7b -out certificate.cer
openssl pkcs12 -export -in certificate.cer -inkey privateKey.key -out certificate.pfx -certfile CAcert.cer

# PFX to PEM
openssl pkcs12 -in certificate.pfx -out certificate.cer -nodes

 

반응형

+ Recent posts