ネットワーク

NGINX Plus による Cookie を使ったセッション維持の方法

こんにちは。 narai です。

みなさん、ロードバランサーで負荷分散はしたいけど、サービスの一連の通信は同じサーバーに振り分け続けたいといったことありませんか?
今回は、NGINX Plus で Cookie を使用してセッション維持をする方法を紹介します。

概要

ロードバランサーで負荷分散をしつつも、同一クライアントからのリクエストは同じサーバーに振り分け続けたい(セッション維持したい)という要件を聞きませんか? その場合、Stciky session という機能で実現できます。 Stciky session を使用するとロードバランサーがユーザーセッションを識別し、特定のセッションのすべてのリクエストを同じサーバーに転送できるようになります。

今回はそんな Sticky Session を NGINX で実現するための方法を紹介します。

NGINX での実装

NGINX OSS にて、セッション維持を行う場合、Barancing method の ip-hash を利用することで実現可能です。 ip-hash はクライアント IP の最初の第三オクテットをハッシュ値として持ち、ハッシュ値が同じであれば同じサーバーに振り分けます。

詳細は nginx の document をご確認ください。 http://nginx.org/en/docs/http/load_balancing.html#nginx_load_balancing_with_ip_hash

なお、ip-hash は非常に簡単に導入できますが、IP アドレスの第 3 オクテットが同じユーザーからのリクエストがすべて同じサーバーに振り分けられてしまうため、負荷の偏りが発生する場合があります。そのため、各ユーザー単位でセッション維持を行いつつ、負荷を分散するために、セッション維持には Cookie を利用する方法が一般的です。

NGINX Plus での実装

NGINX Plus では 上述の IP-Hash に加え、HTTP cookie を使用した以下 3 種類の方法でセッション維持を行うことができます。

  • Sticky Cookie NGINX Plus がクライアントへレスポンスする際に、Upstream サーバーの情報が含まれた Cookie を挿入します
  • Sticky Leran Upstream サーバーが Responce に含める Cookie 情報を記憶して、その値に応じてサーバーに振り分けます
  • Sticky Route あらかじめ、Upstream サーバーに対して値を設定し、HTTP Request の特定ヘッダーや Cookie が設定した値と一致するサーバーに振り分けます

どれも特徴があるので、シチュエーションに合わせて使い分けると良いですね。

方法 特徴
Stciky Cookie 設定が容易、サーバー構成に依存しない
cookie にサーバーの情報が含まれる、クライアントが Cookie を持っている限り同じサーバーに振り分けられる
Stciky Leran JsessionID 毎のセッション維持が可能、サーバー情報が含まれない
、再起動等でエントリが消えるとセッション維持されない
Stciky Route Cookie 情報を正規表現でフィルタリングが可能
システムを理解している必要がある、設定が少し複雑

では、各方法について具体的に見て行きましょう。

Sticky cookie

NGINX Plus がバランシング先のサーバー情報を含んだ Cookie を生成して、レスポンスに挿入します。そして、クライアントからのアクセス時に、Cookie に含まれているサーバーの情報を元にバランシングを行う方法になります。

書式

sticky cookie <cookie名> <option> ;
  • cookie名 : NGINX Plus が指定した Cookie名に振り分け先サーバーの情報を挿入します。具体的にはサーバーの IP:port を MD5 でハッシュした値となります。

  • option : option として以下が使用できます。

    • expires : cookie の有効期限を指定出来ます
    • domain : クライアントが cookie を付与するドメインを指定できます
    • path= : クライアントが cookie を付与する path を指定できます

動作確認

設定は upstream block に sticky cookie ディレクティブを追加します。

upstream server_group {
    zone backend 64k;
    sticky cookie srv_id expires=1h path=/;
    server 172.20.0.2;
    server 172.20.0.3;
}

実際にリクエストを投げると、レスポンスに NGINX Plus が追加した Cookie が含まれることがわかります。

$ curl http://127.0.0.1:10080/app1/index.html -vv -H "Host:localhost.example.com"

