Однажды столкнулся с проблемой: работающее приложение в контейнере нужно было срочно перенести на другой сервер, но остановка означала потерю активных сессий пользователей и данных в памяти. Тогда понял, что классические методы миграции через остановку и перезапуск слишком грубы для современных задач. Но что, если процесс можно буквально заморозить в одном месте и разморозить в другом, сохранив все до последнего байта в памяти и открытых сокетов? Именно это делает CRIU в связке с пространствами имён Linux, создавая технологию, которая кажется фантастикой, но работает в production уже много лет.

Анатомия изоляции: как namespace разделяют миры

Пространства имён в ядре Linux представляют механизм виртуализации системных ресурсов на уровне процессов. Каждое namespace создаёт изолированное представление определённого класса ресурсов. Например, внутри PID namespace процессы видят собственную нумерацию, начинающуюся с PID 1, словно они единственные обитатели системы. Network namespace получает полностью изолированный сетевой стек с собственными интерфейсами, таблицами маршрутизации и портами. Mount namespace даёт отдельную иерархию файловой системы.

Всего существует семь основных типов: UTS управляет именем хоста и доменом, IPC изолирует очереди сообщений System V и POSIX семафоры, User позволяет мапить идентификаторы пользователей между пространствами (критично для unprivileged контейнеров), Cgroup виртуализирует представление о контрольных группах, а Time даёт возможность манипулировать системным временем внутри контейнера. Любой процесс в конкретный момент принадлежит ровно одному экземпляру каждого типа namespace.

Ключевая особенность заключается в абсолютной изоляции: изменения внутри одного namespace никак не затрагивают другие. Можно смело выполнить rm -rf / внутри Mount namespace, и процессы в соседнем пространстве даже не заметят катастрофы. Эта архитектура стала фундаментом для Docker, Podman, LXC и всех современных контейнерных технологий.

CRIU: заморозить время процесса

Checkpoint/Restore In Userspace (CRIU) разработан с 2011 года как инструмент для сохранения и восстановления состояния Linux-процессов без модификаций ядра. Технология использует существующие интерфейсы: ptrace для контроля процессов, механизм parasite code injection для извлечения памяти изнутри адресного пространства, а также чтение метаданных из /proc/$PID/. При checkpoint CRIU замораживает дерево процессов (по умолчанию через ptrace(PTRACE_SEIZE)), собирает карты памяти, содержимое регистров, открытые файлы, сокеты, IPC-объекты и все namespace-конфигурации. Внедрённый паразитный код записывает страницы памяти через vmsplice и pipes в файлы-образы формата Protocol Buffers: core-*.img для регистров, mm-*.img для memory maps, ns-*.img для пространств имён.

При restore CRIU воссоздаёт процессы через fork(), восстанавливает иерархию PID (используя интерфейс /proc/sys/kernel/ns_last_pid для получения нужных идентификаторов), переназначает дескрипторы файлов и сокетов, маппит память через mmap и mremap. Процессы возобновляют выполнение с точного места остановки. В марте 2025 года вышла версия CRIU 4.0 с поддержкой CUDA-приложений NVIDIA, что открыло миграцию контейнеров с GPU-вычислениями. Версия 4.1 добавила поддержку архитектур LoongArch64 и RISC-V.

Механика миграции: три стратегии переноса

Существует три подхода к live-миграции контейнеров: cold migration (полная остановка), pre-copy migration (итеративное копирование) и post-copy migration (ленивая загрузка).

Cold migration работает просто: на исходном хосте выполняем дамп командой:

criu dump -t <PID> -D /tmp/checkpoint --tcp-established --shell-job --leave-stopped

Здесь -t указывает корневой процесс, -D задаёт директорию для образов, --tcp-established сохраняет открытые TCP-соединения, --shell-job поддерживает процессы-оболочки, а --leave-stopped оставляет процессы замороженными после checkpoint. Файлы-образы передаются на целевой хост через rsync, scp или общее хранилище NFS. Затем восстанавливаем:

criu restore -D /tmp/checkpoint --shell-job --restore-detached

Опция --restore-detached отделяет CRIU от родительского процесса после запуска контейнера. Недостаток метода: значительное время простоя (downtime), поскольку весь контейнер замороженным на весь период передачи данных.

Pre-copy migration минимизирует downtime через итеративное копирование. Сначала выполняется pre-dump с опцией --track-mem, которая использует механизм soft-dirty bit ядра Linux для отслеживания изменённых страниц памяти:

