Контейнер-пустышка для overlayed сетей docker swarm
В эфире рубрика "Костыли и Велосипеды".
Не так давно добрались мои руки до docker swarm. Только не нужно напоминать о том, что docker на грани банкротства, а кубернетес правит миром окрестрации контейнеров. Подробно объяснять, что это такое, я не буду. И так достаточно много статей и документации на эту тему. По сравнению с kubernetes, swarm намного легче и проще в обращении, не требует установки и практически не требует настройки и, что удобно, позволяет пользоваться почти теми же docker-compose файлами с минимальными изменениями для деплоя. Если контейнеров немного, то городить кластер на kubernetes не имеет смысла, swarm для этого вполне подойдёт.
Одно из главных прeимуществ, которые мы получаем, используя swarm - это multihosted networks. То есть, локальные закрытые сети, обладающие связностью вне зависимости от того, расположены ли они на одной или более нодах. Если ещё проще, то у вашей пары контейнеров будет возможность сетевого доступа друг к другу по имени сервиса, даже если эти контейнеры запущены на различных машинах. И всё было бы вообще прекрасно, но есть две проблемы, одну из которых я для себя решил, а вторую мне ещё только предстоит решить. Скажу сразу, что возможно, существует и более элегантное решение данной проблемы, но я его к сожалению, не обнаружил.
Вышеописанные распределённые сети обладают одной особенностью - они существуют только на тех нодах, на которых были добавлены руками, либо на тех нодах, на которых запущены контейнеры, находящиеся в этих сетях. Создание и удаление сетей в последнем случае контролируют сами ноды swarm. С одной стороны это логично. Нет ножек - нет мультиков! В смысле - нет контейнеров - нет сетей. С другой - у меня периодически появляются сервисы, которые я деплою другим образом (скриптами из jenkins например) или руками (для тестов - почему бы и нет?) и если я захочу посадить новый контейнер в распределённую сеть, у меня возникнут проблемы, т.к. на нужной ноде этой сети может не оказаться.
Решение простое и в лоб - сделать контейнер-пустышку, находящийся во всех сетях, не занимающийся ничем и раздеплоить его на все хосты. Сказано - сделано. Контейнер из alpine, внутри - изначально сделал while true, в котором крутится sleep. Слышите - что-то стучит? Это наш костыль. Потом я подумал и решил, что немного элегантности нашему костылю может добавить замена бесконечного цикла while true на прослушивание tail -f /dev/null. Так можно вообще не собирать новых контейнеров, убрать Dockerfile, entrypoint и всё прочее. А главное - избавиться от необходимости использовать docker registry.
Так же добавил несколько скриптов, маленький README.md и выложил всё на гитхаб.
Вот результат скрипта, показывающего статус:
./status.sh
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
5l0dm2a6g4gp dummy_dummy.f7poggb4dmvoe9i9tzi47395u alpine:latest sapfir-v6-01 Running Running 8 minutes ago
vysqxzrgaqit dummy_dummy.75cmtxf28kjw49donxcodihxo alpine:latest git Running Running 8 minutes ago
xxhx6ntyqxxj dummy_dummy.nynp0n4gczxh0v132yi8v9z6q alpine:latest dev Running Running 8 minutes ago
xy2e5z9x5lxt dummy_dummy.cq8xgt8was6r2hhlo167c055f alpine:latest cs48700 Running Running 8 minutes ago
kgluizzp07wx dummy_dummy.iugaip6np9ortytpf6cg2t6rf alpine:latest sapfir-v6-03 Running Running 8 minutes ago
Добавление к созданной сети и удаление из неё происходит вполне стандартным образом:
docker network connect <networkname> <containername>
docker network disconnect <networkname> <containername>
Обнаружилась ещё одна странная особенность. Если при деплое я указываю в yaml конфиге некий образ, то при попытке деплоя с помощью docker stack deploy <stackname> я получаю на удалённых нодах ошибки, говорящие о том, что этот образ недоступен. И это даже в том случае, если образ публичный. Alpine, например. Решается это добавлением ключа --with-registry-auth. Насколько я понял, матсер нода передаёт удалённой таким образом все необходимые данные для доступа в удалённые репозитории. Это решает проблему как с открытыми репозиториями вроде dockerhub, так и с личными закрытыми.
Ещё один неочевидный плюс решения с контейнером - пустышкой заключается в том, что у вас появляется место, из которого вы можете проверить доступность удалённых хостов сразу во всех распределённых сетях с любой из нод, так как ping уже на борту, а nmap и tcpdump при необхдоимости ставятся на alpine куда быстрее, чем на тот же debian, например.
Вторая проблема, которую я пока ещё не решил - это деплой с одинаковыми именами. Чего бы я хотел - при деплое одного сервиса в режиме global (по одному контейнеру на ноду), мне бы хотелось, чтобы они получали различные ожидаемые имена. Скажем, с именем хоста на который прошёл деплой в качестве постфикса. Т.е. если у меня есть две ноды с хостнеймами "node1" и "node2" и сервис с неожиданным именем "service", мне бы хотелось иметь возможность после деплоя иметь к ним доступ из соседних контейнеров по именам "service_node1" и "service_node2" например. В нашем же случае, dummy контейнер раздеплоился на пять хостов с одним и тем же именем dummy - спасибо, что без ошибок. И в рамках сети при пинге dummy с различных хостов я получаю один и тот же IP адрес. Т.е. кто первый встал, того и тапки - классика. Есть два варианта решения этой проблемы. Либо поднимать где-то рядом consul, в который каждый контейнер будет рапортовать, на каком хосте он запустился и какой у него серый адрес. Либо можно написать очередной костыль, что несколько проще на мой взгляд - скриптом получать список нод кластера и генерировать yaml файл для деплоя, где каждому экземпляру сервиса выдавать нужное имя. В принципе, несложно. А пока на этом всё.