Nginx+uWSGI+Django部署web伺服器

鐵樂貓發表於2018-08-28

Nginx+uWSGI+Django部署web伺服器

環境說明

  • 進行本文操作前需己搭建好的環境
    • linux系統,我用的是openSUSE
      • 使用了operation使用者的家目錄做為測試環境
    • python3.5.6
    • virtualenv 16.0
    • pip3 18.0
    • nginx 1.13.1
  • 後面進行安裝的環境
    • django 1.11
    • uwsgi-2.0.17.1

前言

在多年前,基於python的web專案,常見的部署方法有:

  • fcgi:用spawn-fcgi或者框架自帶的工具對各個project分別生成監聽程式,然後和http服務互動。
  • wsgi:利用http服務的mod_wsgi模組來跑各個project。

不過uwsgi出現後,大部份部署就不會使用以上的兩個協議了。
因為uwsgi它既不用wsgi協議也不用fcgi協議,而是自創了一個uwsgi的協議,據作者說該協議大約是fcgi協議的10倍那麼快。uWSGI的主要特點如下:

  • 超快的效能。
  • 低記憶體佔用(實測為apache2的mod_wsgi的一半左右)。
  • 多app管理。
  • 詳盡的日誌功能(可以用來分析app效能和瓶頸)。
  • 高度可定製(記憶體大小限制,服務一定次數後重啟等)。

所以在uwsgi協議的基礎上,配合nginx來做python(類如django的框架)的web專案部署就很常見了。

uwsgi 實際上也是一個 http 伺服器,只不過它只面向 python 的網路應用程式。
雖然 uwsgi 也是 http 伺服器,但是卻不便直接使用它部署 python web 應用程式。

uwsgi 所扮演的的角色是後端 http 伺服器,nginx 扮演的角色是前端 http 伺服器,django專案 是客戶所真正要訪問到的提供資料方。 使用者從網頁瀏覽器中發出請求,nginx 伺服器收到請求後,會通過它的 uwsgi 模組將使用者的請求轉發給 uwsgi 伺服器,uwsgi 伺服器通過django處理完畢後將結果返回給 nginx,瀏覽器將最終的結果展現給使用者。

搭建專案

  • 建立一個虛擬環境
    • 建議個人學習和測試的話,直接建在家使用者目錄下,以避免許可權引起的各種拒絕問題
    • 或者正式點,系統再建一個dev使用者,在專門的工作目錄下給好dev使用者的許可權去執行。
    • 虛擬環境的目錄名建議就帶個env點明是虛擬環境,如果和後面的專案取相同的名字你會看到三重同名的串在一起,有點不太好認。
      virtualenv -p python3 py3env
  • 啟動虛擬環境
    source py3env/bin/activate

  • 安裝django1.11,之所以裝這個版本是學習所需要,後面自己的專案最好與時俱進。
    pip install django==1.11

  • 直接使用django-admin命令建立一個django專案
    • 建議不要放在py3env目錄下,而是在專門的工作目錄下建立
      django-admin startproject luffy

如下,己經初步搭建好簡單的專案:

cd luffy

(py3env) operation@opensuse-wordpress:~/work/luffy> tree -L 2
.
├── luffy
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py
  • 建立新應用,例如app01
    python3 manage.py startapp app01
    結構如下
(py3env) operation@opensuse-wordpress:~/work/luffy> tree -L 2
.
├── app01
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── luffy
│   ├── __init__.py
│   ├── __pycache__
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py

Django部署

編輯luffy/luffy/settings.py

# SECURITY WARNING: don`t run with debug turned on in production!
DEBUG = False

ALLOWED_HOSTS = [`*`, ]

# Application definition

INSTALLED_APPS = [
    `django.contrib.admin`,
    `django.contrib.auth`,
    `django.contrib.contenttypes`,
    `django.contrib.sessions`,
    `django.contrib.messages`,
    `django.contrib.staticfiles`,
    `app01`,
]