mkdir /tmp/checkpoint/1
criu dump -t <PID> -D /tmp/checkpoint/1 --leave-running --track-mem --pre-dump

Контейнер продолжает работать, пока образы памяти (обычно самая большая часть) передаются на целевой хост. Затем делается второй pre-dump, который сохраняет только изменённые страницы:

mkdir /tmp/checkpoint/2
criu dump -t <PID> -D /tmp/checkpoint/2 --leave-running --track-mem --pre-dump --prev-images-dir ../1

Можно делать несколько pre-dump итераций для уменьшения объёма финального дампа и сокращения времени заморозки. Финальный dump останавливает контейнер и сохраняет только дельту изменений:

mkdir /tmp/checkpoint/3
criu dump -t <PID> -D /tmp/checkpoint/3 --leave-stopped --prev-images-dir ../2 --tcp-established

На целевом хосте восстановление использует всю цепочку образов:

criu restore -D /tmp/checkpoint/3 --shell-job

Red Hat демонстрировал миграцию контейнера с игровым сервером Xonotic между дата-центрами в Страсбурге и Пекине, где pre-dump передавал 351 МБ памяти, а финальный dump всего 20 МБ, сокращая downtime до 0.15 секунды.

Post-copy migration (lazy migration) обеспечивает минимальный downtime за счёт отложенной загрузки памяти. В этом режиме на целевой хост передаётся только минимальное состояние для запуска приложения, а страницы памяти остаются на исходном узле. Технология использует механизм userfaultfd, появившийся в Linux 4.11 в так называемом non-cooperative mode. На исходном хосте запускаем:

criu dump -t <PID> -D /tmp/checkpoint --lazy-pages --address <source_ip> --port 27

Опция --lazy-pages заставляет dump не записывать страницы памяти в файлы, а подготовить page server для обслуживания запросов по сети. На целевом хосте сначала запускается lazy-pages daemon:

criu lazy-pages --page-server --address <source_ip> --port 27 -D /tmp/checkpoint

Затем restore с lazy-режимом:

criu restore -D /tmp/checkpoint --lazy-pages

Когда восстановленный процесс обращается к отсутствующей странице памяти, возникает page fault, daemon получает уведомление через userfaultfd и запрашивает страницу с исходного хоста, инжектируя её в адресное пространство процесса. Невостребованные страницы загружаются в фоновом режиме. Исследования показывают, что post-copy migration не зависит от объёма используемой памяти в отличие от pre-copy, где время миграции растёт пропорционально memory intensity приложений.

Hybrid migration комбинирует pre-copy и post-copy: сначала итеративно передаются основные страницы через pre-dump, затем финальный checkpoint с --lazy-pages для оставшихся изменений. Количество страниц, передаваемых ленивым способом в hybrid режиме, обычно меньше чем в чистом post-copy, поскольку передаются только dirty pages. Red Hat демонстрировал комбинированный режим с downtime менее 100 миллисекунд для контейнера размером 350+ МБ.

Параметры dump и restore: точная настройка миграции

Команда criu dump обладает богатым арсеналом опций. Базовые параметры:

-t, --tree <PID>           # Корневой процесс дерева
-D, --images-dir <путь>    # Директория для образов
-W, --work-dir <путь>      # Рабочая директория для временных файлов
-v <уровень>               # Детализация логов (например, -v4)
-o, --log-file <файл>      # Файл для записи логов

Управление состоянием после checkpoint:

-R, --leave-running        # Не убивать процессы после дампа
-s, --leave-stopped        # Оставить процессы остановленными

Сетевые опции критичны для миграции с активными соединениями:

--tcp-established          # Сохранить установленные TCP-соединения
--tcp-close                # Закрыть TCP-соединения (не дампить)
--skip-in-flight          # Пропустить TCP в процессе установления

Для работы с namespace и внешними ресурсами:

--external <тип>[параметры]:name     # Указать внешние ресурсы
# Примеры:
--external mnt[/путь]:name           # Внешний bind-монт
--external file[mnt_id:inode]        # Открытый файл
--external tty[rdev:dev]             # Терминал
--external pid[inode]:name           # Внешний PID namespace
--external dev[major/minor]:name     # Устройство

Управление cgroup:

