Ubuntu上使用Gunicorn + Nginx + SSL部署Django+Vue專案

Cancell 抗癌好夥伴」上線前卡在Nginx設定以及Django + Vue的配置一陣子。經過努力嘗試後最終成功突破,決定寫個筆記分享心得。

周子堯 Victor.Chou
14 min readDec 9, 2020

簡單介紹

  • Django
    一個開放原始碼的Web應用框架,由Python寫成。採用了MVT的軟體設計模式,即模型,視圖和模板。Cancell 捨棄了Django的後端渲染模板,採用前後端分離方式做開發。
  • Vue
    前端框架三大天王之一,採用Virtual Dom且具有優異的效能,學習曲線普通加上模板語言比起React簡單多因此選擇它。
  • Gunicorn
    Python Web服務器網關接口HTTP服務器。Gunicorn服務器與許多Web框架廣泛兼容,並且實現簡單,佔用服務器資源少且速度相當快。
  • Nginx
    非同步框架的網頁伺服器,也可以用作反向代理、負載平衡器和HTTP快取。更詳細介紹可以看:介紹1 , 介紹2
  • SSL
    全名是 Secure Sockets Layer,即安全通訊端層,簡而言之,這是一種標準的技術,用於保持網際網路連線安全以及防止在兩個系統之間發送的所有敏感資料被罪犯讀取及修改任何傳輸的資訊,包括潛在的個人詳細資料。

本篇主角 Gunicorn / Nginx

開始前來看一下 Client + Gunicorn + Nginx + Django 的運作邏輯

[核心套路] Nginx只專注在Client端的接口與靜態資源的傳遞,而Gunicorn只負責串接Nginx / Django。這樣有什麼好處?假如今天server內 開了port1 與 port2 分別 python manage.py runserver 兩個django專案,透過Gunicorn可以指定讓Nginx只吃到 port1的Gunicorn程序,而port2則作為開發用,亂改也不會影響到線上網站。

另外連線到ubuntu後透過原始python manage.py runserver方式啟動Django,一旦結束Terminal就會關閉Server,這裡分享個簡單的解決辦法:

// 最後面帶 &
python manage.py runserver 0.0.0.0:80 &

但這方法有個嚴重缺點,一旦server發生任何問題無法得知,也不會有後續應變措施。舉例:假如今天django發生500錯誤,想要自動轉連到某網站顯示伺服器維護中…這時候就是派Nginx + Gunicorn上場 !

行前準備

Gunicorn 主要依賴Python,如果還沒裝Python請參考這篇教學完成安裝。

  • 確認Python版本
python --version //本篇使用3.8
  • 安裝 pip
sudo -H pip3 install --upgrade pip
  • 產生Vue靜態資源

Django Settings指向Vue打包檔設定如下,並設為部署階段 Debug = False

產生靜態資源

python manage.py collectstatic
  • 安裝虛擬測試環境
sudo -H pip3 install virtualenv

如果Server已上線,建議後續維護與更新先在Server開啟virtualenv 做測試,確認無誤後再結束virtualenv並完成布署。

建立測試環境

輸入以下指令建立新的測試環境

// <projectname> 改你喜歡的專案名稱
source <projectname>/bin/activate

簡單測試一下,要能正常跑

sudo python3 manage.py runserver 0.0.0.0:80

安裝Gunicorn

sudo pip install gunicorn

簡單測試你的Django專案

cd ~/<yourprojectdir>
gunicorn --bind 0.0.0.0:8000 <yourproject>.wsgi

成功畫面,測試後確認可以啟用Gunicorn worker

跳出虛擬環境

deactivate

部署Gunicorn Socket

上面測試成功後,要建立Socket來運行Django (非虛擬環境),所以要確保你已經 deactivate

  • 設定socket
sudo vim /etc/systemd/system/gunicorn.socket

把下面程式碼寫入

