Bunchee
Back to Blog

Django AWS EC2 Nginx Gunicorn (ตอน 2)

ขั้นตอนการ Deploy Django Project ไปวาง บน AWS EC2 โดยใช้ Nginx เป็น Web Server โดยทำงานร่วมกับ Gunicorn และเก็บ Static files บน Cloudinary

Django AWS EC2 Nginx Gunicorn (ตอน 2)

ขั้นตอนการ Deploy Django (ครอบคลุมเวอร์ชั่น 5.0) Project ไปวาง บน AWS EC2 โดยใช้ Nginx เป็น Web Server โดยทำงานร่วมกับ Gunicorn และเก็บ Static files บน Cloudinary ตอนที่ 2


แก้ไขไฟล์ตามนี้

django_auth/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/", include("djoser.urls")),
    path("api/", include("users.urls")),
]

users/models.py

สร้างรูปแบบฐานข้อมูลให้กับ User

from django.db import models
from django.contrib.auth.models import (
    BaseUserManager,
    AbstractBaseUser,
    PermissionsMixin,
)


class UserAccountManager(BaseUserManager):
    def create_user(self, email, password=None, **kwargs):
        if not email:
            raise ValueError("โปรดใส่อีเมลด้วยค่ะ")

        email = self.normalize_email(email)
        email = email.lower()

        user = self.model(email=email, **kwargs)

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password=None, **kwargs):
        user = self.create_user(
            email,
            password=password,
            **kwargs
        )
        user.is_staff = True
        user.is_superuser = True
        user.save(using=self._db)
        return user


class UserAccount(AbstractBaseUser, PermissionsMixin):
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)
    email = models.EmailField(
        max_length=255,
        unique=True,
    )
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)

    objects = UserAccountManager()

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["first_name", "last_name"]

    def __str__(self):
        return self.email

users/authentication.py

customize Django REST framework simple jwt

from django.conf import settings
from rest_framework_simplejwt.authentication import JWTAuthentication


class CustomJWTAuthentication(JWTAuthentication):
    def authenticate(self, request):
        try:
            header = self.get_header(request)
            if header is None:
                raw_token = request.COOKIES.get(settings.AUTH_COOKIE)
            else:
                raw_token = self.get_raw_token(header)

            if raw_token is None:
                return None
            validated_token = self.get_validated_token(raw_token)

            return self.get_user(validated_token), validated_token
        except Exception:
            return None

users/views.py

from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from djoser.social.views import ProviderAuthView
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
    TokenVerifyView,
)


class CustomProviderAuthView(ProviderAuthView):
    def post(self, request, *args, **kwargs):
        response = super().post(request, *args, **kwargs)

        if response.status_code == 201:
            access_token = response.data.get("access")
            refresh_token = response.data.get("refresh")

            response.set_cookie(
                "access",
                access_token,
                max_age=settings.AUTH_COOKIE_ACCESS_MAX_AGE,
                path=settings.AUTH_COOKIE_PATH,
                secure=settings.AUTH_COOKIE_SECURE,
                httponly=settings.AUTH_COOKIE_HTTP_ONLY,
                samesite=settings.AUTH_COOKIE_SAMESITE,
            )

            response.set_cookie(
                "refresh",
                refresh_token,
                max_age=settings.AUTH_COOKIE_REFRESH_MAX_AGE,
                path=settings.AUTH_COOKIE_PATH,
                secure=settings.AUTH_COOKIE_SECURE,
                httponly=settings.AUTH_COOKIE_HTTP_ONLY,
                samesite=settings.AUTH_COOKIE_SAMESITE,
            )

        return response


class CustomTokenObtainPairView(TokenObtainPairView):
    def post(self, request, *args, **kwargs):
        response = super().post(request, *args, **kwargs)

        if response.status_code == 200:
            access_token = response.data.get("access")
            refresh_token = response.data.get("refresh")

            response.set_cookie(
                "access",
                access_token,
                max_age=settings.AUTH_COOKIE_ACCESS_MAX_AGE,
                path=settings.AUTH_COOKIE_PATH,
                secure=settings.AUTH_COOKIE_SECURE,
                httponly=settings.AUTH_COOKIE_HTTP_ONLY,
                samesite=settings.AUTH_COOKIE_SAMESITE,
            )

            response.set_cookie(
                "refresh",
                refresh_token,
                max_age=settings.AUTH_COOKIE_REFRESH_MAX_AGE,
                path=settings.AUTH_COOKIE_PATH,
                secure=settings.AUTH_COOKIE_SECURE,
                httponly=settings.AUTH_COOKIE_HTTP_ONLY,
                samesite=settings.AUTH_COOKIE_SAMESITE,
            )
        return response


