← К техническим практикам

CI/CD практика

Сбой деплоя OpenShip: как найти причину

Около 3 мин чтения

Сбой деплоя OpenShip: как найти причину

Сбой деплоя OpenShip быстрее всего устраняется не переустановкой платформы, а проверкой пяти слоёв: локальная сборка, SSH-передача, запуск контейнера, доменная маршрутизация и зависимые сервисы. Если ошибка вызвана нехваткой ресурсов на вашем Mac, отсутствием постоянного подключения или нестабильным рабочим узлом, перенос сборки в удалённую среду будет разумнее, чем бесконечные повторные запуски.

Эта инструкция рассчитана на:

  • разработчиков, которые впервые выкатывают проект через OpenShip и застряли на сборке или публикации;
  • технических специалистов, обслуживающих собственный сервер и сокращающих время поиска неисправности;
  • удалённые команды, запускающие деплой с локального Mac, которому не хватает ресурсов или стабильного времени работы.

Представьте типичный случай: OpenShip показывает, что версия доставлена, контейнер создан, а домен не отвечает. В такой ситуации «деплой завершён» означает только прохождение части цепочки. Приложение всё ещё может слушать только локальный интерфейс, не проходить healthcheck, обращаться к неправильной базе или не иметь корректного DNS-маршрута. Поэтому проверяйте не общий статус, а отдельные подтверждения сборки, передачи, запуска и внешнего ответа.

Сначала зафиксируйте точку отказа

Не меняйте одновременно Dockerfile, DNS, ключи и переменные окружения. Иначе вы потеряете причинно-следственную связь и не поймёте, какое действие действительно помогло.

Сформируйте запись инцидента:

Проект: <ИМЯ_ПРОЕКТА>
Ветка или commit: <COMMIT_ИЛИ_ВЕТКА>
Целевой сервер: <АДРЕС_СЕРВЕРА>
Домен: <ВАШ_ДОМЕН>
Время первой ошибки: <ВРЕМЯ_И_ЧАСОВОЙ_ПОЯС>
Последний успешный commit: <COMMIT>
Текущая версия: <ИДЕНТИФИКАТОР_ВЕРСИИ>

Затем установите, где именно оборвалась цепочка:

  1. команда сборки не завершилась;
  2. образ не передался на сервер;
  3. контейнер создался, но сразу завершился;
  4. контейнер работает, но внешняя маршрутизация не отвечает;
  5. приложение запустилось, но не видит базу, Redis или другой сервис.

Главный диагностический признак — последний подтверждённый слой. Если локальный образ не собран, проверять DNS ещё рано. Если контейнер отвечает на сервере, но домен не открывается, повторная сборка обычно не поможет.

Проверьте сборку до передачи артефакта

Сигнал

Для ошибки сборки важны не только слова failed или error в конце лога. Найдите:

  • первую команду, завершившуюся ненулевым кодом;
  • название шага — установка зависимостей, компиляция, тесты или упаковка;
  • версию Node.js, Python, Go, Rust или другой среды;
  • фактическую команду запуска;
  • наличие ожидаемого каталога с результатом сборки;
  • свободное место, память и нагрузку на локальном устройстве.

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

Снимите доказательства

Сохраните полный журнал, а не только последние строки. Для проекта с Docker дополнительно проверьте конфигурацию:

git status --short
git rev-parse HEAD
cat package.json
sed -n '1,220p' Dockerfile
cat .dockerignore

Если проект использует другой файл сборки, замените команды на:

cat <ФАЙЛ_КОНФИГУРАЦИИ_СБОРКИ>

Повторите сборку вне OpenShip тем же способом, который указан в проекте:

<КОМАНДА_СБОРКИ_ПРОЕКТА>
printf 'exit_code=%s\n' "$?"

Код 0 подтверждает успешное завершение команды, а ненулевое значение требует анализа конкретного шага. Не делайте вывод по одной строке вроде module not found: проверьте, какая версия зависимости установлена, из какого каталога запускается команда и не исключён ли нужный файл через .dockerignore.

