久々に図を書きたくなったので、今回は AWS の VPC ネットワークの主にルーティングについて雑談気味に振り返っていきたいと思います。
基本的なところから、少し発展させた構成はどう実現するのか、ってのを学習用教材風にお絵かきしていきます。
はじめに
最近、実現できると思ってた部分が1つ通らなくて、詰んだかと一瞬諦めかけたところで、10分間くらい思案したらズバッと解決できて気持ちよかったんですよ。我ながら物凄い泥々の技術を駆使して、発想通りにAWS仕様を回避したネットワークを構築してしまった。泥臭すぎてとてもブログに書けないけどっ…自分の中では満点かつスタイリッシュっ
— 外道父 | Noko (@GedowFather) July 7, 2023
元々泥臭いエンジニアリングが得意だったとはいえ、こういう発想の展開力はクラウドだけ真面目にイジってても身につきづらいだろうな、と思い、そういう想像力・構成力につながる記事にできたらなと思った次第です。
昔は、データセンターにゼロから構築すると、グローバル回線の契約から、プライベートネットワークの構成や物理配置まで、色々考えてやることがあって大変でした。
今は設定をチョイチョイってやったり Infrastructure as Code(IaC) をポチるだけなので、良い時代と言えばそうなのですが、老害的に言えばそれだけだとエンジニアとして重要な基礎が抜けてしまい、綺麗なクラウド仕草はできても、1つ2つ突き抜けた発想ができなくなるんじゃないか、と思ったりもします。
だからといって今更物理サーバーやケーブルをイジれってわけでもないし、どんなシステムやプログラムも、小難しい部分を知らない・触らないで済むように発展していく特性があるので、どうしたもんかといったところで教材的意味合いにもできたらと思います。
AWS VPC の基本
まずは VPC の簡単な内容からですが、以降の内容は AWS に限らず、どこのクラウドでも似たりよったりな仕様であり、今回はAWSを元に書いているからタイトルにもつけただけです。また、ルーティングについてが主題なので、Multi-AZ や SecurityGroup 等は図が狭くなるので省いていますが、その辺は適当に脳内補完してください。
まず必ず存在するルーティングは、送信先が VPC CIDR (ここでは 10.1.0.0/20) となり、VPC 内の Subnet 全てが行き来できるようになっています。
そして Public Subnet には Internet G/W を付属させ、外部インターネット全体を表す 0.0.0.0/0 の送り先とすることで、Public 内のリソース自身から外部に通信できるようにします。
Private Subnet の NAT G/W はオプション的存在で、Private のリソースが外部にリクエストを送る必要がある場合は作成して、同様に 0.0.0.0/0 のお任せ先とします。
これら G/W は似たように見えるかもですが、Internet G/W は内側からリクエストを出すのと、外から受けたリクエストのレスポンスを返すために通りますが、NAT G/W は内側からリクエストを出す専用であり、内側のリソースは Globalアドレスを持たないので外からのリクエストは受けられない立ち位置ということになります。
費用と転送
費用的には Internet G/W は存在自体は無料、NAT G/W は有料なので、NAT G/W は必要な場合のみ作るべきということになります。また、どちらも存在する場合で、リソースをどちらに置くか迷う時、Internet G/W のデータ転送はインプットが無料でアウトプットが有料、NAT G/W はイン/アウトどちらも有料なので、機能要件を満たした上でならば、その通信内容によって安く済む方を選ぶべきです。
イン/アウトの例としては、
- 外部からVPC内リソースへのリクエスト = インプット、レスポンス = アウトプット
- VPC内リソース自身による外部からのダウンロード = インプット、アップロード = アウトプット
ソースアドレスのための NAT G/W
次に通信内容としては、PublicIP や EIP を持つリソースから送り出された Internet G/W から出ていくパケットは、その送信元アドレスがそのまま受信側に伝わりますが、Globalアドレスを持たないリソースから出されて NAT G/W を経由すると名前の通りNAT(Network Address Translation) するので、NAT G/W に割り当てられた EIP が受信側にソースアドレスとして伝わります。この特性は、IPアドレスを固定で送信し、受信側のシステムにアドレス制限してもらう時などにも使われます。AWSのGlobalアドレス範囲は公開されていますが、自身のアカウントや VPC において任意の範囲のGlobalIPアドレスを割り当てることはできないため、複数のサーバーからのリクエストを相手側に接続制限してもらうには、この方法が一番手に上がります。二番手としてはプロキシサーバーを経由するとかですが、その場合は設定項目やリソース数が多くなりがちになります。
外部通信のための NAT G/W
Private Subnet に所属しないといけないリソースだけど、外部通信を行う場合、NAT G/W が必要になるので、いくつか例をあげます。Lambda を利用する時、VPC に所属させるかさせないかを選択する必要があり、所属しなければ外部通信が可能ですが、VPC に所属した場合に外部通信したい場合は NAT G/W を通す必要があります。というルールになっています。例えば、RDS に SQL を発行するためにVPC所属にし、かつ外部に存在する AWS API を実行したい場合はこれにあたります。
データセンター移行などでDBレプリケーションをする場合には、Private に存在する RDS が、外部サーバーから暗号化レプリケーションをすることになるので、NAT G/W を通して外部へレプリケーション接続することがあります。ただこれは、VPN を別途接続してルーティングを通せば、プライベートアドレスだけでやり取りできるので、作業量や接続期間などを踏まえて考えることになるでしょう。
ルーティングの適用ルールと通信探索
ここでひとつ、基本ではありますが復習しておくと、ルーティングは 送信先(Destination)の CIDR と、転送先(Target, Gateway) のリソース(IPアドレスやクラウドリソースID)で成り立つ表データで、Routing Table とも言います。とある通信が発生し、その送信先IPアドレス(dst addr)が判明しているとき、その通信の行き先がどこになるのか、を決めるルールとなっています。自宅やその辺のWifiから、遠く離れた国内外のインターネット上のサービスにアクセスできるのは、物理的なネットーワーク以外に、途中経路に数あるルーティングが全て正しく設定・機能しているからと言っても過言ではないでしょう。
Routing Table の Dest CIDR を小さい範囲から順に当てはまるかをチェックしていき、送信先アドレスがその範囲に当てはまった場合はその Target に転送されることが決定されます。どれにも当てはまらず、0.0.0.0/0 が存在する場合は、それが デフォルトルート となり、その Target に転送されます。デフォルトルートもない場合は行き先不明となり破棄されます。
自社環境においては、ある程度複雑なネットワークを構築するときや、構成的に少し遠くにあるサーバーへの接続に失敗する時などに、ルーティングを丁寧に追えることが構築や問題解決を早めてくれます。
送信先アドレスから、その発生元リソースのルーティングを見て、転送先を確認します。その転送先のサーバーにて tcpdump などでパケットが通っているか確認し、通らないならその間に原因を探し、通るならさらにその先の Target で同様のチェックをし・・・最終的に到着するサーバーまで届いているかを見届けます。それで往路の正常性を確認できますが、レスポンスが最初のクライアントに正常に返らない場合は復路に問題があるので、今度は逆順に復路を追ってどこで止まっているかを探ります。
ネットワーク的に問題があるとしたら、クラウドリソースの Routing, SecurityGroup, NetworkACL あたりで、経路途中に EC2 があるなら Routing, VPN Tunnel, firewalld あたりをチェックしていきます。クライアントが出した送信先アドレスを持つサーバーへの到達・往復に問題がないならば、そのサーバー自身のアプリケーションとしての機能やサーバーリソース状態に原因を見出していくことになります。
VPC Peering の基本形
前置きが長くなったので、ここからはサクサク構成例を出していきます。VPC Peering は別VPC同士を繋ぐ仕組みで、アカウント内のVPC同士も接続できますが、今回は別アカウントのVPCを繋ぎます。その場合、1対1の関係性というよりは、中央アカウント的存在に基幹系システムを置き、そこに複数の衛星アカウントが Peering を通して接続するような、1 : N の関係性が多いのではなかろうか、と思います。
VPC Peering は片方のアカウントから、もう一方のアカウントへ接続リクエストを送り、受信側アカウントで承諾することで成立します。成立後は、双方のルーティングを設定することで行き来することが可能になります。
衛星から中央に行きたい場合は、Destination を中央CIDR , Target を Peering ID として衛星アカウントにルーティングを登録します。ただし、これだけだと往路しかないので、中央アカウントからの復路を確保するには、中央アカウントのルーティングに Destination を衛星CIDR , Target を Peering ID として登録する必要があります。
色んなネットワークの設定において、往路を通せば自動的に復路も許可されるものと、往路と復路は別々に許可するものがありますが、ルーティングは許可系ではなくあくまで経路の話なので、行って返ってくるまでが遠足ですということで、迷子にならないよう丁寧に案内してあげる必要があるということです。
基幹系システムには、認証・監視・ログ集約 などがあり、複数のアカウントを管理するようになった時に、各アカウントに基幹系のプロキシやレプリケーション用のリソースを用意して、各アカウント内でそれを利用する方法もあります。が、直接中央リソースにアクセスできた方がそういった余計な衛星でのリソース作成を省略できるので、中央アカウントを意識した設計を早めに手掛けた方が、会社が成長するほどに恩恵を受けられることになります。
少々ひねくれた考えをするならば、中央の基幹系サーバーが SPOF (Single Point Of Failure) に近い状態となり、その影響を避けるために各アカウントにあえてリソースを分散することもあるかもしれません。それでも衛星が10, 20と増えることを想定するならば、どちらが管理しやすく安価かは明白なので、シングルポイントの解決もしくはその箇所のリスクを軽減する方向に考えを振った方が良いと思われます。
オフィスから衛星へ
オフィス内のネットワークから、社員がクラウド上の社内サービスにアクセスしたり、SSHでログインしたり、ということは普通にあると思いますが、複数のアカウントを管理するようになると、その経路を適切に設計しないと無駄といえる手間ひまがかかっていくことになります。直接Global経由でアクセスするためにIPアドレス制限だらけにしたり、各アカウントにVPNサーバーを建てる、ようなクソ設計はまずありえなく、VPN と VPC Peering の両方を合わせて Private な経路を通すのがベターです。
AWS含むクラウド環境を 10.0.0.0/8 に収める設計にしたとき、User の 10.0.0.0/8 への通信はまず社内VPNに行くようルーティングします。
社内VPNが受けた通信は、VPN Tunnel の CIDR ルール(AWS は 10.0.0.0/10)に従って、AWS VPN に転送されます。(※複数のクラウドやデータセンターを扱うことを想定し、クラスA (10.0.0.0/8) をイィ感じに /10 ~ /12 ずつなどに分割して、各環境に仮想設計上の範囲を割り当て、それぞれのクラウド内VPNへルーティングすることになります)
そこで、AWS VPN から衛星VPC に転送されることはルーティング上は可能ですが、そのまま転送してしまうと衛星からの復路の送信先がオフィスCIDRとなってしまい、返り道で迷子になってしまいます。返り道に オフィスCIDR → Peering ID のルーティングを登録すること自体は可能なのですが、Peering に登録された CIDR 以外は暗黙的に破棄されてしまうからです。
- VPC ピアリングの基本 – Amazon Virtual Private Cloud
- ルーティングオプションの例 – Amazon Virtual Private Cloud
- VPC ピアリング接続のルートテーブルを更新する – Amazon Virtual Private Cloud
それを回避するためには、AWS VPN から衛星に向けて出ていく際に NAT して、ソースアドレスを AWS VPN の PrivateAddr に書き換えてあげる必要があります。firewalld でいえばこんな感じ。
1 2 3 |
firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 10 -s 192.168.0.0/16 -d 10.0.0.0/8 -j ACCEPT firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 100 -j DROP firewall-cmd --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -d 10.0.0.0/8 -j MASQUERADE |
VPNとしては他にも、FORWARD するためにインスタンス設定の『送信元/送信先チェック』を停止したり、SecurityGroup を通したり、sysctl の net.ipv4.ip_forward = 1 にしたり、など色々ありますが、そのへんは今回は割愛します。
NATされて衛星に到着し、復路が返る際には、送信先アドレスが AWS VPN になっているので、Peering ID に転送されて無事に AWS VPN に返り、そこからオフィス CIDR の Userアドレスに返ることになるので、VPN Tunnel を通って無事に User まで帰り着くことができます。
こういう構成にしておけば、いくら衛星アカウントが増えても、Terraform とかチョイとポチるだけで安全な経路ができるわけで、なんなら扱うクラウドが増えてもちょっと設定増やして反映するだけになるので、VPN 周りをキッチリ柔軟に取り扱えるのは結構な武器になると言っていいでしょう。
2つ先への接続は不可
仕組みやサービスが増えてくると、こんな感じのこともしたくなる場合があります。しかし、これは実現することが不可能になっています。理由はさきほどのAWSドキュメントに書いてありますが、VPC Peering 範囲外の送信先へは Peering を通して出ていけないからです。また、なんとかして往路を確保したとしても、復路のルーティングを通せずやはり破綻します。
普通に考えて、別のアカウントの外部 G/W から出ていけてしまうと、そのデータ転送費用はどっちが払うねんとか、2つ以上先に自由に転送可能としてしまうと技術的負債候補な設計をユーザーがしてしまうとか、色々グダグダになりそうなので、妥当な仕様であると言えます。
衛星から2つ先へ変化球
さきほど例にあげたオフィスから衛星への通信は、一見オフィスから2つ先の環境に到達していますが、AWSだけで見れば中央アカウントが発生源とも言えるので、実質AWS内では1つ先にしか行っていないため実現可能でした。今度は衛星から少々特殊な構成で2つ先に行ってみるとします。何かしらの事情、例えば中央アカウントのVPC Peering最大数(125) に到達したとか、諸事情で直接中央アカウントには繋げられない、などに遭遇したとして、サブ中央アカウントを経由して基幹システムにアクセスすることにします。
この例は完全に好きモンしか見なくて良いものですし、他にもやり方はあるでしょうが、今回のは firewalld と Route53 を駆使して、基幹システムへの接続用 FQDN を変えることなく、2つ先のアカウントに疎通可能にしたものです。
Peering による、お隣 VPC なら system.example.com でアクセスできるところを、2つ目の FQDN を設定したり、プロキシ的なリソースを作るのは、無駄が増えるので好ましくありません。
そこでまず、正規のゾーンと同じ名前でプライベートゾーンを衛星アカウント内に作成し、対象FQDNの値を隣のサブ中央VPNの Privateアドレスにして登録します。これで名前を変えずにサブ中央の VPN まで往路が確保されることになります。
次に、サブ中央VPN で受けた通信を、filrewalld で DNAT し、丸っと system サーバーの Privateアドレスに転送します。firewalld の設定としてはこんな感じ。
1 2 3 4 5 6 7 8 9 |
HOST="system.example.com" DEST_ADDR=$(host $HOST | awk '{print $NF}') SELF_ADDR=$(ip a | grep inet | grep eth0 | awk '{print $2}') PORT=80 firewall-cmd --permanent \ --add-rich-rule='rule family="ipv4" protocol="tcp" \ destination address="${SELF_ADDR}" forward-port port="${PORT}" \ to-addr="${DEST_ADDR} to-port="${PORT}""' |
IPアドレスとポートのセットに対して、1つの転送ルールを設定できますが、もしポートが被る2つ目の転送をしたければ新しいPrivateアドレスを割り当ててあげる必要がある、という弱点はあります。
衛星アカウントでの Route53 プライベート・レコードの上書きは Terraform でいかようにも自動化できるので、作業は無いに等しいですが、綺麗か汚いかで言えば汚い寄りですし、サーバーのIPアドレス変更に対して対応作業が発生するので、そういうこともできるよ、という1つの事例として受け取ってもらえればと思います。
AWSリソース vs Linux
VPN と VPC の構築管理には、AWSサービスを利用する場合『VPC Transit Gateway』や『VPN Connection』を扱うことができます。できるだけ全てをAWSリソースに寄せるというのも、ひとつの正着なのですが、昔からルーターや VPN を Linux で構築してきた身としては、圧倒的にLinux が勝ると考えています。まず費用が安いです。そこまで重要度の高い用途にしないのであれば、t3.micro や t3.small あたりで十分で、AWS構成と比べると数倍から下手すると規模次第では10倍以上の差額になると思われます。
機能面では『VPC Transit Gateway』にはログや監視など専用機能はあるものの、正直たいして役に立つほどのものではないので、値段に見合うかというとだいぶ微妙な感じがします。Linux の場合、今回例にあげた通り、多少捻くれた構成を実現したり、アクセス制御を自由にしたりと、柔軟性を確保できるのは大きいです。
そして複数のクラウドを扱うことを考えた時、各クラウドに全く同内容のVPNサーバーを構築すればよいので、クラウド間の差を少なく再現でき取り扱いがしやすいです。
VPN用ミドルウェアは採用したいVPNプロトコルに応じて大体存在すると思いますが、オフィス内でVPN接続を任せるネットワーク機が対応しているものに合わせることになるでしょう。
オフィスの方もLinuxなら双方を合わせるだけなので簡単ですが、会社の規模が大きくなるにつれて、徐々にそういうお手製の管理物は減らしていくので、時期によって良いチョイスが異なるかもしれません。
ルーティング自体は特に難しい知識ではありませんが、ルーティング含めたネットワーク通信関連の理解を深め、丁寧に通信を追えるようになることは、構築や問題解決において地味で確かな技術となります。
自社ネットワークの全てを把握したり、クラウドのブラックボックスな箇所で何が起きたか推測したり、多くの経験を積んで迅速柔軟に対応できるITエンジニアになっていきまっしょい:-)