Trong hướng dẫn này, bạn sẽ triển khai Flask trên Ubuntu 22.04. Phần lớn nội dung trong hướng dẫn sẽ tập trung vào cách cài đặt Gunicorn, một application server, cũng như cách khởi chạy ứng dụng và cấu hình Nginx để hoạt động như một front-end reverse proxy.

Điều kiện tiên quyết
Trước khi thực hiện theo hướng dẫn này, bạn cần chuẩn bị:
- Một máy chủ đã cài đặt Ubuntu 22.04, cùng với một người dùng không phải root có quyền sudo.
- Một tên miền đã được cấu hình trỏ về máy chủ của bạn. Bạn có thể mua tên miền từ Namecheap hoặc sử dụng tên miền miễn phí từ Freenom. Hãy đảm bảo bạn đã tạo các bản ghi DNS sau:
- Bản ghi A với
your_domaintrỏ về địa chỉ IP công khai của máy chủ. - Bản ghi A với
www.your_domaincũng trỏ về địa chỉ IP công khai của máy chủ.
- Bản ghi A với
- Có hiểu biết cơ bản về WSGI specification, vì Gunicorn sẽ sử dụng chuẩn này để giao tiếp với ứng dụng Flask của bạn. Hướng dẫn này sẽ trình bày kỹ hơn về WSGI ở phần sau.
Bước 1: Cài đặt các thành phần từ kho phần mềm của Ubuntu
Bước đầu tiên là cài đặt tất cả các thành phần cần thiết từ Ubuntu repositories. Điều này bao gồm pip, trình quản lý gói Python, để bạn có thể cài đặt và quản lý các thư viện Python cần thiết. Bạn cũng sẽ cài đặt thêm các development files dành cho Python cần thiết để biên dịch một số thành phần của Gunicorn.
Trước tiên, hãy cập nhật danh sách gói cục bộ và cài đặt các gói cần thiết để xây dựng môi trường lập trình Python. Bao gồm python3-pip, cùng với một số công cụ và gói hỗ trợ khác nhằm thiết lập một môi trường phát triển vững chắc:
sudo apt update
sudo apt install python3-pip python3-dev build-essential libssl-dev libffi-dev python3-setuptools
Khi đã có các gói này, bước tiếp theo là tạo một môi trường ảo ****cho dự án của bạn.
Bước 2: Tạo môi trường ảo Python
Tiếp theo, bạn sẽ thiết lập một môi trường ảo để tách biệt ứng dụng Flask khỏi các tệp Python khác trên hệ thống của bạn.
Bắt đầu bằng cách cài đặt gói python3-venv, gói này sẽ cài đặt module venv:
sudo apt install python3-venv
Tiếp theo, hãy tạo một thư mục cha cho dự án Flask của bạn. Sau khi tạo xong, di chuyển vào thư mục đó bằng lệnh cd:
mkdir ~/myproject
cd ~/myproject
Tạo một môi trường ảo để lưu các yêu cầu (requirements) Python của dự án Flask bằng cách nhập lệnh:
python3 -m venv myprojectenv
Lệnh này sẽ cài đặt một bản sao cục bộ của Python và pip vào một thư mục có tên myprojectenv nằm trong thư mục dự án của bạn.
Trước khi cài đặt các ứng dụng bên trong môi trường ảo, bạn cần kích hoạt nó. Thực hiện việc này bằng cách nhập lệnh:
source myprojectenv/bin/activate
Dấu nhắc dòng lệnh của bạn sẽ thay đổi để cho biết rằng bạn đang làm việc bên trong môi trường ảo. Nó sẽ trông giống như sau:
(myprojectenv)user@host:~/myproject$
Bước 3: Thiết lập ứng dụng Flask
Bây giờ bạn đang ở trong môi trường ảo, bạn có thể cài đặt Flask và Gunicorn và bắt đầu thiết kế ứng dụng của mình.
Trước tiên, hãy cài đặt wheel bằng phiên bản pip cục bộ để đảm bảo rằng các gói của bạn có thể được cài đặt ngay cả khi chúng không có sẵn dưới dạng tệp wheel:
pip install wheel
Lưu ý
Bất kể bạn đang sử dụng phiên bản Python nào, khi môi trường ảo được kích hoạt, bạn nên sử dụng lệnh pip (không dùng pip3).
Tiếp theo, hãy cài đặt Flask và Gunicorn:
pip install gunicorn flask
Tạo một ứng dụng mẫu
Bây giờ bạn đã có Flask sẵn sàng, bạn có thể tạo một ứng dụng đơn giản. Flask là một microframework, tức là nó không bao gồm nhiều công cụ như các framework đầy đủ tính năng khác, và chủ yếu tồn tại dưới dạng một module mà bạn có thể nhập vào dự án để hỗ trợ khởi tạo ứng dụng web.
Mặc dù ứng dụng của bạn có thể phức tạp hơn, ở đây chúng ta sẽ tạo ứng dụng Flask trong một tệp duy nhất, có tên là myproject.py:
nano ~/myproject/myproject.py
Mã nguồn của ứng dụng sẽ nằm trong tệp này. Tệp sẽ import Flask và khởi tạo một đối tượng Flask. Bạn có thể sử dụng đối tượng này để định nghĩa các hàm sẽ được thực thi khi một route cụ thể được yêu cầu:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "<h1 style='color:blue'>Hello There!</h1>"
if __name__ == "__main__":
app.run(host='0.0.0.0')
Điều này về cơ bản định nghĩa nội dung sẽ được hiển thị khi tên miền gốc được truy cập. Sau khi hoàn tất, hãy lưu và đóng tệp lại.
Nếu bạn đã làm theo hướng dẫn thiết lập máy chủ ban đầu, thì có thể bạn đã bật tường lửa UFW. Để kiểm tra ứng dụng, bạn cần cho phép truy cập vào cổng 5000:
sudo ufw allow 5000
Giờ đây bạn có thể kiểm tra ứng dụng Flask của mình bằng cách nhập lệnh:
python myproject.py
Bạn sẽ thấy đầu ra giống như sau, bao gồm một cảnh báo hữu ích nhắc bạn không nên sử dụng cấu hình máy chủ này trong môi trường sản xuất:
Output
* Serving Flask app "myproject" (lazy loading)
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: off
* Running on <http://0.0.0.0:5000/> (Press CTRL+C to quit)
Truy cập địa chỉ IP của máy chủ của bạn, kèm theo :5000 trên trình duyệt web:
http://your_server_ip:5000
Bạn sẽ thấy nội dung hiển thị tương tự như sau:

