تطوير الويب

MLflow Model Registry وCI/CD: من staging إلى الإنتاج

7 دقائق للقراءة

📌 نظرة عامّة: MLOps حديث: من النموذج إلى الإنتاج

دَرَّبتَ نموذجًا، metrics تفوق نسخة الخدمة الحالية، والرغبة في دفع checkpoint إلى prod قويّة. تلك بالضبط لحظة فقدان الوقت. دون Registry منضبط، binary مَنسوخ يدويًّا على خادم، ولا أحد يعرف ماذا يخدم ماذا في الشهر الموالي.

MLflow Model Registry يحلّ هذه المشكلة بتحويل كلّ نسخة نموذج إلى artefact مُؤَرَّخ، قابل للوسم، وقابل للعنونة بـ URI مستقرّ. منذ MLflow 2.9 (RFC #10336)، الـ stages التاريخية (None، Staging، Production، Archived) مُستَبعَدة لصالح aliases القابلة للتغيير وtags المُهَيكَلَة.

المتطلّبات

  • MLflow ≥ 3.3.0 (webhooks OSS) — نسخة مستقرّة: MLflow 3.12.0
  • خادم MLflow remote (tracking + registry) بـ backend store علاقي وartefact store S3/MinIO/GCS
  • Python 3.10+ وpip install mlflow==3.12.0
  • Docker Engine 24+ وregistry صور خاصّ
  • Repo GitHub مع secrets، workspace Slack مع Incoming Webhook

الخطوة 1 — aliases وtags (مقابل stages المُستَبعَدة)

alias مؤشّر مُسَمَّى قابل للتغيير نحو نسخة دقيقة — champion، challenger، shadow. عدّة aliases يمكن أن تتعايش. URI models:/<name>@<alias> يحلّ إلى النسخة المُشار إليها. tag زوج مفتاح/قيمة (validation_status=passed، owner=ml-platform).

from mlflow import MlflowClient

client = MlflowClient()

# القديم (مُستَبعَد):
# client.transition_model_version_stage(name="fraud-classifier", version=7, stage="Production")

# الجديد (موصى به):
client.set_registered_model_alias(name="fraud-classifier", alias="champion", version=7)
client.set_model_version_tag(name="fraud-classifier", version=7,
                             key="validation_status", value="passed")

الخطوة 2 — تسجيل نموذج في registry

import mlflow, os
mlflow.set_tracking_uri(os.environ["MLFLOW_TRACKING_URI"])

RUN_ID     = os.environ["RUN_ID"]
MODEL_NAME = "fraud-classifier"

model_uri = f"runs:/{RUN_ID}/model"
mv = mlflow.register_model(model_uri=model_uri, name=MODEL_NAME)
print(f"Registered {MODEL_NAME} version {mv.version} from run {RUN_ID}")

المُتَوَقَّع: Registered fraud-classifier version 12. إشارة النجاح: client.get_model_version(MODEL_NAME, mv.version).status == "READY".

الخطوة 3 — إسناد alias « challenger »

from mlflow import MlflowClient
client = MlflowClient()

client.set_registered_model_alias(name="fraud-classifier", alias="challenger", version=mv.version)
client.set_model_version_tag(name="fraud-classifier", version=mv.version,
                             key="validation_status", value="pending")
client.set_model_version_tag(name="fraud-classifier", version=mv.version,
                             key="git_sha", value=os.environ["GITHUB_SHA"])

الخطوة 4 — Workflow GitHub Actions: تقييم آلي

name: mlflow-promote

on:
  workflow_dispatch:
    inputs:
      run_id:
        description: "MLflow run_id"
        required: true

env:
  MODEL_NAME: fraud-classifier
  MLFLOW_TRACKING_URI:      ${{ secrets.MLFLOW_TRACKING_URI }}
  MLFLOW_TRACKING_USERNAME: ${{ secrets.MLFLOW_TRACKING_USERNAME }}
  MLFLOW_TRACKING_TOKEN:    ${{ secrets.MLFLOW_TRACKING_TOKEN }}

jobs:
  register-and-evaluate:
    runs-on: ubuntu-24.04
    outputs:
      version: ${{ steps.register.outputs.version }}
      passed:  ${{ steps.eval.outputs.passed }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install mlflow==3.12.0 scikit-learn==1.5.2 pandas==2.2.3

      - id: register
        run: |
          VERSION=$(python ci/register_challenger.py "${{ inputs.run_id }}")
          echo "version=$VERSION" >> "$GITHUB_OUTPUT"

      - id: eval
        run: |
          if python ci/evaluate_challenger.py "${{ steps.register.outputs.version }}"; then
            echo "passed=true"  >> "$GITHUB_OUTPUT"
          else
            echo "passed=false" >> "$GITHUB_OUTPUT"
            exit 1
          fi

الخطوة 5 — ترقية challenger → champion

promote:
  needs: register-and-evaluate
  if: needs.register-and-evaluate.outputs.passed == 'true'
  runs-on: ubuntu-24.04
  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v5
      with: { python-version: "3.11" }
    - run: pip install mlflow==3.12.0
    - run: python ci/promote_to_champion.py "${{ needs.register-and-evaluate.outputs.version }}"
# ci/promote_to_champion.py
import sys
from mlflow import MlflowClient

MODEL_NAME = "fraud-classifier"
new_version = sys.argv[1]
client = MlflowClient()

try:
    previous = client.get_model_version_by_alias(MODEL_NAME, "champion")
    client.set_model_version_tag(MODEL_NAME, new_version,
                                 "previous_champion_version", str(previous.version))
except Exception:
    pass

client.set_registered_model_alias(MODEL_NAME, "champion", new_version)
client.set_model_version_tag(MODEL_NAME, new_version, "validation_status", "passed")
print(f"champion -> v{new_version}")

الخطوة 6 — بناء صورة Docker عبر mlflow models build-docker

build-image:
  needs: promote
  runs-on: ubuntu-24.04
  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v5
      with: { python-version: "3.11" }
    - run: pip install mlflow==3.12.0

    - name: build docker image
      env:
        MLFLOW_TRACKING_URI:   ${{ secrets.MLFLOW_TRACKING_URI }}
        MLFLOW_TRACKING_TOKEN: ${{ secrets.MLFLOW_TRACKING_TOKEN }}
      run: |
        mlflow models build-docker \
          --model-uri "models:/fraud-classifier@champion" \
          --name "ghcr.io/${{ github.repository_owner }}/fraud-classifier:${{ github.sha }}"

إشارة النجاح: docker run --rm -p 8080:8080 ghcr.io/<owner>/fraud-classifier:<sha> ثم curl localhost:8080/ping يُرجع HTTP 200.

الخطوة 7 — نشر الصورة

deploy:
  needs: build-image
  runs-on: ubuntu-24.04
  permissions:
    packages: write
    contents: read
  steps:
    - uses: docker/login-action@v3
      with:
        registry: ghcr.io
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}

    - run: docker push ghcr.io/${{ github.repository_owner }}/fraud-classifier:${{ github.sha }}

    - uses: azure/setup-kubectl@v4
    - run: echo "${{ secrets.KUBECONFIG_B64 }}" | base64 -d > $HOME/.kube/config

    - run: |
        kubectl set image deployment/fraud-classifier \
          api=ghcr.io/${{ github.repository_owner }}/fraud-classifier:${{ github.sha }} \
          -n inference
        kubectl rollout status deployment/fraud-classifier -n inference --timeout=5m

الخطوة 8 — Webhook لإشعار الانتقالات

from mlflow.webhooks import create_webhook

create_webhook(
    name="slack-champion-transition",
    url="https://relay.internal/mlflow/slack",
    events=["model_version.alias_created"],
    secret="REPLACE_WITH_STRONG_SECRET",
    description="Notify Slack on champion alias movement",
)
# relay Flask
import hmac, hashlib, os, requests
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["MLFLOW_WEBHOOK_SECRET"].encode()
SLACK  = os.environ["SLACK_WEBHOOK_URL"]

@app.post("/mlflow/slack")
def mlflow_slack():
    sig      = request.headers.get("X-Mlflow-Signature", "")
    expected = hmac.new(SECRET, request.data, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(sig, expected):
        abort(401)
    payload = request.get_json()
    if payload.get("alias") != "champion":
        return "", 204
    requests.post(SLACK, json={
        "text": f":rocket: champion -> {payload['name']} v{payload['version']}"
    }, timeout=5)
    return "", 204

الخطوة 9 — rollback (إعادة إسناد alias)

# ci/rollback_champion.py
from mlflow import MlflowClient

MODEL_NAME = "fraud-classifier"
client = MlflowClient()

current = client.get_model_version_by_alias(MODEL_NAME, "champion")
previous_tag = next(
    (t.value for t in client.get_model_version(MODEL_NAME, current.version).tags
     if t.key == "previous_champion_version"),
    None,
)
if not previous_tag:
    raise SystemExit("Aucune version précédente connue, rollback impossible")

client.set_registered_model_alias(MODEL_NAME, "champion", previous_tag)
client.set_model_version_tag(MODEL_NAME, current.version, "validation_status", "rolled_back")
print(f"rollback : champion -> v{previous_tag}")

المُتَوَقَّع: rollback : champion -> v11. إن كانت pods تُخَزِّن النموذج في الذاكرة عند الإقلاع، أضف kubectl rollout restart deployment/fraud-classifier -n inference.

الخطوة 10 — التحقّق وإشارة النجاح

MODEL=fraud-classifier

# 1. Registry يكشف champion
mlflow client get-registered-model-by-alias $MODEL champion

# 2. tag تدقيق على النسخة الحالية
VERSION=$(mlflow client get-registered-model-by-alias $MODEL champion --jsonpath '$.version')
mlflow client get-model-version $MODEL $VERSION --jsonpath '$.tags'

# 3. صورة في prod تخدم نفس النسخة
curl -s https://api.example.com/fraud-classifier/version

# 4. webhook ACTIVE
python -c "from mlflow.webhooks import list_webhooks; \
print([w for w in list_webhooks() if w.name == 'slack-champion-transition'])"

# 5. استدعاء استنتاج
curl -s -X POST https://api.example.com/fraud-classifier/invocations \
  -H "Content-Type: application/json" \
  -d '{"inputs": [[0.1, 0.2, 0.3, 0.4, 0.5]]}'

أخطاء شائعة

العَرَض السبب المحتمل التصحيح
DeprecationWarning: stage parameter is deprecated كود legacy يستدعي transition_model_version_stage استبدل بـ set_registered_model_alias + tag
RESOURCE_DOES_NOT_EXIST: Alias ‘champion’ not found أوّل نشر غَلِّف في try/except
build-docker يفشل على pip conda.yaml يُشير لـ index خاصّ أعد log بـ extra_pip_requirements نظيف
signature HMAC غير صالح secret مُقَطَّع تحقّق من الطول
pod يخدم النسخة القديمة النموذج مُحَمَّل في الذاكرة عند الإقلاع kubectl rollout restart
UI يعرض N-1 cache متصفّح أو LB فَرِّغ cache
webhook مُشَغَّل عدّة مرّات retry على timeout idempotence بـ deduplication

مصادر رسمية

  • MLflow Model Registry — التوثيق الرسمي
  • API mlflow.client
  • MLflow Webhooks (OSS، منذ 3.3.0)
  • RFC #10336 — Deprecating model registry stages
  • MLflow CLI reference

الأدلّة المرتبطة

FAQ

هل stages MLflow ستختفي كلّيًّا؟ مُستَبعَدة منذ 2.9 لكنّها وظيفية في 3.x للتوافق. أيّ pipeline جديد يستند حصرًا إلى aliases وtags.

كم alias نضع على نسخة؟ لا حدّ صارم. عمليًّا: champion، challenger، أحيانًا shadow.

URI models:/<name>@<alias> مع كلّ flavors؟ نعم. حلّ URI يتمّ على مستوى client.

إعادة بناء صورة مع كلّ ترقية؟ موصى به إن غيّرت artefacts التبعيّات. البديل: صورة محايدة تُحَمِّل ديناميكيًّا.

webhook MLflow يُدير retries؟ نعم. يُفرض idempotence في relay.

عدّة بيئات مع aliases؟ aliases scoped (staging-champion) أو فصل في RegisteredModel متمايزة.

لماذا لا mlflow deployments مباشرة؟ يُغطّي targets مُدارة (SageMaker، Azure ML، Databricks Serving). لـ Kubernetes ذاتي، build-docker الخيار القياسي.

مقالات ذات صلة

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité