Pull to refresh

Comments 79

>демонизирует его сам systemd, он же и следит за перезапуском при падениях.
И часто падает?

Для сложных проектов с кучей конфигов рекомендую заводить отдельный проект, в нем уже хранить все конфиги (допы к системным, и самого приложения), скрипты инициализации сервера с нуля и собственно CI\CD. Ну и к самому репозиторию такого проекта доступ только у админа системы.
И часто падает?

Ну бывает, конечно. Например в одном из потоков выбрасывается исключение, что приводит к падению всего приложения. Или asyncio точно так же может что-то выкинуть и завалить сразу все.
Про конфиги в отдельном репозитории — согласен, это вариант, но, конечно же, с сервера доступа к этому репозиторию доступа быть не должно.

Пожалуйста, поясните, почему вам не подходит supervisord.

Видимо, потому, что supervisord не подходит к python3. Во всяком случае, какое-то время не подходил (какие-то костыли там пилили, да), поэтому мной, например, был отправлен на свалку.

IMHO, зачем нужна еще одна сущность в виде supervisord, если все можно решить исползуя возможности systemd?
Ну и автор написал, что "если у вас нет systemd, то можно смотреть в сторону supervisord".

А зачем использовать сторонние решения, если все есть из коробки? Тем более systemd вроде стабильный. Если у вас есть какие-то плюсы supervisord по сравнению с systemd или специфичные случаи использования, поделитесь, мне интересно. Мне для Django+Celery хватает systemd за глаза.
Собственно, уже ответили. supervisord — это еще одна программа, приносящая свои собственные баги. Ждать что она будет надежнее чем системный сервис смысла нет, да и нет у него того что не может systemd.
Если уж деплоить приложение без докера, то лучше тогда уж использовать какой-нибудь ansible проект, который разворачивает проекты. Секреты хранить с помощью ansible vault, например. Если используется gitlab и местный CI, то так же загружать можно в настройки CI секреты. Я последнее время использую три окружения для проектов: dev, staging, prod. И уже в зависимости от этой переменной окружения строится окончательный settings.py. То есть мне не нужно заходить на сервер и править там конфиги вручную. Если проект небольшой, то просто docker-compose и на сервере .env файл со всеми переменными.
Проблема: можно случайно закоммитить файл
.gitignore
Используйте сервис logrotate. Ниже приводится простой конфиг для логов celery.

Почему не journald, если у вас уже есть systemd? Просто пишете в stdout и профит. И ротация из коробки.


переменные окружения. Тоже не очень безопасно, не очень удобно (при soft-reload например)

Эм… что? У вас же systemd, там есть EnvironmentFile, очень удобно. И особой разницы между local.py и этим решением с точки зрения безопасности нет. Если хотите безопасности, вперед, в vault.


Зато настройки, а вернее, хранение только sensetive данных в настройках окружения, а всего остального в файле в проекте позволяет легко узнать разработчикам, какие настройки используются. И это довольно часто бывает важно.

Насчет journald — да, это хорошая идея, спасибо. Надо бы почитать доки и попробовать.
Однако не очень кроссплатформенно. Если приложение пишет в файл, то это будет работать везде, а уж что делать с логами — можно решить системными инструментами (logrotate).

Про локальные параметры я написал что это только моя рекомендация. Каждый делает как удобно в его проекте.
Я встречал совершенно разные подходы — и settings_ivanov.py / settings_stage.py в репозитории, и замену settings.py на нужный при переустановке.
Меня учили отделять данные от кода и я считаю что это правильно.
Переменные окружения легко не перечитаешь без перезапуска, в отличие от конфига. Да и при запуске любого дочернего процесса он наследует все переменные, что не всегда хорошо. Мне это не нравится и я не использую.
Однако не очень кроссплатформенно.

Кроссплатформенно, так как вы пишите в stdout. А обрабатывать stdout можно уже инструментами платформы, в нашем случае это journald. На винде есть какие-то другие штуки. Ну и более того, вполне можно предположить, что ваше приложение все равно не кросс-платформенно.


Меня учили отделять данные от кода и я считаю что это правильно.

Простите, какие данные? Вы предлагаете по не совсем ясным причинам отделять настройки приложения от самого приложения. Какой профит вы от этого получаете? Минусы я знаю, они заключаются в том, что надо каждый раз дергать человека, который занимается деплоем, что бы он правил файл настроек.

