Skip to content

[Linux] 使用 Nginx 在單一端口上做反向代理、轉發不同端口的服務請求

Last Updated on 2024-08-04 by Clay

介紹

Nginx 是一個高性能的 HTTP 伺服器和反向代理伺服器,經常用於網頁的功能分流導向(反向代理)、負載平衡和 HTTP Cache 等應用場景,而本篇著重在紀錄如何透過設定 Nginx,透過不同的 API 請求分流到對應的服務。

在我真實應用的場景中,碰到的一個狀況是:只要上線的服務、公開的伺服器,總是會有非常多的網路惡意機器人程式在不停地試打伺服器的端口(port),想要找到個可以跑進來的漏洞;而我們的防火牆可以限制公開的端口,進而起到防護的作用。

一個非常好的實作是:我們可能有許多的服務起在不同的端口上,而我們可以藉由細分 API 請求的路徑,由 Nginx 代理伺服器協助轉發,而伺服器的端口只由防火牆開放一個即可,其他的服務就由 Nginx 做反向代理轉發。

Nginx 轉發請求示意圖

以下我們就在本機端一步步實際操作一次:

  1. 使用 Python FastAPI + Uvicorn 開啟兩個小服務
  2. 建立 Nginx 代理服務
  3. 測試轉發效果

Python 建立測試用 API 服務

首先安裝需要用到的 fastapi 和 uvicorn 套件。

pip install fastapi uvicorn


接著寫一個測試用的 API 服務、我們可以藉由輸入參數啟動帶有不同 API 路徑的 API 服務。

from fastapi import FastAPI
import uvicorn
import argparse


def create_app(api_name: str, port: str) -> FastAPI:
    app = FastAPI()

    @app.get(f"/api/{api_name}")
    async def read_root():
        return {"message": f"Hi, I am the endpoint /api/{api_name} on port {port}"}

    return app


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="FastAPI server")
    parser.add_argument("--port", type=int, default=8000, help="Port to run the server on")
    parser.add_argument("--api-name", type=str, required=True, help="API name to be used in the endpoint")
    args = parser.parse_args()

    app = create_app(
        api_name=args.api_name,
        port=args.port,
    )
    uvicorn.run(app, host="0.0.0.0", port=args.port)


接著我們輸入:

python3 fastapi_service.py --api-name service1 --port 8001
python3 fastapi_service.py --api-name service2 --port 8002


Output:

INFO:     Started server process [481342]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8001 (Press CTRL+C to quit)


AND


INFO: Started server process [452845]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8002 (Press CTRL+C to quit)

我們就會成功開啟兩個測試用的服務,並且兩者的 API 分別為 /api/service1/api/service2

可以使用 curl 指令確認兩者是否正常運作。

curl "http://127.0.0.1:8001/api/service1"


Output:

{"message":"Hi, I am the endpoint /api/service1 on port 8001"}

建立 Nginx 代理服務

假設我們可能存在很多的服務,我們需要建立一個 Nginx 的代理服務,讓所有的 API 請求都可以分發到正確的資源位址。

首先,我們安裝 Nginx:

sudo apt update
sudo apt install nginx


接著編輯設定檔 /etc/nginx/conf.d/default.conf

sudo vim /etc/nginx/conf.d/default.conf


並寫入設定檔:

upstream service1 {
    server 127.0.0.1:8001;
}

upstream service2 {
    server 127.0.0.1:8002;
}

server {
    listen 443;

    location /api/service1 {
        proxy_pass http://service1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /api/service2 {
        proxy_pass http://service2;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}


這份設定檔的 upstream 區塊,是用於定義上游伺服器的位置,簡單來說就是定義 Nginx 要轉發的 API 請求,究竟該發送的服務與其對應的實際位置

server 區塊,首先定義了 Nginx 服務監聽的端口 443,只要請求發送到 443,統一都會由 Nginx 服務轉發。

location 區塊是非常重要的設定,這裡定義了只要帶有 /api/service1 開頭的請求,都會被代理到 service1 的 server 位置(8001)、/api/service2 開頭的請求,都會被代理到 service2 的 server 位置(8002)。

順帶一提,Nginx 的設定檔也支援正規表示法Regular Expression, RE)的 location 定義。比方說我們可以寫成以下形式:

...
    # ~ 代表區分大小寫
    location ~ ^/api/service1/.*$ {
        proxy_pass http://service1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # ~* 代表不區分大小寫
    location ~* ^/api/service2/.*$ {
        proxy_pass http://service2;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}


最後,我們可以先檢查 Nginx 的設定有無任何錯誤:

sudo nginx -t


接著重新啟動 Nginx 服務:

sudo systemctl restart nginx.service


最後,我們來實際測試轉發的效果究竟如何吧!現在,我們直接向 443 端口發送請求:

curl "http://127.0.0.1:443/api/service1"
curl "http://127.0.0.1:443/api/service2"


Output:

{"message":"Hi, I am the endpoint /api/service1 on port 8001"}
{"message":"Hi, I am the endpoint /api/service2 on port 8002"}


我們可以看到,Nginx 很順利地接收了打到 443 端口的請求,並根據 API 路徑轉發到對應的端口去了!

以上,就是 Nginx 的一個最簡單的配置。實際上,Nginx 還有許多方便的應用,比方說與 certbot 配合,掛上免費的 SSL 憑證、或是由 htpasswd 建立簡單的身份驗證機制。

這些更進一步的 Nginx 應用,預計接下來一起整理成文,若有興趣,可以在我的網站內搜尋看看,屆時應該是已經發文了。

感謝大家讀到這裡,有什麼問題隨時都可以發出來討論,我會盡快回覆的~


References


Read More

Tags:

Leave a Reply