1. 程式人生 > >記基於docker+gunicorn部署sanic專案遇到的很多很多坑

記基於docker+gunicorn部署sanic專案遇到的很多很多坑

前言:

  最近有個專案需要上線,是python中sanic網路非同步框架寫的,並且要求使用docker+nginx來部署專案實現負載均衡,於是乎百度了sanic專案部署,基本上都是基於docker+gunicorn部署sanic專案這篇文章,裡面講的也稍稍微有些簡略,不過對於小白特別不友好,按步驟操作肯定是不行的,因為文章中只舉了很小很小的一個例子,感覺更像demo。而小白可能只是臨時接受部署任務,按部就班的操作是會出現很多錯誤的。現在就來排排坑。(建議先看一遍再動手部署)

一、Dockerfile檔案放在哪?

  這是sanic專案的總目錄,首先明確一點我的主執行檔案是run.py,所以Dockerfile需要放在和我主檔案相同地方,這樣執行build指令就可以直接在當前目錄下建立。可以直接使用vim指令在該專案目錄下建立Dockerfile,記住檔名一定要相同

二、Dockerfile裡面寫什麼?為什麼要寫

FROM taoliu/gunicorn3

WORKDIR /temp1

ADD . /temp1

RUN pip install --upgrade pip

RUN pip install -r requirements.txt -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com

EXPOSE 9010

CMD gunicorn run:app --bind 0.0.0.0:9010 --worker-class sanic.worker.GunicornWorker

  下面一個個來解釋

  FROM:表示這個映象是基於什麼建立的,也就是基礎映象,這裡使用taoliu/gunicorn3表示我使用的是gunicorn3來執行我的映象,可以理解為專案需要一個環境來執行。而這個映象是國內的,所以下載會較快

  WORKDIR:表示工作目錄是在docker中的哪裡,可以稍微理解一下docker是個獨立的系統,就像安裝在Windows中虛擬機器虛擬的linux,和外面的Windows命令和目錄是不相互關聯的。而這個目錄也是我們後面執行命令所在的目錄。

  ADD:這裡使用ADD . /temp1,前面的“.”表示將當前所在目錄的所有檔案全部放到docker中的/temp1這裡要和上面的WORKDIR設定的一樣,命名按照自己喜歡的來,這裡是測試就用temp1代替

  RUN:就相當於開始執行指令(可以理解為在linux中輸入命令列,但其實這是在docker環境中輸入的)了,docker中的系統一些python依賴的庫是不存在的,所以我們開始先更新一下docker中的pip,再由pip獲取專案所需映象,這裡我整理到了固定的檔案內即requirements.txt,推薦使用國內映象。這樣下載較快,我使用的是阿里雲映象。

  阿里雲:http://mirrors.aliyun.com/pypi/simple/

  豆瓣:http://pypi.douban.com/simple/

  清華大學:https://pypi.tuna.tsinghua.edu.cn/simple/

  中國科學技術大學:http://pypi.mirrors.ustc.edu.cn/simple/

  華中科技大學:http://pypi.hustunique.com/

  EXPOSE:設定映象暴露埠,記錄容器啟動時監聽哪些埠,容器啟動時,Docker Daemon會掃描映象中暴露的埠,如果加入-P引數,Docker Daemon會把映象中所有暴露埠匯出,併為每個暴露埠分配一個隨機的主機埠(暴露埠是容器監聽埠,主機埠為外部訪問容器的埠)

  注意:EXPOSE只設置暴露埠並不匯出埠,只有啟動容器時使用-P/-p才匯出埠,這個時候才能通過外部訪問容器提供的服務

  CMD:設定容器的啟動命令,也就是當我們啟動容器的時候執行的命令,我的專案埠是0.0.0.0:9010,檔案是run,所以使用gunicorn3啟動時,命令為上述檔案中。需要注意的是Dockerfile中只能有一條CMD命令,如果寫多了則最後一條生效

三、build映象

  上面的步驟處理完之後就可以build出新映象了,使用下面命令:

sudo docker build -t sanic_item .

  這裡千萬要加後面的點,代表當前路徑下建立映象

四、使用run還是使用create+start

  在對於新技術肯定要經過較多的測試才能很好的掌握,如果使用下面這條命令:

