Bunchee
Back to Blog

Deploy Django บน AWS EC2 Nginx (ตอน 1)

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

Deploy Django บน AWS EC2 Nginx (ตอน 1)

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

สิ่งที่ต้องเตรียมก่อน Deploy Django App

ก่อนจะไปพูดถึงการ Deploy จะขออธิบายถึงสิ่งที่ต้องเตรียมก่อนการ Deploy นะครับ สำหรับคนที่ทำสิ่งเหล่านี้เป็นแล้วสามารถ กดข้าม ได้ครับ


เปิดพอร์ต 8000 บน AWS EC2

ดูวิธีสร้าง AWS EC2 ได้ที่ ลิงค์ นี้ครับ

เมื่อสร้าง EC2 เสร็จแล้ว ให้เปิดพอร์ต 8000 ครับ เพราะ Django ใช้พอร์ต 8000

ปกติการสร้าง EC2 Instance แต่ละครั้ง ระบบจะสร้าง Security group ให้เรา โดยใช้ชื่อว่า launch-wizard-x ถ้าสร้างครั้งแรกก็จะได้ชื่อ launch-wizard-1 และถ้าสร้าง EC2 Instance ครั้งที่ 2 ก็จะได้ชื่อ launch-wizard-2 อย่างนี้ไปเรื่อยๆ ถ้าเราอยากเปิดพอร์ตเพิ่มเติมก็ให้คลิ๊กลิงค์ตรง sg-xxxxxxxxxxx

Deploy Django EC2 Nginx 1

จะได้หน้าจอนี้ ให้คลิ๊กตรง Edit inbound rules

Deploy Django EC2 Nginx 2

ตามด้วย Add rule (ถ้าพอร์ตที่เราต้องการยังไม่ได้เปิดไว้ ในที่นี้คือพอร์ต 8000)

Deploy Django EC2 Nginx 3

ตรงคอลัมน์ Type เลือก Custom TCP ตรงคอลัมน์ Port range ใส่เลข 8000 ตรงคอลัมน์ Source เลือก Anywhere เสร็จแล้วคลิ๊กปุ่ม Save rules

Deploy Django EC2 Nginx 4


ติดตั้ง Oh My Zsh

Oh My Zsh

แม้จะสามารถทำงานบน VSCode ได้ แต่บางครั้งเราอาจอยู่นอกสถานที่ และเราจำเป็นต้องแก้โค้ดผ่าน EC2 Web base terminal เราอาจยอมเสียเวลานิดหน่อย เพื่อติดตั้ง Zsh และ Oh My Zsh บน EC2

เริ่มด้วยการติดตั้ง Zsh และ Oh My Zsh

sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install zsh -y
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

ข้อระวัง: สำหรับ new user ที่ไม่ใช่ root user

สำหรับท่านที่ใช้ Ubuntu บน Linode หรือบน Hetzner และต้องการติดตั้งบน new user ที่ไม่ใช่ root user การรันคำสั่ง sh -c "$(curl -fsSL https://...)" จะทำไม่ได้นะครับ ติดปัญหา Permission ให้แก้ดังนี้

1. ดาวน์โหลดไฟล์ Oh My Zsh's install.sh

wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh

2. แก้ไขไฟล์ install.sh โดยใช้คำสั่ง

sudo nano install.sh

เปลี่ยนโค้ดนี้

if [ -n "$ZDOTDIR" ] && [ "$ZDOTDIR" != "$HOME" ]; then
  ZSH="${ZSH:-$ZDOTDIR/ohmyzsh}"
fi
ZSH="${ZSH:-$HOME/.oh-my-zsh}"

เป็น

ZSH=~/.oh-my-zsh

หรือใช้ลิงค์นี้ก็ได้ครับ ผมแก้โค้ดให้เรียบร้อยแล้ว

wget https://raw.githubusercontent.com/ManotLuijiu/ohmyzsh/master/tools/install.sh

3. แปลงไฟล์ install.sh ให้สามารถ execute ได้

chmod +x install.sh

4. เสร็จแล้วรัน install.sh ได้เลยครับ

./install.sh

Ubuntu จะถามเราว่าต้องการเปลี่ยน shell จาก bash ไปเป็น zsh มั๊ย

Looking for an existing zsh config...
Using the Oh My Zsh template file and adding it to /home/django/.zshrc.