class CustomTokenRefreshView(TokenRefreshView):
    def post(self, request, *args, **kwargs):
        refresh_token = request.COOKIES.get("refresh")

        if refresh_token:
            request.data["refresh"] = refresh_token

        response = super().post(request, *args, **kwargs)

        if response.status_code == 200:
            access_token = response.data.get("access")

            response.set_cookie(
                "access",
                access_token,
                max_age=settings.AUTH_COOKIE_ACCESS_MAX_AGE,
                path=settings.AUTH_COOKIE_PATH,
                secure=settings.AUTH_COOKIE_SECURE,
                httponly=settings.AUTH_COOKIE_HTTP_ONLY,
                samesite=settings.AUTH_COOKIE_SAMESITE,
            )

        return response


class CustomTokenVerifyView(TokenVerifyView):
    def post(self, request, *args, **kwargs):
        access_token = request.COOKIES.get("access")
        if access_token:
            request.data["token"] = access_token

        return super().post(request, *args, **kwargs)


class LogoutView(APIView):
    def post(self, request, *args, **kwargs):
        response = Response(status=status.HTTP_204_NO_CONTENT)
        response.delete_cookie("access")
        response.delete_cookie("refresh")

        return response

users/urls.py

urls ทั้งหมดที่ใช้กับการ Login / Logout

from django.urls import path, re_path
from .views import (
    CustomProviderAuthView,
    CustomTokenObtainPairView,
    CustomTokenRefreshView,
    CustomTokenVerifyView,
    LogoutView,
)

urlpatterns = [
    re_path(
        r"^o/(?P<provider>\S+)/$",
        CustomProviderAuthView.as_view(),
        name="provider-auth",
    ),
    path("jwt/create/", CustomTokenObtainPairView.as_view()),
    path("jwt/refresh/", CustomTokenRefreshView.as_view()),
    path("jwt/verify/", CustomTokenVerifyView.as_view()),
    path("logout/", LogoutView.as_view()),
]

Social Authentication

Google OAuth2

ในบทความนี้ครอบคลุมเฉพาะ การใช้งาน Django Static Files ร่วมกับ Cloudinary ดังนั้นยังไม่ลงรายละเอียดเรื่อง Authentication จึงขอข้ามไปก่อนครับ

Facebook OAuth2

โปรแกรม Django Auth ที่กล่าวถึงในบทความนี้ ตั้งใจจะให้เป็น ซีรี่ย์ ดังนั้นจะกล่าวถึงรายละเอียดการตั้งค่า Facebook OAuth2 ในบทความถัดไป


AWS SES

Django Auth ที่เรากำลังสร้างอยู่นี้ จะรับผิดชอบงานหลังบ้าน ส่วนใหญ่ หรือทั้งหมด (Back-End) เพื่อแบ่งเบาภาระการประมวลผลของ Front-End (NextJS)

ดังนั้นเมื่อลูกค้า หรือ User ขอลงทะเบียนเข้าใช้งานเว็บไซต์ (Front-End) ข้อมูลจะส่งมาประมวลผลที่ Django Auth ซึ่งเป็น Back-End และหนึ่งในนั้นคือการส่งอีเมลยืนยันตัวตนลูกค้า หรือ User ให้ติดตั้ง Package นี้เพิ่มเติมครับ

pip install django-ses
pip freeze > requirements.txt

วิธีสร้างอีเมล เพื่อทดสอบ AWS SES

ไปที่ AWS SES คนที่เข้าใช้งานครั้งแรกกดปุ่ม Get started

Deploy Django EC2 Nginx SES 1

ป้อนที่อยู่อีเมล ใช้ Gmail ก็ได้ครับ คลิ๊ก Next

Deploy Django EC2 Nginx SES 2

ขั้นตอนถัดมาระบบบังคับให้เราป้อน เราป้อน gmail.com ไปก่อนครับ คลิ๊ก Next

Deploy Django EC2 Nginx SES 3

หน้าจอถัดไป กดปุ่ม Get Started

Deploy Django EC2 Nginx SES 4

