Skip to main content
CI/CD8 min read

Исправление ошибок подключения Docker Compose в CI/CD

Потратил 4 часа на отладку ошибок 'Connection refused' в Jenkins. Вот что я узнал о сетевом взаимодействии Docker в CI-пайплайнах.

Part ofCloud & Infrastructure->
By Jason TeixeiraJanuary 5, 2024
DockerJenkinsCI/CDTroubleshooting
Share:
On this page

Представьте: ваш Docker Compose отлично работает на локальной машине. Вы пушите в CI, и внезапно все интеграционные тесты падают с Connection refused.

Контейнер базы данных "запущен". Контейнер API "здоров". Процесс тестирования стартует. Затем он не может подключиться к нужному сервису.

Эта ошибка выглядит случайной, пока вы не вспомните одну вещь: локальный Docker networking и CI Docker networking — это не одно и то же окружение.

Локальная настройка вас обманывает

На вашей машине вы можете подключаться к Postgres через localhost:5432.

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

В CI тестовый раннер может находиться:

  • внутри сети Compose
  • снаружи сети Compose на хосте
  • внутри CI-сервисного контейнера
  • внутри вложенного Docker executor'а

Эти четыре случая используют разные имена хостов.

Вот почему строка подключения может быть "правильной" локально и ошибочной в пайплайне.

Сначала определите, где выполняется тестовый процесс

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

Тестовая команда выполняется внутри сервиса Compose или на хосте CI?

Если тесты выполняются внутри Compose:

code panelTXT
DATABASE_URL=postgres://user:pass@postgres:5432/app

Если тесты выполняются на хосте CI и Compose опубликовал порт:

code panelTXT
DATABASE_URL=postgres://user:pass@127.0.0.1:5432/app

Если тесты выполняются в отдельном CI-контейнере, ни один из вариантов может не сработать, пока не настроен сетевой обмен платформы CI.

Решение по сетевому взаимодействию CIcompose -> tests
Сервис ComposeСетьТестовый раннерБаза данных

Правильное имя хоста зависит от того, где находится тестовый раннер. Имена сервисов работают внутри сети Compose. Опубликованные порты localhost работают с хоста.

Не доверяйте depends_on как гарантии готовности

depends_on может управлять порядком запуска. Он не гарантирует, что Postgres, Redis или ваше приложение готовы принимать подключения.

Распространенный плохой вариант:

code panelYAML
services:
  api:
    depends_on:
      - postgres

Это означает только то, что контейнер postgres запускается до api. Это не означает, что миграции выполнены. Это не означает, что TCP готов. Это не означает, что база данных приняла аутентификацию.

Используйте health checks или явный скрипт ожидания.

code panelYAML
services:
  postgres:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 5s
      timeout: 5s
      retries: 12

  api:
    depends_on:
      postgres:
        condition: service_healthy

Это все еще не решает проблему для каждой платформы CI, но устраняет наиболее распространенное состояние гонки.

Проверьте четыре класса ошибок

Когда я вижу Connection refused, я прохожу по этому порядку.

TCP-проверка скучна, но полезна:

code panelBASH
node -e "require('net').connect(5432, process.env.DB_HOST).on('connect', () => { console.log('ok'); process.exit(0) }).on('error', e => { console.error(e.message); process.exit(1) })"

Если это не сработает, отлаживать ваш тестовый набор приложения еще рано.

Используйте разные строки подключения для разных границ

Один чистый паттерн — сделать границу явной:

code panelENV
DATABASE_URL_INTERNAL=postgres://app:app@postgres:5432/app
DATABASE_URL_HOST=postgres://app:app@127.0.0.1:5432/app

Затем ваша CI-задача выбирает правильную в зависимости от того, где выполняется команда.

Это менее магично, чем попытка заставить один URL работать везде.

Держите миграции отдельно от готовности

База данных может быть здоровой до того, как схема будет готова.

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

code panelBASH
docker compose up -d postgres
docker compose run --rm migrate
docker compose run --rm test

Или запускайте тесты внутри сервиса, который ожидает и то, и другое:

  • здоровье базы данных
  • завершение миграций
  • загрузку seed-данных

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

Отладочный вывод, который я хочу видеть при каждом сбое CI

Не выгружайте секреты. Выводите структуру окружения.

Полезный вывод:

  • Сервисы Docker Compose и их статус
  • Логи контейнера для зависимости
  • Разрешенный хост и порт с замаскированным паролем
  • Имена сетей
  • Статус health check
  • Статус миграций

Пример:

code panelBASH
docker compose ps
docker compose logs --tail=80 postgres
docker network ls

Цель — сделать следующий сбой диагностируемым за один проход.

Урок для продакшена

Боль от сетевого взаимодействия в CI — это предвестник боли от интеграции в продакшене.

Если ваши тесты зависят от надежды, ваши развертывания, вероятно, тоже. Делайте границы сервисов явными. Добавляйте health checks. Разделяйте готовность и миграции. Логируйте правильные факты.

Вот как превратить "работает на моей машине" в то, что пайплайн может доказать.

Reader route

article -> proof -> offer

ReadClusterProofScope

cluster

Cloud & Infrastructure

intent

CI/CD

route

next step

What to do with this

Turn the note into a build path.

If this topic maps to a real business problem, keep reading the cluster, study the academy path, or route the work into a scoped engagement.

Jason Teixeira
Written by
Jason Teixeira
Founder, Sage Ideas Studio · Principal Engineer
livebuild 5d6c8652026-08-05 06:00Z
// solo studio// no analytics resold// every commit human-reviewed