kevy
このページ

kevy をプロキシの後ろに置いて運用する

kevy に AUTH はなく、TLS もなく、そしてこれからも入りません——それは憲章の決定であって、いつか埋まる穴ではありません。認証も暗号化も、すべてプロセスの前で行います。 この章はそのレシピで、書き換えるためではなく、そのまま写すために書かれています。

kevy が開くもの

ポート備考
既定ひとつ6004--threads N同じポートに SO_REUSEPORT のリスナを N 個開くのであって、N 個のポートではありません
--cluster1 + Nメインポートに加えて、シャードごとに port+1+i
KEVY_UNIX_SOCKET=<path>変わらないunix ソケットは追加されるだけで、TCP のリスナはそのまま

既定のバインドは 127.0.0.1 です。それはこの章が求める形そのものです:エンジンはこのホストからしか届かない場所で待ち、公開アドレスを持つのは終端器だけになります。

   client ──TLS──▶  terminator  ──plain──▶  kevy
  (rediss://)     (stunnel / HAProxy /     127.0.0.1:6004
                   nginx stream)           または /run/kevy/kevy.sock

kevy 側は何も変わりません。RESP はホスト名も SNI も絶対 URL も運ばないので、前に置いたバイトプロキシは両側から見えません。

RESP は HTTP ではない

HTTP のリバースプロキシは RESP を運べません。stock の Caddy も同様です:コアに layer-4 モジュールがないので、Caddyfile をどう書いても caddy だけで kevy に TLS を被せることはできません。必要なのは TCP レベルの終端器です:

  • stunnel——最小、どこでもパッケージがあり、この一仕事だけをする;
  • HAProxymode tcp——既に HAProxy を運用している、あるいはヘルスチェックとフェイルオーバーを同じ場所に置きたいなら;
  • nginxstream モジュール——既に nginx があるなら同様。

stunnel → ループバックのポート

[kevy]
accept  = 0.0.0.0:6379
connect = 127.0.0.1:6004
cert    = /etc/kevy/tls/fullchain.pem
key     = /etc/kevy/tls/privkey.pem

HAProxy → unix ソケット

もう一段締めるなら:エンジンのソケットファイルにはファイルシステムの権限が付くので、このホストの中でさえ、届くプロセスは終端器だけになります。

listen kevy
    bind :6379 ssl crt /etc/kevy/tls/kevy.pem
    mode tcp
    timeout client 0
    timeout server 0
    server kevy unix@/run/kevy/kevy.sock

ソケット付きで kevy を起動します:

KEVY_UNIX_SOCKET=/run/kevy/kevy.sock kevy --dir /var/lib/kevy

そのパスについて二つ。既に存在する場合 kevy は起動を拒みます——自分が作ったのではないパスを上書きしません——ので、再起動時に片付けるか、実行ごとに変わるパスを使ってください。そして 127.0.0.1 の TCP リスナはそのまま残ります。ソケットは置き換えではなく追加です。

nginx stream → unix ソケット

stream {
    upstream kevy { server unix:/run/kevy/kevy.sock; }
    server {
        listen 6379 ssl;
        ssl_certificate     /etc/kevy/tls/fullchain.pem;
        ssl_certificate_key /etc/kevy/tls/privkey.pem;
        proxy_pass kevy;
        proxy_timeout 1h;
    }
}

三つとも、タイムアウトに注意してください:ブロッキングの BLPOP や、待っているだけの Pub/Sub 購読者は、一バイトも流れないまま、アプリケーションが望むだけ接続を開いたままにします。アイドル接続を刈るプロキシは、kevy が購読者を落としているのと見分けがつきません

クライアント側

TLS を有効にした標準の Redis クライアントなら——rediss://host:6379——終端された kevy とそのまま話せます。

kevy-cli はできません。 rediss:// に対して Unsupported を返します。kevy が TLS なしで出荷され、CLI にも貸せる TLS スタックがないからです。これは行き当たりばったりで気づくのではなく、先に段取りしておく価値のある運用上の帰結です:ホスト上で直接操作するか、SSH トンネルを使ってください:

ssh -N -L 6004:127.0.0.1:6004 you@host   # そのあと:kevy-cli -p 6004

必要なものだけを晒す

既定のバインドなら、kevy にファイアウォール規則は一つも要りません——そもそもホストの外から届きません。開けるポートは終端器の分だけです。どうしても kevy を実インタフェースにバインドするなら、ループバックのバインドがただでやっていた仕事をファイアウォールが引き受けることになり、そしてそれが、ネットワークと認証のないデータベースとの間にある唯一のものになります。

クラスタモードはこの層を越えられない

単一ノードのクラスタモードは同じホスト上のクライアントのためのもので、プロキシがそれを延長してくれることはありません。CLUSTER SLOTSCLUSTER NODES は kevy がバインドしているアドレスを広告し、0.0.0.0 のワイルドカードのときは 127.0.0.1 に置き換えます(到達不能なアドレスを広告すれば、すべてのクライアントが立ち往生するからです)。announce-address に相当するつまみはありませんので、127.0.0.1:6101 と告げられたキー認識クライアントは、他所からそれを追えません。

リモートからのキー認識ルーティングが要るなら、ルーティング可能なアドレスにバインドし、その 1 + N 個のポートを一対一で通すことになります——それは「ポートを一つだけ晒す」のちょうど反対です。どちらかを選んでください。両立はしません。

実測したこと、していないこと

この章を書きながら、このツリーに対して実測:

  • 上の表のポート面(--cluster がシャードごとに port+1+i を開くことを含む);
  • 素の TCP プロキシを前に置いても kevy に対して透過である(PINGSETGET がフォワーダを通る);
  • TLS を終端しても、手を入れていない kevy に対して RESP が往復する——ループバックのポートと unix ソケットの両方、TLS 1.3、+PONG / +OK / 格納した値がそのまま返る;
  • stock の Caddy(2.11.4)に layer-4 モジュールがないこと;
  • 0.0.0.0 バインド時に CLUSTER SLOTS127.0.0.1 を広告すること;
  • kevy-clirediss:// を拒むこと。

三つの設定ブロックは各製品がこの仕事をする際の標準的な書き方で、ここでは実行していません。実測したのは、それらが実装している「形」のほうです。

参照

  • uds.md——unix ソケットの詳細
  • cluster.md——単一ノードのクラスタモードは何のためか
  • tuning.md——--threads と、少ないほうが速い理由