[Docker] Django 블로그 Nginx Proxy Manager로 서빙하기

◾️ 개요

Django 기반 블로그가 AWS Lightsail에서 호스팅 되고 있는 상황이다. image
현재 배포되고 있는 구조는 위 사진과 같다.

사용자가 www.waytothem.com 또는 waytothem.com으로 접속하면 내부 컨테이너인 nginx가 요청을 받게되고 내부 django app으로부터 데이터를 넘겨주게된다.

이 구성은 도커로 서비스 구성시 매우 간단하고 흔하게 볼 수 있는 구성이다.

그러나 최근 Nginx Proxy Manager라는 좋은 오픈소스를 알게되어 해당 이미지를 현재 배포된 Django 프로젝트에 적용해보려고 한다.

💥 Nginx Proxy Manager에 대한 자세한 설명은 아래 게시글로 들어가면 실습과 함께 해당 개념에 대해 이해할 수 있다.
[Docker] Nginx Proxy Manager(NPM) 구축하기


Nginx Proxy Manager를 적용하게 됨으로써 배포 구성도는 아래와 같이 변경될 예정이다.
image


◾️ 프로젝트 소스코드

https://github.com/JaehyoJJAng/Python_Web_Development_2022


◾️ 사전 준비

1. 도커 네트워크 생성

아래 명령어를 사용하여 도커 네트워크를 생성하자. 도커 네트워크를 생성하면 다른 docker compose에서 앱을 npm 리소스에 붙일 때 networks 옵션만 주면되서 되게 간편해진다.

docker network create npm-net

2. 가비아 도메인

리버스 프록시를 하기 위해서는 도메인이 필요하다.
나는 가비아에서 500원 짜리 .store 도메인을 구입하였다.

추후 dozzle, loki, grafana 등의 앱들도 서브 도메인을 생성하여 리버스 프록시를 적용 할 예정이니 서브 도메인 설정도 해야한다.

관련 방법은 아래 게시글을 참고하자.
가비아 서브 도메인 설정하기 - WTT Devlog


◾️ Docker-Compose

version: '3.2'

services:
  npm:
    image: 'jc21/nginx-proxy-manager:latest'
    depends_on:
      - "db"
    restart: always
    volumes:
      - "./data:/data"
      - "./letsencrypt:/etc/letsencrypt"
      - type: volume #  _static 연동
        source: "static_volume"
        target: "/usr/src/app/_static"
      - type: volume #  _media 연동
        source: "media_volume"
        target: "/usr/src/app/_media"
    ports:
      - "80:80"
      - "81:81"
      - "443:443"
    environment:
      - "TZ=Asia/Seoul"
      - "DB_MYSQL_HOST=db"
      - "DB_MYSQL_PORT=3306"
      - "DB_MYSQL_USER=npm"
      - "DB_MYSQL_PASSWORD=npm"
      - "DB_MYSQL_NAME=npm"
    networks:
      - "npm-net"
    container_name: npm

  db:
    image: 'jc21/mariadb-aria:latest'
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: "npm"
      MYSQL_DATABASE: "npm"
      MYSQL_USER: "npm"
      MYSQL_PASSWORD: "npm"
    volumes:
      - type: volume
        source: "my-data"
        target: "/var/lib/mysql"
    networks:
      - "npm-net"
    container_name: db

  web:
    image: yshrim12/wtt-web:latest
    restart: always
    command: gunicorn do_it_django_prj.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - type: volume #  _static 연동
        source: "static_volume"
        target: "/usr/src/app/_static"
      - type: volume #  _media 연동
        source: "media_volume"
        target: "/usr/src/app/_media"
      - type: bind
        source: "./WTT"
        target: "/usr/src/app"
    expose:
      - "8000"
    env_file:
      - ".env/.env.prod"
    depends_on:
      - "web-db"
    networks:
      - "npm-net"
    container_name: web

  web-db:
    image: postgres:12.0-alpine
    restart: always
    volumes:
      - type: volume
        source: "postgres_data"
        target: "/var/lib/postgresql/data"
    env_file:
      - ".env/.env.prod.db"
    networks:
      - "npm-net"
    container_name: web-db

