Деплой сломался: диагностика и откат с Kamal
Определяем, какая версия обслуживает сайт, проверяем healthcheck и логи, затем возвращаем рабочий релиз без потери причины сбоя.
Красный workflow не всегда означает, что сайт уже сломан. Сборка могла упасть до публикации образа, новый контейнер мог не пройти healthcheck, а проверка HTTPS — завершиться ошибкой уже после переключения трафика.
Разберём восстановление на примере статического сайта из первой статьи. Здесь нет базы данных или миграций: возвращаем предыдущий контейнер с HTML и Nginx. Все домены и адреса вымышлены, значения ключей и идентификаторы релизов в условном выводе скрыты как ********.
1. Не запускаем второй деплой поверх первого
Если работает автодеплой из GitHub Actions, сначала остановите новые автоматические публикации на время восстановления:
gh workflow disable deploy.yml --repo your-github-user/hello-kamal
gh run list --repo your-github-user/hello-kamal --workflow deploy.yml --limit 5
Отключение workflow не отменяет уже созданные запуски. Проверьте выполняющиеся и ожидающие задания. Не начинайте ручной откат параллельно активному kamal deploy: сначала выясните его состояние и дождитесь завершения либо осознанно остановите его.
Состояние блокировки можно посмотреть так:
kamal lock status
Не освобождайте блокировку только потому, что увидели сообщение «locked»: другой деплой может ещё работать.
2. Фиксируем текущее состояние
Из каталога проекта выполните:
kamal app version
kamal app details
kamal app logs --lines 100
Эти команды отвечают на разные вопросы: какая версия запущена, какие контейнеры видны и что пишет приложение. Проверьте также последний успешный и первый неудачный шаг Actions.
Сохраните время сбоя и версии локально. Если делитесь выводом, заранее уберите адреса, пользовательские данные и секреты. Условная запись для публикации:
service: hello-kamal
host: example.com
version: ********
registry_password: ********
request_authorization: ********
Полный docker inspect может содержать переменные окружения, а конфигурация Kamal — секреты. Не прикладывайте такой вывод к публичному issue без проверки.
3. Находим границу сбоя
| Где остановилось | Что проверить |
|---|---|
| Сборка Docker | Dockerfile, COPY, зависимости, текст ошибки сборки |
| Публикация в GHCR | Права токена и доступ workflow к пакету |
| SSH | Нужный пользователь, ключ, known_hosts, доступность порта |
| Запуск контейнера | Логи, команда запуска, конфигурация приложения |
| Healthcheck | Порт, путь /up, фактический HTTP-ответ |
| Проверка после деплоя | Какая версия активна, DNS, HTTPS, содержимое страницы |
Если ошибка произошла до переключения трафика, старая версия может продолжать работать. Сначала подтвердите это через kamal app version и внешний запрос; ненужный откат только добавит изменений.
4. Проверяем healthcheck внутри контейнера
Для нашего Nginx-примера:
kamal app exec --reuse 'nginx -t'
kamal app exec --reuse 'wget -qO- http://127.0.0.1:8080/up'
--reuse выбирает работающий контейнер, а не создаёт новый для команды. Эти проверки показывают состояние именно текущего приложения. Если неудачный новый контейнер уже остановлен, отдельно изучите его логи и версию; успешный /up старого контейнера ничего не доказывает о новом. Опции доступны в командах приложения Kamal.
В конфигурации должны совпадать порт Nginx и app_port:
proxy:
app_port: 8080
healthcheck:
path: /up
Прокси ждёт готовности по настроенному endpoint, но статический 200 на /up не проверяет текст главной страницы. Поэтому после деплоя нужна и внешняя проверка. Механизм описан в документации прокси.
5. Выбираем рабочую версию
Посмотрите сохранённые контейнеры:
kamal app containers -q
Найдите последний заведомо рабочий релиз и его версию в имени контейнера. В статье идентификатор намеренно скрыт:
hello-kamal-web-********
Не подставляйте маску буквально. Используйте реальное значение из собственного вывода и сверяйте его с успешным релизом. До завершения восстановления не запускайте очистку старых контейнеров и образов.
6. Откатываем и проверяем результат
Присвойте PREVIOUS_VERSION реальную версию. Здесь оставлена только маска:
PREVIOUS_VERSION='********'
kamal rollback "$PREVIOUS_VERSION"
Kamal поддерживает возврат к предыдущему релизу; нужная версия должна оставаться доступной. Команда и выбор контейнера разобраны в руководстве rollback.
После завершения:
kamal app version
curl --fail --silent --show-error --location --max-time 20 https://example.com --output /dev/null
Проверьте не только статус: откройте страницу и убедитесь, что вернулось ожидаемое содержимое. Для проблемы с DNS используйте сравнение с curl --resolve, сохраняя проверку TLS.
7. Исправляем Git, затем возвращаем автодеплой
Откат контейнера не отменяет плохой коммит в main. Следующий запуск workflow может снова опубликовать неисправную версию.
Подготовьте исправление или revert проблемного коммита через pull request. Если workflow временно отключён, включите его для проверок, удерживая изменения main под контролем:
gh workflow enable deploy.yml --repo your-github-user/hello-kamal
После успешных проверок слейте исправление и дождитесь нового деплоя. Запишите причину инцидента и проверку, которая должна поймать её в следующий раз: например, Nginx config test, запрос главной страницы или проверку обязательного текста.
Где заканчивается этот способ
Для статического сайта откат обычно возвращает и приложение, и его содержимое. У сервиса с базой данных старый образ может оказаться несовместим с новой схемой, а откат контейнера не восстановит изменённые данные. Для таких систем нужен отдельный план совместимости и восстановления.
Цель восстановления — вернуть рабочую версию и сохранить информацию о причине сбоя. Перезапуски, снятие блокировок и очистка Docker без понимания текущего состояния могут помешать обоим действиям.