Time to change your default shell to zsh:
Do you want to change your default shell to zsh? [Y/n]

ให้ตอบ Y หรือกดปุ่ม Enter ก็ได้ครับ

ข้อสังเกต สำหรับคำตอบที่เป็นค่า Default ทาง Ubuntu จะใส่มาเป็นอักษรตัวใหญ่ เช่น [Y/n] ค่า Default คือ Y หรือ Yes เราสามารถเคาะปุ่ม Enter ผ่านได้เลยครับ ไม่จำเป็นต้องพิมพ์คำว่า Y

Powerline fonts

ติดตั้ง fonts เผื่อๆไว้ครับ เผื่อใครชอบ ถ้าไม่ชอบ ก็ผ่านคำสั่งนี้ไปได้เลยครับ

sudo apt-get install fonts-powerline -y

PowerLevel10K

ต่อไปติดตั้ง Theme ของ Oh My Zsh ชื่อว่า PowerLevel10K

git clone https://github.com/romkatv/powerlevel10k.git $ZSH_CUSTOM/themes/powerlevel10k

zsh-syntax-highlighting

มี Theme แล้ว ก็เพิ่ม Plugin ช่วยงานอีกซัก 2 ตัวครับ ตัวแรกช่วยสร้างสีสันให้ Terminal

git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting

zsh-autosuggestions

ตัวที่สอง มันจะช่วยจำคำสั่งต่างๆที่เราเคยใช้

git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions

Update zshrc

หลังจากติดตั้ง Theme และ Plugin เสร็จแล้ว ก็ต้องแจ้งให้ Zsh ทราบโดยเปิดไฟล์ .zshrc ขึ้นมาครับ ด้วยคำสั่ง

sudo nano ~/.zshrc

ให้คัดลอกโค้ด 2 บรรทัดนี้ไปทับของเดิมในไฟล์ .zshrc ครับ

ZSH_THEME="powerlevel10k/powerlevel10k"
plugins=( git zsh-syntax-highlighting zsh-autosuggestions )

สั่งบันทึกไฟล์ด้วย Ctrl + X แล้วตอบ Y

สั่ง รีสตาร์ท ไฟล์ .zshrc ใหม่ ด้วยคำสั่ง

source ~/.zshrc

Ubuntu จะพาเราเข้าหน้าจอ p10k configure ให้ตอบคำถามไปตามที่เราต้องการได้เลยครับ สำหรับคำตอบสุดท้ายให้ตอบ ข้อ 2 quiet เสร็จแล้วครับ สำหรับการติดตั้ง Oh My Zsh


ขอ Fixed IP (EC2 Elastic IP)

ก่อนจะ connect เข้าใช้ EC2 Instance อย่าลืมขอเลข Fixed IP ให้เรียบร้อยนะครับ

สร้าง User และ Python Environment

connect ไปที่ EC2 Instance ที่เราเพิ่งสร้างเสร็จ update Ubuntu และสร้าง User ใหม่ ให้เรียบร้อยครับ

sudo adduser <your-username>
sudo usermod -aG sudo <your-username>
su <your-username>
cd /home/<your-username>

ในที่นี้ขอใช้ User ใหม่ว่า django จะได้

sudo adduser django
sudo usermod -aG sudo django
su django
cd /home/django

เมื่อเราอยู่ในโฟลเดอร์ของ User ใหม่ เรียบร้อยแล้ว ให้สร้างโฟลเดอร์งานชื่อว่า django-auth และเริ่มต้นด้วยการสร้าง Virtual Environment สำหรับ Python ครับ

sudo apt-get install python3-venv
mkdir django-auth && cd $_
python3 -m venv env

ถ้าเราใช้คำสั่ง ls เราจะเห็นโฟลเดอร์ env

ให้ activate virtual env ด้วยคำสั่ง

source env/bin/activate

สำหรับคนที่ใช้ conda activate virtual-name ก็สามารถใช้คำสั่ง source env/bin/activate ซ้ำได้นะครับ ไม่มี Error ใดๆ

หน้าจอ Terminal บน AWS EC2 Instance จะได้ออกมาประมาณนี้

