Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Деплой и мониторинг
deployment

Деплой и мониторинг

Контейнеризация, Kubernetes, логирование, метрики, алертинг.

Деплой и мониторинг

Деплой — это не конец, а начало. В этой теме научимся контейнеризировать, развёртывать и мониторить MCP серверы в продакшене.


#1. Контейнеризация с Docker

#Базовый Dockerfile

# Dockerfile FROM python:3.11-slim # Рабочая директория WORKDIR /app # Установка зависимостей RUN pip install --no-cache-dir poetry==1.7.0 COPY pyproject.toml poetry.lock* ./ RUN poetry config virtualenvs.create false && \ poetry install --only main --no-interaction # Копирование кода COPY src/ ./src/ # Переменные окружения ENV PYTHONUNBUFFERED=1 ENV PYTHONDONTWRITEBYTECODE=1 # Порт для SSE/WebSocket EXPOSE 8000 # Запуск сервера CMD ["python", "-m", "uvicorn", "src.server:app", "--host", "0.0.0.0", "--port", "8000"]

#Multi-stage сборка

# Dockerfile # Stage 1: Build FROM python:3.11-slim as builder WORKDIR /app RUN pip install --no-cache-dir poetry==1.7.0 COPY pyproject.toml poetry.lock* ./ RUN poetry config virtualenvs.create false && \ poetry install --no-interaction COPY src/ ./src/ # Stage 2: Runtime FROM python:3.11-slim as runtime WORKDIR /app # Создание не-root пользователя RUN useradd -m -u 1000 mcpuser # Копирование из builder COPY --from=builder /usr/local/lib/python3.11/site-packages/ /usr/local/lib/python3.11/site-packages/ COPY --from=builder /app/src/ ./src/ # Переключение на не-root пользователя USER mcpuser # Переменные окружения ENV PYTHONUNBUFFERED=1 EXPOSE 8000 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1 CMD ["python", "-m", "uvicorn", "src.server:app", "--host", "0.0.0.0", "--port", "8000"]

#docker-compose для разработки

# docker-compose.yml version: '3.8' services: mcp-server: build: . ports: - "8000:8000" environment: - DATABASE_URL=postgresql://user:pass@db:5432/mcp - REDIS_URL=redis://redis:6379 - LOG_LEVEL=INFO depends_on: - db - redis volumes: - ./src:/app/src # Hot reload для разработки restart: unless-stopped db: image: postgres:15-alpine environment: - POSTGRES_USER=user - POSTGRES_PASSWORD=pass - POSTGRES_DB=mcp volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" redis: image: redis:7-alpine ports: - "6379:6379" volumes: postgres_data:

#2. Kubernetes деплой

#Deployment

# k8s/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: mcp-server labels: app: mcp-server spec: replicas: 3 selector: matchLabels: app: mcp-server template: metadata: labels: app: mcp-server spec: containers: - name: mcp-server image: myregistry/mcp-server:1.0.0 ports: - containerPort: 8000 env: - name: DATABASE_URL valueFrom: secretKeyRef: name: mcp-secrets key: database-url - name: LOG_LEVEL value: "INFO" resources: requests: memory: "256Mi" cpu: "100m" limits: memory: "512Mi" cpu: "500m" livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 8000 initialDelaySeconds: 5 periodSeconds: 5

#Service

# k8s/service.yaml apiVersion: v1 kind: Service metadata: name: mcp-server spec: selector: app: mcp-server ports: - protocol: TCP port: 80 targetPort: 8000 type: ClusterIP

#Ingress

# k8s/ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: mcp-server annotations: nginx.ingress.kubernetes.io/ssl-redirect: "true" cert-manager.io/cluster-issuer: "letsencrypt-prod" spec: tls: - hosts: - mcp.example.com secretName: mcp-tls rules: - host: mcp.example.com http: paths: - path: / pathType: Prefix backend: service: name: mcp-server port: number: 80

#HorizontalPodAutoscaler

# k8s/hpa.yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: mcp-server-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: mcp-server 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

#3. Логирование

#Структурированное логирование

import logging import json import sys from pythonjsonlogger import jsonlogger class CustomJsonFormatter(jsonlogger.JsonFormatter): """Кастомный JSON форматтер для логов.""" def add_fields(self, log_record, record, message_dict): super().add_fields(log_record, record, message_dict) log_record['level'] = record.levelname log_record['logger'] = record.name log_record['timestamp'] = record.created def setup_logging(level: str = "INFO"): """Настройка структурированного логирования.""" logger = logging.getLogger() logger.setLevel(getattr(logging, level.upper())) handler = logging.StreamHandler(sys.stdout) formatter = CustomJsonFormatter( '%(timestamp)s %(level)s %(name)s %(message)s' ) handler.setFormatter(formatter) logger.addHandler(handler) return logger logger = setup_logging() # Использование logger.info( "Tool called", extra={ "tool_name": "search", "user_id": "123", "duration_ms": 45 } )

#Логирование с корреляцией

import logging from contextvars import ContextVar import uuid # Контекстная переменная для trace_id trace_id_var: ContextVar[str] = ContextVar('trace_id', default='') class TraceFilter(logging.Filter): """Фильтр для добавления trace_id в логи.""" def filter(self, record): record.trace_id = trace_id_var.get() return True def setup_trace_logging(): """Настройка логирования с trace_id.""" handler = logging.StreamHandler() handler.addFilter(TraceFilter()) formatter = logging.Formatter( '%(asctime)s [%(trace_id)s] %(levelname)s %(name)s: %(message)s' ) handler.setFormatter(formatter) logger = logging.getLogger() logger.addHandler(handler) logger.setLevel(logging.INFO) def generate_trace_id() -> str: """Генерация trace_id для запроса.""" return str(uuid.uuid4()) # Использование в MCP инструменте @mcp.tool() async def traced_tool(param: str): trace_id = generate_trace_id() token = trace_id_var.set(trace_id) try: logger.info("Tool started", extra={"param": param}) result = await process(param) logger.info("Tool completed") return result finally: trace_id_var.reset(token)

