Pull to refresh

Comments 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 дают на халяву и не нужно самому его поднимать.

Fabricio поддерживает любые registry. Например, если вы хотите использовать gitlab, то можно использовать следующий конфиг:
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).
Можно даже опустить отдельное указание registry и прописать его в самом названии образа:
image='registry.gitlab.com/user/my_image',
«через переменные, шаблоны и dockerize» — немного непонятно.
Если мне надо деплоить вместе с кодом проекта конфиг nginx сайта проекта, то как оно должно быть устроено правильнее?
И где должна храниться конфигурация?
я через envsubst конфиг nginx собираю прямо в контейнере, те туда темплейт конфига а переменные уже при запуске подставляю но это не dockerize… а только переменные и шаблоны
Это логично и понятно, а про что darken99 писал я не понял…

dockerize это просто удобный шаблонизатор, делает то же что и envsubst (как выше написали) только позволяет сразу запустить процесс внутри контейнера предварительно подготовив конфигурацию на основании шаблонов, шаблоны должны быть внутри образа

А как правильно разделить, если пуш не в мастер, то собирать контейнер в конфигурации для разработки, ну и соответсвенно, если в мастер, то для продакта?

Хороший вопрос. Я бы попробовал из .gitlab-ci.yml вызывать шелл-скрипт, который на основе переменной CI_BUILD_REF_NAME будет производить нужные действия, как-то в таком духе


build.sh
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).

В шагах .gitlab-ci.yml можно выбирать, при коммите в какой бранч выполнять шаг.
И соответственно разные шаги для разных бранчей с разными скриптами.

Вот тут подробно https://habrahabr.ru/company/softmart/blog/310502/
Вот тут официальная документация https://docs.gitlab.com/ce/ci/yaml/README.html#only-and-except
До zero downtime развертывания не планируете довести? :)
Было бы тоже интересно.

Пока нет, но тема интересная и требующая подробного изучения :)

с контейнерами можно осуществить только near-zero downtime. Полноценный zero downtime гарантируется только при использовании кластерных решений (Swarm, Kubernetes, Mesos).

PS: можно конечно постараться осуществить нулевой простой и без помощи кластера контейнеров. Например, играясь с сетевой маршрутизацией между двумя контейнерами по аналогии с сине-зеленым деплоем. Но в этом случае вы скорее всего сделаете свой довольно сложный велосипед, тогда как перечисленные выше решения уже дают необходимое свойство.
Тут варианты:
1. nginx+uwsgi в одном контейнере — да, тут кластер или маршрутизацией управлять.

2. nginx отдельно (отдельный контейнер или непосредственно на сервере) от uwgi — в этом случае можно ведь настроить nginx failover с proxy_next_upstream и нулевым timeout?
Спрашиваю, потому что на практике не делал, но думаю что работает.

3. Исходные коды храняться в data volume (а не в инстансе как в пп.1 и 2), который цепляется в инстанс uwsgi — тут можно использовать возможность uwsgi последовательно перезагружать воркеры, у них довольно подробная инструкция есть на эту тему.

Всё это конечно с допусками:
— Нет изменений в БД, которые не позволяют использовать предыдущую кодовую базу с новой версией БД.
— Время простоя на перезагрузку конфигурации nginx считаем незначительной и отдельный балансировщик делать не будем :)

Или что-то не так?
1. В одном контейнере лучше не запускать более одного сервиса. А маршрутизацию можно осуществлять при помощи iptables (Docker так и делает).

2. Этот вариант в общем-то работает, но не дает zero downtime как такового, ибо nginx в данном случае пытается перенаправить запрос на следующий сервер, только если первый вернул ошибку либо отвалился по таймауту. В случае не идемпотентных запросов такое поведение опасно.

3. Этот вариант немного костыльный (зачем, например, нужны контейнеры, если исходный код сервиса все равно находится снаружи, а если так и нужна просто изоляция, то есть virtualenv и другие инструменты). И уж точно этот вариант не универсальный, то есть будет работать только при определенных условиях (как вы сами заметили) и только в случае с uwsgi.
1. Это понятно

2. nginx ведь не вернет ошибку, он внутри обработает запрос на доступный uwsgi. Клиент увидит только результат того uwsgi, который работает. Ошибки не увидит.
Попытка коннекта на закрытый порт это мне кажется всего несколько миллисекунд, а то и меньше.

А в случае не идемпотентных запросов, что может произойти?
Два инстанса сразу не должны быть доступны для запросов от клиента.
Но получается, что всёравно надо будет портами играться… надо ждать пока новый инстанс запустится, гасить порт старого, открывать порт нового.
В общем понятно.

3. Хранение исходных кодов в data volume (или в host директории) или в контейнере это разные стратегии применяемые в разных условиях (проект, команда, организация, куда идёт развертывание и т.п.).
virtualenv vs docker? Да ну его этот virtualenv…
uwsgi прекрасно работает, зачем гонять образы, если можно заменить только код и пользоваться его возможностями.
А в случае не идемпотентных запросов, что может произойти?

повторять не идемпотентные запросы нельзя, потому что если первый сервер ответил ошибкой на такой запрос, то вполне вероятно, что он мог успеть обновить данные в БД. Если nginx потом перенаправит этот запрос на резервный сервер, то данные могут обновиться дважды.
Я и пишу, что «Два инстанса сразу не должны быть доступны для запросов от клиента.»
А зачем копировать исходники проекта в контейнер с окружением? почему б не сделать 2 контейнера и не пересобирать окружение каждый раз? можно его тогда вообще в открытом репозитарии хранить будет и использовать например для запуска контейнера с celery.
почему б не сделать 2 контейнера

Проще уж тогда вообще один контейнер сделать и к нему подключать volume'ом директорию с кодом. В принципе, такой вариант тоже имеет право на существование.


Контейнеры с celery я, например, вообще запускал ровно из того же образа, в котором жили Django с uwsgi — это удобно, так как демону celery в любом случае понадобятся настройки Django, как минимум настройки доступа к БД.

согласен, у меня так в одном проекте и сделано в elastic beanstalk
1 контейнер с uwsgi
2 от него унаследован образ для celery, изза особеностей EB так удобней стартовый скрипт менять
3 контейнер nginx
4 контейнер logentries который пишет логи собирая их из docker api

скелет приложения в открытом репозитарие не совсем допилен но более менее понятна структура
контейнер logentries который пишет логи собирая их из docker api

Теоретически, можно было обойтись без отдельного контейнера, докер много куда изкоробки умеет логи писать, в том числе и в syslog, откуда практически любой сервис по работе с логами умеет их забирать (например, loggly).

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

Спасибо за статью, может наконец сподвигнусь еще раз попробовать контейнирозвать проект.


Есть вопросы по решению, хотелось бы обсудить:


  1. Забавное решение с uwsgi (while true). Правда ничего лучше так и нет?
  2. Зачем в docker-compose выставлять наружу каждый порт? Они ж друг-другу итак доступны за счет network. Это как биндить всё на 0.0.0.0, когда на самом деле все эти порты (кроме nginx конечно), нужны только внутри.
  1. Можно взять за основу образ alpine:edge (ну, или 3.5, когда выйдет), там uwsgi есть пакетом. Ну или вообще Ubuntu какую-нибудь.
  2. А ведь ваша правда. Я-то у себя на продакшене через unix domain socket'ы сделал. Спасибо, поправил статью.
Sign up to leave a comment.

Articles