Pull to refresh
53
Karma
0
Rating

Software engineer

Несколько советов по организации Python-приложения на сервере

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


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


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

Несколько советов по организации Python-приложения на сервере

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


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

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


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

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

Несколько советов по организации Python-приложения на сервере

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


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


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


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

Несколько советов по организации Python-приложения на сервере

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


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


$ 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 в текущей директории. И если есть, считывает переменные и помещает их собственно в окружение. Такая штука, правда, больше про разработку, а не сервер.

Несколько советов по организации Python-приложения на сервере

Их выставляет пользователь. Или сама система. Или их передаёт процесс-родитель. Возможны варианты, короче. Я суть вопроса, признаться, не уловил

Несколько советов по организации Python-приложения на сервере

Куда бы я засунул переменые окружения? Сложный вопрос. Наверное, я бы их и оставил в переменных окружения :)

Несколько советов по организации Python-приложения на сервере

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


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


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


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

Несколько советов по организации Python-приложения на сервере

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


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

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


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

Несколько советов по организации Python-приложения на сервере

Ну да, засунуть эти оверрайды в отдельный файл — гораздо более продуманная тактика :)

Несколько советов по организации Python-приложения на сервере

Запустили вы в 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')

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

Несколько советов по организации Python-приложения на сервере

Окей, про gunicorn принимается, убедили, хотя случай довольно странный.

Других аргументов, извините, не увидел. И по старинному правилу «бремя доказательства лежит на обвиняющем», прошу всё же показать аргументы :)

Несколько советов по организации Python-приложения на сервере

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


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

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

Несколько советов по организации Python-приложения на сервере

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

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


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

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


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


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

Несколько советов по организации Python-приложения на сервере

Виноват, а воркер целери не может быть мастер-процессом?

Несколько советов по организации Python-приложения на сервере

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

Несколько советов по организации Python-приложения на сервере

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


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


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


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

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


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

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


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


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

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

Несколько советов по организации Python-приложения на сервере

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


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

Несколько советов по организации Python-приложения на сервере

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


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

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

Несколько советов по организации Python-приложения на сервере

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


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

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


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

Несколько советов по организации Python-приложения на сервере

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


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


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

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


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

Information

Rating
Does not participate
Registered
Activity