智能化運維平臺部署(gunicorn+nginx+gevent+supervisor部署flask+vue)

2019年6月13日
[toc]

前言

  簡要介紹一下,爲小白普及基礎知識,老司機請自行略過~
  Flask本身帶着 WSGI server,但是性能差強人意,自帶的web server 更多的是測試用途。線上發佈時,最好使用高性能的 wsgi server或者是聯合nginx做uwsgi 。
  greenlet是一個輕量級的協程庫。
  gevent是基於greenlet的網絡庫,每次遇到io操作,需要耗時等待時,會自動跳到下一個協程繼續執行。
  guincorn是支持wsgi協議的http server,只支持在Unix系統上運行,來源於Ruby的unicorn項目。gevent是它支持的模式之一 ,是爲了解決django、flask這些web框架自帶wsgi server性能低下的問題。它的特點是與各個web框架結合緊密,實現簡單,輕量級,高性能,部署方便。
  Nginx是一款輕量級的Web 服務器/反向代理服務器及電子郵件(IMAP/POP3)代理服務器,在BSD-like 協議下發行。其特點是佔有內存少,併發能力強,在同類型的網頁服務器中表現較好,百度、京東、新浪、網易、騰訊、淘寶等網站都廣泛使用nginx。
  Supervisor是一個UNIX操作系統上的進程管理工具,允許用戶通過其控制許多進程,包括進程啓停、優先級分配等,具有部署簡單、管理集中、高效等特點。
  pipenv能夠有效管理Python多個環境,各種依賴包。過去我們一般用virtualenv搭建虛擬環境,管理python版本,但是跨平臺的使用不太一致,且有時候處理包之間的依賴總存在問題;過去也常常用 pip進行包的管理,pip已經足夠好,但是仍然推薦pipenv,相當於virtualenv和pip的合體,更加強大,在各個平臺的命令都是一樣的,且使用了哈希校驗,無論安裝還是卸載包都十分安全。詳見Python新利器之pipenv

一.環境

1.應用架構:大前端+後端API

(1)大前端框架:iview-admin套餐(vue、vue-router、vuex等)
(2)後端框架:flask套餐(flask、flask-sqlalchemy、python-crontab、sqlparse等)

2.部署架構:nginx+gunicorn+gevent+supervisor

3.服務器環境

IP:11.11.11.11
操作系統:centos7.2

4.部署目錄

安裝目錄:/app/setup
運行目錄:/app/run
nginx日誌目錄:/var/log/nginx
gunicorn日誌目錄:/var/log/gunicorn
supervisor日誌目錄:/var/log/supervisor/
supervisor運行目錄:/var/supervisor/run/

二.安裝python3.7.1

1.移除默認運行python3.6.4

vi ~/.bash_profile

export PATH
#export PATH="/root/.pyenv/bin:$PATH"
#export PYENV_ROOT="$HOME/.pyenv"
#export PATH="$PYENV_ROOT/bin:$PATH"

cd /usr/bin可知
此時默認版本爲2.7.5

lrwxrwxrwx. 1 root root 7 Apr 9 2018 python -> python2
lrwxrwxrwx. 1 root root 9 Apr 9 2018 python2 -> python2.7
-rwxr-xr-x. 1 root root 7136 Nov 20 2015 python2.7

2.下載安裝包

登錄網址https://www.python.org/downloads/release/python-371/
下載Python-3.7.1.tgz

3.傳輸

傳輸到11.11.11.11 的/app/setup目錄下

4.解壓安裝包

cd /app/setup
tar -xvzf Python-3.7.1.tgz

5.安裝

(1)安裝依賴(不管裝沒裝,強擼)

yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel libffi-devel gcc make

(2)編譯安裝軟件

cd  /app/setup/Python-3.7.1
./configure --prefix=/usr/python3
make && make install

6.修改profile

[root@dbmgt1 bin]# vi ~/.bash_profile 
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
        . ~/.bashrc
