Ngày đăng: 14 tháng 3 năm 2024
Trong hướng dẫn này, chúng tôi sẽ thiết lập một ứng dụng WSGI đơn giản do uWSGI cung cấp. Chúng tôi sẽ sử dụng máy chủ web Nginx làm proxy ngược cho máy chủ ứng dụng để cung cấp khả năng xử lý kết nối mạnh mẽ hơn. Chúng tôi sẽ cài đặt và định cấu hình các thành phần này trên máy chủ Ubuntu 14.04.
Trước khi bắt đầu, chúng ta cần hiểu rõ một số thuật ngữ khó hiểu liên quan đến các khái niệm liên quan đến nhau mà chúng ta sẽ giải quyết. Ba thuật ngữ riêng biệt này có vẻ có thể thay thế cho nhau nhưng thực ra chúng có ý nghĩa riêng biệt:
Thông số WSGI xác định giao diện giữa máy chủ web và các phần ứng dụng của ngăn xếp. Trong ngữ cảnh này, “máy chủ web” đề cập đến máy chủ uWSGI, máy chủ này chịu trách nhiệm dịch các yêu cầu của khách hàng sang ứng dụng bằng cách sử dụng thông số WSGI. Điều này giúp đơn giản hóa việc giao tiếp và tạo ra các thành phần được ghép nối lỏng lẻo để bạn có thể dễ dàng hoán đổi hai bên mà không gặp nhiều khó khăn.
Máy chủ web (uWSGI) phải có khả năng gửi yêu cầu đến ứng dụng bằng cách kích hoạt "callable" được xác định. Callable chỉ đơn giản là một điểm vào (entry point) ứng dụng nơi máy chủ web có thể gọi một hàm với một số tham số. Các tham số dự kiến là một dictionary của các biến môi trường và một callable được cung cấp bởi thành phần máy chủ web (uWSGI).
Để phản hồi, ứng dụng trả về một iterable sẽ được sử dụng để tạo nội dung phản hồi của máy khách. Nó cũng sẽ gọi thành phần máy chủ web callable mà nó nhận được dưới dạng tham số. Tham số đầu tiên khi kích hoạt máy chủ web callable sẽ là HTTP status code và tham số thứ hai sẽ là danh sách các tuples, mỗi tuple xác định một tiêu đề và giá trị phản hồi để gửi lại cho máy khách.
Với thành phần “máy chủ web” của tương tác (interaction) do uWSGI cung cấp trong trường hợp này, chúng ta sẽ chỉ cần đảm bảo rằng ứng dụng của chúng ta có những phẩm chất được mô tả ở trên. Chúng tôi cũng sẽ thiết lập Nginx để xử lý các yêu cầu thực tế của khách hàng và ủy quyền chúng cho máy chủ uWSGI.
Để bắt đầu, chúng tôi sẽ cần cài đặt các thành phần cần thiết trên máy chủ Ubuntu 14.04 của mình. Chúng ta có thể thực hiện việc này bằng cách sử dụng apt
và pip
.
Trước tiên, hãy làm mới chỉ mục gói apt
của bạn, sau đó cài đặt các tiêu đề và thư viện Python development, trình quản lý gói pip
Python cũng như máy chủ web Nginx và proxy ngược:
sudo apt-get update
sudo apt-get install python-dev python-pip nginx
Sau khi quá trình cài đặt gói hoàn tất, bạn sẽ có quyền truy cập vào trình quản lý gói pip
Python. Chúng ta có thể sử dụng gói này để cài đặt gói virtualenv
mà chúng ta sẽ sử dụng để tách biệt môi trường Python của ứng dụng của mình khỏi bất kỳ môi trường nào khác có thể tồn tại trên hệ thống:
sudo pip install virtualenv
Khi việc này hoàn tất, chúng ta có thể bắt đầu tạo cấu trúc chung cho ứng dụng của mình. Chúng tôi sẽ tạo môi trường ảo được thảo luận ở trên và sẽ cài đặt máy chủ ứng dụng uWSGI trong môi trường này.
Chúng ta sẽ bắt đầu bằng cách tạo một thư mục cho ứng dụng của mình. Điều này có thể chứa một thư mục lồng nhau chứa mã ứng dụng thực tế trong một ứng dụng hoàn chỉnh hơn. Vì mục đích của chúng tôi, thư mục này sẽ chỉ chứa môi trường ảo và điểm vào WSGI của chúng tôi:
mkdir ~/myapp/
Tiếp theo, di chuyển vào thư mục để chúng ta có thể thiết lập môi trường cho ứng dụng của mình:
cd ~/myapp
Tạo một môi trường ảo bằng lệnh virtualenv
. Chúng tôi sẽ gọi đây là myappenv
để đơn giản:
virtualenv myappenv
Một môi trường Python mới sẽ được thiết lập trong thư mục có tên myappenv
. Chúng ta có thể kích hoạt môi trường này bằng cách gõ:
source myappenv/bin/activate
Lời nhắc (prompt) của bạn sẽ thay đổi để cho biết rằng bạn hiện đang hoạt động trong môi trường ảo. Nó sẽ trông giống như thế này:
(myappenv)username@host:~/my_app$
Nếu bạn muốn rời khỏi môi trường này bất cứ lúc nào, bạn chỉ cần gõ:
deactivate
Nếu bạn đã tắt môi trường của mình, hãy kích hoạt lại môi trường đó để tiếp tục xem hướng dẫn.
Khi môi trường này hoạt động, mọi gói Python được cài đặt sẽ nằm trong hệ thống phân cấp thư mục này. Chúng sẽ không can thiệp vào môi trường Python của hệ thống. Với suy nghĩ này, giờ đây chúng ta có thể cài đặt máy chủ uWSGI vào môi trường của mình bằng pip
. Gói cho việc này được gọi là uwsgi
(đây vẫn là máy chủ uWSGI chứ không phải giao thức `uwsgi):
pip install uwsgi
Bạn có thể xác minh rằng nó hiện có sẵn bằng cách gõ:
uwsgi --version
Nếu nó trả về số phiên bản thì máy chủ uWSGI có sẵn để sử dụng.
Tiếp theo, chúng ta sẽ tạo một ứng dụng WSGI cực kỳ đơn giản bằng cách sử dụng các yêu cầu đặc tả WSGI mà chúng ta đã thảo luận trước đó. Xin nhắc lại, thành phần ứng dụng mà chúng tôi phải cung cấp phải có các thuộc tính sau:
Chúng tôi sẽ viết ứng dụng của mình vào một tệp có tên wsgi.py
trong thư mục ứng dụng của chúng tôi:
nano ~/myapp/wsgi.py
Bên trong tệp này, chúng tôi sẽ tạo ứng dụng tuân thủ WSGI đơn giản nhất có thể. Như với tất cả các mã Python, hãy nhớ chú ý đến phần thụt lề:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return ["<h1 style='color:blue'>Hello There!</h1>"]
Đoạn mã trên tạo thành một ứng dụng WSGI hoàn chỉnh. Theo mặc định, uWSGI sẽ tìm kiếm một callable được gọi là application
, đó là lý do tại sao chúng tôi gọi application
chức năng của mình. Như bạn có thể thấy, nó cần hai tham số.
Đầu tiên chúng tôi gọi là environ
vì nó sẽ là một key-value dictionary giống như biến môi trường. Tên thứ hai được gọi là start_response
và là tên mà ứng dụng sẽ sử dụng nội bộ để chỉ máy chủ web (uWSGI) callable được gửi vào. Cả hai tên tham số này đều được chọn đơn giản vì chúng được sử dụng trong các ví dụ trong thông số PEP 333 xác định các tương tác WSGI.
Ứng dụng của chúng tôi phải lấy thông tin này và thực hiện hai việc. Đầu tiên, nó phải gọi callable mà nó nhận được kèm theo HTTP status code và mọi tiêu đề mà nó muốn gửi lại. Trong trường hợp này, chúng tôi đang gửi phản hồi “200 OK” và đặt tiêu đề Content-Type
thành text/html
.
Thứ hai, nó cần trả về với một iterable để sử dụng làm nội dung phản hồi. Ở đây, chúng ta vừa sử dụng một danh sách chứa một chuỗi HTML. Các chuỗi cũng có thể lặp lại, nhưng bên trong danh sách, uWSGI sẽ có thể xử lý toàn bộ chuỗi chỉ bằng một lần lặp.
Trong trường hợp thực tế, tệp này có thể được sử dụng làm liên kết đến phần còn lại của mã ứng dụng (application code) của bạn. Chẳng hạn, các dự án Django bao gồm tệp wsgi.py
theo mặc định để dịch các yêu cầu từ máy chủ web (uWSGI) sang ứng dụng (Django). Giao diện WSGI được đơn giản hóa vẫn giữ nguyên, bất kể mã ứng dụng thực tế phức tạp đến mức nào. Đây là một trong những điểm mạnh của giao diện.
Lưu và đóng tập tin khi bạn hoàn tất.
Để kiểm tra mã, chúng ta có thể khởi động uWSGI. Chúng tôi sẽ yêu cầu nó sử dụng HTTP trong thời điểm hiện tại và nghe trên cổng 8080
. Chúng tôi sẽ chuyển cho nó tên của tập lệnh (đã xóa hậu tố):
uwsgi --socket 0.0.0.0:8080 --protocol=http -w wsgi
Bây giờ, nếu bạn truy cập địa chỉ IP hoặc tên miền của máy chủ trong trình duyệt web của bạn, theo sau là :8080
, bạn sẽ thấy văn bản tiêu đề cấp đầu tiên mà chúng tôi đã chuyển dưới dạng nội dung trong tệp wsgi.py
của mình:
Dừng máy chủ bằng CTRL-C khi bạn đã xác minh rằng thao tác này hoạt động.
Chúng ta đã hoàn tất việc thiết kế ứng dụng thực tế của mình vào thời điểm này. Bạn có thể hủy kích hoạt môi trường ảo của chúng ta nếu bạn muốn:
deactivate
Trong ví dụ trên, chúng tôi khởi động máy chủ uWSGI theo cách thủ công và truyền cho nó một số tham số trên dòng lệnh. Chúng tôi có thể tránh điều này bằng cách tạo một tệp cấu hình. Máy chủ uWSGI có thể đọc cấu hình ở nhiều định dạng khác nhau, nhưng chúng tôi sẽ sử dụng định dạng .ini
để đơn giản.
Để tiếp tục cách đặt tên mà chúng ta đã sử dụng cho đến nay, chúng ta sẽ gọi tệp myapp.ini
và đặt nó vào thư mục ứng dụng của mình:
nano ~/myapp/myapp.ini
Bên trong, chúng ta cần thiết lập một phần gọi là [uwsgi]
. Phần này là nơi chứa tất cả các mục cấu hình của chúng ta. Chúng ta sẽ bắt đầu bằng cách xác định ứng dụng của chúng ta. Máy chủ uWSGI cần biết ứng dụng callable ở đâu. Chúng ta có thể cung cấp tệp và hàm trong:
[uwsgi]
module = wsgi:application
Chúng ta muốn đánh dấu quy trình uwsgi
ban đầu là quy trình chính và sau đó sinh ra một số quy trình worker. Chúng ta sẽ bắt đầu với năm workers:
[uwsgi]
module = wsgi:application
master = true
processes = 5
Chúng ta thực sự sẽ thay đổi giao thức mà uWSGI sử dụng để giao tiếp với thế giới bên ngoài. Khi chúng ta đang thử nghiệm ứng dụng của mình, chúng tôa đã chỉ định --protocol=http
để có thể xem ứng dụng đó từ trình duyệt web. Vì chúng ta sẽ định cấu hình Nginx làm proxy ngược trước uWSGI nên chúng ta có thể thay đổi điều này. Nginx triển khai cơ chế ủy quyền uwsgi
, đây là giao thức nhị phân nhanh mà uWSGI có thể sử dụng để giao tiếp với các máy chủ khác. Giao thức uwsgi
thực sự là giao thức mặc định của uWSGI, vì vậy chỉ cần bỏ qua đặc tả giao thức, nó sẽ quay trở lại uwsgi
.
Vì chúng ta đang thiết kế cấu hình này để sử dụng với Nginx, nên chúng ta cũng sẽ thay đổi từ việc sử dụng cổng mạng và sử dụng Unix socket thay thế. Điều này an toàn hơn và nhanh hơn. Socket sẽ được tạo trong thư mục hiện tại nếu chúng ta sử dụng đường dẫn tương đối (relative path). Chúng ta sẽ gọi nó là myapp.sock
. Chúng ta sẽ thay đổi quyền thành “664” để Nginx có thể ghi vào nó (chúng ta sẽ khởi động uWSGI với nhóm www-data
mà Nginx sử dụng. Chúng ta cũng sẽ thêm tùy chọn vacuum
, tùy chọn này sẽ loại bỏ socket khi quá trình dừng :
[uwsgi]
module = wsgi:application
master = true
processes = 5
socket = myapp.sock
chmod-socket = 664
vacuum = true
Chúng ta cần một tùy chọn cuối cùng vì chúng tôi sẽ tạo tệp Upstart để khởi động ứng dụng của mình khi khởi động (at boot). Upstart và uWSGI có những ý tưởng khác nhau về tác dụng của tín hiệu SIGTERM đối với một ứng dụng. Để giải quyết sự khác biệt này để các tiến trình có thể được xử lý như mong đợi với Upstart, chúng ta chỉ cần thêm một tùy chọn có tên die-on-term
để uWSGI sẽ hủy tiến trình thay vì tải lại nó:
[uwsgi]
module = wsgi:application
master = true
processes = 5
socket = myapp.sock
chmod-socket = 664
vacuum = true
die-on-term = true
Lưu và đóng tập tin khi bạn hoàn tất. Tệp cấu hình này hiện được đặt để sử dụng với tập lệnh Upstart.
Chúng ta có thể khởi chạy phiên bản uWSGI khi khởi động để ứng dụng của chúng ta luôn khả dụng. Chúng ta sẽ đặt cái này vào thư mục /etc/init
mà Upstart kiểm tra. Chúng ta sẽ gọi đây là myapp.conf
:
sudo nano /etc/init/myapp.conf
Trước tiên, chúng ta có thể bắt đầu với phần mô tả dịch vụ và chọn ra các system runlevels nơi dịch vụ sẽ tự động chạy. Các runlevels (cấp độ chạy) của người dùng tiêu chuẩn là từ 2 đến 5. Chúng ta sẽ yêu cầu Upstart dừng dịch vụ khi nó ở bất kỳ runlevels nào bên ngoài nhóm này (chẳng hạn như khi hệ thống đang khởi động lại hoặc ở chế độ một người dùng):
description "uWSGI instance to serve myapp"
start on runlevel [2345]
stop on runlevel [!2345]
Tiếp theo, sẽ cho Upstart biết người dùng và nhóm nào sẽ chạy quy trình. Chúng tôi muốn chạy ứng dụng bằng tài khoản của chính mình (chúng tôi đang sử dụng demo
trong hướng dẫn này nhưng bạn nên thay thế người dùng của riêng mình). Tuy nhiên, chúng tôi muốn đặt nhóm thành người dùng www-data
mà Nginx sử dụng. Điều này là cần thiết vì máy chủ web cần có khả năng đọc và ghi vào socket mà tệp .ini
của chúng tôi sẽ tạo:
description "uWSGI instance to serve myapp"
start on runlevel [2345]
stop on runlevel [!2345]
setuid demo
setgid www-data
Tiếp theo, chúng ta sẽ chạy các lệnh thực tế để khởi động uWSGI. Vì chúng ta đã cài đặt uWSGI vào môi trường ảo nên chúng ta có thêm một số việc phải làm. Chúng ta chỉ có thể cung cấp toàn bộ đường dẫn đến tệp thực thi uWSGI, nhưng thay vào đó, chúng ta sẽ kích hoạt môi trường ảo. Điều này sẽ dễ dàng hơn nếu chúng ta dựa vào phần mềm bổ sung được cài đặt trong môi trường.
Để làm điều này, chúng ta sẽ sử dụng khối script
. Bên trong, chúng ta sẽ thay đổi thư mục ứng dụng của mình, kích hoạt môi trường ảo (chúng ta phải sử dụng .
trong tập lệnh thay vì source
) và khởi động phiên bản uWSGI trỏ vào tệp .ini
của chúng ta:
description "uWSGI instance to serve myapp"
start on runlevel [2345]
stop on runlevel [!2345]
setuid demo
setgid www-data
script
cd /home/demo/myapp
. myappenv/bin/activate
uwsgi --ini myapp.ini
end script
Như vậy, tập lệnh Upstart của chúng tôi đã hoàn tất. Lưu và đóng tập tin khi bạn hoàn tất.
Bây giờ, chúng ta có thể bắt đầu dịch vụ bằng cách gõ:
sudo start myapp
Chúng ta có thể xác minh rằng nó đã được bắt đầu bằng cách gõ:
ps aux | grep myapp
demo 14618 0.0 0.5 35868 5996 ? S 15:02 0:00 uwsgi --ini myapp.ini
demo 14619 0.0 0.5 42680 5532 ? S 15:02 0:00 uwsgi --ini myapp.ini
demo 14620 0.0 0.5 42680 5532 ? S 15:02 0:00 uwsgi --ini myapp.ini
demo 14621 0.0 0.5 42680 5532 ? S 15:02 0:00 uwsgi --ini myapp.ini
demo 14622 0.0 0.5 42680 5532 ? S 15:02 0:00 uwsgi --ini myapp.ini
demo 14623 0.0 0.5 42680 5532 ? S 15:02 0:00 uwsgi --ini myapp.ini
demo 15520 0.0 0.0 11740 936 pts/0 S+ 15:53 0:00 grep --color=auto myapp
Điều này sẽ tự động bắt đầu khi khởi động. Bạn có thể dừng dịch vụ bất cứ lúc nào bằng cách gõ:
sudo stop myapp
Tại thời điểm này, chúng ta có ứng dụng WSGI và đã xác minh rằng uWSGI có thể đọc và phân phối ứng dụng đó. Chúng ta đã tạo một tệp cấu hình và tập lệnh Upstart. Quá trình uWSGI của chúng ta sẽ lắng nghe trên một socket và giao tiếp bằng giao thức uwsgi
.
Bây giờ chúng ta có thể làm việc để định cấu hình Nginx làm proxy ngược. Nginx có khả năng ủy quyền bằng giao thức uwsgi
để liên lạc với uWSGI. Đây là giao thức nhanh hơn HTTP và sẽ hoạt động tốt hơn.
Cấu hình Nginx mà chúng ta sẽ thiết lập cực kỳ đơn giản. Tạo một tệp mới trong thư mục sites-available
trong hệ thống phân cấp cấu hình của Nginx. Chúng tôi sẽ gọi tệp của mình là myapp
để khớp với tên ứng dụng mà chúng tôi đang sử dụng:
sudo nano /etc/nginx/sites-available/myapp
Trong tệp này, chúng ta có thể chỉ định số cổng và tên miền mà khối máy chủ (server block) này sẽ phản hồi. Trong trường hợp của chúng tôi, chúng tôi sẽ sử dụng cổng mặc định 80:
server {
listen 80;
server_name server_domain_or_IP;
}
Vì chúng ta muốn gửi tất cả các yêu cầu trên miền hoặc địa chỉ IP này tới ứng dụng WSGI của mình nên chúng ta sẽ tạo một khối vị trí (location block) duy nhất cho các yêu cầu bắt đầu bằng /
, khối này phải khớp với mọi thứ. Bên trong, chúng ta sẽ sử dụng lệnh include
để bao gồm một số tham số có giá trị mặc định hợp lý từ một tệp trong thư mục cấu hình Nginx của chúng ta. Tệp chứa những thứ này được gọi là uwsgi_params
. Sau đó, chúng ta sẽ chuyển lưu lượng truy cập đến phiên bản uWSGI của mình qua giao thức uwsgi
. Chúng ta sẽ sử dụng unix socket mà chúng ta đã cấu hình trước đó:
server {
listen 80;
server_name server_domain_or_IP;
location / {
include uwsgi_params;
uwsgi_pass unix:/home/demo/myapp/myapp.sock;
}
}
Đó thực sự là tất cả những gì chúng ta cần cho một ứng dụng đơn giản. Có một số cải tiến có thể được thực hiện để ứng dụng hoàn thiện hơn. Ví dụ: chúng ta có thể xác định một số máy chủ uWSGI upstream bên ngoài khối này và sau đó chuyển chúng sang khối đó. Chúng ta có thể bao gồm thêm một số tham số uWSGI. Chúng ta cũng có thể xử lý trực tiếp mọi tệp tĩnh từ Nginx và chỉ chuyển các yêu cầu động tới phiên bản uWSGI.
Tuy nhiên, chúng ta không cần bất kỳ tính năng nào trong số đó trong ứng dụng three-line của mình, vì vậy chúng ta có thể lưu và đóng tệp.
Kích hoạt cấu hình máy chủ mà chúng ta vừa thực hiện bằng cách liên kết nó với thư mục sites-enabled
:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled
Kiểm tra tệp cấu hình để tìm lỗi cú pháp:
sudo service nginx configtest
Nếu nó báo cáo lại rằng không phát hiện vấn đề gì, hãy khởi động lại máy chủ để thực hiện các thay đổi của bạn:
sudo service nginx restart
Khi Nginx khởi động lại, bạn sẽ có thể truy cập tên miền hoặc địa chỉ IP của máy chủ (không có số cổng) và xem ứng dụng bạn đã định cấu hình:
Nếu bạn đã làm được đến mức này thì bạn đã tạo một ứng dụng WSGI đơn giản và có một số hiểu biết sâu sắc về cách thiết kế các ứng dụng phức tạp hơn. Chúng ta đã cài đặt bộ chứa/máy chủ ứng dụng uWSGI vào một môi trường ảo được tạo ra có mục đích để phục vụ ứng dụng của chúng ta. Chúng ta đã tạo tệp cấu hình và tập lệnh Upstart để tự động hóa quy trình này. Trước máy chủ uWSGI, chúng ta đã thiết lập proxy ngược Nginx có thể giao tiếp với quy trình uWSGI bằng giao thức dây (wire protocol) uwsgi
.
Bạn có thể dễ dàng thấy, điều này có thể được mở rộng như thế nào khi thiết lập môi trường sản xuất thực tế. Chẳng hạn, uWSGI có khả năng quản lý nhiều ứng dụng bằng cách sử dụng cái gọi là “emperor mode”. Bạn có thể mở rộng cấu hình Nginx để cân bằng tải giữa các phiên bản uWSGI hoặc để xử lý các tệp tĩnh cho ứng dụng của mình. Khi cung cấp nhiều ứng dụng, bạn nên cài đặt uWSGI trên toàn cầu thay vì trong môi trường ảo, tùy thuộc vào nhu cầu của bạn. Tất cả các thành phần đều khá linh hoạt, vì vậy bạn có thể điều chỉnh cấu hình của chúng để phù hợp với nhiều tình huống khác nhau.