Redashに環境変数での設定変更が反映されていることをgunicornで確認してみた
Redashで処理時間が超過するクエリを実行すると、初期設定ではタイムアウトの可能性があります。workerが同期処理で、且つ処理時間制限が30秒と短いためです。
長時間処理に耐えられるように非同期設定や処理時間延長を盛り込みたいところですが、本体に修正を入れると更新があった場合の取り込みが困難になります。そこで、workerの動作を変える手段として設定ファイルや環境変数の指定を用います。これらは正確にはRedashがworkerとして用いているgunicornの機能です。
ただ、どのようにすれば変更確認が行えるかが悩みものでした。Redashやgunicornのバージョン別にコードを辿っていった結果、その方法が分かったのでまとめておきます。
ローカルでの確認用セットアップ
手元で手軽に済ませたいため、ローカルで動作するようにインストールを行います。
brew install llvm openssl export LDFLAGS="-L/usr/local/opt/openssl/lib" export CPPFLAGS="-I/usr/local/opt/openssl/include" export VENV_IN_PROJECT=1 git clone git@github.com:getredash/redash.git cd redash pipenv install --python 3.8
gunicornでの設定を出力する
ただし、記事執筆時のRedashで使われているgunicornは設定の確認出力に対応していません。そこでgunicornのバージョンを上げます。
pip install gunicorn==20.1.0
次に、gunicornの起動オプションに--print-config
を追加して実行します。
% .venv/bin/gunicorn --print-config redash.wsgi:app --check-config access_log_format = %(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" accesslog = None backlog = 2048 bind = ['127.0.0.1:8000'] ca_certs = None capture_output = False cert_reqs = 0 certfile = None chdir = /path/to/app check_config = True child_exit = <ChildExit.child_exit()> ciphers = None config = ./gunicorn.conf.py daemon = False default_proc_name = redash.wsgi:app disable_redirect_access_to_syslog = False do_handshake_on_connect = False dogstatsd_tags = enable_stdio_inheritance = False errorlog = - forwarded_allow_ips = ['127.0.0.1'] graceful_timeout = 30 group = 20 initgroups = False keepalive = 2 keyfile = None limit_request_field_size = 8190 limit_request_fields = 100 limit_request_line = 4094 logconfig = None logconfig_dict = {} logger_class = gunicorn.glogging.Logger loglevel = info max_requests = 0 max_requests_jitter = 0 nworkers_changed = <NumWorkersChanged.nworkers_changed()> on_exit = <OnExit.on_exit()> on_reload = <OnReload.on_reload()> on_starting = <OnStarting.on_starting()> paste = None pidfile = None post_fork = <Postfork.post_fork()> post_request = <PostRequest.post_request()> post_worker_init = <PostWorkerInit.post_worker_init()> pre_exec = <PreExec.pre_exec()> pre_fork = <Prefork.pre_fork()> pre_request = <PreRequest.pre_request()> preload_app = False print_config = True proc_name = None proxy_allow_ips = ['127.0.0.1'] proxy_protocol = False pythonpath = None raw_env = [] raw_paste_global_conf = [] reload = False reload_engine = auto reload_extra_files = [] reuse_port = False secure_scheme_headers = {'X-FORWARDED-PROTOCOL': 'ssl', 'X-FORWARDED-PROTO': 'https', 'X-FORWARDED-SSL': 'on'} sendfile = None spew = False ssl_version = 2 statsd_host = None statsd_prefix = strip_header_spaces = False suppress_ragged_eofs = True syslog = False syslog_addr = unix:///var/run/syslog syslog_facility = user syslog_prefix = None threads = 1 timeout = 30 tmp_upload_dir = None umask = 0 user = 503 when_ready = <WhenReady.when_ready()> worker_abort = <WorkerAbort.worker_abort()> worker_class = sync worker_connections = 1000 worker_exit = <WorkerExit.worker_exit()> worker_int = <WorkerInt.worker_int()> worker_tmp_dir = None workers = 1 wsgi_app = None
この時点では設定にはまだ変更を入れていません。結果として worker_class
が初期設定の sync
になっていることが読み取れます。 timeout
も初期値の 30
秒です。
次に設定の上書きを試してみます。 GUNICORN_CMD_ARGS
による指定です。
% GUNICORN_CMD_ARGS="--timeout 120 --worker-class gevent" .venv/bin/gunicorn --print-config redash.wsgi:app --check-config access_log_format = %(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" accesslog = None backlog = 2048 bind = ['127.0.0.1:8000'] ca_certs = None capture_output = False cert_reqs = 0 certfile = None chdir = /path/to/app check_config = True child_exit = <ChildExit.child_exit()> ciphers = None config = ./gunicorn.conf.py daemon = False default_proc_name = redash.wsgi:app disable_redirect_access_to_syslog = False do_handshake_on_connect = False dogstatsd_tags = enable_stdio_inheritance = False errorlog = - forwarded_allow_ips = ['127.0.0.1'] graceful_timeout = 30 group = 20 initgroups = False keepalive = 2 keyfile = None limit_request_field_size = 8190 limit_request_fields = 100 limit_request_line = 4094 logconfig = None logconfig_dict = {} logger_class = gunicorn.glogging.Logger loglevel = info max_requests = 0 max_requests_jitter = 0 nworkers_changed = <NumWorkersChanged.nworkers_changed()> on_exit = <OnExit.on_exit()> on_reload = <OnReload.on_reload()> on_starting = <OnStarting.on_starting()> paste = None pidfile = None post_fork = <Postfork.post_fork()> post_request = <PostRequest.post_request()> post_worker_init = <PostWorkerInit.post_worker_init()> pre_exec = <PreExec.pre_exec()> pre_fork = <Prefork.pre_fork()> pre_request = <PreRequest.pre_request()> preload_app = False print_config = True proc_name = None proxy_allow_ips = ['127.0.0.1'] proxy_protocol = False pythonpath = None raw_env = [] raw_paste_global_conf = [] reload = False reload_engine = auto reload_extra_files = [] reuse_port = False secure_scheme_headers = {'X-FORWARDED-PROTOCOL': 'ssl', 'X-FORWARDED-PROTO': 'https', 'X-FORWARDED-SSL': 'on'} sendfile = None spew = False ssl_version = 2 statsd_host = None statsd_prefix = strip_header_spaces = False suppress_ragged_eofs = True syslog = False syslog_addr = unix:///var/run/syslog syslog_facility = user syslog_prefix = None threads = 1 timeout = 120 tmp_upload_dir = None umask = 0 user = 503 when_ready = <WhenReady.when_ready()> worker_abort = <WorkerAbort.worker_abort()> worker_class = gevent worker_connections = 1000 worker_exit = <WorkerExit.worker_exit()> worker_int = <WorkerInt.worker_int()> worker_tmp_dir = None workers = 1 wsgi_app = None
worker_class
が gevent
に、timeout
が 120
秒になっていることが確認できます。
あとがき
ドキュメントによると以下の順で下になるほど優先度は高くなります。
- 各種環境変数
- PasteScriptでの設定値
-c gunicorn.conf.py
のように指定できるconfigファイル内設定値- キー
GUNICORN_CMD_ARGS
の環境変数に指定する設定値 - gunicorn動作時に渡す設定値
1番目と4番目がかぶっているように見えますが、1の範囲に入るのは以下のような一部の限定された環境変数です。この環境変数に指定がない場合に初めてgunicornの初期設定が適用されます。
SENDFILE
FORWARDED_ALLOW_IPS
--print-config
は設定確認用の処理を実行して終わるため、運用時には外して実行します。バージョンが20.1.0以前の場合は引数を解釈出来ず、エラーで起動しません。
workerに指定したクラスが変更されているのか、ログ上からタイムアウトエラーを見て判断する以外は手段に乏しいところでした。環境変数指定での上書き指定が正確に行えているかの確認に掛かるコストが大きく下げられるので、確認手段に悩んでいる場合にはおすすめです。