Советы по настройке uWSGI в производственной среде

Spread the love

В статье даны рекомендации по настройке 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

Была ли вам полезна эта статья?
[5 / 5]

Spread the love
Подписаться
Уведомление о
guest
0 Комментарий
Inline Feedbacks
View all comments