水深1024m

技術的なメモとか日記的なもの

AWS NLB についてあれこれ

AWS ELB (ALB, CLB) には日頃からだいぶお世話になっているわけですが、新しい Network Load Balancer (NLB) がリリースされましたね。

新しいNetwork Load Balancer – 秒間数百万リクエストに簡単にスケーリング | Amazon Web Services ブログ

雑に言えば CLB TCP モードの次世代版というとこですかね。 ざっくりドキュメントを読みつつ、いくつか気になる点があったのでまとめます。 ドキュメントに記載されていない内容は私が検証した内容です。何か間違いがあればお気軽にご指摘ください。

パケットはどのように流れるのか

一応図にしておきます。なんというか懐かしい (とか言ったら怒られそうな) 流れですね。 ALB や CLB (HTTP, TCP 両方) ではロードバランサがそれぞれの通信を終端していわゆるプロキシのような役割を果たしているのと比較すると、NLB はパケットの送信元/宛先アドレスを書き換えるのみに見えます。LVS の NAT モードと同様の挙動というところでしょうか。あくまで戻りのパケットは NLB に送出されておりいわゆる DSR ではないように見えます。

f:id:kani_b:20170920130201p:plain

internet-facing と internal の違い

基本的に ALB と変わりありません。

internet-facing

  • 起動する AZ および subnet を選ぶ
    • subnet には IGW が attach されている必要がある (いわゆる public)
  • DNS name が割り振られる
    • 返すのは各 AZ ごとに起動時に払い出された public IP もしくは起動時に追加した EIP
  • バックエンドインスタンスは private subnet に所属していても問題ないが、VPC Route Table においてデフォルトルート (0.0.0.0/0) が設定されている必要がある

internal

  • 起動する AZ および subnet を選ぶ
    • subnet に IGW, NAT Gateway などが attach されている必要はない
  • DNS name が割り振られる
    • 返すのは各 AZ ごとにランダム (DHCP?) に割り振られたプライベート IP アドレス

NLB が返す IP アドレスについて

NLB は1つの AZ につき1つのIPアドレスを持ち、それぞれの IP アドレスへのトラフィックは必ずその AZ にルーティングされます。 例えば AZ-A に 10.0.0.0/24, AZ-B に 10.0.1.0/24 の subnet を作成して設定し、A,B それぞれの AZ にまたがる NLB を作成したとすると、DNS が返す IP アドレスは以下それぞれ2つになります。

  • internet-facing
    • xxx.xxx.xxx.xxx (AZ-A 用 public IP / EIP)
    • yyy.yyy.yyy.yyy (AZ-B 用 public IP / EIP)
    • バックエンドへのヘルスチェックにはプライベート IP アドレスが適当に切り出されて使われる
  • internal
    • 10.0.0.x (AZ-A の subnet から切り出した IP)
    • 10.0.1.y (AZ-B の subnet から切り出した IP)
    • ヘルスチェック元 IP アドレスは同じ

クライアントはどちらかを選択して通信を行うことになりますが、その際選択した IP アドレスが所属している subnet と必ず通信を行うことになるわけです。例えば xxx.xxx.xxx.xxx にパケットを送出した際、それが AZ-B のバックエンドインスタンスにルーティングされることはありません。 これが NLB のリリース紹介記事にも書いてある Zonality ってことですね。

Zonality って結局どういうことなのか

先述の通り NLB から見える IP アドレスは必ず1 AZ に紐付きます。 2AZ に NLB をアタッチした状態で DNS を引いてみると必ず2つの IP アドレスが返ってきます。よって、例えば以下のように Web - App - DB のような構成にした際の接続にすべて NLB を使っている構成であれば、先頭の NLB が Zonal であっても後半の NLB で他の AZ にバランスされてしまうように見えます。

f:id:kani_b:20170920130236p:plain

これはあくまでクライアントの実装による話です。 例えば glibc の getaddrinfo(3) は RFC 3484 Rule 9 によって定義されているように、複数の IP アドレスが返された際は longest match によって IP アドレスをソートして返します。上記の図のように、それぞれのインスタンスが同じ Subnet に存在する (= NLB が同じ Subnet にある) 場合、 getaddrinfo(3) を使っているクライアントでは、同じ Subnet, 同じ AZ の NLB エンドポイントが使われるようになります。 *1

Redis や memcached をはじめとした低遅延なデータストアを利用する際には AZ またぎのレイテンシが地味に効いてくるケースがあるため、NLB と Subnet が1対1になっていることは役に立ちそうです。 NLB を利用することで、普段は同じ AZ のインスタンスに接続しつつ、AZ が全滅した際には別の AZ に接続することができます。 ただし、この挙動はあくまでクライアントに依存するものであるため、上記のような挙動をしない場合にはクライアント側での対応が必要ですし、Subnet をまたぐようなケースでもクライアント側での対応が必要です。 また、AZ 間のバランスは接続元/接続先インスタンス共に適切に保たなければ、均衡が崩れて負荷が偏ってしまうことには注意が必要ですね。

