Принципиально, наверное, ничем. Не считая того, что local_settings.yaml каждый пишет так, как удобно ему, а .env — признанный индустрией формат, который принимают такие монстры, как docker, systemd, heroku и куча сошек поменьше.
Хотя нет, есть принципиальное отличие. local_settings.yaml обязан быть, а .env — нет. Это лишь один из удобных девелоперу способов указать переменные окружения. На продакшн так стараются не делать, конечно.
12 факторов — это не конкретная имплементация, это подход. И уж тем более не про контейнеры. Её можно реализовать и на одной машине, хоть и сложнее изоляцию организовать, наверное.
Статья Ваша, безусловно, полезна. Хотя бы вот этим холиваром :) к тому же, я вот узнал, что systemd умеет в EnvironmentFile. Вот реально этого не знал
Понятно, что у каждого опыт свой, но у меня в голове уже вряд ли уложится, как это так можно — вручную заходить по ssh на сервер, чего-то там править. Как можно не использовать контейнеры. Как можно деплоить приложения, не имея гарантии, что они запустятся в том окружении, которое я указал.
Если речь о докере, то там при запуске можно передать ключ типа --env-fle=/path/to/env. Обычный текстовый файл с парами KEY=VALUE по одной на строку.
Для systemd можно такой же файл указать. Для питона существует куча пускалок, которая смотрит, нет ли файла .env в текущей директории. И если есть, считывает переменные и помещает их собственно в окружение. Такая штука, правда, больше про разработку, а не сервер.
Вкратце, идея такая: вводится понятие "ревизия приложения". Ревизия приложения состоит из ревизии кода (натуральной ревизии, из гита которая), списка зависимостей и набора переменных окружения.
Когда нужно задеплоиться: в случае, если появился новый код или поменялось окружение (появилась \ изменилась \ удалилась переменная), создаётся новая ревизия приложения. Технически — это создаётся новый образ, в который помещается кодовая ветка, устанавливаются с нуля все зависимости. И переменные.
Далее на основе этого образа запускается контейнер. И неважно что там: gunicorn, ./manage.py shell_plus, ./manage.py migrate — образ один, набор переменных один. После завершения процесса контейнер умрёт.
На мой взгляд, пример с энвами более красивый. Даже не потому что там энвы, он был бы красивее даже с хардкодом. Из тех хотя бы соображений, что мы определили какие-то переменные сами и знаем, что ничего неожиданного там нет. и никто случайно или по злому умыслу не втыкнул DEBUG = True на продакшн.
И вопрос — какое конкретно значение сейчас на запущенном сервисе на сервере?
На такие вопросы отвечают 12 факторов. И, что немаловажно, отвечают и за свои слова :)
И не бывает таких ситуаций, когда кто-то внезапно поменял конфиг и это не сказалось на запущенном приложении.
Запустили вы в subprocess какой-нибудь внешний процесс, а он получил все ваши пароли, а потом в крэшдампе отправил своим разработчикам.
Вроде бы в subprocess можно создавать чистое окружение, но аргумент всё же
принимается, спасибо.
Я реально не вижу удобства в переменных окружения. Мне лично удобнее прочитать переменную DATABASES как dict из yaml/json файла чем составлять ее из нескольких переменных окружения.
Хе-хе. Если б одна запись составлялась из нескольких переменных, это не было так круто, как есть. Жизнь — она горазда элегантнее.
Уже стало хорошей традицией записывать все кренделя в виде DSN
import dj_database_url as db_config # да, это внешняя зависимость. dj-database-url
DATABASES = {
'default': db_config.config(),
'another': db_config.config(var='ANOTHER_DATABASE_URL'),
}
Однажды видел, правда, не помню где, как особо одарённые люди делали так:
DATABASES = parse_env()
В результате этой надстройки в DATABASES под ключом default попадало распарсенное значение DATABASE_URL, а все переменные вида MYPREFIX_DATABASE_URLпопадали туда же как значение ключа myprefix.
Совершенно аналогично задаются кренделя к e-mail, кешам, очередям типа rabbitmq и прочая, и прочая, и прочая.
С примитивами тоже наглядно. Понятно, что переменная окружения всегда строка (если есть) и None, если не определена. Но многочисленные обвязки позволяют писать очень, на мой взгляд, довольно прикольно. Например, так:
Если руки кривые (а это бывает… ох бывает), то контейнер кокрастыке поможет.
Вот примерно из-за такого мнения многих людей я хочу свалить с этой прогнившей планеты подальше на какой-нибудь Марс. Все живут по принципу фигак-фигак и в продакшен, совсем чувство прекрасного растеряли, нафиг так жить
Из-за какого мнения? Программирование — это не искусство, это прикладная область, за неё кушать дают. А хочется прекрасного — участвуйте в олимпиадах. Денег не заплатят, ну так художник и должен быть голодным :)
Если выломают приложение, то пострадает только приложение, если руки не кривы нужные юниксовые права правильно настроить
А вдруг кривые. Зачем оставлять потенциальную возможность?
Идентичное окружение — зло.
Голословное утверждение. И в корне неверное. Хорошее приложение должно делать только одно: выполнять поставленную бизнес-задачу с разумными затратами русурсов (времени, денег, мощностей). Всё остальное — пожелания, причём не всегда критичные.
Даже если в контейнере говнокод — да ради бога, если он работает, не просит каши и его можно поддерживать. Действительно, контейнер всё стерпит. Хорошее правило.
Я буквально на днях переносил старую кодовую базу на новое железо и старался использовать тот же софт, что и раньше. Так вот, как supervisord не умел 4 года назад убивать воркеры celery при перезапуске задачи, так и не умеет до сих пор. В принципе, этого одного уже достаточно, чтобы им не пользоваться :-)
На мой взгляд, среди всех перечисленных способов Вы порекомендовали самый неудачный. По сути, он имеет все те же самые минусы, что и local_settings.py, кроме одного: его случайно не поместить в репозиторий. Да и то плюс этот сомнительный, с учётом .gitignore
Зато привносит дополнительные минусы:
Магия. Сами упомянули.
Лишняя зависимость. Не столько даже yaml, сколько необходимость размещать этот "магический" код в каком-то файле, чтобы его импортировать
Захардкоженные пути. По идее, они должны быть одинаковы, но где, к примеру, искать /usr/local/etc/ в Windows? :) Или логику городить? Тогда почему бы просто не остановиться на local_settings?
Переменные окружения таких проблем не продуцируют.
Они специально придуманы для конфигурации окружения, в котором запускается процесс. Конфигурация от народа!
Их поддержка есть в каждой операционной системе
Их использование всегда одинаково и на windows, и в macos, и в linux, и в докер-контейнерах.
Они случайно не попадут в версионный контроль
Они прекрасно интегрируются с тем же systemd
Про какие неудобства идёт речь — я так и не понял. И почему они менее защищены чем файл, лежащий снаружи репозитрия.
И вот этот Ваш коммментарий:
Переменные окружения легко не перечитаешь без перезапуска, в отличие от конфига. Да и при запуске любого дочернего процесса он наследует все переменные, что не всегда хорошо. Мне это не нравится и я не использую.
тоже не понял. Особенно в контексте джанги выглядит странно, где конфиг вычисляется при старте приложения.
Потому что если выломают приложение, пострадает только контейнер, а на хост-система
Потому что можно добиться идентичного окружения в на бою и на машине разработчика, даже если на сервере Linux, а у разработчика — macos (или windows, ни к ночи упомянута будет).
Разработка в контернере с файловой системой, примонтированной в ReadOnly дисциплинирует: приходится писать так, чтобы не привязываться к собственно файловой системе. Поэтому приложения получаются такими, что их легче масштабировать
Потому что внешняя зависимость, без которой можно обойтись
Потому что глючный
Потому что python2
Ещё, помнится, был проект circus. Что-то типа supervisord, но для Python3. В бою не испытывал, да и смысла особого нет, поскольку, согласно бритве Оккама, лучше пользоваться тем, что есть.
Потому что запись в файловую систему привязывает нас к собственно файловой системе.
Потому что приложение обязано сообщать о произошедших событиях, а не сохранять их.
Почему надо писать логи в STDOUT
Потому что это стандартный вывод. Он есть везде и везде более-менее одинаково работает
Потому что стандартным выводом процесса может управлять файловая система (или пользователь). Этот стандартный вывод можно грепать в реальном времени; его можно писать в journald, его можно отправлять в fluentd, его даже можно писать в файл и логротейтить :)
Но заниматься этим должно не наше приложение.
Вот Вы утверждаете, что supervisord — лишняя сущность и надо её избегать. Но при этом рекомендуете файлы с логами ротейтить, а конфиги хранить не в переменных окружения, а в файлах. Да ещё и велосипед какой-то для этого придумали. На мой взгляд, это двойные стандарты :)
Если уж избавляться от лишних сущностей, то по-большому. Тезисно:
Логи писать исключительно в stdout;
Если есть systemd, использовать его. Если нет, обновлять сервер, чтобы был. Или менять на другой. Ни в коему случае не использовать supervisord сотоварищи.
Контейнеры по возможности использовать, причём с readonly-файловой системой.
Хранить настройки в переменных окружения
Здесь, напоминаю, только тезисы. Давайте более подробно обсудим в комментариях?
P.S. Хабраюзер ahmpro уже упоминал про 12 факторов. Рекомендую обратить внимание и почитать. Соглашаться со всем не обязательно, но крайне полезно для знакомства.
Принципиально, наверное, ничем. Не считая того, что
local_settings.yaml
каждый пишет так, как удобно ему, а.env
— признанный индустрией формат, который принимают такие монстры, как docker, systemd, heroku и куча сошек поменьше.Хотя нет, есть принципиальное отличие.
local_settings.yaml
обязан быть, а.env
— нет. Это лишь один из удобных девелоперу способов указать переменные окружения. На продакшн так стараются не делать, конечно.У меня такого файла вообще нет, например.
Ну вопрос-то как был задан: как в окружение попадают данные. Как спросили, так и ответил. Ну, точнее, как понял вопрос, так и ответил.
Нет никаких шеллов. Не нужны :)
Ну да. При этом я не вижу ничего плохого в том, чтобы хранить эти переменные в
.service
, при условии защиты последних, конечно.12 факторов — это не конкретная имплементация, это подход. И уж тем более не про контейнеры. Её можно реализовать и на одной машине, хоть и сложнее изоляцию организовать, наверное.
Статья Ваша, безусловно, полезна. Хотя бы вот этим холиваром :) к тому же, я вот узнал, что
systemd
умеет вEnvironmentFile
. Вот реально этого не зналПонятно, что у каждого опыт свой, но у меня в голове уже вряд ли уложится, как это так можно — вручную заходить по ssh на сервер, чего-то там править. Как можно не использовать контейнеры. Как можно деплоить приложения, не имея гарантии, что они запустятся в том окружении, которое я указал.
Так что давайте и вправду закроем эту тему :)
Вот прям нутром чую, что подвох. Но отвечу как есть
Самое простое — перечислить их в командной строке:
Или выставить их в текущий сеанс. Тогда они будут работать для всех процессов, порождённых текущей сессией шелла
Если речь о докере, то там при запуске можно передать ключ типа
--env-fle=/path/to/env
. Обычный текстовый файл с парамиKEY=VALUE
по одной на строку.Для systemd можно такой же файл указать. Для питона существует куча пускалок, которая смотрит, нет ли файла .env в текущей директории. И если есть, считывает переменные и помещает их собственно в окружение. Такая штука, правда, больше про разработку, а не сервер.
И на этот вопрос тоже отвечают 12 факторов.
Вкратце, идея такая: вводится понятие "ревизия приложения". Ревизия приложения состоит из ревизии кода (натуральной ревизии, из гита которая), списка зависимостей и набора переменных окружения.
Когда нужно задеплоиться: в случае, если появился новый код или поменялось окружение (появилась \ изменилась \ удалилась переменная), создаётся новая ревизия приложения. Технически — это создаётся новый образ, в который помещается кодовая ветка, устанавливаются с нуля все зависимости. И переменные.
Далее на основе этого образа запускается контейнер. И неважно что там:
gunicorn
,./manage.py shell_plus
,./manage.py migrate
— образ один, набор переменных один. После завершения процесса контейнер умрёт.На мой взгляд, пример с энвами более красивый. Даже не потому что там энвы, он был бы красивее даже с хардкодом. Из тех хотя бы соображений, что мы определили какие-то переменные сами и знаем, что ничего неожиданного там нет. и никто случайно или по злому умыслу не втыкнул
DEBUG = True
на продакшн.На такие вопросы отвечают 12 факторов. И, что немаловажно, отвечают и за свои слова :)
И не бывает таких ситуаций, когда кто-то внезапно поменял конфиг и это не сказалось на запущенном приложении.
Вроде бы в
subprocess
можно создавать чистое окружение, но аргумент всё жепринимается, спасибо.
Хе-хе. Если б одна запись составлялась из нескольких переменных, это не было так круто, как есть. Жизнь — она горазда элегантнее.
Уже стало хорошей традицией записывать все кренделя в виде DSN
а настройки выглядят так:
Однажды видел, правда, не помню где, как особо одарённые люди делали так:
В результате этой надстройки в
DATABASES
под ключомdefault
попадало распарсенное значениеDATABASE_URL
, а все переменные видаMYPREFIX_DATABASE_URL
попадали туда же как значение ключаmyprefix
.Совершенно аналогично задаются кренделя к e-mail, кешам, очередям типа rabbitmq и прочая, и прочая, и прочая.
С примитивами тоже наглядно. Понятно, что переменная окружения всегда строка (если есть) и
None
, если не определена. Но многочисленные обвязки позволяют писать очень, на мой взгляд, довольно прикольно. Например, так:или так:
кто во что горазд, короче.
Разумеется, это тоже внешняя зависимость, как и в Вашем случае. Но всё же, по чесноку, что более выразительно и явно:
или
? Мы же помним, что явное лучше неявного. Кто его знает, какие переменные понаинжектили злобные русские хакеры в конфиг, который Вы инклюдите? :)
Других аргументов, извините, не увидел. И по старинному правилу «бремя доказательства лежит на обвиняющем», прошу всё же показать аргументы :)
Если руки кривые (а это бывает… ох бывает), то контейнер кокрастыке поможет.
Из-за какого мнения? Программирование — это не искусство, это прикладная область, за неё кушать дают. А хочется прекрасного — участвуйте в олимпиадах. Денег не заплатят, ну так художник и должен быть голодным :)
А вдруг кривые. Зачем оставлять потенциальную возможность?
Голословное утверждение. И в корне неверное. Хорошее приложение должно делать только одно: выполнять поставленную бизнес-задачу с разумными затратами русурсов (времени, денег, мощностей). Всё остальное — пожелания, причём не всегда критичные.
Даже если в контейнере говнокод — да ради бога, если он работает, не просит каши и его можно поддерживать. Действительно, контейнер всё стерпит. Хорошее правило.
Требование же чтобы работало везде… А зачем?
Я буквально на днях переносил старую кодовую базу на новое железо и старался использовать тот же софт, что и раньше. Так вот, как
supervisord
не умел 4 года назад убивать воркерыcelery
при перезапуске задачи, так и не умеет до сих пор. В принципе, этого одного уже достаточно, чтобы им не пользоваться :-)Про хранение настроек
На мой взгляд, среди всех перечисленных способов Вы порекомендовали самый неудачный. По сути, он имеет все те же самые минусы, что и
local_settings.py
, кроме одного: его случайно не поместить в репозиторий. Да и то плюс этот сомнительный, с учётом.gitignore
Зато привносит дополнительные минусы:
yaml
, сколько необходимость размещать этот "магический" код в каком-то файле, чтобы его импортировать/usr/local/etc/
в Windows? :) Или логику городить? Тогда почему бы просто не остановиться наlocal_settings
?Переменные окружения таких проблем не продуцируют.
systemd
Про какие неудобства идёт речь — я так и не понял. И почему они менее защищены чем файл, лежащий снаружи репозитрия.
И вот этот Ваш коммментарий:
тоже не понял. Особенно в контексте джанги выглядит странно, где конфиг вычисляется при старте приложения.
Почему лучше использовать контейнеры.
Почему не нужно использовать
supervisord
Ещё, помнится, был проект circus. Что-то типа
supervisord
, но для Python3. В бою не испытывал, да и смысла особого нет, поскольку, согласно бритве Оккама, лучше пользоваться тем, что есть.Почему не надо писать логи в файл
Почему надо писать логи в STDOUT
Но заниматься этим должно не наше приложение.
Вот Вы утверждаете, что supervisord — лишняя сущность и надо её избегать. Но при этом рекомендуете файлы с логами ротейтить, а конфиги хранить не в переменных окружения, а в файлах. Да ещё и велосипед какой-то для этого придумали. На мой взгляд, это двойные стандарты :)
Если уж избавляться от лишних сущностей, то по-большому. Тезисно:
stdout
;systemd
, использовать его. Если нет, обновлять сервер, чтобы был. Или менять на другой. Ни в коему случае не использоватьsupervisord
сотоварищи.Здесь, напоминаю, только тезисы. Давайте более подробно обсудим в комментариях?
P.S. Хабраюзер ahmpro уже упоминал про 12 факторов. Рекомендую обратить внимание и почитать. Соглашаться со всем не обязательно, но крайне полезно для знакомства.