fi
# User specific environment and startup programs
PATH=$PATH:$HOME/bin
export PATH
#export PATH="/root/.pyenv/bin:$PATH"
#export PYENV_ROOT="$HOME/.pyenv"
#export PATH="$PYENV_ROOT/bin:$PATH"
#eval "$(pyenv init -)"
export PYENV_ROOT="/usr/python3"
export PATH="$PYENV_ROOT/bin:$PATH"

注意:
yum依賴於/usr/bin/python,指向python2,後續還需使用yum進行依賴安裝,所以不要動這個文件

7.升級pip

(1)如果沒有外網

pip install --index http://11.4.76.252/simple/ --trusted-host=11.4.76.252 --upgrade pip

(2)如果有外網

pip install  --upgrade pip

8.安裝pipenv

pip install pipenv

三.代碼環境安裝

1.從gitlab拉取代碼

cd /app/run
mkdir www
cd www
git init
git pull https://gitlab.www.com.cn/88888/automation.git

輸入工號密碼後完成代碼拉取

2.創建虛擬環境

cd /app/run/www/AutoDB
yum install postgresql-devel*
pipenv install
pipenv install gunicorn
pipenv install supervisor
pipenv install gevent

說明:由於系統用到pg數據庫,需要安裝psycopg2組件,安裝該組件在linux環境下需要先安裝postgresql-devel依賴包,否則會報錯:Error: pg_config executable not found.
此外,由於部署需要,安裝gevent、gunicorn、supervisor組件

3.安裝證書(如果已申請證書,該步驟可忽略)

(1)安裝依賴


yum install openssl

yum install openssl-devel

(2)製作證書


cd /app/run/www/AutoDB

#生成key
openssl genrsa -des3 -out server.key 1024
#生成csr
openssl req -new -key server.key -out server.csr
#生成免輸入密碼的key,不然網站啓動時總是要求輸入key
openssl rsa -in server.key -out server_nopwd.key
#生成crt
openssl x509 -req -days 3650 -in server.csr -signkey server_nopwd.key -out server.crt

4.代碼運行驗證(驗證時防火牆可開啓5000端口)

cd /app/run/www/AutoDB
pipenv shell
python start.py

瀏覽器訪問https://11.11.11.11:5000
如果有正常返回頁面,則成功

四.wsgi服務器安裝部署

WSGI的全稱是Web Server Gateway Interface,翻譯過來就是Web服務器網關接口。具體的來說,WSGI是一個規範,定義了Web服務器如何與Python應用程序進行交互,使得使用Python寫的Web應用程序可以和Web服務器對接起來,基於gunicorn的強大性能,採用gunicorn作爲flask的wsgi服務器是最佳實踐之一。WSGI的理解詳見什麼是wsgiuWSGI和Gunicorn

1.安裝gunicorn

cd /app/run/www
pipenv install gunicorn

2.編寫配置文件

在項目主目錄下新增gunicorn.conf

import os
bind='0.0.0.0:5000'
workers=4
backlog=2048
worker_class="gevent" #sync, gevent,meinheld
debug=True
proc_name='gunicorn_www.pid'
pidfile='/var/log/gunicorn/wwwpid.log'
errorlog='/var/log/gunicorn/wwwerror.log'
accesslog='/var/log/gunicorn/wwwaccess.log'
loglevel='debug'
threads=4
worker_connections = 2000
keyfile='server.key'
certfile='server.cer'

參考官方文檔

3.創建gunicorn日誌目錄

在/var/log目錄下創建gunicorn目錄

cd /var/log
mkdir gunicorn

4.驗證gunicorn配置

/root/.local/share/virtualenvs/AutoDB-zUItRR7g/bin/gunicorn -c gunicorn.conf start:autodb_app

如果瀏覽器可以正常訪問平臺,說明配置成功
gunicorn的路徑由pipenv shell可查看到虛擬環境路徑

五.web服務器

基於nginx的強大性能,採用nginx作爲web服務器,

1.安裝依賴包

yum install gcc-c++
yum install -y pcre pcre-devel
yum install -y zlib zlib-devel
yum install -y openssl openssl-devel

2.下載nginx安裝包

http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm

3.傳輸到/app/setup

4.安裝nginx

rpm -Uvh nginx-release-centos-7-0.el7.ngx.noarch.rpm

或者

 yum install -y nginx