如上,主要是:

  • 將DEBUG由True改成False(雖然只是在學習和測試搭建的,也要顧及好安全)
  • 在ALLOWED_HOSTS 預設的空列表中填入你自己打算使用的域名,我這裡測試的時候填的是*,真正上線部署的時候不建議填成萬用字元的*,而是要填允許訪問的主機域名。一般django用於做後端伺服器,而前端會有一個域名。詳情可見django官方文件。
  • INSTALLED_APPS 列表下增加appo1,表示將app01應用給安裝註冊上。
  • 補充,後面配置好nginx和新建好域名之後,我將ALLOWED_HOSTS = [`*`, ]修改成ALLOWED_HOSTS = [`luffy.tielemao`, ]了,通過域名訪問仍然正常。而直接敲ip地址則會報400錯誤。

編輯luffy/app01/views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

def index(request):
    return HttpResponse(`Hello Hero`)

由於只是簡單演示,所以上面只是讓使用者訪問index頁面後,得到一個顯示Hello Hero的頁面。

編輯luffy/luffy/urls.py

from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r`^admin/`, admin.site.urls),
    url(r`^index/`, views.index),
]

執行並測試

首先是執行,由於是單獨對django進行執行測試,所以只是在進入專案根目錄後命令列敲以下命令:

python manage.py runserver 0.0.0.0:8083
其中0.0.0.0表示捆綁監聽伺服器上的所有網路卡IP地址,當然也就包括了127.0.0.1和外網的公網ip了。

最初我測試的是ALLOWED_HOSTS填的允許的HOSTS是自己的域名.tielemao.com,結果直接在瀏覽器上敲IP加8083埠接index當然是報錯:

Nginx+uWSGI+Django部署web伺服器

Bad Request(400)而由於我將DEBUG值設為了False,所以也不會將除錯資訊暴露出來。

然後我將.tielemao.com改為了萬用字元*(不安全,表示允許所有主機頭)。
再訪問http://39.x.x.x:8083/index/的時候,就能正常訪問到了:
Nginx+uWSGI+Django部署web伺服器

  • 注意,我使用的是8083埠並且在防火牆和雲控制平臺上的安全組處己設定了開放該埠。

django能執行無誤後,ctrl+c將它停止,再接下來做下面的操作。

uWSGI部署

同樣為了環境的隔離和純淨,這次我也選擇在同樣的虛擬環境下安裝:
pip3 install uwsgi
安裝完成後可uwsgi --version檢視版本,
uwsgi --python-version還可以間接檢視到python的版本。

  • 編寫一個用於簡單測試uwsgi的python指令碼
    vim test-uwsgi.py,內容如下:
def application(env,start_response):
    start_response(`200 OK`,[(`Content-Type`,`text/html`)])
    return [b"Hello Hero, all for one "]

測試執行uWSGI

以下命令表示執行uwsgi服務,同樣是在8083埠上開放web訪問。

  • 注意--http 後是一個空格再接:埠號。
    uwsgi --http :8083 --wsgi-file test-uwsgi.py

訪問web伺服器8083埠,正常顯示test-uwsgi.py指令碼中返回顯示的文字。
Nginx+uWSGI+Django部署web伺服器

因為我們現在要做的是基於nginx和uwsgi部署django,從客戶端發起請求到伺服器響應請求,會經過一下幾個環節:

the web client <-> the web server(nginx) <-> the socket <-> uwsgi <-> Django

而單獨測試uWSGI執行,訪問顯示正常的話,說明下面3個環節是通暢的:

the web client <-> uWSGI <-> Python

ctrl+c中止程式,再來進行以下的測試。

使用uWSGI執行django專案

在虛擬環境下,進入到django根目錄下後敲以下命令:

 uwsgi --http :8000 --module luffy.wsgi

效果和直接敲以下命令

python manage.py runserver 0.0.0.0:8000

是一樣的。

  • 注:--module luffy.wsgi為載入指定的wsgi模組,你專案起的是什麼名字,一般就是專案名.wsgi

這樣測試下來,可證明web客戶端通過uWSGI到Django是通暢的:

the web client <-> uWSGI <-> Django

ctrl+c中止程式,再來進行以下的測試

uWSGi熱載入Djangoa專案

在啟動命令後面加上引數:

uwsgi --http :8083 --module luffy.wsgi --py-autoreload=1

同樣,這個時候訪問伺服器8083埠,也就是訪問了django專案(luffy)。
Nginx+uWSGI+Django部署web伺服器

而且還多了一個引數:
--py-autoreload 監控python模組以觸發過載(僅在開發中使用)

# 類似的釋出命令
/home/operation/work/py3env/bin/uwsgi --http 0.0.0.0 :8000 --chdir /home/operation/work/luffy --module luffy.wsgi
# 此時修改django程式碼,uWSGI會自動載入django程式,頁面生效
# 除此外,還可以接上很多引數

ctrl+c中止程式,再進行下面環節:

部署nginx

nginx配置uwsgi和django

  • uwsgi_params檔案拷貝到專案資料夾下。
  • uwsgi_params檔案一般在/etc/nginx/目錄下,也可以從nginx的github頁面下載。

uwsgi_params配置檔案如下:

uwsgi_param  QUERY_STRING       $query_string;
uwsgi_param  REQUEST_METHOD     $request_method;
uwsgi_param  CONTENT_TYPE       $content_type;
uwsgi_param  CONTENT_LENGTH     $content_length;

uwsgi_param  REQUEST_URI        $request_uri;
uwsgi_param  PATH_INFO          $document_uri;
uwsgi_param  DOCUMENT_ROOT      $document_root;
uwsgi_param  SERVER_PROTOCOL    $server_protocol;
uwsgi_param  REQUEST_SCHEME     $scheme;
uwsgi_param  HTTPS              $https if_not_empty;

uwsgi_param  REMOTE_ADDR        $remote_addr;
uwsgi_param  REMOTE_PORT        $remote_port;
uwsgi_param  SERVER_PORT        $server_port;
uwsgi_param  SERVER_NAME        $server_name;
  • 拷貝到專案下:
    sudo cp /etc/nginx/uwsgi_params /home/operation/work/luffy/

  • 屬主屬組等許可權重新修改
sudo chown operation:operation uwsgi_params
sudo chmod 664 /home/operation/work/luffy/uwsgi_params
  • 在專案資料夾下建立檔案luffy_nginx.conf,填入並修改下面內容:
# luffy_nginx.conf

# the upstream component nginx needs to connect to
upstream django {
    # server unix:///path/to/your/mysite/mysite.sock; 
    # for a file socket
    server 127.0.0.1:8083; 
    # for a web port socket (we`ll use this first)
}

# configuration of the server
server {
    # the port your site will be served on
    listen      8000;
    # the domain name it will serve for
    server_name luffy.tielemao.com; 
    # substitute your machine`s IP address or FQDN
    charset     utf-8;

    # max upload size
    client_max_body_size 75M;   # adjust to taste

    # Django media
    location /media  {
        alias /home/operation/work/luffy/media;  
        # your Django project`s media files - amend as required
    }

    location /static {
        alias /home/operation/work/luffy/static; 
        # your Django project`s static files - amend as required
    }

    # Finally, send all non-media requests to the Django server.
    location / {
        uwsgi_pass  django;
        include     /home/operation/work/luffy/uwsgi_params; 
        # the uwsgi_params file you installed
    }
}

這個配置檔案告訴nginx從檔案系統中拉起media和static檔案作為服務,
同時響應django的request。

為此,我也在相應的專案目錄下先建立起media和static資料夾。

為方便nginx使用,我在/etc/nginx/vhosts.d下建立了一個luffy_nginx.conf的軟連結。當然,也有人是直接用預設的sites-enabled目錄,自己看著來就行了。

sudo ln -s /home/operation/work/luffy/luffy_nginx.conf luffy.conf

django部署static檔案

  • 在專案資料夾下,建立好靜態檔案目錄:
    mkdir static

  • django的setting檔案中,新增一行:
    STATIC_ROOT = os.path.join(BASE_DIR, “static/”)

  • 執行
    python manage.py collectstatic

  • 在專案資料夾下,建立一個media媒體檔案目錄(用於存放圖片音樂等檔案做測試)
    mkdir media

在media目錄中,我傳入了一個圖片檔案princekin_fox.jpg用於測試nginx。

引數STATIC_ROOT用在哪?
通過python3 manage.py collectstatic收集所有你使用的靜態檔案儲存到STATIC_ROOT