--manage-cgroups                     # Сохранить конфигурацию cgroup
--freeze-cgroup                      # Использовать cgroup freezer вместо ptrace
--cgroup-props <yaml>                # YAML-описание свойств cgroup
--cgroup-dump-controller <имя>      # Дампить только указанный контроллер

Параметры для итеративной миграции:

--track-mem                          # Отслеживать изменения памяти
--pre-dump                          # Выполнить предварительный дамп
--prev-images-dir <путь>            # Путь к предыдущим образам (относительный)

Для post-copy миграции:

--lazy-pages                        # Включить lazy-режим
--address <IP>                      # IP-адрес page server
--port <порт>                       # Порт для page server
--page-server                       # Запустить page server

Команда criu restore использует свой набор параметров:

-d, --restore-detached              # Отделить CRIU после старта
-s, --leave-stopped                 # Не возобновлять процессы
-S, --restore-sibling               # Восстановить как собрата CRIU
-r, --root <путь>                   # Сменить rootfs перед восстановлением

Присоединение к существующим namespace:

-J, --join-ns NS:{PID|NS_FILE}[,...]
# Поддерживаемые типы: ipc, net, time, user, uts
# Пример:
--join-ns net:1234                  # Присоединить к network namespace процесса 1234

Для оптимизации передачи данных используется page server с автодедупликацией через опцию --auto-dedup, которая автоматически удаляет дублирующиеся страницы путём пробивания дыр в родительских образах:

criu page-server --port 27 -D /tmp/checkpoint --auto-dedup

Глубокая интеграция с namespace: сердце технологии

CRIU сохраняет полное состояние всех типов namespace. При checkpoint утилита читает конфигурации из /proc/$PID/ns/ и записывает метаданные в ns-*.img. Для Network namespace сохраняются все интерфейсы (включая veth-пары, мосты), IP-адреса, таблицы маршрутизации, правила iptables и состояние открытых сокетов. UTS namespace фиксирует hostname и domain name. IPC namespace сохраняет очереди сообщений System V, POSIX message queues, семафоры и разделяемую память. Mount namespace записывает всю иерархию монтирования с типами файловых систем и флагами.

При restore CRIU пересоздаёт эти пространства через системные вызовы unshare (создание нового namespace) и setns (вход в существующий). Для сетевого namespace восстанавливаются все интерфейсы командами через netlink, поднимаются мосты, назначаются IP-адреса. Важно обеспечить доступность IP-адресов на целевом хосте, для чего используются либо floating IP, либо overlay-сети, либо действия через action scripts.

Особенность PID namespace: прямое присоединение через --join-ns не поддерживается из-за ограничений ядра. Вместо этого при dump можно пометить PID-пространство как внешнее через --external pid[inode]:name, а при restore использовать --inherit-fd для восстановления в уже существующем PID namespace.

User namespace критичен для unprivileged контейнеров. CRIU сохраняет карты UID/GID mapping из /proc/$PID/uid_map и /proc/$PID/gid_map, восстанавливая их через запись в соответствующие файлы нового namespace. Cgroup namespace виртуализирует представление процесса о своём положении в иерархии контрольных групп, что важно для корректной работы systemd внутри контейнеров.

Интеграция с экосистемой: Docker, Podman, runC

Docker поддерживает checkpoint/restore с версии 1.13 (2017 год) в экспериментальном режиме. Требуется активация флага в daemon.json и установка CRIU ≥3.12. Команды:

docker checkpoint create <контейнер> <имя_checkpoint>
docker start --checkpoint <имя_checkpoint> <контейнер>

Для миграции между хостами нужен ручной экспорт (файлы лежат в /var/lib/docker/containers/<ID>/checkpoints/). Контейнер должен запускаться с расширенными capabilities:

docker run --cap-add SYS_PTRACE --cap-add SYS_ADMIN \
           --security-opt seccomp=unconfined <образ>

Podman предлагает более зрелую реализацию с версии 0.10.1 (октябрь 2018). С версии 1.4.0 доступен прямой экспорт:

podman container checkpoint -l --export=/tmp/chkpt.tar.gz
podman container restore --import=/tmp/chkpt.tar.gz --name <новое_имя>

Опция --name задаёт имя для восстановленного контейнера. Podman также умеет создавать OCI-образы из checkpoint через --create-image и публиковать их в registry. Требуется CRIU ≥3.12 (для SELinux-поддержки ≥3.13).

