2015/12/09

在Raspbian上安裝nginx、PHP、Django與uWSGI

這篇要在Raspberry Pi 2與Raspbian(2015-11-21)上頭,安裝nginx、PHP、uWSGI、Django,都不算是小軟體,設定都很複雜,執行選項與參數眾多,非三言兩語能道盡,這一篇僅是簡短記錄我的安裝經驗;若有問題,還請查閱官方文件。

輕量型網站伺服器nginx(發音engine x),比起龐大的Apache,更適合用於資源受限的機器,如Raspberry Pi。前端的網站伺服器nginx搞定後,接下來會安裝PHP,以及Django與uWSGI。 底下記錄如何自行下載原始碼並編譯安裝,因為若是打包好的套件,其版本往往過於老舊。

以下分成三個部分:nginx、PHP、Django與uWSGI,後兩個部分各自獨立。

nginx的部份:

首先是nginx編譯時以及使用時需要的套件。
$ sudo apt-get install gcc build-essential libpcre3 libpcre3-dev zlib1g zlib1g-dev openssl libssl-dev

其中gcc與build-essential是建置工具,libpcre3與libpcre3-dev是Perl Compatible Regular Expression(PCRE),zlib1g與zlib1g-dev是壓縮工具,openssl與libssl-dev則是SSL/TLS。

下載nginx原始檔並解壓縮。我下載的是1.9.7版(約865 KB),可到nginx官網查看最新版本。
$ wget http://nginx.org/download/nginx-1.9.7.tar.gz
$ tar zxvf nginx-1.9.7.tar.gz

然後進行組態設定,configure有很多參數可設定,此處都使用預設值,因此預設安裝路徑是/usr/local/nginx:
$ cd nginx-1.9.7
$ ./configure

但是最後卻出現底下訊息:
Configuration summary
  + using system PCRE library
  + OpenSSL is not used
  + md5: using system crypto library
  + sha1: using system crypto library
  + using system zlib library

呃,因為預設關閉SSL模組,所以把指令改成:
$ ./configure --with-http_ssl_module

訊息就會變成「+ using system OpenSSL library」。

然後進行編譯建置與安裝:
$ make
$ sudo make install

預設安裝到/usr/local/nginx,若欲修改,請在執行configure時加上參數--prefix,例如「--prefix=/usr/local/nginx-1.9.7」。

然後試著查詢看看吧,可看到版本編號與組態參數。
$ cd /usr/local/nginx
$ ./nginx -V
nginx version: nginx/1.9.7
built by gcc 4.9.2 (Raspbian 4.9.2-10)
built with OpenSSL 1.0.1k 8 Jan 2015
TLS SNI support enabled
configure arguments: --with-http_ssl_module

接著實際執行,若一切正常,nginx執行檔不會有任何輸出。
$ cd /usr/local/nginx
$ sudo ./nginx

然後請開啟瀏覽器,輸入nginx所在機器的IP位址,應可看到如下畫面,代表nginx動起來囉。

該網頁檔位於/usr/local/nginx/html/index.html,你可試著修改看看。

此時若再試著執行一次,將會出現錯誤,因為連接埠80已被佔用。
$ cd /usr/local/nginx
$ sudo ./nginx
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
...
nginx: [emerg] still could not bind()

透過其他參數控制nginx的啟動與停止:
$ cd /usr/local/nginx
$ sudo ./nginx -s stop

stop代表停止(使用TERM訊號),quit也是停止(使用QUIT訊號),reopen是重新開啟記錄檔,reload是重新載入組態檔。

如果stop與quit都無法停止nginx的話,可到/usr/local/nginx/logs/nginx.pid,檔案裡放的是nginx形成的pid(另可以指令ps -ax | grep nginx查詢),然後以指令kill殺掉行程。

因為是自己從原始碼檔安裝,所以沒有init腳本檔,開機時不會自動執行;請另行下載這支init腳本檔,把/opt的部份改成你的安裝路徑(本篇使用預設值/usr/local);檔名取為nginx,放在/etc/init.d/裡,修改擁有者以及執行權限。

然後便能以底下指令(其中一個即可),啟動nginx作為系統服務。
$ sudo /etc/init.d/nginx start

$ sudo service nginx start

其他參數還有stop、restart、status等等。

若想在開機時自動執行,指令如下。
$ sudo update-rc.d -f nginx defaults

然後重開機,看看nginx是否已成為服務在背景執行。

