跳至主要内容

面向机器学习的 Podman 入门:精简 MLOps 工作流程

轻量、无守护进程的 Docker Desktop 替代方案,简化容器管理,加速机器学习模型的训练、评估与部署。
· 13分钟 阅读

使用 AI 探索

IT 领域的每位开发者和运维工程师都熟悉使用 Docker 在本地或云端构建与部署应用。不过,作为开发者或机器学习运维工程师,您可能正在寻求优化资源、提升安全性并改进系统集成的方案。Podman 提供了一个值得关注的替代选择。它是一款免费、开源的工具,可替代 Docker 和 Docker Desktop。

在本教程中,我们将介绍 Podman 是什么、Podman 与 Docker 的差异,以及如何安装和使用 Podman。此外,我们还将讲解如何使用 Dockerfile 和 Podman 命令在本地训练、评估和部署机器学习模型。

Podman 的 MLOPs 特色图片

图片来源:作者

什么是 Podman?

Podman 是一款开源的容器管理工具,旨在为开发者和机器学习工程师提供无缝且安全的使用体验。与 Docker 不同,Podman 采用无守护进程(daemonless)架构,支持以无特权(rootless)方式运行容器,从而提升安全性和灵活性。这一关键特性使其无需 root 权限即可运行容器,降低了潜在风险。

Podman 完全兼容 OCI(开放容器倡议)标准,确保使用 Podman 创建的容器与镜像可以轻松集成到其他符合 OCI 的工具与平台中,如 runc、Buildah 和 Skopeo。此外,Podman 支持创建与管理 pod,即共享同一网络命名空间的一组容器,类似于 Kubernetes 的 pod。

使用 Podman 的一大优势在于,其体验与 Docker 相当接近。命令行界面与 Docker 类似,您也可以从 Docker Hub 拉取镜像。这种相似性让熟悉 Docker 的用户能够轻松迁移,同时还提供满足容器化应用开发与部署不断演进需求的高级特性。

Podman 只是 MLOps 工具库中的一个工具。想全面了解 MLOps 生态中的各类工具,请阅读博文《2025 年必知的 25 款 MLOps 工具》。

Docker 与 Podman 对比

Docker 和 Podman 是两款知名的容器管理工具,各自拥有不同的特性与能力。以下对比将帮助您了解它们的差异,并协助您选择更契合需求的工具。

 

Docker

Podman

架构

Docker 采用客户端-服务器架构,依赖名为 dockerd 的守护进程。

Podman 无需守护进程,采用 fork-exec 模型,提升了安全性与简洁性。

安全性

Docker 默认以 root 身份运行容器,可能带来安全风险。

Podman 默认支持无特权容器,降低安全风险。

镜像管理

Docker 使用其自有工具构建并管理容器镜像。

Podman 借助 Buildah 构建镜像,并可运行来自 Docker 注册表的镜像。

兼容性

Docker 使用广泛,并与众多 CI/CD 工具集成。

Podman 提供与 Docker 兼容的 CLI,便于在不改动工作流的情况下迁移。

容器编排

Docker 支持 Docker Swarm 与 Kubernetes 进行编排。

Podman 不支持 Docker Swarm,但可通过 pod 与 Kubernetes 协同工作。

平台支持

Docker 可原生运行于 Linux、macOS 和 Windows(配合 WSL)。

Podman 同样支持 Linux、macOS 和 Windows(配合 WSL)。

性能

Docker 以高效的资源管理与快速部署闻名。

Podman 整体性能相当,且通常具有更快的启动时间。

使用场景

适合需要成熟工具与集成的项目。

适合优先关注安全与轻量化的环境,非常适用于大规模部署。

在 Docker 与 Podman 之间进行选择,主要取决于项目的具体需求,尤其是安全性、兼容性与编排方面。

对于既有的 CI/CD 流水线与完善的容器管理,Docker 仍是强有力的选项;而对强调安全与无特权操作的环境,Podman 则提供了一个安全、轻量的替代方案,同时具备更快的启动速度,适合大规模部署。

查看我们的入门文章 《面向数据科学的 Docker》,其中包含示例代码与案例。

安装 Podman 

首先,请前往官方网站下载并安装 Podman Desktop 安装包。

podman 官网

来源:Podman

安装过程简洁迅速。几分钟内,您将进入入门界面,并被询问是否安装可选扩展。