django@ip-xxx-xx-xx-xxx:~$ mkdir django-auth && cd $_
django@ip-xxx-xx-xx-xxx:~/django-auth$ ls
django@ip-xxx-xx-xx-xxx:~/django-auth$ python3 -m venv env
django@ip-xxx-xx-xx-xxx:~/django-auth$ ls
env
django@ip-xxx-xx-xx-xxx:~/django-auth$ source env/bin/activate
(env) django@ip-xxx-xx-xx-xxx:~/django-auth$

ในกรณีที่ต้องการออกจาก Virtual Environment ให้พิมพ์คำสั่ง

deactivate

สร้าง Django Project

เริ่มติดตั้ง django ใน Virtual environment

pip install django

ตรงนี้สำคัญนะครับ ให้สังเกตว่าตั้งแต่ขั้นตอนนี้ เราทำงานบน Virtual Environment แล้วนะครับ ดังนั้นถ้าเราสั่ง python compile นอก Virtual Environment เราจะพบ Error นะครับ เราต้องใช้คำสั่ง python ใน Virtual Environment เท่านั้นครับ

สร้าง Django App ระบบหลังบ้าน

เราจะสร้าง Django App ที่ใช้ดูแลระบบหลังบ้าน เช่น ระบบ Auth โดยใช้ Django REST framework ทำงานร่วมกับ Djoser ให้ติดตั้ง Packages ดังนี้

pip install djangorestframework djoser django-cors-headers python-dotenv

เมื่อใช้คำสั่ง pip list จะได้ Python packages ทั้งหมดดังนี้ครับ

(env) django@ip-xxx-xx-xx-xxx:~/django-auth$ pip list
Package                       Version
----------------------------- ------------
asgiref                       3.7.2
certifi                       2023.11.17
cffi                          1.16.0
charset-normalizer            3.3.2
cryptography                  41.0.7
defusedxml                    0.8.0rc2
Django                        5.0
django-cors-headers           4.3.1
django-templated-mail         1.1.1
djangorestframework           3.14.0
djangorestframework-simplejwt 5.3.1
djoser                        2.2.2
idna                          3.6
oauthlib                      3.2.2
pip                           22.0.2
pycparser                     2.21
PyJWT                         2.8.0
python-dotenv                 1.0.0
python3-openid                3.2.0
pytz                          2023.3.post1
requests                      2.31.0
requests-oauthlib             1.3.1
setuptools                    59.6.0
social-auth-app-django        5.4.0
social-auth-core              4.5.1
sqlparse                      0.4.4
typing_extensions             4.9.0
urllib3                       2.1.0
(env) django@ip-xxx-xx-xx-xxx:~/django-auth$

ให้สังเกตว่า ตอนนี้เรามี social-auth-core ติดตั้งมาพร้อมแล้วจากคำสั่งข้างต้น

เสร็จแล้วเริ่มด้วยการสร้างโปรแกรมแม่ ที่ชื่อว่า django_auth ด้วยคำสั่ง

django-admin startproject django_auth .

จะได้

(env) django@ip-xxx-xx-xx-xxx:~/django-auth$ django-admin startproject django_auth .
(env) django@ip-xxx-xx-xx-xxx:~/django-auth$ ls
django_auth  env  manage.py
(env) django@ip-xxx-xx-xx-xxx:~/django-auth$ cd django_auth
(env) django@ip-xxx-xx-xx-xxx:~/django-auth/django_auth$ ls
__init__.py  asgi.py  settings.py  urls.py  wsgi.py
(env) django@ip-xxx-xx-xx-xxx:~/django-auth/django_auth$

ต่อไปสร้างโปรแกรมลูก ที่จะมาดูแลระบบ Users ก่อนอื่นให้พิมพ์คำสั่ง pwd และ ls เพื่อดูว่าเราอยู่ในโฟลเดอร์รากหรือเปล่า

(env) django@ip-xxx-xx-xx-xxx:~/django-auth$ pwd
/home/django/django-auth
(env) django@ip-xxx-xx-xx-xxx:~/django-auth$ ls
django_auth  env  manage.py
(env) django@ip-xxx-xx-xx-xxx:~/django-auth$

โดยโฟลเดอร์รากจะต้องมองเห็นไฟล์ manage.py นะครับ เสร็จแล้วสร้างโปรแกรมลูกที่ชื่อว่า users ด้วยคำสั่ง

