天天看點

[原創]智能化運維平台部署(gunicorn+nginx+gevent+supervisor部署flask+vue)

2019年6月13日

前言

簡要介紹一下,為小白普及基礎知識,老司機請自行略過~

  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

[[email protected] 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

如果有正常傳回頁面,則成功

筆者後來申請了證書server.cer和密碼檔案server.key,後續配置以申請的證書名稱為準

四.wsgi伺服器安裝部署

WSGI的全稱是Web Server Gateway Interface,翻譯過來就是Web伺服器網關接口。具體的來說,WSGI是一個規範,定義了Web伺服器如何與Python應用程式進行互動,使得使用Python寫的Web應用程式可以和Web伺服器對接起來,基于gunicorn的強大性能,采用gunicorn作為flask的wsgi伺服器是最佳實踐之一。WSGI的了解詳見什麼是wsgi和uWSGI和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

[[email protected] nginx]# cd /etc/nginx
[[email protected] 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端口

[[email protected] services]#  firewall-cmd --zone=public --list-ports
[[email protected] services]# firewall-cmd --zone=public --add-port=80/tcp --permanent 
success
[[email protected] services]# firewall-cmd --zone=public --add-port=443/tcp --permanent 
success
[[email protected] services]# firewall-cmd --reload
success
[[email protected] 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

繼續閱讀