Вот краткое изложение некоторых кардинальных "грехов", которые я совершил при использовании 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 может быть отличным инструментом для локальной разработки. Хотя у него есть несколько подводных камней, он обычно приносит много преимуществ командам разработчиков, особенно когда используется в сочетании с интеграционными тестами.
Анонсы других публикаций и возможность комментирования (если тут не можете) в телеге.
metamorph
Дополнение по греху №2: наличие ufw на хосте никоим образом не мешает докеру открывать порты наружу.
Запустили, к примеру, elasticsearch как -p 9200:9200 — получили дыру в безопасности размером с техас.
il_da_r Автор
Согласен. Его просто надо правильно приготовить. Я писал про это на github.
https://github.com/il-da-r/dockerufw
Jerry88
К сожалению, данный подход не позволяет блокировать сам процесс docker-proxy, который и открывает данный порт на всех интерфейсах. Этот процесс, как и сам docker, имеет привелегию CAP_NET_ADMIN, что позволяет открывать порт в обход файрвола. Если потенциально в нем есть какая-то проблема, то будет небольшая дырка с рут-привелегиями и всеми капами. Лучше всего указать докеру дефолтный адрес для проброса порта как 127.0.0.1, указав параметр "ip": "127.0.0.1" в конфиге докера. Конечно, это не запрещает явно указать -p 0.0.0.0:8080:8080, но это уже буратинство :)
it1804
Я однажды по не знанию сделал такую дыру, с тех пор запускаю dockerd с опцией --iptables=false, ну или в daemon.json опцию «iptables»: false. Бонус в отличие 127.0.0.1:xxxx:yyyy — теперь можно нормально управлять списком трастед хостов через iptables/ufw.
metamorph
Насколько я помню, от iptables=false у докера отваливается
жопанетворкингДетали сейчас не вспомню, но что-то прекращало работать.
it1804
Ни разу ничего не отваливалось из-за этого. Много лет в проде работает.
Единственное, нужно ещё добавить в правила форвардинга трафик контейнеров, и маскарадинг. Без этого действительно сеть в контейнерах сломается
Что-то типа такого:
metamorph
О, спасибо
gecube
Очень важное замечание. Потому что дальше идёт описание сценария именно в дев среде. Для прода эти «грехи» неприменимы
К сожалению, не помогает. Даже ввели DOCKER-USER цепочку — лучше не стало.
Поэтому совет публикации на 127.0.0.1:xxxx:yyyy — достаточно мудрый и я его поддерживаю.
С «грехом 1» даже интереснее — потому что если мы используем сеть хоста и публикуем внутри контейнера приложение на 0.0.0.0, то у нас файрволл как раз нормально работает. И в этом случае «нормально закрытый» файрволл отсекает все обращения не с локальной машины (что можно использовать как раз для прода — т.к. изоляция докер подсетей на самом деле достаточно эфемерна)
Опечатка — конечно, же healthy. Спорный вопрос, на самом деле. Действительно порядок запуска очень важен бывает. И тогда есть два варианта — либо писать его в каком-то внешнем скрипте (баш? Мейкфайл?), либо выносить логику в отдельный контейнер. Но все же хочется, чтобы все запускалось одной командой. Тогда цепочка взаимодействия через хелсчеки и порядок вызовов выглядит не самой дурацкой. Но в каждом конкретном случае надо принимать решение самостоятельно — идеального, хорошего и универсального солюшена нет.
«Отлично». Т.е. проблема в версии 3? Ну, так не пользуйтесь ею. 3.х придуман для сворм, это параллельная версия формата компоуз, которая не замещает 2.х По функционалу же они примерно идентичны.
Это тоже не так. Во-первых, приложение не существует в вакууме и можно объединить разработку на локалхосте, а зависимости — крутить в контейнерах. Чтобы основную машину не пачкать.
Вторая история — многие IDE уже поддерживают разработку в контейнерах. Бывают нюансы — да. Но в целом, понятно, что делать и как бороться с проблемами.
pavia
Не понятно при чём здесь docker-compose, все аргументы относятся к docker как таковому. Складывается впечатление, что автор не понимает сути технологии. Ну и аналитика на уровне детского сада ясельной группы заявление: «Я думаю, что для производственного использования docker-compose обычно не подходит.» А НЕОБЫЧНО подходит? А почему не подходит, потому что допускает неправильное использование? Это как то слишком.
DarkHost
Ну так это современная мода такая. «Язык программирования требует внимательного отношения к предоставляемым возмозможностям? Назовем этот яп опасным для разработки.»
gsaw
А я использую. До определенного уровня сложности имхо удобнее и проще, чем громоздить кубернетис или ещё какую конструкцию.
Кстати насчёт вывешивания портов наружу (привязка к 0.0.0.0). Если исправить "грех номер 1" то ведь автоматически исправляется и "грех номер 2"? Какой потом смысл в expose для отдельных контейнеров если все и так видят друг друга в docker Network? Ведь в таком случае точка входа (к примеру http сервер) как раз и должна привязываться к 0.0.0.0 интерфейсу.
saintbyte
Так использование сети докер это прекрасная фишка =) Которая позволяет создать вещь в себе и она прекрасна пока не начинается маштабирование на проде =) Но думать про маштабирование на проде это оверинженеринг и головная боль девопса а не разработчика
amarao
Поток догм, которые полезны некоторым, но которые пытаются выдать за истину всем.
--net=host
— это единственный адектватный метод получить хорошо работающую сеть в докере. (10к соединений в секунду, 10Гбит/с флуда на инспекцию, you name it).Да, в некоторых случаях это излишне. Но заявления "вы используете неправильно", мягко говоря, преувеличены.
gecube
еще раз — автор говорит про использование в dev (локальная машина разраба). На проде совсем другие задачи и правила. И с тезисом, что на проде нужен
--net=host
иiptables: false
в конфиге докера я полностью согласенgecube
найс фичу затащили — так что порядку запуска быть!
shadowalone
Да уж. обмельчали DevOps, аж так, что пишут на хабр исключительно на опыте dev.
Если бы ТС действительно работал с докером, он бы не опустился до той чуши, которую мы здесь прочли.