เสร็จแล้วครับ ตอนนี้ AWS SES จะส่งอีเมลไปยืนยันตัวตน ให้เปิดอีเมลที่ท่านระบุตามขั้นตอนด้านบน แล้วคลิ๊กลิงค์ ที่ AWS SES ส่งมา

Deploy Django EC2 Nginx SES 5

ให้ทำซ้ำอีกครั้ง โดยเพิ่มอีก 1 อีเมล ไปที่ Verified identities แล้วกดปุ่ม Create identity

Deploy Django EC2 Nginx SES 6

หน้าจอถัดไปให้เลือก Email address แล้วเติมอีเมลอันใหม่เข้าไป เสร็จแล้ว กดปุ่ม Create identity

Deploy Django EC2 Nginx SES 7

SES SMTP Credential

ขั้นตอนต่อไปเราจะสร้าง SMTP Credential เมื่อไปที่หน้าจอ Amazon SES ตรง Sidebar ด้านซ้ายมือ คลิ๊ก SMTP settings แล้วเลือก Create SMTP credentials

Deploy Django EC2 Nginx SES 8

ต่อไปให้เลือก Create user

Deploy Django EC2 Nginx SES 9

ให้คัดลอก SMTP credentials ไว้ครับ หรือดาวน์โหลดไฟล์ มาเก็บไว้

ไปที่ไฟล์ .env.local คัดลอก SMTP Credentials ดังนี้ครับ

AWS_SES_ACCESS_KEY_ID="" # ตรงนี้ต้องแก้ ตามเลข 2
AWS_SES_SECRET_ACCESS_KEY="" # ตรงนี้ต้องแก้ ตามเลข 3

AWS_SES_FROM_EMAIL="your-email@gmail.com" # ตรงนี้ต้องแก้ ตามอีเมลที่เรากำหนดใน Verified identities ใช้ gmail ก็ได้ครับ

ไปที่ IAM คลิ๊กตรง User ที่เราเพิ่งสร้างซึ่งขึ้นต้นด้วย ses-smtp-user-xxxxxx

ตามด้วย Add permissions

เมื่อคลิ๊กจะได้ Drop down list ให้เลือก Add permissions อีกครั้ง

เลือก Attach policies directly ค้นหาคำว่า SES เลือก AmazonSESReadOnlyAccess กดปุ่ม Next

แล้วกดปุ่ม Add permissions

ตอนนี้เราจะมี 2 Policies ตามรูปด้านล่าง

เสร็จแล้วครับ สำหรับขั้นตอนการตั้งค่า AWS SES


Custom Domain Name

ต่อไปเราจะตั้งชื่อ โดเมน บน AWS Route 53 ให้ไปที่ EC2 คัดลอก IP Address ตรง Elastic IP addresses

ไปเปิดหน้าจอ AWS Route 53 เลือกชื่อโดเมนที่เราต้องการ แล้วกดปุ่ม Create record

เข้ามาที่หน้าจอ Create record ให้ใส่ชื่อ sub-domain เช่นเราต้องการสร้าง django-auth-api.example.com ก็ให้ใส่แค่ django-auth-api เลือก Record type เป็น A

ตรง Value ให้วางเลข IP Address ที่เราคัดลอกมาจาก EC2 เสร็จแล้วกดปุ่ม Create records


Development -> Production

เมื่อมาถึงขั้นตอนนี้เราสามารถ Run Django App ผ่านคำสั่ง

python manage.py runserver

และ Django App จะทำงานบนพอร์ต 8000 แต่อย่างไรก็ตามบทความนี้เราจะไม่ทดสอบบน Development นะครับ เราจะไปทดสอบทีเดียวบน Production ดังนั้นอย่าเพิ่งสั่ง runserver ครับ


Gunicorn

เนื่องจาก คำสั่ง gunicorn ต้องใช้ร่วมกับ package django แต่ django อยู่ใน Virtual Environment ดังนั้น แทนที่จะติดตั้ง Gunicorn บน Ubuntu ด้วยคำสั่ง

sudo apt-get install gunicorn

ผมขอเปลี่ยนมาติดตั้ง Gunicorn ใน Virtual Environment แทนแล้วกันครับ โดยตอนนี้ตรวจดูให้แน่ใจว่าเคอร์เซอร์ของเราอยู่ที่ Virtual Environdment แล้วรันคำสั่ง

pip install gunicorn

ลองใช้คำสั่ง which gunicorn เราจะพบว่า gunicorn อยู่ที่ env/bin

