Amazon Network Load Balancer(NLB)でUDPを負荷分散してみた
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.
ヘルスチェックの設定では注意が必要です。
ヘルスチェックの性質上、一方通行な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生成)。
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にあとからセキュリティグループを適用できないこと