Khi bạn hoàn tất, hãy nhấn CTRL-C trong cửa sổ terminal để dừng máy chủ phát triển Flask.
Tạo điểm khởi đầu WSGI
Tiếp theo, hãy tạo một tệp sẽ đóng vai trò là điểm khởi đầu cho ứng dụng của bạn. Tệp này sẽ hướng dẫn máy chủ Gunicorn cách tương tác với ứng dụng.
Đặt tên tệp là wsgi.py:
nano ~/myproject/wsgi.py
Trong tệp này, hãy import đối tượng Flask từ ứng dụng của chúng ta và sau đó chạy nó:
from myproject import app
if __name__ == "__main__":
app.run()
Lưu và đóng tập tin khi bạn hoàn tất.
Bước 4: Cấu hình Gunicorn
Ứng dụng của bạn hiện đã được viết xong với điểm khởi đầu được xác định. Giờ bạn có thể chuyển sang bước cấu hình Gunicorn.
Trước khi tiếp tục, hãy kiểm tra xem Gunicorn có thể chạy ứng dụng một cách chính xác hay không.
Bạn có thể làm điều này bằng cách truyền tên điểm khởi đầu của ứng dụng cho Gunicorn. Cấu trúc của entry point gồm tên của module (bỏ phần đuôi .py), cộng với tên của callable trong ứng dụng. Trong trường hợp này, đó là wsgi:app.
Bạn cũng cần chỉ định interface và port để lắng nghe bằng cách sử dụng đối số 0.0.0.0:5000, nhằm đảm bảo ứng dụng được khởi chạy trên một giao diện công khai:
cd ~/myproject
gunicorn --bind 0.0.0.0:5000 wsgi:app
Bạn sẽ thấy đầu ra tương tự như sau:
Output
[2020-05-20 14:13:00 +0000] [46419] [INFO] Starting gunicorn 20.0.4
[2020-05-20 14:13:00 +0000] [46419] [INFO] Listening at: <http://0.0.0.0:5000> (46419)
[2020-05-20 14:13:00 +0000] [46419] [INFO] Using worker: sync
[2020-05-20 14:13:00 +0000] [46421] [INFO] Booting worker with pid: 46421
Truy cập địa chỉ IP của máy chủ của bạn trên trình duyệt web, thêm :5000 vào cuối địa chỉ:
http://your_server_ip:5000
Bạn sẽ thấy đầu ra của ứng dụng như sau:

