نحوه Dockerize کردن Django و Postgre و Nginx و LetsEncrypt - برای محیط Prod

1402/05/04 | 676 |
django

در این پست به نحوه داکرایز کردن پروژه های جنگو برای فاز Production خواهیم پرداخت و در ادامه به راه کار های موجود و یک قالب آماده برای پیاده سازی پروژه های جنگو را به شما معرفی خواهیم کرد. در نظر داشته باشید که برای درک این موضوع نیاز به یادگیری داکر خواهید داشت و می بایست تا حد خیلی خوبی به ساختار ایجاد Dockerfile و همچنین استفاده از docker-compose اشراف داشته باشید.

 

پیاده سازی پروژه های جنگو با قالب آماده من

 
لینک رپو: 

 

در قسمت قبل به نحوه پیاده سازی پروژه های جنگو در فاز stage اشاره شد و مدل پیاده سازی آن را با هم تجربه کردیم، اما در این قسمت می خواهیم با اضافه کردن تنظیمات ssl سرویس خود را امن کنیم. در کل می توان گفت بیشترین نسبت تغییرات در بخش nginx و settings جنگو خواهد بود و تغییر خاصی در فایل ها نیست و بیشتر مروری بر مدل خواهیم داشت.

فرض بر این است که پروژه جنگو شما آماده برای پیاده سازیست و ما می خواهیم مسیر و نحوه پیاده سازی را با یکدیگر انجام دهیم. برای این کار پروژه ساده زیر را در نظر بگیرید.

│   .gitattributes
│   LICENSE
│   README.md
│   .gitignore
│   requirements.txt
└─── core
      ├─── staticfiles
      ├─── static
      ├─── media
      ├─── manage.py
      └─── core
            │   asgi.py
            │   error_views.py
            │   settings.py
            │   urls.py
            │   wsgi.py
            └─── __init__.py
    

 

ساخت requirements

پس از ایجاد تنظیمات بالا نیاز است که برای پروژه جنگو خود فایل requirements.txt را ایجاد و تمام ماژول هایی که قرار است استفاده کنیم را درج نماییم. که نمونه زیر حاوی ماژول gunicorn است.

# requirements.txt

# general  modules
django >3.2,<3.3

# env controls
python-decouple

# deployment module
gunicorn

# database client
psycopg2-binary

ساخت Dockerfile

بخش تنظیمات نرم افزار به پایان رسیده و حال می بایست به ساخت Dockerfile و docker-compose بپردازیم. در ساخت Dockerfile کافیست که به شکل زیر عمل نمایید. و تنظیمات لازم را برای Dockerfile مربوط به جنگو در نظر بگیرید.

# dockefiles/prod/django/Dockerfile

# pull official base image
FROM python:3.10-alpine as base

LABEL maintainer="bigdeli.ali3@gmail.com"

RUN apk add --update --virtual .build-deps \
    build-base \
    postgresql-dev \
    python3-dev \
    libpq


# install dependencies
COPY ./requirements.txt .
RUN pip install --upgrade pip && pip install -r requirements.txt


# Now multistage build
FROM python:3.10-alpine
RUN apk add libpq
COPY --from=base /usr/local/lib/python3.9/site-packages/ /usr/local/lib/python3.9/site-packages/
COPY --from=base /usr/local/bin/ /usr/local/bin/

# set work directory
WORKDIR /usr/src/app

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# copy project
COPY ./core .

در فایل بالا برای پایین نگه داشتن حجم و سطح دسترسی کاربری python با استفاده از alpine-image اقدام به ساخت کانتینر می کنیم که در دو مرحله صورت میگیرد. مرحله اول نصب ماژول ها و آماده سازی پکیج ها خواهد بود ( در صورتی که بخوایهد از این dockerfile برای ساخت کانتینر های worker ویا beater استفاده نمایید حجم کمتری اشغال می شود) و در مرحله بعدی سورس اصلی که قرار است پیاده سازی بر روی آن صورت بگیرد ساخته خواهد شد.

سپس برای nginx نیاز است که dockerfile دیگری ایجاد شود تا بتوان تنظیماتی که در زیر ایجاد می کنید را به آن منتقل و در زمان اجرا در دسترس سرویس قرار داد.