можно предположить, что ваше приложение все равно не кросс-платформенно.

Предположение не очень обоснованно. У меня есть кросс-платформенные проекты. На windows запускается django-приложение под апачем через WSGI и вот чего я точно не хочу, так это логов приложения в логах апача.
На мой взгляд, модуль logging отлично справляется с выводом в файлы (разные) и эти файлы удобно обрабатывать (чем-то сторонним), но смешивать я не советую.

Простите, какие данные?

Настройки — это данные.
У меня идея следующая: в папке проекта не должно быть никаких файлов, которые изменяются в период между обновлениями версии. Код — отдельно, данные, логи, временные файлы, конфиги — в своих папках.
Далеко не всегда получается так что весь проект написан на одном языке и чтение параметров из единого json/yaml/cfg файла удобнее.
в папке проекта не должно быть никаких файлов, которые изменяются в период между обновлениями версии.

Эм… а код? Стоит заметить, что очень часто код и настройки меняются одновременно, потому что добавляется новый функционал, который как бы нужно настраивать.


Далеко не всегда получается так что весь проект написан на одном языке и чтение параметров из единого json/yaml/cfg файла удобнее.

Эм… это как у вас так получается проект на разных языках? Вызов скриптов на других языках?

Тем не менее, код — сам по себе, настройки — отдельно. Если вы посмотрите в файловую систему Linux, вы примерно таким образом организованные файлы и найдете, это не я придумал.

Эм… это как у вас так получается проект на разных языках? Вызов скриптов на других языках?

Ну а что такого? Чем меньше языков, тем проще, конечно, но не всегда так получается. Особенно когда в проект приходят новые творческие участники.
Тем не менее, код — сам по себе, настройки — отдельно. Если вы посмотрите в файловую систему Linux, вы примерно таким образом организованные файлы и найдете, это не я придумал.

Да, и поэтому каждый пакет тянет с собой настройки или же приложение заполняет дефолтные настройки. А потом еще надо при баг-репортах добавлять конфиги, иначе ничего не понятно.
Но у вас то окружение, которое вы контролируете, можно позволить себе больше в угоду удобству.

ну, вот поэтому вы и воротите всякую фигню, потому что в докер не умеете =)
Ага, докер, блокчейн, devops, go и еще несколько модных слов в каждом проекте помогают сохранить лицо перед начальством и добавить уверенности в отчеты инвесторам.
А так же масштабировать приложение*
*кроме блокчейна, он не помогает.

Да, гораздо лучше настраивать все на сервере исключительно ручками и там хранить настройки. Когда вы один, можно вообще делать любую дичь и все будет отлично, а вот как только начинается нормальная работа, то вот тут приходится делать по нормальному. И так уж получилось, что контейнеризация (в том числе и докер) заставляет задуматься о том, что такое нормально немного раньше.


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

:sarcasm:
Вы забыли kubernates (какой же докер без него?)
И aws. Тогда уж и terraform.
gitlab, ansible
Плюс consul, vault
Что еще модно… ceph как block storage
Мониторинг, прометеус, elk для логов
Своя автономная зона, cdn, сеть фильтрации трафика и ddos
Кавку куданибудь там сбоку надо обязательно, как вишенку на торт
зачем бросаться в крайности? можно просто стараться следовать 12factor.net
Где вы храните логи и как вы их просматриваете? journalctl?
Я бы еще добавил, что когда разрабатываешь проект параллельно пиши например bash скрипт, где ты описываешь как все ставишь. Таким образом при переустановки системы или переезде на другой сервер можно за пару минут все поставить без боли. Возможно для больших и серьезных проектов надо что-то по серьезнее типа докера или ansible как уже выше говорили.

Для любых проектов нужен ansible или другая система. Я понимаю предупреждения против докера, особенно на мелких проектах, но если вы не используете ansible или альтернативы — вы делаете неправильно.

Был, например, CFEngine. А потом еще Chef и puppet. До него жили на самописных скриптах и жили плохо, да.


А еще люди умирали 1000 лет назад в 30 лет в том числе от антисанитарии. Будете ли иронизировать о том, как же люди жили раньше без мыла?


