Azure VM の SNAT の話


Azure VM は NAT Gateway のようなリソースがなくても勝手に SNAT して Internet 接続ができるようになっています。
ただ、勝手にいい感じにやってくれる親切設計な反面、仕組みを理解せずに使ってる人が多すぎるので、整理しておこうかなと。

SNAT に使用される Public IP

一応ドキュメントに記載はあるんですが、わかりづらいのでシンプルにまとめると以下のような感じになります。

  • VM に Public IP がある場合: VM (NIC) に紐づいた Public IP で SNAT
  • VM に Public IP がなく、外部 LB に紐づいている場合: LB の Public IP で SNAT
  • VM に Public IP も外部 LB にも紐づいていない場合: 当該リージョンの任意の Public IP で SNAT

したがって、送信元 IP を固定したい (対向側の Firewall 等で IP 指定で許可設定をしたい) 場合には、前者 2 点のいずれかになります。

SNAT Port の割り当て

VM が Public IP を持たないシナリオの場合、個々の VM に割り当てられる SNAT Port の数に注意が必要です。

  • VM に Public IP がある場合: 64k Port
  • VM に Public IP がなく、外部 LB に紐づいている場合: 最大で 1024 Port
  • VM にも外部 LB にも紐づいていない場合: 1024 Port

1 つの Public IP では TCP / UDP それぞれ 64k Port しか使えませんので、外部 LB の Public IP を使用する (複数の VM で Public IP を共有する) 場合 は、1 台あたりに割り当てられる Port が 1024 までに限られる点に気を付けましょう。(なお、LB 配下の VM が 1 台だけであっても、1024 Port しか使えません)
なお、LB のバックエンド プールが 50 台を超える場合には、割り当て Port がさらに少なくなります。(参考)

SNAT Port を使い果たした場合

LB の SNAT Port が使い果たした場合、当然ですが SNAT できないため Public IP 宛の通信ができなくなります。
Azure VM から Public IP あてに多数のセッションを張る場合や、Docker などのコンテナを利用するような場合は、1024 Port を早々に食いつぶす可能性もあるので、きちんと見積もりや設計を行いましょう。(Azure 基盤側としては、VM 上で何台コンテナが起動しているかは把握できないので、同一 VM 上で稼働するすべてのコンテナで 1024 Port を共有することになります。)

ちなみに、負荷テストや再現試験などで SNAT Port を意図的に枯渇させたい場合、Linux で hping3 を使うのがお手軽です。
例えば、以下のような形で実行することで、SYN パケットを100 マイクロ秒ごとに任意の Public IP (xx.xx.xx.xx) の 65000 番の Port に対して、ひたすら投げつけることができます。(一歩間違うと SYN Flood 攻撃になるので、やり方と通信先は慎重に検討しましょう。)

apt install hping3 -y
hping3 -S -i u100 -p 65000 <xx.xx.xx.xx>

外部 LB 配下の VM (すなわち 1024 Port しか使えない環境) で上記を実行すると、1 秒ほどで大量の応答が返ったのち、突然出力が止まるかと思います。Ctrl + C で hping3 を終了すると、以下のように 1024 packets までは応答が得られたものの、あとはパケロスしていたことが確認できます。

len=46 ip=xx.xx.xx.xx ttl=57 DF id=0 sport=65000 flags=SA seq=1011 win=29200 rtt=30.4 ms
len=44 ip=xx.xx.xx.xx ttl=64 DF id=0 sport=65000 flags=SA seq=1016 win=29200 rtt=29.8 ms
len=44 ip=xx.xx.xx.xx ttl=64 DF id=0 sport=65000 flags=SA seq=1007 win=29200 rtt=31.0 ms
len=44 ip=xx.xx.xx.xx ttl=64 DF id=0 sport=65000 flags=SA seq=1013 win=29200 rtt=30.3 ms
len=44 ip=xx.xx.xx.xx ttl=64 DF id=0 sport=65000 flags=SA seq=1012 win=29200 rtt=30.6 ms
len=44 ip=xx.xx.xx.xx ttl=64 DF id=0 sport=65000 flags=SA seq=1006 win=29200 rtt=31.4 ms
len=44 ip=xx.xx.xx.xx ttl=64 DF id=0 sport=65000 flags=SA seq=1009 win=29200 rtt=31.1 ms
len=44 ip=xx.xx.xx.xx ttl=64 DF id=0 sport=65000 flags=SA seq=1008 win=29200 rtt=31.2 ms
len=44 ip=xx.xx.xx.xx ttl=64 DF id=0 sport=65000 flags=SA seq=1014 win=29200 rtt=30.4 ms
len=44 ip=xx.xx.xx.xx ttl=64 DF id=0 sport=65000 flags=SA seq=1021 win=29200 rtt=29.5 ms
len=44 ip=xx.xx.xx.xx ttl=64 DF id=0 sport=65000 flags=SA seq=1019 win=29200 rtt=29.9 ms
len=44 ip=xx.xx.xx.xx ttl=64 DF id=0 sport=65000 flags=SA seq=1022 win=29200 rtt=29.5 ms
len=44 ip=xx.xx.xx.xx ttl=64 DF id=0 sport=65000 flags=SA seq=1018 win=29200 rtt=30.1 ms
len=44 ip=xx.xx.xx.xx ttl=64 DF id=0 sport=65000 flags=SA seq=1017 win=29200 rtt=30.2 ms
len=44 ip=xx.xx.xx.xx ttl=64 DF id=0 sport=65000 flags=SA seq=1020 win=29200 rtt=29.9 ms
len=44 ip=xx.xx.xx.xx ttl=64 DF id=0 sport=65000 flags=SA seq=1023 win=29200 rtt=29.4 ms
^C
--- xx.xx.xx.xx hping statistic ---
21434 packets transmitted, 1024 packets received, 96% packet loss
round-trip min/avg/max = 3.5/9.2/32.9 ms

