在機器學習專案開發過程中,建構一致的開發環境及簡化佈署流程至關重要。本文將探討如何結合 Docker 與 Batect,打造高效且安全的機器學習開發及佈署流程。首先,利用 Docker 容器化應用程式及其依賴項,確保開發環境的一致性。接著,使用 Batect 簡化 Docker 操作,定義各種任務,例如模型訓練、API 測試等,並透過本地伺服器搭建及 API 測試,縮短開發反饋迴圈。此外,文章也涵蓋如何使用 GitHub Actions 建立 CI/CD 管道,自動化執行模型訓練和佈署任務,並說明如何將模型 API 映像釋出到容器登入檔,以及如何運用雲端服務供應商佈署映像。最後,文章強調安全性議題,闡述如何移除不必要的依賴、自動化檢查和更新依賴,以及最佳化 Docker 映像大小,以降低安全風險並提升效率。

建立本地開發環境

接下來,我們將安裝所有作業系統級別和應用程式級別的依賴項,以便在本地開發機器學習模型。這可以透過一個命令來完成:./batect setup。正如之前所承諾的,這裡是我們解釋 Batect 工作原理的地方。

Batect 工作原理

當我們執行 ./batect setup 時,Batect 執行 setup 任務,我們在 batect.yml 中定義了這個任務。setup 任務簡單地定義為在開發容器中執行 ./scripts/setup.sh 指令碼。讓我們看看這個任務在 batect.yml 中是如何定義的:

containers:
  dev:
    build_directory: .
    volumes:
      - local: .
        container: /code
      - type: cache
        name: python-dev-dependencies
        container: /opt/.venv
    build_target: dev

tasks:
  setup:
    description: 安裝 Python 依賴項
    run:
      container: dev
      command: ./scripts/setup.sh

這就是我們執行 Batect 任務的方式(例如,setup)。--output=all 選項顯示任務執行時的日誌,提供視覺反饋,這對於長時間執行的任務(如依賴項安裝和模型訓練)尤其有用。

容器定義

container 區塊定義了我們的開發映像。這裡,我們指定 Docker 建立時和執行時的組態,例如要掛載的卷或資料夾、Dockerfile 的路徑(即 build_directory)以及多階段 Dockerfile 的建立目標。一次 Batect 建立這個開發映像後,它將被其他任務重用(例如,smoke-test-model-trainingapi-teststart-api-locally)。因此,我們不需要等待漫長的重建。

任務定義

task 區塊定義了我們的 setup 任務,它由兩個簡單的部分組成:要執行的命令和使用的容器。我們還可以指定其他 Docker 執行時組態選項,例如卷和埠。

Dockerfile 組態

讓我們更深入地瞭解第二步,並看看我們如何組態 Dockerfile:

FROM python:3.10-slim-bookworm AS dev
WORKDIR /code

RUN apt-get update && apt-get -y install gcc

RUN pip install poetry
ADD pyproject.toml /code/
RUN poetry config installer.max-workers 10

ARG VENV_PATH
ENV VENV_PATH=$VENV_PATH
ENV PATH="$VENV_PATH/bin:$PATH"

CMD ["bash"]

我們指定了基礎映像,它將形成我們自己映像的基礎層。python:3.10-slim-bookworm 映像大小為 145 MB,而 python:3.10 映像大小為 915 MB。在本章結尾,我們將描述使用小映像的好處。

WORKDIR 指令設定了預設的工作目錄,適用於 Dockerfile 中的任何後續 RUNCMDENTRYPOINTCOPYADD 指令。它也是當我們啟動容器時的預設啟動目錄。你可以將其設定為任何你想要的目錄,只要你保持一致即可。在這個例子中,我們將 /code 設定為工作目錄,並且這裡是我們將在下一步啟動容器時放置程式碼的地方。