У вас будут реальные аргументы, почему не стоит использовать ansible на мелких проектах, кроме "Ой, сложна"? Поверьте, оверхед по времени ближе к середине проекта выравнивается, а если вы уже имеете опыт его использования, то самописные скрипты требуют значительно больше времени на кастомизацию.

Ну так знаете можно дохрена чего нагородить в маленький проект. И докер и систему мониторинга и риал тайм мониторинг с отправкой сообщений на приватный телеграм канал… Все зависит от потребностей. Я ни в коем случае не говорю, что ансибль плохо. Просто не вижу смысл ставить его приоритетным для маленького проекта, как минимум ансибль это дополнительная сущность, а чем меньше сущностей используешь, тем надежнее. Когда уже будет понятно, что проект будет разрастаться, можно и ансибль прикрутить. Научиться использовать его конечно будет не лишним.
У меня бывало проекты умещались меньше чем на 50 строк баш скрипта, нафиг мне тут ансибль?

Вы пишете доку по тому, как развернуть ваш проект? Вот ansible это дока, только которая еще и выполняется.
В добавок к этому размер ansible скриптов всегда четко следует размерам проекта или связанной инфраструктуры. Если у вас просто баш скрипт, то вы или выгружаете его на сервер через ansible или разворачиваете там git репозиторий с проектом (и ключи для него). И все, это делается довольно быстро.


Ну и "а чем меньше сущностей используешь, тем надежнее", подразумевает, что вы не будете делать то, что делает ansible, но нет, у вас все равно будут самописные скрипты по деплою. И вместо того, что бы использовать продукт, который для этого разрабатывался вы предлагаете на коленке напилить ad-hoc решение, у которого не будет никаких преимуществ. Зачем?