STATIC_ROOT 資料夾 是用來將所有STATICFILES_DIRS中所有資料夾中的檔案,以及各app中static中的檔案都複製過來,把這些檔案放到一起是為了用nginx等部署的時候更方便。

重新載入nginx進行測試

先進行檢測,看之前的配置檔案有無錯誤。
sudo /usr/sbin/nginx -t

重新載入nginx讓軟連結的luffy_nginx.conf生效。
sudo /usr/sbin/nginx -s reload

訪問http://luffy.tielemao.com:8000/media/princekin_fox.jpg看能否正常訪問到圖片資源:
Nginx+uWSGI+Django部署web伺服器

注意在做這一步之前,我是己經配置好域名和傳送了一個用於測試的圖片到伺服器上。

我己經能成功訪問到資源了,所以接下來再做下一步測試。

測試nginx 應用 uWSGI 和 test.py

還記得之前寫好的一個測試的test-uwsgi.py檔案嗎?
我們就用配置好的nginx來訪問uwsgi啟動的test-uwsgi.py好了。
首先,啟動uwsgi,並且埠是nginx配置中的負載均衡池8083埠:
uwsgi --socket :8083 --wsgi-file test-uwsgi.py

訪問http://luffy.tielemao.com:8000/,實際上訪問的就是uwsgi的8083埠。
Nginx+uWSGI+Django部署web伺服器

如上圖,能正常訪問。表明下面的環節通暢:

the web client <-> the web server <-> the socket <-> uWSGI <-> Python

測試成功後中止程式,以便進行下面環節。

用UNIX socket取代TCP port

修改luffy_nginx.conf

# luffy_nginx.conf

# the upstream component nginx needs to connect to
upstream django {
    server unix:///home/operation/work/luffy/luffy.sock;
    # for a file socket
    # server 127.0.0.1:8083; 
    # for a web port socket (we`ll use this first)
}

上面其實是將原來的server 127.0.0.1:8083加以註釋,而原來加了#註釋的server unix:///home/operation/work/luffy/luffy.sock則取消了註釋,也就是不是直接指定埠,而是指定了一個sock檔案。

  • 要注意的是,這個luffy.sock並不是自己提前寫好的什麼配置檔案,而是由程式自動生成的。如圖:
    Nginx+uWSGI+Django部署web伺服器

重新載入nginx,然後在專案下執行uWSGI

uwsgi --socket /home/operation/work/luffy/luffy.sock --wsgi-file test-uwsgi.py 

訪問http://luffy.tielemao.com:8000/ 報502閘道器錯誤。
檢查nginx的錯誤日誌error.log,一般位置會在/var/log/nginx/error.log,建議使用tail察看檔案尾部的後10行。
會發現類似以下報許可權受阻的錯誤:

2018/08/27 20:17:44 [crit] 25771#25771: *1847865 connect() to unix:///home/operation/work/luffy/luffy.sock failed (13: Permission denied) while connecting to upstream, client: 223.72.74.11, server: luffy.tielemao.com, request: "GET / HTTP/1.1", upstream: "uwsgi://unix:///home/operation/work/luffy/luffy.sock:", host: "luffy.tielemao.com:8000"

那麼就中止uwsgi程式,新增上socket許可權後再次執行:

uwsgi --socket /home/operation/work/luffy/luffy.sock --wsgi-file test-uwsgi.py --chmod-socket=664

還是報錯的話,就需將operation使用者新增到nginx組中了,當然某些系統中nginx組也可能是www-data

將operation使用者新增到nginx組中,記得要加上-a引數,不然operation將會離開原來的operation組。

sudo usermod -a -G nginx operation

然後將專案目錄也歸屬到nginx組中

sudo chown operation:nginx -R luffy

這樣就能保證nginx能對luffy.sock有足夠的許可權了。
果然,設定好許可權後,就能正常訪問了。

每次都執行上面命令來拉起django專案實在不方便,我們可以將這些配置寫在.ini檔案中,能大大簡化工作。

停掉uwsgi服務後,見下一環節。

uwsgi部署加強

使用uwsgi配置檔案執行django專案