# dockerfiles/prod/nginx/Dockerfile

FROM nginx:alpine

COPY ./config/* /etc/nginx/
COPY ./entrypoint.sh /entrypoint.sh

USER root

RUN apk add --no-cache openssl bash
RUN chmod +x /entrypoint.sh


CMD ["/entrypoint.sh"]

دستورات مربوط به entry point را در همان پوشه قرار می دهیم:

# dockerfiles/prod/nginx/entrypoint.sh

#!/bin/bash

set -e

echo "Checking for dhparams.pem"
if [ ! -f "/vol/proxy/ssl-dhparams.pem" ]; then
  echo "dhparams.pem does not exist - creating it"
  openssl dhparam -out /vol/proxy/ssl-dhparams.pem 1024
fi

echo "Checking for fullchain.pem"
if [ ! -f "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" ]; then
  echo "No SSL cert, enabling HTTP only..."
  envsubst '\$DOMAIN' < /etc/nginx/default.conf.tpl > /etc/nginx/conf.d/default.conf
else
  echo "SSL cert exists, enabling HTTPS..."
  envsubst '\$DOMAIN' < /etc/nginx/default-ssl.conf.tpl > /etc/nginx/conf.d/default.conf
fi

nginx-debug -g 'daemon off;'

ساختار بالا برای تصمیم گیری این است که سرویس شما در حال حاضر دارای ssl است یا خیر و درصورت عدم پیدا کردن فایل مورد نظر سایت را بدون در نظر گرفتن موارد امنیتی و میشه گفت برای اولین بار به حالت اجرا در میاورد.

دلیل این عمل این است که برای اینکه سرویس دریافت گواهی بتواند عمل کد نیاز دارد که سرویس فعلی در حالت اجرا قرار گرفته باشد.

حال نیاز به فایل تنظیمات nginx است که می بایست دو فایل ایجاد شوید. یکی برای تنظیمات پایه تا بدون در نظر گرفتن ssl آن را اجرا نماید و دیگری برای اجرا با در نظر گرفتن گواهی ssl:

# dockerfiles/prod/nginx/config/default.conf.tpl

server {
    listen 80;

    server_name ${DOMAIN} www.${DOMAIN};

    # server logs
    access_log  /var/log/nginx/access_log.log;
    error_log /var/log/nginx/error_log.log;


    location /.well-known/acme-challenge/ {
        root /vol/www/;
    }


    location / {        
        proxy_redirect     off;
        proxy_pass   http://backend:8000;
    }
}

و تنظیمات مبتنی بر گواهی ssl

# dockerfiles/prod/nginx/config/default-ssl.conf.tpl


server {
    listen 80;

    server_name ${DOMAIN} www.${DOMAIN};

    # server logs
    access_log  /var/log/nginx/access_log.log;
    error_log /var/log/nginx/error_log.log;


    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }


    location / {
        return 301 https://${DOMAIN}$request_uri;
    }
}

 

server {
	listen 443 ssl;

    # server names
    server_name ${DOMAIN};

    # slow connections timeout
    # client_body_timeout 5s;
    # client_header_timeout 5s;

    # rate limiting
    # limit_req zone=mylimit burst=20 delay=20;
    # limit_req_status 429;

    # Let's Encrypt parameters
    ssl_certificate     /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem;
    # keepalive_timeout   70;
    
    # ssl configs which has been given from cert bot
    ssl_session_cache shared:le_nginx_SSL:10m;
    ssl_session_timeout 1440m;
    ssl_session_tickets off;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
    ssl_dhparam /vol/proxy/ssl-dhparams.pem;

    # charset config
    charset     utf-8;

    # max upload size
    client_max_body_size 10M;   # adjust to taste

	server_tokens off;

    # Secruity headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
    add_header Content-Security-Policy "default-src 'self'; img-src * data:; font-src 'self' data:; frame-src 'self' https://www.google.com/; connect-src 'self' ;style-src 'self' https://cdn.jsdelivr.net ;script-src 'self' https://cdn.jsdelivr.net ; object-src 'self' ;frame-ancestors 'self'; form-action 'self'; base-uri 'self';";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options nosniff;
    add_header Referrer-Policy "strict-origin";
    add_header Permissions-Policy "geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()";
    add_header Set-Cookie "Path=/; HttpOnly; Secure;  SameSite=strict;";
    add_header Cache-Control "private, no-cache, no-store, must-revalidate, max-age=0" always;


    # static files directory
    location /static/ {
      autoindex on;
      alias /home/app/static/;
    }
    
    # media files directory
    location /media/ {
      autoindex on;
      alias /home/app/media/;
    }

    location / {
        # proxy_pass_request_headers on;
        # proxy_buffering on;
        # proxy_buffers 8 24k;
        # proxy_buffer_size 2k;  
        proxy_redirect      off;
        proxy_set_header    Host                $host;
        proxy_set_header    REMOTE_ADDR         $remote_addr;
        proxy_set_header    X-Url-Scheme        $scheme;
        proxy_set_header    X-Real-IP           $remote_addr;
        proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto   https;
        proxy_set_header    User-Agent          $http_user_agent;
        proxy_pass   http://backend:8000;
    }
}

اما برای انجام عملیات دریافت گواهی نیاز به سرویس Certbot است که بتوانید از طریف آن گواهی ssl رایگان خود را دریافت نمایید.

Certbot یک ابزار رایگان و باز منبع باز برای دریافت و نصب گواهینامه SSL رایگان از Let's Encrypt است. گواهینامه SSL یک فایل رمزگذاری شده است که به وب سرور شما اجازه می دهد که با مرورگرهای کاربران به صورت امن ارتباط برقرار کند. این گواهینامه شامل یک کلید خصوصی و یک کلید عمومی است که به وسیله آن، اطلاعات بین مرورگر کاربر و سرور شما رمزگذاری می شود.Certbot ابزاری است که به صورت خودکار این گواهینامه ها را دریافت و نصب می کند و در صورت نیاز به تجدید آن ها نیز به صورت خودکار این کار را انجام می دهد. با نصب و استفاده از Certbot می توانید وب سایت خود را به راحتی از HTTP به HTTPS تغییر دهید و ارتباط بین کاربران و سرور خود را بهبود بخشید. Certbot با استفاده از پروتکل ACME، امکان دریافت گواهینامه SSL رایگان از Let's Encrypt را فراهم می کند. این گواهینامه ها برای 90 روز معتبر بوده و Certbot به صورت خودکار این گواهینامه ها را تمدید خواهد کرد، بنابراین نیازی به نگرانی برای تجدید گواهینامه ها نیست. Certbot قابلیت اجرا در تمام سیستم عامل ها و وب سرورهای متداول را داراست و به صورت پیشفرض بر روی بسیاری از سیستم عامل ها موجود است.

# dockerfiles/prod/certbot/Dockerfile

FROM certbot/certbot:v1.27.0

COPY certify-init.sh /opt/
RUN chmod +x /opt/certify-init.sh

ENTRYPOINT []
CMD ["certbot", "renew"]

و فایل اجرای دستورات certbot

# dockerfiles/prod/certbot/certify-init.sh

#!/bin/sh

# Waits for proxy to be available, then gets the first certificate.

set -e

until nc -z nginx 80; do
    echo "Waiting for proxy..."
    sleep 5s & wait ${!}
done

echo "Getting certificate..."

certbot certonly \
    --webroot \
    -w "/vol/www/" \
    -d "$DOMAIN" \
    --email $EMAIL \
    --force-renewal \
    --rsa-key-size 4096 \
    --agree-tos \
    --noninteractive

تا به اینجا تمام بخش های اساسی عملکرد سرویس ها پیاده سازی شده اند به جز یک بخش که تنظیمات امنیتی مربوط به جنگو را شامل می شود:

# core/core/settings.py

# security configs for production
if config("USE_SSL_CONFIG", cast=bool, default=False):
    # Https settings
    SESSION_COOKIE_SECURE = True
    CSRF_COOKIE_SECURE = True
    SECURE_SSL_REDIRECT = True

    # HSTS settings
    SECURE_HSTS_SECONDS = 31536000  # 1 year
    SECURE_HSTS_PRELOAD = True
    SECURE_HSTS_INCLUDE_SUBDOMAINS = True

    # more security settings
    SECURE_CONTENT_TYPE_NOSNIFF = True
    SECURE_BROWSER_XSS_FILTER = True
    X_FRAME_OPTIONS = "SAMEORIGIN"
    SECURE_REFERRER_POLICY = "strict-origin"
    USE_X_FORWARDED_HOST = True
    SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

و در نهایت برای اجرای تمام سرویس ها نیاز به یک docker-compose است:

version: "3.9"
   
services:

  db:
    container_name: db
    image: postgres:alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    env_file:
      - ./envs/prod/db/.env
    restart: always
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U postgres']
      interval: 10s
      timeout: 5s
      retries: 5

  backend:
    build: 
      context: .
      dockerfile: dockerfiles/prod/django/Dockerfile
    container_name: backend
    command: sh -c "python3 manage.py check_database && \
                    python3 manage.py makemigrations --noinput && \
                    python3 manage.py migrate --noinput && \
                    python3 manage.py collectstatic --noinput && \
                    gunicorn --bind 0.0.0.0:8000 core.wsgi:application"
    volumes:
      - static_volume:/usr/src/app/static
      - media_volume:/usr/src/app/media
    expose:
      - "8000"
    env_file:
      - ./envs/prod/django/.env
    depends_on:
      - db
  
  certbot:
    build: 
      context: ./dockerfiles/prod/certbot/
    command: echo "Skipping..."
    container_name: certbot
    env_file:
      - ./envs/prod/nginx/.env
    volumes: 
      - certbot_www_volume:/vol/www/
      - certbot_certs_volume:/etc/letsencrypt/
    depends_on:
      - nginx

  nginx:
    container_name: nginx
    build:
      context: ./dockerfiles/prod/nginx/
    restart: always
    env_file:
      - ./envs/prod/nginx/.env
    ports:
      - 443:443
      - 80:80
    volumes:
      - static_volume:/home/app/static
      - media_volume:/home/app/media
      - certbot_www_volume:/vol/www/
      - proxy_dhparams:/vol/proxy
      - certbot_certs_volume:/etc/letsencrypt/

    depends_on:
      - backend

volumes:
  postgres_data:
  static_volume:
  media_volume:
  certbot_www_volume:
  certbot_certs_volume:
  proxy_dhparams:

برای اجرای سرویس مربوطه کافیست که طبق دستورات زیر عمل نمایید:

docker compose -f docker-compose-prod.yml  run --rm certbot /opt/certify-init.sh

ابتدا دستور بالا را برای آماده سازی و گرفتن گواهی اجرا می کنید و سپس تمام سرویس را پایین میاورد و مجدد راه اندازی می کنید. و تمام

docker compose -f docker-compose-prod.yml down

# حتما باید سرویس را پایین بیاورید

docker compose -f docker-compose-prod.yml up

 


avatar
علی بیگدلی

نویسنده

دوره های من در مکتبخونه

آموزش جنگو پیشرفته
  • سطح: پیشرفته 4.9
آموزش جنگو Django
  • سطح: مقدماتی 4.6

آخرین پست ها

نحوه نصب و پیاده سازی Caprover بر روی لینوکس
نحوه نصب و پیاده سازی Caprover بر روی لینوکس
  • django 1402/11/29
استفاده از محیط docker-compose و dev container در مدیریت پروژه
استفاده از محیط docker-compose و dev container در مدیریت پروژه
  • django 1402/11/22
پایشگر آب و هوا و کنترل از طریق MQTT با نرم افزار IOT Dashboard
پایشگر آب و هوا و کنترل از طریق MQTT با نرم افزار IOT Dashboard
  • micro python 1402/06/11

آخرین دوره ها

آموزش arduino
آموزش Arduino
  • رایگان 77 دانشجو
آموزش رابط گرافیکی Tkinter
آموزش Tkinter
  • رایگان 402 دانشجو