我們安裝 gcc(GNU 編譯器集合),以處理某個 Python 函式庫的維護者未發布特定 CPU 指令的輪子(wheel)的情況。使用 gcc,即使 Python 包只有某種 CPU 型別的輪子(例如,Intel 處理器),而維護者忽略了發布另一種 CPU 型別的輪子(例如,M1 處理器),我們仍然可以使用 gcc 來編譯和安裝所需的包。

開發環境設定與Docker應用

在開發過程中,設定一個適合的開發環境是非常重要的。這不僅能夠提高開發效率,還能夠確保開發過程的穩定性和可靠性。在這個章節中,我們將介紹如何使用Docker和Poetry來設定一個開發環境。

安裝和組態Poetry

首先,我們需要安裝和組態Poetry。Poetry是一個Python的包管理工具,能夠幫助我們管理開發環境中的依賴包。以下是安裝和組態Poetry的步驟:

# 安裝Poetry
pip install poetry

# 組態Poetry
poetry config virtualenvs.in-project true
poetry config virtualenvs.path /opt/.venv

建立Dockerfile

接下來,我們需要建立一個Dockerfile來定義我們的開發環境。Dockerfile是一個文字檔案,包含了一系列的指令,用於建立一個Docker映像。以下是建立Dockerfile的步驟:

# 使用官方的Python映像
FROM python:3.9-slim

# 設定工作目錄
WORKDIR /code

# 安裝依賴包
COPY poetry.lock ./
COPY pyproject.toml ./
RUN poetry install

# 匯出埠
EXPOSE 80

# 設定預設命令
CMD ["bash"]

啟動開發環境

現在,我們可以啟動我們的開發環境了。以下是啟動開發環境的步驟:

# 啟動容器(使用batect)
./batect start-dev-container

# 啟動容器(不使用batect)
docker run -it --rm -v $(pwd):/code -p 80:80 loan-default-prediction:dev

Docker和batect的區別

Docker和batect都是用於容器化的工具,但是它們有不同的作用。Docker是一個容器化平臺,能夠幫助我們建立、啟動和管理容器。batect是一個用於自動化Docker容器的工具,能夠幫助我們簡化Docker容器的啟動和管理過程。

內容解密:

在上面的步驟中,我們使用了Docker和Poetry來設定一個開發環境。Docker是一個容器化平臺,能夠幫助我們建立、啟動和管理容器。Poetry是一個Python的包管理工具,能夠幫助我們管理開發環境中的依賴包。透過使用Docker和Poetry,我們可以建立一個穩定和可靠的開發環境,提高開發效率和品質。

圖表翻譯:

以下是建立Dockerfile的流程圖:

  flowchart TD
    A[建立Dockerfile] --> B[安裝依賴包]
    B --> C[匯出埠]
    C --> D[設定預設命令]
    D --> E[建立Docker映像]

這個流程圖展示了建立Dockerfile的步驟,從建立Dockerfile到建立Docker映像。

使用 Docker 和 Batect 進行機器學習開發

在進行機器學習(ML)開發時,保持一致的開發環境和簡化命令列操作是非常重要的。這篇文章將介紹如何使用 Docker 和 Batect 來實作這些目標。

Docker 容器化

Docker 容器化技術允許我們將應用程式和其依賴項封裝到一個容器中,從而實作跨平臺的佈署和執行。使用 Docker,我們可以建立一個包含所有必要依賴項的容器,包括 Python、TensorFlow 等機器學習框架。

# 啟動 Docker 容器
docker run -v $(pwd):/code -p 8080:80 my-ml-image

在這個命令中,-v $(pwd):/code 將當前工作目錄掛載到容器中的 /code 目錄,從而實作宿主機和容器之間的檔案同步。-p 8080:80 將容器中的 80 埠對映到宿主機的 8080 埠,從而允許我們從外部存取容器中的服務。

Batect 自動化

Batect 是一個用於自動化 Docker 容器操作的工具。它允許我們定義一系列的任務(task),每個任務都對應一個特定的 Docker 容器操作。