uwsgi支援ini,xml等多種配置方式,以ini為例,在/home/operation/work/conf目錄下新建uwsgi_luffy.ini,示例配置如下:

# luffy_uwsgi.ini file
[uwsgi]

# Django-related settings
# the base directory (full path)
# 指定執行目錄,其實就是專案的根目錄
chdir = /home/operation/work/luffy

# Django`s wsgi file
# 匯入django專案的wsgi模組
module = luffy.wsgi:application

# 載入wsgi-file
wsgi-file = luffy/wsgi.py

# the virtualenv (full path)
# 配置虛擬環境python相關依賴包所在路徑
home = /home/operation/work/py3env

# 補充,uwsgi啟動時的使用者和使用者組,注意要按你自己的實際許可權進行配置
# 我這邊是因為專案屬主是operation,而operation我設定了新增進了nginx組
# 但這種情況下uid仍不能設定nginx,除非你的專案目錄所有檔案屬主都改為了nginx
uid = operation
gid = nginx

# process-related settings
# 開啟master主程式
master = true 

# maximum number of worker processes
# 開啟多少個程式數,workers項也等同processes
# threads項則是設定執行執行緒,測試倒不用設定上執行緒
processes = 4

# the socket (use the full path to be safe)
# 設定使用的socket埠或socket地址
# socket = 0.0.0.0:8000
# 上面的socket建議配置成一個luffy.socket檔案後使用nginx來連線uWSGI執行,不然容易報socket的請求頭錯誤和許可權錯誤等。
socket = /home/operation/work/luffy/luffy.socket

# ... with appropriate permissions - may be needed
# 配置生成的sock檔案的許可權
chmod-socket = 664 

# clear environment on exit
# 退出時清空環境,其實就是將自動生成的luffy.sock和相關pid檔案給幹掉。
vacuum = true

uwsgi指定配置檔案啟動django專案,建議使用nginx使用者執行:
uwsgi --ini /home/operation/work/conf/uwsgi_luffy.ini

  • 注:以上啟動後如果你是在配置檔案中直接指定的socket = 0.0.0.0:8000可能會產生如下問題:瀏覽器訪問伺服器8000埠加上url後,瀏覽器會報連線出錯,而伺服器執行後臺也會看到如下錯誤資訊:
    invalid request block size: 21573 (max 4096)...skip

之前我們直接使用http協議的方式就不會出現塊請求大小超出

 uwsgi --http :8000 --module luffy.wsgi

究其原因,使用配置檔案啟動是以socket方式提供通訊埠,預設的協議是tcp,和http不同。socket請求頭預設大小是4096,所以請求頭超出預設大小後就會出現錯誤。當然後面我們可以通過和nginx合作的方式解決。

而如果只是想測試,那麼只要在上面的命令後面再指定塊請求大小-b 24576之類的便可以解決。

我們中止uwsgi後重新指定塊大小,執行命令:
uwsgi -b 24576 --ini /home/operation/work/conf/uwsgi_luffy.ini
可以解決請求頭錯誤。

不過ini配置檔案主要是用於和nginx配合,這也是為什麼前面講述完nginx的部署後再回過頭來講uwsgi的ini配置檔案。

  • 將uWSGI中使用的相同選項放入一個配置檔案中,然後要求uWSGI使用該檔案執行。這會使得管理配置變得更加容易。

  • 要注意的是,因為要配合nginx,所以生成的專案名.sock檔案,nginx需要能有許可權讀寫。

如下圖:
Nginx+uWSGI+Django部署web伺服器
由於我前面執行uwsgi命令時使用的是operation使用者,
這樣子自動生成的luffy.sock檔案屬組並不是nginx的,所以ini配置檔案中最好加上uid和gid的配置項使用nginx使用者執行uwsgi。

# uwsgi啟動使用者名稱和使用者組
uid = operation
gid = nginx

我是修改完luffy.sock的屬組為nginx後就能正常訪問到django專案了,不然會被nginx報502錯誤。

安裝uWSGI到真實環境中