なお、SNAT で使用した Port はセッションが閉じられたり (FIN/ACK)、強制的に切断されたり (RST)、アイドル タイムアウトに達した場合 (既定で 4 分) に開放されます。

また、外部 LB が Standard SKU の場合に限り、使用済みの SNAT Port をメトリックの機能で確認することが可能です。
先の例のように Ctrl + C で止めずに、長時間 1024 Port を消費させたままの状態にすると、以下のような感じになります。

Basic SKU の外部 LB を使っている場合は、上記のようなメトリックは利用できませんが、たしか診断ログの機能でアラート イベント ログに “Ports exhausted” と記録されたかと思います。 (参考)

SNAT Port の割り当てを増やしたい場合

VM (NIC の ipconfig) あたり 1024 Port では足りないという場合、対処策は以下のいずれかになります。

  • VM に Public IP を直接紐づける
  • Standard SKU の外部 LB で allocatedOutboundPorts を設定して、SNAT Port の割り当てを変更する

後者の allocatedOutboundPorts は Basic SKU では使えず、Standard SKU でのみ提供される機能ですが、1 VM あたりに割り当てる SNAT Port を 10000 Port などと明示的に設定することが可能です。(もちろん、1 台当たりの Port × LB 配下の VM 台数が 64k に収まるように設計する必要があります。)

現状 Portal からは設定ができないため、Azure PowerShell や CLI、REST、JSON テンプレートなどで設定する必要があります。(参考)

# 既存の LB の設定を取得
$LB = Get-AzLoadBalancer -ResourceGroupName SNAT-Test -Name SNAT-Test-StandardLB

# OutboundRule で AllocatedOutboundPort 等のパラメーターを設定
Add-AzLoadBalancerOutboundRuleConfig -LoadBalancer $LB -Name OutboundRule -AllocatedOutboundPort 10000 -Protocol All -EnableTcpReset -IdleTimeoutInMinutes 4 -FrontendIpConfiguration $LB.FrontendIpConfigurations -BackendAddressPool $LB.BackendAddressPools[0]

# LoadBalancingRule による SNAT を無効化 (これを忘れるとエラーになるので注意)
$LB.LoadBalancingRules[0].DisableOutboundSNAT = $true

# 設定を保存
Set-AzLoadBalancer -LoadBalancer $LB

以上、Azure での SNAT でハマりやすいポイントと対処策についてでした。


Azure VM での ICMP (ping, traceroute) について


Azure VM で ICMP を扱う際には、 いくつか 気を付けないといけない点があります。
Azure をそこそこ使っている人でもハマることがあると思う (特に Azure VM から外部宛) ので、応答が得られない際のご参考までに。

Azure VM に対して ping, traceroute する場合 (Inbound)

NSG の受信許可設定が必要

Azure VM の NIC と Subnet に紐づいた NSG で、受信セキュリティ規則にて ICMP の許可設定を行っているかを確認しましょう。

OS 内の Firewall で受信許可設定が必要

NSG に加えて、当然ですが OS 内の Firewall でも ICMP を許可しましょう。
(特に Windows はデフォルトで不許可です)

LB 経由の ICMP は不可

Azure の Load Balancer は TCP / UDP の通信しか中継できません。
必ず VM の IP に対して直接 ping, traceroute を実行しましょう。

Azure VM から ping, traceroute する場合 (Outbound)

VM に直接 Public IP の付与が必要

Azure では、VM に Public IP を付与せずとも、外部 LB やデータセンターの任意の IP で SNAT して Internet 接続ができます。
ただ、LB は TCP / UDP の通信しか中継できないため、VM に直接 Public IP を紐付けていない構成では ICMP の応答が返りません。

traceroute には NSG で受信許可設定が必要

traceroute の通信は、宛先に指定した IP アドレス以外から応答が戻るため、NSG で ICMP がブロックされていると応答が戻りません。
(TTL が 0 になった時点で、経路上のルーターの IP アドレスから Time Exceeded の応答が返るためやむを得ません。)

ちなみに、はじめの数ホップ (Azure 内) は応答が返りませんが、途中からはちゃんと応答が返るので焦らずに待ちましょう。

その他のパターンも見つけたら追記します。


Azure DNS のネーム サーバーを揃えたい