> GET /app1/index.html HTTP/1.1
> Host:localhost.example.com

< HTTP/1.1 200 OK
< Set-Cookie: srv_id=646c669f3ea194955b37901f68dfc4a6; expires=Mon, 20-Feb-23 07:58:48 GMT path=/  ###  NGINX Plus が挿入
< upstream_server: 172.20.0.2:80    ### 振り分けられたサーバー
app1 server

そして、次のリクエストに Cookie が含まれていると同じサーバーに振り分けられます。

# curl http://127.0.0.1:10080/app1/index.html -vv -H "Host:localhost.example.com" -H "cookie:srv_id=646c669f3ea194955b37901f68dfc4a6"

> GET /app1/index.html HTTP/1.1
> Host:localhost.example.com
> cookie:srv_id=646c669f3ea194955b37901f68dfc4a6   ### NGINX Plus が挿入した Cookie

< HTTP/1.1 200 OK
< Set-Cookie: srv_id=646c669f3ea194955b37901f68dfc4a6; expires=Mon, 20-Feb-23 08:04:25 GMT; max-age=3600; domain=.example.com; path=/
< upstream_server: 172.20.0.2:80                   ### 初回と同じサーバーに振り分けられる
app1 server

このように、HTTP Responce に NGINX Plus が振り分け先サーバーの情報を cookie として挿入することで、同じサーバーに振り分けられるようになります。 また、サーバー構成に依存せずにセッション維持を行えるため、簡単に導入することができます。 なお、本機能では NGINX Plus は、クライアントとサーバーの紐づけを行っておりません。 Cookie の情報を見て判断しているだけとなりますので、HTTP Request に Cookie が含まれていないと、セッション維持されません。

Sticky learn

サーバーからの Cookie 情報を利用してセッション維持を行う方法であり、具体的には NGINX Plus が振り分け先サーバーと、サーバーが挿入した Cookie 情報を紐づけエントリとして管理しています。

書式

sticky learn
    create=$upstream_cookie_<cookie名>
    lookup=$cookie_<cookie名>
    zone=<zone名>:<zone size> timeout=<timeout> ;
  • create : サーバーが挿入したセッション維持に使用する cookie を指定します。
  • lookup : クライアントが 2 回目以降のリクエストで付与してくる Cookie を指定します。 基本的にはサーバーが挿入する Cookie 名と同じになります。
  • zone : Cookie 情報を保存するメモリ領域に任意の名称とサイズを指定します。 サイズが 1m の場合、64bit プラットフォームであれば約 4000 エントリを保持できます。
  • timeout: エントリの保存時間を指定します。

$upstream_cookie_cookie名$cookie_cookie名 は NGINX で Cookie を参照する際に使用する変数となります。 詳しくは以下を展開してください。

NGINX にて Cookie を参照する方法

NGINX ではリクエストに含まれる Cookie 情報は以下のように変数を使用することで参照できます。 $cookie_cookie名 そのため、example-cookie という cookie を参照したい場合、$cookie_example-cookie と指定します。 同様に upstream server からのレスポンスに含まれる cookie を参照する場合、 $upstream_cookie_cookie名 で参照することが可能です。

動作確認

設定は upstream block に sticky learn ディレクティブを追加します。 また、サーバーはレスポンスの際に Set-Cookie persist=$hostname を挿入する設定になっています。

upstream server_group {
    zone backend 64k;
    sticky learn
       create=$upstream_cookie_persist
       lookup=$cookie_persist
       zone=client_sessions:1m
       timeout=1h;
    server 172.20.0.2;
    server 172.20.0.3;
}

リクエストを投げてみると、以下のようにサーバーが付与した Cookie が含まれていることが確認できます。

$ curl http://127.0.0.1:10080/app1/index.html -vv -H "Host:localhost.example.com"

> GET /app1/index.html HTTP/1.1
> Host:localhost.example.com

< HTTP/1.1 200 OK
< Set-Cookie: persist=nginx-app1  # サーバーが付与した Cookie
< upstream_server: 172.20.0.2:80  # 振り分けられたサーバー
app1 server