python manage.py startapp users

Terminal จะมีหน้าตาแบบนี้

(env) django@ip-xxx-xx-xx-xxx:~/django-auth$ python manage.py startapp users
(env) django@ip-xxx-xx-xx-xxx:~/django-auth$ ls
django_auth  env  manage.py  users
(env) django@ip-xxx-xx-xx-xxx:~/django-auth$

settings.py

ให้แก้ไขไฟล์ settings.py ที่อยู่ในโฟลเดอร์ django_auth

sudo nano django_auth/settings.py

หรือถ้าจะให้สะดวกกว่านั้นให้เขียนโค้ดผ่าน VSCode ครับ โดยตั้งค่าการเชื่อมต่อ VSCode และ EC2 ตามลิงค์นี้ครับ

ก่อนจะย้ายไปทำงานบน VSCode ให้รันคำสั่ง

pip freeze > requirements.txt

แนะนำให้อ่าน บทความนี้ เพิ่มเติมครับ

หลังจากเชื่อม EC2 ได้แล้ว จะอยู่ที่หน้าจอ VSCode Terminal ให้เลือก Virtual Environment ที่เราสร้างไว้บน Ubuntu EC2 ดังนี้

ถ้าเปิดใช้ VSCode ครั้งแรกอาจเจอคำถามเรื่องความปลอดภัย ให้ติ๊กถูก และตอบ Yes

