xbe.at

MLOps and deployment

Dockerfile and Docker Compose

8 min read, 16 sections

Contents
  1. Dockerfile Basics with Python
  2. Multi-Stage Python Builds
  3. Docker Compose for Python Development
  4. Python Database Integration
  5. Development vs Production Configurations
  6. Containerized Python Testing
  7. Python Microservices Architecture
  8. Environment Management
  9. Health Checks and Monitoring
  10. Container Resource Management
  11. Network Configuration and Service Discovery
  12. Security Best Practices
  13. Real-world Application: ML Model Deployment
  14. Results for ML Model Deployment
  15. Real-world Application: Distributed Task Processing
  16. Additional Resources

Dockerfile Basics with Python

A Dockerfile is a text document containing instructions to build a Docker image automatically. For Python applications, it defines the base image, working directory, dependency installations, and commands to run the application.

# Example Python application
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return "Hello from Dockerized Flask!"

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

# Corresponding Dockerfile
'''
FROM python:3.9-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY app.py .

EXPOSE 5000
CMD ["python", "app.py"]
'''

Multi-Stage Python Builds

Multi-stage builds allow creating optimized Docker images by separating build dependencies from runtime dependencies, significantly reducing the final image size while maintaining functionality and security.

'''
# Multi-stage Dockerfile
FROM python:3.9 AS builder

WORKDIR /build
COPY requirements.txt .
RUN pip install --user -r requirements.txt

FROM python:3.9-slim
WORKDIR /app

COPY --from=builder /root/.local /root/.local
COPY . .

ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
'''

# Example requirements.txt
'''
numpy==1.21.0
pandas==1.3.0
scikit-learn==0.24.2
'''

Docker Compose for Python Development

Docker Compose orchestrates multi-container Python applications, defining services, networks, and volumes in a declarative YAML format. It enables consistent development environments and simplifies service dependencies management.

'''
version: '3.8'

services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/app
    environment:
      - FLASK_ENV=development
    depends_on:
      - db

  db:
    image: postgres:13
    environment:
      - POSTGRES_USER=myuser
      - POSTGRES_PASSWORD=mypassword
      - POSTGRES_DB=mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
'''

Python Database Integration

The integration of Python applications with databases in Docker requires proper configuration of connection parameters and environment variables, ensuring data persistence and secure communication between containers.

import os
from sqlalchemy import create_engine
from flask_sqlalchemy import SQLAlchemy
from flask import Flask

app = Flask(__name__)

# Database configuration
db_params = {
    'user': os.environ.get('POSTGRES_USER', 'myuser'),
    'password': os.environ.get('POSTGRES_PASSWORD', 'mypassword'),
    'host': os.environ.get('DB_HOST', 'db'),
    'port': os.environ.get('DB_PORT', '5432'),
    'database': os.environ.get('POSTGRES_DB', 'mydb')
}

app.config['SQLALCHEMY_DATABASE_URI'] = (
    f"postgresql://{db_params['user']}:{db_params['password']}"
    f"@{db_params['host']}:{db_params['port']}/{db_params['database']}"
)

db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)

Development vs Production Configurations

Managing different Docker configurations for development and production environments requires careful consideration of security, performance, and debugging capabilities while maintaining consistency across deployments.

'''
# docker-compose.dev.yml
version: '3.8'
services:
  web:
    build: 
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - .:/app
    environment:
      - FLASK_ENV=development
      - DEBUG=1

# docker-compose.prod.yml
version: '3.8'
services:
  web:
    build: 
      context: .
      dockerfile: Dockerfile.prod
    environment:
      - FLASK_ENV=production
      - DEBUG=0
    deploy:
      replicas: 3
      restart_policy:
        condition: on-failure
'''

Containerized Python Testing

Docker containers provide isolated environments for running Python tests, ensuring consistent test execution across different platforms and preventing environmental dependencies from affecting test results.

'''
# Dockerfile.test
FROM python:3.9-slim

WORKDIR /tests
COPY requirements.txt requirements-test.txt ./
RUN pip install -r requirements.txt -r requirements-test.txt

COPY tests/ ./tests/
COPY src/ ./src/

CMD ["pytest", "--cov=src", "tests/"]
'''

# test_example.py
import pytest
from src.math_operations import calculate_stats

def test_calculate_mean():
    data = [1, 2, 3, 4, 5]
    result = calculate_stats(data)
    assert result['mean'] == 3.0
    assert result['std'] == pytest.approx(1.58, rel=1e-2)