Разделите кодовую ошибку и нехватку ресурсов

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

Docker собирает образ по инструкциям Dockerfile, включая FROM, RUN, COPY, CMD и HEALTHCHECK; это означает, что ошибка может появиться до запуска контейнера, ещё на этапе формирования образа. (docs.docker.com)

Исправляйте сначала конфигурацию проекта:

  • закрепите ожидаемую версию среды выполнения;
  • проверьте lock-файл зависимостей;
  • убедитесь, что команда сборки запускается из правильного каталога;
  • вынесите секреты из Dockerfile;
  • проверьте, что финальный образ содержит каталог, который ожидает команда запуска.

После исправления повторите локальную сборку и только при успешном результате переходите к передаче.

Сопоставьте SSH-соединение и передачу образа

Сигнал

Проблемы SSH делятся на три разных класса:

Ситуация Что вы видите Что нужно доказать Предварительный вывод
Первичное соединение не устанавливается тайм-аут, отказ, неизвестный ключ хоста адрес, порт, маршрут, отпечаток и ответ SSH проблема до входа на сервер
Передача прерывается вход успешен, но копирование обрывается подробный журнал передачи и состояние сети соединение нестабильно или сервер ограничивает поток
Вход есть, команда не выполняется permission denied, нет доступа к Docker или каталогу пользователь, группы, права и рабочий путь проблема полномочий, а не доступности SSH

OpenShip передаёт собранный образ на целевой сервер через SSH, поэтому успешная авторизация и успешная доставка — разные факты, которые нужно проверять отдельно.

Снимите доказательства

Проверьте соединение без запуска деплоя:

ssh -vvv \
  -i <ПУТЬ_К_ПРИВАТНОМУ_КЛЮЧУ> \
  -p <ПОРТ_SSH> \
  <ПОЛЬЗОВАТЕЛЬ>@<АДРЕС_СЕРВЕРА> \
  'id && hostname && pwd'

В результате должны быть видны:

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

Проверьте права на закрытый ключ:

ls -l <ПУТЬ_К_ПРИВАТНОМУ_КЛЮЧУ>
ssh-keygen -lf <ПУТЬ_К_ПУБЛИЧНОМУ_КЛЮЧУ>

Не отключайте проверку ключа хоста «для исправления ошибки». Настройки StrictHostKeyChecking, IdentityFile и known_hosts предназначены именно для контроля доверенного сервера и выбора конкретной идентификации. (man.openbsd.org)

После входа проверьте права на Docker и каталог временной передачи:

ssh -i <ПУТЬ_К_КЛЮЧУ> <ПОЛЬЗОВАТЕЛЬ>@<АДРЕС_СЕРВЕРА> \
  'docker version && docker info >/dev/null && test -w <КАТАЛОГ_ПЕРЕДАЧИ>'

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

Минимизируйте публичную поверхность

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

Если соединение устанавливается, но передача обрывается, сравните:

  • размер передаваемого артефакта;
  • время до разрыва;
  • загрузку диска на сервере;
  • свободную память;
  • записи firewall и системного журнала;
  • повторяемость ошибки на том же commit.

Только после этого меняйте сеть или параметры передачи.

Разберите запуск контейнера и healthcheck

Сигнал

Статус «контейнер создан» не равен статусу «сервис доступен». Контейнер может:

  • завершиться из-за неверного ENTRYPOINT или CMD;
  • слушать 127.0.0.1, а не интерфейс контейнера;
  • использовать порт, отличный от заявленного;
  • не получить обязательную переменную окружения;
  • запуститься раньше базы данных;
  • пройти процесс запуска, но провалить healthcheck.

В Docker healthcheck имеет отдельный статус: контейнер может быть запущен, но отмечен как unhealthy. Результат проверки хранится в состоянии контейнера, поэтому его нужно смотреть отдельно от обычного статуса. (docs.docker.com)

Снимите доказательства

На сервере получите состояние и логи:

docker ps -a --no-trunc
docker logs --tail <КОЛИЧЕСТВО_СТРОК> <ИМЯ_КОНТЕЙНЕРА>
docker inspect <ИМЯ_КОНТЕЙНЕРА>