#4. Метрики и мониторинг

#Prometheus метрики

from prometheus_client import Counter, Histogram, Gauge, start_http_server import time # Метрики REQUEST_COUNT = Counter( 'mcp_requests_total', 'Total MCP requests', ['tool', 'status'] ) REQUEST_DURATION = Histogram( 'mcp_request_duration_seconds', 'Request duration', ['tool'], buckets=(0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0) ) ACTIVE_CONNECTIONS = Gauge( 'mcp_active_connections', 'Active connections' ) ERROR_COUNT = Counter( 'mcp_errors_total', 'Total errors', ['tool', 'error_type'] ) def metrics_middleware(func): """Декоратор для сбора метрик.""" @wraps(func) async def wrapper(*args, **kwargs): tool_name = func.__name__ start_time = time.time() ACTIVE_CONNECTIONS.inc() try: result = await func(*args, **kwargs) REQUEST_COUNT.labels(tool=tool_name, status='success').inc() return result except Exception as e: REQUEST_COUNT.labels(tool=tool_name, status='error').inc() ERROR_COUNT.labels(tool=tool_name, error_type=type(e).__name__).inc() raise finally: duration = time.time() - start_time REQUEST_DURATION.labels(tool=tool_name).observe(duration) ACTIVE_CONNECTIONS.dec() return wrapper # Запуск endpoint метрик start_http_server(9090) # /metrics endpoint

#Grafana дашборд

{ "dashboard": { "title": "MCP Server", "panels": [ { "title": "Request Rate", "targets": [{ "expr": "rate(mcp_requests_total[5m])" }] }, { "title": "Error Rate", "targets": [{ "expr": "rate(mcp_errors_total[5m])" }] }, { "title": "P95 Latency", "targets": [{ "expr": "histogram_quantile(0.95, rate(mcp_request_duration_seconds_bucket[5m]))" }] } ] } }

#5. Health Checks

#Endpoints здоровья

from fastapi import FastAPI, HTTPException from datetime import datetime import asyncio app = FastAPI() @app.get("/health") async def health_check(): """Базовая проверка здоровья.""" return { "status": "healthy", "timestamp": datetime.utcnow().isoformat(), "version": "1.0.0" } @app.get("/ready") async def readiness_check(): """Проверка готовности обрабатывать запросы.""" checks = {} # Проверка БД try: async with db_pool.acquire() as conn: await conn.fetchval("SELECT 1") checks["database"] = "ok" except Exception as e: checks["database"] = f"error: {e}" # Проверка Redis try: await redis.ping() checks["redis"] = "ok" except Exception as e: checks["redis"] = f"error: {e}" # Проверка внешнего API try: async with aiohttp.ClientSession() as session: async with session.get("https://api.example.com/health", timeout=5) as resp: resp.raise_for_status() checks["external_api"] = "ok" except Exception as e: checks["external_api"] = f"error: {e}" all_healthy = all(v == "ok" for v in checks.values()) if not all_healthy: raise HTTPException(status_code=503, detail=checks) return {"status": "ready", "checks": checks} @app.get("/live") async def liveness_check(): """Проверка живости (не заблокирован ли процесс).""" return {"status": "alive"}

#6. Алертинг

#Prometheus AlertManager правила

# alertmanager/rules.yml groups: - name: mcp_server rules: - alert: HighErrorRate expr: rate(mcp_errors_total[5m]) > 0.1 for: 5m labels: severity: warning annotations: summary: "High error rate on {{ $labels.instance }}" description: "Error rate is {{ $value }} errors/sec" - alert: HighLatency expr: histogram_quantile(0.95, rate(mcp_request_duration_seconds_bucket[5m])) > 1 for: 10m labels: severity: warning annotations: summary: "High latency on {{ $labels.instance }}" description: "P95 latency is {{ $value }}s" - alert: PodCrashLooping expr: rate(kube_pod_container_status_restarts_total[15m]) > 0 for: 5m labels: severity: critical annotations: summary: "Pod {{ $labels.pod }} is crash looping"

#7. CI/CD пайплайн

#GitLab CI

# .gitlab-ci.yml stages: - test - build - deploy variables: DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA test: stage: test image: python:3.11 script: - pip install poetry - poetry install - poetry run pytest tests/ --cov=src --cov-report=xml - poetry run coverage report --fail-under=80 artifacts: reports: coverage_report: coverage_format: cobertura path: coverage.xml build: stage: build image: docker:24 services: - docker:24-dind script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker build -t $DOCKER_IMAGE . - docker push $DOCKER_IMAGE only: - main deploy-staging: stage: deploy image: bitnami/kubectl script: - kubectl set image deployment/mcp-server mcp-server=$DOCKER_IMAGE -n staging environment: name: staging only: - main deploy-production: stage: deploy image: bitnami/kubectl script: - kubectl set image deployment/mcp-server mcp-server=$DOCKER_IMAGE -n production environment: name: production when: manual only: - main

#Ключевые выводы

КомпонентНазначение
DockerКонтейнеризация для переносимости
KubernetesОркестрация, масштабирование, self-healing
ЛогированиеСтруктурированные логи с trace_id
МетрикиPrometheus для мониторинга производительности
Health checks/health, /ready, /live endpoints
АлертингУведомления о проблемах

Следующая тема: Интеграция с системами — legacy, микросервисы, event-driven архитектура.

Далее: Интеграция с системами