介紹如何使用 Django 搭配 PostgreSQL 資料庫、Nginx 與 Gunicorn 網頁伺服器,開發與布署基礎的網頁應用程式。
安裝必要套件
這裡我們選擇採用 Python 3 的環境進行開發,搭配 PostgreSQL 資料庫、Nginx 網頁伺服器,這些相關套件可透過 apt
安裝:
# 安裝 Pyton3、PostgreSQL、Nginx、curl 相關套件 sudo apt install python3-pip python3-dev libpq-dev \ postgresql postgresql-contrib nginx curl
至於 Gunicorn 會在後續的開發過程中透過 pip
安裝。
設定 PostgreSQL 資料庫
進入 PostgreSQL 資料庫的 psql
互動式指令操作環境:
# 進入 PostgreSQL 資料庫操作環境 sudo -u postgres psql
在 PostgreSQL 資料庫中,建立一個專門用於 Django 專案的資料庫(這裡我們將此資料庫命名為 myproject
):
-- 建立 myproject 資料庫 CREATE DATABASE myproject;
接著建立一個專案專用的 PostgreSQL 資料庫帳號(帳號名稱取為 myprojectuser
):
-- 建立 myprojectuser 使用者帳號 CREATE USER myprojectuser WITH PASSWORD 'password';
其中 password
是設定的密碼,可自由更換。
以下是一些預設的 PostgreSQL 資料庫帳號連線設定,設定好這些設定可以加速資料庫的操作。
-- 設定預設編碼 ALTER ROLE myprojectuser SET client_encoding TO 'utf8'; -- 設定預設 transaction isolation 規則 ALTER ROLE myprojectuser SET default_transaction_isolation TO 'read committed'; -- 設定時區 ALTER ROLE myprojectuser SET timezone TO 'Asia/Taipei';
Django 預設的編碼是採用 UTF8,所以這裡將 PostgreSQL 的 client_encoding
也設定為 utf8
,而時區的部分 Django 預設是 UTC,建議改為自己所在的時區,以台灣來說就是 Asia/Taipei
。
最後授予 myprojectuser
使用者在 myproject
資料庫上的所有權限:
-- 授予 myprojectuser 使用者在 myproject 資料庫上的所有權限 GRANT ALL PRIVILEGES ON DATABASE myproject TO myprojectuser;
建立 Python 虛擬環境
先將 pip
更新至最新版之後,安裝 virtualenv
:
# 更新 pip 至最新版本 sudo -H pip3 install --upgrade pip # 安裝 virtualenv sudo -H pip3 install virtualenv
建立一個 Django 專案目錄 myprojectdir
,並在專案目錄中建立一個專案用的 Python 虛擬環境:
# 建立專案目錄 mkdir ~/myprojectdir cd ~/myprojectdir # 建立 Python 虛擬環境 virtualenv myprojectenv
啟用新建的 Python 虛擬環境:
# 啟用 Python 虛擬環境
source myprojectenv/bin/activate
在專用的 Python 虛擬環境中,使用 pip
安裝 Django、Gunicorn 與 PostgreSQL 相關的 Python 模組:
# 安裝 Django、Gunicorn 與 PostgreSQL 相關的 Python 模組
pip install django gunicorn psycopg2-binary
建立 Django 專案
在剛建立好的專案目錄 ~/myprojectdir
中,建立一個新的 Django 專案 myproject
:
# 建立 Django 專案
django-admin startproject myproject ~/myprojectdir
此時 ~/myprojectdir
目錄中會包含以下三個項目:
~/myprojectdir/manage.py
:Django 專案管理用的指令稿。~/myprojectdir/myproject/
:Django 專案套件目錄,內含__init__.py
、settings.py
、urls.py
、asgi.py
與wsgi.py
這幾個檔案。~/myprojectdir/myprojectenv/
:之前建立的 Python 虛擬環境。
Django 專案建立好之後,首先要修改 ~/myprojectdir/myproject/settings.py
這個 Django 專案的設定檔。Django 預設所使用的資料庫是 SQLite,這裡我們要將 SQLite 改為 PostgreSQL,將上面建立好的 PostgreSQL 資料庫、帳號與密碼填入其中,而這裡 PostgreSQL 的轉接器(adaptor)我們採用剛剛以 pip
安裝的 psycopg2
:
# PostgreSQL 資料庫連線設定 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'myproject', # 資料庫名稱 'USER': 'myprojectuser', # 帳號 'PASSWORD': 'password', # 密碼 'HOST': 'localhost', # 資料庫主機 'PORT': '', # 連接埠(預設為 5432) } }
Django 可以透過 ALLOWED_HOSTS
設定 Django 應用程式允許提供服務的主機或網域名稱(也就是瀏覽器網址列上的呈現的主機位址),以下是一些設定範例:
# 設定允許提供服務的主機名稱與 IP 位址 ALLOWED_HOSTS = [ 'example.com', '192.168.1.2'] # 設定允許提供服務的網域、子網域與 IP 位址 ALLOWED_HOSTS = [ '.example.com', '192.168.1.2'] # 設定允許提供服務的 IP,並開放本機存取權限 ALLOWED_HOSTS = [ '192.168.1.2', '.localhost', '127.0.0.1', '[::1]']
設定 Django 專案的靜態檔案網址以及布署路徑:
# 靜態檔案網址 STATIC_URL = '/static/' # 靜態檔案布署路徑 STATIC_ROOT = BASE_DIR / 'static'
設定使用中文語系,以及台北時區:
# 設定中文語系 LANGUAGE_CODE = 'zh-Hant' # 設定台北時區 TIME_ZONE = 'Asia/Taipei'
初始化資料庫
建立 Django 的資料庫遷移(migration)指令稿,並執行資料庫遷移:
# 建立資料庫遷移(migration)指令稿 ~/myprojectdir/manage.py makemigrations # 執行資料庫遷移 ~/myprojectdir/manage.py migrate
建立 Django 專案管理者帳號:
# 建立 Django 專案管理者帳號
~/myprojectdir/manage.py createsuperuser
建立管理者帳號時,要輸入帳號名稱與 Email,並設定密碼。
Django 靜態檔案
在正式的服務環境中,會將靜態檔案與動態的 Python 指令稿分開處理,通常動態的 Python 指令稿會讓 Python 的 WSGI 網頁伺服器(例如 Gunicorn)處理,而靜態檔案則交給普通的網頁伺服器(例如 Nginx)。
Django 應用程式開發完成之後,可以執行以下指令將所有應用程式中的靜態(static)檔案都布署至 STATIC_ROOT
所指定目錄之下:
# 布署靜態檔案
~/myprojectdir/manage.py collectstatic
實際上這個靜態檔案布署指令是在 Django 專案開發完成之後,在布署階段才需要執行的,此處是先進行測試。
測試 Django 應用程式
執行 Django 測試用的內建網頁伺服器,讓網頁伺服器開在每一個網路介面的 8000
連接埠:
# 啟動 Django 網頁伺服器 ~/myprojectdir/manage.py runserver 0.0.0.0:8000
接著看看是否可以正常開啟 http://server_domain_or_IP:8000/
這個網址,其中 server_domain_or_IP
就換成自己伺服器的主機名稱或 IP 位址。(此處的 server_domain_or_IP
記得要加入 ALLOWED_HOSTS
中)
若可以正常看到 Django 專案預設畫面,就表示一切正常,接著就可以按下 Ctrl + c 停止 Django 內建的網頁伺服器。
測試 Gunicorn 伺服器
設定好 Django 應用程式之後,要測試 Django 應用程式是否可以正常在 Gunicorn 伺服器中運行。
在 Django 應用程式中,預設就有提供最基本的 WSGI 設定檔 myproject/wsgi.py
,這個檔案也是一個 Python 模組,其中建立的 application
變數就是 Gunicorn 伺服器跟應用程式溝通的管道,Gunicorn 伺服器可以直接透過這個設定檔來運行 Django 應用程式:
# 使用 Gunicorn 伺服器運行 Django 應用程式 cd ~/myprojectdir gunicorn --bind 0.0.0.0:8000 myproject.wsgi
執行之後的效果會跟 Django 內建伺服器類似,但是在這裡使用 Gunicorn 伺服器時,靜態(static)檔案會出現找不到的問題,這是正常的。這裡我們同樣檢查 http://server_domain_or_IP:8000/
這個頁面,除了靜態檔案之外是否都可以正常運作,若正常就表示沒問題。
測試完成後,就可以按下 Ctrl + c 停止 Gunicorn 網頁伺服器。最後離開 Python 虛擬環境:
# 離開 Python 虛擬環境
deactivate
設定 systemd
系統服務
在設定與測試完 Django 應用程式與 Gunicorn 網頁伺服器之後,我們接著要設定讓系統的 systemd
可以自動管理 Django 應用程式與 Gunicorn 網頁伺服器的啟動、運行與停止。
這裡我們將建立一個 Gunicorn 的 socket 設定檔,以及另一個 Gunicorn 的 service 設定檔,在此設定之下,systemd
會開啟一個 UNIX socket 傾聽 Gunicorn 的請求,當收到請求的時候,就自動啟動 Gunicorn 伺服器來進行處理。
新增 /etc/systemd/system/gunicorn.socket
設定檔,填入以下內容:
[Unit] Description=gunicorn socket [Socket] ListenStream=/run/gunicorn.sock # Our service won't need permissions for the socket, since it # inherits the file descriptor by socket activation # 只有 Nginx 服務會需要存取此 socket SocketUser=www-data # 更嚴格限制 socket 權限 SocketMode=600 [Install] WantedBy=sockets.target
新增 /etc/systemd/system/gunicorn.service
設定檔,填入以下內容:
[Unit] Description=gunicorn daemon Requires=gunicorn.socket After=network.target [Service] Type=notify # 執行服務的使用者與群組 User=myuser Group=www-data # 可寫入的執行期間目錄(位於 /run/ 之下) RuntimeDirectory=gunicorn # 工作目錄 WorkingDirectory=/home/myuser/myprojectdir # 服務執行指令 ExecStart=/home/myuser/myprojectdir/myprojectenv/bin/gunicorn \ --access-logfile - \ --workers 3 \ --bind unix:/run/gunicorn.sock \ myproject.wsgi:application # 重新載入指令 ExecReload=/bin/kill -s HUP $MAINPID # 服務終止模式 KillMode=mixed # 停止服務等待時間 TimeoutStopSec=5 # 是否有獨立的暫存空間 PrivateTmp=true [Install] WantedBy=multi-user.target
設定好 gunicorn.socket
與 gunicorn.service
設定檔之後,啟用 gunicorn.socket
服務,並將其設定為開機自動啟動:
# 立即啟動、同時設定開機自動啟動 gunicorn.socket sudo systemctl enable --now gunicorn.socket
啟動 gunicorn.socket
服務之後,檢查該服務的狀態:
# 檢查 gunicorn.socket 服務狀態
systemctl status gunicorn.socket
若要查看 gunicorn.socket
服務的記錄(Gunicorn 伺服器的輸出訊息),可以使用 journalctl
指令查看:
# 查看 gunicorn.socket 服務記錄 journalctl -u gunicorn.socket
我們可以使用 curl
發送測試的 HTTP 請求,看看 gunicorn.service
服務是否有正常被啟動:
# 測試 Gunicorn 網頁伺服器 sudo -u www-data curl --unix-socket /run/gunicorn.sock localhost
正常來說,systemd
收到 HTTP 請求之後,會自動啟動 gunicorn.service
來處理 HTTP 請求,所以執行之後會看到正常的 HTML 網頁原始碼。
我們可以接著檢查 gunicorn.service
服務的狀態:
# 檢查 gunicorn.service 服務狀態
systemctl status gunicorn.service
正常來說,這時候的 gunicorn.service
服務是處於運行狀態的。
如果在執行 curl
或查看 gunicorn.service
服務狀態出現問題,可以使用 journalctl
指令查看 gunicorn.service
服務記錄:
# 查看 gunicorn.service 服務記錄 journalctl -u gunicorn.service
如果在除錯的過程中,修改了 gunicorn.service
設定檔內容,就要讓 systemd
重新載入設定檔,再重新啟動 gunicorn.service
服務:
# 讓 systemd 重新載入設定檔 sudo systemctl daemon-reload # 重新啟動 gunicorn.service sudo systemctl restart gunicorn
Nginx 網頁伺服器
Gunicorn 伺服器設定完畢之後,最後一步就是設定 Nginx 網頁伺服器,讓外部的連線導入 Gunicorn 伺服器。
新增一個 Nginx 網站設定檔 /etc/nginx/sites-available/myproject
,內容如下:
# Django 應用程式 server { # 傾聽埠號 listen 80; listen [::]:80; # 伺服器主機名稱 server_name server_domain_or_IP; # 忽略找不到 favicon.ico 的錯誤 location = /favicon.ico { access_log off; log_not_found off; } # 靜態檔案路徑 location /static/ { root /home/myuser/myprojectdir; } # 動態網頁 location / { include proxy_params; proxy_pass http://unix:/run/gunicorn.sock; } }
建立一個連結檔,將 /etc/nginx/sites-available/myproject
連結至 /etc/nginx/sites-enabled
目錄之下:
# 建立連結檔 sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled
測試 Nginx 設定檔,看看是否有錯誤:
# 測試 Nginx 設定檔 sudo nginx -t
重新啟動 Nginx 網頁伺服器服務:
# 重新啟動 Nginx 網頁伺服器服務
sudo systemctl restart nginx
這樣就完成 Nginx 與 Gunicorn 伺服器的整合設定了。
以 Certbot 安裝 SSL 憑證
若希望在 Nginx 網頁伺服器上加裝 SSL 憑證,採用 HTTPS 的加密傳輸,可以在設定好主機的 DNS 正解記錄之後,透過 Certbot 安裝免費的 SSL 憑證。
參考 Certbot 官方網站上的安裝步驟,首先將 snap
更新至最新版:
# 更新 snap 至最新版
sudo snap install core
sudo snap refresh core
透過 snap
安裝 certbot
:
# 安裝 certbot sudo snap install --classic certbot sudo ln -s /snap/bin/certbot /usr/bin/certbot
透過 certbot
取得並安裝 SSL 憑證至 Nginx 伺服器:
# 取得並安裝憑證至 Nginx 伺服器 sudo certbot --nginx
這樣就完成整套的 Django 應用程式運行環境的建置與布署了。若要更進一步停用不安全的 TLS 1.1 與 TLS 1.0 協定,只使用 TLS 1.2 與 TLS 1.3 協定,可以參考 Ubuntu Linux 設定 Nginx 使用 TLS 1.2 與 1.3 教學與範例。
Ubuntu 防火牆
若 Ubuntu Linux 有啟用 UFW 防火牆,在開發與測試階段,可以執行以下指令開啟 Django 所使用的 TCP 連接埠,預設的的埠號為 8000
:
# 開啟 Ubuntu 防火牆 TCP 的 8000 連接埠 sudo ufw allow 8000/tcp
或是直接將 UFW 防火牆暫時關閉:
# 停用 Ubuntu 防火牆
sudo ufw disable
當開發與測試完成後,可恢復防火牆設定:
# 刪除 Ubuntu 防火牆規則 sudo ufw delete allow 8000/tcp # 啟用 Ubuntu 防火牆 sudo ufw enable
而在正式服務階段,只有 Nginx 網頁伺服器會直接接受外部的連線,所以只需要開啟 Nginx 網頁伺服器所需的防火牆設定:
# 開啟 Nginx 網頁伺服器用的防火牆 sudo ufw allow 'Nginx Full'
記錄檔
若需要將 Gunicorn 的紀錄訊息紀錄至 /var/log/
之下,可以修改 /etc/systemd/system/gunicorn.service
設定檔,將服務執行指令加上記錄檔位置,並設定 PID 檔案位置:
# 服務執行指令 ExecStart=/home/myuser/myprojectdir/myprojectenv/bin/gunicorn \ --access-logfile /var/log/gunicorn/access.log \ --error-logfile /var/log/gunicorn/error.log \ --pid /var/run/gunicorn/gunicorn.pid \ --workers 3 \ --bind unix:/run/gunicorn.sock \ myproject.wsgi:application
接著建立放置記錄檔的目錄,並設定權限:
# 建立放置記錄檔的目錄 mkdir /var/log/gunicorn # 設定權限 chown myuser:www-data /var/log/gunicorn/
然後重新載入 systemd
設定檔,並重新啟動 gunicorn.service
服務:
# 讓 systemd 重新載入設定檔 sudo systemctl daemon-reload # 重新啟動 gunicorn.service sudo systemctl restart gunicorn
最後新增一個 /etc/logrotate.d/gunicorn
設定檔,內容如下:
/var/log/gunicorn/*.log { weekly missingok rotate 52 compress delaycompress notifempty create 0640 health www-data postrotate kill -USR1 $(cat /var/run/gunicorn/gunicorn.pid) endscript }