一、項目簡介
在本文中,將一步一步搭建一個簡單的Flask + Virtualenv + uWSGI + Nginx 架構的Web服務,可以作為新手的學習也可作為記錄備忘。
如果你安裝好了環境並有一定基礎可以直接從第五節開始部署。
項目中只是演示了瀏覽器訪問地址,獲得文本返回的過程,本人盡量把配置解釋的清晰。基於搭建好的架構,后續可以將業務層(Python)進行擴展,本文不做研究 ,比如:
1、擴展業務代碼:實現json、靜態資源等等的請求響應。
2、基於業務的數據庫查詢和部署。
3、服務器端的部署完備(優化):域名、緩存、負載均衡、安全、備份、防火牆、異步IO等。
4、項目代碼管理。
二、框架介紹
1、Virtualenv
- virtualenv可以創建新的Python環境,獨立的虛擬環境之間互不干擾,在有些場景下非常有用,例如:
(1)同時擁有兩個python項目,一個是python2.7的,另一個是python3的,可以創建兩個虛擬環境。
(2)同時擁有兩個python項目,都依賴一個module(模塊)的不同版本,可以創建兩個不同的虛擬環境,分別安裝這個module的不同版本。
(3)同時擁有兩個python項目,各自依賴不同的module,可以在兩個不同的虛擬環境里安裝模塊並開發項目。
- virtualenvwrapper工具:在virtualenv的基礎上提供了一些更方便的命令,本文只介紹通過此工具管理虛擬環境。
2、Flask
Flask是一個Python的輕量級web框架,是基於Python開發並且依賴jinja2模板(模板語言)和 Werkzeug WSGI(WSGI工具集)服務的一個微型框架,對於Werkzeug本質是Socket服務端,其用於接收http請求並對請求進行預處理,然后觸發Flask框架,開發人員基於Flask框架提供的功能對請求進行相應的處理,並返回給用戶,如果要返回給用戶復雜的內容時,需要借助jinja2模板來實現對模板的處理,即:將模板和數據進行渲染,將渲染后的字符串返回給用戶瀏覽器。
Werkzeug的補充:Werkzeug是WSGI工具包,他可以作為一個Web框架的底層庫。它不是一個web服務器,也不是一個web框架,而是一個工具包,官方的介紹說是一個 WSGI 工具包,它可以作為一個 Web 框架的底層庫,因為它封裝好了很多 Web 框架的東西( python web WSGI 開發相關的功能),例如 :
- 路由處理:如何根據請求 URL 找到對應的視圖函數
- request 和 response 封裝: 提供更好的方式處理request和生成response對象
- 自帶的 WSGI server: 測試環境運行WSGI應用
3、uWSGI
(1)WSGI
全稱 Web Server Gateway Interface ,是為 Python 語言定義的 Web 服務器和 Web 應用程序或框架之間的一種簡單而通用的接口(協議)。WSGI是Web 服務器(uWSGI)與 Web 應用程序或應用框架(Flask,Django)之間的一種低級別的接口。可以參考閱讀 PEP3333。
WSGI 分為兩個部分
- Server/Gateway: 即是HTTP Server, 負責從客戶端(Nginx、apache、IIS)接收請求,將 request 轉發給 application, 並將 application(可能是個Flask應用) 返回的response 返回給客戶端。
- Application/Framework: 一個python web 應用或 web 框架接收由 server 轉發的request,處理請求,並將處理結果返回給 server。
(2)uWSGI
uWSGI是一個Web服務器,C語言編寫,它實現了WSGI協議、uwsgi、http等協議。它要做的就是把HTTP協議轉化成語言支持的網絡協議。比如把HTTP協議轉化成WSGI協議,讓Python可以直接使用。
(3)uwsgi
與WSGI一樣,是uWSGI服務器的獨占通信協議,用於定義傳輸信息的類型(type of information)。每一個uwsgi packet前4byte為傳輸信息類型的描述。
4、Nginx
Nginx ("engine x") 是一個高性能的HTTP和反向代理服務器,也是一個IMAP/POP3/SMTP服務器 。可以作為一個HTTP服務器進行網站的發布處理,另外nginx可以作為反向代理進行負載均衡的實現。因它的穩定性、豐富的功能集、示例配置文件和低系統資源的消耗而聞名。
三、訪問過程
1、描述一:web客戶端->web服務器->業務代碼的訪問過程 (通過協議通信)
2、描述二:不同用戶場景->服務器->不同應用的訪問過程(簡單架構,不包括復雜的部署,緩存等)
(1) 部署多個APP,采用單個Nginx,多個uwsgi+Flask,每個uWSGI監聽不同端口。(標准)
(2) 部署多個APP,采用單個Nginx,單個uWSGI,多個Flask路由,Nginx和uwsg通過一個端口通信。(不好)
注意:這種模式不是標准方式,“轉發”任務應該是由Nginx實現,通過多個端口和uWSGI通信,每個端口對應一個應用,如同描述一。這里靠路由區分,是不好的。
四、環境准備
1、基本環境
(1)阿里ECS服務器 CentOS7.4 ,如需用戶密鑰部署可以參考https://www.cnblogs.com/cleven/p/10899171.html
(2)Python環境:Python3.7,pip ,安裝過程省略。
2、虛擬環境:
(1)安裝
sudo pip3 install virtualenv sudo pip3 install virtualenvwrapper
(2)創建虛擬環境
mkvirtualenv -p python3 env1
-p 后面的參數指定了python3(也有可能要換成python3.2/python3.4,具體要看你系統里面/use/bin/里面的文件是什么名字),如果去掉這個參數,就會使用系統默認的python。最后一個參數env1是創建的這個環境的名字。
(3)管理虛擬環境
deactivate # 退出當前虛擬環境
workon env1 # 使用虛擬環境env1
rmvirtualenv env1 # 刪除某個虛擬環境
lsvirtualenv # 列出所有虛擬環境
cdvirtualenv # 進入虛擬環境存儲目錄
(4)在服務器上同步本地的虛擬環境包
在開發機器上執行下面這個命令,來列出所有的包並保存到packages.txt,其中-l參數是只列出當前虛擬環境的包:
pip3 freeze -l > packages.txt
然后在部署到生產環境的時候,把packages.txt也復制到每個機器,並在每個機器上執行:
pip3 install -r packages.txt
(5)注意點
安裝完虛擬環境,重啟shell后,workon等命令就無法使用了,這是因為沒有添加環境變量:
export WORKON_HOME=/home/.virtualenv #虛擬環境所放置的目錄,可以自行指定 source /usr/local/bin/virtualenvwrapper.sh
配置完上面,使用 source ~/.bashrc 命令就可以了
3、web環境
(1)Flask ,虛擬環境下安裝
pip3 install flask
檢驗一下Flask和虛擬環境的安裝情況:
<1> 創建項目目錄
mkdir webtest
<2> 進入虛擬環境
workon env1
<3> 進入webtest,創建hello.py
from flask import Flask app = Flask(__name__) @app.route("/app/flask/") def hello_flask(): return "Hello Flask!" if __name__ == "__main__": #Flask 開啟監聽所有地址的8888端口 app.run(host='0.0.0.0', port=8888)
<4> 啟動服務,並測試。 Flask是有內置web server的(一般只是測試時使用,不安全,可控性差,線上還是使用uWSGI部署)
python3 hello.py
在瀏覽器訪問 [服務器的ip地址]:8888/app/flask/ ,瀏覽器中如果顯示“Hello Flask”,則Flask配置正常。
(2)uWSGI,虛擬環境下安裝
pip3 install uwsgi
(3)Nginx:不用在虛擬環境下安裝
sudo yum install nginx
五、部署步驟
1、創建uWSGI項目目錄
在/home/project/mysite目錄下創建如下結構
2、編寫run.py 啟動腳本
from flask import Flask app = Flask(__name__) @app.route("/app/uwsgi/") def hello_flask(): return "Hello uWSGI!"
3、配置uWSGI:
(1)進入項目目錄,編輯uwsgi.ini
vim uwsgi.ini
(2)下面給出配置文件的詳細說明(有些字段注釋掉了,畢竟我們是簡單的搭建uWSGI服務)
[uwsgi] chdir=/home/project/mysite/ # 項目目錄 home=/home/zhangqi/.virtualenvs/env1 # 虛擬環境的路徑 wsgi-file=%(chdir)/run.py # 項目的啟動腳本文件路徑
#module=run # 項目的啟動腳本名字,不能是路徑,和wsgi-file功能類似
callable=app # 程序內啟用的application變量名,一般而言都是app=Flask(__name__),所以這里是app master=true # 啟用主進程 processes=2 # worker進程個數 threads=2 # 每個進程的線程數 procname-prefix-spaced=mysite # uwsgi的進程名稱前綴 ,使用 ps -ef | grep mysite查看
#############———————— 注釋掉的一些配置 ————————##############
#chmod-socket=666 # socket文件的訪問權限(socket字段配置的是文件的情況) #logfile-chmod=644 #log權限 #uid=zhangqi # 啟動uwsgi的用戶名 #gid=zhangqi # 啟動uwsgi的用戶組 #py-autoreload=1 # py文件修改,自動加載 #vacuum=true # 退出uwsgi是否清理中間文件,包含pid、sock和status文件 #harakiri=30 # 設置自中斷時間 #post-buffering=4096 # 設置緩沖 #touch-reload=%(chdir) # 動態監控文件變化
###########################################################
############———————— 設置uWSGI的socket連接 ————————############ # # 方式一: socket文件,配置nginx時候使用。socket文件需要使用socket函數編寫。本文中沒有使用此方式 #socket=%(chdir)/uwsgi/uwsgi.sock # # 方式二:綁定地址+端口 socket=:8001 # # 方式三:監聽http端口,測試時候使用。如果不使用Nginx,瀏覽器是http協議,無法使用socket直接通信 #http=0.0.0.0:8001 # ################################################################ ############———————— 設置uWSGI的管理文件 ————————############ # # status文件,可以查看uwsgi的運行狀態 # 命令:uwsgi --connect-and-read uwsgi/uwsgi.status stats=%(chdir)/uwsgi/uwsgi.status # # pid文件,通過該文件可以控制uwsgi的重啟和停止 # 命令:uwsgi --reload uwsgi/uwsgi.pid # 命令:uwsgi --stop uwsgi/uwsgi.pid pidfile=%(chdir)/uwsgi/uwsgi.pid # # 日志文件,通過該文件查看uwsgi的日志 daemonize=%(chdir)/uwsgi/uwsgi.log # #############################################################
(3)啟動uWSGI,注意是虛擬環境。
uwsgi --ini uwsgi.ini
(4)調試uWSGI
- 查看uWSGI進程
ps -ef | grep mysite #或者 netstat -antp |grep 8001
顯示如下,uWSGI啟動正常
- 查看uWSGI狀態。
會以json格式顯示出完整內容,包括每個總的狀態,每個work是狀態,響應時間等,非常全面。
uwsgi --connect-and-read uwsgi/uwsgi.status
也有一些開源的監控工具可以使用:uwsgitop
# pip3 install uwsgitop # uwsgitop uwsgi/uwsgi.status
- 控制uWSGI服務器
uwsgi --ini uwsgi.ini # 啟動 uwsgi --reload uwsgi.pid # 重啟 uwsgi --stop uwsgi.pid # 關閉
4、配置Nginx:
(1)編輯配置文件 nginx.conf(主):
vim /etc/nginx/nginx.conf
內容如下
########### 每個指令必須有分號結束 ################# user nginx; #配置用戶或者組,默認為nobody nobody worker_processes auto; #允許生成的進程數,默認為1 pid /run/nginx.pid; #指定nginx進程運行文件存放地址 include /usr/share/nginx/modules/*.conf; #加載動態模塊 #制定日志路徑和級別。這個設置可以放入全局塊,http塊,server塊 #級別依次為:debug|info|notice|warn|error|crit|alert|emerg error_log /var/log/nginx/error.log; events { worker_connections 1024; #最大連接數,默認為512 #use epoll; #事件驅動模型,select|poll|kqueue|epoll|resig|/dev/poll|eventport #accept_mutex on; #設置網路連接序列化,防止驚群現象發生,默認為on #multi_accept on; #設置一個進程是否同時接受多個網絡連接,默認為off } http { default_type application/octet-stream; #默認文件類型,默認為text/plain keepalive_timeout 65;#連接超時時間,默認為75s,可以在http,server,location塊配置 include /etc/nginx/conf.d/*.conf; #虛擬主機配置,引入不同server的配置(uWSGI) include /etc/nginx/mime.types; #引入文件類型 #sendfile_max_chunk 100k; #每個進程每次調用傳輸數量最大值,默認為0(無上限) ################## 日志 ######################### #自定義日志格式 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; #接入日志設置 access_log /var/log/nginx/access.log main; #access_log off; #取消服務日志 # #################################################### #################### SSL證書加密 ################### # #ssl_protocols TLSv1 TLSv1.1 TLSv1.2 SSLv3;#啟動特定的加密協議 #ssl_prefer_server_ciphers on;#設置協商加密算法時,優先使用服務端的加密套件,而不是客戶端瀏覽器 # #################################################### #################### 緩存性能優化 ################### # # 1、open_file_cache :配置可以存儲的緩存 # max設置緩存中的最大元素數; 在緩存溢出時,刪除最近最少使用(LRU)的元素; # inactive是指經過多長時間文件沒被請求后刪除緩存。 # inactive設置20s,等到至少20s不訪問這個文件,相應緩存的這個文件的更改信息才會被刪除。 # 例: # open_file_cache max=1000 inactive=20s; # # # 2、open_file_cache_valid:多長時間檢查一次緩存的有效信息。 # 設置為30s,也就是說即使一直訪問這個文件,30s后會檢查此文件的更改信息是否變化,發現變化就更新 # 例: # open_file_cache_valid 30s; # # # 3、open_file_cache_min_uses : # 在上面open_file_cache 的 inactive時間內文件的最少使用次數。如果超過這個數字,文件更改信息一直是在緩存中打開的。 # 例: # open_file_cache_min_uses 2; # # 4、文件錯誤是否也同樣緩存 # open_file_cache_errors on; # #################################################### ################# 數據傳輸優化 ################### # # 允許sendfile( )方式傳輸文件,提高傳輸性能,默認為off # 可以在http塊,server塊,location塊配置 sendfile on; # # 設置調用tcp_cork方法,數據包不會馬上傳送出去,等到數據包最大時,一次性的傳輸出去,這樣有助於解決網絡堵塞。默認on, # tcp_nopush on; # # 打開tcp_nodelay ,禁用Nagle的緩沖算法,並在數據可用時立即發送,優化傳輸效率,默認on # tcp_nodelay on; # # #################################################### ################### 散列表大小 ######################## # Nginx使用散列表來存儲MIME type與文件擴展名。 # types_hash_bucket_size 設置了每個散列表占用的內存大小,其影響散列表的沖突率。 # 值越大,就會消耗更多的內存,但散列key的沖突率會降低,檢索速度就更快。 # 值越小,消耗的內存就越小,但散列key的沖突率可能上升。 # 默認1024。 types_hash_max_size 2048; #################################################### ################# Nginx負載均衡 (本文中沒有使用)################ # 負載均衡算法: # 1、熱備 # 2、輪詢 # 3、加權輪詢 # 4、ip_hash(相同的客戶端ip請求相同的服務器) # # 使用服務器列表: # 1、定義服務器列表 mysvr # 2、server的location模塊中將請求轉向mysvr 定義的服務器列表 # # server { #服務器訪問信息的配置 # ..... # location ~*^.+$ { #訪問路由的配置 # proxy_pass http://mysvr; #請求轉向mysvr 定義的服務器列表 # } # # # 寫法舉例: # 1、熱備 # upstream mysvr { //服務器列表 # server 127.0.0.1:7878; # server 192.168.10.121:3333 backup; #熱備 # } # # 2、輪詢 # upstream mysvr { # server 127.0.0.1:7878; # server 192.168.10.121:3333; # } # # 3、加權(參數) # upstream mysvr { # server 127.0.0.1:7878 weight=2 max_fails=2 fail_timeout=2; # server 192.168.10.121:3333 weight=1 max_fails=2 fail_timeout=1; # } # # 4、ip_hash # upstream mysvr { # server 127.0.0.1:7878; # server 192.168.10.121:3333; # ip_hash; # } # # nginx負載調優總結 :https://www.jianshu.com/p/4fa08f2a04ed # ################################################# }
(2)Flask站點對應的server配置:
上面的主配置中,有此句“include /etc/nginx/conf.d/*.conf;” ,我們將Flask對應的server模塊,單獨寫在一個文件里(當然也可以直接寫在上面的主配置中),一個server中又通過多個location指向不同應用。
理論上,一個Nginx對應多個站點,一個站點對應一個server,一個server又可以對應多個location(應用)。
- 進入conf.d文件夾,並創建flask.conf
$ sodu touch /etc/nginx/conf.d/flask.conf
- 內容如下
server {
listen 81; #監聽外部端口(瀏覽器)
server_name 39.xxx.xxx.xxx; #服務器地址
charset utf-8;
client_max_body_size 5M;
root /usr/share/nginx/html; #默認根目錄
include /etc/nginx/default.d/*.conf;
#配置路由
location / { #對根目錄的訪問都匹配,此處沒有做正則匹配講解
include uwsgi_params; #引入uwsgi
uwsgi_pass localhost:8001; #通過localhost:8001端口和uWSGI通信(這是綁定的IP和端口,對應上文中uWSGI.ini里面的配置)
# uwsgi_pass unix:/home/project/mysite/uwsgi/uwsgi.sock; #本文沒有使用socket文件進行演示。
}
location /static { #靜態文件,直接訪問路徑,不用進入uWSGI進行處理
alias /home/project/mysite/static/;
}
error_page 404 /404.html;
location = /40x.html {
#可以更改錯誤頁面路徑
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
#可以更改錯誤頁面路徑
}
}
(2)控制Nginx:
- 啟動
$nginx
或者
$systemctl start nginx
- 重啟
nginx -s reload #熱啟動,配置文件重裝載
kill -HUP 主進程號或進程號文件路徑 #平滑重啟
systemctl restart nginx
- 停止
system stop nginx nginx -s stop #快速關閉 nginx -s quit #正常關閉
或者通過進程控制
ps -ef | grep nginx
kill -QUIT 主進程號 #從容停止
kill -TERM 主進程號 #快速停止
kill -9 主進程號 #強制停止
(3)查看Nginx狀態:
- ps -ef | grep nginx
- systemctl status nginx
5、測試服務
(1)方法一:
瀏覽器輸入 39.xxx.xxx.xxx:81/app/uwsgi/ ,如果頁面顯示 “Hello uWSGI!” ,大功告成!
(2)方法二:
curl http://39.xxx.xxx.xxx:81/app/uwsgi/ ,終端輸出 “Hello uWSGI!”,OK。
六、收尾
整個項目下來並不是很復雜,主要是熟悉配置流程,並且根據搭好的基礎框架,升級業務功能。
在調試過程中如果出現問題,可以查看各個日志,根據 上文: 三、訪問過程 進行逐步排查解決。
最后,后端領域還有很多東西需要學習,如有錯誤之處還望大家多多指教,本文多以做技術交流和配置備忘之用。