Introduction au MLOps

Le déploiement de modèles de machine learning en production présente des défis uniques : scalabilité, reproductibilité, monitoring et maintenance. Docker et Kubernetes forment une combinaison puissante pour résoudre ces problématiques en offrant containerisation et orchestration.

Prérequis

  • Connaissances de base en Python et ML
  • Docker installé sur votre machine
  • Kubectl et accès à un cluster Kubernetes
  • Un modèle ML entraîné (nous utiliserons scikit-learn)

Étape 1 : Créer l'Application ML

Commençons par créer une API simple avec Flask pour servir notre modèle :

Structure du Projet

ml-app/
├── app.py
├── model.py
├── requirements.txt
├── Dockerfile
├── model.pkl
└── k8s/
    ├── deployment.yaml
    ├── service.yaml
    └── ingress.yaml

Code de l'Application (app.py)

from flask import Flask, request, jsonify
import joblib
import numpy as np
import logging

app = Flask(__name__)
logging.basicConfig(level=logging.INFO)

# Charger le modèle au démarrage
model = joblib.load('model.pkl')

@app.route('/health', methods=['GET'])
def health_check():
    return jsonify({'status': 'healthy'}), 200

@app.route('/predict', methods=['POST'])
def predict():
    try:
        data = request.get_json()
        features = np.array(data['features']).reshape(1, -1)
        
        prediction = model.predict(features)[0]
        probability = model.predict_proba(features)[0].max()
        
        return jsonify({
            'prediction': int(prediction),
            'confidence': float(probability),
            'status': 'success'
        })
    
    except Exception as e:
        app.logger.error(f"Erreur de prédiction: {str(e)}")
        return jsonify({'error': str(e)}), 400

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

Requirements (requirements.txt)

Flask==2.3.3
scikit-learn==1.3.0
joblib==1.3.2
numpy==1.24.3
gunicorn==21.2.0

Étape 2 : Containerisation avec Docker

Dockerfile Optimisé

# Utiliser une image Python légère
FROM python:3.9-slim

# Définir le répertoire de travail
WORKDIR /app

# Copier les requirements et installer les dépendances
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copier le code de l'application
COPY . .

# Créer un utilisateur non-root pour la sécurité
RUN useradd -m -u 1000 mluser && chown -R mluser:mluser /app
USER mluser

# Exposer le port
EXPOSE 5000

# Utiliser Gunicorn pour la production
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "app:app"]

Construction de l'Image

# Construire l'image
docker build -t ml-app:v1.0 .

# Tester localement
docker run -p 5000:5000 ml-app:v1.0

# Tester l'API
curl -X POST http://localhost:5000/predict \
  -H "Content-Type: application/json" \
  -d '{"features": [5.1, 3.5, 1.4, 0.2]}'

Étape 3 : Optimisation Docker

Multi-stage Build

# Build stage
FROM python:3.9-slim as builder

WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

# Production stage
FROM python:3.9-slim

WORKDIR /app

# Copier les packages installés
COPY --from=builder /root/.local /root/.local
COPY . .

# Mettre à jour PATH
ENV PATH=/root/.local/bin:$PATH

RUN useradd -m -u 1000 mluser && chown -R mluser:mluser /app
USER mluser

EXPOSE 5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "app:app"]

Étape 4 : Déploiement Kubernetes

