天天看點

Docker一拖三Tornado6.2 + Nginx + Supervisord非阻塞負載均衡容器

作者:劉悅技術分享

容器,又見容器。Docker容器的最主要優點就在于它們是可移植的。一套服務,其所有的依賴關系可以捆綁到一個獨立于Linux核心、平台分布或部署模型的主機版本的單個容器中。此容器可以傳輸到另一台運作Docker的主機上,并且在沒有相容性問題的情況下執行。而傳統的微服務架構會将各個服務單獨封裝為容器,雖然微服務容器化環境能夠在給定數量的基礎架構内實作更高的工作負載密度,但是,在整個生産環境中建立、監視和銷毀的容器需求總量呈指數級增長,進而顯著增加了基于容器管理環境的複雜性。

藉此,本次我們将服務化零為整,将Tornado服務和Nginx伺服器以及配套的監控管理程式Supervisor內建到一個單獨的容器中,将其高度可移植性最大化地發揮。

Docker具體安裝流程請移玉步到:一寸當機一寸血,十萬容器十萬兵|Win10/Mac系統下基于Kubernetes(k8s)搭建Gunicorn+Flask高可用Web叢集

整體容器内的系統架構如圖所示:

Docker一拖三Tornado6.2 + Nginx + Supervisord非阻塞負載均衡容器

首先,建立項目目錄 mytornado:

mkdir mytornado           

這裡web服務架構我們使用業内著名的非阻塞異步架構Tornado6.2,建立一個服務的入口檔案main.py

import json
import tornado.ioloop
import tornado.web
import tornado.httpserver
from tornado.options import define, options


define('port', default=8000, help='default port', type=int)


class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, Tornado")


def make_app():
    return tornado.web.Application([
        (r"/", IndexHandler),
    ])


if __name__ == "__main__":
    tornado.options.parse_command_line()
    app = make_app()
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()           

這裡運作端口我們通過指令行傳參的方式進行監聽,友善多程序服務的啟動。

之後,建立 requirements.txt 項目依賴檔案:

tornado==6.2           

接下來,建立Nginx的配置檔案 tornado.conf