❯ which gunicorn
/home/django/django-auth/env/bin/gunicorn

systemd socket

ให้สร้างไฟล์ gunicorn.socket ด้วยคำสั่ง

sudo nano /etc/systemd/system/gunicorn.socket

คัดลอกโค้ดนี้เข้าไปได้เลยครับ ขั้นตอนนี้จะยังไม่มีอะไรครับ

[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

สั่งบันทึกไฟล์ แล้วออกมาที่ Terminal

systemd service

สร้างอีกไฟล์ คู่กันครับ แต่รอบนี้จะเป็น gunicorn.service

sudo nano /etc/systemd/system/gunicorn.service

ไฟล์ gunicorn.service จะซับซ้อนกว่าไฟล์ gunicorn.socket นิดหน่อย ให้รันคำสั่ง

which gunicorn

จะได้คำตอบตามด้านล่างนี้ ให้เก็บคำตอบไว้ก่อนครับ

/home/django/django-auth/env/bin/gunicorn

เข้าไปที่ไฟล์ django_auth/settings.py

❯ pwd
/home/django/django-auth
❯ ls
django_auth  env  manage.py  requirements.txt  users

ค้นหาบรรทัดที่มีโค้ดนี้อยู่ครับ

WSGI_APPLICATION = "xxxx.wsgi.application"

ในที่นี้จะเป็น

WSGI_APPLICATION = "django_auth.wsgi.application"

เราได้ข้อมูลครบแล้ว ให้เปิดไฟล์ gunicorn.service ด้วยคำสั่ง

sudo nano /etc/systemd/system/gunicorn.service

คัดลอกโค้ดนี้เข้าไปครับ

[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target

[Service]
User=<your-user>
Group=www-data
WorkingDirectory=<Project-Directory-Root>
Environment="PATH=/home/<your-user>/<your-project>/env/bin"
ExecStart=path-to-gunicorn \
          --access-logfile - \
          --workers 3 \
          --bind unix:/run/gunicorn.sock \
 <your-project>.wsgi:application

[Install]
WantedBy=multi-user.target

ซึ่งในกรณีนี้ หลังจากแก้ไขแล้ว จะได้

[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target

[Service]
User=django
Group=www-data
WorkingDirectory=/home/django/django-auth
Environment="PATH=/home/django/django-auth/env/bin"
ExecStart=/home/django/django-auth/env/bin/gunicorn \
          --access-logfile - \
          --workers 3 \
          --bind unix:/run/gunicorn.sock \
 django_auth.wsgi:application

[Install]
WantedBy=multi-user.target

เสร็จแล้วให้แจ้ง Ubuntu ว่าเราจะใช้งาน Gunicorn ด้วย 3 คำสั่งนี้

sudo systemctl daemon-reload
sudo systemctl start gunicorn
sudo systemctl enable gunicorn

จะได้หน้าจอ Terminal ประมาณนี้

❯ sudo systemctl daemon-reload
❯ sudo systemctl start gunicorn
❯ sudo systemctl enable gunicorn
Created symlink /etc/systemd/system/multi-user.target.wants/gunicorn.service → /etc/systemd/system/gunicorn.service.

เสร็จแล้วลองเช็คว่ามี Error มั๊ย ด้วยคำสั่ง

sudo systemctl status gunicorn

ถ้ามี Error ให้ลองใช้คำสั่ง

sudo journalctl -u gunicorn

โดยปกติ Error มักเกิดจากมันหา gunicorn ไม่พบ ดังนั้นวิธีแก้เบื้องต้นคือต้องระบุ Path-to-gunicorn ในไฟล์ gunicorn.service ให้ถูกต้อง ซึ่งในกรณีของเรา เราจะเก็บไว้ใน env/bin

Restart Gunicorn

ทุกครั้งที่มีการแก้ไขไฟล์ ดังกล่าวข้างต้น ให้รีสตาร์ท gunicorn ด้วยคำสั่ง

sudo systemctl daemon-reload
sudo systemctl restart gunicorn

Nginx

เนื่องจาก Nginx ไม่ได้ใช้งาน Package บน Django จึงสามารถติดตั้งบน Ubuntu ได้เลยครับ ไม่ต้องติดตั้งใน Virtual Environment

sudo apt-get install nginx -y

sites-available

ให้สร้างไฟล์ <project-name> ในโฟลเดอร์ /etc/nginx/sites-available/

ด้วยคำสั่ง

sudo nano /etc/nginx/sites-available/<project-name>

ในที่นี้จะเป็น django-auth

sudo nano /etc/nginx/sites-available/django-auth

คัดลอกโค้ดนี้เข้าไปวางครับ

server {
    listen 80;
    server_name server_domain_or_IP;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/<your-user>/<your-project>;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
}

ให้สังเกตว่า proxy_pass ตรงบรรทัดสุดท้าย คือ url ที่เรากำหนดในไฟล์ gunicorn.socket

[Socket]
ListenStream=/run/gunicorn.sock

เมื่อแก้ไขไฟล์ให้ตรงกับข้อมูลของเรา จะได้โค้ดใหม่ดังนี้

server {
    listen 80;
    server_name staging-auth-api.example.com;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/django/django-auth;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
}

sites-enabled

ให้คัดลอกลิงค์จาก sites-available ไปที่ sites-enabled

sudo ln -s /etc/nginx/sites-available/django-auth /etc/nginx/sites-enabled/

ถ้าเข้าไปดูในโฟลเดอร์ /etc/nginx/sites-enabled/ จะพบไฟล์ django-auth และมีรายละเอียดข้างในเหมือนกัน

ทดสอบ nginx

ให้ทดสอบว่าโค้ด ที่เราเขียนใน Nginx ถูกต้องมั๊ย

sudo nginx -t

เปิดพอร์ตให้ nginx

เราจะเปิดพอร์ตบน Ubuntu ให้ Nginx ด้วยคำสั่ง

sudo ufw allow 'Nginx Full'

เสร็จแล้วครับ ตอนนี้เราได้ Deploy Django บน AWS EC2 และ Nginx เรียบร้อยแล้ว


run คำสั่ง Django 3 คำสั่ง

ตอนนี้เราสามารถรันคำสั่ง Django ทั้ง 3 คำสั่งสำคัญได้แล้ว เพื่อทดสอบว่ามันสามารถทำงานบน Production Environment และไม่มี Error

python manage.py makemigrations
python manage.py migrate
python manage.py runserver

ถ้าหน้าจอ Terminal แสดงผลแบบนี้ แสดงว่าผ่าน ไม่มี Error ใดๆ

❯ python manage.py makemigrations
/home/django/django-auth
False
Migrations for 'users':
  users/migrations/0001_initial.py
    - Create model UserAccount

ลองสั่ง migrate

❯ python manage.py migrate
/home/django/django-auth
False
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, django_ses, sessions, social_django, users
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0001_initial... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying users.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying django_ses.0001_initial... OK
  Applying sessions.0001_initial... OK
  Applying social_django.0001_initial... OK
  Applying social_django.0002_add_related_name... OK
  Applying social_django.0003_alter_email_max_length... OK
  Applying social_django.0004_auto_20160423_0400... OK
  Applying social_django.0005_auto_20160727_2333... OK
  Applying social_django.0006_partial... OK
  Applying social_django.0007_code_timestamp... OK
  Applying social_django.0008_partial_timestamp... OK
  Applying social_django.0009_auto_20191118_0520... OK
  Applying social_django.0010_uid_db_index... OK
  Applying social_django.0011_alter_id_fields... OK
  Applying social_django.0012_usersocialauth_extra_data_new... OK
  Applying social_django.0013_migrate_extra_data... OK
  Applying social_django.0014_remove_usersocialauth_extra_data... OK
  Applying social_django.0015_rename_extra_data_new_usersocialauth_extra_data... OK

ขั้นตอน migrate ผ่านทั้งหมดครับ ขั้นตอนที่ 3 สั่ง runserver ลองดูครับ

❯ python manage.py runserver
/home/django/django-auth
False
/home/django/django-auth
False
Performing system checks...

System check identified no issues (0 silenced).
December 12, 2023 - 13:03:27
Django version 5.0, using settings 'django_auth.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

เนื่องจาก Django App ของเราทำงานเป็น Back-End ดังนั้นมันยังไม่มี root url หรือ Home page (http://yourdomain.com)ให้เข้าไปที่ http://yourdomain.com/admin นะครับ เพื่อทดสอบ ตามรูปด้านล่าง

แบบนี้ถือว่าผ่านครับ แต่ให้สังเกตว่ามี 2 จุดที่ยังไม่เรียบร้อย

  • ไฟล์ css ยังไม่ทำงาน

  • เว็บไซต์ของเราขึ้น Not Secure

django auth api no css

Collectstatic

เราจะไล่เก็บไฟล์ CSS และไฟล์ JS ทั้งหมด ไปเก็บไว้ที่ Cloudianry CDN เราเรียกกระบวนการนี้ว่า Collectstatic

ให้ไปที่โฟลเดอร์ราก (มีไฟล์ manage.py) ตรวจดูให้แน่ใจว่า Virtual Environment Active อยู่ แล้วใช้คำสั่ง

python manage.py collectstatic

นั่งรอซักครู่ไฟล์ static (css, js) กำลัง Sync ไปที่ Cloudinary

เมื่อเสร็จแล้วไปที่ Cloudinary -> Media Library -> Folders

เราจะเห็นโฟลเดอร์ static อยู่ตรงนั้น

เมื่อคลิ๊กเข้าไปจะพบ 3 โฟลเดอร์ 1 ไฟล์

ลอง รีเฟรซ หน้าจอดูครับ ตอนนี้ไฟล์ CSS ทำงานแล้วครับ ถ้าตรวจสอบดู ไฟล์ CSS จะถูกเก็บไว้ที่ Cloudinary CDN -> https://res.cloudinary.com/xxxxx/raw/upload/v1/static/


enable HTTPS ด้วย Certbot

เหลือขั้นตอนสุดท้ายแล้วครับ คือการเพิ่มความปลอดภัยให้กับเว็บไซต์ของเราด้วย SSL หรือที่เราคุ้นเคยในรูป HTTPS โปรโตคอล

ก่อนอื่นให้ติดตั้ง certbot ก่อนครับ

sudo apt-get install certbot python3-certbot-nginx -y

เสร็จแล้วก็รันคำสั่งด้านล่างนี้ได้เลย

sudo certbot --nginx

จะมีคำถาม 3 คำถาม ดังนี้ครับ

  1. Email Address -> ให้ใส่ที่อยู่อีเมล

  2. Consent for Terms of Service -> ให้ตอบ Y

  3. Subscribe email -> ตอบ Y หรือ N ก็ได้ครับ

❯ sudo certbot --nginx
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
 (Enter 'c' to cancel): admin@example.com

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf. You must
agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: N
Account registered.

Which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: staging-auth-api.example.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel):
Requesting a certificate for staging-auth-api.example.com

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/staging-auth-api.example.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/staging-auth-api.example.com/privkey.pem
This certificate expires on 2024-03-11.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

Deploying certificate
Successfully deployed certificate for staging-auth-api.example.com to /etc/nginx/sites-enabled/django-auth
Congratulations! You have successfully enabled HTTPS on https://staging-auth-api.example.com

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

เมื่อกลับไปเว็บไซต์ของเรา ตอนนี้เราจะเห็นเครื่องหมายกุญแจ แสดงว่าเว็บไซต์มี SSL หรือ HTTPS เรียบร้อยแล้วครับ

สรุปการ Deploy Django บน EC2 Nginx

บทความนี้จะเน้นเรื่องการ Deployment Django Project เป็นหลัก โดยจะพูดถึงการตั้งค่าในส่วนต่างๆ เพื่อให้ Django App สามารถทำงานได้ แต่จะยังไม่พูดถึงว่ามันทำงานอย่างไร และทำอะไรได้บ้าง ซึ่งจะได้พูดถึงในบทความต่อไป

วิธีแก้ Certbot fail

ปัญหา: เมื่อขอ SSL ผ่านคำสั่ง sudo certbot --nginx แล้วไม่สำเร็จ ปัญหาน่าจะมาจาก DNS เช่น เราเพิ่งผูก Domain name กับ IP ซึ่งมันต้องใช้เวลาในการกระจายข้อมูลนี้ไปสู่โลกอินเตอร์เน็ต ดังนั้นตราบใดที่เลข IP ยังไม่ได้ชี้ไปที่ Domain name ของเรา ตัว Certbot ก็จะหา Domain name ของเราไม่เจอนั่นเอง การขอ Certificate สำหรับ SSL จึงไม่สำเร็จ

วิธีแก้: ให้เราเช็ค Domain name ด้วยเครื่องมือเหล่านี้ครับ

ถ้าขึ้นข้อความแบบนี้ ถือว่าผ่านครับ

All OK!
No issues were found with <your-domain>.com. If you are having problems with creating an SSL certificate, please visit the Let's Encrypt Community forums and post a question there.

เอกสารอ้างอิง