Mở đầu

Tập này đưa ứng dụng quản lý chi tiêu từ runserver sang bộ đôi chuẩn production: Gunicorn chạy ứng dụng Python, Nginx đứng trước làm reverse proxy. Kết quả: ứng dụng chạy ổn định như dịch vụ hệ thống, tự khởi động lại khi lỗi và phục vụ đúng qua tên miền. Nối tiếp tập 2, tập này dùng cấu hình production đã tách theo biến môi trường để chạy thật.

Vì sao cần cả hai?

runserver chỉ dành cho phát triển: một tiến trình, không chịu tải đồng thời và không tự phục hồi. Gunicorn là máy chủ WSGI chạy ứng dụng Python, phân phối request cho nhiều worker. Nginx đứng trước lo phần việc Gunicorn làm kém: giữ kết nối chậm, phục vụ file tĩnh, gắn chứng chỉ TLS.

1. Cài đặt và chạy thử Gunicorn

Cài Gunicorn và chạy thử ứng dụng quản lý chi tiêu:

pip install gunicorn
gunicorn config.wsgi:application --bind 127.0.0.1:8000 --workers 3

Quy tắc khởi điểm là (2 × số CPU) + 1, nhưng máy chủ nhỏ một đến hai CPU chỉ cần ba worker. Ghi cấu hình vào gunicorn.conf.py để mọi lần chạy đều giống nhau:

bind = "127.0.0.1:8000"
workers = 3
timeout = 30
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"

timeout hủy request chậm như xuất báo cáo thay vì để treo mãi. Kiểm tra bằng curl http://127.0.0.1:8000/ trước khi cấu hình Nginx.

2. Chạy Gunicorn bằng systemd

Máy chủ thật cần Gunicorn tự chạy khi khởi động và tự khởi động lại khi crash. Tạo service systemd chạy dưới người dùng không có quyền root:

[Unit]
Description=Quan ly chi tieu Django
After=network.target
[Service]
User=deploy
WorkingDirectory=/srv/expense
EnvironmentFile=/srv/expense/.env
ExecStart=/srv/expense/venv/bin/gunicorn config.wsgi:application
Restart=on-failure
[Install]
WantedBy=multi-user.target

Kích hoạt bằng systemctl enable --now expense rồi kiểm tra systemctl status expense. File .env phải thuộc về deploy và có quyền đọc hạn chế, vì nó chứa SECRET_KEY và mật khẩu database từ tập 2. Gunicorn chỉ lắng nghe trên localhost.

3. Nginx làm reverse proxy

Cấu hình Nginx chuyển request từ tên miền vào Gunicorn và chuyển đúng header gốc:

server {
listen 80;
server_name chitieu.example.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
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 $scheme;
client_max_body_size 10m;
}
location /static/ {
alias /srv/expense/staticfiles/;
}
}

Hai header trên cho Django biết IP thật và request gốc có dùng HTTPS hay không. Bật kèm trong settings production:

SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
ALLOWED_HOSTS = ["chitieu.example.com"]

Chỉ tin header này khi Nginx là lớp duy nhất nhận request từ Internet. Sau khi sửa cấu hình, chạy nginx -t rồi systemctl reload nginx.

4. Kiểm tra đầu cuối

Kiểm tra từ trong ra ngoài: Gunicorn (systemctl status expense), ứng dụng (curl -I http://127.0.0.1:8000/), rồi mới qua Nginx (curl -I https://chitieu.example.com/). Đọc log Gunicorn khi gặp lỗi 502, log Nginx khi request không tới được Gunicorn. Thử reboot một lần để chắc chắn mọi service tự lên.

Áp dụng vào dự án của bạn

Với ứng dụng quản lý chi tiêu trên VPS nhỏ, ba worker, timeout 30 giây và Nginx phục vụ static như trên là đủ cho giai đoạn đầu. Khi người dùng tăng, tăng worker theo CPU trước — tập 19 sẽ nói khi nào cần mở rộng. Ghi tên miền, cổng Gunicorn và đường dẫn log vào runbook để xử lý sự cố nhanh.

Bài viết liên quan

Câu hỏi thường gặp

Bao nhiêu worker là đủ cho máy chủ nhỏ?

Bắt đầu với (2 × số CPU) + 1 và theo dõi CPU cùng thời gian phản hồi.

Vì sao vẫn cần Nginx khi Gunicorn đã chạy được?

Gunicorn giỏi chạy code Python nhưng kém ở việc giữ kết nối chậm, phục vụ file tĩnh và quản lý TLS. Nginx nhận phần việc đó để worker xử lý logic.

Gặp lỗi 502 Bad Gateway thì sửa từ đâu?

Kiểm tra Gunicorn trước: service có đang chạy không, cổng 8000 có lắng nghe không, log lỗi nói gì. Nếu Gunicorn khỏe mà vẫn 502, kiểm tra proxy_pass và chạy nginx -t.

File tĩnh do ai phục vụ?

Nginx phục vụ trực tiếp qua khối location /static/, nhanh hơn nhiều so với Gunicorn đọc file từ đĩa mỗi request. Cách tổ chức static và media sẽ có ở tập 5.

Nguồn tham khảo

  • Tài liệu Gunicorn: docs.gunicorn.org
  • Tài liệu Nginx, module ngx_http_proxy_module: nginx.org
  • Tài liệu Django, hướng dẫn triển khai: docs.djangoproject.com