2021年12月9日 星期四

Nginx學習筆記 (4) - Load balacing負載平衡實作


為應付龐大流量時後端可能不是只有一個伺服器,Nginx作為中間代理,能解析用戶端來的請求的路徑,做派發到特定後端伺服器處理,當中也可做些負載平衡的設定,讓流量分散到各伺服器,防止只有單一伺服器繁忙的狀況


測試環境準備

首先來寫一個簡單的python flask server


資料夾結構:

docker-compose.yaml

nginx/

├─ log/

├─ html/

│  ├─ index.html

├─ default.conf

server/

├─ app.py

├─ Dockerfile

├─ requirements.txt


Dockerfile:

from python:3.8

ADD . /app
WORKDIR /app

RUN pip install -r requirements.txt


requirements.txt:

flask==2.0.1


先利用上面的 Dockerfile build我們的app image

只要在 server 資料夾裡執行build指令就可:  docker build -t test-app .           

會build出一個名為test-app的image


app.py:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return 'hello'

if __name__ == "__main__":
    app.run(host="0.0.0.0", debug=True)


docker-compose.yaml:

version: '3'
services:
  nginx:
    image: nginx
    container_name: my-nginx
    volumes:
      #- ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./nginx/html:/usr/share/nginx/html
      - ./nginx/log:/var/log/nginx
    ports:
      - "80:80"
    environment:
      - NGINX_PORT=80
    restart: always
    networks:
      - mylab
  app:
    image: test-app   # 使用我們上面build的image
    container_name: my-app
    volumes:
      - ./server:/app
    ports:
      - "5000:5000"
    environment:
      - FLASK_RUN_PORT=5000
      - FLASK_ENV=development
      - FLASK_APP=app.py
    command: ["flask", "run", "-h", "0.0.0.0"]
    networks:
      - mylab
  app-2:
    image: test-app
    container_name: my-app-2
    volumes:
      - ./server:/app
    ports:
      - "5001:5001"
    environment:
      - FLASK_RUN_PORT=5001
      - FLASK_ENV=development
      - FLASK_APP=app.py
    command: ["flask", "run", "-h", "0.0.0.0"]
    networks:
      - mylab

networks:
  mylab:
    driver: bridge


docker-compose up 運行,再試著以 curl localhost:5000 連線,應該就可以收到flask server回覆的 hello 字串

如此我們就設定好我們的後端server ,接下來是要設定Nginx將Request導向後端server


Nginx 的 Load Balance 設定

上面的 docker-compose up 會跑起兩個相同功能的server,container my-app 跟 container my-app-2

那我們就試著設定 Nginx 讓流量分散到這兩個 app 吧


default.conf:

upstream app {
    server my-app:5000;
    server my-app-2:5001;
}

server {

    listen  80;
    server_name localhost;

    location / {
        proxy_pass http://app;
    }
    
}


upstream 可以定義 server pool,upstream 做為入口,幫忙分流進宣告的 server

以上面的例子來看,我們定義了一個 upstream app,app 後接的是我們 docker-compose 跑起的 my-app 和 my-app2 這兩個 server


稍微說明一下 docker-compose 的網路,上面的 docker-compose.yaml 每個 service 都有寫 networks: mylab,另外最下面有定義networks mylab 是使用 bridge 連線,就我們跑起這個 docker-compose,會架起一個叫 mylab 的網路,而在這網路的 service 就有 my-nginx, my-app, my-app-2 這三個容器,容器名也做為 hostname,在這 mylab 網路裡 my-nginx 容器裡 ping my-app 能成功 ping 到

因此 upstream 裡的 server 才會寫成 my-app:5000

如果你想測試 ping 看看,可以這麼做:

docker exec -ti my-nginx bash     # 先進入my-nginx container裡

### 在 my-nginx 容器中執行 ###
apt-get update
apt-get install iputils-ping    # 要先下載ping tool
ping my-app


proxy_pass url會將請求導向後面的 url 指向的位置

prxoy_pass 設定 path 要注意:

location /name/ {
    proxy_pass http://app/remote/;
}

如果傳 `http://localhost/name/1` 會導到 `http://app/remote/1`,因為當proxy_pass is specified with a URI 時,只會將 /name/ 後的值接到 app/remote/ 後


location /name/ {
    proxy_pass http://app;
}

如果傳 `http://localhost/name/1` 則會導到 `http://app/name/1`,因為當proxy_pass is specified without a URI 時,則會將整個 path 值接到後面


測試

首先跑起我們的 services: 跑 docker-compose up

接著連線多次看看:curl localhost

會看到這樣的結果


my-app 和 my-app-2 輪流收到訊息,顯示我們成功做到分流


另外也可試著把一個 my-aooserver 關掉看看:docker stop my-app

會發現 Nginx 之後都會改只傳給 my-app-2


Nginx的負載平衡也會考量進這個server狀態,若server沒回應時,之後的輪詢將不會輪到此server

但Nginx只提供被動偵測(Nginx Plus才有主動偵測),當某 server 連線失敗達到 max_fails 次數,會判斷這個 server 不能用,會等待過 fail_timeout 時間後才會再去請求此 server,如果之後成功,會恢復之前的輪詢方式

預設定是 fail_timeout=10, max_fails=1



沒有留言:

張貼留言