FreeBSD で h2o + Gunicorn で PowerDNS Admin を実行する

PowerDNS を PostgreSQL バックエンドでインストールして、PowerDNS Admin を動かしたいということで、 順を追って手順を記録していきます。

もくじ

  • PostgreSQL をインストールする
  • PowerDNS をインストールする
  • PowerDNS Admin をインストールする
  • h2o + Gunicorn で実行する ← 今回

Gunicorn の設定と実行

WSGI の実装でよく使われているものは uWSGI と Gunicorn があるそうなのですが、PowerDNSAdmin の requirements.txt に Gunicorn が入っていてインストールされているので、そちらを使います。

Gunicorn を経由するため、Flask を 127.0.0.1 にのみ bind するように変更します。 前回作成した standalone.py を production.py にコピーして以下を変更します。

BIND_ADDRESS="127.0.0.1"

また、PowerDNSAdmin に付属の run.py は Gunicorn からの実行に使えないので、それ用の web.py を powerdns-admin ディレクトリ(run.py と同じ)に作成します。

from powerdnsadmin import create_app

app = create_app()

/usr/home/pdnsadm/gunicorn_config.py を作成し、次のように Gunicorn の設定を記述します。基本的には自分しか使わないので、workers と threads は控えめに。また、h2o との接続には Unix ソケットを使います。

chdir = "/usr/home/pdnsadm/powerdns-admin"
raw_env = [
        "FLASK_CONF=/usr/home/pdnsadm/powerdns-admin/configs/production.py",
        "FLASK_APP=powerdnsadmin/__init__.py",
        ]
bind = "unix:/tmp/pdnsadmin.sock"
backlog = 2048
workers = 1
threads = 3
worker_class = "sync"
worker_connections = 1000
timeout = 30
keepalive = 2

max_requests = 512
spew = False
daemon = False
pidfile = None
umask = 0
user = "pdnsadm"
group = "pdns"
tmp_upload_dir = None

access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
loglevel = "info"

pdnsadmin のホームディレクトリで以下を実行し、Gunicorn が実行できることを確認します。

$ ./webpy39/bin/gunicorn 

/usr/local/etc/rc.d/pdnsadmin

service コマンドから Gunicorn が実行できるように、次のような rc.d ファイルを作成します。 できたら実行権限を chmod 555 pdnsadmin でつけるのを忘れずに。 sleep 3 をつけたのは、そうしないと restart 時に、シグナルが発行されてから Gunicorn が終了するまでに時間がかかり、その前に start_cmd が実行されてしまって restart がうまくいかないからです。

#!/bin/sh

# PROVIDE: pdnsadmin
# REQUIRE: DAEMON pdns fluent-bit
# KEYWORD: shutdown

#
# Add the following line to /etc/rc.conf to enable pdnsadmin:
# pdnsadmin_enable (bool):      Set to "NO" by default.
#                               Set it to "YES" to enable pdnsadmin.
#

. /etc/rc.subr

name=pdnsadmin
desc="PowerDNS Admin web UI daemon"
rcvar=${name}_enable

load_rc_config ${name}

command="/usr/home/pdnsadm/webpy39/bin/gunicorn"
config="/usr/home/pdnsadm/gunicorn_config.py"
pidfile="/var/run/pdnsadmin.pid"
command_interpreter="/usr/home/pdnsadm/webpy39/bin/python"

start_cmd=start_cmd
stop_cmd='pkill -TERM -U pdnsadm -F ${pidfile}; sleep 3'

pdnsadmin_enable=${pdnsadmin_enable:-NO}

start_cmd()
{
        check_startmsgs && echo "Starting ${name}."
        daemon -f -u pdnsadm -p ${pidfile} ${command} web:app --config ${config} --log-syslog
}

run_rc_command $1

h2o の設定と実行

関係ありそうなところはこんな感じです。

hosts:
  www.example.net:
    paths:
      "/":
        proxy.reverse.url: http://[unix:/tmp/pdnsadmin.sock]/
        proxy.preserve-host: ON
      "/static":
        file.dir: /usr/home/pdnsadm/powerdns-admin/powerdnsadmin/static

サブディレクトリで実行する

ここまでの設定だと http(s)://www.example.net/ のルートを取られてしまうので、できればサブディレクトリを切ってそこで実行するようにしたいです。http(s)://www.example.net/powerdns/ とか。

変更点は2箇所あって、まず web.py で SCRIPT_NAME を設定します。

import os
from powerdnsadmin import create_app

os.environ["SCRIPT_NAME"] = "/powerdns"
app = create_app()

次に、h2o.conf を次のように変更します。

hosts:
  www.example.net:
    paths:
      "/powerdns/static":
        file.dir: /usr/home/pdnsadm/powerdns-admin/powerdnsadmin/static
      "/powerdns":
        proxy.reverse.url: http://[unix:/tmp/pdnsadmin.sock]/powerdns
        proxy.preserve-host: ON

これで、http(s)://www.example.net/powerdns/ で PowerDNSAdmin を使えるようになりました。