ngx_mrubyでアクティブヘルスチェックするTCPのロードバランサを作るメモ

半年くらい前の話、RDSのリードレプリカのロードバランサを置きたくて、TCPのロードバランサを試していた。Cloud Watchのメトリクスによって、振り分け対象から除外したかったので、アクティブヘルスチェックできるものを探していたが、

  • ELBのNetwork Load Balancer
  • HAProxy
  • nginx stream module

これらはポートでヘルスチェックするだけだったのでやめた。nginxはnginx Plusを購入したらできるっぽいが試さなかった。

HAProxyを試したときの設定方法はこれ Fargate による HAProxy で RDS postgres のロードバランシング - Qiita

あげくngx_rubyに行き着いて設定したが、結局、これ以降使わなくなってしまったが、自由度の高いTCPのロードバランサをさくっと立てられるのは、今後も何か役に立つかもしれないので思い出す用のメモ。

ngx_mrubyの設定はこれだけで、 mruby_stream_code ハンドラで待機してるサーバから1台選んでそれをアップストリームに設定してプロキシする。

    mruby_stream_init_code '
      u = Userdata.new
      u.server_map = JSON::parse open("/usr/local/nginx/conf/server_map.json").read
      u.backups = u.server_map["backup"].map{|e| e["endpoint"]}
    ';

    server {
        listen 5432;
        mruby_stream_code '
          u = Userdata.new
          upstreams = JSON::parse Redis.new("redis", 6379).get("upstreams")
          c = Nginx::Stream::Connection.new "pgservers"
          c.upstream_server = upstreams.length > 0 ?
            upstreams[rand upstreams.length] : u.backups[rand u.backups.length]
        ';
        proxy_pass pgservers;
    }

待機してるサーバは、ここではヘルスチェックの結果から、Redisにメモしている。

ヘルスチェック結果はRedis等に書くことで外だしできるので、ここにはその処理はない。 当初は、mruby無いでやる必要があると思って、mrubyでcloud watch API を呼ぶmrb_gemを書いた*1 が、整合性のあるデータストアで、mrubyのハンドラから読めれば何でも良い。これだけのためにRedisを立ち上げるのは大げさだと思う。

一回引っかかったのは、RDSのエンドポイントでサーバを記述していたときに、ヘルシーなサーバがいても、アップストリームがnilになるという問題。 これは、都度 getaddrinfo してIPアドレスでアップストリームを設定することで解決した。そして、まつもとりーさんにもこういう感じの使い方を想定している旨教えていただいた。

https://github.com/sabmeua/ngx_mruby_rdsproxy/commit/dcb10eb3ee33f04d7a14d9447269e5e207e765d4

以上で、こちらはRDSと同一VPCでfagateで起動することを想定してDockerfileにしたもの。ヘルスチェック用コンテナも含む GitHub - sabmeua/ngx_mruby_rdsproxy: Example load balancer for RDS using ngx_mruby