เมื่อเข้าไปถึงให้เปิด Terminal ขึ้นมาด้วยการกดปุ่ม Ctrl + ` พร้อมกัน แล้วให้ดูตรงมุมขวาล่างสุด ให้คลิ๊กตรง เวอร์ชั่นของ Python

เลือก + Enter interpreter path...

ต่อไปให้เลือก Find...

ไปที่โฟลเดอร์ /home/django/django-auth/ ตามด้วยคลิ๊กตรงโฟลเดอร์ env

สำหรับคนที่สร้าง Username และ ชื่อ Project ต่างออกไป ให้หาโฟลเดอร์ตามนี้ครับ

/home/<your-username>/<your-porject-name>/

ขั้นตอนสุดท้ายเลือก python3

ตอนนี้ VSCode จะใช้ Virtual Environment ตัวเดียวกับ EC2 จะไม่เกิด Error ใดๆครับ

ให้คัดลอก settings.py จากไฟล์นี้ มาวางที่ django_auth/settings.py


.env.local

เมื่อถึงขั้นตอนนี้ให้สร้างไฟล์ .env.local ที่โฟลเดอร์ราก ด้วยคำสั่ง

touch .env.local

คัดลอกเนื้อหาในไฟล์ .env.local บน Gist มาวางในไฟล์ .env.local บน VSCodeได้เลยครับ


แก้ไข settings.py ตามนี้

Origin และ Domain Name

ให้แก้ไขโค้ดข้างล่างนี้ โดยการเติมชื่อโดเมนของท่านเข้าไป เช่น example.com จะได้

CSRF_TRUSTED_ORIGINS = ["https://*.example.com"] # ตรงนี้ต้องแก้
CSRF_COOKIE_DOMAIN = ".example.com" # ตรงนี้ต้องแก้
CORS_ALLOWED_ORIGINS = [
    "https://example.com", # ตรงนี้ต้องแก้
    "https://www.example.com", # ตรงนี้ต้องแก้
    "https://django-auth.example.com", # เช่น event.your-domain.com
    "http://localhost:3000",
    "http://127.0.0.1:3000",
]
CORS_ORIGIN_WHITELIST = [
    "https://example.com", # ตรงนี้ต้องแก้
    "https://www.example.com", # ตรงนี้ต้องแก้
    "https://django-auth.example.com", # เช่น event.your-domain.com
    "http://localhost:3000",
    "http://127.0.0.1:3000",
]

เชื่อมต่อฐานข้อมูลบน AWS RDS

ให้ติดตั้งเพิ่มอีก 1 package คือ psycopg2-binary

pip install psycopg2-binary
pip freeze > requirements.txt

เนื่องจากโดยปกติ Security Group ของ AWS RDS จะไม่เปิดให้เชื่อมต่อผ่าน Internet สามารถอ่านวิธีแก้ปัญหา ได้จาก

รายละเอียดการสร้าง AWS RDS และเทคนิคในการเชื่อม RDS ผ่าน Internet

เมื่อสร้างฐานข้อมูลเสร็จแล้ว เราจะได้หน้าจอนี้ครับ

คลิ๊กตรง View connection details จะได้หน้าจอแสดงรายละเอียด Credential

ตอนนี้เราขาดเพียงชื่อ ฐานข้อมูล ให้ไปที่แท็บ Configuration มองหา DB name

เมื่อได้ข้อมูลมาครบทั้ง 4 รายการแล้ว ก็ไปที่ไฟล์ settings.py มองหา DATABASES แล้วแก้ตามนี้ครับ

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "postgres", # ตรงนี้ต้องแก้ ตามเลข 4
        "USER": "postgres", # ตรงนี้ต้องแก้ ตามเลข 1
        "PASSWORD": "your-password", # ตรงนี้ต้องแก้ ตามเลข 2
        "HOST": "<DB instance ID>.xxxxxxxxx.ap-southeast-1.rds.amazonaws.com", # ตรงนี้ต้องแก้ ตามเลข 3
        "PORT": "5432",
    }
}

การเชื่อมต่อฐานข้อมูลระหว่าง Django และ AWS RDS PostgreSQL ก็เสร็จเท่านี้ครับ


Cloudinary

ถ้าต้องการใช้งาน Cloudinary ให้ติดตั้งเพิ่มอีก 1 Package ครับ

pip install django-cloudinary-storage
pip freeze > requirements.txt

วิธีหา CLOUD_NAME, API_KEY และ API_SECRET

ไปที่ cloudinary.com เลือก Programmable Media -> Dashboard จะได้หน้าจอตามรูปด้านล่างนี้ เราสามารถคัดลอก Cloud Name, API Key และ API Secret ได้เลย แล้วเอาค่าที่ได้ทั้งหมดไปใส่ในไฟล์ .env.local

CLOUDINARY_CLOUD_NAME="" # ตรงนี้ต้องแก้ ตามเลข 3
CLOUDINARY_API_KEY="" # ตรงนี้ต้องแก้ ตามเลข 4
CLOUDINARY_API_SECRET="" # ตรงนี้ต้องแก้ ตามเลข 5

กำหนด Credential สำหรับเชื่อมต่อ Cloudinary

ดูให้แน่ใจว่าในไฟล์ django_auth/settings.py ตรง CLOUDINARY_STORAGE ได้กำหนดค่าไว้ตามนี้

CLOUDINARY_STORAGE = {
    "CLOUD_NAME": getenv("CLOUDINARY_CLOUD_NAME"),
    "API_KEY": getenv("CLOUDINARY_API_KEY"),
    "API_SECRET": getenv("CLOUDINARY_API_SECRET")
}

Debug

Pain point: Django App มองไม่เห็นไฟล์ css

ใครที่มีปัญหา Django App หาไฟล์ css ไม่เจอ ให้แก้ดังนี้

Solution: วิธีระบุ STATIC_URL

หลังจากสั่ง python manage.py collecstatic แล้ว ไฟล์ Statics ทั้งหมดจะไปอยู่ที่โฟลเดอร์ static บน Cloudinary เราสามารถหา Cloudinary URL ได้จากรูปด้านล่าง

วาง URL บน Web Browser ก็จะได้ STATIC_URL บน Cloudinary จะมีรูปแบบตามนี้

https://res.cloudinary.com/<your-account>/raw/upload/vxxxxxxxx/static/admin/css/forms.c731915b3b1e.css

ให้ไปที่ไฟล์ /etc/nginx/sites-available/your-project ด้วยคำสั่ง

sudo nano /etc/nginx/sites-available/myproject

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

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

ตรง static ให้แก้เป็น

location /static {
        rewrite ^/static(.*)$ https://res.cloudinary.com/<your-account>/raw/upload/vxxxxxxxx/static$1 redirect;
    }

AWS S3 Storage

บางคนอาจอยากอัพโหลด Static files ไปไว้บน AWS S3 และ Cloudfront แทนที่จะวางไว้บน Cloudinary ซึ่งจะอธิบายในบทความถัดไป เบื้องต้นนี้ให้ลง Package ไว้ก่อนครับ

pip install django-storages
pip freeze > requirements.txt

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