Для healthcheck используйте:

docker inspect \
  --format='{{json .State.Health}}' \
  <ИМЯ_КОНТЕЙНЕРА>

Проверьте, какой процесс слушает ожидаемый адрес:

docker exec <ИМЯ_КОНТЕЙНЕРА> \
  sh -lc 'ss -lntp || netstat -lntp'

Если утилиты нет в образе, проверяйте через внутренний HTTP-запрос:

docker exec <ИМЯ_КОНТЕЙНЕРА> \
  sh -lc 'curl -fsS http://<ВНУТРЕННИЙ_АДРЕС>:<ПОРТ>/health'

Docker получает вывод docker logs из стандартных потоков процесса, поэтому приложение, которое пишет ошибки только во внутренний файл, может оставить вас без полезного журнала через стандартный интерфейс. (docs.docker.com)

Исправьте причину, а не цикл перезапуска

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

Проверьте:

docker inspect \
  --format='{{.Config.Entrypoint}} {{.Config.Cmd}}' \
  <ИМЯ_КОНТЕЙНЕРА>

docker inspect \
  --format='{{json .Config.Env}}' \
  <ИМЯ_КОНТЕЙНЕРА>

Типичные выводы:

  • ошибка сразу после старта — неверная команда или отсутствующий файл;
  • повторяющийся тайм-аут — недоступная база, Redis или внешний API;
  • connection refused на healthcheck — неправильный адрес или порт;
  • permission denied при чтении файла — пользователь контейнера не имеет нужных прав;
  • процесс жив, но HTTP-запрос не проходит — ошибка маршрута, порта или готовности приложения.

В Docker политика перезапуска может скрывать первоначальный сбой повторными запусками. Поэтому сначала сохраняйте первый стартовый лог, а уже потом настраивайте restart или повторяйте деплой. (docs.docker.com)

Проверьте домен по цепочке DNS — TLS — HTTP

Сигнал

Если приложение доступно на сервере, но домен не открывается, проверяйте внешний путь строго по порядку:

  1. публичная DNS-запись указывает на ожидаемый адрес;
  2. сертификат выпущен для нужного имени;
  3. пограничный прокси принимает запрос;
  4. маршрут направляет запрос в нужный контейнер;
  5. приложение возвращает корректный HTTP-ответ.

В архитектуре OpenShip домен подключается к пограничному слою, который завершает TLS и передаёт запрос новой версии контейнера. Внутренняя доступность приложения не доказывает, что внешний маршрут настроен правильно.

Снимите доказательства

Публичную DNS-картину проверьте с нескольких резолверов:

dig <ВАШ_ДОМЕН> A +short
dig <ВАШ_ДОМЕН> AAAA +short
dig <ВАШ_ДОМЕН> CNAME +short

Команда dig используется для получения и анализа DNS-ответов; важно сравнивать не только наличие записи, но и её тип, значение и соответствие целевой инфраструктуре. (isc.org)

Затем проверьте HTTP и TLS:

curl -Ivs https://<ВАШ_ДОМЕН>/
curl -Ivs http://<ВАШ_ДОМЕН>/
openssl s_client \
  -connect <ВАШ_ДОМЕН>:<ПОРТ_TLS> \
  -servername <ВАШ_ДОМЕН> </dev/null

Сохраните:

  • DNS-ответ;
  • имя, на которое выдан сертификат;
  • срок действия и цепочку сертификата;
  • код HTTP;
  • заголовок Location, если есть перенаправление;
  • время ответа;
  • текст ошибки пограничного прокси.

Для ACME-сертификата домен должен пройти проверку контроля имени. Если DNS указывает не туда или внешний HTTP-маршрут недоступен для выбранного способа проверки, выпуск сертификата не завершится. (letsencrypt.org)

Если локально работает, а снаружи нет

Выполните запрос с самого сервера:

curl -Ivs http://<ВНУТРЕННИЙ_АДРЕС_ИЛИ_СЕРВИС>:<ПОРТ>/