runC, будучи низкоуровневым рантаймом, даёт прямой доступ к функциям CRIU. Команды поддерживают pre-copy через флаг --pre-dump и post-copy через --lazy-pages:

runc checkpoint --pre-dump --image-path parent <контейнер>
runc checkpoint --image-path image --parent-path ../parent <контейнер>
runc restore --image-path image <контейнер>

LXC использует обёртку lxc-checkpoint:

lxc-checkpoint -n <имя> -D /tmp/checkpoint           # дамп
lxc-checkpoint -r -n <имя> -D /tmp/checkpoint       # восстановление

Kubernetes пока не имеет нативной поддержки, хотя работа ведётся через KEP (Kubernetes Enhancement Proposal). Существуют сторонние операторы для миграции подов с использованием CRIU.

Ограничения и подводные камни реального мира

Первая проблема: требования к окружению. Версии загруженных библиотек должны совпадать между исходным и целевым хостом, поскольку процесс ожидает функции по тем же адресам. Ядро Linux должно быть собрано с CONFIG_CHECKPOINT_RESTORE, CONFIG_NAMESPACES, CONFIG_PID_NS, CONFIG_NET_NS и другими флагами. Большинство современных дистрибутивов включают эти опции, но embedded-системы могут не поддерживать.

Сетевые соединения остаются уязвимым местом. Хотя --tcp-established сохраняет TCP-сокеты, задержки миграции могут привести к тайм-аутам и разрыву соединений со стороны удалённых клиентов. Приложения должны корректно обрабатывать переподключения. UDP-трафик восстанавливается надёжнее благодаря stateless-природе протокола.

Файловая система требует идентичности. Все файлы, доступные процессам при миграции, должны быть доступны на обоих узлах через shared filesystem (NFS, GlusterFS, CEPH) или предварительную синхронизацию через rsync. Открытые файлы на удалённых ФС могут не восстановиться. Deleted files (файлы, удалённые но всё ещё открытые) представляют особую сложность.

GPU-состояние до недавнего времени не поддерживалось. Проект CRIUgpu интегрировал утилиту cuda-checkpoint от NVIDIA с CRIU для прозрачного checkpoint контейнеров с GPU, что критично для машинного обучения где модели загружаются в GPU-память на часы тренировки.

Права доступа: CRIU обычно требует root и capabilities SYS_PTRACE, SYS_ADMIN. С ядром Linux 5.11+ появилась возможность unprivileged CRIU через capability CAP_CHECKPOINT_RESTORE, но с ограничениями на некоторые типы namespace.

CRIU не поддерживает dump non-linear memory mappings (нелинейных отображений памяти), что может вызывать проблемы с некоторыми специфичными приложениями.

Производительность и оптимизация: практический опыт

Исследования на Raspberry Pi 3 с CRIU 3.10 показали, что для контейнеров с интенсивным использованием памяти pre-copy migration демонстрирует линейный рост времени миграции, тогда как post-copy migration сохраняет стабильное время независимо от объёма. Hybrid подход с pre-copy и post-copy обеспечивает минимальный downtime, поскольку количество faulted pages (страниц, загружаемых по требованию) меньше чем в чистом post-copy.

Механизм image cache и image proxy для disk-less migration может использоваться с опцией --auto-dedup на page server для автоматической дедупликации при итеративных дампах. Дедупликация работает путём пробивания дыр в файлах через fallocate() с флагом FALLOC_FL_PUNCH_HOLE, эффективно освобождая дисковое пространство без изменения pagemap.

Для больших контейнеров рекомендуется использовать tmpfs для хранения образов, что ускоряет запись/чтение. При restore с образами в tmpfs (то есть в RAM) следует использовать --auto-dedup, чтобы использование RAM не росло по мере восстановления.

Технология CRIU в связке с пространствами имён Linux превратила live-миграцию контейнеров из теоретической концепции в работающий инструмент production-систем. Три стратегии миграции (cold, pre-copy, post-copy) дают гибкость выбора между простотой, минимизацией downtime и оптимизацией сетевого трафика. Интеграция с Docker, Podman и runC делает технологию доступной, хотя ограничения по совместимости окружения и сетевым соединениям требуют внимательного планирования. Для stateful-приложений, научных вычислений и сценариев с дорогой инициализацией CRIU предоставляет значительные преимущества, превращая миграцию контейнеров в рутинную операцию современных инфраструктур.