2025年10月10日 星期五

在 render.com 佈署 Python 網頁應用程式 (二)

前一篇測試中, 我們已順利在 render.com 雲端主機上佈署了一個簡單的 Flask app, 本篇則是要將之前於 Mapleboard 上佈署的仿 Google Cloud Functions (GCF) 的 serverless 函式服務專案移植到 render.com 上, 做法參考 :



1. 系統架構摘要 : 

此專案是由主程式 serverless.py, SQLite 資料庫 serverless.db, 環境變數檔 .env, 以及一個子目錄 functions 組成, functions 裡面包含 8 個系統程式與一個 __init__.py (為了符合 Python 套件要求之空檔案), 架構如下 :

serverless
├── functions
│   ├── add_function.py
│   ├── clear_stats.py
│   ├── delete_function.py
│   ├── edit_function.py
│   ├── __init__.py
│   ├── list_functions.py
│   ├── save_function.py
│   ├── show_stats.py
│   └── update_function.py
├── .env 
├── serverless.db (初始化時自動建立)
└── serverless.py

管理者線上建立的函式模組 (HTTP API) 程式也是放在 functions 子目錄下, 函式模組必須定義一個 main() 函式, 傳入 request 物件與關鍵字參數 kwargs : 

# func_module.py
def main(request, **kwargs):
    config=kwargs.get('config', {})  # 預設為空 dict
    result='do something'
    return result

注意, **kwargs 參數是必須的, 主要用來傳遞從主程式傳來的 API key/token 參數, 即使此函式用不到也必須接收. 


2. 建立 GitHub 儲存庫 : 

本篇使用的 severless 版本是 v4, 原始碼放在 GitHub :


如果要佈署在 Render 上運行, 必須另外建立一個獨立的 repo 來存放應用程式, 依照前一篇測試的程序建立名為 serverless 的 repo :




在 GitHub 手動建立資料夾的方法是透過建立它底下的檔案, 輸入資料夾名稱 functions 後加一個 / 就會自動出現下一個輸入框, 這時輸入資料夾底下的一個檔案名稱 (例如空檔案 __init__.py), 按 Commit changes 即可 :




這樣 serverless 這個 repo 下就產生了一個 functions 資料夾與其下的空檔案 __init__.py, 其餘系統檔案就可以利用拖曳方式批次上傳, 在 functions 下按右方的 Add file 鈕點選 Upload files :




將本機 serverless/functions 下的 8 個系統檔案拖曳到上傳區後按 Commit changes :





然後跳回 repo 根目錄, 新增相依套件檔 requirements.txt, 基本上需要 gunicorn, flask 與 python-dotenv 這三個外部套件, 另外又添加 requests, lxml, 與 beautifulsoup4 這三個爬蟲套件, 按 Commit changes 存檔 :



如果以後需要用到其他套件, 只要修改 requirements.txt 重新佈署即可. 

最後建立設定檔 Procfile (注意沒有副檔名), 用來告訴平台這個服務要怎麼啟動, 內容如下 :

web: gunicorn serverless:app




其中 web 是告訴 Render 這是一個 HTTP Web Service, 要用 gunicorn (一種 WSGI HTTP 伺服器) 來啟動應用程式 serverless.py, 而 app 則是 Flask 物件的名稱, 這必須與 serverless.py 裡的 app=Flask(__name__) 一致才行. 所以 serverless:app 意思是載入 serverless.py 檔案和裡面的 app 物件作為服務的進入口. 




這樣就完成應用程式佈署前的準備了, repo 網址如下 :

 

3. 將 serverless 佈署到 Render : 

在佈署 serverless app 專案之前, 先用記事本編輯一個環境變數檔 .env 用來儲存平台管理密碼, 會用到的 OpenAI 與 Gemini 等 LLM 的 API key, 以及 Telegram 與 LINE Bot 的存取權杖等資訊 :




這個 .env 檔應存放在另外的資料夾, 切勿放在 repo 專案資料夾下, 以免有時改用 Git 上傳專案到 GitHub 時也把 .env 一起上傳會有洩密風險 (此處我使用網頁上傳無此風險). 

接下來要再次授權 Render 來存取 serverless 這個 repo, 因為在前一篇測試中我們基於安全考量, 選擇僅授權 Render 存取我們選定的 repo, 所以每次有新的服務要佈署時就要做一次授權動作, 在 Render 的儀錶板網頁中按右上角的頭像, 點選 "Account settings" :