Если локальный запрос успешен, а внешний возвращает тайм-аут, ищите проблему в:

  • DNS;
  • firewall;
  • пограничном прокси;
  • правилах маршрутизации;
  • сертификате;
  • конфликте IPv4 и IPv6;
  • неправильной записи домена для preview- или production-окружения.

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

Восстановите подключение базы данных без удаления тома

Сигнал

Ошибка подключения к базе данных обычно относится к одному из трёх классов:

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

OpenShip может подключать PostgreSQL и Redis к приложению через изолированную внутреннюю сеть. Поэтому адрес базы внутри контейнера может отличаться от адреса, который вы используете с локального Mac или внешнего сервера.

Снимите доказательства

Проверьте состояние сервисов:

docker ps -a
docker logs --tail <КОЛИЧЕСТВО_СТРОК> <ИМЯ_КОНТЕЙНЕРА_БАЗЫ>
docker network inspect <ИМЯ_ВНУТРЕННЕЙ_СЕТИ>

Внутри контейнера приложения проверьте DNS-имя и порт:

docker exec <ИМЯ_КОНТЕЙНЕРА_ПРИЛОЖЕНИЯ> \
  sh -lc 'getent hosts <ИМЯ_СЕРВИСА_БАЗЫ>'

Для PostgreSQL можно выполнить проверку готовности, если клиент установлен:

docker exec <ИМЯ_КОНТЕЙНЕРА_ПРИЛОЖЕНИЯ> \
  sh -lc 'pg_isready -h <ИМЯ_СЕРВИСА_БАЗЫ> -p <ПОРТ_БАЗЫ>'

Сравните фактические переменные с ожидаемым окружением:

docker inspect \
  --format='{{range .Config.Env}}{{println .}}{{end}}' \
  <ИМЯ_КОНТЕЙНЕРА_ПРИЛОЖЕНИЯ>

Секреты в отчёт не копируйте. Записывайте только имя переменной, факт её наличия и окружение, к которому она относится.

Перед восстановлением сделайте резервную копию

До миграций, восстановления или изменения томов:

  1. определите текущий контейнер и data volume;
  2. создайте резервную копию;
  3. проверьте, что архив читается;
  4. сохраните идентификатор версии приложения;
  5. только затем исправляйте строку подключения или схему.

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

Зафиксируйте контрольные точки перед повторным запуском

После исправления не нажимайте «Deploy» без плана проверки. Используйте такую последовательность:

Этап Контрольная операция Доказательство успеха Действие при сбое
Сборка повтор команды на том же commit полный лог и код 0 вернуться к Dockerfile и зависимостям
Передача тест SSH и доставка артефакта журнал входа и завершённой передачи проверить ключ, сеть и права
Запуск состояние контейнера и стартовый лог контейнер не завершается проверить ENTRYPOINT, переменные и ресурсы
Готовность healthcheck и реальный запрос корректный ответ приложения проверить порт, адрес и зависимость
Домен DNS, TLS и внешний HTTP ожидаемый код и сертификат исправлять только слой маршрутизации
Данные чтение и тестовая запись операция подтверждена проверить сеть, секреты и миграции
Откат запуск предыдущей версии старый endpoint снова отвечает сохранить логи и остановить дальнейшие изменения

Проверяйте не только /health, но и реальный пользовательский маршрут:

curl -fsS https://<ВАШ_ДОМЕН>/<РЕАЛЬНЫЙ_МАРШРУТ>

Для API добавьте ожидаемый метод и безопасное тестовое тело:

curl -fsS -X POST \
  -H 'Content-Type: application/json' \
  -d '<ТЕСТОВОЕ_JSON_БЕЗ_СЕКРЕТОВ>' \
  https://<ВАШ_ДОМЕН>/<API_МАРШРУТ>

Healthcheck может подтверждать только доступность процесса, но не корректность авторизации, базы, очереди или бизнес-логики. Поэтому финальная приёмка должна включать чтение, запись и проверку внешнего домена.

Когда продолжать ремонт, а когда менять среду

