Миграция с виртуальных машин на контейнеры (VM → Docker/K8s)
Перенести приложение с VM на контейнеры — задача, с которой рано или поздно сталкивается каждая команда. Мы в своей практике видели, как legacy-приложения, работавшие годами на выделенных серверах, ломались в контейнерах из-за неочевидных зависимостей. Наши инженеры помогли десяткам клиентов сократить время деплоя в 3 раза и исключить проблемы с окружением. Ниже — пошаговая методология, проверенная на проектах с 50+ микросервисами. Сравнение VM и контейнеров: контейнеры стартуют за 1-2 секунды против 30-60 секунд у VM, а TCO снижается на 30-50%.
Почему контейнеризация выгоднее VM?
VM требует установки ОС, настройки репозиториев, обновлений ядра — вручную или через Ansible. Контейнер собирается один раз и запускается где угодно. По нашим данным, разработка на контейнерах ускоряет цикл CI/CD на 40%. В Kubernetes вы получаете autoscaling (HPA/VPA), self-healing и rolling updates «из коробки». Плюс экономия на лицензиях ОС — до 50% затрат на инфраструктуру.
С чего начинается инвентаризация VM?
Перед написанием первого Dockerfile нужно понять что именно запущено на VM. Часто оказывается, что на одной «прод-машине» живут: nginx как reverse proxy, само приложение на Node.js, PostgreSQL, Redis, cron-задача для бэкапов и локальный почтовый агент Postfix. Это семь отдельных контейнеров, если делать правильно.
# Что запущено на VM
systemctl list-units --type=service --state=running
ps auxf | grep -v grep
# Какие порты слушают
ss -tlnp
# Какие крон-задачи
crontab -l
ls /etc/cron.d/ /etc/cron.daily/
# Зависимости приложения (пример для Node.js)
cat package.json | jq '.dependencies | keys[]'
node --version; npm --versionРезультат инвентаризации — список всего что нужно перенести, с пониманием зависимостей между компонентами.
Dockerfile: типичные грабли при контейнеризации
Логирование. Приложение пишет в /var/log/app/access.log. В контейнере логи должны идти в stdout/stderr — иначе их не подхватит Docker daemon, Fluentd или Loki. Вариант: добавить симлинк ln -sf /dev/stdout /var/log/app/access.log в Dockerfile. Вариант лучше — исправить конфигурацию логирования в самом приложении.
Конфигурация через переменные окружения. Хардкоженный путь /etc/app/config.yaml нужно заменить на ENV-переменные или volume mount. Twelve-Factor App — не просто модная концепция, а требование для нормальной работы в K8s.
Пользователи и права. Приложение запускается от root на VM. В контейнере это security risk. Нужно добавить непривилегированного пользователя:
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:20-alpine
RUN addgroup -g 1001 appgroup && adduser -u 1001 -G appgroup -s /bin/sh -D appuser
WORKDIR /app
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --chown=appuser:appgroup . .
USER appuser
EXPOSE 3000
CMD ["node", "server.js"]
Init-система. Приложение использует supervisord или запускает дочерние процессы напрямую. В контейнере PID 1 должен корректно обрабатывать SIGTERM и перенаправлять сигналы дочерним процессам. Если приложение не умеет этого — нужен tini как init:
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "server.js"]Stateful данные. Приложение пишет загруженные файлы в /var/uploads/. В контейнере это нужно вынести в PersistentVolume (K8s) или монтировать через -v (Docker). Данные не должны жить внутри контейнера.
Как выглядит типовой манифест для Kubernetes?
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-backend
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
containers:
- name: backend
image: registry.internal/app-backend:1.2.3
ports:
- containerPort: 3000
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: database-url
- name: REDIS_URL
value: "redis://redis-service:6379"
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
volumeMounts:
- name: uploads
mountPath: /app/uploads
volumes:
- name: uploads
persistentVolumeClaim:
claimName: app-uploads-pvc
Отсутствие resources — одна из самых частых ошибок при первом деплое в K8s. Без limits pod может сожрать всю память на ноде, без requests scheduler не может корректно распределить поды.
Cron-задачи → CronJob
Системный cron переносится в K8s CronJob:
apiVersion: batch/v1
kind: CronJob
metadata:
name: db-backup
spec:
schedule: "0 2 * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: registry.internal/db-backup:latest
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: database-url
concurrencyPolicy: Forbid предотвращает запуск нового job если предыдущий ещё выполняется.
Как обеспечить надёжность при миграции?
Мы гарантируем, что после перехода все ключевые метрики SLO будут под контролем. Настройка мониторинга Prometheus+Grafana входит в стандартный пакет. Все конфиги версионируются в Git — это устраняет риск потери изменений. Наша команда — сертифицированные Kubernetes администраторы и облачные инженеры с 10+ лет опыта. Мы уже провели более 30 успешных миграций для fintech, e-commerce и SaaS-продуктов.
| Критерий | VM | Контейнеры (Docker/K8s) |
|---|---|---|
| Время запуска | 30-60 сек | 1-2 сек |
| Использование ресурсов | Тяжелая ОС, изоляция через гипервизор | Легковесный процесс, разделяемое ядро |
| Стоимость (TCO) | Выше из-за лицензий и избыточного резервирования | Ниже на 30-50% за счёт compact placement |
Что входит в нашу работу по миграции
- Аудит VM-инфраструктуры и схема зависимостей
- Dockerfile для каждого микросервиса с multi-stage build
- Helm-чарты для деплоя в K8s
- Настройка CI/CD (GitLab CI / GitHub Actions)
- Мониторинг и алертинг (Prometheus, Loki, Grafana)
- Документация по эксплуатации и runbook
- Передача знаний команде заказчика (1-2 дня обучения)
Какую стратегию миграции выбрать?
Правильная последовательность для production-приложения:
- Контейнеризация — написать Dockerfile, проверить локально
- CI для образа — сборка и push в registry при каждом merge в main
- Деплой в staging на K8s, прогон тестов
- Параллельный запуск — старая VM и новые поды работают одновременно, трафик на VM
- Переключение трафика — Ingress или DNS переключается на K8s Service
- Наблюдение — неделя в production, сравнение метрик с baseline
- Вывод VM — после подтверждения стабильности
Никогда не переключайте трафик и одновременно не выключайте VM. Это кажется очевидным, но в стрессе перед дедлайном об этом забывают.
Готовы начать миграцию? Получите консультацию нашего инженера.
Сроки
| Сложность приложения | Срок контейнеризации | Срок миграции в K8s |
|---|---|---|
| Простое (1 процесс, без state) | 1–2 дня | 2–3 дня |
| Среднее (2–3 компонента, есть state) | 3–5 дней | 1 неделя |
| Сложное (5+ компонентов, legacy зависимости) | 1–2 недели | 2–4 недели |
Первоначальный аудит VM-инфраструктуры и оценка — 1–2 дня. Итоговые сроки определяются после него. Закажите аудит VM-инфраструктуры — мы подготовим план миграции за 1–2 дня.







