Amazon Network Load Balancer(NLB)でUDPを負荷分散してみた

NLBでUDPを負荷分散する場合、ヘルスチェックにはTCP/HTTP(S)を用います
2024.01.30

AWSのレイヤー4のロードバランサーAmazon Network Load Balancer(NLB)を利用すると、TCP/UDPプロトコルをリスナーとしたロードバランシングが可能です。

internet facingなNLBでUDPをロードバランシングする機会がありましたので、疎通確認できる最小構成を紹介します。

まとめ

NLBでUDPを扱う場合のポイントを先に整理します。

  • Application Load Balancer(ALB)はレイヤー7、Network Load Balancer(NLB)はレイヤー4
  • Classic Load Balancer(CLB)はレイヤー4/7に対応するが、UDPは未対応
  • NLBでUDPを処理する場合も、ヘルスチェックはTCP
  • NLBにはセキュリティグループの適用が推奨される
  • NLBにセキュリティグループを適用すると、オリジンのセキュリティグループのソースにはNLBのセキュリティグループを指定すれば良い

構成

2台のEC2上で動作するUDPサービスをinternet facingなNLBでロードバランシングします。 また、NLBにはセキュリティグループを適用します。

以降では、各リソースについて解説します

Network Load Balancer(NLB)

UDPプロトコルを受け付けるinternet facingなNLBを作成します。

セキュリティグループの設定では注意が必要です。

NLBは2023年8月からセキュリティグループに対応し、設定することが推奨されています。

セキュリティグループを設定せずにNLBを作成すると、あとからセキュリティグループを設定することはできません。 一方で、セキュリティグループとともに作成したNLBに対して、セキュリティグループを変更することは可能です。

後述するように、NLBのセキュリティグループは、バックエンドサービスのセキュリティグループのインバウンドルールのソースに使えるため、必ずNLB専用のセキュリティグループを適用しましょう。

ターゲットグループ

UDPプロトコルを受け付けるターゲットグループを作成します。

プロトコルに UDP または TCP_UDP を指定した場合、クライアントIPアドレスは必ず保持されます。

Client IP preservation can't be disabled for UDP and TCP_UDP target groups.

https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#target-group-attributes

ヘルスチェックの設定では注意が必要です。

ヘルスチェックの性質上、一方通行なUDPは使えず、TCP/HTTP/HTTPSプロトコルの中から選択します。 通信プロトコルが異なるため、サービスとヘルスチェックでポートを揃えることも分けることも可能です。

EC2

今回の構成では、ターゲットにEC2(Ubuntu 22.04)を指定しています。

EC2のセキュリティグループのインバウンドルールでは、以下の通信を許可します。

  • UDPサービス
  • NLBからのヘルスチェック

ソースには、NLBにアタッチしたセキュリティグループを指定します。

UDP サービスの起動

OpenBSD netcatで8080ポートのUDPをLISTENするには、以下のコマンドを実行します

$ nc -ulkv 8080
Bound on 0.0.0.0 8080

ヘルスチェック用サービスの起動

ヘルスチェック用に TCP 8080ポートでもLISTENします。

$ nc -klv 8080
Listening on 0.0.0.0 8080
Connection received on ip-172-31-6-159.ap-northeast-1.compute.internal 55971
Connection received on ip-172-31-6-159.ap-northeast-1.compute.internal 42034
Connection received on ip-172-31-6-159.ap-northeast-1.compute.internal 40071
...

アクセス元はNLBのENIです。

ターゲットのヘルスステータスが Healthy になっていることを確認して下さい。

LISTEN しているポートを確認

アプリ用UDP・ヘルスチェック用TCPをLISTENしていることを確認します。

$ netstat -nltu
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN
tcp6       0      0 :::22                   :::*                    LISTEN
udp        0      0 127.0.0.1:323           0.0.0.0:*
udp        0      0 0.0.0.0:8080            0.0.0.0:*
udp        0      0 127.0.0.53:53           0.0.0.0:*
udp        0      0 172.31.3.246:68         0.0.0.0:*
udp6       0      0 ::1:323                 :::*

ヘルスチェック用コンテナ

実行基盤がコンテナの場合、次のURLで紹介されているようなサイドカーコンテナを用意しましょう。

amazon ec2 - Healthcheck for NetworkLoadBalancer with UDP ECS service - Stack Overflow

動作確認

NLBに向けてUDPでデータ送信すると、バックエンドのUDPサーバーでデータを受け取れることを確認します。

次に、データを受け取っているEC2のヘルスチェックを落とすと、もう一台のEC2に通信が向けられることを確認します。

クライアントアプリ

netcat(nc)から

NLBのDNS名に対してUDP(-u)で接続し、メッセージを送信し、サーバーでデータを受け取れていることを確認します。

$ nc -uv <NLB-DNS-NAME>.elb.ap-northeast-1.amazonaws.com 8080

PING # なにかメッセージを入力

Pythonから

Pythonから1秒間隔でUDPデータを送信するクライアントアプリは以下の通りです(Chat GPT生成)。

udp_client.py

import socket
import time

def udp_client(host, port):
    # Create a socket
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    try:
        counter = 0
        while True:
            # Convert the incremented number to a string
            message = str(counter) + '\n'

            # Send the message via UDP
            client_socket.sendto(message.encode('utf-8'), (host, port))

            print(f"Sent: {message}")

            # Increment the counter
            counter += 1

            # Wait for 1 second
            time.sleep(1)

    except KeyboardInterrupt:
        print("Client stopped by user.")
    finally:
        # Close the socket
        client_socket.close()

if __name__ == "__main__":
    # Replace with the IP address of www.example.com
    server_host = "XXX.elb.ap-northeast-1.amazonaws.com"
    server_port = 8080

    udp_client(server_host, server_port)

$ python3 udp_client.pyでクライアントプログラムを実行し、サーバーでデータを受け取れていることを確認します。

$ nc -ulkv 8080
Bound on 0.0.0.0 8080
0
1
2
3
4
...

ヘルスステータスに連動したロードバランシングの確認

UDPメッセージを受け取っているEC2インスタンスでヘルスチェック用のTCPサーバーを落とし、以下を確認してください。

  • ターゲットグループの Health statusが Unhealthy になること
  • もう一台のEC2にUDPのトラフィックが流れること

最後に

Amazon NLBを使ったUDPのロードバランシング方法について紹介しました。

以下の3点を抑えておけば、UDP NLBと格闘する時間を減らせます

  • ヘルスチェックのためにTCP系プロトコルの口も用意すること
  • NLBにセキュリティグループを設定することで、バックエンドのアクセスコントロールがシンプルになること
  • セキュリティグループの当たっていないNLBにあとからセキュリティグループを適用できないこと