# 定義 Batect 任務
tasks:
  smoke-test-model-training:
    run: scripts/tests/smoke-test-model-training.sh
  api-test:
    run: scripts/tests/api-test.sh
  train-model:
    run: scripts/train-model.sh

在這個例子中,我們定義了三個 Batect 任務:smoke-test-model-trainingapi-testtrain-model。每個任務都對應一個特定的指令碼檔案,該指令碼檔案包含了具體的操作命令。

執行 Batect 任務

要執行 Batect 任務,我們可以使用以下命令:

# 執行 smoke-test-model-training 任務
./batect smoke-test-model-training

# 執行 api-test 任務
./batect api-test

# 執行 train-model 任務
./batect train-model

這些命令將啟動對應的 Docker 容器,並執行指定的指令碼檔案。由於 Docker 的快取機制,執行這些任務的速度與在容器內執行命令的速度相同。

本地伺服器搭建與API測試

在本章節中,我們將探討如何建立一個本地的Web API伺服器,該伺服器封裝了我們的機器學習模型,並能夠接收預測請求並傳回模型的預測結果。這樣做的好處是可以在本地進行手動測試或自動化測試,避免了「推播以檢視是否有效」的反模式。這種反模式會延長反饋迴圈的時間(從幾秒鐘到幾分鐘),因為我們需要等待CI/CD管道中的測試和佈署執行,以測試程式碼中的一行修改。

以下是如何啟動我們的Web API並與其互動的步驟:

啟動API

首先,使用以下命令啟動API:

$ ./batect start-api

然後,使用以下命令向API傳送請求:

$ scripts/request-local-api.sh

這些命令可以讓我們在本地測試API,無需等待CI/CD管道的執行。

編輯器組態

組態編輯器是依賴管理的重要步驟。透過組態編輯器使用專案的虛擬環境,可以提高編輯器的功能,讓它能夠提供更好的程式碼提示和建議。在第7章中,我們將描述如何在兩個簡單的步驟中實作這一點:

  1. 指定虛擬環境:在編輯器中指定虛擬環境的路徑。這可以在PyCharm和VS Code中完成,具體步驟請參考第7章。
  2. 使用編輯器命令和鍵盤快捷鍵:透過使用編輯器命令和鍵盤快捷鍵,可以實作程式碼補全、引數資訊、內嵌檔案、重構等功能。這些將在第7章中進行詳細介紹。

為了完成第一步,需要使用虛擬環境的路徑。這個路徑可以透過go指令碼的最後一步獲得,也可以透過其他方式查詢到。

Mermaid 圖表:API 啟動流程

  flowchart TD
    A[啟動 API] --> B[執行 batect start-api]
    B --> C[啟動 API 伺服器]
    C --> D[傳送請求到 API]
    D --> E[接收 API 回應]

圖表翻譯:

上述 Mermaid 圖表描述了啟動 API 的流程。首先,啟動 API 伺服器,然後傳送請求到 API,最後接收 API 的回應。這個流程可以讓我們在本地測試 API。

程式碼解說

# 啟動 API
import subprocess

def start_api():
    # 執行 batect start-api
    subprocess.run(["./batect", "start-api"])

# 傳送請求到 API
import requests

def send_request():
    # 傳送請求到 API
    response = requests.get("http://localhost:8000/predict")
    return response.json()

# 啟動 API 和傳送請求
start_api()
response = send_request()
print(response)

內容解密:

上述程式碼描述瞭如何啟動 API 伺服器和傳送請求到 API。首先,定義了一個 start_api 函式,該函式執行 batect start-api 命令啟動 API 伺服器。然後,定義了一個 send_request 函式,該函式傳送請求到 API 並傳回回應。最後,啟動 API 伺服器和傳送請求到 API,然後列印回應。

使用 GitHub Actions 執行模型訓練

在這個例子中,我們將使用 GitHub Actions 來建立一個 CI/CD 管道,執行模型訓練任務。這個管道將使用 Docker 來安裝和組態必要的依賴項,並在一個臨時的計算例項上執行模型訓練。