Khi bạn đã xác nhận rằng ứng dụng hoạt động đúng cách, hãy nhấn CTRL-C trong cửa sổ terminal để dừng lại.
Khi đã hoàn tất việc sử dụng môi trường ảo (virtual environment), bạn có thể hủy kích hoạt nó bằng lệnh:
deactivate
Bây giờ, bất kỳ lệnh Python nào cũng sẽ sử dụng lại môi trường Python của hệ thống.
Tiếp theo, hãy tạo file unit cho systemd. Việc tạo file unit cho systemd sẽ cho phép hệ thống khởi động của Ubuntu tự động chạy Gunicorn và phục vụ ứng dụng Flask mỗi khi máy chủ khởi động lại.
Để bắt đầu, hãy tạo một file unit với phần mở rộng .service trong thư mục /etc/systemd/system:
sudo nano /etc/systemd/system/myproject.service
Bên trong file, bạn sẽ bắt đầu với phần [Unit], phần này được dùng để khai báo metadata và các dependencies. Hãy thêm mô tả cho service của bạn tại đây và yêu cầu hệ thống khởi động chỉ chạy service này sau khi mạng đã được khởi động xong:
/etc/systemd/system/myproject.service
[Unit]
Description=Gunicorn instance to serve myproject
After=network.target
Tiếp theo, thêm phần [Service]. Phần này sẽ chỉ định user và group mà bạn muốn tiến trình sẽ chạy dưới quyền của họ. Hãy cấp quyền sở hữu tiến trình cho tài khoản người dùng thông thường của bạn, vì tài khoản đó sở hữu tất cả các tệp liên quan. Đồng thời, cấp quyền nhóm cho group www-data để Nginx có thể dễ dàng giao tiếp với các tiến trình Gunicorn. Hãy thay thế username trong hướng dẫn bằng tên người dùng thật của bạn:
/etc/systemd/system/myproject.service
[Unit]
Description=Gunicorn instance to serve myproject
After=network.target
[Service]
User=sammy
Group=www-data
Tiếp theo, hãy khai báo working directory và thiết lập biến môi trường PATH để hệ thống khởi động biết rằng các file thực thi cho tiến trình nằm trong môi trường ảo của bạn. Đồng thời, chỉ định lệnh để khởi động service. Lệnh này sẽ thực hiện các công việc sau:
- Khởi động 3 tiến trình worker (bạn có thể điều chỉnh số lượng này tùy theo nhu cầu)
- Tạo và liên kết tới một file socket Unix, có tên
myproject.sock, nằm trong thư mục dự án. Ta sẽ đặt giá trị umask là007, để đảm bảo file socket được tạo với quyền truy cập cho chủ sở hữu và nhóm, còn người khác sẽ bị hạn chế truy cập. - Chỉ định tên file WSGI entry point, cùng với hàm Python có thể gọi được trong file đó (ví dụ:
wsgi:app)
Systemd yêu cầu bạn phải cung cấp đường dẫn đầy đủ đến file thực thi Gunicorn, vốn được cài đặt bên trong môi trường ảo của bạn.
Hãy thay thế username và các đường dẫn của dự án bằng thông tin cụ thể của bạn:
/etc/systemd/system/myproject.service
[Unit]
Description=Gunicorn instance to serve myproject
After=network.target
[Service]
User=sammy
Group=www-data
WorkingDirectory=/home/sammy/myproject
Environment="PATH=/home/sammy/myproject/myprojectenv/bin"
ExecStart=/home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app
Cuối cùng, hãy thêm phần [Install]. Phần này sẽ cho systemd biết nên liên kết service này với mục nào nếu bạn bật chế độ tự khởi động cùng hệ thống. Trong trường hợp này, bạn muốn service được khởi động khi hệ thống ở chế độ multi-user đã hoạt động đầy đủ:
/etc/systemd/system/myproject.service
[Unit]
Description=Gunicorn instance to serve myproject
After=network.target
[Service]
User=sammy
Group=www-data
WorkingDirectory=/home/sammy/myproject
Environment="PATH=/home/sammy/myproject/myprojectenv/bin"
ExecStart=/home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app
[Install]
WantedBy=multi-user.target
Vậy là file service của bạn dành cho systemd đã hoàn tất. Hãy lưu và đóng file lại ngay bây giờ.
Giờ đây, bạn có thể khởi động service Gunicorn mà bạn vừa tạo và bật chế độ khởi động cùng hệ thống bằng các lệnh sau:
sudo systemctl start myproject
sudo systemctl enable myproject
Hãy kiểm tra trạng thái của service:
sudo systemctl status myproject
Bạn sẽ thấy đầu ra tương tự như sau:
Output
● myproject.service - Gunicorn instance to serve myproject
Loaded: loaded (/etc/systemd/system/myproject.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2022-05-10 19:40:41 UTC; 9s ago
Main PID: 17300 (gunicorn)
Tasks: 4 (limit: 2327)
Memory: 56.0M
CPU: 514ms
CGroup: /system.slice/myproject.service
├─17300 /home/sammy/myproject/myprojectenv/bin/python3 /home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app
├─17301 /home/sammy/myproject/myprojectenv/bin/python3 /home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app
├─17302 /home/sammy/myproject/myprojectenv/bin/python3 /home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app
└─17303 /home/sammy/myproject/myprojectenv/bin/python3 /home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app
May 10 19:40:41 r systemd[1]: Started Gunicorn instance to serve myproject.
. . .
Nếu bạn thấy bất kỳ lỗi nào, hãy chắc chắn rằng bạn đã xử lý chúng trước khi tiếp tục với hướng dẫn.
Bước 5: Cấu hình Nginx để proxy các yêu cầu
Lúc này, máy chủ ứng dụng Gunicorn của bạn đã hoạt động, sẵn sàng nhận các yêu cầu thông qua file socket nằm trong thư mục dự án. Bây giờ, bạn có thể cấu hình Nginx để proxy các yêu cầu web tới socket đó bằng cách thêm một vài cấu hình đơn giản.
Hãy bắt đầu bằng cách tạo một file cấu hình server block mới trong thư mục sites-available của Nginx. Đặt tên file là myproject để đồng bộ với phần còn lại của hướng dẫn:
sudo nano /etc/nginx/sites-available/myproject
Mở file server block vừa tạo và khai báo cho Nginx lắng nghe trên cổng mặc định 80. Đồng thời, chỉ định rằng block này sẽ xử lý các yêu cầu gửi đến tên miền của máy chủ của bạn:
/etc/nginx/sites-available/myproject
server {
listen 80;
server_name your_domain www.your_domain;
}
Tiếp theo, hãy thêm một khối location để khớp với mọi yêu cầu. Bên trong khối này, bạn sẽ:
- Bao gồm file
proxy_params, file này chứa các tham số cấu hình chung cho việc proxy cần được thiết lập. - Sau đó, sử dụng chỉ thị
proxy_passđể chuyển tiếp các yêu cầu đến file socket mà bạn đã định nghĩa trước đó.
Cấu hình này sẽ cho phép Nginx hoạt động như một reverse proxy, chuyển tiếp các request từ trình duyệt đến ứng dụng Flask đang chạy qua Gunicorn:
/etc/nginx/sites-available/myproject
server {
listen 80;
server_name your_domain www.your_domain;
location / {
include proxy_params;
proxy_pass <http://unix>:/home/sammy/myproject/myproject.sock;
}
}
Lưu và đóng file lại sau khi bạn hoàn tất việc chỉnh sửa.
Để kích hoạt cấu hình server block của Nginx mà bạn vừa tạo, hãy tạo một liên kết từ file đó đến thư mục sites-enabled bằng lệnh sau:
sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled
Khi file đã nằm trong thư mục đó, bạn có thể kiểm tra xem có lỗi cú pháp nào trong cấu hình Nginx hay không bằng lệnh sau:
sudo nginx -t
Nếu lệnh trên trả về mà không báo lỗi nào, hãy khởi động lại tiến trình Nginx để nó tải lại cấu hình mới bằng lệnh sau:
sudo systemctl restart nginx
Cuối cùng, hãy điều chỉnh lại cấu hình tường lửa. Bạn không còn cần mở cổng 5000 nữa, nên có thể xóa quy tắc cho phép truy cập cổng này. Sau đó, cho phép toàn quyền truy cập vào máy chủ Nginx bằng cách bật các quy tắc liên quan đến Nginx.
sudo ufw delete allow 5000
sudo ufw allow 'Nginx Full'
Giờ đây, bạn có thể truy cập vào tên miền của máy chủ bằng trình duyệt web của mình:
http://your_domain
Bạn sẽ thấy đầu ra của ứng dụng của mình hiển thị như sau:

Lưu ý: Bạn sẽ nhận được lỗi HTTP 502 gateway nếu Nginx không thể truy cập vào file socket của Gunicorn. Thông thường, nguyên nhân là do thư mục home của người dùng không cho phép các user khác truy cập vào các tệp bên trong.
Nếu file socket của bạn có tên là /home/sammy/myproject/myproject.sock, hãy đảm bảo rằng thư mục /home/sammy có quyền tối thiểu là 0755. Bạn có thể sử dụng công cụ như chmod để thay đổi quyền như sau:
sudo chmod 755 /home/sammy
Sau đó, tải lại trang để xem lỗi HTTP 502 đã biến mất chưa.
Nếu bạn gặp bất kỳ lỗi nào, hãy thử kiểm tra các mục sau:
sudo less /var/log/nginx/error.log: kiểm tra log lỗi của Nginx.sudo less /var/log/nginx/access.log: kiểm tra log truy cập của Nginx.sudo journalctl -u nginx: kiểm tra log tiến trình của Nginx.sudo journalctl -u myproject: kiểm tra log Gunicorn của ứng dụng Flask.
Bước 6: Bảo mật ứng dụng
Để đảm bảo lưu lượng truy cập đến máy chủ của bạn luôn an toàn, hãy lấy một chứng chỉ SSL cho tên miền của bạn. Có nhiều cách để thực hiện việc này, bao gồm:
- Lấy chứng chỉ miễn phí từ Let’s Encrypt
- Tạo chứng chỉ self-signed
- Mua chứng chỉ từ nhà cung cấp khác và cấu hình Nginx để sử dụng, theo hướng dẫn từ Bước 2 đến Bước 6 của bài “Cách tạo chứng chỉ SSL self-signed cho Nginx trên Ubuntu 22.04”
Trong hướng dẫn này, để đơn giản và nhanh chóng, chúng ta sẽ sử dụng tùy chọn đầu tiên: Let’s Encrypt.
Hãy cài đặt gói Certbot cho Nginx bằng apt:
sudo apt install python3-certbot-nginx
Certbot cung cấp nhiều cách để lấy chứng chỉ SSL thông qua các plugin. Plugin dành cho Nginx sẽ tự động xử lý việc cấu hình lại Nginx và tải lại cấu hình khi cần thiết.
Để sử dụng plugin này, hãy nhập lệnh sau:
sudo certbot --nginx -d your_domain -d www.your_domain
Lệnh này chạy certbot với plugin --nginx, sử dụng tùy chọn -d để chỉ định các tên miền mà bạn muốn chứng chỉ có hiệu lực.
Nếu đây là lần đầu tiên bạn chạy certbot, bạn sẽ được yêu cầu nhập địa chỉ email và đồng ý với các điều khoản dịch vụ. Sau khi hoàn tất, certbot sẽ liên hệ với máy chủ của Let’s Encrypt và thực hiện một thử thách để xác minh rằng bạn thực sự sở hữu tên miền đang yêu cầu cấp chứng chỉ.
Nếu xác minh thành công, certbot sẽ hỏi bạn muốn cấu hình HTTPS như thế nào:
Output
Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
-------------------------------------------------------------------------------
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
-------------------------------------------------------------------------------
Select the appropriate number [1-2] then [enter] (press 'c' to cancel):
Chọn tùy chọn của bạn rồi nhấn ENTER. Cấu hình sẽ được cập nhật và Nginx sẽ tự động tải lại để áp dụng các thiết lập mới.
Sau đó, certbot sẽ hiển thị một thông báo cho biết quá trình đã thành công và vị trí lưu trữ các chứng chỉ của bạn:
Output
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/your_domain/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/your_domain/privkey.pem
Your cert will expire on 2020-08-18. To obtain a new or tweaked
version of this certificate in the future, simply run certbot again
with the "certonly" option. To non-interactively renew *all* of
your certificates, run "certbot renew"
- Your account credentials have been saved in your Certbot
configuration directory at /etc/letsencrypt. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Certbot so
making regular backups of this folder is ideal.
- 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>
Nếu bạn đã làm theo hướng dẫn cài đặt Nginx trong phần yêu cầu ban đầu, bạn sẽ không còn cần mở cổng HTTP dư thừa nữa:
sudo ufw delete allow 'Nginx HTTP'
Để xác minh cấu hình, hãy truy cập lại vào tên miền của bạn, lần này sử dụng https://:
https://your_domain
Bạn sẽ thấy lại đầu ra của ứng dụng, kèm theo biểu tượng bảo mật của trình duyệt, cho biết rằng trang web của bạn đã được bảo mật.
Kết luận
Trong hướng dẫn này, bạn đã tạo và bảo mật một ứng dụng Flask đơn giản trong một môi trường ảo Python. Bạn đã tạo một WSGI entry point để bất kỳ máy chủ ứng dụng nào hỗ trợ WSGI cũng có thể giao tiếp với ứng dụng. Sau đó, bạn đã cấu hình máy chủ ứng dụng Gunicorn để thực hiện chức năng đó.
Tiếp theo, bạn đã tạo một file service cho systemd để tự động khởi động ứng dụng mỗi khi máy chủ boot. Bạn cũng thiết lập một server block của Nginx để chuyển tiếp lưu lượng truy cập từ trình duyệt đến ứng dụng Flask, và cuối cùng, bạn đã bảo mật toàn bộ lưu lượng truy cập đến máy chủ của mình bằng Let’s Encrypt.
Flask là một framework rất đơn giản nhưng cực kỳ linh hoạt, cung cấp đầy đủ chức năng cho ứng dụng của bạn mà không ép buộc bạn tuân theo một cấu trúc hay thiết kế cứng nhắc. Bạn có thể sử dụng toàn bộ mô hình (stack) được mô tả trong hướng dẫn này để triển khai các ứng dụng Flask mà bạn xây dựng.