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生成)。
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にあとからセキュリティグループを適用できないこと