sudo docker run --name sanic1 -p 8080:8080 sanic_item

  --name:為容器起個別名,這樣可以使用這個別名操作容器,而不需要用隨機的容器ID來進行操作

  -p:來指定埠,前面的埠是linux的,後面的埠是docker中專案執行的埠,兩個可以一樣可以不一樣。但是為了方便開發及測試,建議儘量一樣.

  後面就是映象名了。而這條run語句就等於create+start即建立並開啟容器。這裡說一下容器是基於映象來執行的。關於容器和映象的關係要深入docker中瞭解。這裡不詳細介紹。這條語句的弊端就是第一次使用的部署可以使用,但是如果第二次還使用,那麼就會不停的建立新容器。在不瞭解的情況下,以為run指令只會執行映象。沒想到docker中是根據映象來建立容器再執行。濫用run指令的結果圖:

  可以看出很多是基於一個映象來建立的容器。其實第一次使用完run之後便可使用一些經常使用的命令:

sudo docker stop sanic1 #停止當前執行的容器,前提是run指令時有--name來指代名字
sudo docker start sanic1 #只需要啟動就行,不需要再用run或create
sudo docker ps #檢視當前執行映象
sudo docker ps -a #檢視當前容器狀態,正在執行的容器PORTS列會有引數,上圖就是沒有容器正在執行
sudo docker rmi 映象ID/別名 #需要先使用ps指令找到要刪除的映象ID再刪除
sudo docker rm 容器ID/別名 #比如刪除sanic1這個容器sudo docker rm sanic1

五、使用nginx實現負載均衡

  首先明確一點一個埠不能同時被兩個程式所佔用。就打個比方來說,現在的專案前端發來的請求訪問的是8080埠,如果是單機版專案,直接執行監聽8080即可。但是目前遇到的情況是專案部署在docker上,還要啟動nginx來監聽。也就會造成這個埠有兩個程序在監聽,這時候只能啟動一個。並且我們要部署多個後臺服務。所以首先考慮nginx。我的nginx配置檔案如下(注意看中文註釋部分)

http { #代表HTTP協議其他的協議需要其他定義
    include       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  logs/access.log  main;
  
    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;
  server{ 
        listen 8080;#監聽8080埠
        server_name localhost;
       access_log  /usr/logs/host1.access.log  main;#開啟日誌功能,只要訪問這個埠的都會有輸出,具體的名字和目錄自己指定,main為上面定義的日誌格式
        location /{
          proxy_pass http://localhost; #對於傳送到8080埠的請求可以進行轉發
          }
        }
  server { #nginx預設服務,這裡只需要放入前端介面即可
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        access_log  /usr/logs/host.access.log  main; #不同埠我建立了不同日誌,必須要保證日誌所在目錄有效才會建立,同時日誌我這裡沒設定無快取刷出,若要看見實時訊息的才設定
    root /usr/local/dist2/;
       # location / {
        #    root   html;
         #   index  index.html index.htm;
    location /{
        index index.html index.htm;
      }
    }

    upstream localhost{#這裡是實現負載均衡的策略,我的上一篇部落格中介紹過為什麼使用這種方法
        ip_hash;       
    server 0.0.0.0:9010 weight=1;
    server 0.0.0.0:9011 weight=1;
    server 0.0.0.0:9012 weight=1;       
        }
}#這裡代表HTTP協議的請求即定義的服務完成

  可以看到我們對於8080埠的請求,轉發到了9010,9011,9012三個埠,那麼docker需要準備三個不同映象,來建立三個不同容器,這樣才能實現nginx的代理。對於sanic專案只需要改一下執行埠即可,其餘部分相同,Dockerfile中的埠也要與其對應。比如我當前的sanic專案改了三個不同埠為9010,9011,9012。(對應上面講的Dockerfile也要變),然後build三個不同映象為sanic_item1,sanic_item2,sanic_item3。然後啟動三個命令視窗輸入下面的指令

sudo docker run --name sanic1 -p 9010:9010 sanic_item1
sudo docker run --name sanic2 -p 9011:9011 sanic_item2
sudo docker run --name sanic3 -p 9012:9012 sanic_item3

  便能執行三個容器了。

  注意:這裡介紹的是縱向擴充套件的負載均衡架構,也是一個介紹。如果多臺其實只要改ip地址即可,埠可以不改變。這裡所說的docker+nginx實現負載均衡,不需要Nginx部署在docker內部,Nginx只是一個請求轉發的工具,如果是遇到上千萬級請求或上億級,超過了nginx的負荷才需要部署多臺。主要的壓力還是在後端和前端處理和解析資料上。

總結:

  這篇部落格主要是目前使用sanic專案部署的資料特別少,而這麼少的資料中整理起來,以及遇到的一些bug很難查詢。所以稍微講了一下部署專案遇到的坑和解決方案。建議先通讀一遍,腦子裡大概有個印象再上手部署。

 

 

&n