Продолжайте исправление в OpenShip, если ошибка воспроизводится на одном commit и связана с:

  • неверной командой сборки;
  • отсутствующей переменной;
  • ошибкой маршрута;
  • неправильным портом;
  • недоступной зависимостью;
  • правами конкретного пользователя.

Меняйте узел сборки или среду, если:

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

В таком случае проблема уже не в OpenShip как маршрутизаторе деплоя, а в надёжности рабочего узла.

Сохраните шаблон доказательств для команды

Для каждого инцидента используйте одну карточку:

Симптом:
Последний успешный commit:
Неисправный commit:
Слой отказа:
Первое сообщение об ошибке:
Код завершения:
Команда воспроизведения:
Окружение:
Изменённые файлы:
Проверка SSH:
Проверка контейнера:
Проверка healthcheck:
Проверка DNS:
Проверка TLS:
Проверка HTTP:
Проверка базы:
Резервная копия создана: да / нет
Предыдущая версия сохранена: да / нет
Результат отката:
Окончательное исправление:

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

Если сбой вызван именно локальной сборкой, нехваткой ресурсов или тем, что ваш Mac нельзя оставить подключённым на весь цикл, текущая схема имеет три слабых места: процесс зависит от свободной памяти личного устройства, сетевого соединения и времени, в течение которого компьютер не перейдёт в сон. В этом случае более устойчивым вариантом будет удалённый Mac с постоянным доступом, сохранением логов и повторяемым окружением. Для оценки такого сценария можно сравнить аренду Mac mini для удалённой сборки или выбрать облачную среду Mac в США. Это не отменяет диагностику OpenShip, но убирает локальный узел как отдельную точку отказа — особенно когда вам нужны временная сборка, тестовый сервер или удалённый CI/CD-узел.

Частые вопросы

С какого места начинать, если сборка OpenShip завершилась ошибкой?

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

Что проверить, если OpenShip не подключается к серверу через SSH?

Разделите проблему на три случая: соединение не устанавливается, передача образа обрывается или команда после входа не имеет прав. Проверьте адрес, порт, пользователя, ключ и отпечаток хоста отдельной командой ssh -vvv. Не открывайте дополнительные порты без необходимости: разрешите SSH только с доверенных адресов и сохраните результат проверки для последующего сравнения.

Почему домен не открывается после успешного деплоя OpenShip?

Статус деплоя подтверждает доставку и запуск версии, но не гарантирует корректный DNS, сертификат или внешний HTTP-ответ. Сначала получите публичную DNS-запись для домена, затем проверьте сертификат и только после этого выполните curl с заголовками. Если приложение отвечает на сервере, но не снаружи, ищите проблему в маршрутизации, пограничном прокси, firewall или записи DNS.

Как найти причину постоянного перезапуска приложения?

Сравните три источника: docker logs, состояние контейнера и результат healthcheck. Ошибка команды ENTRYPOINT или CMD обычно появляется в стартовом логе, неверный порт проявляется при проверке доступности, а зависимость от базы может давать повторяющиеся тайм-ауты. Временно остановите бесконечный цикл перезапуска, сохраните предыдущий контейнер и запустите исправленную версию отдельно, не удаляя старую.

Как восстановить подключение OpenShip к базе данных без потери данных?

Сначала подтвердите, что сервис базы запущен и доступен из сети приложения, затем проверьте строку подключения, имя базы, пользователя и секреты целевого окружения. Перед любым восстановлением сделайте резервную копию и зафиксируйте состояние тома. Удаление data volume не является универсальным ремонтом: оно может устранить повреждённое локальное состояние, но одновременно уничтожить единственную копию данных.

CI/CD на M4 Mac mini — без лишних хлопот

Xcode, Fastlane, CocoaPods, and SPM are first-class on macOS. Mac mini M4 unified memory keeps signing and archiving smooth; ~4W standby power suits 24/7 build nodes.

View Kvmkit plans

Нужна техническая поддержка или консультация?

При проблемах с Mac-инстансами или CI/CD сначала загляните в центр помощи.