2025年7月5日 星期六

Mapleboard MP510-50 測試 (二十五) : 用預設站台執行應用程式

我在 2023 年時就已經在 Mapleboard 上安裝了 Nginx 伺服器, 去年 (2024) 初為了測試 LINE bot 申請了域名, 今年過年長假時也將 ufw 防火牆設定好, 但是好事多磨, 接下來因忙於其他項目而沒有時間繼續 Mapleboard 的架站計畫. 上週因 Ubuntu 軟體更新造成主機無法連線, 解決後想說那就趁這機會將 Python 架站環境搞定吧. 

本系列之前的文章參考 :



1. Nginx 網站架構 : 

之前安裝的 Nginx 是一個 web (HTTP) 伺服器, 其用途是作為一個反向代理 (Reverse proxy) 伺服器, 負責處理 HTTP 請求, 靜態檔案服務, 與負載平衡等功能, 所謂反向代理是指代表內部的伺服器群去接收外部使用者 "進來" 的請求 (例如 Nginx 伺服器); 這是相對於幫助內部的主機 "出去" 訪問外部網站的正向代理伺服器而言 (例如翻牆用的 VPN 伺服器). 簡言之, Nginx 在伺服主機中的角色是面對客戶端 (像是餐廳外場接待員), 接收並回應所有來自網路的訪問請求 (HTTP/HTTPS). , 它與客戶端, 以及應用伺服器之關係如下圖所示 (上方關係圖是使用 mermaid 繪製的) :




如果 Nginx 收到的是一個靜態檔案的請求, 例如 CSS, JavaScript, 圖片等 (好比是客人要看菜單), 它就直接從檔案系統中取出並回應給客戶端; 但如果請求的是需要 Python 處理的動態內容, 它就會將此 HHTP 請求轉交給後端的應用伺服器 (Gunicorn 或 uWSGI 等, 可看成是廚房的領班), 再由應用伺服器將這個 HTTP 請求的內容翻譯成一個符合 WSGI 規範的 Python 物件 (通常是一個 environ 字典) 並傳給一個 Python 應用程式 (Flask, Django 等, 可想成是廚師團隊) 進行業務邏輯處理 (查詢資料庫, 計算, 渲染 HTML 模板等, 可比喻為廚師按照點餐進行料理), 完成後回傳一個符合 WSGI 規範的回應給應用伺服器, 它再把這個回應翻譯成一個標準的 HTTP 回應傳回給 Nginx. 參考 :


總之, Nginx 的設計目標是專注於高效地處理網路 I/O 而不是執行應用程式邏輯, 應用伺服器才是負責應用程式的執行, 這種分離的架構設計讓每個組件都能專注於自己擅長的功能, 兩者的主要功能摘要說明如下表 : 


Web 伺服器 (Nginx / Apache) WSGI 應用伺服器 (Gunicorn / uWSGI)
處理 HTTP 請求與回應(快速、非同步) 執行應用程式邏輯(根據 WSGI 規範)
提供靜態檔案(CSS、JS、圖片) 產生動態內容(HTML、API 回應)
負責反向代理與負載平衡 管理應用程式生命週期,用多個 worker 處理請求
處理 SSL / HTTPS 加密連線 不處理 SSL,僅專注程式執行
不執行 Python 程式 提供 Python 應用執行環境
不負責資料庫連線 應用程式可直接連接資料庫


常用的 WSGI 應用伺服器有 uWSGI 與 Gunicorn, 差異比較如下表 :


項目 Gunicorn uWSGI
易用性 ✅ 簡單易用,CLI 直觀,適合新手 ❌ 複雜且選項繁多,新手容易混淆
設定方式 ✅ 用 `gunicorn app:app` 即可啟動 ❌ 支援多種設定方式 (CLI/INI/XML) 但難以管理
官方文件 ✅ 清晰、簡潔 ⚠️ 文件龐大且結構不友善
效能 ✅ 優秀效能,滿足中型應用 ✅ 效能極高,適合大型部署
功能擴充性 ⚠️ 功能較少但足夠簡潔 ✅ 功能強大,如 mount、FastCGI 等
ASGI 支援 ❌ 僅支援 WSGI,不支援非同步 ❌ 也僅支援 WSGI
啟動速度 ✅ 啟動快,立即部署 ⚠️ 啟動略慢,初始化較多
穩定與成熟度 ✅ 穩定成熟,廣泛用於生產環境 ✅ 穩定度高,大型系統常用
社群活躍度 ✅ 活躍,更新穩定 ⚠️ 維護相對緩慢
內建 HTTP Server ✅ 有 ✅ 有
Emperor 模式 ❌ 不支援 ✅ 支援,可管理多應用
推薦使用情境 Flask / Django 初學者、中小型部署 需複雜管理與高併發的大型系統