障害時に使われる IP アドレス

IP アドレスと AZ が紐付いているので、例えば AZ-B のバックエンドインスタンスが全滅したしまった際に yyy.yyy.yyy.yyy あるいは 10.0.1.y をクライアントが選択すると、どのバックエンドインスタンスにもルーティングされないことになってしまいます。 ざっと実験した感じ、NLB では以下のような挙動をするようでした。(internet-facing, internal 共通)

  • 全ての AZ に healthy なバックエンドインスタンスが存在する場合、全 AZ の IP アドレスを返す
  • healthy なバックエンドインスタンスがいない AZ が1つ以上存在する場合、その AZ を抜いた IP アドレス (上記の例だと xxx.xxx.xxx.xxx) のみを返す
  • 全ての AZ でバックエンドインスタンスが全滅した場合、全 AZ の IP アドレスを返す

よって、片方の AZ が全滅するような障害においても、DNS name をベースとして (IP アドレスを直接べた書きなどせず) クライアントが接続しようとする場合、全滅した AZ に接続しようとしてタイムアウト…といったことにはならないようです。Route53 の Alias Record にも対応しており、こちらを設定した場合でも同様の挙動をします。 NLB 自体の IP アドレスは固定ですが、複数 AZ を利用する際に複数ある IP アドレスをそのままドメインの A レコードとして設定してしまうと、AZ が全滅した際でもクライアントが接続できない AZ の IP アドレスを使ってしまうリスクが残ることになります。 そのような設定をする場合は Route53 の Healthcheck および Failover 機能 (もしくは他社 DNS における同様の機能) を一緒に使うことになるでしょう。 Active/Active な状態にしておくことで、 NLB の DNS レコードと同じような挙動を実現できそうです。

セキュリティグループについて

NLB そのものにはセキュリティグループを設定できません。バックエンドインスタンスにおいてセキュリティグループを設定する必要があります。 バックエンドインスタンスには送信元 IP アドレスがクライアントのままになっているパケットが届きますので、これをベースにして設定をする必要があります。 インターネットに公開する場合、ターゲットとなるポートは 0.0.0.0/0 からのアクセスを許可、ヘルスチェック用ポートは NLB が起動している subnet の CIDR からのアクセスを許可すれば良いでしょう。

internet-facing な NLB を使う場合、クライアントからバックエンドインスタンスへの直接アクセスを許可する必要がある?

という書き込みをちらほら見かけましたが、答えはNOです。 バックエンドインスタンスがクライアント (インターネット) からの直接アクセスを受けるためには以下の3条件が揃っている必要があります。

  • バックエンドインスタンスが所属する subnet に IGW が attach されている
  • バックエンドインスタンスに public IP もしくは EIP が割り振られている
  • バックエンドインスタンスが所属するセキュリティグループに適切な許可設定 (e.g. 80/tcp に 0.0.0.0/0 からのアクセスを許可) がされている

NLB のバックエンドインスタンスとなるためには、このうち最後の条件のみを満たしていれば良いです。 (上述の通り、 VPC Route Table においてデフォルトルート (0.0.0.0/0) が設定されている必要はあります)
よって、例えば以下のように NLB は public subnet に起動し、private subnet のインスタンストラフィックを流す (インターネットからのトラフィックは NLB のみで受ける) といった構成も問題なく行えることになります。

f:id:kani_b:20170920130251p:plain

ただし、internal においてもセキュリティグループが利用できないという制約は変わりません。つまり、バックエンドインスタンスのセキュリティグループにクライアントインスタンスのセキュリティグループを許可設定してもアクセスができません。 細かい制御を求められるところではちょっと不便ですね。

まとめ

NLB の挙動や存在する制約などについてまとめました。 アクセス制御の点で少し面倒な部分はあるものの、ALB や CLB と比較してもより普通に (X-F-F や Proxy Protocol などを考慮せず) 使え、かつ IP 固定なのは便利だなと思います。あと起動がめっちゃ早いです。 Zonality もクライアント依存な部分はありますが個人的には便利 (欲を言えばクロス AZ と選びたい) だなと思いました。 ALB で受けられない, もしくは不便なユースケースは NLB を積極的に使うと便利そう。

Simple Icons の更新もお待ちしてます!!!

*1:気になる方は glibc の getaddrinfo.c あたりを読んでみると雰囲気がわかると思います