有個實驗性質 Python 小網站想丟上 Linux 執行,手工下指令驗證可行後,延續過去在 Linux 跑 ASP.NET Core 網站的經驗,下一步就想把它包成 Docker 容器,用起來才方便。

ASP.NET Core 要包 Docker 我不陌生,但 Python 網站是頭一回,什麼都新鮮。但有相關經驗還是有差,小細節跟差異處摸索一下,倒也很快搞定。

先說我這次的專案結構,程式是用 Flask 跑網站,有掛 static 目錄放 HTML、CSS、JavaScript、圖檔等靜態檔案,API 金鑰、URL 等設定則存在 .env 檔。(記得用 .gitignore 排除,以免推上 Github 機密全都露)

琢磨出來的 Dockerfile 打包檔長這樣:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY *.py .
COPY static ./static
CMD ["python", "web.py"]

首先選擇基底 Image,一般有 python:<version>python:<version>-slimpython:<version>-alpine 三種選擇,體積從大到小,愈精簡的版本內建程式庫愈少,可能需要額外安裝設定。一般若無特殊考量可用 -slim 版。以我這次的案例比較簡單,沒用到太高深複雜的功能,程式在三種版本都能正常運作,編譯成的 Image 大小分別為 1.23GB、304MB 及 234MB,其中應用程式跟套件大約佔 220MB ~ 180MB (不同版本安裝大小不同,有興趣可用 docker history <image-id> 查詢)。依據查到的文章,若程式或套件用到 glibc (如影像處理、OpenCV),用 alpine 很容易踩到雷,建議用 slim 或標準版。

Dockerfile 的下一步是切換將容器的工作目錄設成 /app (之後目標路徑可用 . 代表 /app),複製 requirements.txt 執行 pip install --no-cache-dir -r requirements.txt 在容器裡安裝套件,容器都是單次安裝快取效用不大,加入 --no-cache-dir 選項可減少 Image 大小。

由於我的專案目錄不是所有檔案都要部署到容器裡,故不能用 COPY . . 一次搞定,改為分別複製 *.py 及 static 目錄。

最後指定執行 python web.py 啟動程式。

呼叫 docker build -t <image-nam> . 便能編譯好容器 Image。

在 Linux 上運行 Docker,我偏愛的 Nginx Certbot Docker 懶人安裝法(參考:HTTPS Nginx Docker 之懶人安裝法) 搭配 Docker Composer (參考:ASP.NET Core Docker 筆記 2 - 組合容器建構系統)。docker-compose.yaml 定義如下,沒什麼大學問,主要就是將儲存 API Key 的 .env 檔跟 Log 檔用 volumes 指向容器外的實體檔案。

version: '3.8'

services:
  my-container:
    image: <image-name>
    container_name: <container-name>
    ports:
      - "5000:5000"
    volumes:
      - ./.env:/app/.env
      - ./app.log:/app/app.log

Python 網站程式搬進容器可正常啟動,但我無法透過 curl http://localhost:5000 連上網站。研究了一下,我原本 Flask 程式寫法如下:

from flask import Flask
app = Flask(__name__)
# ... 略 ...
if __name__ == '__main__':
    app.run(debug=True, port=5000)

Flask 會傾聽 127.0.0.1 的 5000 Port,在本機執行沒問題,但搬進 Docker 後與外界溝通需透過 172.19.0.2 等 Docker 內部 IP,故程式要改為:

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True, port=5000)

Flask 會同時繫結到 127.0.0.1 及 172.19.0.2 的 5000 Port,如此就能順利運作了。

就醬,我也成功學會用 Docker 跑 Python 網站了。

【後記】實驗網站沒想過承受負載的問題,感謝讀者 Marcus Peck 補充,若要正式提供線上服務,建議搭配 gunicorn 使用,以支援多 Worker 並行處理。

簡單來說,Flask 內部是用 Werkzeug 當作處理 HTTP 請求與傳送回應的網站框架(WSGI Server),不太有能力在短時間消化大量請求,若用來上線營運流量偏大便可能被搞掛,故需要換成更成熟穩定的 WSGI Server,而 gunicorn 是常見的選項。

更多細節可以參考這篇:Flask想上線? 你還需要一些酷東西 by Byte and Ink

不管什麼領域,一旦頭洗下去就要開始面對源源不絕的魔鬼細節,等真要上線商轉時再來仔細研究好了。

This blog post describes the process of containerizing a Flask-based Python website using Docker. I share their experience of detailing the Dockerfile setup, base image choices, and resolving issues related to Flask’s default host binding. Adjustments to the Flask app and Docker configurations are explained to ensure smooth operation within the container environment.


Comments

Be the first to post a comment

Post a comment