networks:
  npm-net:
    name: npm-net
    external: true

volumes:
  postgres_data: {}
  my-data: {}
  static_volume: {}
  media_volume:  {}

80,443은 웹 서비스를 위한 포트이고, 81번의 경우 어드민 페이지임.


1. 컨테이너 실행

작성이 끝났으면 docker-compose up -d --build 명령어로 컨테이너를 생성하도록 하자.

NAME                COMMAND                  SERVICE             STATUS              PORTS
db                  "/scripts/run.sh"        db                  running             3306/tcp
npm                 "/init"                  npm                 running             0.0.0.0:80-81->80-81/tcp, 0.0.0.0:443->443/tcp, :::80-81->80-81/tcp, :::443->443/tcp
web                 "gunicorn do_it_djan…"   web                 running             8000/tcp
web-db              "docker-entrypoint.s…"   web-db              running             5432/tcp

◾️ NPM 접속

컨테이너가 모두 정상적으로 실행됐다면 http://<IP_또는_도메인주소>:81으로 접속해보자.
image
초기 실행 시 계정 정보는 다음과 같다. admin@example.com / changeme

초기에 로그인을 하게되면 계정명, 이메일 주소, 패스워드를 모두 변경해야 한다.


1. proxy hosts 등록

프록시 호스트를 등록하기 위해 Dashboard - Proxy Hosts로 들어가도록 하자.
image


’Add Proxy Hosts’를 클릭하고 필수 정보를 입력하도록 하자.
image
image
다 작성하고 ’save’를 누르면 리버스 프록시 설정은 끝이다!

  • Domain Names: 가비아,Route53,CloudFlare 등에서 구입한 도메인 이름
  • Foward Hostname / IP: Docker Compose에서 작성했던 컨테이너 이름
  • Foward Port: Docker container 내부 Port

이제 도메인 주소를 브라우저에 입력한 후 접속해보자.
image
서비스에 정상적으로 접속이 되었다 ..

하지만 뭔가 이상하다. 정적 파일들이 적용되지 않은 것 같다!
우선 이 문제는 🔺 트러블슈팅에서 다시 다루는 걸로 하고

SSL 인증서를 적용하러 가보자.


2. SSL 인증서 적용

이전에 nginx 컨테이너를 올려서 배포할 때는 certbot 컨테이너를 생성해야했고, init-letsencrpt.sh 파일도 다운로드 받아서 군데군데 수정해줘야 했고 굉장히 귀찮은 작업의 연속이었는데

Nginx Proxy Manager는 딸칵 한 번이면 될 정도로 SSL 인증서를 정말 쉽게 인증 받을 수 있다!

먼저 이전에 만든 Proxy hosts의 ‘edit’ 창으로 가서 SSL 탭으로 이동해보자.
image


그럼 다음과 같이 SSL Certificate를 선택할 수 있는데
image
나는 이전에 SSL 인증서를 한 번 만들어봤기에 인증서 기록이 아직 남아있다.


’Request a new SSL Certificate’를 클릭하면 여러 옵션들이 뜬다.
image
다음과 같은 옵션들을 선택하고 ’save’를 누르면 끝이다.

각 옵션들에 대한 설명은 여기서 하지 않겠다. 궁금하면 GPT를 사용하자.


그럼, 다음과 같이 hosts의 SSL 상태가 Let’s Encrypt로 나오게 된다.
image


정상적으로 발급됐는지 브라우저에 도메인을 입력하여 접속해보자.
image
정상적으로 발급되었다! ☺️


🔺 트러블슈팅

1. 정적 파일 이슈


현재 배포된 블로그에 정적 파일들이 적용되지 않는다.

nginx proxy manager를 도입하기 전에는 로컬에서 nginx.conf 파일을 직접 수정하여 static 파일 및 media 파일의 서빙 경로를 직접 지정한 후 nginx 컨테이너의 /etc/nginx/conf.d/nginx.conf 파일로 마운트했었다.
image


