地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

【Nginx】nginx upstream パッシブヘルスチェックのmax_failsは何を対象にカウントするのか

仕事でこのfailsは何をfailsとしてカウントしてるんだっけ?となったので調べった。

公式を読む。max_failsは指定された回数失敗するとupstreamから外されるということが書いてある。この失敗とは?についての言及はここには無い

max_fails=number
sets the number of unsuccessful attempts to communicate with the server that should happen in the duration set by the fail_timeout parameter to consider the server unavailable for a duration also set by the fail_timeout parameter. By default, the number of unsuccessful attempts is set to 1. The zero value disables the accounting of attempts. What is considered an unsuccessful attempt is defined by the proxy_next_upstream, fastcgi_next_upstream, uwsgi_next_upstream, scgi_next_upstream, memcached_next_upstream, and grpc_next_upstream directives.

で、よくわからないのでぐぐってみると以下の記事がヒットした。内容を読むとconnectionタイムアウトかread/writeのタイムアウトが失敗とみなすらしい。加えてL7レベルでの失敗もproxy_next_upstreamに定義することで追加することも出来る模様。

recruit.gmo.jp

errorは対向のサーバがダウンしている時などが該当します。timeoutはproxy_connect_timeout、proxy_send_timeout、proxy_read_timeoutいずれかの閾値が該当します。invalid_headerは様々な理由がありそうですが、対向のサーバが正常に動作していないといったことが考えられます。

またproxy_next_upstreamでhttp_500, http_502, http_503, http_504が定義されている時は、これらも失敗とみなされます。

この話を以下のような最小構成のconfigから見つけるのは少し難しいかもと感じてしまった。(自分で0から書くならよいが初めて触ったconfigだと自分の場合は気づくのに時間がかかりそう...)

upstream app {
    server  127.0.0.1:10080  max_fails=100 fail_timeout=10;
    server  127.0.0.2:10080  max_fails=100 fail_timeout=10;
}

server {
    listen 80 default_server;

    location / {
        proxy_pass http://app;
    }
}

おまけ

暇(じゃないけど)なのでソースを読んで見る

max_failsの判定処理は以下

static ngx_int_t
ngx_stream_upstream_get_chash_peer(ngx_peer_connection_t *pc, void *data)
{
            if (peer->max_fails // max_failsが設定されているかつ現在のfail数が設定値より大きいかつfail_timeoutの時間以下である場合に選定先から外れる
                && peer->fails >= peer->max_fails
                && now - peer->checked <= peer->fail_timeout)
            {
                continue;
            }

peer->failsが失敗数peerはupstreamとのコネクションなど(結構巨大)を管理している構造体。failsをインクリメントしてるのは以下。

void
ngx_stream_upstream_free_round_robin_peer(ngx_peer_connection_t *pc, void *data,
    ngx_uint_t state)
{

    if (state & NGX_PEER_FAILED) { // NGX_PEER_FAILEDの場合にfailsはインクリメントされる
        now = ngx_time();

        peer->fails++;
        peer->accessed = now;
        peer->checked = now;

        if (peer->max_fails) {
            peer->effective_weight -= peer->weight / peer->max_fails;

            // ログレベルをWARNにしておけばNginxのエラーログから中身を見ることもできるっぽい
            if (peer->fails >= peer->max_fails) {
                ngx_log_error(NGX_LOG_WARN, pc->log, 0,
                              "upstream server temporarily disabled");
            }
        }

NGX_PEER_FAILEDになるケースは以下

static void
ngx_http_upstream_next(ngx_http_request_t *r, ngx_http_upstream_t *u,
    ngx_uint_t ft_type) // 第3引数はエラータイプを表している
{
    ngx_msec_t  timeout;
    ngx_uint_t  status, state; 

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http next upstream, %xi", ft_type);

    if (u->peer.sockaddr) {

        if (u->peer.connection) {
            u->state->bytes_sent = u->peer.connection->sent;
        }

        if (ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_403
            || ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_404) // statusが403か404の場合はfailsはインクリメントしない
        {
            state = NGX_PEER_NEXT;

        } else {
            state = NGX_PEER_FAILED; // ここに入る必要がある
        }

        u->peer.free(&u->peer, u->peer.data, state);
        u->peer.sockaddr = NULL;
    }

NGX_PEER_FAILEDに入るかどうかはngx_http_upstream_next()の呼び出し元で決める模様。ngx_http_upstream_next()を呼んだ時点で403と404以外はfailsはインクリメントされる。

static ngx_int_t
ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u)
{
        if (u->peer.tries > 1
            // next_upstreamこれが設定された値
            // proxy_next_upstream error | timeout | invalid_header | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | http_429 | non_idempotent
            // のように指定されているので立っているフラグでエラーかどうかを判定していく
            && ((u->conf->next_upstream & mask) == mask) 
            && !(u->request_sent && r->request_body_no_buffering)
            && !(timeout && ngx_current_msec - u->peer.start_time >= timeout))
        {
            ngx_http_upstream_next(r, u, un->mask);  // http_500だけ指定されていてupstreamが503を返した場合はngx_http_upstream_next()は呼ばない
            return NGX_OK;
        }

指定できる値は以下の構造体で管理されているので実は書き換えることで410とか599とか謎なステータスで次のupstreamへ処理を移すことも可能

static ngx_http_upstream_next_t  ngx_http_upstream_next_errors[] = {
    { 500, NGX_HTTP_UPSTREAM_FT_HTTP_500 },
    { 502, NGX_HTTP_UPSTREAM_FT_HTTP_502 },
    { 503, NGX_HTTP_UPSTREAM_FT_HTTP_503 },
    { 504, NGX_HTTP_UPSTREAM_FT_HTTP_504 },
    { 403, NGX_HTTP_UPSTREAM_FT_HTTP_403 },
    { 404, NGX_HTTP_UPSTREAM_FT_HTTP_404 },
    { 429, NGX_HTTP_UPSTREAM_FT_HTTP_429 },
    { 0, 0 }
};