PHP的部份:

安裝PHP之前,先安裝相依套件:
$ sudo apt-get install libxml2-dev libevent-dev

接著要安裝PHP,並使用PHP的FastCGI process manager(PHP-FPM)。首先要下載PHP原始碼檔,並無直接的網址,請到官網找尋下載連結,我下載的是5.6.16版,檔名是php-5.6.16.tar.gz。然後解壓縮,切換目錄:
$ tar zxvf php-5.6.16.tar.gz
$ cd php-5.6.16

然後進行組態設定、建置編譯、測試與安裝:
$ ./configure --enable-fpm
$ make -j4
$ make test
$ sudo make install

執行configure時應加上參數--enable-fpm,啟用PHP-FPM。make指令加上參數-j4,因為Pi 2有四個處理器核心。make test測試通過後,進行安裝動作。

然後複製相關設定檔:
$ sudo cp php.ini-development /usr/local/php/php.ini
$ sudo cp /usr/local/etc/php-fpm.conf.default /usr/local/etc/php-fpm.conf

把PHP-FPM的執行檔複製到正確的目錄:
$ sudo cp sapi/fpm/php-fpm /usr/local/bin

修改PHP設定檔/usr/local/php/php.ini,找到cgi.fix_pathinfo並設為0,可避免安全漏洞。
cgi.fix_pathinfo=0

修改PHP-FPM設定檔/usr/local/etc/php-fpm.conf,設定正確的擁有者與群組:
user = www-data
group = www-data

然後便可執行PHP-FPM,預設應會使用埠號9000:
$ sudo /usr/local/bin/php-fpm
(本篇不讓它成為系統服務、重開機時不會自動執行,有興趣者請自行動手。)

然後要修改nginx的設定檔/usr/local/nginx/conf/nginx.conf,讓它能處理.php的請求,轉交給PHP-FPM,大致上應含有底下兩個區塊。溫馨提醒:修改前應先備份。
location / {
    root html;
    index index.php index.html index.htm;
}

location ~* \.php$ {
    fastcgi_index index.php;
    fastcgi_pass 127.0.0.1:9000;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param SCRIPT_NAME $fastcgi_script_name;
}

然後重新啟動nginx:
$ sudo service nginx restart

$ /usr/local/nginx/sbin/nginx -s reload

在/usr/local/nginx/html/下建立檔案index.php,放入如下內容:
<?php phpinfo(); ?>

以瀏覽器載入,應可看到如下畫面:
接著要換成Python語言,可先把nginx的設定檔/usr/local/nginx/conf/nginx.conf改回原狀。

Django與uWSGI的部份:

WSGI(Web Server Gateway Interface)由PEP 333與PEP 3333制定標準,乃是Python網頁應用程式(譬如Django專案)與外界(網站伺服器)溝通的介面。很久以前,Python網頁應用程式開發框架,都只能連接特定的介面與API,譬如CGI、FastCGI、mod_python等,甚至只能與某種網站伺服器溝通,若兩端能夠遵從WSGI規格,開發人員便可自由地選用想要的網站伺服器(如nginx、Apache)與Python網頁應用程式開發框架(如Django、Flask)。

安裝開發Python程式的相關套件:
$ sudo apt-get install python python-dev python-pip

python應是Python 2.x版,python-dev包含建置Python模組所需的標頭檔、靜態程式庫與開發工具,python-pip是Python的套件管理工具。

我本來想先試試FastCGI,於是安裝了python-flup,包含三套WSGI伺服器(閘道器)實作,分別是FastCGI、AJP(Apache JServ Protocol 1.3)、SCGI(Simple CGI)。照理說應可使用Django的指令runfcgi(它會使用flup),便可啟動測試用的伺服器,可惜沒成功。Django自1.7版開始揚棄FastCGI,於1.9版移除。

接著使用指令pip來安裝Django,我安裝的是1.9.0版:
$ sudo pip install Django==1.9.0
Collecting Django==1.9.0
  Downloading Django-1.9-py2.py3-none-any.whl (6.6MB) 
    100% |████████████████████████████████| 6.6MB 7.0kB/s
Installing collected packages: Django   
Successfully installed Django-1.9

建立Django專案,取名為mysite:
$ django-admin startproject mysite

切換目錄,並且執行測試用伺服器,請把192.168.1.15換成你的IP位址。
$ cd mysite
$ python manage.py runserver 192.168.1.15:8000
Performing system checks...