Python Microservices Architecture

Microservices architecture in Docker allows breaking down complex Python applications into smaller, independently deployable services that communicate through well-defined APIs and message queues.

# Service 1: User Authentication
from flask import Flask, jsonify
import jwt

app = Flask(__name__)

@app.route('/auth', methods=['POST'])
def authenticate():
    # Authentication logic
    token = jwt.encode(
        {'user_id': 123, 'role': 'user'},
        'secret_key',
        algorithm='HS256'
    )
    return jsonify({'token': token})

'''
# docker-compose.yml for microservices
services:
  auth_service:
    build: ./auth
    ports: 
      - "5000:5000"
  
  data_service:
    build: ./data
    ports:
      - "5001:5000"
    depends_on:
      - auth_service
'''

Environment Management

Proper environment variable management in Docker ensures secure configuration handling and flexibility across different deployment scenarios while maintaining application security.

import os
from dotenv import load_dotenv

# Config class for different environments
class Config:
    def __init__(self):
        load_dotenv()
        
    @property
    def database_url(self):
        return os.getenv('DATABASE_URL')
    
    @property
    def api_key(self):
        return os.getenv('API_KEY')
    
    @property
    def debug_mode(self):
        return os.getenv('DEBUG', 'False').lower() == 'true'

'''
# .env.example
DATABASE_URL=postgresql://user:pass@localhost:5432/db
API_KEY=your_secret_key
DEBUG=False

# docker-compose.yml environment section
services:
  web:
    env_file:
      - .env.production
    environment:
      - DEBUG=False
'''

Health Checks and Monitoring

Implementing health checks and monitoring in Dockerized Python applications ensures reliable operation and provides insights into application performance and container health status.

from flask import Flask
import prometheus_client
from prometheus_client import Counter, Histogram
import time

app = Flask(__name__)

# Prometheus metrics
REQUEST_COUNT = Counter(
    'request_count', 'App Request Count',
    ['method', 'endpoint', 'http_status']
)
REQUEST_LATENCY = Histogram(
    'request_latency_seconds',
    'Request latency in seconds'
)

@app.route('/health')
def health_check():
    return {
        'status': 'healthy',
        'timestamp': time.time(),
        'version': '1.0.0',
        'dependencies': {
            'database': check_db_connection(),
            'cache': check_cache_status()
        }
    }

'''
# Docker health check configuration
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:5000/health || exit 1
'''

Container Resource Management

Managing container resources effectively ensures optimal performance of Python applications while preventing resource exhaustion. Docker allows precise control over CPU, memory, and other system resources.

'''
# docker-compose.yml with resource constraints
version: '3.8'
services:
  web:
    build: .
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M
'''

# Memory-aware Python code
import psutil
import resource

def monitor_resources():
    # Get container memory limits
    with open('/sys/fs/cgroup/memory/memory.limit_in_bytes') as f:
        memory_limit = int(f.read())
    
    # Current usage
    process = psutil.Process()
    memory_info = process.memory_info()
    
    return {
        'memory_usage': memory_info.rss,
        'memory_limit': memory_limit,
        'cpu_percent': process.cpu_percent()
    }

Network Configuration and Service Discovery

Docker networking enables seamless communication between containerized Python services while providing service discovery capabilities for dynamic microservice architectures.

from consul import Consul
import socket

class ServiceRegistry:
    def __init__(self):
        self.consul = Consul(host='consul')
        self.service_name = 'python-app'
        
    def register(self):
        service_id = f"{self.service_name}-{socket.gethostname()}"
        self.consul.agent.service.register(
            name=self.service_name,
            service_id=service_id,
            address=socket.gethostname(),
            port=5000,
            tags=['python', 'api'],
            check=self.get_health_check()
        )

'''
# Network configuration in docker-compose.yml
version: '3.8'
services:
  app:
    networks:
      - backend
      - frontend
  
networks:
  backend:
    driver: bridge
    internal: true
  frontend:
    driver: bridge
'''

Security Best Practices

Implementing security measures in Dockerized Python applications involves proper user permissions, secure secrets management, and runtime protection mechanisms.

'''
# Secure Dockerfile configuration
FROM python:3.9-slim

# Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser

# Set working directory and permissions
WORKDIR /app
COPY --chown=appuser:appuser . .

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

# Switch to non-root user
USER appuser

# Use security options
EXPOSE 5000
CMD ["python", "secure_app.py"]
'''

# Secure application configuration
import secrets
from cryptography.fernet import Fernet

