Вот краткое изложение некоторых кардинальных "грехов", которые я совершил при использовании docker-compose.

Вы познакомились с docker-compose либо по собственному выбору, либо по необходимости. Вы используете его некоторое время, но считаете его неуклюжим. Я здесь, чтобы сказать вам: "Вы, вероятно, неправильно его используете".

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

В любом случае, вот краткое изложение некоторых из кардинальных "грехов", которые я совершил при использовании docker-compose.

В этой статье я сосредоточусь на сценариях использования, связанных с интеграционным тестированием и использованием docker-compose в качестве среды разработки. Я думаю, что для производственного использования docker-compose обычно не подходит.

Грех №1: Вы используете сеть хоста

Одна из первых вещей, которые новички находят обременительными - это использование сетей Docker. Это еще один уровень знаний, который нужно добавить в свой багаж после того, как вы привыкнете к основам docker build и docker run ... и, честно говоря, зачем вам вообще разбираться в этих сетях Docker? Все нормально работает через сеть хоста, правда? Неправильно!

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

По умолчанию docker-compose запускает свои контейнеры в отдельной сети с именем имякаталога_default. Так что на самом деле вам не нужно делать ничего особенного, чтобы воспользоваться преимуществами сетей Docker.

Эта сеть сразу дает вам ряд преимуществ:

  • Эта сеть более изолирована от вашей сети хоста, поэтому маловероятно, что особенности вашего системного окружения приведут к изменению поведения настройки сборки. У вас есть доступ к Интернету, но любые порты, которые вы хотите сделать доступными с хоста, должны быть объявлены с привязкой порта.

  • Если служба начинает прослушивать 0.0.0.0 (как должны делать контейнеры), то настройка сети хоста откроет этот порт в вашей локальной сети. Если вы используете сеть Docker, она предоставит доступ только к этому порту.

  • Сервисы смогут общаться, используя их имена в качестве имен хостов. Итак, если у вас есть служба с именем db и в ней есть служба, прослушивающая порт 5432, вы можете получить к ней доступ из любой другой службы через db: 5432. Обычно это более понятно, чем localhost: 5432. А поскольку нет риска конфликта портов localhost, у нас больше шансов избежать ошибок при использовании портов в разных проектах.

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

Грех №2: Вы привязываете порты к 0.0.0.0 хоста

Я видел это везде, вы видели это везде, все видели это везде: привязка портов как 8080:8080. На первый взгляд это выглядит безобидно. Но дьявол кроется в деталях. Эта чрезвычайно распространенная привязка портов не просто перенаправляет порт контейнера на локальный хост - она перенаправляет его, чтобы он был доступен на каждом сетевом интерфейсе в вашей системе, включая все, что вы используете для подключения к Интернету.

Это означает, что очень вероятно, что ваши контейнеры разработки постоянно прослушивают вашу локальную сеть - когда вы дома, когда вы в офисе или когда вы находитесь в McDonald’s. Это всегда доступно. Это может быть опасно. Не делай этого.

«Но я использую ufw, мои порты по умолчанию недоступны».

Это может быть правдой, но если вы используете эту настройку docker-compose в команде, у одного из ваших товарищей по команде может не быть брандмауэра на своем ноутбуке.

Исправить очень просто: просто добавьте 127.0.0.1: впереди. Так, например, 127.0.0.1:8080:8080. Это просто указывает докеру, чтобы он открывал порт только для петлевого сетевого интерфейса и ничего больше.

Грех №3: ??Вы используете helthy для координации запуска служб

Я хотел бы в кое-чем признаться. Я на 100% виноват в этом.

Основная причина, по которой эта проблема такая важная, заключается в том, что Docker или Docker Compose не поддерживают ее. Версия 2.1 формата docker-compose имела параметр depends_on для которого можно было установить значение service_healthy. Кроме того, каждая служба может иметь команду проверки работоспособности, которая может сообщать docker-compose - healthy. Что ж этого больше нет в версии 3.0 и никакой замены для него не предлагается.

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

Это становится немного более обременительным когда вы запускаете интеграционный тест и процедуры, предназначенные для инициализации тестовой среды (например, предварительное заполнение базы данных некоторыми тестовыми данными) службы в конечном итоге могут не запуститься до того, как другая служба будет готова. Таким образом, аргумент о том, что «в любом случае он должен быть устойчивым в производственной среде», здесь не совсем применим, потому что код для заполнения БД тестовыми данными никогда не используется в производственной среде.

В таких случаях вам нужно что-то, что ждет готовности сервисов. Docker рекомендует использовать wait-for-it, Dockerize или wait-for. Однако обратите внимание, что готовность порта не всегда является признаком того, что служба готова к использованию. Например, в интеграционном тесте с использованием определенной базы данных SQL с определенной схемой порт становится доступным при инициализации базы данных, однако тест может работать только после применения определенной миграции схемы. Сверху могут потребоваться проверки для конкретного приложения.

Проблема №4: Вы запускаете БД в режиме docker-compose, а тест - на хосте

Вот ситуация: вы хотите запустить несколько модульных тестов, но эти тесты зависят от некоторых внешних служб. Может быть база данных, может быть Redis, может быть другой API. Легко: давайте поместим эти зависимости в docker-compose и подключим к ним unit test.

Это здорово, но учтите, что ваши тесты больше не являются просто модульными. Теперь это интеграционные тесты. Следует принять во внимание еще одно важное различие: вам нужно будет учесть настройку тестовой среды и teardown. Обычно лучше всего выполнять setup/teardown вне тестового кода - основная причина в том, что может быть несколько разных пакетов в зависимости от этих внешних служб

Если вы в конечном итоге разделите настройку теста и teardown, вы можете, приложив дополнительные усилия, поместить свой интеграционный тест в контейнер.

Контейнерные тесты означают:

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

  • Интеграционный тест не зависит от какой-либо другой конфигурации локальной системы или настройки среды, например… ваших учетных данных JFrog или каких-либо зависимостей сборки. Контейнер изолирован.

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

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

Совет по использованию контейнерных интеграционных тестов - использовать для них отдельное определение docker-compose. Например, если большинство ваших сервисов существует в docker-compose.yml, вы можете добавить docker-compose.test.yml с настройками интеграционных тестов. Это означает, что docker-compose up вызывает ваши обычные службы, а docker-compose -f docker-compose.yml -f docker-compose.test.yml up запускает ваши интеграционные тесты. Полный пример того, как этого добиться, можно найти в этом отличном репозитории для интеграционного тестирования docker-compose от Ardan Labs.

Хорошо, хорошо - называть это неправильным не совсем справедливо. Есть много ситуаций, когда предпочтительнее не использовать контейнеры. В качестве простого примера, многие языки имеют глубокую интеграцию IDE, что делает вставку контейнера между языком и IDE практически невозможным. Есть много веских причин не делать этого.

Заключение

Docker Compose может быть отличным инструментом для локальной разработки. Хотя у него есть несколько подводных камней, он обычно приносит много преимуществ командам разработчиков, особенно когда используется в сочетании с интеграционными тестами.

Анонсы других публикаций и возможность комментирования (если тут не можете) в телеге.