Архитектура микросервиса и план развертывания
Микросервис маршрутизации пациентов принимает входящие запросы, определяет по набору правил целевой сервис и перенаправляет вызов. Это критичный компонент в медицинских информационных системах, где задержка или ошибка маршрутизации напрямую влияет на скорость оказания помощи. Сервис состоит из трёх слоёв: REST API на входе, движок правил маршрутизации и хранилище конфигураций.
Полный цикл развертывания от Dev до Prod укладывается в шесть этапов. Сначала приложение упаковывается в Docker-образ с многоэтапной сборкой. Затем создаётся Helm-чарт с разделением конфигураций по окружениям. Чарт деплоится в Kubernetes-кластер, где Ingress-контроллер и балансировщик открывают доступ к сервису. На четвёртом этапе настраивается горизонтальное автомасштабирование и распределение подов по узлам. Финальный шаг - экспорт метрик в Prometheus и настройка алертов для контроля корректности маршрутизации. Все примеры проверены на Kubernetes 1.31 и совместимы с облачными и bare-metal кластерами.
Если вы только начинаете переход от монолитной архитектуры к микросервисам, изучите наш план миграции монолита на микросервисы. Для тех, кто уже работает с контейнерами, но ещё не переносил приложения из Docker Compose, подготовлено руководство по миграции в Kubernetes.
Упаковка микросервиса в Docker-образ
Для примера возьмём микросервис на Go - он даёт минимальный размер образа и быстрый старт. Сервис слушает порт 8080, имеет эндпоинты /route для маршрутизации и /health для проверки готовности. Контейнеризация начинается с Dockerfile, который определяет среду выполнения и способ сборки.
Оптимизация Dockerfile для production
Многоэтапная сборка сокращает итоговый образ в 8–12 раз. На первом этапе компилируем бинарник в golang:alpine, на втором - копируем его в минимальный scratch-образ. Такой подход исключает компилятор, исходный код и системные утилиты из production-контейнера.
# Этап сборки
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /patient-router .
# Production-образ
FROM scratch
COPY --from=builder /patient-router /patient-router
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
USER 1001
EXPOSE 8080
HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
CMD ["/patient-router", "-healthcheck"]
ENTRYPOINT ["/patient-router"]
Сравнение размеров: образ на golang:alpine занимает 380 МБ, на ubuntu:22.04 - 820 МБ, итоговый scratch-образ - 14 МБ. Разница критична для скорости загрузки в кластер и занимаемого дискового пространства на узлах.
Файл .dockerignore исключает из контекста сборки тесты, документацию и Git-историю:
*.md
.git
.gitignore
*_test.go
docs/
helm/
Запуск от непривилегированного пользователя (UID 1001) блокирует повышение привилегий внутри контейнера. Директива HEALTHCHECK даёт Kubernetes информацию о готовности процесса - без неё kubelet не сможет определить, завис ли сервис после старта.
Сборка и тегирование образа:
docker build -t patient-router:1.0.0 .
docker tag patient-router:1.0.0 registry.example.com/patient-router:1.0.0
docker push registry.example.com/patient-router:1.0.0
Для production-окружения обязательно используйте собственный registry. Если вы разворачиваете инфраструктуру в облаке, Timeweb Cloud предоставляет управляемый Kubernetes с интегрированным container registry и балансировщиком нагрузки.
Создание Helm-чарта для микросервиса
Helm-чарт упаковывает все Kubernetes-манифесты в шаблоны с параметризацией. Структура чарта для patient-router:
patient-router/
├── Chart.yaml
├── values.yaml
├── values-dev.yaml
├── values-prod.yaml
└── templates/
├── deployment.yaml
├── service.yaml
├── ingress.yaml
├── hpa.yaml
└── servicemonitor.yaml
Файл Chart.yaml содержит метаданные и версию чарта. В values.yaml вынесены параметры по умолчанию: количество реплик, образ, лимиты ресурсов, хост для Ingress. Шаблон deployment.yaml использует эти значения через синтаксис {{ .Values.replicaCount }}.
Пример параметризованного Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: patient-router
template:
metadata:
labels:
app: patient-router
spec:
containers:
- name: patient-router
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: 8080
resources:
requests:
cpu: {{ .Values.resources.requests.cpu }}
memory: {{ .Values.resources.requests.memory }}
limits:
cpu: {{ .Values.resources.limits.cpu }}
memory: {{ .Values.resources.limits.memory }}
Разделение конфигураций для Dev и Prod окружений
Вместо дублирования чарта используйте несколько values-файлов. Базовый values.yaml содержит общие настройки, values-dev.yaml и values-prod.yaml переопределяют специфичные для окружения параметры.
Dev-окружение - одна реплика, минимальные ресурсы, внутренний Ingress-хост:
# values-dev.yaml
replicaCount: 1
image:
tag: latest
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
ingress:
host: patient-router-dev.internal
Prod-окружение - три реплики, гарантированные ресурсы, публичный хост с TLS:
# values-prod.yaml
replicaCount: 3
image:
tag: 1.0.0
resources:
requests:
cpu: 500m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
ingress:
host: patient-router.example.com
tls:
enabled: true
secretName: patient-router-tls
Установка в разные окружения одной командой:
# Dev
helm install patient-router ./patient-router -f values-dev.yaml -n dev
# Prod
helm install patient-router ./patient-router -f values-prod.yaml -n production
Такой подход исключает рассинхронизацию шаблонов между окружениями - структура Deployment, Service и Ingress всегда идентична, меняются только значения параметров.
Развертывание в Kubernetes и настройка балансировщика
После установки чарта проверяем состояние подов и сервисов:
kubectl get pods -n production -l app=patient-router
kubectl get svc -n production
kubectl get ingress -n production
Ingress-контроллер принимает внешний трафик и направляет его к Service patient-router. Манифест Ingress с правилами маршрутизации:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Release.Name }}
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
tls:
- hosts:
- {{ .Values.ingress.host }}
secretName: {{ .Values.ingress.tls.secretName }}
rules:
- host: {{ .Values.ingress.host }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ .Release.Name }}
port:
number: 8080
Для bare-metal кластеров без облачного балансировщика используйте MetalLB. Он назначает внешний IP из пула адресов для Service типа LoadBalancer, к которому привязан Ingress-контроллер. Настройка пула адресов в MetalLB:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: production-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.200-192.168.1.210
Подробный разбор настройки Ingress-контроллеров и Service Mesh для production-сред читайте в нашем руководстве по оркестрации динамических приложений.
Проверка работоспособности и отладка
Три команды решают 90% проблем после деплоя. Первая - логи конкретного пода:
kubectl logs -n production deployment/patient-router --tail=50
Вторая - детальное описание пода с событиями:
kubectl describe pod -n production -l app=patient-router
Третья - проверка эндпоинтов Service (должны совпадать с IP подов):
kubectl get endpoints -n production patient-router
Типичные ошибки при деплое:
- ImagePullBackOff - неверный тег образа или отсутствие доступа к registry. Проверьте
imagePullSecretsв Deployment и наличие образа в registry. - CrashLoopBackOff - приложение падает после старта. Изучите логи пода, чаще всего причина в неверных переменных окружения или недоступности внешних зависимостей.
- ErrImagePull - неверный URL registry. Проверьте поле
image.repositoryв values-файле.
Горизонтальное масштабирование и отказоустойчивость
Horizontal Pod Autoscaler автоматически изменяет количество реплик на основе метрик. Базовая настройка по CPU и памяти:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: patient-router
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: patient-router
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
behavior:
scaleDown:
stabilizationWindowSeconds: 300
Параметр stabilizationWindowSeconds: 300 предотвращает резкое уменьшение реплик при кратковременном спаде нагрузки. HPA опрашивает метрики каждые 15 секунд, но решение о масштабировании принимает на основе среднего значения за окно стабилизации.
PodDisruptionBudget защищает от одновременного отключения всех подов при обслуживании узлов:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: patient-router-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: patient-router
При minAvailable: 2 и трёх репликах кластер никогда не оставит сервис без ответа - даже во время планового drain'а узла минимум два пода продолжат обработку запросов.
Распределение подов по узлам кластера
PodAntiAffinity гарантирует, что реплики не окажутся на одном физическом узле. При отказе узла остальные реплики продолжают работу:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: patient-router
topologyKey: kubernetes.io/hostname
Правило requiredDuringSchedulingIgnoredDuringExecution жёсткое - планировщик не разместит под на узле, где уже есть реплика с такой же меткой. Для кластеров из трёх узлов это даёт равномерное распределение: по одному поду на узел.
Мониторинг и алертинг для микросервиса
Микросервис экспортирует метрики через эндпоинт /metrics в формате Prometheus. Ключевые метрики для маршрутизации пациентов:
# HELP patient_routing_latency_seconds Время выполнения маршрутизации
# TYPE patient_routing_latency_seconds histogram
patient_routing_latency_seconds_bucket{le="0.01"} 1523
patient_routing_latency_seconds_bucket{le="0.05"} 3842
patient_routing_latency_seconds_bucket{le="0.1"} 4102
patient_routing_latency_seconds_bucket{le="+Inf"} 4120
patient_routing_latency_seconds_sum 184.5
patient_routing_latency_seconds_count 4120
# HELP patient_routing_errors_total Общее количество ошибок маршрутизации
# TYPE patient_routing_errors_total counter
patient_routing_errors_total{reason="rule_not_found"} 12
patient_routing_errors_total{reason="timeout"} 3
# HELP patient_routing_requests_total Общее количество запросов
# TYPE patient_routing_requests_total counter
patient_routing_requests_total{status="200"} 4089
patient_routing_requests_total{status="404"} 12
patient_routing_requests_total{status="500"} 19
ServiceMonitor сообщает Prometheus, с каких подов собирать метрики:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: patient-router
spec:
selector:
matchLabels:
app: patient-router
endpoints:
- port: http
path: /metrics
interval: 15s
Для визуализации используйте Grafana с дашбордом, который показывает: latency по перцентилям (p50, p95, p99), rate ошибок в минуту, количество реплик и загрузку CPU. Готовый JSON дашборда доступен в репозитории чарта.
Примеры правил алертинга для маршрутизации
PrometheusRule определяет условия, при которых отправляется уведомление. Два критичных алерта для сервиса маршрутизации пациентов:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: patient-router-alerts
spec:
groups:
- name: patient-router
rules:
- alert: PatientRoutingLatencyHigh
expr: |
histogram_quantile(0.95,
rate(patient_routing_latency_seconds_bucket[5m])
) > 0.2
for: 5m
labels:
severity: warning
annotations:
summary: "Высокая задержка маршрутизации пациентов"
description: "95-й перцентиль задержки превышает 200 мс в течение 5 минут. Текущее значение: {{ $value }}с"
- alert: PatientRoutingErrorRateHigh
expr: |
rate(patient_routing_errors_total[5m]) > 0.05
for: 3m
labels:
severity: critical
annotations:
summary: "Высокий уровень ошибок маршрутизации"
description: "Количество ошибок превышает 0.05 в секунду. Проверьте доступность downstream-сервисов и правила маршрутизации."
Пороговые значения подобраны для медицинской системы, где задержка маршрутизации выше 200 мс неприемлема. Алерт PatientRoutingErrorRateHigh срабатывает за три минуты - этого достаточно, чтобы отфильтровать кратковременные всплески, но не пропустить реальную деградацию. Настройте интеграцию Alertmanager с Telegram, Slack или PagerDuty для мгновенного оповещения дежурной смены.
Полный цикл observability - от сбора логов до настройки дашбордов - разобран в нашем руководстве по мониторингу и логированию Docker в production. Для комплексного взгляда на DevOps-практики 2026 года обратитесь к обзору актуальных инструментов и конфигураций.
Развёрнутый кластер с мониторингом и алертингом готов к приёму production-трафика. Метрики подтверждают корректность маршрутизации, HPA держит нагрузку, PodDisruptionBudget страхует от отказов инфраструктуры. При росте объёмов достаточно увеличить maxReplicas в HPA и добавить узлы в кластер - архитектура масштабируется линейно.