NLBでオンプレ・AWSの様々なターゲットIPと重複するポートへの通信を振り分ける
ちゃだいん(@chazuke4649)です。
先日 Network Load Balancer (以下NLB)の特性を活かしたネットワーク構成を組む機会があったので紹介します。
概要
構成図
背景や要件
- 2つのVPCピアリングでつなげる
- VPC-1のクライアントサーバからVPC-2のターゲットサーバへアクセスしたい
- ターゲット側には複数台存在し、複数同じ受けポートでリッスンしている
- これをクライアント側でポート番号を指定することによって意図したターゲットにアクセスできるように振り分けたい
- 待ち受けポートはhttp/httpsに限らず、SSHなど様々なポートへ通信させる必要がある
- ターゲットによってはVPC-2とサイト間VPN接続でつながっているオンプレ環境とも通信させる必要がある
対応方法
- NLBに中継させる
- NLB側でリスナー側のポートとターゲットグループ側のポートを必要な通信の数だけ用意する
- 一部ターゲットグループの宛先としてオンプレのターゲットサーバのIPを指定する
リスナー・ターゲットグループ組み合わせ一覧
NLBでは以下のようにリスナーとターゲットグループを組み合わせることができます。
No. | リスナー側ポート番号 | アクション | ターゲットグループ名 | ターゲットグループ側ポート番号 | ターゲットIP |
---|---|---|---|---|---|
1 | TCP:10022 | 転送 | tg-10022 | TCP:22 | TargetServer1 |
2 | TCP:10080 | 転送 | tg-10080 | TCP:80 | TargetServer1 |
3 | TCP:20022 | 転送 | tg-20022 | TCP:22 | TargetServer2 |
4 | TCP:20080 | 転送 | tg-20080 | TCP:80 | TargetServer2 |
5 | TCP:30080 | 転送 | tg-30080 | TCP:80 | TargetServer3 |
クライアント側で宛先側のポート指定が可能であれば、それぞれ一意のポート番号単位で、ターゲット側のポート番号とIPの組み合わせを変えるといった方法です。
これを行うと、クライアントとNLB間はリスナー側ポート番号で通信し、NLBとターゲット間はターゲットグループ側ポート番号で通信することが可能です。
やってみた
NLBがメインなので以下リソースの構築は割愛します。
- VPC関連(サブネット、ルートテーブルなど)
- VPCピアリング
- クライアント・ターゲットサーバのEC2インスタンス
- セキュリティグループ
NLBを作成する
今回はTerraformで構築しています。 以下がサンプルのTerraformのtfファイルとなります。
######################################################### # NLB: example-nlb ######################################################### resource aws_lb example-nlb { name = "example-nlb" load_balancer_type = "network" internal = true enable_deletion_protection = true enable_cross_zone_load_balancing = true subnet_mapping { subnet_id = aws_subnet.private-1a-1.id private_ipv4_address = "172.31.128.10" } subnet_mapping { subnet_id = aws_subnet.private-1c-1.id private_ipv4_address = "172.31.136.10" } } ######################################################### # NLB Listener & Target group 10022 ######################################################### resource aws_lb_listener listner-10022 { load_balancer_arn = aws_lb.example-nlb.arn port = "10022" protocol = "TCP" default_action { target_group_arn = aws_lb_target_group.tg-10022.arn type = "forward" } } resource aws_lb_target_group tg-10022 { name = "example-tg-10022" port = "22" protocol = "TCP" target_type = "ip" vpc_id = aws_vpc.example.id deregistration_delay = "300" depends_on = [aws_lb.example-nlb] health_check { port = "traffic-port" protocol = "TCP" healthy_threshold = "3" unhealthy_threshold = "3" interval = "30" } } resource aws_lb_target_group_attachment tg-10022 { target_group_arn = aws_lb_target_group.tg-10022.arn target_id = "172.31.129.10" port = 22 } ######################################################### # NLB Listener & Target group 30080 ######################################################### resource aws_lb_listener listner-30080 { load_balancer_arn = aws_lb.example-nlb.arn port = "30080" protocol = "TCP" default_action { target_group_arn = aws_lb_target_group.tg-30080.arn type = "forward" } } resource aws_lb_target_group tg-30080 { name = "example-tg-30080" port = "22" protocol = "TCP" target_type = "ip" vpc_id = aws_vpc.example.id deregistration_delay = "300" depends_on = [aws_lb.example-nlb] health_check { port = "traffic-port" protocol = "TCP" healthy_threshold = "3" unhealthy_threshold = "3" interval = "30" } } resource aws_lb_target_group_attachment tg-30080 { target_group_arn = aws_lb_target_group.tg-30080.arn target_id = "10.0.0.10" port = 80 # オンプレなどVPC外のIPを指定する場合にallに指定する availability_zone = "all" }
- 上記では長くなりすぎるのでリスナー・ターゲットグループ一覧の
No.1
とNo.5
のみ記述しています。(実際はmoduleを使って共通化すべきでしょう) - 11~18行目では、NLBの特徴である静的IPの指定を行っています。今回は2つのサブネットにまたがる形で両サブネットにそれぞれプライベートIPを入力しています
- 99行目では、ターゲットIPがオンプレ側(VPC外)の場合のみ、
availability_zone = "all"
の指定が必要です
疎通確認(HTTP)
まず、クライアントサーバにSSHで入ります。
実際に通信ができるかどうか確認していきます。以下ブログで紹介されているようなコマンドを使用します。
【初心者向け】各OSのTCP通信チェックコマンド入門(2018年 更新版) | DevelopersIO
以下コマンドを実行します。
[ec2-user@ip-192-168-1-214 ~]$ curl -v telnet://172.31.128.10:10080 * Rebuilt URL to: telnet://172.31.128.10:10080/ * Trying 172.31.128.10... * TCP_NODELAY set * Connected to 172.31.128.10 (172.31.128.10) port 10080 (#0)
TCPコネクションが貼られていることが確認できました。
ただし、本当にHTTPを返してくれるかわかりません。次は、 curl -I
でHTTPレスポンスヘッダを取得してみます。
[ec2-user@ip-192-168-1-214 ~]$ curl -I 172.31.128.10:10080 HTTP/1.1 200 OK Date: Fri, 12 Mar 2021 08:59:48 GMT Server: Apache/2.4.46 () Upgrade: h2,h2c Connection: Upgrade Last-Modified: Fri, 12 Mar 2021 08:59:11 GMT ETag: "12-5bd531d172fe5" Accept-Ranges: bytes Content-Length: 18 Content-Type: text/html; charset=UTF-8
ターゲットサーバ側で起動しているApahcheのテストページのレスポンスヘッダを取得することができました。HTTPの疎通確認は以上です。
疎通確認(SSH)
次はSSHです。こちらは同じくNLBの1a側のAZの静的プライベートIP宛にポート番号を指定して、SSH接続を試みます。
[ec2-user@ip-192-168-1-214 ~]$ssh -p 10022 -i test.key ec2-user@172.31.128.10 The authenticity of host '54.168.234.185 (54.168.234.185)' can't be established. ECDSA key fingerprint is SHA256:NfbQno8PS3VjO8BlXdJzIhs7fvfgl4QwFxtQsJHqg6y. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '54.168.234.185' (ECDSA) to the list of known hosts. __| __|_ ) _| ( / Amazon Linux 2 AMI ___|\___|___| https://aws.amazon.com/amazon-linux-2/
上記の通り成功しました。
成功ついでに、netstat
コマンドで 本当に22番ポートが使用されているか確認してみます。
[ec2-user@ip-172-31-129-10 ~]$ netstat -n |grep 22 tcp 0 0 172.31.129.10:22 172.31.128.10:44387 SYN_RECV tcp 0 0 172.31.129.10:22 xxx.xx.xx.xxx:22814 ESTABLISHED ...
上記の通り、2つのうちどちらも22番ポートで受け付けており、NLB側のIPとはTCPのある通信状態(SYN_RECV)を示しています。また、xxx.xx.xx.xxx(ローカルマシンのグローバルIP)とESTABLISHED(TCPコネクションが確立できている状態)であることが確認できました。
トラブルシュート
最初、疎通確認がうまくいかないことがありましたが、以下ポイントを確認すると概ね解決しました。
- ルートテーブルの設定に不備がないか
- ターゲットグループのヘルスチェックがHealthyかどうか
- ターゲットサーバのセキュリティグループでNLBからのIPとポートを許可しているか など
終わりに
NLBを触るとネットワークにおけるトランスポート層の理解が深まることがわかりました。
それではこの辺で。ちゃだいん(@chazuke4649)でした。