System check identified no issues (0 silenced).

You have unapplied migrations; your app may not work properly until they are applied.
Run 'python manage.py migrate' to apply them.

December 08, 2015 - 14:14:34
Django version 1.9, using settings 'mysite.settings'
Starting development server at http://192.168.1.15:8000/
Quit the server with CONTROL-C.

啟動瀏覽器,輸入網址並加上埠號,例如「192.168.1.15:8000」,應會看到如下畫面。
同時runserver那端應會輸出類似底下的訊息:
[08/Dec/2015 14:16:22] "GET / HTTP/1.1" 200 1767

哇,以上已經成功建立Django專案,並且執行測試用伺服器,從瀏覽器(客戶端)存取。

接下來要安裝uWSGI伺服器:
$ sudo pip install uWSGI

先撰寫符合WSGI規範的簡單Python程式,取名為uWSGI_test.py:
def application(env, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    return ["Hello World"] # python2
    #return [b"Hello World"] # python3
   
然後直接讓uWSGI伺服器同時擔任網站伺服器的角色,並執行uWSGI_test.py:
$ uwsgi --http :8000 --wsgi-file uWSGI_test.py

開啟瀏覽器,載入網址,譬如「192.168.1.15:8000」,應可看到「Hello World」字樣,代表成功了。

測試過簡單的程式之後,該讓先前建立的Django專案登場了吧:
$ cd mysite
$ uwsgi --http :8000 --module mysite.wsgi

同樣以瀏覽器載入網址,應也會看到先前成功執行Django專案、寫著「It worked!」的畫面。

以上的作法是瀏覽器經由HTTP協定與uWSGI伺服器溝通,它把請求轉成符合WSGI協定的形式,交由uWSGI_test.py或Django專案處理並回應。

接下來要把nginx放進去,由uWSGI擔任nginx與Django之間的中介角色。nginx早已加入uWSGI模組提供支援,nginx透過uWSGI伺服器原生支援的uwsgi協定與之溝通(效率最高),然後uWSGI伺服器透過轉接功能轉成WSGI形式,交給Django處理。

修改nginx設定檔/usr/local/nginx/conf/nginx.conf,把底下內容放在http區塊之內:
    upstream django {
        server 127.0.0.1:8001;
    }

    server {
        listen 8000;
        server_name localhost;
        charset utf-8;
        location / {
            uwsgi_pass django;
            include uwsgi_params;
        }
    }

其中將透過埠號8001與uWSGI伺服器(以及它之後的WSGI程式,如Django專案)溝通,另開啟埠號8000供外界存取。

然後重新啟動nginx、載入新設定值:
$ sudo service nginx restart

接著先試試小小的測試程式:
$ uwsgi --socket :8001 --wsgi-file uWSGI_test.py

再試試Django專案:
$ cd mysite
$ uwsgi --socket :8001 --module mysite.wsgi

以瀏覽器載入網址,如「192.168.1.15:8000」,確認可看到如同先前的畫面,「Hello World」與「It worked!」,耶。

也可讓瀏覽器載入網址「192.168.1.15:8001」,直接存取uWSGI伺服器,但應會失敗,因為此時它不是說HTTP協定,瀏覽器應出現錯誤畫面,而uWSGI端大概會出現類似的錯誤訊息「invalid request block size: 21573 (max 4096)...skip」。

上述作法使用TCP socket,但也可改用Unix socket,照理說負擔較低。請修改nginx設定檔/usr/local/nginx/conf/nginx.conf:

    upstream django {
        # server 127.0.0.1:8001;
        server unix:///home/pi/mysite/mysite.sock;
    }

存檔後重新啟動nginx:
$ sudo service nginx restart

接著是uWSGI的部份,指令應改為:
$ uwsgi --socket mysite.sock --module mysite.wsgi

$ uwsgi --socket mysite.sock --module mysite.wsgi --chmod-socket=666

因為必須給予適當權限,才能讓nginx使用該socket。若成功的話,以瀏覽器載入,應會出現「It worked!」的畫面。

架構如下:
瀏覽器 - 網站伺服器(nginx) - socket(uwsgi協定) - uWSGI伺服器 - Python程式(Django專案)

uWSGI還能讀取.ini設定檔、以emperor模式執行,也應該成為系統服務,在開機時自動啟動(或可參考此處的init腳本檔)。

參考資料:

No comments:

Post a Comment