Today

Перенос Docker-контейнера между двумя Linux-хостами

Это перевод оригинальной статьи Migrating a Docker container between two Linux hosts.

Подписывайтесь на телеграм-канал usr_bin, где я публикую много полезного по Linux, в том числе ссылки на статьи в этом блоге.

При смене Docker-хостов мы часто думаем: «Я копирую, перезапускаю — и всё». Но Docker-сервис — это не только образ: есть ещё тома, переменные окружения, порты, иногда база данных… и именно здесь всё ломается, если действовать слишком быстро.

Источник: goldeneagle

В этой статье я придерживаюсь простого и понятного подхода: сначала инвентаризация, затем перенос данных, после этого повторное развёртывание (в идеале через Docker Compose) на новом хосте. Цель — вернуть всё в рабочее состояние так же, как было раньше, чтобы ни один сервис не запустился «пустым» и ни одна конфигурация не потерялась в процессе.

Вот чему я научился на собственном опыте: перед переносом сначала протестируйте процедуру на некритичном сервисе и оставьте старый сервер работающим на несколько часов или даже дней, чтобы убедиться, что все сервисы работают корректно. Документируйте всё, что вы делаете; это поможет, если вы допустите ошибку, а также может пригодиться позже. И перед переносом сделайте резервную копию. Излишней осторожности здесь не бывает.

Список всех контейнеров

Первый шаг — вывести список всех контейнеров, чтобы увидеть как активные, так и забытые сервисы. Обычная команда docker ps скрывает остановленные контейнеры, а именно там обычно и обнаруживаются неожиданности. Выполните:

docker ps -a --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"

Это даёт компактное представление, с которым проще работать, чем с необработанным выводом JSON. Здесь важно не просто получить список контейнеров, а сформировать минимальную инвентаризацию. Имена показывают, как адресуются сервисы, образы показывают, откуда они получены, статус даёт представление об ожидаемом рабочем состоянии, а порты показывают, как они подключены к сети.

Тома

Когда список контейнеров уже получен, пора переходить к томам, поскольку именно они содержат фактическое состояние вашей системы. Контейнеры можно пересоздать за считанные секунды, но именно тома содержат конфигурацию, базы данных и историю. Не следует рассматривать перенос как проблему контейнеров вместо проблемы данных.

docker inspect <container> | grep -A 20 "Mounts"

для получения общей картины используйте:

docker ps -a --format "table {{.Names}}\t{{.Mounts}}"
docker volume ls

Пути, начинающиеся с /, указывают на bind mount, который напрямую сопоставляется с каталогами на хосте. Они ведут себя предсказуемо при переносе, потому что их можно копировать как любую другую папку. Именованные тома, с другой стороны, существуют во внутреннем хранилище Docker. Они переносимы, но не являются прозрачными.

Типичный результат проверки наглядно показывает эту разницу:

"Mounts": [
 {
 "Type": "bind",
 "Source": "/opt/data/homeassistant",
 "Destination": "/config"
 },
 {
 "Type": "volume",
 "Name": "influxdb-data",
 "Destination": "/var/lib/influxdb2"
 }
]

На этом этапе полезно привести способ хранения данных к единому виду. Преобразование именованных томов в bind mount превращает миграцию в обычную задачу по переносу файлов, а не в Docker-специфичную операцию.. Это не является обязательным, но обычно значительно упрощает всё, что будет дальше.

Воспроизводимые и пользовательские образы

Образы создают проблему другого рода. Некоторые образы воспроизводимы по своей природе, другие — нет. Получить их список просто:

docker images
docker inspect <container> | grep -E "Image"

Осторожность требуется именно при интерпретации результатов. Образы из публичных реестров, например postgres, nginx или redis, а также образы, поддерживаемые известными организациями, можно повторно загрузить на целевой хост с минимальными усилиями. Их версии следует зафиксировать, но переносить их вручную нет необходимости.

