DevOps:如何將 Nginx + uWSGI + Django 的服務部署在一台主機的根目錄
How to deploy the Django application at the root on a host machine using Nginx + uWSGI + Django
部署的設定千百種,坑也是千百種,所以趁著還有記憶的時候趕快紀錄一下。完整的 sample code 請參考 nginx-uwsgi-django-depoly-at-root。
以下的設定皆是在 Mac
上進行測試,若是在 CentOS 7
或是 Ubuntu
上的話,Nginx
的部分可能需要微調。
OS Information and packages version
macOS Catalina, Version 10.15.6
conda 4.8.5
django 3.1.2
uwsgi 2.0.19.1
nginx version: nginx/1.19.3
1. Build a simple Django project
本文不對 Django
做太多說明,詳細的教學與設定可以參考官方文件 Get started with Django。如果懶得看的話,照著以下的步驟就可以建立出一個簡易的 Django Application。
建立專案並移動到專案目錄下。
django-admin startproject dj3
cd dj3
在 dj3
專案中建立第一個名為 api
的 app
,並且在這個 api
中新增一個 urls.py
的檔案。project
與 app
的差異請參考 官方文件。
django-admin startapp api
touch api/urls.py
將剛剛建立的名為 api
的 app
註冊到 dj3
這個 project
中,將 dj3/settings.py
的檔案修改如下,
# dj3/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'api',
]
並且在最下方新增 STATIC_ROOT
的路徑。
這個路徑是部署時將靜態檔案收集到同一個路徑下,方便 Nginx
讀取用。
# dj3/settings.py
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'static'
接著修改一些安全性設定,首先將 SECRET_KEY
改成吃環境變數的方式,避免直接暴露於版本控制中。
P.S.: 本文為了 Demo 方便,將預設產生的 SECRET_KEY
值設定成 default
,請不要使用於正式環境。
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv('SECRET_KEY', '!^-p96ces65=nkgedrbvc2v5^y6asmm5#y#-3+&u7edzju16pu')
再來則是 Django 自帶的 Debug 模式,由於 Debug 模式會呈現很多資訊,如路徑、變數名稱、變數內容⋯⋯等等,請不要使用於正式環境。
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.getenv('DEBUG', True)
為了讓 api
的內容可以被讀取到,需要修改 dj3
的路由設定,將 dj3/urls.py
的檔案修改如下。
# dj3/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('api.urls')),
]
在剛剛新增的 api/urls.py
檔案中,增加以下的程式碼。
# api/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
將 api/views.py
的檔案修改如下。
# api/views.py
from django.shortcuts import render
from django.http import HttpResponse
def index(request):
return HttpResponse("I am Django 3.1 !!!")
好啦!一個簡易的 Django application 就完成了。
最後啟動 Django 自帶的開發伺服器。
python manage.py runserver
啟動後如果沒有出現錯誤,應該就會在 terminal 中看到
Django version 3.1.2, using settings 'dj3.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
在瀏覽器中開啟 http://localhost:8000/api/,就可以看到 I am Django 3.1 !!!
的文字;
而開啟 http://localhost:8000/admin/ 的話,就可以看到 Django 自帶的管理介面啦。
2. Run the Django project using uWSGI
Django 自帶的開發伺服器,其功能只是讓開發者在開發過程中可以方便的看到變化,其效能並不適合拿到正式環境中使用。
Django 常見的 Web Application Server 有 uWSGI、gunicorn。
這邊採用 uWSGI
,而詳細的設定可以參考 官方文件
確認在 dj3
專案的路徑下後,新增一個 uWSGI
的設定檔,並新增以下的設定。
touch uwsgi.ini
# dj3/uwsgi.ini
[uwsgi]
http = :8003
module = dj3.wsgi:application
master = True
processes = 1
threads = 1
vacuum = True
pidfile = /tmp/dj3-master.pid
以下簡易說明各項設定:
http
: 使用 HTTP protocal 將服務跑在127.0.0.1:8003
。module
: 設定 uWSGI 的 modele,也就是dj3
專案中的wsgi.py
中的application
物件。pidfile
: 設定一個 pidfile,否則ctrl + C
會關不掉。
最後,將啟動 uWSGI 伺服器
uwsgi --ini uwsgi.ini
接著在瀏覽器中開啟 http://localhost:8003/api/,就可以看到 I am Django 3.1 !!!
的文字;
而開啟 http://localhost:8003/admin/ 的話,就可以看到 Django 自帶的管理介面啦。
注意是 8003
而不是先前的 8000
喔!
你應該會注意到當你開啟 http://localhost:8003/admin/ 可以看到輸入帳號密碼的欄位,但是原本的 CSS
樣式卻都消失了。
這是因為我們沒有在 uwsgi.ini
中設定靜態檔案的路徑,如果你要用 uWSGI
伺服器來取用靜態檔案的話,你可以在設定檔中新增 static-map
的設定,不過在本文中,我將用 Nginx
來取用靜態檔案。
3. The preparation of Nginx
Nginx 是一個高效且功能五花八門的 Web Server,同時它還具有 Reverse proxy、Load balancer 與 HTTP cache⋯⋯等功能,而在這邊我將利用 Nginx
作為 Reverse proxy 及取用靜態檔案。
Nginx
的兩大功能:
Reverse proxy
: 監聽80
port 並將其 Request 導向 uWSGI 伺服器(8003
port),最後再將 Respones 回傳給 Nginx 伺服器。Nginx
作為Reverse proxy
所支援的 protocal 除了HTTP
以外,也包含FastCGI
、uwsgi
、SCGI
與memcached
。
Serve static files
: 由於靜態檔案(如CSS
、JavaScript
、png
等) 並不會隨著 Request 而更動內容,所以 Nginx 可以直接取用靜態檔案而不需要經過 uWSGI 伺服器。
其架構大致如下:
# Dynamic respones
browser <-> 80 port <-> nginx <-> 8003 port <-> uwsgi <-> django
# Static files
browser <-> 80 port <-> nginx <-> static files
首先,確認你已經安裝 Nginx,並且移動到該路徑下
- macOS:
/usr/local/etc/nginx
- CentOS 7:
/etc/nginx
- Ubuntu:
/usr/local/nginx
修改 nginx.conf
,將預設的 conf.d/*.conf
註解掉,並新增 include /.../.../nginx/sites-enabled/*
的設定。
macOS
# include /usr/local/etc/nginx/conf.d/*.conf;
include /usr/local/etc/nginx/sites-enabled/*;
CentOS 7
# include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
接著建立兩個資料夾
mkdir sites-available
mkdir sites-enabled
在 sites-available
中建立設定檔
touch sites-available/deploy-at-root-proxy-pass.conf
設定檔會放置在 sites-available
資料夾中,而當確定要使用某個設定檔的時候,再利用 symbolic link(soft link)
將該設定檔連結到 sites-enabled
中,避免直接在 nginx.conf
或 conf.d
中修改設定。
這樣做的好處是,你可以在 sites-available
存放多個不同服務的設定檔,但只有將要啟動的設定檔連結到 sites-enabled
,如果你要轉換到另一個設定檔的時候,只要單純在 sites-enabled
將不要的 soft link
刪除,並且重新連結新的設定檔到 sites-enabled
即可。
4. Run the Django project using uWSGI + Nginx with proxy_pass
這節先使用 proxy_pass
作為設定。
修改先前所建立的 deploy-at-root-proxy-pass.conf
# sites-available/deploy-at-root-proxy-pass.conf
server {
# the port your site will be served on
listen 80;
# the domain name it will serve for
# substitute your machine's IP address or FQDN
server_name _;
charset utf-8;
client_max_body_size 75M; # adjust to taste
location / {
proxy_pass http://127.0.0.1:8003/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /static/ {
alias /your/path/dj3/static/;
}
}
注意!部署時,記得切換到 dj3
專案目錄後利用 collectstatic
指令,將所有靜態檔案都收集到 STATIC_ROOT
中。
cd dj3
python manage.py collectstatic
利用 symbolic link
將 sites-available/deploy-at-root-proxy-pass.conf
連結至 sites-enabled/
路徑下(注意:要用絕對路徑!)。
# macOS
ln -s /usr/local/etc/nginx/sites-available/deploy-at-root-proxy-pass.conf /usr/local/etc/nginx/sites-enabled/
# CentOS 7
ln -s /etc/nginx/sites-available/deploy-at-root-proxy-pass.conf /etc/nginx/sites-enabled/
檢查設定檔是否正確
nginx -t
重新啟動 Nginx
# macOS
brew services restart nginx
# CentOS 7
systemctl restart nginx
Nginx
重啟後,開啟 http://localhost/admin/ 的話,就可以看到 Django 自帶的管理介面,而且 CSS 樣式也可以正常顯示。同時,開啟 http://localhost:8003/admin/ 管理介面時,仍舊沒有 CSS 樣式,這表示靜態檔案確實地是由 Nginx
所取用而不是由 uWSGI
。
最後,這個簡易的 Django application 就已經成功地被部署到主機的根目錄啦~~~
5. Run the Django project using uWSGI + Nginx with uwsgi_pass
本節使用 uwsgi_pass
作為設定。
首先,將 uwsgi.ini
中的 http
protocal 修改成 socket
,socket
預設使用 UNIX protocal。
# dj3/uwsgi.ini
[uwsgi]
# http = :8003
socket = :8003
接著在 sites-available
中建立設定檔
touch sites-available/deploy-at-root-uwsgi-pass.conf
修改 deploy-at-root-uwsgi-pass.conf
# sites-available/deploy-at-root-uwsgi-pass.conf
server {
# the port your site will be served on
listen 80;
# the domain name it will serve for
# substitute your machine's IP address or FQDN
server_name _;
charset utf-8;
client_max_body_size 75M; # adjust to taste
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:8003;
}
location /static/ {
alias /your/path/dj3/static/;
}
}
利用 symbolic link
將 sites-available/deploy-at-root-uwsgi-pass.conf
連結至 sites-enabled/
路徑下(注意:要用絕對路徑!)。
# macOS
ln -s /usr/local/etc/nginx/sites-available/deploy-at-root-uwsgi-pass.conf /usr/local/etc/nginx/sites-enabled/
# CentOS 7
ln -s /etc/nginx/sites-available/deploy-at-root-uwsgi-pass.conf /etc/nginx/sites-enabled/
檢查設定檔是否正確
nginx -t
重新啟動 Nginx
# macOS
brew services restart nginx
# CentOS 7
systemctl restart nginx
Nginx
重啟後,開啟 http://localhost/admin/ 的話,就可以看到 Django 自帶的管理介面,而且 CSS 樣式也可以正常顯示。
如果你像前一節一樣用瀏覽器開啟 http://localhost:8003/admin/ 管理介面時,會發現無法開啟;同時,檢查 uWSGI
的 terminal 時會發現這樣的訊息。
invalid request block size: 21573 (max 4096)...skip
這是因為我們在設定 uwsgi_pass
之前,有先將 uwsgi.ini
的設定檔改成 socket = :8003
,這表示我們的 Django 服務是使用 UNIX socket 跑在 127.0.0.1:8003
上,但是並不是使用 HTTP protocal,所以無法用瀏覽器打開。
最後,這個簡易的 Django application 也成功地被部署到主機的根目錄啦~~~
Leave a comment