Hateburo: kazeburo hatenablog

Operations Engineer / Site Reliability / 運用系小姑 / Perl Monger

Nginxのupstreamコンテキストでの名前解決に nginx-dynamic-upstream-resolve-servers を使う

Nginxでproxyする際に、名前解決を動的に行ってからupstreamへ接続をするというのは、S3だったりConsulで管理されたサーバ群に対するproxyを行いたい場合に使いたくなる機能のひとつ。

ただ、upstreamコンテキスト内の resolve オプションは有償の Nginx Plusにしかない機能なので、set変数を使ってやるというのがこれまでの方式

resolver 127.0.0.1 valid=30s ipv6=off;
server {
  set $backend_host "backend-apps.service.dc.consul:8080";

  location / {
    proxy_pass http://$backend_host;
  }
}

この方式は問題なく動くのですが、

  • upstream keepaliveが有効にならない
  • 名前解決した結果に複数のIPがあっても、接続が失敗した際のretryが行われない

といった問題、課題がありました。

そこで、nginxにchoconを同居させ

upstream chocon {
  server 127.0.0.1:8080 max_fails=5 fail_timeout=3s;
  server 127.0.0.2:8080 max_fails=5 fail_timeout=3s backup;
  keepalive 16;
  keepalive_requests 1000;
}

server {
  proxy_http_version 1.1;
  proxy_set_header Connection "";
  proxy_set_header Host "backend-apps.service.dc.consul.ccnproxy";
  location / {
    proxy_pass http://chocon;
  }
}

などという方法をとっていましたが、ミドルウェアを減らしよりシンプルにしたいなとずっと考えていました。

3rd party moduleの利用

流れ星に resolve オプションがOSSにきますようにと願いながら、3rd party moduleでresolve相当の機能がある

github.com

を試しました。

こちらのnginxモジュールを使うと、

resolver 127.0.0.1 valid=30s ipv6=off;

upstream backend_apps {
  server backend-apps.service.dc.consul:8080 resolve max_fails=10 fail_timeout=3s;
  keepalive 16;
  keepalive_requests 1000;
}

server {
  proxy_http_version 1.1;
  proxy_set_header Connection "";
  location / {
    proxy_pass http://backend_apps;
  }
}

と、resolveオプションが使えるようになります。何これ最高!

、、、だったのですが、検証の結果、一見問題ないのですが、サーバの増減時にNginxのworkerがsegmentation faultで落ちるということがわかりました。

モジュール自体、最終更新から5年たっており、修正される気配もなさそうです。

そこで次に見つけたのが nginx-upstream-dynamic-servers のissueで紹介されていた zhaofeng0019 さんの nginx-dynamic-upstream-resolve-servers

github.com

このモジュールは nginx-upstream-dynamic-servers のsegvなどのissueを修正し、いくつか変更を加えています。もっとも大きな違いは、Nginx本体へのpatchが必要なことです。コードを追いかけていけば本体へのpatchは納得する範囲です。

使い方は nginx-upstream-dynamic-servers とまったく同じで。use_lastというオプションが追加されています。

ビルド方法

cubicdaiyaことbokkoのnginx-buildを使ってbuildしていきます。nginx-1.18.0で確認済みです

configure.sh

#!/bin/sh

./configure \
        --prefix=/path/to/nginx-1.18 \
        --with-http_ssl_module \
        --with-http_realip_module \
        --with-http_stub_status_module \
        --with-http_v2_module \
        --with-pcre-jit \

modules3rd.ini

[nginx-upstream-dynamic-resolve-servers]
form=git
url=https://github.com/zhaofeng0019/nginx-upstream-dynamic-resolve-servers.git
rev=9ad9322e99fa9f07fb3dc1cbdef355d1ae81f376

ビルド

Nginxへのpatchはこちら

$ curl -O https://gist.githubusercontent.com/kazeburo/01d1901eece9341aaaa92194ce1b4251/raw/03a126359746b8a41ed03124aa952120ae83815d/nginx-upstream-dynamic-resolve-servers.patch
$ nginx-build \
    -clear \
    -d work \
    -v 1.18.0 \
    -patch nginx-upstream-dynamic-resolve-servers.patch \
    -patch-opt "-p1" \
    -c configure.sh \
    -m modules3rd.ini \
    -zlib \
    -zlibversion=1.2.11 \
    -verbose \
    -pcre \
    -pcreversion=8.44 \
    -openssl \
    -opensslversion=1.1.1g \

これでビルドできると思われます。Mac上で確認しています。

実は nginx-upstream-dynamic-resolve-servers にもsegvする問題があったのですが、PRを出したところ解決していただきました。

早速productionでもconsulで構成するserver群に対して nginx-upstream-dynamic-resolve-servers でproxyして問題なく動いてます。consul maintを行って数秒後にはリクエストが正しく止まること、keepaliveされることなどは確認、検証しています。便利!!