Пользовательские образы требуют большего внимания, потому что если образ не соответствует шаблону repository/name или был создан локальной сборкой либо с помощью docker commit, он содержит состояние, которое невозможно восстановить где-либо ещё. Такие образы необходимо явно экспортировать. Если вы не уверены, каким образом был создан образ, часто имеет смысл перед переносом пересобрать его из Dockerfile, даже если это потребует дополнительных усилий в краткосрочной перспективе.

Обычно я создаю таблицу в Markdown, которая включает имена контейнеров, типы томов, пути монтирования, классификацию образов, версии, порты, переменные окружения и все неявные зависимости, например предположения об использовании фиксированных IP-адресов.

Когда подготовка завершена, прежде чем делать что-либо ещё, создайте резервную копию вне системы, которую собираетесь изменять. Это нужно не столько из соображений осторожности, сколько для того, чтобы сохранить возможность отката, если какие-либо предположения окажутся неверными, а такое случается нередко.

Преобразование именованных томов в bind mount

Если именованные тома преобразованы в bind mount, процесс становится понятнее. Рассмотрим контейнер базы данных, данные которого находятся в каталоге томов Docker. Перенос включает остановку стека, копирование данных в каталог проекта, обновление compose.yaml так, чтобы он ссылался на относительный путь, и повторный запуск сервиса:

docker compose down
cp -RT /var/lib/docker/volumes/patchmon_postgres_data/_data /docker/stacks/patchmon/postgres-data
docker compose up -d

В зависимости от того, каким образом контейнер ожидает получать доступ к файловой системе, может потребоваться корректировка прав доступа. Именно на этом этапе часто проявляются скрытые предположения относительно идентификаторов пользователей внутри контейнеров.

Перенос томов между хостами

Перенос томов между хостами по сути представляет собой процесс архивирования. На исходной системе перейдите в каталог, содержащий ваши стеки, остановите сервисы и создайте сжатый архив:

docker compose down
tar czf migration-data-stirling-pdf$(date +%Y%m%d).tar.gz stirling-pdf
scp migration-data-*.tar.gz user@new-host:/tmp/

На целевой системе распакуйте архив в заранее подготовленную структуру каталогов:

mkdir -p /opt/stacks/
tar xzf migration-data-stirling-pdf20251117.tar.gz -C /opt/stacks/

Иногда возникает соблазн временно включить SSH-доступ для пользователя root ради удобства. Если вы решите так сделать, сразу после завершения передачи данных верните настройки обратно. Если оставить его включённым, это создаст риск, который никак не связан с самим переносом, но его легко не заметить.

Перенос образов (Pull или Export)

Перенос образов достаточно прост. Сначала повторно загрузите стандартные образы:

docker compose pull

Экспортируйте и загрузите пользовательские образы:

docker save -o utcar-utcar.tar utcar-utcar:latest
scp utcar-utcar.tar user@new-host:/tmp/
docker load -i utcar-utcar.tar

Если контейнер был изменён непосредственно с помощью docker commit, это состояние можно сохранить таким же способом, хотя обычно это признак того, что такой подход следует заменить полноценным процессом сборки.

Развёртывание на новом хосте — это этап, на котором всё объединяется. Структура каталогов должна повторять логическую структуру ваших сервисов, при этом каждый стек должен содержать свой compose.yaml, файл переменных окружения и соответствующие каталоги данных.

docker compose pull
docker compose up -d
docker compose logs -f

Действительно ли работает?

На этом этапе система уже запущена, но её корректная работа ещё не подтверждена. Следует проверить состояние контейнеров, просмотреть логи на наличие ошибок и убедиться, что смонтированные тома содержат ожидаемые данные.

docker compose ps
docker compose logs <service>
docker compose exec <service> ls -la <mount>

Существует тенденция воспринимать Docker как инструмент, который по умолчанию упрощает инфраструктуру. На практике он переносит сложность в те места, которые легче игнорировать до тех пор, пока они не станут критически важными. Перенос делает эти уровни сложности очевидными. Время, потраченное в начале на их описание и систематизацию, обычно определяет, будет ли весь процесс управляемым или хаотичным.

На этом все! Спасибо за внимание! Если статья была интересна, подпишитесь на телеграм-канал usr_bin, где будет еще больше полезной информации.