將設定頁面往下拉到 "Git Deployment Credentials" 這一項, 按 GitHub 帳號右邊的三個小點按鈕, 點選 "Configure on GitHub" :




這時會彈出 GitHub 登入頁, 輸入 GitHub 密碼 :




然後在 "Only select repositories" 項目下選取這次要授權的儲存庫 serverless :




選定後按 Save 鈕 : 




這樣便完成授權了. 

回到 Dashboard 頁面, 點選專案 MyProject (免費帳戶只能有一個專案) : 




按 + new service 鈕點選 Web service :





在 Git Provider 中點選 serverless 這個已授權 repo :




在佈署設定頁面中, Name 欄位預設會用 repo 名稱 (可自行修改), 主機地點可選擇離台灣較近的 Singapore : 




Start command 預設是 gunicorn app:app, 須與上面 Procfile 一致改為 gunicorn serverless:app, 實體類型 (即計費方案) 選 Hobby 免費方案 : 




點開 Advanced 項目, 按 +Add Secrete File 鈕 :




在彈出視窗的 File Name 欄中填入 .env, 然後將內容貼到底下, 按 Save 鈕存檔 :




Auto-Deploy 這一項要從 On-commit 改為 off, 因為預設只要修改設定或程式碼就會自動重新佈署, 這會清掉服務已產生的內容 :




最後按左下角的 "Deploy Web Service" 鈕開始佈署專案 : 




這時頁面上方會顯示服務網址 (HTTPS 可做 Web hook 用) : 

https://serverless-fdof.onrender.com

右下角會顯示佈署進度 : 




出現 "Your service is live" 表示佈署完成, 服務已上線運行 : 




這時拜訪 web app 網址就會出現此服務的首頁了 : 




點 "登入系統" 連結會出現登入頁面, 輸入密碼按登入 :




按 "查看函式列表" 連結 :




按 "新增函式" 連結 :




首先來新增一個 hello 函式, 程式碼如下 : 

# hello.py
def main(request, **kwargs):
    # 先從請求字串擷取 name 參數 (?name=)
    name=request.args.get('name')
    if not name:   # 請求字串沒有 name 參數
        # 嘗試從 RESTful 子路徑 (例如 /function/hello/Tony) 中擷取 name 參數 
        subpath=request.view_args.get('subpath', '')   # 取得 Flask 傳入的 subpath 參數
        parts=subpath.strip('/').split('/')   # 拆解成串列例如 ['Tony']
        if parts:
            name=parts[0]   # 取第一段作為 name (例如 'Tony')
    if not name:
        name='World'
    return f'Hello {name}!'

此函式模組會嘗試從 URL 字串中找尋有無傳遞 name 參數, 例如 :

~/function/hello?name=Tony

或從 RESTful 子路徑中找尋子路徑, 例如 :

~/function/hello/Tony

然後回應 "Hello Tony!", 否則回應 "Hello World!" :






測試結果如下 : 




由於使用 Render 免費方案架設的服務若超過 15 分鐘沒有任何請求, 則服務會進入休眠狀態, 容器資料被保存起來後便被清除, 等到被喚醒時才又重建容器恢復服務. 

如果要讓服務不休眠, 則必須在 15 分鐘內至少對它提出一個請求, 最常用的方法便是在其他主機中設定 crontab, 我利用 Pi 3A+ 每 10 分鐘用 curl 對 ~/function/hello 提出請求 :

pi@raspberrypi:~ $ crontab -e   

輸入如下設定 :

*/10 * * * * curl -s https://serverless-fdof.onrender.com/function/hello > /dev/null

半小時後檢視 Dashboard 右下角的系統日誌可知 crontab 確實有請求 (curl) :




2025-10-13 補充 :

經過雙十國慶連假三天測試下來, 發現 render.com 免費帳戶還蠻好用的, 只要用一台樹莓派的 crontab 在 15 分鐘內對網站做一次 HTTP 請求就可以避免服務進入休眠, 我在 serverless 平台上建了一個 hello 函式, 每 6 分鐘對 hello 請求一次, 三天下來果然都沒進入休眠持續運行 : 



沒有留言 :