그러나, nginx proxy manager를 도입한 지금은 정적 파일 서빙 설정을 어디다가 해주어야 하는걸까? 동일하게 npm 컨테이너의 /etc/nginx/conf.d 경로에 설정 파일을 만들어주고 마운트 시켜줘야하나?

검색 결과 명쾌한 답을 찾을 수 있었다.(https://dimensionquest.net/2021/02/host-static-site-on-npm/)

Proxy hosts를 등록하게 되면 ./data/nginx/proxy_host/ 경로에 등록한 순서대로 1.conf, 2.conf .. 형식으로 파일이 생성되는데, proxy hosts를 등록할 때 기입했던 필수 정보들에 대한 설정들이 모두 .conf 파일에 들어가게 되는 것이었다.
image


그 설정들 또한 Proxy Hosts의 Advanced 탭에서 추가할 수 있다!
image
이렇게 설정을 추가한 후 save를 해주자.


설정을 추가해주고 2.conf 파일을 읽어보면 위에서 추가한 설정이 그대로 들어간 것을 확인할 수 있을 것이다.

cat ./data/nginx/proxy_host/2.conf

image


2. logs 확인

그러나 여전히 정적 파일이 적용이 되지 않고 있다!.

원인이 궁금해 로그 파일을 찾기 시작했다.

find . -name "*.log" -type f 2>/dev/null
./data/logs/fallback_access.log
./data/logs/proxy-host-2_error.log
./data/logs/proxy-host-1_error.log
./data/logs/proxy-host-1_access.log
./data/logs/fallback_error.log
./data/logs/letsencrypt-requests_access.log
./data/logs/letsencrypt-requests_error.log
./data/logs/proxy-host-2_access.log

npm에서 등록된 proxy host들의 로그는 아무래도 ./data/logs에 저장되나 보다.


proxy_host-2에서 발생한 에러 로그를 읽어보았다.

cat ./data/logs/proxy-host-2_error.log

2023/12/08 23:44:03 [error] 212#212: *57 open() "/usr/src/app/_staticblog/css/footer.css" failed (2: No such file or directory), client: x.x.x.x, server: waytothem.store, request: "GET /static/blog/css/footer.css HTTP/1.1", host: "waytothem.store", referrer: "http://waytothem.store/"
2023/12/08 23:44:03 [error] 212#212: *57 open() "/usr/src/app/_staticsingle_pages/images/mini_007.png" failed (2: No such file or directory), client: x.x.x.x, server: waytothem.store, request: "GET /static/single_pages/images/mini_007.png HTTP/1.1", host: "waytothem.store", referrer: "http://waytothem.store/"

"/usr/src/app/_staticsingle_pages/images/..." 이쪽 부분이 조금 이상하다. 아무래도 ‘/’ 를 빼먹은 것 같다.

Proxy hosts 페이지로 이동하여 Advanced를 다시 확인해보자.
image
정말로 ’/’를 빼먹었다!

원래라면 아래처럼 들어갔어야 했다.

location /static/ {
    alias /usr/src/app/_static/;
}

location /media/ {
    alias /usr/src/app/_media/;
}

수정하고 다시 접속하니 정상적으로 static이 로드된다!
image

관련 글

지식 공유2분 읽기

[Docker] 컨테이너(Container)에 할당되는 CPU 제한하기

개요 이번 게시글에서는 도커 컨테이너에서 CPU를 최적으로 활용하는 방법에 대해서 기록해보려고 한다. 호스트 CPU Core 정보 확인 먼저 컨테이너에 CPU를 최적으로 할당하기 위해서는, 현재 호스트 시스템의 CPU 정보에 대해서...

도커
지식 공유1분 읽기

[Docker] 배그 최고급 꾸러기 시뮬레이터 배포

개요 최고급 꾸러미에 대한 확률 체감을 위한 시뮬레이터 프로그램을 구현하고 도커로 배포하였음. 배포 로직은 AWS EC2 인스턴스에 도커를 사용하여 nginx proxy manager를 배포하고 리버스 프록시로 홈 서버에 있는 시뮬레...

도커