Azure DNS で DNS ゾーンを作成すると、ns1-XX.azure-dns.com / .net / .org / .info の 4 つのネーム サーバー (権威サーバー) が割り当てられますが、XX の部分はランダムで決まります。逆引きゾーン (113.0.203.in-addr.arpa 等) を複数作成するような場合に、同一のネーム サーバーを利用したいといった場合があるかと思いますが、そのような場合には PowerShell のスクリプト等で同一名のゾーンを複数作成してガチャを引きましょう。(今のところ、ネーム サーバーは 01 – 09 の 9 個のみのようで、10 個目を作ろうとすると「The zone ‘113.0.203.in-addr.arpa’ is not available.」の様なエラーで失敗します。)

$ZoneName = "113.0.203.in-addr.arpa"
$Location = "JapanEast"

for($i = 9; $i -lt 10; $i++){
    New-AzResourceGroup -Name "TempRG$i" -Location $Location
    New-AzDnsZone -Name $ZoneName -ResourceGroupName "TempRG$i" -ZoneType Public
}

作成が完了すると、以下のように 01 – 09 のネームサーバーに割り当てられた 9 個の DNS ゾーンが確認できます。ここから利用したいものを選んで、残りは消してしまいましょう。

ちなみに、Azure DNS はリソース グループの移動に対応しているので、[移動] – [別のリソース グループに移動する] から任意のリソース グループへ移動することも可能です。

リソース グループを一つずつ消すのは面倒なので、必要なものだけ移動してから、PowerShell でサクっと消しましょう。

$Location = "JapanEast"

for($i = 0; $i -lt 9; $i++){
    Remove-AzResourceGroup -Name "tempRG$i" -Force
}

ちなみに、Azure DNS で逆引きゾーンをホストする方法はドキュメントもあるのであわせてどうぞ。


Microsoft の IP アドレス系の情報



Azure に限らず、Microsoft 所有の IP アドレスの情報が Download Center で公開されているのを見つけたので、忘れないようにメモしておきます。

それぞれ Details とかに書いてありますが、これらのデータは更新されることがあるので Weekly とかでチェックしましょう。


Azure VM でネットワーク帯域制限をかける方法



VPN や ExpressRoute の帯域を特定のサーバーが食いつぶす時など、Azure VM であえてネットワークの帯域を制限したいという場合は、OS の QoS の機能を使いましょう。(逆に言えば、VPN Gateway や VNET 等の Azure 側で帯域制限をかける機能はありません)

以下のように、[gpedit.msc] – [コンピューターの構成] – [Windows の設定] – [ポリシー ベースの QoS] – [新規ポリシーの作成] から設定できます。

なお、受信方向の帯域制限は極めて限定的にしか出来ませんので、基本的には上記の QoS を大量の通信を発生させている送信元で設定する必要があります。どうしてもインバウンドの通信を制限したいという場合は、TCP のウインドウ サイズを絞ることができるので、それを使いましょう。(TCP の輻輳制御とか、ウインドウの概念は当然ご存知かと思いますが、知らない人は 3 分間 NetWorking を見て勉強しましょう)

※ 当然ですが、TCP のウインドウ サイズを制御するだけなので、以下のような制限が伴います。

  • TCP 以外のプロトコルは制御できません (無差別に送られてくる UDP パケットを抑えることはできません)
  • 帯域幅を明示的に制限できるわけではありません (受信用のバッファ サイズを 16  MB -> 1 MB -> 256 KB -> 64 KB と 4 段階で設定できるだけなので、xx Mbps といった制限はできません)
  • 帯域制御をおこなうトラフィックを細かく設定はできません (すべての受信トラフィックに対して一律で帯域幅が制限されます

以下のように、 [gpedit.msc] – [コンピューターの構成] – [Windows の設定] – [ポリシー ベースの QoS] – [QoS の詳細設定] から設定できます。

ということで、受信側で制限するのは無理があるので、送信側でアウトバウンドの通信を制限しましょう。OS の設定なので、Azureに限らずオンプレでも AWS とかの他社クラウドでも使えると思います。

P.S. ネットワークの最適化に関しては、Tech Summit 2018 のセッションがおすすめです。


Azure Automation で Invoke-WebRequest したあと Parse してくれない件



Azure Automation で wget したあと、プロパティが参照できなくて毎回同じ場所で時間を浪費してるのでメモ。

$Result = Invoke-WebRequest "http://www.contoso.com"
$Result.Links.href
Invoke-WebRequest : The response content cannot be parsed because the Internet Explorer engine is not available, or
Internet Explorer's first-launch configuration is not complete. Specify the UseBasicParsing parameter and try again.
At line:1 char:1
+ Invoke-WebRequest "https://bangumi.org/epg/td?broad_cast_date=2019031 ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotImplemented: (:) [Invoke-WebRequest], NotSupportedException
+ FullyQualifiedErrorId : WebCmdletIEDomNotSupportedException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

UseBasicParsing を使って、最小限のパースだけで我慢しましょう。

$Result = Invoke-WebRequest "http://www.contoso.com" -UseBasicParsing
$Result.Links.href