Ubuntu上使用Gunicorn + Nginx + SSL部署Django+Vue專案
「Cancell 抗癌好夥伴」上線前卡在Nginx設定以及Django + Vue的配置一陣子。經過努力嘗試後最終成功突破,決定寫個筆記分享心得。
簡單介紹
- Django
一個開放原始碼的Web應用框架,由Python寫成。採用了MVT的軟體設計模式,即模型,視圖和模板。Cancell 捨棄了Django的後端渲染模板,採用前後端分離方式做開發。
- Vue
前端框架三大天王之一,採用Virtual Dom且具有優異的效能,學習曲線普通加上模板語言比起React簡單多因此選擇它。
- Gunicorn
Python Web服務器網關接口HTTP服務器。Gunicorn服務器與許多Web框架廣泛兼容,並且實現簡單,佔用服務器資源少且速度相當快。
- 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
解決了! 太神奇了傑克