到目前為止,我們都是在虛擬環境下工作的,接下來介紹將uwsgi裝在實際環境中,且將uwsgi加入到nginx組(有的是www-data組)中,就可避免現在所遇到的許可權等問題。

  • 退出虛擬環境
    deactivate

  • 安裝uWSGI,注意要使用python3中的pip3來進行安裝。我係統中是python2.7和python3.5.6共存的,不過預設的環境是2.7。
    sudo /usr/local/python3.5.6/bin/pip3 install uwsgi

  • 之前我使用預設的pip進行安裝,結果是python2版本的uwsgi,在執行虛擬環境中的python3的django專案時會報以下錯誤:
Python version: 2.7.13 (default, Jan 03 2017, 17:41:54) [GCC]
Set PythonHome to /home/operation/work/py3env
ImportError: No module named site
VACUUM: unix socket /home/operation/work/luffy/luffy.sock removed.

報模組匯入錯誤,原因還是在於我真實環境使用的是python2.7版本的原因,ini中配置了虛擬環境家目錄是python3的依賴庫,pip安裝的uwsgi不能正確匯入。

  • 再次檢查,在真實環境中是否能如同虛擬環境之前一樣能執行django專案。
    當然,由於我前面的python3沒有匯入系統環境中,所以此處仍然要像前面使用python3的pip3一樣,需要打全路徑:
sudo /usr/local/python-3.5.6/bin/uwsgi --ini /home/operation/work/conf/uwsgi_luffy.ini
  • 模組無法匯入的錯誤己排除了,但是還會報一個bind繫結拒絕的錯誤。
    Nginx+uWSGI+Django部署web伺服器

出現這個錯誤還是和檔案許可權有關,因為我之前uwsgi的ini配置檔案中uid設定的使用者是nginx,而專案真正的屬主是operation這個使用者,所以我將uid設定回operation就解決問題了,當然將專案整個屬主設為nginx使用者也是可行的。

# 因為許可權問題踩了不少坑,在此繼續加強強調。
# 注意的是你要按自己的實際環境配置
uid = operation
gid = nginx

成功執行uwsgi的截圖我也放出給大家對比一下看吧。
當然,訪問頁面成功的截圖就不放了,和之前測試成功的是一樣的。

Nginx+uWSGI+Django部署web伺服器

uwsgi配置檔案更多引數

前面己經給大家看到了uwsgi的ini檔案的一些配置引數,在此以luffy專案為例,再介紹一些常用到的引數:

# set an environment variable
# 設定變數環境,其實就是應用上django專案中的settings
env = DJANGO_SETTINGS_MODULE=luffy.settings

# create a pidfile
# 建立pid檔案,注意許可權,所以一般放置在tmp目錄下
pidfile = /tmp/project-master.pid 

# background the process & log
# 後臺執行,有用,基本都會設定上,同時還會輸出日誌
daemonize = /var/log/uwsgi/luffy.log 

# 以固定的檔案大小(單位kb),進行切割日誌
# 下例為50M大小一個日誌檔案
log-maxsize = 50000000

# 不記錄請求資訊的日誌。只記錄錯誤以及uWSGI內部訊息到日誌中。
disable-logging = true
#
# 在指定的地址上,開啟狀態服務。應該就是你訪問該地址,就能看到整個uwsgi
的狀態,類似nginx也有。所以一般會只在環回地址上開放。
stats = 127.0.0.1:8009

# 以下都是一些效能和資源上的限制和調配
# respawn processes taking more than 20 seconds
harakiri = 20 
# limit the project to 128 MB
# 控制程式的總記憶體量
limit-as = 128 
# respawn processes after serving 5000 requests
max-requests = 5000 
  • 訪問stats狀態服務頁面大致如下:
    Nginx+uWSGI+Django部署web伺服器

uWSGI開機啟動服務

繼續以django的luffy專案為例,讓uWSGI開機就啟動服務,編輯檔案/etc/rc.local, 新增下面內容到這行程式碼之前exit 0:

/usr/local/python-3.5.6/bin/uwsgi --ini /home/operation/work/conf/uwsgi_luffy.ini

要注意的是記得ini配置檔案中設定好使用者使用者組,sock檔案,sock檔案的許可權和後臺執行等配置。

end
by 鐵樂與貓

參考

相關文章