このタイミングで NGINX Plus は、スティッキーセッションのエントリを保持します。 そして、Cookie を含んだリクエストが来ると、同じサーバーに振り分けられます。

# curl http://127.0.0.1:10080/app1/index.html -vv -H "Host:localhost.example.com" -H "Cookie: persist=nginx-app1"

> GET /app1/index.html HTTP/1.1
> Host:localhost.example.com
> Cookie:persist=nginx-app1

< HTTP/1.1 200 OK
< Set-Cookie: persist=nginx-app1
< upstream_server: 172.20.0.2:80   # 初回と同じサーバーに振り分けられる
app1 server

このように、Sticky Learn では、サーバーが挿入した Cookie 情報を元にセッション維持を行います。そのため、サーバー構成を把握する必要があります。 また、NGINX Plus 自身でエントリを持つため、冗長構成時には zone sync にてエントリ情報を同期させる必要があります。

Sticky route

NGINX Plus 上でサーバーに対して任意の文字列(route)を割り当て、リクエストの Cookie が route に一致した場合、特定のサーバーに振り分けます。

書式

sticky route $cookie_<cookie名> ;
server 172.20.0.2 route=<文字列>;
  • $cookie_cookie名 : リクエストで参照する Cookie を指定します。
  • route= : server の route オプションを使用して、文字列を指定します。

動作確認

以下の設定では、リクエストに含まれる persist cookie が nginx-app1 であれば 172.20.0.2 に、nginx-app2 であれば172.20.0.3 に振り分けます。

upstream backend_lb {
    zone backend_zone 64k;
    sticky route $cookie_persist;

    server 172.20.0.2 route=nginx-app1;
    server 172.20.0.3 route=nginx-app2;
}

リクエストを投げてみると、以下のように cookie の内容によって振り分け先のサーバーが変わります。

# curl http://127.0.0.1:10080/app1/index.html -vv -H "Host:localhost.example.com" -H "Cookie: persist=nginx-app1"

> GET /app1/ HTTP/1.1
> Host: localhost.example.com:10080
> Cookie: persist=nginx-app1

< HTTP/1.1 200 OK
< Set-Cookie: persist=nginx-app1
< upstream_server: 172.20.0.2:80
app1 server

# curl http://127.0.0.1:10080/app1/index.html -vv -H "Host:localhost.example.com" -H "Cookie: persist=nginx-app2"

> GET /app1/ HTTP/1.1
> Host: localhost.example.com:10080
> Cookie: persist=nginx-app2

< HTTP/1.1 200 OK
< Set-Cookie: persist=nginx-app2
< upstream_server: 172.20.0.3:80
app1 server

このように、Sticky Route では、あらかじめ指定した文字列と一致する Cookie を持つリクエストが来た場合に、特定のサーバーに振り分けます。 なお、より高度な使い方としては、map ディレクティブを使って、正規表現にて条件にマッチする文字列をフィルタすることが可能です。 以下は、map ディレクティブ で Cookie の末尾を抜き出して、振り分け先を判断するような設定です。

# 正規表現で Cookie の末尾を取得
map $cookie_example $route_cookie {
    ~(?<route>.)$ $route;
}

# route は末尾の値がいくつかを指定
upstream backend_lb {
    zone backend_zone 64k;
    sticky route $route_cookie;
    server nginx-app1 route=a;
    server nginx-app2 route=b;
}

こうすることで、example cookie の末尾が a であれば nginx-app1 に振り分けるようになります。

まとめ

ここまでセッション維持の方法についてみてきました。 NGINX OSS では Source IP によるセッション維持しかできませんが、NGINX Plus を利用するとかなり柔軟な制御ができるようになります。 セッション維持はロードバランサーに求められる要件としても一般的なものであるため、ぜひ、NGINX Plus による柔軟な制御をお試しください。

今回はここまでとなります。また次回の Blog でお会いしましょう。

この記事に関連する製品・サービス