upstream mytornado {
    server 127.0.0.1:8000;
    server 127.0.0.1:8001;
}
server {
    listen      80;

    location / {
        proxy_pass http://mytornado;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}           

這裡Nginx監聽80端口,反向代理到本地系統的8000和8001端口,這裡我們使用預設的負載均衡方案:輪詢,如果有其他需求,可以按照其他的方案進行修改:

1、輪詢(預設)
每個請求按時間順序逐一配置設定到不同的後端伺服器,如果後端伺服器down掉,能自動剔除。

upstream backserver {
    server 192.168.0.14;
    server 192.168.0.15;
}


2、權重 weight
指定輪詢幾率,weight和通路比率成正比,用于後端伺服器性能不均的情況。

upstream backserver {
    server 192.168.0.14 weight=3;
    server 192.168.0.15 weight=7;
}


3、ip_hash( IP綁定)
上述方式存在一個問題就是說,在負載均衡系統中,假如使用者在某台伺服器上登入了,那麼該使用者第二次請求的時候,因為我們是負載均衡系統,每次請求都會重新定位到伺服器叢集中的某一個,那麼已經登入某一個伺服器的使用者再重新定位到另一個伺服器,其登入資訊将會丢失,這樣顯然是不妥的。

我們可以采用ip_hash指令解決這個問題,如果客戶已經通路了某個伺服器,當使用者再次通路時,會将該請求通過雜湊演算法,自動定位到該伺服器。

每個請求按通路ip的hash結果配置設定,這樣每個訪客固定通路一個後端伺服器,可以解決session的問題。

upstream backserver {
    ip_hash;
    server 192.168.0.14:88;
    server 192.168.0.15:80;
}


4、fair(第三方插件)
按後端伺服器的響應時間來配置設定請求,響應時間短的優先配置設定。

upstream backserver {
    server server1;
    server server2;
    fair;
}


5、url_hash(第三方插件)
按通路url的hash結果來配置設定請求,使每個url定向到同一個後端伺服器,後端伺服器為緩存時比較有效。

upstream backserver {
    server squid1:3128;
    server squid2:3128;
    hash $request_uri;
    hash_method crc32;
}           

下面我們編寫Supervisor的配置檔案supervisord.conf:

[supervisord]
nodaemon=true

[program:nginx]
command=/usr/sbin/nginx

[group:tornadoes]
programs=tornado-8000,tornado-8001

[program:tornado-8000]
command=python3.8 /root/mytornado/main.py --port=8000
# 執行目錄
directory=/root/mytornado
# 自動重新開機
autorestart=true
# 啟動supervisor時,程式自啟動
autostart=true
# 日志
stdout=/var/log/tornado-8000.log
redirect_stderr=true
loglevel=info

[program:tornado-8001]
command=python3.8 /root/mytornado/main.py --port=8001
# 執行目錄
directory=/root/mytornado
# 自動重新開機
autorestart=true
# 啟動supervisor時,程式自啟動
autostart=true
# 日志
stdout=/var/log/tornado-8001.log
redirect_stderr=true
loglevel=info           

Supervisor 是專門用來在類 Unix 系統上監控管理程序的工具,釋出于 2004 年,它對應的角色分别為 Supervisorctl 和 Supervisord。後者的主要作用是啟動配置好的程式、響應 Supervisorctl 發過來的指令以及重新開機退出的子程序,而前者是 Supervisor 的用戶端,它以指令行的形式提供了一系列參數,來友善使用者向 Supervisord 發送指令,常用的有啟動、暫停、移除、更新等指令。

這裡我們主要使用Supervisor針對Tornado服務進行監控和管理,這裡預設的項目目錄為/root/mytornado/

程序配置兩個,分别對應nginx的監聽端口:8000和8001

最後,編寫容器配置檔案Dockerfile:

FROM yankovg/python3.8.2-ubuntu18.04

RUN sed -i "s@/archive.ubuntu.com/@/mirrors.163.com/@g" /etc/apt/sources.list \
    && rm -rf /var/lib/apt/lists/* \
    && apt-get update --fix-missing -o Acquire::http::No-Cache=True
RUN apt install -y nginx supervisor pngquant

# application
RUN mkdir /root/mytornado
WORKDIR /root/mytornado
COPY  main.py /root/mytornado/
COPY  requirements.txt /root/mytornado/
RUN pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/

# nginx
RUN rm /etc/nginx/sites-enabled/default
COPY tornado.conf /etc/nginx/sites-available/
RUN ln -s /etc/nginx/sites-available/tornado.conf /etc/nginx/sites-enabled/tornado.conf
RUN echo "daemon off;" >> /etc/nginx/nginx.conf

# supervisord
RUN mkdir -p /var/log/supervisor
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# run
CMD ["/usr/bin/supervisord"]           

這裡基礎鏡像選擇預裝了Python3.8的Ubuntu18,兼具了小體積和可擴充的特性,添加apt-get的安裝源之後,分别安裝Nginx以及Supervisor。

随後,依照Supervisor配置檔案内所書,在容器内部建立項目目錄/root/mytornado/

并且将上面編寫好的main.py以及requirements.txt複制到容器内部,運作pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ 安裝項目的所有依賴。

最後,将tornado.conf和supervisord.conf也拷貝到對應的配置路徑中,分别啟動Nginx和Supervisor服務。

編寫好之後,在項目根目錄的終端内運作指令打包鏡像:

docker build -t 'mytornado' .           

首次編譯會等待一小會兒,因為需要下載下傳基礎鏡像服務:

liuyue:docker_tornado liuyue$ docker build -t mytornado .
[+] Building 16.2s (19/19) FINISHED                                                                                                          
 => [internal] load build definition from Dockerfile                                                                                    0.1s
 => => transferring dockerfile: 37B                                                                                                     0.0s
 => [internal] load .dockerignore                                                                                                       0.0s
 => => transferring context: 2B                                                                                                         0.0s
 => [internal] load metadata for docker.io/yankovg/python3.8.2-ubuntu18.04:latest                                                      15.9s
 => [internal] load build context                                                                                                       0.0s
 => => transferring context: 132B                                                                                                       0.0s
 => [ 1/14] FROM docker.io/yankovg/python3.8.2-ubuntu18.04@sha256:811ad1ba536c1bd2854a42b5d6655fa9609dce1370a6b6d48087b3073c8f5fce      0.0s
 => CACHED [ 2/14] RUN sed -i "s@/archive.ubuntu.com/@/mirrors.163.com/@g" /etc/apt/sources.list     && rm -rf /var/lib/apt/lists/*     0.0s
 => CACHED [ 3/14] RUN apt install -y nginx supervisor pngquant                                                                         0.0s
 => CACHED [ 4/14] RUN mkdir /root/mytornado                                                                                            0.0s
 => CACHED [ 5/14] WORKDIR /root/mytornado                                                                                              0.0s
 => CACHED [ 6/14] COPY  main.py /root/mytornado/                                                                                       0.0s
 => CACHED [ 7/14] COPY  requirements.txt /root/mytornado/                                                                              0.0s
 => CACHED [ 8/14] RUN pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/                                       0.0s
 => CACHED [ 9/14] RUN rm /etc/nginx/sites-enabled/default                                                                              0.0s
 => CACHED [10/14] COPY tornado.conf /etc/nginx/sites-available/                                                                        0.0s
 => CACHED [11/14] RUN ln -s /etc/nginx/sites-available/tornado.conf /etc/nginx/sites-enabled/tornado.conf                              0.0s
 => CACHED [12/14] RUN echo "daemon off;" >> /etc/nginx/nginx.conf                                                                      0.0s
 => CACHED [13/14] RUN mkdir -p /var/log/supervisor                                                                                     0.0s
 => CACHED [14/14] COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf                                                        0.0s
 => exporting to image                                                                                                                  0.0s
 => => exporting layers                                                                                                                 0.0s
 => => writing image sha256:2dd8f260882873b587225d81f7af98e1995032448ff3d51cd5746244c249f751                                            0.0s
 => => naming to docker.io/library/mytornado                                                                                            0.0s           

打包成功後,運作指令檢視鏡像資訊:

docker images           

可以看到鏡像總大小不到1g:

liuyue:docker_tornado liuyue$ docker images
REPOSITORY                           TAG       IMAGE ID       CREATED         SIZE
mytornado                            latest    2dd8f2608828   4 hours ago     828MB           

接着讓我們來啟動容器:

docker run -d -p 80:80 mytornado           

通過端口映射技術,将容器内的80端口服務映射到主控端的80端口。

輸入指令檢視服務程序:

docker ps           

顯示正在運作:

docker ps
CONTAINER ID   IMAGE       COMMAND                  CREATED         STATUS         PORTS                               NAMES
60e071ba2a36   mytornado   "/usr/bin/supervisord"   6 seconds ago   Up 2 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp   frosty_lamport
liuyue:docker_tornado liuyue$           

此時我們打開浏覽器通路 http://127.0.0.1

Docker一拖三Tornado6.2 + Nginx + Supervisord非阻塞負載均衡容器

沒有任何問題。

同時可以根據運作中的容器id來選擇進入對應的容器:

liuyue:docker_tornado liuyue$ docker exec -it 60e071ba2a36 /bin/sh
#           

在容器内部我們可以看到項目的所有檔案:

# pwd
/root/mytornado
# ls
main.py  requirements.txt
#           

重要的是,可以使用Supervisor對既有的Tornado程序進行管理操作,

檢視所有程序:

supervisorctl status           

根據配置檔案,我們容器内運作着三個服務:

# supervisorctl status
nginx                            RUNNING   pid 10, uptime 0:54:28
tornadoes:tornado-8000           RUNNING   pid 11, uptime 0:54:28
tornadoes:tornado-8001           RUNNING   pid 12, uptime 0:54:28           

依據服務名稱,對服務進行停止操作:

# supervisorctl stop tornadoes:tornado-8001
tornadoes:tornado-8001: stopped
# supervisorctl status
nginx                            RUNNING   pid 10, uptime 0:55:52
tornadoes:tornado-8000           RUNNING   pid 11, uptime 0:55:52
tornadoes:tornado-8001           STOPPED   Dec 28 08:47 AM
#           

再次啟動:

# supervisorctl start tornadoes:tornado-8001                       
tornadoes:tornado-8001: started
# supervisorctl status
nginx                            RUNNING   pid 10, uptime 0:57:09
tornadoes:tornado-8000           RUNNING   pid 11, uptime 0:57:09
tornadoes:tornado-8001           RUNNING   pid 34, uptime 0:00:08
#           

如果服務程序意外終止,Supervisor可以對其進行拉起操作,滿血複活:

# ps -aux | grep python
root         1  0.0  0.1  55744 20956 ?        Ss   07:58   0:01 /usr/bin/python /usr/bin/supervisord
root        11  0.0  0.1 102148 22832 ?        S    07:58   0:00 python3.8 /root/mytornado/main.py --port=8000
root        34  0.0  0.1 102148 22556 ?        S    08:48   0:00 python3.8 /root/mytornado/main.py --port=8001
root        43  0.0  0.0  11468  1060 pts/0    S+   08:51   0:00 grep python
# kill -9 34
# supervisorctl status
nginx                            RUNNING   pid 10, uptime 1:00:27
tornadoes:tornado-8000           RUNNING   pid 11, uptime 1:00:27
tornadoes:tornado-8001           RUNNING   pid 44, uptime 0:00:16           

如果願意,也可以将編譯好的鏡像送出到Dockerhub上,這樣可以做到随時使用随時拉取,不需要每次都進行編譯操作,這裡我已經将鏡像推送到雲端,需要的話可以直接拉取使用:

docker pull zcxey2911/mytornado:latest           

Dockerhub的具體操作流程請參見:利用DockerHub在Centos7.7環境下部署Nginx反向代理Gunicorn+Flask獨立架構

結語:誠然,Docker容器技術消除了線上線下的環境差異,保證了服務生命周期的環境一緻性标準化。開發者使用鏡像實作标準開發環境的建構,開發完成後通過封裝着完整環境和應用的鏡像進行遷移,藉此,測試和運維人員可以直接部署軟體鏡像來進行測試和釋出,大大簡化了持續內建、測試和釋出的過程。

但是我們也不得不正視容器技術現有階段的劣勢,那就是性能的損耗,Docker容器對CPU和記憶體的使用幾乎沒有任何開銷,但它們會影響I/O和OS互動。這種開銷是以每個I/O操作的額外周期的形式出現的,是以小I/O比大I/O遭受的損失要大得多。這種開銷增加了I/O延遲,減少了用于有用工作的CPU周期,進而限制了吞吐量。也許不久的将來,随着核心技術的提升,該缺陷會被逐漸解決,最後奉上項目位址,與君共觞:https://github.com/zcxey2911/Docker_Tornado6_Supervisor_Python3.8