部署前後端為獨立的 Docker 節點

『伺服器部署 Vue 和 Django 項目的全記錄』一文中,介紹了在伺服器中使用 Nginx 部署前後端項目的過程。然而,當 Web 應用流量增多時,需要考慮負載均衡、流量分發、容災等情況,原生的部署方式通常難以滿足需求。此時,引入 Docker 部署多節點,能夠在單台高性能伺服器或伺服器集群中搭建更完善的部署架構。

本文主要以 Vue 和 Django 項目為例介紹 Docker 部署的流程,稍帶 Docker 簡介和基礎的 Nginx 負載均衡配置。

Docker 簡介與安裝

簡單介紹 Docker 相關概念,具體需要讀者另外學一學,推薦『Docker-從入門到實踐』

Docker 是什麼

Docker 是一個開源的應用容器引擎,可以讓開發者打包應用和依賴到一個輕量級、可移植的容器中,並發布到任何流行的 Linux 機器上,也可以實現虛擬化。容器使用沙箱機制,相互之間不存在介面,其與宿主機通過埠轉發進行通訊,性能開銷低。

Docker 部署 Web 應用有以下優點:

  • 容器適合持續集成和持續交付(CI/CD)流程
  • 響應式部署和擴展,其可移植性和輕量級的特性支援實時擴展或拆除服務
  • Docker 輕巧快速,支援開發者在同一機器上運行更多工作負載

Docker 工作流程

Docker 包括三個概念:

  • 鏡像(Image):相當於一個 root 文件系統。
  • 容器(Container):鏡像和容器的關係類似於對象程式設計中的類和實例,鏡像是靜態的定義,容器是鏡像運行時的實體。容器可以被創建、啟動、停止、刪除、暫停等。
  • 倉庫(Repository):倉庫可看成一個程式碼控制中心,用來保存鏡像。

Docker 的工作流程通常為:

  1. 從倉庫中拉取(pull)官方或基準鏡像
  2. 在 Dockerfile 中描述應用和安裝依賴的指令,構建鏡像
  3. 由鏡像創建和運行容器

Docker 安裝

『Ubuntu 安裝 Docker 環境』.

部署架構

在不考慮多節點負載均衡時,本文的部署架構如下:

前後端項目分離部署,分別部署在兩個 Nginx 節點,對應兩個域名或兩個埠。

deploy-structure

Nginx + Docker 部署前端

首先,Vue 項目打包為 dist 文件夾,同目錄下新建 Dockerfilevhosts.conf 文件和 logs 文件夾,作用見下列程式碼塊中的注釋。

.
├── dist            # Vue 項目打包用以部署的文件夾
├── Dockerfile      # 用於建立 Docker 鏡像
├── vhosts.conf     # 容器中啟動 Nginx 服務的配置文件
└── logs            # 映射容器中的 Nginx 日誌目錄,以便在宿主機查看日誌

Dockerfile 文件內容:

# 設置基礎鏡像
FROM nginx:latest
#設置CTS時區
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' > /etc/timezone
# 將dist文件中的內容複製到 /usr/share/nginx/html/ 這個目錄下面
COPY ./dist /usr/share/nginx/html/
#用本地的 vhosts.conf 配置來替換 nginx 鏡像里的默認配置
COPY vhosts.conf /etc/nginx/conf.d/vhosts.conf

vhosts.conf 文件內容:

server {
    listen       80;
    server_name  localhost;
    access_log  /var/log/nginx/host.access.log  main;
    error_log  /var/log/nginx/error.log  error;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

構建 Docker 鏡像

在終端中輸入以下命令,根據 Dockerfile 構建鏡像。

docker build -f Dockerfile -t nginx/hsheng-mall:v1.0.0 .
# 鏡像名稱:nginx/hsheng-mall
# 版本號:v1.0.0

運行 Docker 容器

依據創建的鏡像 nginx/hsheng-mall:v1.0.0 運行容器。

docker run -d -p 8081:80 --name=hsheng-mall -v /home/hsheng/www/hsheng-mall/logs:/var/log/nginx nginx/hsheng-mall:v1.0.0
# 宿主機 8081 埠映射容器 80 埠
# 容器名稱:hsheng-mall
# 宿主機 /home/hsheng/www/hsheng-mall/logs 目錄映射容器 /var/log/nginx 目錄
# 鏡像名稱:nginx/hsheng-mall:v1.0.0

宿主機 Nginx 轉發

與原生 Nginx 部署類似,在 /etc/nginx/conf.d 目錄下創建配置文件 hsheng-mall.conf,內容如下:

server {
    listen 443 ssl; # 埠,若部署 https 域名則為 443
    server_name aaa.abc.com; # 域名或 IP

    location / {
        proxy_pass //127.0.0.1:8081;   # 轉發本機(宿主機) 8081 埠,已與 Docker 埠建立映射
        proxy_redirect default;
    }

    ssl_certificate   /home/hsheng/www/hsheng-mall/ssl_certs/aaa.abc.com_bundle.crt;    # ssl證書絕對路徑
    ssl_certificate_key  /home/hsheng/www/hsheng-mall/ssl_certs/aaa.abc.com.key;    # ssl證書私鑰絕對路徑
    ssl_session_timeout 5m;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
    ssl_prefer_server_ciphers on;
}

server {
    listen 80;
    server_name aaa.abc.com;
    # 把http的域名請求轉成https
    return 301 //$host$request_uri;
}

而後重啟 Nginx 服務,即可通過 server_name 訪問 Docker 的應用服務。

sudo nginx -s reload

Nginx + Docker 部署 uWSGI 後端

項目部署目錄結構如下:

.
├── src                     # Django 項目源碼
│   ├── manage.py
│   ├── requirements.txt    # Python 項目依賴包
│   ├── uwsgi.ini           # uWSGI 配置文件
│   ├── start.sh            # Django 服務啟動腳本
|   └── ...
├── Dockerfile              # 用於建立 Docker 鏡像
└── logs                    # 映射容器中的 uWSGI 日誌目錄,以便在宿主機查看日誌

Dockerfile 文件內容:

FROM python:3.8
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' > /etc/timezone
RUN mkdir -p /var/www/html/backend
COPY ./src /var/www/html/backend/
WORKDIR /var/www/html/backend

RUN pip install -i //pypi.doubanio.com/simple uwsgi
RUN pip install -i //pypi.doubanio.com/simple/ -r requirements.txt

# Windows環境下編寫的start.sh每行命令結尾有多餘的\r字元,需移除
RUN sed -i 's/\r//' ./start.sh
RUN chmod +x ./start.sh

uwsgi.ini 文件內容:

[uwsgi]
socket = 0.0.0.0:8000   # 容器內 uWSGI 服務須為 0.0.0.0,以便與宿主機建立正常連接
project = backend
base = /var/www/html
base-app = hshengmall
chdir = %(base)/%(project)
wsgi-file = %(base)/%(project)/%(base-app)/wsgi.py
master = true
processes = 8
threads = 4
enable-threads = true
buffer-size = 65536
post-buffering = 32768
vacuum = true
pidfile = %(base)/uwsgi/%(project)-master.pid
daemonize = %(base)/uwsgi/uwsgi.log
chmod-socket = 664
# 設置一個請求的超時時間(秒),如果一個請求超過了這個時間,則請求被丟棄
harakiri = 300
# 當一個請求被harakiri殺掉會,會輸出一條日誌
harakiri-verbose = true

start.sh 文件內容:

python manage.py makemigrations&&
python manage.py migrate&&
uwsgi --ini /var/www/html/backend/uwsgi.ini

構建 Docker 鏡像

在終端中輸入以下命令,根據 Dockerfile 構建鏡像。

docker build -f Dockerfile -t python/hsheng-mall-backend:v1.0.0 .

運行 Docker 容器

依據創建的鏡像 python/hsheng-mall-backend:v1.0.0 運行容器。

docker run -it -p 8001:8000 --name=hsheng-mall-backend -v /home/hsheng/www/hsheng-mall-backend/logs:/var/www/html/uwsgi -d python/hsheng-mall-backend:v1.0.0

啟動服務

進入容器:

docker exec -it <container_id> /bin/bash

運行啟動腳本:

./start.sh

這樣即成功啟動了一個後端服務容器,若想做多節點負載均衡,可以修改埠映射關係,按上述步驟多創建和啟動幾個容器。

宿主機 Nginx 轉發

/etc/nginx/conf.d 目錄下創建配置文件 hsheng-mall-backend.conf,內容如下:

server {
    listen 443 ssl;
    server_name api-aaa.abc.com;

    location / {
        include /etc/nginx/uwsgi_params;
        uwsgi_pass 127.0.0.1:8001;  # 轉發本機(宿主機) 8001 埠,已與 Docker 埠建立映射
    }

    ssl_certificate   /home/hsheng/www/hsheng-mall-backend/ssl_certs/api-aaa.abc.com_bundle.crt;
    ssl_certificate_key  /home/hsheng/www/hsheng-mall-backend/ssl_certs/api-aaa.abc.com.key;
    ssl_session_timeout 5m;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
    ssl_prefer_server_ciphers on;
}

server {
    listen 80;
    server_name api-aaa.abc.com;
    return 301 //$host$request_uri;
}

而後重啟 Nginx 服務,即可通過 server_name 請求後端服務。

sudo nginx -s reload

*Nginx 負載均衡

簡略描述一個負載均衡的結構:Nginx + Docker 多節點部署架構。

前端節點為靜態節點,通常只需要單個節點即可,可使用 CDN 加速優化訪問。因此,當請求流量大時,主要通過增多後端 Docker+uWSGI 節點進行負載均衡。

deploy-structure-total

為實現上圖架構,首先根據本文第四章節「Docker 部署 uWSGI 後端節點」創建和啟動 3 個 Docker 後端服務節點,分別映射至宿主機 8001 ~ 8003 埠。

而後,修改宿主機用於部署後端的 Nginx 配置文件,例如本文的 hsheng-mall-backend.conf,添加 upstream。修改後文件內容應為:

upstream uwsgicluster {
    server 127.0.0.1:8001;
    server 127.0.0.1:8002;
    server 127.0.0.1:8003;
}
server {
    listen 443 ssl;
    server_name api-aaa.abc.com;

    location / {
        include /etc/nginx/uwsgi_params;
        uwsgi_pass uwsgicluster  # 轉發上游集群
    }

    ssl_certificate   /home/hsheng/www/hsheng-mall-backend/ssl_certs/api-aaa.abc.com_bundle.crt;
    ssl_certificate_key  /home/hsheng/www/hsheng-mall-backend/ssl_certs/api-aaa.abc.com.key;
    ssl_session_timeout 5m;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
    ssl_prefer_server_ciphers on;
}

server {
    listen 80;
    server_name api-aaa.abc.com;
    return 301 //$host$request_uri;
}

可以看到,與單節點不同之處在於,新增了 upstream 定義,並將 uwsgi_pass 修改為定義的 upstream 名稱。

在配置文件中,還能設置各個節點的權重分配等,此處不展開介紹,默認為輪詢方式,請求隨機派發到各節點。