整體而言, uWSGI 雖然功能完整, 可配置度高且效能極佳, 但設定太複雜新手容易踩雷; 而 Gunicorn 則簡潔直觀且設定容易, 其開發社群活躍度也非常高, 適合大多數中小型系統佈署網頁應用程式專案, 也是 Flask 與 Django 推薦的 WSGI 伺服器. 

注意, 一個伺服器主機可以同時安裝 uWSGI 與 Gunicorn, 兩者在套件依賴上不會相衝突, 只要在 Nginx 中用 location 設定分流監聽不同的 port 且用 systemd 指定要啟動哪一個應用伺服器即可. 


2. 安裝 WSGI 應用伺服器 Gunicorn 

Gunicorn 可以直接用 pip3 安裝 : 

pip3 install gunicorn

tony1966@LX2438:~$ pip3 install gunicorn   
Defaulting to user installation because normal site-packages is not writeable
Collecting gunicorn
  Downloading gunicorn-23.0.0-py3-none-any.whl (85 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 85.0/85.0 KB 494.1 kB/s eta 0:00:00
Requirement already satisfied: packaging in ./.local/lib/python3.10/site-packages (from gunicorn) (25.0)
Installing collected packages: gunicorn
Successfully installed gunicorn-23.0.0


3. 建立 Flask 網頁應用程式 : 

用 pip3 install Flask 安裝 Flask 套件, 我之前已安裝, 故加上 -U 升到最新版 :

pip3 install Flask -U   

tony1966@LX2438:~$ pip3 install Flask -U  
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: Flask in ./.local/lib/python3.10/site-packages (3.1.0)
Collecting Flask
  Downloading flask-3.1.1-py3-none-any.whl (103 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 103.3/103.3 KB 655.3 kB/s eta 0:00:00
Requirement already satisfied: werkzeug>=3.1.0 in ./.local/lib/python3.10/site-packages (from Flask) (3.1.3)
Requirement already satisfied: jinja2>=3.1.2 in ./.local/lib/python3.10/site-packages (from Flask) (3.1.5)
Requirement already satisfied: itsdangerous>=2.2.0 in ./.local/lib/python3.10/site-packages (from Flask) (2.2.0)
Requirement already satisfied: markupsafe>=2.1.1 in ./.local/lib/python3.10/site-packages (from Flask) (3.0.2)
Requirement already satisfied: click>=8.1.3 in ./.local/lib/python3.10/site-packages (from Flask) (8.1.8)
Requirement already satisfied: blinker>=1.9.0 in ./.local/lib/python3.10/site-packages (from Flask) (1.9.0)
Installing collected packages: Flask
  Attempting uninstall: Flask
    Found existing installation: Flask 3.1.0
    Uninstalling Flask-3.1.0:
      Successfully uninstalled Flask-3.1.0
Successfully installed Flask-3.1.1

接著寫一個 Flask 網頁程式來測試, 先建立一個 flask_apps 資料夾, 然後在底下用 nano 編輯一個 hello.py 程式 : 

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

程式內容如下 :

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

@app.route("/")
def hello():
    return "<h1>Hello World!</h1>"

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

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

如果直接用 python3 執行此程式, 則 __name__ 的值會是 __main__, 因此會呼叫 app.run(), 這會來啟動 Flask 自帶的 Werkzeug 開發伺服器, 此處是會監聽 localhost 的 8080 埠 (若不指定預設監聽 5000 埠). 按 Ctrl+O 存檔後再按 Ctrl+X 跳出 nano, 然後用 python3 指令執行 hello.py, 這時就會呼叫 app.run() :

tony1966@LX2438:~/flask_apps$ python3 hello.py   
 * Serving Flask app 'hello'
 * 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:8080  
Press CTRL+C to quit
 * Restarting with watchdog (inotify)
 * Debugger is active!
 * Debugger PIN: 650-986-211

開啟瀏覽器於網址列輸入 127.0.0.1:8080 或 localhost:8080 就會看到 hello.py 渲染出來的網頁:




4. 啟動 Gunicorn 應用伺服器 : 

上面的 Flask 網頁應用程式是利用 python3 指令直接執行, 這會呼叫 app.run() 啟動 Flask 內建的開發伺服器 Werkzeug 來監聽指定的埠口, 但此開發伺服器為單執行續效能不佳, 只能用在開發測試. 在生產環境要啟動 Gunicorn 應用伺服器來執行 Flask 應用程式. 

啟動 Gunicorn 伺服器的語法如下 :

gunicorn -w 程序數 -b IP:埠號 應用程式主檔名:應用程式物件名稱   或 

gunicorn --worker 程序數 --bind IP:埠號 應用程式主檔名:應用程式物件名稱

參數說明如下 :
  • -w 或 --worker 參數 :
    用來設定 Gunicorn 的用來執行 app 的程序數 (worker process),  worker 是 process (不是 thread), 每個 worker 是一個獨立的 Python process, 它會入一次載整個 app 與套件, Gunicorn 使用多程序架構 (multi-process), 使用多個 worker 可充分利用 CPU 資源以同時處理多個請求, 提升併發處理能力.  但是 worker 數越多也會耗掉更多記憶體資源 (通常 Flask 應用程式的一個 worker 大約消耗 20 ~ 100 MB 記憶體), 若記憶體充足, 為了達到 CPU 使用率最大化, 通常會依照下列計算式來設定 worker 數 : 

    workers = 2 × 核心數 + 1

    大多數網站是 I/O bound, 處理請求的大部分時間是花在等待呼叫 API 與等待檔案或資料庫存取, 多開 worker 能有效填補空閒 CPU 資源避免 idle, 而且 Gunicorn 預設是 synchronous (blocking I/O), 所以使用多個 worker 可以平衡處理效率. 但若配置後發現吃掉過多記憶體, 可以改成指定一個 worker 占用一個 CPU 核心.
  • -b 或 --bind 參數 :
    指定 Gunicorn 監聽的 IP 位址和埠號, 考量安全性建議讓 Gunicorn 監聽 127.0.0.1, 不要用 0.0.0.0 監聽主機所有網卡, 避免客戶端直接輸入主機 IP 而繞過 Nginx 控管. 生產環境正確的作法應該是讓 Flask 程式監聽 1024 以上的埠 (例如 8080), 然後設定 Nginx 作為反向代理讓它監聽 80 埠, 並將對 80 埠的請求透過應用伺服器轉發給 Flask 應用程式監聽的 8080 埠. 注意, 基於安全考量, 為避免未經授權的使用者開啟低埠口服務, 在 Linux 系統中只允許 root 或特權使用者可以監聽 1024 以下的埠號 (包括 80 埠). 
首先來設定 workers 數, 我的 Mepleboard 是 ARM Cortex-A55 架構, CPU 是 4 核心, 這可用 nproc 指令查詢 :

tony1966@LX2438:~$ nproc   
4

或者可用 lscpu 指令從 CPU 的完整資訊中找到核心數 : 

tony1966@LX2438:~$ lscpu  
架構:                   aarch64
  CPU 作業模式:         32-bit, 64-bit
  Byte Order:            Little Endian
CPU(s):                  4
  On-line CPU(s) list:   0-3
供應商識別號:           ARM
  Model name:            Cortex-A55
    型號:               0
    每核心執行緒數:     1
    Core(s) per cluster: 4
    Socket(s):           -
    Cluster(s):          1
    製程:               r1p0
    CPU max MHz:         2100.0000
    CPU min MHz:         1000.0000
    BogoMIPS:            48.00
    Flags:               fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
Vulnerabilities:         
  Itlb multihit:         Not affected
  L1tf:                  Not affected
  Mds:                   Not affected
  Meltdown:              Not affected
  Mmio stale data:       Not affected
  Retbleed:              Not affected
  Spec store bypass:     Not affected
  Spectre v1:            Mitigation; __user pointer sanitization
  Spectre v2:            Not affected
  Srbds:                 Not affected
  Tsx async abort:       Not affected

這塊 Mapleboard 配置 4GB DRAM, 但為了避免耗用過多記憶體, 我將 worker 數設為核心數 4, 以執行上面的 Flask 應用程式 hello.py 為例, 首先必須切換到應用程式 hello.py 所在的目錄 flask_apps 下 (否則會出現 ModuleNotFoundError 或 ImportError 錯誤), 然後用下列指令啟動 Gunicorn 伺服器來執行此 app : 

tony1966@LX2438:~$ cd flask_apps  
tony1966@LX2438:~/flask_appsgunicorn -w 4 -b 127.0.0.1:8080 hello:app   
[2025-07-05 00:47:19 +0800] [170529] [INFO] Starting gunicorn 23.0.0
[2025-07-05 00:47:19 +0800] [170529] [INFO] Listening at: http://127.0.0.1:8080 (170529)
[2025-07-05 00:47:19 +0800] [170529] [INFO] Using worker: sync
[2025-07-05 00:47:19 +0800] [170530] [INFO] Booting worker with pid: 170530
[2025-07-05 00:47:19 +0800] [170531] [INFO] Booting worker with pid: 170531
[2025-07-05 00:47:19 +0800] [170532] [INFO] Booting worker with pid: 170532
[2025-07-05 00:47:19 +0800] [170534] [INFO] Booting worker with pid: 170534

可見已經啟動了 4 個 Process, 其 PID 為 170530~170534. 也可以將上面的啟動參數放在一個設定檔, 例如 gunicorn_config.py 裡面, 然後使用 -c 參數指定於啟動時從檔案讀取設定值 :

# gunicorn_config.py
bind='127.0.0.1:8080'  # 綁定的地址和端口
workers=4            # worker 進程數

然後用下列指令啟動 Gunicorn 伺服器 : 

gunicorn -c gunicorn_config.py hello:app   

Gunicorn 啟動後會占住終端機, 如果要停止 Gunicorn 伺服器只要按 Ctrl+C 即可, Gunicorn 會將 worker process 全部終止, 並釋放終端機控制權. 如果不想讓 Gunicorn 佔據終端機, 可以在指令後面加上 & 把 gunicorn 指令丟到背景執行 :

gunicorn -w 4 -b 127.0.0.1:8080 hello:app &   

執行後終端機會回到提示符號, 可以繼續使用, 不會被 gunicorn 指令佔據. 

雖然啟動 Gunicorn 伺服器後就可以看到網頁了, 但是 Gunicorn 伺服器的角色是 WSGI 應用伺服器而非 Web 伺服器, 處理 HTTP 請求與回應乃是 Nginx 伺服器的工作, Nginx 會負責將非靜態網頁的 HTTP 請求轉發給 Gunicorn 伺服器來處理, 但這需要在 Nginx 的設定檔中設定反向代理.


5. 設定 Nginx 伺服器的反向代理 : 

Nginx 伺服器可同時運行多個虛擬主機站台 (web sites), 它有兩個管理虛擬主機 (server block) 配置的重要目錄 : 
  • /etc/nginx/sites-available/ : 
    此目錄用於存放所有的虛擬主機設定檔, 每個設定檔對應一個站台, 修改這個目錄下的虛擬主機設定檔並不會立即影響 Nginx 的運行, 因為這些設定檔不會自動加載, 只有在它們被鏈接到 /etc/nginx/sites-enabled/ 目錄時才會真正生效. 
  • /etc/nginx/sites-enabled/ :
    此目錄用於存放啟用的虛擬主機設定檔的符號鏈接 (symlink), 當要在 Nginx 中啟用某個網站時, 只要用 ln 指令將其設定檔從 /etc/nginx/sites-available/ 目錄鏈接到此目錄即可. 
之前安裝完 Nginx 後就已經有一個預設站台 default (是的, 它沒有副檔名), 去年在向 Let's Encrypt 申請 SSL/TLS 憑證時, Certbot 修改了 default 的設定, 添加監聽安全連線的 443 埠以及將 http 導向 https 的設定, 以下為了測試 Nginx -> Gunicorn > Flask app 的運作架構是否正常, 先不建立新的站台, 直接去修改 default 站台的設定, 其原始檔案位於 /etc/nginx/sites-enabled/ 下, 用 ls -ls 指令可知 /etc/nginx/sites-enabled/default 只是一個指向 /etc/nginx/sites-available/default 的符號鏈接而已, 並非真正的檔案 :

tony1966@LX2438:~$ ls -ls  /etc/nginx/sites-enabled/default     
0 lrwxrwxrwx 1 root root 34 Mar  2  2023 /etc/nginx/sites-enabled/default -> /etc/nginx/sites-available/default

所以修改 default 設定檔須編輯 /etc/nginx/sites-available/ 下的那個 :

tony1966@LX2438:~$ sudo nano /etc/nginx/sites-available/default   

找到 server_name 是域名的那個區塊 (有 'managed by Certbot' 者) :

    server_name tony1966.cc; # managed by Certbot


        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
        }

在 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 / {
    # Flask test site:hello  
    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;
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                #try_files $uri $uri/ =404;  
        }




這裡面最重要的就是 proxy_pass http://127.0.0.1:8080; 這一行, 它用來串接 Gunicorn 與 Flask 應用程式, 指示 Nginx 把對 80/443 埠的 HTTP/HTTPS 請求導向至 http://127.0.0.1:8080, 而這正是 Flask 應用程式所監聽的端口. 另外還要把 try_files $uri $uri/ =404; 這一行註解掉, 否則請求 https://tony1966.cc/hello/Tony 時會得到 404, 因為 try_files 會以為 /hello/Tony 是要找靜態檔案, 結果找不到就直接回 404, 註解掉 try_files 這一行後, 任何從 / 開始的路徑 (例如 /hello/Tony) 都會被代理給 Gunicorn ➜ Flask 處理了. 

按 Ctrl+O 存檔後按 Ctrl+X 跳出 nano, 用下列指令檢查站台設定檔是否正確 (是否存在語法錯誤或其他問題) :  

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/hello/Tony : 




6. 將 Gunicorn 設為系統服務 (systemd) : 

上面啟動 Gunicorn 伺服器來執行 Flask 應用程式是在終端機中下 gunicorn 指令, 這種方式 Gunicorn 會佔據該終端機, 雖然可以將指令丟到背景執行解決此問題, 但系統當機重啟後它不會自動啟動, 必須手動重新執行 gunicorn 指令, 這樣就會影響到應用程式的持續服務, 最好的辦法是將此 Gunicorn+app 做成系統服務, 這樣不僅系統當機重啟開機會自動啟動應用程式, 程式若當掉也會自動重啟, 而且不會佔用終端機, Log 也可用 journalctl 集中管理. 

首先用 nano 建立一個新的 systemd service 檔案, 此例可取名為 hello.service : 

tony1966@LX2438:~$ sudo nano /etc/systemd/system/hello.service   
[sudo] tony1966 的密碼: 

輸入如下內容 : 

[Unit]
Description=Gunicorn instance to serve Flask hello app
After=network.target

[Service]
User=tony1966
Group=www-data
WorkingDirectory=/home/tony1966/flask_apps 
ExecStart=/home/tony1966/.local/bin/gunicorn --workers 4 --bind 127.0.0.1:8080 hello:app 
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

注意, WorkingDirectiory 表示應用程式所在的目錄, 若指定錯誤將無法順利啟動此服務. 另外, 由於我的 Gunicorn 安裝於使用者目錄下  (/home/tony1966/.local/bin/gunicorn), systemd 無法存取 ~/.local/bin 的執行檔, 為了能正常啟動 hello 服務, 此處必須使用 Gunicorn 安裝位置的絕對路徑 /home/tony1966/.local/bin/gunicorn. 

按 Ctrl+O 存檔後按 Ctrl+X 跳出 nano, 然後回到上面以手動方式用 gunicorn -w 4 -b 127.0.0.1:8080 hello:app 指令啟動的 Gunicorn 伺服器的終端機視窗, 按 Ctrl+C 停止 Gunicon 伺服器, 以下列指令確認是否所有 worker process 都已結束 (沒有回應任何東西即已全部結束) :

tony1966@LX2438:~$ ps aux | grep gunicorn | grep -v grep   (或 pgrep -f gunicorn)
tony1966@LX2438:~$  

這個動作很重要, 因為如果不先停掉舊的 gunicorn process, 新的 systemd 服務會因為 port (此處為 127.0.0.1:8080) 已經被佔用而導致啟動失敗或產生衝突, 有時 systemd 會因偵測到殘留的 gunicorn process 造成服務啟動異常或資源被鎖定. 

如果還有殘留的 Gunicorn 程序, 可用下列指令強制刪除 :

tony1966@LX2438:~$ pkill -f gunicorn   

終止手動執行的 Gunicorn 程序後, 即可輸入下列指令重新讀取 systemd 配置 :

sudo systemctl daemon-reexec    
sudo systemctl daemon-reload   

tony1966@LX2438:~$ sudo systemctl daemon-reexec   
[sudo] tony1966 的密碼: 
tony1966@LX2438:~$ sudo systemctl daemon-reload   
[sudo] tony1966 的密碼: 

然後用下列指令啟動 hello 服務於開機時自動執行 :

sudo systemctl enable <服務名稱> 

tony1966@LX2438:~$ sudo systemctl enable hello   
Created symlink /etc/systemd/system/multi-user.target.wants/hello.service → /etc/systemd/system/hello.service.

然後用下列指令檢查此系統服務知狀態 : 

sudo systemctl status <服務名稱>

tony1966@LX2438:~$ sudo systemctl status hello   
● hello.service - Gunicorn instance to serve Flask hello app
     Loaded: loaded (/etc/systemd/system/hello.service; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2025-07-06 14:55:45 CST; 59ms ago
   Main PID: 223187 (gunicorn)
      Tasks: 1 (limit: 4213)
     Memory: 2.5M
        CPU: 52ms
     CGroup: /system.slice/hello.service
             └─223187 /usr/bin/python3 /home/tony1966/.local/bin/gunicorn --workers 4 --bind 127.0.0.1:8080 hello:app

Jul 06 14:55:45 LX2438 systemd[1]: Started Gunicorn instance to serve Flask hello app.

出現 Active: active (running) 表示系統服務已正常啟動 hello 這個服務了, 這時拜訪 https://tony1966.cc 與 https://tony1966.cc/Tony 都可以正常顯示與上面手動啟動 Gunicorn 時相同的結果. 

注意, 如果應用程式, Gunicorn 或 systemd 的設定有修改, 以及 Flask 等框架有升版等, 必須重新載入此系統服務才會看到變更的效果, 因為 Gunicorn 預設不會自動偵測程式碼與設定是否有變更; 而且長時間執行後, Gunicorn 可能累積記憶體用量, 重啟可釋放資源. 

重啟服務指令如下 :

sudo systemctl restart  <服務名稱>  

如果應用程式在開發中, 重啟時可以加上 --reload 參數, 這樣 Gunicorn 就會自動監看 .py 檔是否有異動, 有的話就會自動重新載入應用程式而毋須重啟服務. 但加上 --reload 參數會影響效能, 正式部署上線時不要用 --reload 參數. 另外, 修改 HTML template 或 static file 時可以不用重啟服務, 但為保險起見建議重啟較好. 

常用系統服務指令摘要如下表 : 


 系統服務指令  說明
 sudo systemctl start <service_name>  啟動 <service_name> 服務
 sudo systemctl stop <service_name>  停止 <service_name> 服務
 sudo systemctl restart <service_name>  重新啟動 <service_name> 服務(程式變更後使用)
 sudo systemctl status <service_name>  查看 <service_name> 服務目前的執行狀態
 sudo systemctl enable <service_name>  設定 <service_name> 服務開機自動啟動
 sudo systemctl disable <service_name>  取消 <service_name> 服務開機自動啟動
 sudo journalctl -u <service_name>  檢視 <service_name> 服務的執行紀錄(log)


OK, 透過與 ChatGPT, Claud, 與 Grok 交談詢問, 來回測試了五天終於把 Nginx + Gunicorn + Flask 的運作流程與原裡搞清楚了, 真是累死我了, 但學到非常多 Linux 網站主機知識, 收獲滿滿. 

沒有留言 :