class SecureConfig:
    def __init__(self):
        self.key = Fernet.generate_key()
        self.cipher_suite = Fernet(self.key)
        
    def encrypt_secret(self, secret):
        return self.cipher_suite.encrypt(secret.encode())
        
    def decrypt_secret(self, encrypted_secret):
        return self.cipher_suite.decrypt(encrypted_secret).decode()

Real-world Application: ML Model Deployment

This implementation demonstrates deploying a machine learning model using Docker, including data preprocessing, model serving, and API endpoint creation.

from flask import Flask, request, jsonify
import joblib
import numpy as np
from sklearn.preprocessing import StandardScaler

app = Flask(__name__)

# Load model and scaler
model = joblib.load('model.pkl')
scaler = joblib.load('scaler.pkl')

@app.route('/predict', methods=['POST'])
def predict():
    data = request.json['data']
    features = np.array(data).reshape(1, -1)
    scaled_features = scaler.transform(features)
    prediction = model.predict(scaled_features)
    
    return jsonify({
        'prediction': prediction.tolist(),
        'probability': model.predict_proba(scaled_features).tolist()
    })

'''
# ML Model Dockerfile
FROM python:3.9-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY model.pkl scaler.pkl ./
COPY app.py .

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

Results for ML Model Deployment

This slide presents the performance metrics and deployment results for the machine learning model containerization implementation from the previous slide.

# Performance Testing Results
import requests
import time
import statistics

def test_model_performance():
    url = "http://localhost:5000/predict"
    latencies = []
    
    # Test data
    test_data = {"data": [1.2, 0.5, 3.2, 2.1]}
    
    # Perform 1000 requests
    for _ in range(1000):
        start_time = time.time()
        response = requests.post(url, json=test_data)
        latency = (time.time() - start_time) * 1000  # ms
        latencies.append(latency)
    
    return {
        "avg_latency": statistics.mean(latencies),
        "p95_latency": statistics.quantiles(latencies, n=20)[18],
        "success_rate": sum(1 for l in latencies if l < 100) / len(latencies),
        "memory_usage": "256MB",
        "container_cpu": "0.2 cores"
    }

'''
# Example Output:
{
    "avg_latency": 12.5,
    "p95_latency": 18.3,
    "success_rate": 0.995,
    "memory_usage": "256MB",
    "container_cpu": "0.2 cores"
}
'''

Real-world Application: Distributed Task Processing

Implementation of a distributed task processing system using Docker Compose, Redis for message queuing, and Celery for task execution.

from celery import Celery
from flask import Flask
import redis

app = Flask(__name__)
celery = Celery('tasks', broker='redis://redis:6379/0')
redis_client = redis.Redis(host='redis', port=6379, db=0)

@celery.task
def process_data(data):
    # Complex data processing
    result = perform_computation(data)
    redis_client.set(f"result_{data['id']}", str(result))
    return result

@app.route('/process', methods=['POST'])
def submit_task():
    task = process_data.delay(request.json)
    return jsonify({'task_id': task.id})

'''
# docker-compose.yml
version: '3.8'
services:
  web:
    build: .
    ports:
      - "5000:5000"
    depends_on:
      - redis
      - worker
  
  worker:
    build: .
    command: celery -A tasks worker --loglevel=info
    depends_on:
      - redis
  
  redis:
    image: redis:6.2-alpine
'''

Additional Resources

More in MLOps and deployment

  1. Experiment Tracking in Machine Learning with Python11 min

    Experiment tracking is a crucial aspect of machine learning projects, enabling data scientists to organize, compare, and reproduce their work efficiently. It...

  2. Docker Components Explained14 min

    The Docker SDK for Python provides a high-level API to interact with Docker images programmatically. This implementation demonstrates connecting to Docker...

  3. Introduction to Docker and Python Integration6 min

    Docker is a containerization platform that simplifies application deployment and management. This slideshow will cover the basics of integrating Python...

  4. Docker Cheat Sheet for Python Developers7 min

    Docker SDK for Python enables programmatic control of containers through intuitive APIs. This approach provides automation capabilities for container...

  5. Machine Learning Model Lifecycle! Scoping, Data, Modeling, Deployment6 min

    The scoping phase is crucial for defining project goals and requirements. It involves identifying the problem, determining feasibility, and setting success...

  6. Batch vs. Real-time Inference Visual Explanation9 min

    Inference is the process of making predictions using a trained machine learning model. There are two primary types of inference: real-time and batch. This...