如果您的 Windows 未安装 WSL,它将自动进行安装。

Podman Desktop 入门界面

接下来,设置 Podman machine。

设置 Podman machine

与 Docker 相比,您无需单独设置 machine。然而,在 Podman 中,您可以同时管理多个运行不同容器的 machine,从而实现更佳的资源管理。

我们的 machine 已启动并运行,随时可以创建镜像并运行容器。

Podman machine 正在运行

为验证 Podman 是否正常工作,我们将从 quay.io 拉取一个示例镜像并执行容器。

$ podman run quay.io/podman/hello

Podman machine 已成功拉取镜像并运行容器,同时输出了日志。

Trying to pull quay.io/podman/hello:latest...
Getting image source signatures
Copying blob sha256:81df7ff16254ed9756e27c8de9ceb02a9568228fccadbf080f41cc5eb5118a44
Copying config sha256:5dd467fce50b56951185da365b5feee75409968cbab5767b9b59e325fb2ecbc0
Writing manifest to image destination
!... Hello Podman World ...!

         .--"--.
       / -     - \
      / (O)   (O) \
   ~~~| -=(,Y,)=- |
    .---. /`  \   |~~
 ~/  o  o \~~~~.----. ~~
  | =(X)= |~  / (O (O) \
   ~~~~~~~  ~| =(Y_)=-  |
  ~~~~    ~~~|   U      |~~

Project:   https://github.com/containers/podman
Website:   https://podman.io
Desktop:   https://podman-desktop.io
Documents: https://docs.podman.io
YouTube:   https://youtube.com/@Podman
X/Twitter: @Podman_io
Mastodon:  @[email protected]

使用 Podman 构建 MLOps 项目

在本次 MLOps 项目中,我们将通过 Dockerfile 和 Podman 实现模型的自动化训练与评估,并对模型进行服务化。流程与 Docker 类似,但我们将使用 Podman CLI 构建镜像并运行容器。

如果您对相关概念还不熟悉,可通过完成MLOps 概念课程学习基础知识。

1. 搭建机器学习项目

要搭建机器学习项目,我们需要创建训练与服务脚本,并准备 requirements.txt 文件用于安装所需的 Python 包。

训练脚本将加载信用评分分类数据集,进行处理与编码并训练模型,同时完成模型评估。最后,我们将使用 pickle 格式保存预处理与训练管道及模型。

src/train.py: 

# src/train.py

import os
import pickle

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestClassifier
from sklearn.impute import SimpleImputer
from sklearn.metrics import (
    accuracy_score,
    classification_report,
    confusion_matrix,
    roc_auc_score,
)
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder


def load_data():
    data_path = "data/train.csv"
    df = pd.read_csv(data_path,low_memory=False)
    print("Data loaded successfully!")
    return df


def preprocess_data(df):
    # Drop unnecessary columns
    df = df.drop(columns=["ID", "Customer_ID", "SSN", "Name", "Month"])

    # Drop rows with missing values
    df = df.dropna()

    # Convert data types
    # Convert the 'Age' column to numeric, setting errors='coerce' to handle non-numeric values
    df["Age"] = pd.to_numeric(df["Age"], errors="coerce")

    # Filter the DataFrame to include only rows where 'Age' is between 1 and 60
    df = df[(df["Age"] >= 1) & (df["Age"] <= 60)]

    df["Annual_Income"] = pd.to_numeric(df["Annual_Income"], errors="coerce")
    df["Monthly_Inhand_Salary"] = pd.to_numeric(
        df["Monthly_Inhand_Salary"], errors="coerce"
    )

    # Separate features and target
    X = df.drop("Credit_Score", axis=1)
    y = df["Credit_Score"]

    print("Data preprocessed successfully!")
    return X, y


def encode_data(X):
    # Identify categorical and numerical features
    categorical_features = [
        "Occupation",
        "Credit_Mix",
        "Payment_of_Min_Amount",
        "Payment_Behaviour",
        "Type_of_Loan",
    ]
    numerical_features = X.select_dtypes(include=["int64", "float64"]).columns.tolist()

    # Define preprocessing steps
    numerical_transformer = SimpleImputer(strategy="median")

    categorical_transformer = Pipeline(
        steps=[
            ("imputer", SimpleImputer(strategy="most_frequent")),
            ("onehot", OneHotEncoder(handle_unknown="ignore")),
        ]
    )

    preprocessor = ColumnTransformer(
        transformers=[
            ("num", numerical_transformer, numerical_features),
            ("cat", categorical_transformer, categorical_features),
        ]
    )

    return preprocessor


def split_data(X, y):
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.2, random_state=42
    )
    return X_train, X_test, y_train, y_test


def train_model(X_train, y_train, preprocessor):
    # Create a pipeline with preprocessing and model
    clf = Pipeline(
        steps=[
            ("preprocessor", preprocessor),
            ("classifier", RandomForestClassifier(n_estimators=100)),
        ]
    )

    # Train the model
    clf.fit(X_train, y_train)

    # Return the trained model
    return clf


def evaluate_model(clf, X_test, y_test):
    # Predict and evaluate
    y_pred = clf.predict(X_test)
    acc = accuracy_score(y_test, y_pred)
    report = classification_report(y_test, y_pred, labels=["Poor", "Standard", "Good"])
    cm = confusion_matrix(y_test, y_pred, labels=["Poor", "Standard", "Good"])

    # Calculate AUC score
    y_test_encoded = y_test.replace({"Poor": 0, "Standard": 1, "Good": 2})
    y_pred_proba = clf.predict_proba(X_test)
    auc_score = roc_auc_score(y_test_encoded, y_pred_proba, multi_class="ovr")

    # Print metrics
    print("Model Evaluation Metrics:")
    print(f"Accuracy: {acc}")
    print(f"AUC Score: {auc_score}")
    print("Classification Report:")
    print(report)

    # Plot confusion matrix
    plt.figure(figsize=(8, 6))
    plt.imshow(cm, interpolation="nearest", cmap=plt.cm.Blues)
    plt.title("Confusion Matrix")
    plt.colorbar()
    tick_marks = np.arange(3)
    plt.xticks(tick_marks, ["Poor", "Standard", "Good"], rotation=45)
    plt.yticks(tick_marks, ["Poor", "Standard", "Good"])
    plt.ylabel("True label")
    plt.xlabel("Predicted label")
    plt.tight_layout()
    cm_path = os.path.join("model", "confusion_matrix.png")
    plt.savefig(cm_path)
    print(f"Confusion matrix saved to {cm_path}")


def save_model(clf):
    model_dir = "model"
    os.makedirs(model_dir, exist_ok=True)
    model_path = os.path.join(model_dir, "model.pkl")

    # Save the trained model
    with open(model_path, "wb") as f:
        pickle.dump(clf, f)
    print(f"Model saved to {model_path}")


def main():
    # Execute steps
    df = load_data()
    X, y = preprocess_data(df)
    preprocessor = encode_data(X)
    X_train, X_test, y_train, y_test = split_data(X, y)
    clf = train_model(X_train, y_train, preprocessor)
    evaluate_model(clf, X_test, y_test)
    save_model(clf)


if __name__ == "__main__":
    main()

模型服务脚本将通过模型文件加载已保存的模型管道,创建一个接收用户字典列表的 POST 请求函数,将其转换为 DataFrame,输入模型生成预测并返回预测标签。我们使用 FastAPI 作为 API 框架,仅需少量代码即可提供模型服务。

src/app.py:

# src/app.py

import pickle
from fastapi import FastAPI
from pydantic import BaseModel
import pandas as pd
import os

# Load the trained model
model_path = os.path.join("model", "model.pkl")
with open(model_path, "rb") as f:
    model = pickle.load(f)

app = FastAPI()


class InputData(BaseModel):
    data: list  # List of dictionaries representing feature values


@app.post("/predict")
def predict(input_data: InputData):
    # Convert input data to DataFrame
    X_new = pd.DataFrame(input_data.data)
    # Ensure the columns match the training data
    prediction = model.predict(X_new)
    # Return predictions
    return {"prediction": prediction.tolist()}



我们需要创建一个 requirements.txt 文件,包含运行上述脚本所需的全部 Python 包。该文件将用于在 Docker 容器中搭建运行环境,确保我们可以顺利执行 Python 脚本。

requirements.txt: 

fastapi
uvicorn[standard]
numpy
pandas
scikit-learn
pydantic
matplotlib

2. 创建 Dockerfile

创建一个“Dockerfile”,并添加以下代码。

该 Dockerfile 将执行以下步骤:

  1. 使用 Python 3.9 slim 作为基础镜像
  2. 将 /app 设为工作目录
  3. 从 requirements.txt 安装 Python 依赖
  4. 将源代码与数据文件拷贝进容器
  5. 创建 model 目录并运行训练脚本
  6. 对外暴露 API 的 8000 端口
  7. 使用 uvicorn 在 8000 端口启动 FastAPI 应用
# Dockerfile

FROM python:3.9-slim

WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy the application code
COPY ./src/ ./src/
COPY ./data/ ./data/

# Ensure the model directory exists and is copied
RUN mkdir -p model

# Run the training script during the build
RUN python src/train.py

# Expose the port for the API
EXPOSE 8000


# Run the FastAPI app
CMD ["uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "8000"]

本地工作区应按如下结构组织:

  1. 一个包含所有 CSV 文件的 data 文件夹
  2. 一个 models 文件夹
  3. 一个存放 Python 脚本的 src 文件夹
  4. 主目录下包含 requirements.txt 与 Dockerfile

其余文件为自动化与 Git 操作的辅助组件。

机器学习项目目录

3. 使用 Podman 构建 Docker 镜像

构建 Docker 镜像很简单:只需在 Dockerfile 所在的当前目录,使用 build 命令并指定镜像名称。

$ podman build -t mlops_app .

构建工具会按顺序执行 Dockerfile 中的所有指令,从环境搭建到提供机器学习应用服务。

我们也可以在日志中看到模型评估结果。模型准确率为 75%,ROC AUC 得分为 0.89,属于中等水平。

STEP 1/11: FROM python:3.9-slim
STEP 2/11: WORKDIR /app
--> Using cache 72ac9e49ae29da1ff19e118653efca17e7a489ae9e7ead917c83d942a3ea4e13
--> 72ac9e49ae29
STEP 3/11: COPY requirements.txt .
--> Using cache 3a05ca95caaf98c448c53a796714328bf9f7cff7896cce348f84a26b8d0dae61
--> 3a05ca95caaf
STEP 4/11: RUN pip install --no-cache-dir -r requirements.txt
--> Using cache 28109d1183449396a5df0006ab603dd5cf2aa2c06a810bdc6bcf0f843f855ee0
--> 28109d118344
STEP 5/11: COPY ./src/ ./src/
--> f814f699c58a
STEP 6/11: COPY ./data/ ./data/
--> 922550900cd0
STEP 7/11: RUN mkdir -p model
--> 36fc01f2d169
STEP 8/11: RUN python src/train.py
Data loaded successfully!
Data preprocessed successfully!
Data encoded successfully!
Data split successfully!
Model trained successfully!
Model Evaluation Metrics:
Accuracy: 0.7546181417149159
ROC AUC Score: 0.8897184704689612
Classification Report:
              precision    recall  f1-score   support

        Good       0.71      0.67      0.69      1769
        Poor       0.75      0.76      0.75      3403
    Standard       0.77      0.78      0.78      5709

    accuracy                           0.75     10881
   macro avg       0.74      0.74      0.74     10881
weighted avg       0.75      0.75      0.75     10881

Model saved to model/model.pkl
--> 5d4777c08580
STEP 9/11: EXPOSE 8000
--> 7bb09a613e7f
STEP 10/11: WORKDIR /app/src
--> 06b6394c2e2d
STEP 11/11: CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
COMMIT mlops-app
--> 9a7a42b03664
Successfully tagged localhost/mlops-app:latest
9a7a42b03664f1e4631330cd682cb2de26e513c5d776fa2ce2042b3bb9455e14

如果您打开 Podman Desktop,并点击“Images”标签页,您会看到您的 mlops_app 镜像已成功创建。

Podman Desktop Images 标签页

查看《面向数据科学的 Docker》速查表,学习所有相关的 Docker 命令。只需将首个命令 docker 替换为 podman 即可。

4. 使用 Podman 运行 Docker 容器

我们将使用 run 命令,从 mlops-app 镜像以分离模式(-d)启动一个名为 "mlops_container" 的容器,并将容器的 8000 端口映射到宿主机的 8000 端口。这样即可从容器外部访问 FastAPI 应用。

$ podman run -d --name mlops_container -p 8000:8000 mlops-app  

要查看 "mlops_container" 的所有日志,请使用 logs 命令。

$ podman logs -f mlops_container    

输出:                                                                  

INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     10.88.0.1:36886 - "POST /predict HTTP/1.1" 200 OK

您也可以打开 Podman Desktop,点击“Containers”标签页查看正在运行的容器。

Podman Desktop Containers 标签页

在 Podman Desktop 中查看日志:点击“mlops_container”,然后选择“Terminal”标签页。

Podman Desktop 容器日志

5. 测试 ML 推理服务

我们现在通过访问交互式 Swagger UI 进行测试:http://localhost:8000/docs。Swagger UI 提供了友好的界面,便于您探索所有可用的 API 端点。

ML 应用的 Swagger UI 界面

我们也可以在终端中使用 CURL 命令测试 API。

$ curl -X POST "http://localhost:8000/predict" \
     -H "Content-Type: application/json" \
     -d '{
           "data": [
             {
               "Age": 35,
               "Occupation": "Engineer",
               "Annual_Income": 85000,
               "Monthly_Inhand_Salary": 7000,
               "Num_Bank_Accounts": 2,
               "Num_Credit_Card": 3,
               "Interest_Rate": 5,
               "Num_of_Loan": 1,
               "Type_of_Loan": "Personal Loan",
               "Delay_from_due_date": 2,
               "Num_of_Delayed_Payment": 1,
               "Changed_Credit_Limit": 15000,
               "Num_Credit_Inquiries": 2,
               "Credit_Mix": "Good",
               "Outstanding_Debt": 10000,
               "Credit_Utilization_Ratio": 30,
               "Credit_History_Age": 15,
               "Payment_of_Min_Amount": "Yes",
               "Total_EMI_per_month": 500,
               "Amount_invested_monthly": 1000,
               "Payment_Behaviour": "Regular",
               "Monthly_Balance": 5000
             }
           ]
         }'

FastAPI 服务器运行正常,成功处理了用户输入并返回准确的预测结果。

{"prediction":["Good"]}

6. 停止并移除容器

体验完 API 后,我们将使用 stop 命令停止容器。

$ podman stop mlops_container 

同时也可以使用 rm 命令移除容器,释放系统资源。移除前必须先停止容器。

$ podman rm mlops_container     

7. 移除镜像

要移除名为 "mlops-app" 的本地容器镜像,我们将使用 rmi 命令。

$ podman rmi mlops-app 

如果您在运行上述代码或编写 Dockerfile 时遇到问题,请查看 GitHub 仓库kingabzpro/mlops-with-podman。其中包含使用指南和在您本地执行代码所需的全部文件。

您学习的下一步是尝试用 Podman 构建10 个 Docker 项目创意(从入门到高级),以加深您对 Podman 生态的理解。

结语

在某些用例下,Podman 是 Docker 的有力替代,但许多开发者仍偏好 Docker Desktop 与 CLI,这主要得益于 Docker 广泛的集成与友好的工具链。

不过,对于简单直接的 MLOps 项目,工程师可能会选择 Podman,相较于 Docker Desktop,其更为轻量且易于设置。

在本教程中,我们通过与 Docker 的对比,介绍了流行的容器管理工具 Podman,并演示了如何安装 Podman Desktop。同时我们带您完成一个基于 Podman 的 MLOps 项目,涵盖创建 Dockerfile、构建镜像与运行容器。Podman 上手简单,如果您已熟悉 Docker,将会体会到其平滑的迁移体验。

参加MLOps 部署与全生命周期课程,深入了解现代 MLOps 框架,包括机器学习模型的生命周期与部署。

Abid Ali Awan

作者

Abid Ali Awan

作为一名持证的数据科学家,我热衷于利用前沿技术打造创新的机器学习应用。凭借在语音识别、数据分析与报告、MLOps、对话式人工智能以及自然语言处理方面的扎实背景,我不断打磨构建智能系统的能力,力求带来切实影响。除技术专长外,我也擅长沟通,能够将复杂概念提炼为清晰、简明的表述。因此,我成为数据科学领域备受关注的博主,与不断壮大的数据专业人士社区分享洞见与实践经验。目前,我专注于内容创作与编辑,借助大语言模型打造有力且吸引人的内容,帮助企业与个人更好地发挥数据价值。

主题