Советы по настройке uWSGI в производственной среде
В статье даны рекомендации по настройке uWSGI в производственной среде. Несмотря на свою мощь, настройки uWSGI по умолчанию обусловлены обратной совместимостью и не идеальны для новых развертываний. Много полезных функций могут быть пропущены из-за их большого количества и сложной документации. В этой статье будет предложено создание базовой конфигурацию и ее постепенное расширение
Мы настоятельно рекомендуем всем пользователям uWSGI прочитать официальный документ «Things to Know».
Базовая конфигурация
[uwsgi] strict = true master = true enable-threads = true vacuum = true ; Удалить sockets во время выключения single-interpreter = true die-on-term = true. ; Отключение при получении SIGTERM (по умолчанию respawn) need-app = true disable-logging = true log-4xx = true log-5xx = true
Давайте рассмотрим каждую опцию подробнее.
strict Mode
strict = true
Эта опция указывает, что uWSGI не должен запускаться, если какой-либо параметр в файле конфигурации не был явно распознан uWSGI. Эта функция выключена по умолчанию из-за некоторых других функций, описанных в документе «Things to Know»:
Вы можете легко добавить несуществующие параметры в свои файлы конфигурации (в качестве, пользовательских параметров или элементов конфигурации, связанных с приложением). Это действительно удобная функция, но она может привести к головной боли при опечатках. Строгий режим (–strict) отключит эту функцию, и разрешает только допустимые параметры uWSGI.
Лучше всего включить это поле по умолчанию, поскольку риск неправильного ввода параметра конфигурации uWSGI в производственной среде достаточно высок.
master
master = true
Основной процесс uWSGI который необходим для повторного вызова worker, ведения журналов логов и управления многими другими функциями (памятью, cron, тайм-ауты worker …и т.д.). Без этой функции uWSGI — просто тень своего истинного я.
Этот параметр всегда должен быть установлен в true, только если вы не используете более сложную конфигурацию с «emperor» для развертывания нескольких приложений или не отлаживаете специфическое поведение, для которого вы хотите, чтобы uWSGI был ограничен.
enable-threads
enable-threads = true
uWSGI по умолчанию отключает потоки Python, как описано в документе «Things to Know ».
По умолчанию плагин Python не инициализирует GIL. Это означает, что в созданном вами приложение потоки не будут работать. Если вам нужны потоки, не забудьте включить их с помощью enable-threads. Запуск uWSGI в режиме многопоточности (с параметрами threads) автоматически включит поддержку потоков. Это «странное» поведение по умолчанию выбрано из соображений производительности.
Лучше оставить эту опцию включенным по умолчанию и выключать ее в каждом конкретном случае.
uWSGI имеет дополнительные плагины для интеграции с другими асинхронными решениями, такими как eventlet, gevent и asyncio (хотя они не совместимы с ASGI).
vacuum
vacuum = true
Эта опция говорить uWSGI очищать любые временные файлы или созданные им сокеты UNIX, такие как HTTP-сокеты, pid-файлы или административные FIFO.
Хранение этих файлов может создать проблему при некоторых обстоятельствах, например, если разработчик запускает uWSGI от своего собственного пользователя и становится владельцем этих файлов. Если у пользователя нет прав на удаление этих файлов, uWSGI не сможет работать должным образом.
single-interpreter
single-interpreter = true
По умолчанию uWSGI запускается в режиме нескольких интерпретаторов (interpreters), что позволяет размещать несколько служб в каждом рабочем процессе. Вот старая запись (Here) в списке рассылки об этой функции:
Несколько интерпретаторов — это круто, но есть сообщения о некоторых расширениях, которые плохо с ними взаимодействуют. Когда эта опция включена, uWSGI будет влиять на весь ThreadState (внутреннюю структуру Python) при каждом запросе. Это не так сильно влияет на производительность, но с некоторыми приложениями/расширениями, могут быть конфликты.
С ростом числа микросервисов, мы не рекомендуем размещать более одного сервиса в одном рабочем процессе. Отключение этой функции, не оказывает негативного влияния, а также уменьшает вероятность возникновения проблем с совместимостью.
die-on-term
die-on-term = true
Эта функция лучше всего описана в официальном документе «Things to Know».
До uWSGI 2.1 отправка сигнала SIGTERM в uWSGI по умолчанию означает «жесткую перезагрузку стека» вместо полного уничтожения процесса. Чтобы выключать uWSGI, вместо него используется SIGINT или SIGQUIT. Если вы абсолютно не можете жить с таким неуважительным отношением uWSGI к SIGTERM, включите опцию «die-on-term». К счастью, это некорректное поведение было исправлено в uWSGI 2.1
Вы должны включить эту функцию, потому что она заставляет uWSGI вести себя так, как ожидал бы любой здравомыслящий разработчик. Без него kill или любой инструмент, который отправляет SIGTERM (например, некоторые инструменты мониторинга системы), не смогут уничтожить (kill) uWSGI процесс.
need-app
need-app = true
Этот параметр предотвращает запуск uWSGI, если он не может найти или загрузить модуль приложения. Без этой опции uWSGI будет игнорировать любой синтаксис и ошибки импорта, возникающие при запуске, и запустит пустую оболочку, которая вернет 500 для всех запросов. Это может создать проблему, поскольку системы мониторинга могут наблюдать, что uWSGI успешно запущен, и предположить, что приложение доступно для запросов на обслуживание, хотя на самом деле это не так.
uWSGI может запускаться без загрузки приложения, так как это поведение используется для загрузки приложение динамически после запуска. Вот комментарий GitHub от unbit, разработчики uWSGI, в отношении этого поведения:
… потому что в 2008-2009 годах это был единственный поддерживаемый способ настройки приложений (веб-сервер сообщал серверу приложений о загрузке приложения). Если вы попытаетесь вернуться к тому времени (когда даже концепция прокси не была так широко распространена), вы поймете, почему это было довольно очевидное решение в программном обеспечение, предназначенного для хостинга.
Теперь все изменилось, но все еще существуют десятки клиентов (платящих клиентов), которые используют этот тип настройки, и поэтому мы не можем изменить настройки по умолчанию.
Logging
disable-logging = true
По умолчанию в uWSGI включено довольно подробное ведение журнала. Целесообразно отключить стандартное ведение логов uWSGI, особенно если ваше приложение генерирует краткие и содержательные логи.
log-4xx = true log-5xx = true
Будьте осторожны: если вы решите отключить стандартный вывод журнала uWSGI, мы рекомендуем использовать параметры log-4xx и log-5xx, чтобы повторно включить встроенную запись в журнал uWSGI для ответов с кодами состояния HTTP 4xx или 5xx. Это гарантирует, что критические ошибки будут всегда регистрироваться.
Управление процессами Worker
Worker Recycling
Worker Recycling может предотвратить проблемы, которые становятся очевидными со временем, такие как утечки памяти или непреднамеренные состояния. Однако в некоторых случаях это может повысить производительность, поскольку у более новых процессов появляется новое пространство памяти.
uWSGI предлагает несколько способов утилизации workers. Предполагая, что ваше приложение сравнительно быстро перезагружается, все три метода, перечисленные ниже, должны быть практически безвредными и обеспечивать защиту от различных сценариев сбоев.
max-requests = 1000 ; Перезапуск workers после указанного количества запросов max-worker-lifetime = 3600 ; Перезапуск workers после указанного времени в сек reload-on-rss = 2048 ; Перезапуск workers после потребления указанного количества памяти worker-reload-mercy = 60 ; Время ожидания перед уничтожение процессов workers
Эта конфигурация перезапустит процесс worker после любого из следующих событий:
- было обработано 1000 запросов
- worker потребил более 2 ГБ памяти
- по прошествию 1 часа
Динамическое масштабирование Worker (cheaper)
При включение подсистемы uWSGI cheaper, основной процесс порождает worker в ответ на увеличение трафика и постепенно останавливает worker по мере снижения трафика.
Существуют различные алгоритмы, позволяющие определить, сколько worker должно быть доступно в данный момент. Алгоритм busyness пытается всегда иметь запасных worker, что полезно при ожидании неожиданных скачков трафика.
cheaper-algo = busyness processes = 500 ; Максимально допустимое количество workers cheaper = 8 ; Минимально допустимое количество workers cheaper-initial = 16 ; workers, созданные при запуске cheaper-overload = 1 ; Продолжительность цикла в секундах cheaper-step = 16 ; Сколько workers на spawn за один раз cheaper-busyness-multiplier = 30 ; Сколько циклов ждать, прежде чем убивать workers cheaper-busyness-min = 20 ; Ниже этого порога убивает workers (если стабильно для множителя циклов) cheaper-busyness-max = 70 ; Выше этого порога порождаются новые workers cheaper-busyness-backlog-alert = 16 ; Порождает аварийных workers, если в очереди ожидает больше этого количества запросов cheaper-busyness-backlog-step = 2 ; Сколько аварийных workers будет создано, если в очереди слишком много запросов
Hard Timeouts (harakiri)
harakiri = 60 ; Насильственно убивает workers через 60 секунд
Опция —harakiri запускает SIGKILL через указанное количество секунд. Без этой функции зависший процесс может остаться застрявшим навсегда. Если проблема, вызывающая зависание процесса, является достаточно распространенной, вы можете столкнуться с ситуацией, когда не будет доступных workers, и ваша служба станет недоступной.
Harakiri может быть установлен динамически в коде Python с помощью модуля uwsgidecorators, но это является дополнением.
Разрешение получение сигналов Workers от ОС (py-call-osafterfork)
py-call-osafterfork = true
По умолчанию workers не могут получать сигналы ОС. Этот флаг позволит им получать такие сигналы, как signal.alarm. Это необходимо, если вы собираетесь использовать модуль signal в worker.
Из-за относительно резкого характера kill и перезапуска worker мы склонны использовать signal.alarm(seconds), чтобы попытаться изящно использовать время ожидания запросов, прежде чем прибегнуть к харакири.
Эта функция должна быть включена по умолчанию, поскольку мы ожидаем, что процессы будут реагировать на сигналы, которые им отправляются.
Процесс Labeling
По умолчанию имена процессов worker uWSGI — это просто команды, используемые для их запуска.
bgreen89 180976 180847 0 17:20 pts/27 00:00:00 uwsgi3.6 --module my_svc:service --http11-socket :29292 --ini etc/my_svc_uwsgi.ini --need-app bgreen89 180977 180847 0 17:20 pts/27 00:00:00 uwsgi3.6 --module my_svc:service --http11-socket :29292 --ini etc/my_svc_uwsgi.ini --need-app bgreen89 180978 180847 0 17:20 pts/27 00:00:00 uwsgi3.6 --module my_svc:service --http11-socket :29292 --ini etc/my_svc_uwsgi.ini --need-app
uWSGI предоставляет некоторые функции, которые могут помочь идентифицировать workers:
auto-procname = true
bgreen89 188116 188115 4 17:21 pts/27 00:00:00 uWSGI master bgreen89 188191 188116 0 17:21 pts/27 00:00:00 uWSGI worker 1 bgreen89 188192 188116 0 17:21 pts/27 00:00:00 uWSGI worker 2 bgreen89 188193 188116 0 17:21 pts/27 00:00:00 uWSGI worker 3
Это полезно, но мы столкнемся с проблемами, если запустим более одного экземпляра uWSGI на одном компьютере.
procname-prefix = "mysvc " # обратите внимание на пробел
Теперь мы можем четко определить, какие процессы принадлежат какому сервису.
bgreen89 41120 138777 44 17:32 pts/27 00:00:00 mysvc uWSGI master bgreen89 41172 41120 0 17:32 pts/27 00:00:00 mysvc uWSGI worker 1 bgreen89 41173 41120 0 17:32 pts/27 00:00:00 mysvc uWSGI worker 2 bgreen89 41174 41120 0 17:32 pts/27 00:00:00 mysvc uWSGI worker 3
Иногда может быть полезно определить, что делает каждый worker, не просматривая журналы логов. uWSGI предоставляет функцию setprocname через свой API-интерфейс Python, который позволяет службе динамически устанавливать имя своего процесса. Это позволяет добавить специфичный для приложения контекст о каждом worker. Эта дополнительная информация может быть неоценимой в случае сбоя, когда кто-то быстро попытается найти проблемный ресурс.
bgreen89 41120 138777 44 17:32 pts/27 00:00:00 mysvc uWSGI master bgreen89 41120 138777 44 17:32 pts/27 00:00:00 mysvc username /path/to/url uWSGI worker 1 bgreen89 41172 41120 0 17:32 pts/27 00:00:00 mysvc johndoe /index.html uWSGI worker 2 bgreen89 41173 41120 0 17:32 pts/27 00:00:00 mysvc janedoe /assets/data uWSGI worker 3
Полный конфиг
[uwsgi] strict = true master = true enable-threads = true vacuum = true ; Удалить sockets во время выключения single-interpreter = true die-on-term = true ; Отключение при получении SIGTERM (по умолчанию перегрузка) need-app = true disable-logging = true ; Отключение встроенного логирования log-4xx = true ; но включение для 4xx log-5xx = true ; и 5xx harakiri = 60 ; насильственно уничтожение workers через 60 секунд py-callos-afterfork = true ; позволяют workers получать сигналы max-requests = 1000 ; Перезапуск workers после этого количества запросов max-worker-lifetime = 3600 ; Перезапуск workers через указанного количества секунд reload-on-rss = 2048 ; Перезапуск workers после потребление указанного количества памяти worker-reload-mercy = 60 ; Время ожидания перед удалением workers cheaper-algo = busyness processes = 128 ; Разрешенное максимальное количество workers cheaper = 8 ; Минимальное количество worker cheaper-initial = 16 ; Workers созданные во время старта cheaper-overload = 1 ; Продолжительность цикла в секундах cheaper-step = 16 ; Количество workers при из порождение cheaper-busyness-multiplier = 30 ; Сколько циклов ждать, прежде чем убивать workers cheaper-busyness-min = 20 ; Ниже этого порога убивать workers (если стабильно для множителя циклов) cheaper-busyness-max = 70 ; Выше этого порога порождать новых workers cheaper-busyness-backlog-alert = 16 ; Порождать аварийных workers, если в очереди ожидает больше этого количества запросов cheaper-busyness-backlog-step = 2 ; Количество аварийных workers, если в очереди слишком много запросов
Дополнительные функции uWSGI
uWSGI предоставляет множество других функций, которые решают общие проблемы, с которыми сталкиваются разработчики сервисов. Использование этих функций может снизить общую сложность системы по сравнению с внешними решениями.
Cron/Timer
Главный процесс uWSGI может управлять периодическим выполнением произвольного кода. Это полезная альтернатива ручному управлению другим процессом.
- Таймеры могут переодически выполнять код с заданным интервалом, начинающимся при запуске службы.
- Cron выполняют код в указанную минуту, час, день и т. д.
- Доступна специальная версия этих функций unique, чтобы гарантировать, что ранее запущенные timers/cron не будут запускаться, пока не завершиться предыдущий экземпляр.
import uwsgi from . import do_some_work def periodic_task(signal: int): print(f"Received signal {signal}") do_some_work() # выполнять каждые 20 минут на первом доступном worker uwsgi.add_timer(99, 1200) # исполнять на 20-й минуте каждого часа, каждый день, # минута, час, день, месяц, день недели uwsgi.add_cron(99, 20, -1, -1, -1, -1)
Эти функции также доступны в качестве декораторов из модуля uwsgidecorators:
import uwsgidecorators @uwsgidecorators.timer(1200) def periodic_task(signal: int): print(f"Received signal {signal}") do_some_work()
Обратите внимание, что модули uwsgi и uwsgidecorators внедряются в среду выполнения при запуске службы с помощью uWSGI. Этот импорт завершится неудачно при запуске в обычном интерпретаторе Python, поэтому вам может понадобиться написать более защищенный код:
try: import uwsgi import uwsgidecorators except ImportError: UWSGI_ENABLED = False uwsgi = uwsgidecorators = None else: UWSGI_ENABLED = True if UWSGI_ENABLED: pass # do uWSGI only logic here
Также обратите внимание, что этот модуль не обеспечивает поддержку таймеров одноразового использования, они повторяют события. Обратитесь к модулю signal
для одноразовых таймеров.
Locks
Модуль Python uWSGI экспортирует функции lock и unlock (к сожалению, без менеджера контекста — context manager) для управления синхронизацией критических разделов между процессами workers.
import uwsgi def critical_code(): uwsgi.lock() do_some_important_stuff() uwsgi.unlock()
Cache system
Главный процесс uWSGI может предоставить кеш в памяти для совместного использования workers, который способен хранить простые байтовые строки с ключом.
Пример создания кэша с размером ключа 20 байтов и значением 8 байтов:
--cache2 name=mycache,items=150000,blocksize=8,keysize=20
Будьте осторожны: ключи или значения, которые слишком велики для кэша, не смогут быть вставлены в кеш и у них не будет ошибок для данной операции.
Пример использования Python API:
import uwsgi # опять же, работает только в контексте активного процесса UWSGI uwsgi.cache_set(key, value, timeout) uwsgi.cache_get(key) uwsgi.cache_inc(key, amount) # atomic increments! uwsgi.cache_dec(key, amount)
Mules
uWSGI mules — это дополнительные процессы, предназначенные для асинхронной обработки задач от workers, обслуживающих запросы.
Вы можете запустить один процесс с —mule и отправить ему задачи в своем коде Python:
from uwsgidecorators import timer @timer(60, target="mule") # выполнять эту функцию каждую минуту на доступном mule def do_some_work(signak: int) -> None: pass
Так же существует декоратор mstimer для запуска таймеров с точностью до миллисекунды.
Пример назначения функции в foo.py для постоянного запуска mule:
import uwsgi def loop(): while True: msg = uwsgi.mule_get_msg() # blocks until a uwsgi worker sends a message print(f"Got a message: {msg}") if __name__ == '__main__': loop()
Затем добавьте —mule=foo.py к команде запуска uWSGI.
Вы можете отправлять сообщения мулам с помощью API uwsgi.mule_msg. Обратите внимание, что сообщения должны быть байтовыми литералами.
Заключение
uWSGI находится в уникальном положении с точки зрения богатства функций, которые он предоставляет, а также неожиданности своих настроек по умолчанию. По умолчанию потоки отключены, сигналы игнорируются или обрабатываются за пределами спецификации, и т.д. Обнаружение этих поведенческих особенностей в лучшем случае приведет к потере времени разработчика а в худшем случае к простоям производства. Настройка uWSGI, чтобы избежать этих проблем, является ответственным делом. Но после изучения всех этих особенностей, впечатляющий набор функций uWSGI может быть использован для создания приложений надежным и эффективным способом. Mules, таймеры, подсистемы кэширования и ведения журналов логов позволяет обеспечивать нужной функциональностью без задействования дополнительных внешних компонентов. Использование решений, предоставляемых uWSGI, позволяет разработчикам развертывать эти компоненты в простой конфигурации, упрощая развертывание и общую сложность системы.
Оригинальная статья: Configuring uWSGI for Production Deployment