Deployment (k8s/deployment.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ml-app-deployment
  labels:
    app: ml-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: ml-app
  template:
    metadata:
      labels:
        app: ml-app
    spec:
      containers:
      - name: ml-app
        image: ml-app:v1.0
        ports:
        - containerPort: 5000
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 5000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 5000
          initialDelaySeconds: 5
          periodSeconds: 5
        env:
        - name: FLASK_ENV
          value: "production"

Service (k8s/service.yaml)

apiVersion: v1
kind: Service
metadata:
  name: ml-app-service
spec:
  selector:
    app: ml-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 5000
  type: ClusterIP

Ingress (k8s/ingress.yaml)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ml-app-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/rate-limit: "100"
spec:
  rules:
  - host: ml-app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: ml-app-service
            port:
              number: 80

Étape 5 : Déploiement et Gestion

Commandes de Déploiement

# Appliquer les configurations
kubectl apply -f k8s/

# Vérifier le déploiement
kubectl get deployments
kubectl get pods
kubectl get services

# Voir les logs
kubectl logs -f deployment/ml-app-deployment

# Mise à jour rolling
kubectl set image deployment/ml-app-deployment ml-app=ml-app:v1.1

Étape 6 : Monitoring et Observabilité

ConfigMap pour la Configuration

apiVersion: v1
kind: ConfigMap
metadata:
  name: ml-app-config
data:
  MODEL_VERSION: "v1.0"
  LOG_LEVEL: "INFO"
  MAX_REQUESTS_PER_MINUTE: "1000"

Métriques Prometheus

# Ajouter à app.py
from prometheus_client import Counter, Histogram, generate_latest

REQUEST_COUNT = Counter('ml_requests_total', 'Total ML requests')
REQUEST_LATENCY = Histogram('ml_request_duration_seconds', 'ML request latency')

@app.route('/metrics')
def metrics():
    return generate_latest()

@REQUEST_LATENCY.time()
@app.route('/predict', methods=['POST'])
def predict():
    REQUEST_COUNT.inc()
    # ... reste du code

Étape 7 : Sécurité et Bonnes Pratiques

NetworkPolicy

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: ml-app-netpol
spec:
  podSelector:
    matchLabels:
      app: ml-app
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: ingress-nginx
    ports:
    - protocol: TCP
      port: 5000

Secrets pour les Données Sensibles

# Créer un secret
kubectl create secret generic ml-app-secrets \
  --from-literal=api-key=your-api-key \
  --from-literal=db-password=your-password

# Utiliser dans le deployment
env:
- name: API_KEY
  valueFrom:
    secretKeyRef:
      name: ml-app-secrets
      key: api-key

Étape 8 : CI/CD Pipeline

GitHub Actions Workflow

name: ML App CI/CD

on:
  push:
    branches: [ main ]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Build Docker image
      run: |
        docker build -t ${{ secrets.REGISTRY }}/ml-app:${{ github.sha }} .
        
    - name: Push to registry
      run: |
        echo ${{ secrets.REGISTRY_PASSWORD }} | docker login -u ${{ secrets.REGISTRY_USER }} --password-stdin
        docker push ${{ secrets.REGISTRY }}/ml-app:${{ github.sha }}
        
    - name: Deploy to Kubernetes
      run: |
        kubectl set image deployment/ml-app-deployment ml-app=${{ secrets.REGISTRY }}/ml-app:${{ github.sha }}

Étape 9 : Scaling et Performance

Horizontal Pod Autoscaler

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: ml-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: ml-app-deployment
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

Tests et Validation

Test de Charge

# Utiliser Apache Bench
ab -n 1000 -c 10 -p data.json -T application/json http://ml-app.example.com/predict

# Ou avec Python
import requests
import concurrent.futures
import time

def test_prediction():
    response = requests.post('http://ml-app.example.com/predict',
                           json={'features': [5.1, 3.5, 1.4, 0.2]})
    return response.status_code

# Test concurrent
with concurrent.futures.ThreadPoolExecutor(max_workers=50) as executor:
    futures = [executor.submit(test_prediction) for _ in range(1000)]
    results = [future.result() for future in futures]

Troubleshooting

Commandes de Debug

# Vérifier les pods
kubectl describe pod 

# Accéder à un pod
kubectl exec -it  -- /bin/bash

# Voir les événements
kubectl get events --sort-by=.metadata.creationTimestamp

# Vérifier les ressources
kubectl top pods
kubectl top nodes

Conclusion

Ce tutoriel vous a guidé à travers le processus complet de déploiement d'une application ML avec Docker et Kubernetes. Cette approche offre :

  • Scalabilité automatique selon la demande
  • Haute disponibilité avec la réplication
  • Déploiements sans interruption avec rolling updates
  • Monitoring intégré pour la production
  • Sécurité renforcée avec les bonnes pratiques K8s

Cette architecture MLOps robuste vous permettra de déployer et maintenir vos modèles ML en production avec confiance.

Besoin d'aide pour implémenter votre pipeline MLOps ? Contactez-moi pour un accompagnement personnalisé.