2025年7月14日 星期一

Mapleboard MP510-50 測試 (二十七) : 用獨立站台執行應用程式 (2)

在前一篇測試中, 我用獨立的站台 hello 取代預設站台 default 來執行一個 Flask 網頁應用程式 hello.py, 如果網站有多個 app 要執行, 是不是要為每個 app 創建一個站台呢? 對於相同的網域來說這是行不通的, 會有域名衝突 (server name conflicting) 問題. 

如果在 /etc/nginx/sites-enabled/ 底下有兩個網站設定檔 server name 相同 (例如 tony1966.cc 與 www.tony1966.cc), 也都是監聽 80 埠, 則在用 sudo nginx -t 檢測全部網站設定檔時會出現域名衝突的警告, 例如 : 

ony1966@LX2438:~$ sudo nginx -t       
nginx: [warn] conflicting server name "tony1966.cc" on 0.0.0.0:80, ignored

這是因為 Nginx 不允許多個 server 區塊 (Context blocks) 使用相同的 server_name (例如 tony1966.cc) 且監聽相同的埠之故. 這時 Nginx 會以特定規則 (如果域名與埠號雷同, 就以設定檔名稱的字典順序排序後尋找第一個) 選擇其中一個網站設定檔使其生效, 其他相衝突的設定檔無效 (因此這些站台不會被啟動). 總之, Nginx 網站設定檔 server 區塊的 server_name 與 listen 組合必須是獨一無二的. 

如果要在同一域名下執行多個 web app, 解決方案是在網站設定檔中使用子路徑 (URL path), 也就是用 location 區塊為各個 app 指定不同的 URL 路徑, 並以 proxy_pass 指令將對此 URL 的請求轉發到上游的應用伺服器 (例如 Gunicorn 或 uWSGI), 例如 : 

server {  # 區塊
    listen 80;   # 監聽 80 埠 (HTTP) 的指令
    server_name tony1966.cc www.tony1966.cc;    # 域名的指令

    # 第一個應用程式:app1.py
    location /app1/ {   # 位置區塊 1 : URL 路徑 : tony1966.cc/app1/
        proxy_pass http://127.0.0.1:8080/;     # 轉發至 Gunicorn 伺服器
        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;
    }

    # 第二個應用程式:app2.py
    location /app2/ {   # 位置區塊 2 : URL 路徑 : tony1966.cc/app2/
        proxy_pass http://127.0.0.1:8081/;    # 轉發至 Gunicorn 伺服器
        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;
    }

此例會將對 tony1966.cc/app1/ 的請求轉發至 Gunicorn 的 http://127.0.0.1:8080/ 執行監聽該埠之應用程式; 將 tony1966.cc/app2/ 的請求轉發至 Gunicorn 的 http://127.0.0.1:8081/ 執行監聽該埠之應用程式. 

本篇旨在前一篇執行 hello.py 基礎上新增另一個應用程式 hello2.py, 透過修改站台設定檔 location 指令的 URL 路徑來執行這兩個 web app. 

本系列全部的測試紀錄參考 :



1. 建立 Flask 網頁應用程式 hello2.py : 

先切換到 flask_apps 資料夾, 用 nano 編輯一個 hello2.py, 把之前的 hello.py 內容複製過來, 將英文回應改成中文回應, 並將測試用的開發伺服器監聽埠改為 8081 : 

tony1966@LX2438:~$ cd flask_apps   
tony1966@LX2438:~/flask_apps$ nano hello2.py   

# hello2.py 
from flask import Flask
app = Flask(__name__)
app.debug=True

@app.route("/")
def hello():
    return "<h1>歡迎來到我的網站!</h1>"

@app.route("/hello/<name>") 
def hello_name(name): 
    return "<h1>您好, %s</h1>" % name 

if __name__ == "__main__":
    app.run(host='127.0.0.1', port=8081)    # 啟動 Werkzeug 開發伺服器

然後用 python3 執行此 app : 

tony1966@LX2438:~/flask_apps$ python3 hello2.py   
 * Serving Flask app 'hello2'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:8081
Press CTRL+C to quit
 * Restarting with watchdog (inotify)
 * Debugger is active!
 * Debugger PIN: 650-986-211
127.0.0.1 - - [11/Jul/2025 16:02:20] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [11/Jul/2025 16:02:20] "GET /favicon.ico HTTP/1.1" 404 -
127.0.0.1 - - [11/Jul/2025 16:02:37] "GET /hello/Tony HTTP/1.1" 200 -

用瀏覽器拜訪 127.0.0.1:8081 與 127.0.0.1:8081/hello/Tony 可看到網頁結果如下 :




2. 啟動 Gunicorn 程序執行 hello2.py : 

在前一篇測試中我們已啟動 Gunicorn 來監聽 127.0.0.1:8080 埠, 只要 Nginx 轉發 HTTP 請求就會執行 hello.py 來產生回應. 可用下列指令查詢目前執行中的 Gunicorn 程序 : 

tony1966@LX2438:~/flask_apps$ ps aux | grep gunicorn   
tony1966  223187  0.0  0.2  34156  9840 ?        Ss   Jul06   4:03 gunicorn: master [hello:app]
tony1966  223192  0.0  0.6  42348 24688 ?        S    Jul06   0:25 gunicorn: worker [hello:app]
tony1966  223193  0.0  0.6  42344 24492 ?        S    Jul06   0:25 gunicorn: worker [hello:app]
tony1966  223194  0.0  0.6  42344 24612 ?        S    Jul06   0:25 gunicorn: worker [hello:app]
tony1966  223195  0.0  0.6  42344 24640 ?        S    Jul06   0:25 gunicorn: worker [hello:app]
tony1966  413781  0.0  0.0   9136  1712 pts/0    S+   11:07   0:00 grep --color=auto gunicorn

接下來用下列指令啟動 Gunicorn 伺服器監聽 127.0.0.1 的 8081 埠, 當收到請求時就執行 hello2.py 的 app 物件 :  

tony1966@LX2438:~/flask_apps$ gunicorn -w 4 -b 127.0.0.1:8081 hello2:app    
[2025-07-14 15:32:39 +0800] [418476] [INFO] Starting gunicorn 23.0.0
[2025-07-14 15:32:39 +0800] [418476] [INFO] Listening at: http://127.0.0.1:8081 (418476)
[2025-07-14 15:32:39 +0800] [418476] [INFO] Using worker: sync
[2025-07-14 15:32:39 +0800] [418477] [INFO] Booting worker with pid: 418477
[2025-07-14 15:32:39 +0800] [418478] [INFO] Booting worker with pid: 418478
[2025-07-14 15:32:39 +0800] [418479] [INFO] Booting worker with pid: 418479
[2025-07-14 15:32:39 +0800] [418480] [INFO] Booting worker with pid: 418480

下列指令可確認 Gunicorn 正在監聽 8081 埠 :

tony1966@LX2438:~$ sudo lsof -i :8081   
[sudo] tony1966 的密碼: 
COMMAND      PID     USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
gunicorn: 418476 tony1966    5u  IPv4 5927413      0t0  TCP localhost:tproxy (LISTEN)
gunicorn: 418477 tony1966    5u  IPv4 5927413      0t0  TCP localhost:tproxy (LISTEN)
gunicorn: 418478 tony1966    5u  IPv4 5927413      0t0  TCP localhost:tproxy (LISTEN)
gunicorn: 418479 tony1966    5u  IPv4 5927413      0t0  TCP localhost:tproxy (LISTEN)
gunicorn: 418480 tony1966    5u  IPv4 5927413      0t0  TCP localhost:tproxy (LISTEN)

目前 Gunicorn 共有兩組程序分別監聽 8080 與 8081 埠了 :

tony1966@LX2438:~$ ps aux | grep gunicorn   
tony1966  223187  0.0  0.2  34156  9192 ?        Ss   Jul06   4:09 gunicorn: master [hello:app]
tony1966  223192  0.0  0.6  42348 24424 ?        S    Jul06   0:26 gunicorn: worker [hello:app]
tony1966  223193  0.0  0.6  42344 24224 ?        S    Jul06   0:26 gunicorn: worker [hello:app]
tony1966  223194  0.0  0.6  42344 24308 ?        S    Jul06   0:26 gunicorn: worker [hello:app]
tony1966  223195  0.0  0.6  42344 24316 ?        S    Jul06   0:26 gunicorn: worker [hello:app]
tony1966  418476  0.0  0.5  34156 20248 pts/0    S+   15:32   0:01 gunicorn: master [hello2:app]
tony1966  418477  0.0  0.6  41156 26008 pts/0    S+   15:32   0:00 gunicorn: worker [hello2:app]
tony1966  418478  0.0  0.6  41156 25984 pts/0    S+   15:32   0:00 gunicorn: worker [hello2:app]
tony1966  418479  0.0  0.6  41156 26004 pts/0    S+   15:32   0:00 gunicorn: worker [hello2:app]
tony1966  418480  0.0  0.6  41156 26024 pts/0    S+   15:32   0:00 gunicorn: worker [hello2:app]
tony1966  419855  0.0  0.0   9136  1700 pts/1    S+   16:01   0:00 grep --color=auto gunicorn


3. 修改站台設定檔 hello :   

用 nano 編輯前一篇測試的站台設定檔 hello, 加入第二個 location 指令, 將對 https://tony1966.cc/hello2/ 的請求轉發至 127.0.0.1 的 8081 埠 : 

tony1966@LX2438:~$ sudo nano /etc/nginx/sites-available/hello   
[sudo] tony1966 的密碼: 


server {
    server_name tony1966.cc www.tony1966.cc;

    location / {
        proxy_pass http://127.0.0.1:8080/;
        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 /hello2/ {
        proxy_pass http://127.0.0.1:8081/;
        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;
    }


    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/tony1966.cc/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/tony1966.cc/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot


}
server {
    if ($host = www.tony1966.cc) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    if ($host = tony1966.cc) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

}

其中 location / 根目錄路徑是轉發到前一篇的 127.0.0.1:8080 來執行 hello.py; 藍色字的部分為新增的 URL 子路徑, 會將對 /hello2/ 子路徑的請求轉發到 127.0.0.1:8081 來執行 hello2.py. 

然後用 nginx -t 檢查所有站台設定檔 : 

tony1966@LX2438:~$ sudo nginx -t   
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

沒有問題就重啟 Nginx 伺服器 : 

tony1966@LX2438:~$ sudo systemctl reload nginx   

這樣便完成所有設定, 用瀏覽器連線 https://tony1966.cc 與 https://tony1966.cc/hello2 即可分別執行不同的應用程式 hello.py 與 hello2.py :





沒有留言 :