Last Updated on 2024-08-04 by Clay
介紹
Nginx 是一個高性能的 HTTP 伺服器和反向代理伺服器,經常用於網頁的功能分流導向(反向代理)、負載平衡和 HTTP Cache 等應用場景,而本篇著重在紀錄如何透過設定 Nginx,透過不同的 API 請求分流到對應的服務。
在我真實應用的場景中,碰到的一個狀況是:只要上線的服務、公開的伺服器,總是會有非常多的網路惡意機器人程式在不停地試打伺服器的端口(port),想要找到個可以跑進來的漏洞;而我們的防火牆可以限制公開的端口,進而起到防護的作用。
一個非常好的實作是:我們可能有許多的服務起在不同的端口上,而我們可以藉由細分 API 請求的路徑,由 Nginx 代理伺服器協助轉發,而伺服器的端口只由防火牆開放一個即可,其他的服務就由 Nginx 做反向代理轉發。
以下我們就在本機端一步步實際操作一次:
- 使用 Python FastAPI + Uvicorn 開啟兩個小服務
- 建立 Nginx 代理服務
- 測試轉發效果
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 應用,預計接下來一起整理成文,若有興趣,可以在我的網站內搜尋看看,屆時應該是已經發文了。
感謝大家讀到這裡,有什麼問題隨時都可以發出來討論,我會盡快回覆的~