GitHub Actions 檔案

我們需要建立一個 .github/workflows/ci.yaml 檔案,定義 GitHub Actions 的工作流程。以下是範例內容:

name: CI/CD 管道

on: [push]

jobs:
  train-model:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - name: 訓練模型
        run: ./batect train-model

這個檔案定義了一個名為 train-model 的工作流程,當程式碼函式庫發生推播事件時觸發。工作流程將在 ubuntu-20.04 環境中執行,並執行兩個步驟:簽出程式碼函式庫和訓練模型。

Batect 檔案

我們需要建立一個 batect.yml 檔案,定義 Docker 容器的組態。以下是範例內容:

containers:
  dev:
    image: python:3.9-slim
    volumes:
      - .:/app
    working_directory: /app
    command: python train.py

這個檔案定義了一個名為 dev 的 Docker 容器,使用 python:3.9-slim 映象。容器將掛載當前目錄到 /app,並設定工作目錄為 /app。當容器啟動時,將執行 python train.py 命令。

執行模型訓練

當我們推播程式碼函式庫時,GitHub Actions 將觸發 train-model 工作流程。工作流程將在 ubuntu-20.04 環境中執行,並執行 ./batect train-model 命令。這個命令將啟動 Docker 容器,安裝和組態必要的依賴項,並執行模型訓練任務。

內容解密:

  • name: CI/CD 管道:定義工作流程的名稱。
  • on: [push]:定義工作流程的觸發事件。
  • jobs::定義工作流程的工作。
  • train-model::定義工作的名稱。
  • runs-on: ubuntu-20.04:定義工作的執行環境。
  • steps::定義工作的步驟。
  • - uses: actions/checkout@v3:簽出程式碼函式庫。
  • - name: 訓練模型:定義步驟的名稱。
  • run: ./batect train-model:執行 ./batect train-model 命令。
  • containers::定義 Docker 容器的組態。
  • dev::定義容器的名稱。
  • image: python:3.9-slim:定義容器的映象。
  • volumes::定義容器的掛載目錄。
  • working_directory: /app:定義容器的工作目錄。
  • command: python train.py:定義容器的啟動命令。

圖表翻譯:

  graph LR
    A[GitHub Actions] -->|觸發|> B[工作流程]
    B -->|執行|> C[Docker 容器]
    C -->|安裝依賴項|> D[模型訓練]
    D -->|執行|> E[結果]

這個圖表展示了 GitHub Actions 的工作流程,從觸發事件到執行結果。工作流程將啟動 Docker 容器,安裝和組態必要的依賴項,並執行模型訓練任務。

佈署機器學習模型的Web API

在這個步驟中,我們將釋出模型API映像到容器登入檔中,並執行命令以告知雲端服務提供者佈署具有特定標籤的映像。這個階段中,我們只需要基礎設施相關的依賴項,例如aws-cdk(AWS)、gcloud(GCP)、azure-cli(Azure)或Terraform。

為了使程式碼樣本簡單且通用化,無論您使用哪個雲端服務提供者,我們都選擇使用偽程式碼來演示這個步驟。

佈署映像到Docker登入檔

# .github/workflows/ci.yaml
name: CI/CD管道

on: [push]
jobs:

  # ... 其他工作(例如執行測試)
  publish-image:
    runs-on: ubuntu-20.04
    steps:

      - uses: actions/checkout@v3
      - name: 釋出映像到Docker登入檔

        run: docker push loan-default-prediction-api:${{github.run_number}}
  deploy-api:
    runs-on: ubuntu-20.04
    steps:

      - uses: actions/checkout@v3
      - name: 佈署模型
        run: ./batect deploy-api
        needs: [publish-image]

定義佈署任務

# batect.yml
容器:
  deploy-api-container:
    映像: google/cloud-sdk:latest
  任務:
    deploy-api:
      說明: 佈署API映像
      執行:
        容器: deploy-api-container
        命令: gcloud run deploy my-model-api --image IMAGE_URL

在這個例子中,我們使用偽程式碼來演示如何將映像從CI/CD管道推播到Docker登入檔中,以及如何佈署這個映像作為API。

注意到,我們沒有需要指定另一個Dockerfile,因為batect允許我們使用預建映像來定義任務。

感謝容器和batect,我們可以在CI或本地機器上以相同的方式執行這個任務,僅需使用./batect deploy-api命令。

佈署Docker映像到容器主機服務

偽程式碼如下:

gcloud run deploy my-model-api --image IMAGE_URL

這個命令將佈署具有特定標籤的映像到雲端服務提供者的容器主機服務中。

在接下來的章節中,我們將描述容器登入檔和雲容器主機服務等構建塊,以幫助您瞭解機器學習模型的生產路徑。

安全依賴管理

2017年,攻擊者透過Apache Struts漏洞入侵Equifax信用監控公司的系統。這個事件表明了依賴管理的重要性。

在下一節中,我們將討論兩個關鍵的實踐,以幫助您安全地管理專案中的依賴項。

提升安全性和效率的實踐

近年來,資料安全漏洞事件層出不窮,例如Equifax事件,暴露了1.43億美國人的個人資料,並導致公司損失3.8億美元。這些事件往往是由於公司使用的軟體版本過時,且存在已知的安全漏洞。例如,Equifax使用的Apache Struts版本存在已知的安全漏洞,儘管維護者已經發現、披露並修復了這個漏洞。

在Python開發中,也存在許多依賴包的安全漏洞,可能導致雲端憑證被竊取或任意程式碼執行。因此,保持依賴包的更新和安全至關重要。

移除不必要的依賴

不必要的依賴,包括過大的基礎映象和未使用的應用級別依賴,會導致多個問題。首先,它們會增加專案的攻擊面,讓專案更容易受到惡意攻擊。其次,它們會增加建置、釋出和下載映象的時間,延長CI/CD管道的反饋迴圈,並可能阻礙專案在生產環境中的自動擴充套件。

最後,不必要的依賴會使專案混亂和難以維護,即使依賴不使用,其轉移依賴(例如,孫依賴)仍可能對其他依賴和轉移依賴產生影響。

為了避免這些問題,我們應該:

  • 使用盡可能小的基礎映象,例如使用python:3.10-slim-bookworm映象(145 MB)而不是python:3.10映象(1 GB)。
  • 定期審查和移除不必要的依賴。

自動化檢查和更新依賴

手動保持依賴包的更新和安全可能很麻煩。幸運的是,近年來,技術已經進步,我們可以輕鬆地在專案中實施自動化檢查和更新依賴的功能。

透過實施這些實踐,我們可以減少安全風險,建立更安全和高效的ML管道和應用程式。

# 使用python:3.10-slim-bookworm映象
FROM python:3.10-slim-bookworm

# 安裝必要的依賴
RUN pip install --no-cache-dir numpy pandas

# 移除不必要的依賴
RUN pip freeze > requirements.txt
RUN pip uninstall -y -r requirements.txt
  graph LR
    A[專案] -->|包含|> B[基礎映象]
    B -->|包含|> C[依賴]
    C -->|可能包含|> D[安全漏洞]
    D -->|可能導致|> E[攻擊]
    E -->|可能導致|> F[資料洩露]
    F -->|可能導致|> G[經濟損失]

圖表翻譯:

此圖表展示了專案、基礎映象、依賴、安全漏洞、攻擊、資料洩露和經濟損失之間的關係。它強調了保持依賴包的更新和安全的重要性,以避免安全漏洞和攻擊。

最佳化 Docker 映像大小

在開發和佈署 Python 應用程式時,使用 Docker 可以簡化環境設定和依賴管理。然而,Docker 映像的大小可能會迅速增加,尤其是當您包含了許多開發依賴時。這篇文章將介紹如何使用 Docker 多階段建構(multistage builds)來排除開發依賴,從而減小生產環境下的 Docker 映像大小。

