Комментарии 34
А что и зачем вы копируете rsync'ом если у вас код в образе докера?
rsync
'ом я копирую всё содержимое репозитория, чтобы можно было его в случае чего подредактировать на сервере, не заходя внутрь докера. Как мне кажется, это проще, чем вспоминать ключи команды docker exec
, а потом ещё посылать HUP
всем затронутым процессам.
А что мешает сделать профиль для девелопмента в docker-compose и менять себе на здоровье локально перед коммитом?
Может для тестовой среды такое еще и можно делать, но если это production...
Насчёт отладочного профиля в docker-compose
вы правы, так и нужно делать.
Но без rsync
'а все равно не обойтись — конфиги для Nginx (и, потенциально, для других частей стека) хранятся в репозитории.
Вопрос конфига для nginx решается через переменные, шаблоны и dockerize.
Ну и как бы конфигурация должна храниться отдельно.
А в целом спасибо за статью, у меня как раз в процессе перевод django приложения в Docker, возьму статью за основу :)
dockerize
Ух ты, не знал про такую штуку, спасибо, изучу :)
у меня как раз в процессе перевод django приложения в Docker
Мы тоже решали эту задачу. Решили на наш взгляд довольно лаконично. Плюс: нулевой простой, применение и откат миграций и другие радости Docker.
prefrontalCortex, кстати, тоже на Fabric
Вы, я так понимаю, автор того самого fabricio
? Я в процессе построения описанного в статье решения на него натыкался, но решил, что быстрее напишу свой quick-and-dirty fabfile.
У меня всё-таки больше упор на инфраструктуру Gitlab, в котором даже закрытый Docker registry дают на халяву и не нужно самому его поднимать.
from fabricio import tasks
from fabricio.apps.python.django import DjangoContainer
my_app = tasks.ImageBuildDockerTasks(
container=DjangoContainer(
name='my_app',
image='user/my_image',
options=dict(
ports='8000:8000',
),
),
registry='registry.gitlab.com',
hosts=['user@example.com'],
)
Fabricio только по-умолчанию пытался работать с локальным registry (сейчас этот механизм deprecated, чтобы не сбивать больше пользователей с толку, теперь надо всегда задавать registry вручную, если нужно использовать репозитарий, отличный от hub.docker.com).
Если мне надо деплоить вместе с кодом проекта конфиг nginx сайта проекта, то как оно должно быть устроено правильнее?
И где должна храниться конфигурация?
Хороший вопрос. Я бы попробовал из .gitlab-ci.yml
вызывать шелл-скрипт, который на основе переменной CI_BUILD_REF_NAME
будет производить нужные действия, как-то в таком духе
if [ "$CI_BUILD_REF_NAME" = "master" ]; then
docker-compose build
else
docker-compose -f debug.yml build
fi
Соответственно, в debug.yml
должна быть описана конфигурация для сборки (кстати, конфиги docker-compose можно наследовать друг от друга; как говорится, don't repeat yourself).
И соответственно разные шаги для разных бранчей с разными скриптами.
Вот тут подробно https://habrahabr.ru/company/softmart/blog/310502/
Вот тут официальная документация https://docs.gitlab.com/ce/ci/yaml/README.html#only-and-except
Было бы тоже интересно.
Пока нет, но тема интересная и требующая подробного изучения :)
PS: можно конечно постараться осуществить нулевой простой и без помощи кластера контейнеров. Например, играясь с сетевой маршрутизацией между двумя контейнерами по аналогии с сине-зеленым деплоем. Но в этом случае вы скорее всего сделаете свой довольно сложный велосипед, тогда как перечисленные выше решения уже дают необходимое свойство.
1. nginx+uwsgi в одном контейнере — да, тут кластер или маршрутизацией управлять.
2. nginx отдельно (отдельный контейнер или непосредственно на сервере) от uwgi — в этом случае можно ведь настроить nginx failover с proxy_next_upstream и нулевым timeout?
Спрашиваю, потому что на практике не делал, но думаю что работает.
3. Исходные коды храняться в data volume (а не в инстансе как в пп.1 и 2), который цепляется в инстанс uwsgi — тут можно использовать возможность uwsgi последовательно перезагружать воркеры, у них довольно подробная инструкция есть на эту тему.
Всё это конечно с допусками:
— Нет изменений в БД, которые не позволяют использовать предыдущую кодовую базу с новой версией БД.
— Время простоя на перезагрузку конфигурации nginx считаем незначительной и отдельный балансировщик делать не будем :)
Или что-то не так?
2. Этот вариант в общем-то работает, но не дает zero downtime как такового, ибо nginx в данном случае пытается перенаправить запрос на следующий сервер, только если первый вернул ошибку либо отвалился по таймауту. В случае не идемпотентных запросов такое поведение опасно.
3. Этот вариант немного костыльный (зачем, например, нужны контейнеры, если исходный код сервиса все равно находится снаружи, а если так и нужна просто изоляция, то есть virtualenv и другие инструменты). И уж точно этот вариант не универсальный, то есть будет работать только при определенных условиях (как вы сами заметили) и только в случае с uwsgi.
2. nginx ведь не вернет ошибку, он внутри обработает запрос на доступный uwsgi. Клиент увидит только результат того uwsgi, который работает. Ошибки не увидит.
Попытка коннекта на закрытый порт это мне кажется всего несколько миллисекунд, а то и меньше.
А в случае не идемпотентных запросов, что может произойти?
Два инстанса сразу не должны быть доступны для запросов от клиента.
Но получается, что всёравно надо будет портами играться… надо ждать пока новый инстанс запустится, гасить порт старого, открывать порт нового.
В общем понятно.
3. Хранение исходных кодов в data volume (или в host директории) или в контейнере это разные стратегии применяемые в разных условиях (проект, команда, организация, куда идёт развертывание и т.п.).
virtualenv vs docker? Да ну его этот virtualenv…
uwsgi прекрасно работает, зачем гонять образы, если можно заменить только код и пользоваться его возможностями.
А в случае не идемпотентных запросов, что может произойти?
повторять не идемпотентные запросы нельзя, потому что если первый сервер ответил ошибкой на такой запрос, то вполне вероятно, что он мог успеть обновить данные в БД. Если nginx потом перенаправит этот запрос на резервный сервер, то данные могут обновиться дважды.
почему б не сделать 2 контейнера
Проще уж тогда вообще один контейнер сделать и к нему подключать volume'ом директорию с кодом. В принципе, такой вариант тоже имеет право на существование.
Контейнеры с celery я, например, вообще запускал ровно из того же образа, в котором жили Django с uwsgi — это удобно, так как демону celery в любом случае понадобятся настройки Django, как минимум настройки доступа к БД.
1 контейнер с uwsgi
2 от него унаследован образ для celery, изза особеностей EB так удобней стартовый скрипт менять
3 контейнер nginx
4 контейнер logentries который пишет логи собирая их из docker api
скелет приложения в открытом репозитарие не совсем допилен но более менее понятна структура
контейнер logentries который пишет логи собирая их из docker api
Теоретически, можно было обойтись без отдельного контейнера, докер много куда изкоробки умеет логи писать, в том числе и в syslog, откуда практически любой сервис по работе с логами умеет их забирать (например, loggly).
Спасибо за статью, может наконец сподвигнусь еще раз попробовать контейнирозвать проект.
Есть вопросы по решению, хотелось бы обсудить:
- Забавное решение с uwsgi (while true). Правда ничего лучше так и нет?
- Зачем в docker-compose выставлять наружу каждый порт? Они ж друг-другу итак доступны за счет network. Это как биндить всё на 0.0.0.0, когда на самом деле все эти порты (кроме nginx конечно), нужны только внутри.
Автоматическое развёртывание Django из GitLab