sudo ln -s /opt/myservice/config/systemd/*.service /etc/systemd/system/

Взламываем сайт, залезаем в файл /opt/myservice/config/systemd/*.service (который на запись естественно никто не закрыл), меняем в файле User=myuser на User=root и ExecStart по вкусу — опа, рут успешно получен хацкером.

Хороший пример, спасибо. Да, наверное тогда правильнее будет скопировать вместо симлинков и поставить права?

Вроде как сервисы в systemd вообще может создавать сам пользователь без вмешательства рута (где-то в ~/.config/systemd если верить арчевики), но я тут не спец, сам я по старой привычке юзаю supervisord с копированием конфигов)

Еще бы добавил в важные моменты логирования — copytruncate в logrotate сначала делает копию, а потом урезает файл. Все строки, которые будут записаны в лог после копирования но перед урезанием успешно потеряются.
Да, к сожалению, вы правы. Спасибо, добавил

Вот Вы утверждаете, что supervisord — лишняя сущность и надо её избегать. Но при этом рекомендуете файлы с логами ротейтить, а конфиги хранить не в переменных окружения, а в файлах. Да ещё и велосипед какой-то для этого придумали. На мой взгляд, это двойные стандарты :)


Если уж избавляться от лишних сущностей, то по-большому. Тезисно:


  1. Логи писать исключительно в stdout;
  2. Если есть systemd, использовать его. Если нет, обновлять сервер, чтобы был. Или менять на другой. Ни в коему случае не использовать supervisord сотоварищи.
  3. Контейнеры по возможности использовать, причём с readonly-файловой системой.
  4. Хранить настройки в переменных окружения

Здесь, напоминаю, только тезисы. Давайте более подробно обсудим в комментариях?


P.S. Хабраюзер ahmpro уже упоминал про 12 факторов. Рекомендую обратить внимание и почитать. Соглашаться со всем не обязательно, но крайне полезно для знакомства.

Почему не надо писать логи в файл


  • Потому что писать на жёсткий диск долго;
  • Потому что запись в файловую систему привязывает нас к собственно файловой системе.
  • Потому что приложение обязано сообщать о произошедших событиях, а не сохранять их.

Почему надо писать логи в STDOUT


  • Потому что это стандартный вывод. Он есть везде и везде более-менее одинаково работает
  • Потому что стандартным выводом процесса может управлять файловая система (или пользователь). Этот стандартный вывод можно грепать в реальном времени; его можно писать в journald, его можно отправлять в fluentd, его даже можно писать в файл и логротейтить :)
    Но заниматься этим должно не наше приложение.

Почему не нужно использовать supervisord


  • Потому что внешняя зависимость, без которой можно обойтись
  • Потому что глючный
  • Потому что python2

Ещё, помнится, был проект circus. Что-то типа supervisord, но для Python3. В бою не испытывал, да и смысла особого нет, поскольку, согласно бритве Оккама, лучше пользоваться тем, что есть.

Потому что глючный

Как активно пользующий supervisord заявляю, что нет


Потому что python2

Как кодящий на обоих питонах заявляю, что ничего плохого в этом нет


(с первым пунктом не спорю)

Я буквально на днях переносил старую кодовую базу на новое железо и старался использовать тот же софт, что и раньше. Так вот, как supervisord не умел 4 года назад убивать воркеры celery при перезапуске задачи, так и не умеет до сих пор. В принципе, этого одного уже достаточно, чтобы им не пользоваться :-)

Зачем supervisord'у убивать воркеры celery? Ему нужно убить лишь мастер-процесс, а мастер-процесс должен убить всех своих воркеров самостоятельно — именно так у меня работает celery внутри supervisord уже несколько лет, всё прекрасно и не глючит.
Виноват, а воркер целери не может быть мастер-процессом?

Я не разбирался в деталях, как работает celery и что он может, но я его сейчас запускаю как celery -A myapp worker --concurrency=2 -B и это запускает четыре процесса: мастер, воркер, воркер и celerybeat. Когда я отправляю SIGINT мастер-процессу, остальные три процесса автоматически завершаются следом за ним.

Почему лучше использовать контейнеры.


  • Потому что если выломают приложение, пострадает только контейнер, а на хост-система
  • Потому что можно добиться идентичного окружения в на бою и на машине разработчика, даже если на сервере Linux, а у разработчика — macos (или windows, ни к ночи упомянута будет).
  • Разработка в контернере с файловой системой, примонтированной в ReadOnly дисциплинирует: приходится писать так, чтобы не привязываться к собственно файловой системе. Поэтому приложения получаются такими, что их легче масштабировать
  • Это, чёрт возьми, модно!
Потому что если выломают приложение, пострадает только контейнер, а на хост-система

Если выломают приложение, то пострадает только приложение, если руки не кривы нужные юниксовые права правильно настроить


Потому что можно добиться идентичного окружения

Идентичное окружение — зло. Хорошо сделанное приложение должно одинаково хорошо работать не а каком-то ОДНОМ, а а ЛЮБОМ окружении — от фряхи на большом сервере до малинки в ближайшей кофеварке. Если разработчик не в состоянии обеспечить работу приложения за пределами одного окружения, это указывает на кривизну его рук, а контейнеры лишь прикроют говнокод в данном случае. (Есть, конечно, редкие исключения, когда делать работоспособность в любом окружении слишком геморройно, но например обычные веб-сайты абсолютно точно не в их числе.)


Контейнер всё стерпит, ага

Это, чёрт возьми, модно!

Напомнить, кем являются 95% населения?)

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

Сейчас все стараются изолировать код от системного окружения где это запускается (12 факторов этому способствует), то на что вы собственно и обратили внимание.
Контейнеры лишь удобный механизм сделать это с минимальными трудозатратами, сводя к минимуму необходимость разработчику думать о том, где его код будет запущен.
То, что некоторые юзают контейнеры по старинке, запихивая всякую фигню в них, то дело в некомпетентности, а не в самих контейнерах.

Это, чёрт возьми, модно!


Мода лишь следствие удобства, которое контейнеры дают. Да, не бесплатно, но кто говорил, что это серебряная пуля? :D
Под идентичное окружением имелся ввиду один и тот же набор (с совпадением вплоть до минорных версий) библиотек и системных зависимостей

Если нужно воспроизвести баг, который в других условиях не воспроизводится, то это хорошо. Но опять же затачиваться целиком на одну конкретную версию плохо: хорошо сделанное приложение должно работать в пределе на ЛЮБЫХ версиях зависимых библиотек, тем более в пределах одной мажорной версии (суровая реальность, конечно, накладывает ограничения из-за всяких там багов и обратной совместимости, но стремиться к поддержке по возможности больше числа версий нужно). Идеальное по моему мнению приложение должно работать где угодно от устаревшей центоси до самой свежей убунты, если нет уважительных причин для иного (лень разработчиков — не уважительная причина).

Идеальное приложение не имеет зависимостей и вообще написанного кода, так что давайте без фантазий :D
Если выломают приложение, то пострадает только приложение, если руки не кривы нужные юниксовые права правильно настроить

А вдруг кривые. Зачем оставлять потенциальную возможность?


Идентичное окружение — зло.

Голословное утверждение. И в корне неверное. Хорошее приложение должно делать только одно: выполнять поставленную бизнес-задачу с разумными затратами русурсов (времени, денег, мощностей). Всё остальное — пожелания, причём не всегда критичные.


Даже если в контейнере говнокод — да ради бога, если он работает, не просит каши и его можно поддерживать. Действительно, контейнер всё стерпит. Хорошее правило.


Требование же чтобы работало везде… А зачем?

А вдруг кривые

Тогда и контейнер не поможет :D


Действительно, контейнер всё стерпит. Хорошее правило. Требование же чтобы работало везде… А зачем?

Вот примерно из-за такого мнения многих людей я хочу свалить с этой прогнившей планеты подальше на какой-нибудь Марс. Все живут по принципу фигак-фигак и в продакшен, совсем чувство прекрасного растеряли, нафиг так жить

Если руки кривые (а это бывает… ох бывает), то контейнер кокрастыке поможет.


Вот примерно из-за такого мнения многих людей я хочу свалить с этой прогнившей планеты подальше на какой-нибудь Марс. Все живут по принципу фигак-фигак и в продакшен, совсем чувство прекрасного растеряли, нафиг так жить

Из-за какого мнения? Программирование — это не искусство, это прикладная область, за неё кушать дают. А хочется прекрасного — участвуйте в олимпиадах. Денег не заплатят, ну так художник и должен быть голодным :)

Программирование — это не искусство

Вот примерно из-за такого мнения и хочу свалить :)


А хочется прекрасного — участвуйте в олимпиадах

А я участвовал, в них обычно как раз лютейший фигак-фигак-и-в-продакшен)

даже если на сервере Linux, а у разработчика — macos

А отличная от линукса ОС, кстати, автоматически означает необходимость использования виртуальной машины (Docker на макоси использует VirtualBox, а на винде Hyper-V, если я правильно понял его документацию). Но виртуалка по сути ведь является контейнером тоже — зачем пихать контейнер внутрь контейнера? Можно выкатить ansible-плейбук на убунточку, запихнутую в виртуалбокс, и получить идентичное продакшену окружение — я для своих сайтов делаю именно так.

vagrant, provisioning энсимблом и автоматический накат дампов с лайва(или транка) просто удобнейший способ локальной разработки, для нового разработчика это пара команд:
git clone
vagrant up
./setup.sh (внутри виртуалки, если проект достаточно сложный и требуется донастройка, которая тоже автоматическая и заранее написана, используя теже ansible-роли)

Про хранение настроек


На мой взгляд, среди всех перечисленных способов Вы порекомендовали самый неудачный. По сути, он имеет все те же самые минусы, что и local_settings.py, кроме одного: его случайно не поместить в репозиторий. Да и то плюс этот сомнительный, с учётом .gitignore


Зато привносит дополнительные минусы:


  • Магия. Сами упомянули.
  • Лишняя зависимость. Не столько даже yaml, сколько необходимость размещать этот "магический" код в каком-то файле, чтобы его импортировать
  • Захардкоженные пути. По идее, они должны быть одинаковы, но где, к примеру, искать /usr/local/etc/ в Windows? :) Или логику городить? Тогда почему бы просто не остановиться на local_settings?

Переменные окружения таких проблем не продуцируют.


  • Они специально придуманы для конфигурации окружения, в котором запускается процесс. Конфигурация от народа!
  • Их поддержка есть в каждой операционной системе
  • Их использование всегда одинаково и на windows, и в macos, и в linux, и в докер-контейнерах.
  • Они случайно не попадут в версионный контроль
  • Они прекрасно интегрируются с тем же systemd

Про какие неудобства идёт речь — я так и не понял. И почему они менее защищены чем файл, лежащий снаружи репозитрия.


И вот этот Ваш коммментарий:


Переменные окружения легко не перечитаешь без перезапуска, в отличие от конфига. Да и при запуске любого дочернего процесса он наследует все переменные, что не всегда хорошо. Мне это не нравится и я не использую.

тоже не понял. Особенно в контексте джанги выглядит странно, где конфиг вычисляется при старте приложения.

Да, вы все правильно написали, но, простите, однобоко.
Я уже приводил аргументы, не вижу смысла снова повторять, тем более гуглится довольно много еще аргументов почему использовать переменные окружения не все рекомендуют.

тоже не понял. Особенно в контексте джанги выглядит странно, где конфиг вычисляется при старте приложения.

Ок, давайте возьмем тот же gunicorn с его child-процессами. Если вы подложили новый код (или новые настройки даже) и выполнили «kill -HUP», то он аккуратно перезапустит воркеры и они начнут выполняться уже с новым кодом, трафик потерян не будет. Однако переменные окружения будут те же самые, что и при старте мастера. И поменять их у мастера вы не сможете без рестарта процесса.
Окей, про gunicorn принимается, убедили, хотя случай довольно странный.

Других аргументов, извините, не увидел. И по старинному правилу «бремя доказательства лежит на обвиняющем», прошу всё же показать аргументы :)
Ну еще пример с переменными окружения там где дочерние процессы наследуют окружение родителя. Запустили вы в subprocess какой-нибудь внешний процесс, а он получил все ваши пароли, а потом в крэшдампе отправил своим разработчикам.
В сети бродят страшилки про «ps -eww », но у меня не получилось повторить.
Можно привести из пальца высосанный вектор атаки когда, получив доступ к выполнению кода с непривилегированным пользователем можно попробовать почитать все файлы в /proc/<все pidы>/environ (для процессов из-под этого пользователя), но слишком маловероятна удача.
Все перечисленные в этом комменте проблемы лечатся запуском из-под другого пользователя и очисткой переменных окружения перед запуском, но все ли так делают?

Я реально не вижу удобства в переменных окружения. Мне лично удобнее прочитать переменную DATABASES как dict из yaml/json файла чем составлять ее из нескольких переменных окружения.
Запустили вы в subprocess какой-нибудь внешний процесс, а он получил все ваши пароли, а потом в крэшдампе отправил своим разработчикам.

Вроде бы в subprocess можно создавать чистое окружение, но аргумент всё же
принимается, спасибо.


Я реально не вижу удобства в переменных окружения. Мне лично удобнее прочитать переменную DATABASES как dict из yaml/json файла чем составлять ее из нескольких переменных окружения.

Хе-хе. Если б одна запись составлялась из нескольких переменных, это не было так круто, как есть. Жизнь — она горазда элегантнее.


Уже стало хорошей традицией записывать все кренделя в виде DSN


DATABASE_URL=postgres://user:supersecretpassword@hostname.com:5432/db?timeout=20
ANOTHER_DATABASE_URL=mysql://user:passwrd@mysqlserver.com/mysqldb

а настройки выглядят так:


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, если не определена. Но многочисленные обвязки позволяют писать очень, на мой взгляд, довольно прикольно. Например, так:


SECRET_KEY = env('SECRET_KEY', cast=str, default='')
DEBUG = env('DEBUG', default=False, cast=bool)
ALLOWED_HOSTS = env('ALLOWED_HOSTS', cast=list, default=['*']) # разделяет строку по запятой

или так:


SECRET_KEY = env.string('SECRET_KEY')
DEBUG = env.boolean('DEBUG')

кто во что горазд, короче.


Разумеется, это тоже внешняя зависимость, как и в Вашем случае. Но всё же, по чесноку, что более выразительно и явно:


import_settings("/usr/local/etc/myservice.yaml")

или


DEBUG = env('DEBUG', default=False)
SECRET_KEY = env('SECRET_KEY_FROM_MY_ENV_VAR')

? Мы же помним, что явное лучше неявного. Кто его знает, какие переменные понаинжектили злобные русские хакеры в конфиг, который Вы инклюдите? :)

У меня тут на одном моём сайте два десятка настроек только из тех, что переопределены для боевого окружения, плюс при желании можно насчитать ещё полсотни-сотню настроек со значениями по умолчанию. Лично мне запихивать это всё в переменные окружения было бы не очень удобно)
Ну да, засунуть эти оверрайды в отдельный файл — гораздо более продуманная тактика :)
А как бы вы сделали с переменными окружения? Куда бы вы их засунули?
Куда бы я засунул переменые окружения? Сложный вопрос. Наверное, я бы их и оставил в переменных окружения :)
Но переменные окружения вроде ведь не берутся сами по себе из воздуха? Откуда они появляются?
Их выставляет пользователь. Или сама система. Или их передаёт процесс-родитель. Возможны варианты, короче. Я суть вопроса, признаться, не уловил

Выставляет пользователь где и как? Процесс-родитель у себя их откуда берёт? Какие именно варианты возможны?


Я суть вопроса, признаться, не уловил

Вот у меня двадцать условных параметров для сайта и некий ваш условный контейнер, например — как конкретно мне запустить «неважно что там: gunicorn, ./manage.py shell_plus, ./manage.py migrate» с этими вот двадцатью параметрами с использованием этих ваших переменных окружения?

Да они точно так же пишут их в файл манифеста для докера, только добавляют там еще больше настроек для самого докера.

Вот прям нутром чую, что подвох. Но отвечу как есть


Самое простое — перечислить их в командной строке:


$ DEBUG=1 SECRET_KEY=123 ./manage.py migrate
$ DEBUG=1 SECRET_KEY=123 ./manage.py shell_plus

Или выставить их в текущий сеанс. Тогда они будут работать для всех процессов, порождённых текущей сессией шелла


export DEBUG=1
export SECRET_KEY=123
$ ./manage.py migrate
$ ./manage.py shell_plus

Если речь о докере, то там при запуске можно передать ключ типа --env-fle=/path/to/env. Обычный текстовый файл с парами KEY=VALUE по одной на строку.


Для systemd можно такой же файл указать. Для питона существует куча пускалок, которая смотрит, нет ли файла .env в текущей директории. И если есть, считывает переменные и помещает их собственно в окружение. Такая штука, правда, больше про разработку, а не сервер.

Вот про командную строку уж лучше бы вы не писали, право слово. Я даже не приводил это в аргументах, ибо «ps eww » сразу всех сдаст. Да и, серьезно, в командную строку передавать over 20 параметров?

Текущий сеанс — еще лучше… Даже при разработке — если у меня на проект 5 окон терминала открыто, как мне с этим управляться?

В итоге все равно пишем в файл, просто по другому оформленный.

Ну вопрос-то как был задан: как в окружение попадают данные. Как спросили, так и ответил. Ну, точнее, как понял вопрос, так и ответил.


Да и, серьезно, в командную строку передавать over 20 параметров?

Нет никаких шеллов. Не нужны :)


В итоге все равно пишем в файл, просто по другому оформленный.

Ну да. При этом я не вижу ничего плохого в том, чтобы хранить эти переменные в .service, при условии защиты последних, конечно.

Позвольте полюбопытствовать, а чем --env-fle=/path/to/env принципиально отличается от условного --something=/path/to/local_settings.yaml?)

Принципиально, наверное, ничем. Не считая того, что local_settings.yaml каждый пишет так, как удобно ему, а .env — признанный индустрией формат, который принимают такие монстры, как docker, systemd, heroku и куча сошек поменьше.


Хотя нет, есть принципиальное отличие. local_settings.yaml обязан быть, а .env — нет. Это лишь один из удобных девелоперу способов указать переменные окружения. На продакшн так стараются не делать, конечно.


У меня такого файла вообще нет, например.

Уже стало хорошей традицией записывать все кренделя в виде DSN

Я, было, в комментарии сначала написал что DATABASES не более чем пример, что DB URI удобнее конечно же. Но потом стер, думал и так понятно :)

Я тоже могу спросить что красивее выглядит:
AWS_API_KEY = env('QWERTY', default=None)
AWS_API_SECRET = env('ZXCVB', default=None)
AWS_BUCKET_NAME = env('mybucket', default=None)

Или:
AWS:
    API_KEY: QWERTY
    SECRET: ZXCVB
    BUCKET: mybucket

И вопрос — какое конкретно значение сейчас на запущенном сервисе на сервере?

DEBUG = env('DEBUG', default=False)
SECRET_KEY = env('SECRET_KEY_FROM_MY_ENV_VAR')

а теперь вас пригласили разобраться с проблемой на сервере. Вы заходите по ssh и видите запущенный какой-то сервис. Его раньше поддерживал какой-то разработчик, но сейчас его нет.
Вроде есть README, в нем инструкция как запускать, какие переменные окружения ставить. В логах совершенно не то что ожидается. Вы останавливаете, затем снова запускаете с теми же (наверное?) переменными. Все работает как надо. Теперь остается гадать — а с ЧЕМ было оно запущено до этого? Возможно сервис запустили вручную с переменными для дебага? А хз теперь.
Это реальный пример из жизни, года два назад еще.

Когда все в одном (ок, пусть даже в нескольких) файлах — мне кажется что все-таки удобнее их контролировать, чем собирать по разным местам.
Из последнего примера — возможный контраргумент — можно запустить сервис, а потом поменять конфиг. Но согласитесь, это гораздо меньше вероятность сделать случайно чем запустить сервис с другими переменными окружения.

На мой взгляд, пример с энвами более красивый. Даже не потому что там энвы, он был бы красивее даже с хардкодом. Из тех хотя бы соображений, что мы определили какие-то переменные сами и знаем, что ничего неожиданного там нет. и никто случайно или по злому умыслу не втыкнул DEBUG = True на продакшн.


И вопрос — какое конкретно значение сейчас на запущенном сервисе на сервере?

На такие вопросы отвечают 12 факторов. И, что немаловажно, отвечают и за свои слова :)


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

Ок, это уже вопрос вкусовщины, я так и знал что все может свестись к холивару.
Последний вопрос: имея приложение джанги как мне запустить консольный отладчик (`manage.py shell_plus`) с теми же параметрами, с которыми стартует приложение? А еще миграции и остальные команды?

И на этот вопрос тоже отвечают 12 факторов.


Вкратце, идея такая: вводится понятие "ревизия приложения". Ревизия приложения состоит из ревизии кода (натуральной ревизии, из гита которая), списка зависимостей и набора переменных окружения.


Когда нужно задеплоиться: в случае, если появился новый код или поменялось окружение (появилась \ изменилась \ удалилась переменная), создаётся новая ревизия приложения. Технически — это создаётся новый образ, в который помещается кодовая ветка, устанавливаются с нуля все зависимости. И переменные.


Далее на основе этого образа запускается контейнер. И неважно что там: gunicorn, ./manage.py shell_plus, ./manage.py migrate — образ один, набор переменных один. После завершения процесса контейнер умрёт.

Простите, я не могу вам ответить. Как только я скажу что-то против контейнеров, сразу налетит толпа и растопчет тут все, я к такому не готов.
Контейнеры — это неплохо, но не всегда есть прямой смысл их использовать.
Я не вижу необходимости менять у себя сложившуюся практику и считаю что она логична и работает хорошо. Я не пишу ультимативных призывов делать именно так. Количество добавивших статью в закладки говорит о том, что многим что-то показалось полезным.

Мне кажется мы в этой ветке достаточно обсудили преимущества и недостатки переменных и конфигов.

12 факторов — это не конкретная имплементация, это подход. И уж тем более не про контейнеры. Её можно реализовать и на одной машине, хоть и сложнее изоляцию организовать, наверное.


Статья Ваша, безусловно, полезна. Хотя бы вот этим холиваром :) к тому же, я вот узнал, что systemd умеет в EnvironmentFile. Вот реально этого не знал


Понятно, что у каждого опыт свой, но у меня в голове уже вряд ли уложится, как это так можно — вручную заходить по ssh на сервер, чего-то там править. Как можно не использовать контейнеры. Как можно деплоить приложения, не имея гарантии, что они запустятся в том окружении, которое я указал.


Так что давайте и вправду закроем эту тему :)

sudo chown root: /opt/myservice/config/systemd/*.service
sudo chmod 770 /opt/myservice/config/systemd/*.service
sudo cp /opt/myservice/config/systemd/*.service /etc/systemd/system/


Чисто в целях повышения образованности:
Первая команда — лишняя. Т.к команда cp, выполненная через sudo и так сделает владельцем файла root.
Вторая команда тоже сделает не совсем то, что вы, видимо, ожидаете. Если файла в целевом каталоге нет, то после копирования его права будут не 770, а 750. В данном случае это не играет большой роли, однако.
А если целевые файлы есть, то вообще не имеет значение какие права вы выставляете в исходном файле — они не будут «скопированы» в целевой.
JFYI.
Sign up to leave a comment.

Articles