Docker 多階段建構

Docker 多階段建構允許您在同一個 Dockerfile 中定義多個建構階段。每個階段可以有自己的基礎映像、工作目錄和指令。這使得您可以將開發和生產環境分開,減少最終映像的大小。

以下是一個示例 Dockerfile,展示瞭如何使用多階段建構來排除開發依賴:

# 第一階段:開發環境
FROM python:3.10-slim-bookworm AS dev
WORKDIR /code
RUN apt-get update && apt-get -y install gcc
RUN pip install poetry
ADD pyproject.toml /code/
RUN poetry config installer.max-workers 10
ARG VENV_PATH
ENV VENV_PATH=$VENV_PATH
ENV PATH="$VENV_PATH/bin:$PATH"
CMD ["bash"]

# 第二階段:生成 requirements.txt
FROM dev AS builder
COPY poetry.lock /code
RUN poetry export --without dev --format requirements.txt --output requirements.txt

# 第三階段:生產環境
FROM python:3.10-slim-bookworm AS prod
WORKDIR /code
COPY src /code/src
COPY scripts /code/scripts
COPY artifacts /code/artifacts
COPY --from=builder /code/requirements.txt /code
RUN pip install --no-cache-dir -r /code/requirements.txt
CMD ["./scripts/start-api-prod.sh"]

在這個例子中,第一階段 (dev) 建立了一個開發環境,包含所有開發依賴。第二階段 (builder) 生成了一個 requirements.txt 檔案,包含所有生產環境所需的依賴。第三階段 (prod) 建立了一個生產環境,包含所有必要的依賴和應用程式程式碼。

減小映像大小

使用這個多階段建構方法,可以將 Docker 映像的大小從 1.3 GB(開發環境)減小到 545 MB(生產環境)。這是因為開發環境包含了許多不必要的依賴,例如開發工具和測試框架,而生產環境只包含了必要的依賴和應用程式程式碼。

最佳化 Docker 映像檔大小並自動化安全性漏洞檢查

在前面的章節中,我們已經瞭解瞭如何使用 Docker 來佈署我們的應用程式。然而,為了確保我們的應用程式的安全性和效率,我們需要最佳化 Docker 映像檔的大小並自動化安全性漏洞檢查。

從建構本地開發環境到佈署機器學習模型的 Web API,本文深入探討了利用 Docker 與 Batect 建立高效且安全的機器學習Pipeline的關鍵步驟。透過剖析 Batect 的工作原理、容器定義與任務組態,我們理解了如何簡化複雜的指令操作,並確保開發環境的一致性。尤其值得一提的是,Batect 與 Docker 的結合,不僅加速了建置、測試與佈署流程,更提升了跨平臺的相容性,有效解決了「推播以檢視是否有效」的反模式,縮短了開發的回饋迴圈。

然而,構建高效的Pipeline並非僅止於環境建置。本文進一步分析瞭如何最佳化 Docker 映像檔大小,並強調了移除不必要依賴項和使用精簡基礎映像的重要性。透過多階段建構,我們可以有效區分開發與生產環境,大幅縮減映像檔大小,進而提升佈署效率和安全性。此外,自動化安全性漏洞檢查的匯入,更能有效降低潛在風險,避免類似 Equifax 事件的發生。從程式碼範例到流程圖,本文提供了詳盡的實務指引,讓讀者能輕鬆上手。

展望未來,隨著機器學習應用日益普及,容器化技術的重要性將更加凸顯。持續整合與持續佈署(CI/CD)的實踐,結合自動化安全性漏洞檢查,將成為確保機器學習Pipeline安全穩定的關鍵。玄貓認為,掌握這些最佳實務,並持續關注容器技術與安全領域的最新發展,將有助於開發者構建更強健、更可靠的機器學習應用。對於追求高效能和高安全性的企業而言,投資於這些技術的學習和應用將帶來顯著的效益。