[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/gunicorn.sock
[Install]
WantedBy=sockets.target

P.S 如何退出? 先按Esc 在打 :符號 shift+ : 然後底部輸入wq + enter 。

  • 取得 Gunicorn 位置
which gunicorn

把下面這段複製起來

/usr/local/bin/gunicorn
  • 建立Gunicorn.service
sudo vim /etc/systemd/system/gunicorn.service

寫入以下程式碼

[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
[Service]
User=ubuntu
Group=www-data
WorkingDirectory=<專案目錄位置>
ExecStart=<你剛剛複製起來的程式碼> \
--access-logfile - \
--workers 3 \
--bind unix:/run/gunicorn.sock \
<projectname>.wsgi:application
[Install]
WantedBy=multi-user.target

完成範例

[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target

[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/Cancell-django/cancell
ExecStart=/usr/local/bin/gunicorn \
--access-logfile - \
--workers 3 \
--bind unix:/run/gunicorn.sock \
cancell.wsgi:application

[Install]
WantedBy=multi-user.target
  • 啟動Gunicorn socket
sudo systemctl start gunicorn.socket
sudo systemctl enable gunicorn.socket
  • 檢查Gunicorn socket是否運作成功,成功會顯示active (running)字樣
sudo systemctl status gunicorn.socket
  • 檢查gunicorn.sock 檔案是否存在/run這個資料夾中
file /run/gunicorn.sock

正確要出現 /run/gunicorn.sock: socket

  • 測試Gunicorn Socket 運作狀況
sudo systemctl status gunicorn

看到 Active (running) 代表ok!

  • 發個Curl測試看看
curl --unix-socket /run/gunicorn.sock localhost

有看到 html 內容,代表成功! 如果Django settings中的 Debug = False ,html會顯示 404 ,那是正常的請略過。

部署Nginx

相較於Gunicorn,Nginx設定相對簡單多了。

  • 安裝Nginx
sudo apt-get install nginx
  • Nginx代理傳給Gunicorn
sudo vim /etc/nginx/sites-available/<projectname>.conf或是sudo nano /etc/nginx/sites-available/<projectname>.conf

配置conf內容

# <projectname>.conf 配置server {  # 監聽port 80,符合的domain與ip
listen 80;
server_name <domain.name> <ip>;
location = /favicon.ico { access_log off; log_not_found off; } location /static/ {
# 指向django靜態資源
alias /home/ubuntu/<專案目錄>/static/;
}
location /media/ {
# 指向django媒體資源
alias /home/ubuntu/<專案目錄>/media/;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
}

將檔案連結到啟動網站的目錄來啟動該檔案

sudo ln -s /etc/nginx/sites-available/<projectname>.conf /etc/nginx/sites-enabled

簡易測試 Nginx

sudo nginx -t

成功的話會顯示 test is successful

<projectname>.conf 有任何異動都需要重啟Nginx

sudo systemctl restart nginx

如果啟動後發現 static / media靜態資源 ngnix無法讀取到並且顯示 403錯誤,則必須給予ngnix讀取權限

sudo usermod -a -G <username> www-data

以我的範例是

sudo usermod -a -G ubuntu www-data

到這邊已幾乎完成Gunicon + Nginx配置,未來有任何修改或是需要重啟兩者,需要按照以下兩步驟執行

  • 清除所有程序
pkill gunicorn
sudo systemctl stop nginx
sudo systemctl daemon-reload
pkill -f runserver
  • 重啟程序
sudo systemctl start gunicorn
sudo systemctl enable gunicorn
sudo systemctl restart nginx

以上不須另外執行 python manage.py runserver ,關閉ubuntu terminal也會繼續執行!

# 切換成管理者 & 打開 ec2-user 使用的個人目錄,允許任何人進入該目錄sudo su -
more /var/log/nginx/error.log
chmod o+x ~ubuntu/
exit

如果想要查看Django的 Log訊息可以輸入

sudo journalctl -u gunicorn

套用SSL(HTTPS)

本篇採用 Let’s Encrypt 做為免費的簽證。

如何申請跟獲得簽證請參考 這篇教學,就不再多敘述。

P.S 需要注意的是,發送domain給 Let’s Encrypt 取得憑證失敗5次要等 1hr,當初一直失敗被鎖後等個一小時回來就成功了,不要問我為什麼我也不知道..

當你完成申請憑證後,以「Cancell 抗癌好夥伴」為例,我們希望使用者永遠都是連到具有https且去除www的網址,實際情況範例:

http://cancell.tw 自動跳轉到 https://cancell.tw
http://www.cancell.tw 自動跳轉到 https://cancell.tw

這時候竟可以透過Nginx來實現~ 開啟<projectname>.conf

sudo vim /etc/nginx/sites-available/<projectname>.conf或是sudo nano /etc/nginx/sites-available/<projectname>.conf

修改配置

# <projectname>.conf 配置server {
# 監聽port 80,並作轉網址
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 301 https://cancell.tw$request_uri;
}
server {
# 監聽port 443
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
include snippets/ssl-cancell.tw.conf;
include snippets/ssl-params.conf;

location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
# 指向django靜態資源
alias /home/ubuntu/<專案目錄>/static/;
}
location /media/ {
# 指向django媒體資源
alias /home/ubuntu/<專案目錄>/media/;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
# 為了讓nginx可以讓外面連的到隱藏檔來完成認證
location ~ /\.well-known\/acme-challenge {
root /var/www/html;
allow all;
}
}

執行上面所提的 清除所有程序 與 重啟程序,恭喜你!!大功告成啦。

分享這麼多看似簡單,實際上當初與跟團隊大概花了兩周時間不斷嘗試才突破難關,今天把整個流程詳細紀錄,希望可以幫到有需要的人。

補充遇到一些奇怪問題的解決辦法

  • 「問題」Gunicorn炸掉並顯示以下錯誤:Warning: journal has been rotated since unit was started, output may be incomplete.

解決辦法:https://www.ghl.name/archives/how-to-solve-journal-has-been-rotated-problem.html

sudo journalctl --vacuum-size=1G  // 執行這段把log大小上限調到1GB

或是可以清空所有 journalctl 紀錄

du -d 0 -h /var/log/journal/  查詢目前存放大小journalctl --since "1 min ago"  查看最近一分鐘的logsudo rm -rf /var/log/journal/*  清空journal目錄下所有檔案sudo systemctl restart systemd-journald.service  記得重啟systemd-journald

解決了! 太神奇了傑克

--

--

周子堯 Victor.Chou
周子堯 Victor.Chou

Written by 周子堯 Victor.Chou

「Cancell 抗癌好夥伴」創辦人與核心前端開發者&UIUX設計師,曾任智冠遊戲網頁視覺設計師、ASUS資深前端工程師,熱衷於設計領域與前端生態。https://vicchoutw.com