Міграція з віртуальних машин на контейнери (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 дні.







