DevOps для C++ — воспроизводимое развёртывание
C++ программа на проде ломается по причинам, которые невозможно воспроизвести локально: чужая версия glibc, отсутствующий libssl, другая локаль, неподходящая модель CPU. Контейнеры решают эту боль — приложение и его зависимости упакованы вместе, и хост-система влияет только через ядро.
Карта темы
- Контейнеры vs виртуальные машины — namespaces, cgroups, разделение ядра.
- Docker — образы, слои, registry, многоэтапные сборки.
- Развёртывание C++ — static vs dynamic linking, оптимизация размера образа.
- Оркестрация — Kubernetes на коротком обзоре.
Контейнеры vs виртуальные машины
Виртуальная машина эмулирует полное «железо» (или паравиртуализует его). Внутри запускается своё ядро ОС, свой userspace, свой образ диска. Сильная изоляция, тяжёлый старт (секунды), большой размер (гигабайты).
Контейнер — изолированный процесс на ядре хоста. Изоляцию даёт ядро Linux через:
- Namespaces —
pid,net,mnt,uts,ipc,user,cgroup— каждый видит свой набор PID, сетевых интерфейсов, монтирований. - Cgroups — ограничивают CPU, RAM, IO, сеть.
- Seccomp / capabilities / AppArmor — ограничивают системные вызовы и привилегии.
Контейнер делит ядро с хостом → Linux-контейнер на Windows запускается через виртуальную машину (WSL2 или Docker Desktop VM). Запуск контейнера — миллисекунды, размер — от 5 МБ (Alpine + статический бинарник) до сотен МБ.
⚠️ Контейнер слабее изолирован, чем VM — побег из контейнера через дыру в ядре даёт root на хосте. Для multi-tenant code используют либо VM, либо microVM (Firecracker), либо gVisor.
Docker
Образ — иммутабельный набор слоёв (UnionFS). Каждая команда Dockerfile создаёт слой. Слои кешируются и переиспользуются между образами.
Контейнер — запущенный экземпляр образа + writable верхний слой.
Registry — хранилище образов: Docker Hub, GitHub Container Registry, ECR. Тег latest не версия — это просто человекочитаемая ссылка, которая молча меняется.
Многоэтапные сборки (multi-stage)
Для C++ — критичная техника. Билд-стейдж содержит компилятор и зависимости (700 МБ), runtime-стейдж — только бинарник и runtime библиотеки (10–50 МБ).
FROM gcc:13 AS build
WORKDIR /src
COPY . .
RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -j
FROM debian:bookworm-slim
COPY --from=build /src/build/myapp /usr/local/bin/
USER 1000:1000
ENTRYPOINT ["myapp"]
⚠️ Не запускай контейнеры от root. Установи USER — иначе побег из контейнера = root на хосте.
Развёртывание C++
Static linking — все зависимости в одном бинарнике (-static). Образ становится крошечным (Alpine + статический бинарник = 10 МБ), но glibc плохо линкуется статически — используй musl или динамический.
Dynamic linking — стандарт. Образ нужен с подходящим glibc (или musl). ldd binary показывает зависимости; убедись, что все *.so доступны в финальном образе.
Размер образа: используй debian:slim, ubuntu:24.04 (минималистичные), или distroless (только runtime, без shell). Alpine — самый маленький, но musl ≠ glibc; некоторые библиотеки ведут себя иначе.
Оркестрация
Kubernetes управляет десятками/тысячами контейнеров: распределяет по нодам, рестартует упавшие, балансирует трафик. Для C++ сервиса значимы:
- Resource limits —
cpu: 1000m,memory: 512Mi— соответствуют cgroup ограничениям. - Liveness / readiness probes — Kubernetes рестартует контейнер при провале liveness; не направляет трафик при провале readiness.
- HPA (Horizontal Pod Autoscaler) — масштабирует по CPU/memory/custom metric.
Для одного-двух сервисов Kubernetes избыточен — docker compose или systemd-units часто достаточно.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
| Запуск контейнера от root | Побег = root на хосте |
Использование тега latest в production | Несовместимая версия после очередного docker pull |
Секреты в ENV Dockerfile | Видны в docker inspect и истории слоёв |
| Однослойный билд с тулчейном | Образ 1+ ГБ вместо 50 МБ |
Линковка glibc статически без musl | Странные баги в getaddrinfo и dlopen |
Игнор liveness probe | Зависший процесс не перезапустится |
COPY . /app без .dockerignore | В образ попадают .git, build/, секреты |
Сборка под другой архитектурой без --platform | «Работает у меня» — на ARM сервере не работает |
Значение для собеседований
DevOps — частая часть собеседования на backend C++ позицию. Интервьюер проверяет:
- Понимаешь ли разницу между контейнером и VM на уровне ядра.
- Знаешь ли намёки на namespaces и cgroups (не обязательно наизусть, но направление).
- Видишь ли разницу между статической и динамической линковкой и их влияние на размер образа.
- Не повторяешь ли мифы: «Docker = виртуальная машина», «контейнеры дают полную изоляцию».
Типичный неправильный ответ: «Контейнер — это лёгкая виртуальная машина со своим ядром». Нет — контейнер делит ядро хоста, и в этом его и сила (быстрый старт), и слабость (более слабая изоляция).
Популярные направления:
- Чем контейнер отличается от VM на уровне ядра?
- Что такое namespaces и cgroups? Какие бывают namespaces?
- Зачем нужны multi-stage сборки для C++?
- Почему
latest— плохой тег для production? - Когда стоит Kubernetes, а когда хватит
docker compose?