5.啓動nginx

systemctl start nginx.service
systemctl enable nginx.service

6.驗證是否安裝成功

瀏覽器訪問http://11.11.11.11:80,如果正常返回html頁面說明安裝成功

7.配置nginx

思路:
系統採用前後端分離的架構,可以將前端靜態文件與後端應用程序分開部署。爲實現更爲安全的訪問配置,通過nginx反向代理實現應用程序部署端口的隱藏。
部署方案及難點如下:
(1)項目前端採用VUE大前端架構,編譯後爲html、js等純靜態文件,部署在80端口。
難點:由於前端vue的路由模塊採用了history模式,需要解決前端路由請求刷新後報404錯誤的問題;
(2)後端程序部署在5000端口,對外開放443端口,nginx監聽443端口,限制只接收https請求。接到外部請求後,判斷路徑是靜態文件還是API請求,靜態文件請求直接跳轉靜態目錄,API請求轉發到5000端口。
難點:實現https認證方式的配置

(1)修改主配置文件nginx.conf

[root@dbmgt1 nginx]# cd /etc/nginx
[root@dbmgt1 nginx]# vi nginx.conf 
user root root;
worker_processes 10;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
    worker_connections 1024;
}
http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    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;
    sendfile on;
    #tcp_nopush on;
    keepalive_timeout 65;
    #gzip on;
    include /etc/nginx/conf.d/*.conf;
}

(2)創建項目配置文件

按照部署思路分爲adbo_http.conf和adbo_https.conf兩個文件,分別實現大前端項目的部署和後端API程序的部署
先移除默認配置

cd /etc/nginx/conf.d
mv  default.conf default.conf.bak
a.創建adbo_http.conf配置文件
server {
    listen       80;
    server_name  adbo.www.com.cn;
    location /static {
        alias /app/run/www/AutoDB/AutoDB/static;
    }
    location / {
        root /app/run/www/AutoDB/AutoDB/templates;
        index index.html;
        if (!-e $request_filename) {
            rewrite ^/(.*) /index.html last;
            break;
        }
    }
}

注:通過if條件判斷,解決前端vue項目history模式的路由刷新問題,如果不加判斷,刷新會出現404 詳見VUE路由history模式坑記-NGINX
vue的路由模式詳見Vue-router 中hash模式和history模式vue-router兩種模式,到底什麼情況下用hash,什麼情況下用history模式呢?

b.創建adbo_https.conf配置文件
vi adbo_https.conf 
server {
    listen       443 ssl;
    server_name  adbo.www.com.cn;
    ssl_certificate  /app/run/www/AutoDB/server.cer;
    ssl_certificate_key  /app/run/www/AutoDB/server.key;
    ssl_session_timeout 5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
    ssl_prefer_server_ciphers on;
    location /static {
        alias /app/run/www/AutoDB/AutoDB/static;
    }

    location / {
        proxy_pass https://adbo.www.com.cn:5000;
        #add_header Access-Control-Allow-Origin 'http://adbo.www.com.cn';
        #add_header Access-Control-Allow-Methods 'GET, POST,PUT,DELETE, OPTIONS';
        #add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
    }
    # redirect server error pages to the static page /50x.html
    #
}

注:排查問題可通過nginx日誌排查 詳見
如果出現權限問題,執行以下命令 詳見

setsebool httpd_can_network_connect on -P

8.驗證配置是否成功

瀏覽器輸入11.11.11.11 ,訪問正常即成功
nginx配置https詳見Nginx 配置 HTTPS 服務器
注意:基於域名的證書,必須通過域名訪問才能認證成功!基於ip的訪問將被拒絕!!

六.防火牆配置

開通80、443端口

[root@dbmgt1 services]#  firewall-cmd --zone=public --list-ports
[root@dbmgt1 services]# firewall-cmd --zone=public --add-port=80/tcp --permanent 
success
[root@dbmgt1 services]# firewall-cmd --zone=public --add-port=443/tcp --permanent 
success
[root@dbmgt1 services]# firewall-cmd --reload
success
[root@dbmgt1 services]# firewall-cmd --zone=public --list-ports
443/tcp 80/tcp

注:防火牆啓停命令

systemctl start/stop firewalld.service
systemctl enable/disable firewalld.service

七.oracle客戶端安裝配置

由於程序使用cx_Oracle模塊訪問oracle數據庫,需要安裝oracle客戶端,並且添加相應的環境變量,以便讓cx_Oracle識別到。

1.下載客戶端

下載地址:https://www.oracle.com/technetwork/topics/linuxx86-64soft-092277.html

2.傳輸到/app/setup

3.安裝客戶端

cd /app/setup
rpm -Uvh oracle-instantclient12.1-basic-12.1.0.2.0-1.x86_64.rpm

4.修改環境變量

(1)通過whereis oracle查看安裝目錄
(2)修改環境變量

vi ~/.bash_profile
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
        . ~/.bashrc
fi

# User specific environment and startup programs

PATH=$PATH:$HOME/bin

export PATH
#export PATH="/root/.pyenv/bin:$PATH"
#export PYENV_ROOT="$HOME/.pyenv"
#export PATH="$PYENV_ROOT/bin:$PATH"
#eval "$(pyenv init -)"
export PYENV_ROOT="/usr/python3"
export PATH="$PYENV_ROOT/bin:$PATH"
export LD_LIBRARY_PATH=/usr/lib/oracle/12.1/client64/lib:$LD_LIBRARY_PATH
export PATH=/usr/lib/oracle/12.1/client64/bin:$PATH
export PATH=/usr/lib/oracle/12.1/client64/lib:$PATH
NLS_LANG=american_america.ZHS16GBK
export NLS_LANG
export ORACLE_HOME=/usr/lib/oracle/12.1/client64

5.驗證是否配置成功

cd /app/run/www/AutoDB
pipenv shell
python
import cx_Oracle
connection = cx_Oracle.Connection("HXBTEST4_SPT/[email protected]:1521/zhtestdb1")

如果沒有報錯說明配置成功
如果報錯:cx_Oracle.DatabaseError: ORA-21561: OID generation failed
通過hostname查看主機名 dbmgt1
將主機名添加到配置

vi /etc/hosts
11.11.11.11 dbmgt1

八.supervisor安裝配置

1.安裝supervisor

cd /app/run/www/AutoDB
pipenv install supervisor

2.初始化supervisor配置文件

pipenv shell
echo_supervisord_conf > /etc/supervisord.conf

3.修改配置

vi /etc/supervisord.conf
; Sample supervisor config file.
;
; For more information on the config file, please see:
; http://supervisord.org/configuration.html
;
; Notes:
; - Shell expansion ("~" or "$HOME") is not supported. Environment
; variables can be expanded using this syntax: "%(ENV_HOME)s".
; - Quotes around values are not supported, except in the case of
; the environment= options as shown below.
; - Comments must have a leading space: "a=b ;comment" not "a=b;comment".
; - Command will be truncated if it looks like a config file comment, e.g.
; "command=bash -c 'foo ; bar'" will truncate to "command=bash -c 'foo ".

[unix_http_server]
file=/var/supervisor/run/supervisor.sock ; the path to the socket file
;chmod=0700 ; socket file mode (default 0700)
;chown=nobody:nogroup ; socket file uid:gid owner
;username=user ; default is no username (open server)
;password=123 ; default is no password (open server)

;[inet_http_server] ; inet (TCP) server disabled by default
;port=127.0.0.1:9001 ; ip_address:port specifier, *:port for all iface
;username=user ; default is no username (open server)
;password=123 ; default is no password (open server)

[supervisord]
logfile=/var/log/supervisor/supervisord.log ; main log file; default $CWD/supervisord.log
logfile_maxbytes=50MB ; max main logfile bytes b4 rotation; default 50MB
logfile_backups=10 ; # of main logfile backups; 0 means none, default 10
loglevel=info ; log level; default info; others: debug,warn,trace
pidfile=/var/supervisor/run/supervisord.pid ; supervisord pidfile; default supervisord.pid
nodaemon=false ; start in foreground if true; default false
minfds=1024 ; min. avail startup file descriptors; default 1024
minprocs=200 ; min. avail process descriptors;default 200
;umask=022 ; process file creation umask; default 022
;user=supervisord ; setuid to this UNIX account at startup; recommended if root
;identifier=supervisor ; supervisord identifier, default is 'supervisor'
;directory=/tmp ; default is not to cd during start
;nocleanup=true ; don't clean up tempfiles at start; default false
;childlogdir=/tmp ; 'AUTO' child log dir, default $TEMP
environment=PATH=/usr/lib/oracle/12.1/client64/lib:/usr/lib/oracle/12.1/client64/bin:%(ENV_PATH)s,NLS_LANG=american_america.ZHS16GBK,ORACLE_HOME=/usr/lib/oracle/12.1/client64:%(ENV_ORACLE_HOME)s,LD_LIBRARY_PATH=/usr/lib/oracle/12.1/clien
t64/lib:%(ENV_LD_LIBRARY_PATH)s ; key value pairs to add to environment
;strip_ansi=false ; strip ansi escape codes in logs; def. false

; The rpcinterface:supervisor section must remain in the config file for
; RPC (supervisorctl/web interface) to work. Additional interfaces may be
; added by defining them in separate [rpcinterface:x] sections.

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

; The supervisorctl section configures how supervisorctl will connect to
; supervisord. configure it match the settings in either the unix_http_server
; or inet_http_server section.

[supervisorctl]
serverurl=unix:///var/supervisor/run/supervisor.sock ; use a unix:// URL for a unix socket
;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket
;username=chris ; should be same as in [*_http_server] if set
;password=123 ; should be same as in [*_http_server] if set
;prompt=mysupervisor ; cmd line prompt (default "supervisor")
;history_file=~/.sc_history ; use readline history if available

; The sample program section below shows all possible program subsection values.
; Create one or more 'real' program: sections to be able to control them under
; supervisor.

[program:adbo]
command=/root/.local/share/virtualenvs/AutoDB-zUItRR7g/bin/gunicorn -c gunicorn.conf start:autodb_app ; the program (relative uses PATH, can take args)
;command=/root/.local/share/virtualenvs/AutoDB-zUItRR7g/bin/python start.py
;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
numprocs=1 ; number of processes copies to start (def 1)
directory=/app/run/www/AutoDB ; directory to cwd to before exec (def no cwd)
;umask=022 ; umask for process (default None)
priority=999 ; the relative start priority (default 999)
autostart=true ; start at supervisord start (default: true)
;startsecs=1 ; # of secs prog must stay up to be running (def. 1)
startretries=10 ; max # of serial start failures when starting (default 3)
autorestart=true ; when to restart if exited after running (def: unexpected)
;exitcodes=0 ; 'expected' exit codes used with autorestart (default 0)
;stopsignal=QUIT ; signal used to kill process (default TERM)
stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10)
;stopasgroup=false ; send stop signal to the UNIX process group (default false)
;killasgroup=false ; SIGKILL the UNIX process group (def false)
;user=chrism ; setuid to this UNIX account to run the program
;redirect_stderr=true ; redirect proc stderr to stdout (default false)
stdout_logfile=/tmp/adbo.log ; stdout log path, NONE for none; default AUTO
stdout_logfile_maxbytes=50MB ; max # logfile bytes b4 rotation (default 50MB)
;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10)
;stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
;stdout_events_enabled=false ; emit events on stdout writes (default false)
;stdout_syslog=false ; send stdout to syslog with process name (default false)
stderr_logfile=/tmp/adbo_err.log ; stderr log path, NONE for none; default AUTO
stderr_logfile_maxbytes=50MB ; max # logfile bytes b4 rotation (default 50MB)
stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10)
;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
;stderr_events_enabled=false ; emit events on stderr writes (default false)
;stderr_syslog=false ; send stderr to syslog with process name (default false)
;environment=A="1",B="2" ; process environment additions (def no adds)
;serverurl=AUTO ; override serverurl computation (childutils)

; The sample eventlistener section below shows all possible eventlistener
; subsection values. Create one or more 'real' eventlistener: sections to be
; able to handle event notifications sent by supervisord.

;[eventlistener:theeventlistenername]
;command=/bin/eventlistener ; the program (relative uses PATH, can take args)
;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
;numprocs=1 ; number of processes copies to start (def 1)
;events=EVENT ; event notif. types to subscribe to (req'd)
;buffer_size=10 ; event buffer queue size (default 10)
;directory=/tmp ; directory to cwd to before exec (def no cwd)
;umask=022 ; umask for process (default None)
;priority=-1 ; the relative start priority (default -1)
;autostart=true ; start at supervisord start (default: true)
;startsecs=1 ; # of secs prog must stay up to be running (def. 1)
;startretries=3 ; max # of serial start failures when starting (default 3)
;autorestart=unexpected ; autorestart if exited after running (def: unexpected)
;exitcodes=0 ; 'expected' exit codes used with autorestart (default 0)
;stopsignal=QUIT ; signal used to kill process (default TERM)
;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10)
;stopasgroup=false ; send stop signal to the UNIX process group (default false)
;killasgroup=false ; SIGKILL the UNIX process group (def false)
;user=chrism ; setuid to this UNIX account to run the program
;redirect_stderr=false ; redirect_stderr=true is not allowed for eventlisteners
;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO
;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10)
;stdout_events_enabled=false ; emit events on stdout writes (default false)
;stdout_syslog=false ; send stdout to syslog with process name (default false)
;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO
;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10)
;stderr_events_enabled=false ; emit events on stderr writes (default false)
;stderr_syslog=false ; send stderr to syslog with process name (default false)
;environment=A="1",B="2" ; process environment additions
;serverurl=AUTO ; override serverurl computation (childutils)

; The sample group section below shows all possible group values. Create one
; or more 'real' group: sections to create "heterogeneous" process groups.

;[group:thegroupname]
;programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions
;priority=999 ; the relative start priority (default 999)

; The [include] section can just contain the "files" setting. This
; setting can list multiple files (separated by whitespace or
; newlines). It can also contain wildcards. The filenames are
; interpreted as relative to this file. Included files *cannot*
; include files themselves.

;[include]
;files = relative/directory/*.ini

注意:由於supervisor運行在pipenv虛擬環境下,爲了將root用戶的環境變量複製一份傳遞到supervisor的子進程中,需要配置environment參數變量
詳見supervisor 加載系統環境變量問題Supervisor and Environment Variables

九.發佈步驟

部署成功後,後續應用更新的發佈步驟如下:

1.本地提交代碼

git commit -m '新增功能***'
git push

2.服務器拉取代碼

cd /app/run/www
git pull https://gitlab.www.com.cn/88888/automation.git
輸入工號密碼

3.重啓應用

cd /app/run/www/AutoDB
pipenv shell
supervisorctl restart adbo

十.後記

  本次部署過程較爲曲折坎坷,踩了很多坑,也收穫了很多,對gunicorn、gevent、supervisor、異步同步、nginx、ssl證書、vue的路由模式等技術有了更深入的理解。
  最值得一提的是:代碼中有使用threading的Thread類實現耗時業務的處理,不等處理完就返回值給前端,以提升web頁面的用戶體驗,包括運維腳本自動化執行模塊、實時查詢自助服務模塊等功能。這些功能在本機調試app.run()跑沒有問題,但是部署上去後發現後端處理請求時會等到耗時業務執行完成後才返回,無法達到預期效果。
  反覆排查gunicorn、supervisor、nginx的日誌,卻沒有發現任何問題,代碼調試也沒有任何報錯,百思不得其解,甚至懷疑時空是不是出現了錯亂。在否定與自我否定中不斷測試驗證,萬幸發現了一個特例:spt用戶密碼自助申請,這個功能也是耗時業務,但是部署上去後具有預期效果。OK,開始深扒代碼邏輯
  該功能處理邏輯如下:
  1.連接數據庫,執行修改數據庫spt用戶密碼的sql腳本
  2.根據用戶申請時間,time.sleep相應時間
  3.到時間後,再次連接數據庫,將spt用戶的密碼修改爲原密碼
  業務邏輯本身看不出什麼特別之處,只能比對分析,找了實時查詢模塊的例子進行對比分析
  1.連接數據庫,執行查詢數據庫狀態的sql腳本,結束
  一對比,恍然大悟!關鍵點就在time.sleep!!!
  分析如下:
  本地app.run採用的是flask框架自帶的網絡模式,是阻塞型web模式,thread調用的是threading原生的代碼,原來的設計是遇到耗時任務時,新建一個線程去處理,web主線程不管結果出沒出直接返回,因此可以滿足不讓前端用戶等待的需求。
  而部署方案採用gunicorn+gevent的模式,也就是通過協程的方式實現非阻塞,此時每個網絡請求都由協程去處理,由於協程與線程既不對等也不是上下級關係(具體差別詳見進程、線程和協程之間的區別和聯繫),按照原來的設計方式,在協程A中調用新線程thread,協程A會等待該線程的函數堆棧處理完才能返回(個人理解此時協程A啓動的新線程B還是在A的上下文中,不是獨立的新的線程,因此如果要結束A協程,必須要等到它發起的B線程執行完畢後才能結束)。
  原理搞清楚了,問題關鍵在於協程調用線程,怎麼解決呢?答案秒出:協程搞線程搞不定,搞協程總能搞定吧~原理不懂的繼續看進程、線程和協程之間的區別和聯繫(Ps:爲自己的機智打call)
  解決方案:由於gevent是猴子補丁的一種(不懂請看關於Monkey Patch猴子補丁),利用greenlet可以動態將運行的代碼改成協程模式,查詢官網及gevent源碼發現,該補丁會對Socket、time等模塊進行動態修改,比如將time.sleep修改爲greenlet.sleep等,因此,只要代碼中調用sleep函數,就會進入新的協程,這也說明了spt申請功能爲何可以多協程運行而不影響主協程(實際上,spt申請功能第1步並沒有進入一個新的協程,是在第2步開始才因爲time.sleep進入新的協程,由於第1步執行時間短而第2步sleep時間長,因此,筆者一開始分析時誤認爲其完美實現耗時任務與前端及時響應相分離的需求)。
  因此,最簡單的懶人方式,在腳本自動化執行的函數開頭新增一行代碼:time.sleep(1),歡迎進入協程的時代!重新發布並驗證,完美解決問題~

  本次部署歷經千辛萬苦,成功的一刻真想仰天長嘯!感謝堅持前進的自己,感謝我文中和文末所鏈接的所有文章的作者!

import time

#實時查詢執行腳本
#腳本狀態:等待執行/執行中/執行異常/執行成功/取消執行
#作業狀態:執行中/執行成功/執行異常
def rt_doscript(jobid):
    time.sleep(1)
    returnflag=False
    jobdata=RTJOB.query.get(jobid)
    scriptlist=jobdata.scriptlist
    #遍歷每個腳本,讀取要執行的對象列表,遍歷對象列表,連接每一個對象,執行腳本,調用rt_writestatus記錄執行結果,寫入日誌,實現操作與日誌分離
    for index,scriptdata in enumerate(scriptlist):
        #寫入開始時間
        rtscript_writebegin(jobdata.id,index,datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
......

參考文檔
http://docs.gunicorn.org/en/stable/settings.html#ssl
http://www.zhangdongshengtech.com/article-detials/81
https://www.cnblogs.com/songxingzhu/p/8568432.html
https://www.cnblogs.com/ameile/p/7447232.html
https://www.jianshu.com/p/a83a8f5d68dd?utm_campaign=maleskine&utm_content=note&utm_medium=writer_share&utm_source=weibo
https://blog.csdn.net/u012965373/article/details/52066580
https://www.cnblogs.com/cwp-bg/p/8780204.html
https://www.58jb.com/html/175.html
https://www.jianshu.com/p/09e522b8d64b
https://blog.csdn.net/xudailong_blog/article/details/80490137
https://blog.csdn.net/xudailong_blog/article/details/80821326
https://www.cnblogs.com/zhuzhenwei918/p/6892066.html
https://segmentfault.com/a/1190000010151973?utm_source=tag-newest
https://www.cnblogs.com/robert871126/p/10107258.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章