ネットワークエンジニアのITブログ

長らくネットワークで生活してきましたが、ここ数年クラウドとサーバー系に触れる機会が増えて、日々成長しています。最近のお気に入りはNSXALBとGoogle Cloud。

NSXのルートアドバタイズでルート情報を制御する

NSXのTier0ゲートウェイでは、ダイナミックルーティングプロトコルとして、BGP、OSPFがサポートされていて、オーバーレイで作成したセグメントやスタティックルートの経路情報を広報することができます。
今回、ルートアドバタイズという機能を使用して、特定の経路情報を広報したり、広報させないといった制御を実施してみます。

構成

ベースのネットワーク構成はこちらになります。

今回は、以下のようなシチュエーションを想定して設定します。
・BIG-IPで負荷分散用のVIPを仮想的に定義している。(VLANは定義していない)
・ACIからBIG-IPのVIP宛にスタティックを設定している。
・VIP2、VIP4をBIG-IPからNSXALBに移行し、BIG-IP、NSXALBのどちらのVIPにも通信可能とする。

実現したいこと

NSXALBでVIPセグメントを作成するとOSPFに広報されてしまうので、192.168.161.0/24の広報を停止する。
ただし、VIP2[.21]、VIP4[.41]への通信は行いたいので、/32で広報する。
併せて、VIPセグメントのGWアドレス[.1]、SEのI/Fアドレス[.2/.3を広報する。

設定変更前

Tier1でVIPセグメントを作成した状態で、OSPFで接続しているC891のルーティングテーブルを確認してみます。
192.168.161.0/24は、BIG-IP宛のスタティックによりOSPFの情報を受信してもルートテーブルにはのってきません。
ただし、show ip ospf databaseで確認すると、192.168.161.0/24はデータベース上には登録されています。

C891#sh ip route
Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
       ia - IS-IS inter area, * - candidate default, U - per-user static route
       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP
       a - application route
       + - replicated route, % - next hop override, p - overrides from PfR

Gateway of last resort is 192.168.100.1 to network 0.0.0.0

S*    0.0.0.0/0 [1/0] via 192.168.100.1
      192.168.3.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.3.0/24 is directly connected, Vlan3
L        192.168.3.2/32 is directly connected, Vlan3
      192.168.13.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.13.0/24 is directly connected, Vlan13
L        192.168.13.2/32 is directly connected, Vlan13
      192.168.100.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.100.0/24 is directly connected, Vlan100
L        192.168.100.254/32 is directly connected, Vlan100
O E2  192.168.151.0/24 [110/20] via 192.168.13.63, 02:07:28, Vlan13
                       [110/20] via 192.168.13.62, 02:08:25, Vlan13
S     192.168.161.0/24 [1/0] via 192.168.3.1 ★
C891#

C891#sh ip os database

            OSPF Router with ID (192.168.100.254) (Process ID 10)

                Router Link States (Area 0)

Link ID         ADV Router      Age         Seq#       Checksum Link count
192.168.13.62   192.168.13.62   862         0x80000007 0x003378 1
192.168.13.63   192.168.13.63   792         0x80000007 0x003177 1
192.168.100.254 192.168.100.254 1783        0x80000185 0x0006BE 3

                Net Link States (Area 0)

Link ID         ADV Router      Age         Seq#       Checksum
192.168.13.2    192.168.100.254 1783        0x80000005 0x00F4C0

                Type-5 AS External Link States

Link ID         ADV Router      Age         Seq#       Checksum Tag
0.0.0.0         192.168.100.254 533         0x80000134 0x00AFF8 10
192.168.151.0   192.168.13.62   922         0x80000005 0x00FD47 4016
192.168.151.0   192.168.13.63   832         0x80000005 0x00F74C 4016
192.168.161.0   192.168.13.62   540         0x80000002 0x0095A8 4016 ★
192.168.161.0   192.168.13.63   516         0x80000002 0x008FAD 4016 ★
C891#

Tier0ゲートウェイの設定

[ネットワーク]-[Tier0ゲートウェイ]から「t0gw01」の左にある3点マークを選択する。

編集を選択する。

ルート再配分の「1」を選択する。

ルート再配分の設定から、「redistribute-route」の左にある3点マークを選択する。

編集を選択する。

ルート再配分の「1」を選択する。

アドバタイズされたTier1サブネットの中にある「スタティックルート」にチェックを入れ、適用を選択する。

追加を選択する。

適用を選択する。

保存を選択する。


Tier1ゲートウェイの設定

[ネットワーク]-[Tier1ゲートウェイ]から「t1gw01」の左にある3点マークを選択する。

編集を選択する。

ルートアドバタイズから、「ルートアドバタイズルールの設定」にある「設定」を選択する。

「ルートアドバタイズルールの設定」から、「ルートアドバタイズルールの追加」を選択する。

以下の情報を入力し、「追加」を選択する。
ここでは、アドバタイズアクションを「拒否」とすることで、経路情報を広報しなくなります。
名前:192.168.161.0/24
サブネット:192.168.161.0/24
フィルタの適用:有効
アドバタイズアクション:拒否


再度「ルートアドバタイズルールの追加」を選択し、以下の情報を順番に登録して適用を選択する。
名前:192.168.161.1/32
サブネット:192.168.161.1/32
フィルタの適用:無効
名前:192.168.161.2/32
サブネット:192.168.161.2/32
フィルタの適用:無効
名前:192.168.161.3/32
サブネット:192.168.161.3/32
フィルタの適用:無効
名前:VIP2_192.168.161.21/32
サブネット:192.168.161.21/32
フィルタの適用:無効
名前:VIP4_192.168.161.41/32
サブネット:192.168.161.41/32
フィルタの適用:無効

保存を選択する。


動作確認

C891にてルーティングテーブルを確認しましょう。
192.168.161.1~3、21、41の経路情報をO E2で受信していることが確認できました。
また、show ip ospf databaseでは、192.168.161.0/24の経路情報が広報されていないことが確認駅ました。

C891#sh ip route
Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
       ia - IS-IS inter area, * - candidate default, U - per-user static route
       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP
       a - application route
       + - replicated route, % - next hop override, p - overrides from PfR

Gateway of last resort is 192.168.100.1 to network 0.0.0.0

S*    0.0.0.0/0 [1/0] via 192.168.100.1
      192.168.3.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.3.0/24 is directly connected, Vlan3
L        192.168.3.2/32 is directly connected, Vlan3
      192.168.13.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.13.0/24 is directly connected, Vlan13
L        192.168.13.2/32 is directly connected, Vlan13
      192.168.100.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.100.0/24 is directly connected, Vlan100
L        192.168.100.254/32 is directly connected, Vlan100
O E2  192.168.151.0/24 [110/20] via 192.168.13.63, 02:40:16, Vlan13
                       [110/20] via 192.168.13.62, 02:41:13, Vlan13
      192.168.161.0/24 is variably subnetted, 6 subnets, 2 masks
S        192.168.161.0/24 [1/0] via 192.168.3.1 ★
O E2     192.168.161.1/32 [110/20] via 192.168.13.63, 00:09:05, Vlan13 ★
                          [110/20] via 192.168.13.62, 00:09:05, Vlan13 ★
O E2     192.168.161.2/32 [110/20] via 192.168.13.63, 00:09:05, Vlan13 ★
                          [110/20] via 192.168.13.62, 00:09:05, Vlan13 ★
O E2     192.168.161.3/32 [110/20] via 192.168.13.63, 00:09:05, Vlan13 ★
                          [110/20] via 192.168.13.62, 00:09:05, Vlan13 ★
O E2     192.168.161.21/32 [110/20] via 192.168.13.63, 00:09:05, Vlan13 ★
                           [110/20] via 192.168.13.62, 00:09:05, Vlan13 ★
O E2     192.168.161.41/32 [110/20] via 192.168.13.63, 00:09:05, Vlan13 ★
                           [110/20] via 192.168.13.62, 00:09:05, Vlan13 ★
C891#

C891#sh ip os da

            OSPF Router with ID (192.168.100.254) (Process ID 10)

                Router Link States (Area 0)

Link ID         ADV Router      Age         Seq#       Checksum Link count
192.168.13.62   192.168.13.62   1221        0x80000008 0x003179 1
192.168.13.63   192.168.13.63   1134        0x80000008 0x002F78 1
192.168.100.254 192.168.100.254 1877        0x80000186 0x0004BF 3

                Net Link States (Area 0)

Link ID         ADV Router      Age         Seq#       Checksum
192.168.13.2    192.168.100.254 1877        0x80000006 0x00F2C1

                Type-5 AS External Link States

Link ID         ADV Router      Age         Seq#       Checksum Tag
0.0.0.0         192.168.100.254 608         0x80000135 0x00ADF9 10
192.168.151.0   192.168.13.62   1281        0x80000006 0x00FB48 4016
192.168.151.0   192.168.13.63   1210        0x80000006 0x00F54D 4016
192.168.161.1   192.168.13.62   682         0x80000001 0x0069D6 4014 ★
192.168.161.1   192.168.13.63   682         0x80000001 0x0063DB 4014 ★
192.168.161.2   192.168.13.62   682         0x80000001 0x005FDF 4014 ★
192.168.161.2   192.168.13.63   682         0x80000001 0x0059E4 4014 ★
192.168.161.3   192.168.13.62   682         0x80000001 0x0055E8 4014 ★
192.168.161.3   192.168.13.63   682         0x80000001 0x004FED 4014 ★
192.168.161.21  192.168.13.62   682         0x80000001 0x00A08B 4014 ★
192.168.161.21  192.168.13.63   682         0x80000001 0x009A90 4014 ★
192.168.161.41  192.168.13.62   682         0x80000001 0x00D740 4014 ★
192.168.161.41  192.168.13.63   682         0x80000001 0x00D145 4014 ★
C891#

これで、移行したVIP2、VIP4宛はOSPFでTier0へルーティングされ、それ以外の経路は、今まで通りスタティックによりBIG-IPへルーティングできることが確認できました。

ESXiホストの起動中にパープルスクリーンでSecurity Violationが発生した際の対応

ホームラボのESXiホストを再起動した際に応答がなかったので、直接コンソール接続で確認したところ、パーブルスクリーンが表示されていたので、原因と対応方法について紹介します。

発生した事象

直前にネットワーク構成変更やケーブル接続変更を行った際にホストに通信できなくなり、ESXiのネットワーク設定を初期化し再起動を行ったので、直接コンソール接続したところパーブルスクリーンで停止していました。

どうやら、セキュリティ違反があって起動できなかった模様です。
以下のページを参照するよう記載があったので参照してみました。
https://via.vmw.com/security-violation

上記ページを参照するとVMのKBにリダイレクトされました。
kb.vmware.com

原因

KBを参照するとパーブルスクリーンのメッセージは以下の3パターンがあるようですが、私のメッセージは、Aのセキュリティ違反の検出に該当しました。

セキュリティ違反の場合は、以下の項目をチェックするよう記載があったので確認したところ、2点目のsecure bootが無効になっていました。
1.If TPM 2.0 has been disabled, re-enable it.
2.If UEFI secure boot has been disabled, enable it back.
3.If execInstalledOnly boot option is set to FALSE, change it back to its initial value (i.e. TRUE).
Add "execInstalledOnly=TRUE" to the boot command-line (press shift+o when mboot starts and you see a 5 second countdown, right after the bios finishes running).

そもそも、今までsecure bootは有効となっていたのに、なぜ、設定が変更されたのかを思い出したところ、ネットワーク設定を初期化したタイミングで、BIOSファームウェアの更新がかかっており、このタイミングでBIOSの設定がリセットされていたことが根本原因でした。

対策

原因がわかったので、secure bootの有効化を行います。
BIOSメニューからセキュアブートの構成を表示します。

無効になっていたので、チェックを入れて有効化します。

対策を実施し、再起動したところ無事ESXiが起動してくれました。
一時はどうなるかと思いましたが、エラーが発生したときは、焦らず画面のメッセージを確認し、一つ一つ切り分けをすることで対処できると改めて思いました。

NSX 4.1.2でOSPF Routingをやってみる

以前、NSX-T 3.1.1.0を導入したときは、OSPFがサポートされておらず、動的ルーティングはBGPだけでした。その後、すぐにOSPFがサポートされたのですが、今回、NSX4.1.2の環境があるので、オンプレにあるCisco 891FJ-K9とOSPF接続してみたいと思います。
NSXの環境は、以前の記事で紹介したスタティック接続の環境をOSPFに変更していきます。
hironw.hatenablog.com

ネットワーク構成


Tier-0ゲートウェイでOSPF有効化

Tier-0ゲートウェイのHAモード変更

スタティックルートを利用していた時のHAモードは、アクティブ/スタンバイとし、ルートをアクティブに片寄せしていましたが、OSPF利用時は、両系アクティブで動作させたいので、アクティブ/アクティブに変更します。
[ネットワーク]-[Tier-0ゲートウェイ]から「t0gw01」を編集モードで開きます。

HAモードを「アクティブ/スタンバイ」から「アクティブ/アクティブ」に変更して、保存を選択します。

BGPの無効化

デフォルトだとBGPが有効となっているので、「無効」に変更して、保存を選択します。

OSPFの設定

OSPFを「無効」から「有効」に変更し、エリア定義の「設定」を選択します。

エリア定義の設定で、「エリア定義の追加」を選択します。

エリアIDを「0」で入力し、保存を選択します。

設定が正しく反映されたら、閉じるを選択します。

OSPFのエリア定義は、NSX-Vの環境を構築した際に、エリア定義を「0」として作成しているので、今回はこのエリアに含めることとします。
NSX-Vの時に作成したエリア定義はこちらの記事にあります。
hironw.hatenablog.com

ルート再配分の設定

ルート再配布は、BGPを「無効」、OSPFを「有効」に変更し、ルート再配分の「設定」を選択します。

ルート再配分の設定から、「ルート再配分の追加」を選択します。

以下の内容を入力し、ルート再配分の「設定」を選択します。
名前:redistribute
宛先プロトコル:OSPF

アドバタイズされたTier-1サブネットにある「接続されたインターフェイスおよびサブネット」にチェックを入れ、「適用」を選択します。
もし、Tier-0に接続されたサブネットをOSPFに広報したい場合は、画面上部の「Tier-0サブネット」内にある「接続されたインターフェイスおよびサブネット」にチェックを入れます。

ルート再配布が「1」となったことを確認し、「追加」を選択します。

追加されたことを確認し、「適用」を選択します。

ルート再配布が「1」となったことを確認し、「保存」を選択します。

Tier-1ゲートウェイの設定

ルートアドバタイズの設定

Tier-0ゲートウェイにアドバタイズするルートが「有効」となっていることを確認します。この設定はデフォルト無効なので、スタティックルート、OSPF、BGPなどでルーティングするときには有効にする必要があります。

Cisco 891FJのOSPF設定

すでにNSX-V環境とは「192.168.3.0/24」のセグメントと接続しているので、NSX-Tの環境とは「192.168.13.0/24」で接続するため、以下の設定を追加します。

!
router ospf 10
 passive-interface Vlan100
 network 192.168.3.0 0.0.0.255 area 0
 network 192.168.13.0 0.0.0.255 area 0 ★ココの設定を追加
 network 192.168.100.0 0.0.0.255 area 0
 default-information originate
!

周辺機器のスタティック設定

周辺機器であるC2960-L3、NVR510に「192.168.151.0/24」へのスタティックルートを設定します。
本来は、すべてOSPFで制御したいのですが、各機器がOSPFをサポートしていないためスタティックの設定を行います。

NVR510の設定
ip route 192.168.151.0/24 gateway 192.168.100.254

C2960-L3の設定
ip route 192.168.151.0 255.255.255.0 192.168.13.2

動作確認

Cisco 891FJでルーティングテーブルを確認し、NSX-Tのオーバーレイで作成した「192.168.151.0/24」の情報を、2台のEdgeから受信していることを確認できました。

C891#sh ip route
Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
       ia - IS-IS inter area, * - candidate default, U - per-user static route
       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP
       a - application route
       + - replicated route, % - next hop override, p - overrides from PfR

Gateway of last resort is 192.168.100.1 to network 0.0.0.0

S*    0.0.0.0/0 [1/0] via 192.168.100.1
      192.168.3.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.3.0/24 is directly connected, Vlan3
L        192.168.3.2/32 is directly connected, Vlan3
O     192.168.4.0/24 [110/2] via 192.168.3.254, 00:05:13, Vlan3 ★NSX-VのESG01~DLR01間のルート
      192.168.13.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.13.0/24 is directly connected, Vlan13
L        192.168.13.2/32 is directly connected, Vlan13
      192.168.100.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.100.0/24 is directly connected, Vlan100
L        192.168.100.254/32 is directly connected, Vlan100
O E2  192.168.131.0/24 [110/0] via 192.168.3.254, 00:00:53, Vlan3 ★NSX-Vのオーバーレイ
O E2  192.168.151.0/24 [110/20] via 192.168.13.63, 01:35:08, Vlan13 ★NSX-Tのオーバーレイ
                       [110/20] via 192.168.13.62, 01:33:52, Vlan13 ★NSX-Tのオーバーレイ
C891#

Tier-0ゲートウェイのルーティングテーブルは、「t0gw01」を編集モードにする際の項目内に、「OSPFルーティングテーブルのダウンロード」を選択することで、CSVファイルをダウンロードし確認することができます。

CSVファイルの中身を確認すると、Cisco 891FJにしか接続していない以下2つの経路がOSPFで受信していることが確認できました。
192.168.3.0/24
192.168.100.0/24

最後に、Cisco 891FJからセグメント「192.168.151.0/24」のゲートウェイである192.168.151.1へ疎通できることを確認しました。

C891#ping 192.168.151.1
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 192.168.151.1, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/4 ms
C891#

以上で、NSXでのOSPF Routingの紹介でした。

DataScriptでURIによる負荷分散を行う

今までの負荷分散は一意のFQDNへのアクセスに対して、複数のサーバへ負荷分散を行ってきましたが、ユーザーによりURIが指定されている場合、URIの文字列によって対象のサーバを指定する方法を紹介します。
BIG-IPなどでは、iRuleにより実現していましたが、NSXALBだとDataScriptを使用します。

ネットワーク構成

ネットワーク構成は、TerraformでVSを作成したcent71、cent72のサーバを利用します。
hironw.hatenablog.com

VS、Pool、サーバの接続イメージは以下のようになります。
負荷分散対象のサーバ2台に対して、URIに「/test01/」が含まれている場合は「pool_cent71」へ、URIに「/test02/」が含まれている場合は「pool_cent72」へ転送します。

Poolの作成

まずは、Poolとサーバの組み合わせを以下のような構成でPoolを作成します。
Name:pool_cent71
Default Server Port:80
Server Name:192.168.10.71
IP Address:192.168.10.71
Health Monitors:System-HTTP



同様に、pool_cent72のPoolも作成します。
Name:pool_cent72
Default Server Port:80
Server Name:192.168.10.72
IP Address:192.168.10.72
Health Monitors:System-HTTP

VIPの作成

VIPとして192.168.3.72を作成します。

DataScriptの作成

URIによる負荷分散は、DataScripにより実現するため、スクリプトを作成します。
[Templates]-[Scripts]-[DataScripts]から「CREATE」を選択します。

以下の内容で設定を行います。
Name:Test-Datascript
Pools:pool_cent71、pool_cent72

[Events]-[ADD]から「HTTP Request」を選択し、スクリプトを貼り付けます。

POOL_01                   = "pool_cent71"
POOL_02                   = "pool_cent72"
PATH_01                   = "/test01/"
PATH_02                   = "/test02/"
path                      = avi.http.get_path()
POOL_01_up, POOL_01_total = avi.pool.get_servers(POOL_01)
POOL_02_up, POOL_02_total = avi.pool.get_servers(POOL_02)
UserAgent                 = avi.http.get_header("user-agent")
-- ----------------------------------------------------------
if string.beginswith(path, "index.html") and UserAgent == "avi/1.0" then
   if (POOL_01_up >= 1) or (POOL_02_up >= 1)then
      avi.http.response(200)
   else
      avi.http.response(503)
   end
elseif string.contains(path, PATH_01) then
   avi.vs.log("LOG1: path hit! " .. path)
   avi.pool.select(POOL_01)
elseif string.contains(path, PATH_02) then
   avi.vs.log("LOG2: path hit! " .. path)
   avi.pool.select(POOL_02)
else
   avi.vs.log("LOG3: path not hit!" .. path)
   avi.http.close_conn()
end

DataScriptの処理について説明していきます。
最初の「if string.beginswith(path, "index.html") and UserAgent == "avi/1.0"」は、beginswith(path, "index.html")関数で取得したpathが、「index.html」で始まる場合かつ、UserAgentが「avi/1.0」である場合という条件になり、これは、SEからのヘルスチェックが対象になります。
「 if (POOL_01_up >= 1) or (POOL_02_up >= 1)then」は、2台のサーバのいずれかのPoolがUPしていた場合となり、ヘルスチェックの結果を条件としています。
「avi.http.response(200)」は条件にマッチした場合、Response Code 「200」を返し、「avi.http.response(503)」はどちらのPoolもDOWNしていた場合、Response Code 「503」を返します。

「elseif string.contains(path, PATH_01) then」は、ヘルスチェックでないアクセスが来た場合で、「string.contains(path, PATH_01)」関数で取得したpathに、「/test01/」が含まれていた場合という条件になります。
条件にマッチした場合、「avi.vs.log("LOG1: path hit! " .. path)」で指定した文字列をログに出力し、「avi.pool.select(POOL_01)」で「pool_cent71」に転送します。
同様に、「/test02/」が含まれていた場合は、「pool_cent72」に転送します。

いずれの文字列も含んでいなかった場合は、「avi.http.close_conn()」でコネクションをクローズします。

VSの作成

ここまで作成してきた、Pool、VIP、DataScripを組み合わせ、以下のパラメータでVSを作成します。
VSを新規作成し、以下の内容を入力したら、「NEXT」を選択します。
Name:VS_Datascript_URI
VS VIP:vip_192.168.3.72
Services:80
TCP/UDP Profile:System-TCP-Proxy
Application Profile:System-HTTP

[Polices]-[DataScripts]から「Add DataScript」を選択します。

作成した「Test-Datascript」を選択し「NEXT」を選択します。

後は特に変更はせず、「NEXT」→「SAVE」を選択します。

動作確認

ブラウザから「http://192.168.3.72/test01/start.html」にアクセスするとcent71のページが表示され、「http://192.168.3.72/test02/start.html」にアクセスするとcent72のページが表示され、URIにより対象サーバへ転送していることの確認ができました。

DataScriptによる負荷分散の紹介は以上です。

TerraformでNSXALBのVSを作成してみる

今まで、VMware、Azure、AWSと触ってきましたが、一切自動化というのをしてきていませんでした。
先日、Google Cloudを初めて触った際に同僚からTerraform使うと構築楽だよと言われ触ったところ、構築した環境をコマンド一つで削除できることに感動しました。
業務では、構築がうまくいかない場合、作業を切り戻すこともあり、その手順も調べながら作成していましたが、Terraformは、あらかじめて設定した内容を「apply」で実行し、作業前の状態に戻すのを「destroy」というコマンドだけで実現できます。
これをVMwareの製品でも実現したいと思い、NSXALBのVS作成手順を調べたので紹介します。

ネットワーク構成

ネットワーク構成は、vSphere7で構築したホームラボ環境を利用します。
今回、赤字で記載しているVIPへアクセスすると、2台のLinuxサーバに負荷分散される環境を作っていきます。

Terraformのインストール

ダウンロードと配置

今回、Terraformを実行する端末は、WIndows10になるため、Windows版のTerraformをインストールします。
Terraformの公式サイトから、環境に合わせてダウンロードします。
developer.hashicorp.com
ダウンロードしたファイルを解凍し、実行ファイルを任意の場所に配置します。
今回は、「c:\work\Terraform\alb」配下に「terraform.exe」を配置しました。

c:\work\Terraform\alb>
c:\work\Terraform\alb>dir
2024/01/14  22:45    <DIR>          .
2024/01/14  22:45    <DIR>          ..
2023/12/13  18:11        82,848,432 terraform.exe
               1 個のファイル          82,848,432 バイト
               2 個のディレクトリ  663,619,153,920 バイトの空き領域
c:\work\Terraform\alb>
Pathを通す

Pathを通すために環境変数の設定を行います。
[コントロールパネル]-[システムとセキュリティ]-[システム]-[システムの詳細設定]から「環境変数」をクリックします。

変数の「Path」を選択し、編集をクリックします。

新規から画面のように「Terraform.exe」を配置したフォルダを設定します。

設定後は、全て「OK」をクリックして設定を閉じます。
コマンドプロンプトを起動し、Pathが通っていることを確認します。
正しく設定されていれば、Terraformのバージョンが表示されます。

C:\Users\hippi>terraform -v
Terraform v1.6.6
on windows_amd64
C:\Users\hippi>

tfファイルの作成

設定を投入するための設定ファイル(tfファイル)を作成します。

providerファイルの作成

最初にNSXALBに接続するための情報を「Provider.tf」として作成します。
拡張子を.tfとしたテキストファイルを作成し、以下の情報を貼り付けます。

#NSXALB接続定義
terraform {
  required_providers {
    avi = {
      source  = "vmware/avi"
      version = "22.1.3"
    }
  }
}

#認証情報設定
provider "avi" {
  avi_controller = "avi51.home.local"
  avi_tenant     = "admin"
#ユーザー名を設定
  avi_username   = "xxxxxx"
#パスワードを設定
  avi_password   = "xxxxxx"
  avi_version    = "22.1.3"
}
mainファイルの作成

Pool、VIP、VSの情報を「main.tf」として作成します。
拡張子を.tfとしたテキストファイルを作成し、以下の情報を貼り付けます。

#Tenant情報取得
data "avi_tenant" "admin" {
  name = "admin"
}

#Clouds情報取得
data "avi_cloud" "default_cloud" {
  name       = "Defaul-Cloud"
  tenant_ref = data.avi_tenant.admin.id
}

#vrf情報取得
data "avi_vrfcontext" "vrf01" {
  name       = "vrf01"
  tenant_ref = data.avi_tenant.admin.id
}

#HealthMonitor作成
resource "avi_healthmonitor" "monitor" {
  name              = "custom-HTTP"
  type              = "HEALTH_MONITOR_HTTP"
  send_interval     = "5"
  receive_timeout   = "4"
  successful_checks = "2"
  failed_checks     = "2"
}

#Pool作成
resource "avi_pool" "pool01" {
  name                = "pool_http"
  tenant_ref          = data.avi_tenant.admin.id
  cloud_ref           = data.avi_cloud.default_cloud.id
  vrf_ref             = data.avi_vrfcontext.vrf01.id
  lb_algorithm        = "LB_ALGORITHM_ROUND_ROBIN"
  health_monitor_refs = [avi_healthmonitor.monitor.id]
  servers {
    ip {
      type = "V4"
      addr = "192.168.10.71"
    }
    port = 80
  }
  servers {
    ip {
      type = "V4"
      addr = "192.168.10.72"
    }
    port = 80
  }
}

#VIP作成
resource "avi_vsvip" "vip01" {
  name = "VIP_192.168.3.11"
  vip {
    vip_id = "0"
    ip_address {
      type = "V4"
      addr = "192.168.3.11"
    }
    auto_allocate_ip = false
  }
  tenant_ref      = data.avi_tenant.admin.id
  cloud_ref       = data.avi_cloud.default_cloud.id
  vrf_context_ref = data.avi_vrfcontext.vrf01.id
}

#VS作成
resource "avi_virtualservice" "vs01" {
  name            = "vs_http"
  tenant_ref      = data.avi_tenant.admin.id
  pool_ref        = avi_pool.pool01.id
  vsvip_ref       = avi_vsvip.vip01.id
  cloud_ref       = data.avi_cloud.default_cloud.id
  vrf_context_ref = data.avi_vrfcontext.vrf01.id
  cloud_type      = "CLOUD_NONE"
  services {
    port       = 80
    enable_ssl = false
  }
}

リソースの作成

事前の設定が完了したので、実際にコマンドを実行してリソースを作成していきます。

初期化&プラグインのインストール(teraform init)

まず最初に、作成したtfファイルのあるディレクトリに移動し、「terraform init」を実行します。
これは、Terraform 構成ファイルを含む作業ディレクトリを初期化し、必要なプラグインやモジュールのインストールをしてくれます。

PS C:\work\Terraform\alb>
PS C:\work\Terraform\alb> terraform init

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of vmware/avi from the dependency lock file
- Using previously-installed vmware/avi v22.1.3

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
PS C:\work\Terraform\alb>
PS C:\work\Terraform\alb>
実行計画(terraform plan)

続いて、「terraform plan」を実行します。
これは、リソースを作成する実行計画にあたるもので、どのようなリソースに対してどのような設定を行うかあらかじめ表示してくれます。
もし、コードに問題があれば、どこでエラーが発生しているか教えてくれます。

PS C:\work\Terraform\alb>
PS C:\work\Terraform\alb> terraform plan
data.avi_tenant.admin: Reading...
data.avi_tenant.admin: Read complete after 0s [id=https://avi51.home.local/api/tenant/admin]
data.avi_vrfcontext.vrf01: Reading...
data.avi_cloud.default_cloud: Reading...
data.avi_vrfcontext.vrf01: Read complete after 0s [id=https://avi51.home.local/api/vrfcontext/vrfcontext-bf0c8942-5281-4505-8056-3b73119a0cab]
data.avi_cloud.default_cloud: Read complete after 0s

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # avi_healthmonitor.monitor will be created
  + resource "avi_healthmonitor" "monitor" {
      + allow_duplicate_monitors = (known after apply)
      + description              = (known after apply)
      + disable_quickstart       = (known after apply)
      + failed_checks            = "2"
      + id                       = (known after apply)
      + is_federated             = "false"
      + monitor_port             = (known after apply)
      + name                     = "custom-HTTP"
      + receive_timeout          = "4"
      + send_interval            = "5"
      + successful_checks        = "2"
      + tenant_ref               = (known after apply)
      + type                     = "HEALTH_MONITOR_HTTP"
      + uuid                     = (known after apply)
    }

  # avi_pool.pool01 will be created
  + resource "avi_pool" "pool01" {
      + analytics_profile_ref                 = (known after apply)
      + append_port                           = "NON_DEFAULT_80_443"
      + application_persistence_profile_ref   = (known after apply)
      + autoscale_launch_config_ref           = (known after apply)
      + autoscale_policy_ref                  = (known after apply)
      + capacity_estimation                   = "false"
      + capacity_estimation_ttfb_thresh       = "0"
      + cloud_config_cksum                    = (known after apply)
      + cloud_ref                             = (known after apply)
      + connection_ramp_duration              = "10"
      + created_by                            = (known after apply)
      + default_server_port                   = "80"
      + delete_server_on_dns_refresh          = "true"
      + description                           = (known after apply)
      + east_west                             = (known after apply)
      + enable_http2                          = "false"
      + enabled                               = "true"
      + fewest_tasks_feedback_delay           = "10"
      + graceful_disable_timeout              = "1"
      + gslb_sp_enabled                       = (known after apply)
      + health_monitor_refs                   = (known after apply)
      + host_check_enabled                    = "false"
      + id                                    = (known after apply)
      + ignore_server_port                    = "false"
      + ignore_servers                        = false
      + inline_health_monitor                 = "true"
      + ipaddrgroup_ref                       = (known after apply)
      + lb_algo_rr_per_se                     = "false"
      + lb_algorithm                          = "LB_ALGORITHM_ROUND_ROBIN"
      + lb_algorithm_consistent_hash_hdr      = (known after apply)
      + lb_algorithm_core_nonaffinity         = "2"
      + lb_algorithm_hash                     = "LB_ALGORITHM_CONSISTENT_HASH_SOURCE_IP_ADDRESS"
      + lookup_server_by_name                 = "false"
      + max_concurrent_connections_per_server = "0"
      + min_health_monitors_up                = (known after apply)
      + min_servers_up                        = (known after apply)
      + name                                  = "pool_http"
      + pki_profile_ref                       = (known after apply)
      + pool_type                             = "POOL_TYPE_GENERIC_APP"
      + request_queue_depth                   = "128"
      + request_queue_enabled                 = "false"
      + resolve_pool_by_dns                   = (known after apply)
      + rewrite_host_header_to_server_name    = "false"
      + rewrite_host_header_to_sni            = "false"
      + routing_pool                          = "false"
      + server_disable_type                   = "DISALLOW_NEW_CONNECTION"
      + server_name                           = (known after apply)
      + server_timeout                        = "0"
      + service_metadata                      = (known after apply)
      + sni_enabled                           = "true"
      + ssl_key_and_certificate_ref           = (known after apply)
      + ssl_profile_ref                       = (known after apply)
      + tenant_ref                            = "https://avi51.home.local/api/tenant/admin"
      + tier1_lr                              = (known after apply)
      + use_service_port                      = "false"
      + use_service_ssl_mode                  = "false"
      + uuid                                  = (known after apply)
      + vrf_ref                               = "https://avi51.home.local/api/vrfcontext/vrfcontext-bf0c8942-5281-4505-8056-3b73119a0cab"

      + servers {
          + autoscaling_group_name    = (known after apply)
          + availability_zone         = (known after apply)
          + description               = (known after apply)
          + enabled                   = "true"
          + external_orchestration_id = (known after apply)
          + external_uuid             = (known after apply)
          + hostname                  = (known after apply)
          + mac_address               = (known after apply)
          + nw_ref                    = (known after apply)
          + port                      = "80"
          + preference_order          = (known after apply)
          + prst_hdr_val              = (known after apply)
          + ratio                     = "1"
          + resolve_server_by_dns     = "false"
          + rewrite_host_header       = "false"
          + server_node               = (known after apply)
          + static                    = "false"
          + verify_network            = "false"
          + vm_ref                    = (known after apply)

          + ip {
              + addr = "192.168.10.71"
              + type = "V4"
            }
        }
      + servers {
          + autoscaling_group_name    = (known after apply)
          + availability_zone         = (known after apply)
          + description               = (known after apply)
          + enabled                   = "true"
          + external_orchestration_id = (known after apply)
          + external_uuid             = (known after apply)
          + hostname                  = (known after apply)
          + mac_address               = (known after apply)
          + nw_ref                    = (known after apply)
          + port                      = "80"
          + preference_order          = (known after apply)
          + prst_hdr_val              = (known after apply)
          + ratio                     = "1"
          + resolve_server_by_dns     = "false"
          + rewrite_host_header       = "false"
          + server_node               = (known after apply)
          + static                    = "false"
          + verify_network            = "false"
          + vm_ref                    = (known after apply)

          + ip {
              + addr = "192.168.10.72"
              + type = "V4"
            }
        }
    }

  # avi_virtualservice.vs01 will be created
  + resource "avi_virtualservice" "vs01" {
      + active_standby_se_tag              = "ACTIVE_STANDBY_SE_1"
      + advertise_down_vs                  = "false"
      + allow_invalid_client_cert          = "false"
      + analytics_profile_ref              = (known after apply)
      + application_profile_ref            = (known after apply)
      + azure_availability_set             = (known after apply)
      + bot_policy_ref                     = (known after apply)
      + bulk_sync_kvcache                  = "false"
      + close_client_conn_on_config_update = "false"
      + cloud_config_cksum                 = (known after apply)
      + cloud_ref                          = (known after apply)
      + cloud_type                         = "CLOUD_NONE"
      + created_by                         = (known after apply)
      + delay_fairness                     = "false"
      + description                        = (known after apply)
      + east_west_placement                = "false"
      + enable_autogw                      = "true"
      + enable_rhi                         = (known after apply)
      + enable_rhi_snat                    = (known after apply)
      + enabled                            = "true"
      + error_page_profile_ref             = (known after apply)
      + flow_dist                          = "LOAD_AWARE"
      + flow_label_type                    = "NO_LABEL"
      + fqdn                               = (known after apply)
      + host_name_xlate                    = (known after apply)
      + id                                 = (known after apply)
      + ign_pool_net_reach                 = "false"
      + limit_doser                        = "false"
      + max_cps_per_client                 = "0"
      + microservice_ref                   = (known after apply)
      + min_pools_up                       = (known after apply)
      + name                               = "vs_http"
      + network_profile_ref                = (known after apply)
      + network_security_policy_ref        = (known after apply)
      + pool_group_ref                     = (known after apply)
      + pool_ref                           = (known after apply)
      + remove_listening_port_on_vs_down   = "false"
      + scaleout_ecmp                      = "false"
      + se_group_ref                       = (known after apply)
      + security_policy_ref                = (known after apply)
      + server_network_profile_ref         = (known after apply)
      + service_metadata                   = (known after apply)
      + ssl_profile_ref                    = (known after apply)
      + ssl_sess_cache_avg_size            = "1024"
      + sso_policy_ref                     = (known after apply)
      + tenant_ref                         = "https://avi51.home.local/api/tenant/admin"
      + test_se_datastore_level_1_ref      = (known after apply)
      + traffic_clone_profile_ref          = (known after apply)
      + traffic_enabled                    = "true"
      + type                               = "VS_TYPE_NORMAL"
      + use_bridge_ip_as_vip               = "false"
      + use_vip_as_snat                    = "false"
      + uuid                               = (known after apply)
      + vh_parent_vs_ref                   = (known after apply)
      + vh_type                            = "VS_TYPE_VH_SNI"
      + vrf_context_ref                    = "https://avi51.home.local/api/vrfcontext/vrfcontext-bf0c8942-5281-4505-8056-3b73119a0cab"
      + vsvip_cloud_config_cksum           = (known after apply)
      + vsvip_ref                          = (known after apply)
      + waf_policy_ref                     = (known after apply)
      + weight                             = "1"

      + services {
          + enable_http2                     = "false"
          + enable_ssl                       = "false"
          + horizon_internal_ports           = "false"
          + is_active_ftp_data_port          = "false"
          + override_application_profile_ref = (known after apply)
          + override_network_profile_ref     = (known after apply)
          + port                             = "80"
          + port_range_end                   = "0"
        }
    }

  # avi_vsvip.vip01 will be created
  + resource "avi_vsvip" "vip01" {
      + cloud_ref                = (known after apply)
      + east_west_placement      = "false"
      + id                       = (known after apply)
      + name                     = "VIP_192.168.3.11"
      + tenant_ref               = "https://avi51.home.local/api/tenant/admin"
      + tier1_lr                 = (known after apply)
      + uuid                     = (known after apply)
      + vrf_context_ref          = "https://avi51.home.local/api/vrfcontext/vrfcontext-bf0c8942-5281-4505-8056-3b73119a0cab"
      + vsvip_cloud_config_cksum = (known after apply)

      + vip {
          + auto_allocate_floating_ip = "false"
          + auto_allocate_ip          = "false"
          + auto_allocate_ip_type     = "V4_ONLY"
          + availability_zone         = (known after apply)
          + avi_allocated_fip         = "false"
          + avi_allocated_vip         = "false"
          + enabled                   = "true"
          + floating_subnet6_uuid     = (known after apply)
          + floating_subnet_uuid      = (known after apply)
          + network_ref               = (known after apply)
          + port_uuid                 = (known after apply)
          + prefix_length             = "32"
          + subnet6_uuid              = (known after apply)
          + subnet_uuid               = (known after apply)
          + vip_id                    = "0"

          + ip_address {
              + addr = "192.168.3.11"
              + type = "V4"
            }
        }
    }

Plan: 4 to add, 0 to change, 0 to destroy.

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
PS C:\work\Terraform\alb>
PS C:\work\Terraform\alb> 

実行結果から、「Plan: 4 to add, 0 to change, 0 to destroy.」4つのリソースを追加するという表示が出ており、エラーも内情であることを確認できます。

リソースの作成(terraform apply)

実行計画に問題がないことを確認したら、「terraform apply」を実行してリソースを作成します。
途中で、「Enter a value:」という表示が出たら、「yes」を入力してEnterキーを押下するとリソースが作成されます。

PS C:\work\Terraform\alb> terraform apply
data.avi_tenant.admin: Reading...
data.avi_tenant.admin: Read complete after 1s [id=https://avi51.home.local/api/tenant/admin]
data.avi_vrfcontext.vrf01: Reading...
data.avi_cloud.default_cloud: Reading...
data.avi_cloud.default_cloud: Read complete after 0s
data.avi_vrfcontext.vrf01: Read complete after 0s [id=https://avi51.home.local/api/vrfcontext/vrfcontext-bf0c8942-5281-4505-8056-3b73119a0cab]

~省略~

Plan: 4 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: <strong>yes</strong>

avi_healthmonitor.monitor: Creating...
avi_vsvip.vip01: Creating...
avi_healthmonitor.monitor: Creation complete after 0s [id=https://avi51.home.local/api/healthmonitor/healthmonitor-33542429-cf4d-4d8f-b9f9-22578b353e2d]
avi_pool.pool01: Creating...
avi_vsvip.vip01: Creation complete after 1s [id=https://avi51.home.local/api/vsvip/vsvip-15586354-ed1f-41d4-bb5d-b2d358a858fe]
avi_pool.pool01: Creation complete after 1s [id=https://avi51.home.local/api/pool/pool-55d4b021-fca5-481a-98ef-621e9fd046fc]
avi_virtualservice.vs01: Creating...
avi_virtualservice.vs01: Creation complete after 0s [id=https://avi51.home.local/api/virtualservice/virtualservice-aa1e84d4-ce26-4106-8198-1bb504565419]

Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
PS C:\work\Terraform\alb> 

最終行に「Apply complete!」と表示されていたら作成成功になります。

実行結果の確認

実際にGUIで確認するとリソースが作成されていることが確認できました。

リソースの削除(terraform destroy)

作成したリソースを削除する場合は、「terraform destroy」を実行します。
このコマンドを実行すると「terraform apply」で作成したリソースをすべて削除できます。

PS C:\work\Terraform\alb>
PS C:\work\Terraform\alb> terraform destroy
data.avi_tenant.admin: Reading...
avi_healthmonitor.monitor: Refreshing state... [id=https://avi51.home.local/api/healthmonitor/healthmonitor-33542429-cf4d-4d8f-b9f9-22578b353e2d]
data.avi_tenant.admin: Read complete after 1s [id=https://avi51.home.local/api/tenant/admin]
data.avi_vrfcontext.vrf01: Reading...
data.avi_cloud.default_cloud: Reading...
data.avi_vrfcontext.vrf01: Read complete after 0s [id=https://avi51.home.local/api/vrfcontext/vrfcontext-bf0c8942-5281-4505-8056-3b73119a0cab]
data.avi_cloud.default_cloud: Read complete after 0s
avi_vsvip.vip01: Refreshing state... [id=https://avi51.home.local/api/vsvip/vsvip-15586354-ed1f-41d4-bb5d-b2d358a858fe]
avi_pool.pool01: Refreshing state... [id=https://avi51.home.local/api/pool/pool-55d4b021-fca5-481a-98ef-621e9fd046fc]
avi_virtualservice.vs01: Refreshing state... [id=https://avi51.home.local/api/virtualservice/virtualservice-aa1e84d4-ce26-4106-8198-1bb504565419]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

~省略~

Plan: 0 to add, 0 to change, 4 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

avi_virtualservice.vs01: Destroying... [id=https://avi51.home.local/api/virtualservice/virtualservice-aa1e84d4-ce26-4106-8198-1bb504565419]
avi_virtualservice.vs01: Destruction complete after 0s
avi_vsvip.vip01: Destroying... [id=https://avi51.home.local/api/vsvip/vsvip-15586354-ed1f-41d4-bb5d-b2d358a858fe]
avi_pool.pool01: Destroying... [id=https://avi51.home.local/api/pool/pool-55d4b021-fca5-481a-98ef-621e9fd046fc]
avi_vsvip.vip01: Destruction complete after 0s
avi_pool.pool01: Destruction complete after 0s
avi_healthmonitor.monitor: Destroying... [id=https://avi51.home.local/api/healthmonitor/healthmonitor-33542429-cf4d-4d8f-b9f9-22578b353e2d]
avi_healthmonitor.monitor: Destruction complete after 0s

Destroy complete! Resources: 4 destroyed.
PS C:\work\Terraform\alb> 

コマンドが正常に処理され、リソースが削除されたことが確認できました。

便利なツールやコマンド

tfファイルは、テキストエディタで作成することができますが、コードエディタを使用すると、コードの入力補完やコマンドの実行もできてしまう優れものです。

Visual Studio Code

Microsoft社が無償で提供しているコードエディタです。
私もこれを利用してコードをの作成や実行をしています。
特に「表示」から「ターミナル」を選択すると、画面下部にコマンドプロンプトが表示されるので、コードの作成や修正をしながら、すぐにplan、apply、destroy を実行できるのは便利です。

terraform show

「terraform show」コマンドは、適用済みの全リソースの情報を確認することができます。
コードを作成する際に、パラメーターの名称がわからない時など、基本のパラメータのみTerraformで実行し、その後、GUIで設定してこのコマンドを事項すると、どのパラメータに作成されたか確認することができます。

PS C:\work\Terraform\alb> terraform show
# data.avi_cloud.default_cloud:
data "avi_cloud" "default_cloud" {}

# data.avi_tenant.admin:
data "avi_tenant" "admin" {
    config_settings     = []
    configpb_attributes = []
    id                  = "https://avi51.home.local/api/tenant/admin"
    name                = "admin"
    uuid                = "admin"
}

# data.avi_vrfcontext.vrf01:
data "avi_vrfcontext" "vrf01" {
    bfd_profile              = []
    bgp_profile              = []
    cloud_ref                = "https://avi51.home.local/api/cloud/cloud-660ea483-4e48-41d4-882b-75df0b1da3bb"
    configpb_attributes      = []
    debugvrfcontext          = []
    id                       = "https://avi51.home.local/api/vrfcontext/vrfcontext-bf0c8942-5281-4505-8056-3b73119a0cab"
    internal_gateway_monitor = []
    name                     = "vrf01"
    static_routes            = [
        {
            disable_gateway_monitor = ""
            if_name                 = ""
            labels                  = []
            next_hop                = [
                {
                    addr = "192.168.3.1"
                    type = "V4"
                },
            ]
            prefix                  = [
                {
                    ip_addr = [
                        {
                            addr = "0.0.0.0"
                            type = "V4"
                        },
                    ]
                    mask    = "0"
                },
            ]
            route_id                = "1"
        },
    ]
    tenant_ref               = "https://avi51.home.local/api/tenant/admin"
    uuid                     = "vrfcontext-bf0c8942-5281-4505-8056-3b73119a0cab"
}
terraform state show

「terraform state show」コマンドは、指定したリソースだけの情報を確認することができます。
例えば、VIPの情報だけ取得したい場合は、以下のようにコマンドを実行します。

PS C:\work\Terraform\alb>
PS C:\work\Terraform\alb> terraform state show avi_vsvip.vip01
# avi_vsvip.vip01:
resource "avi_vsvip" "vip01" {
    cloud_ref           = "https://avi51.home.local/api/cloud/cloud-660ea483-4e48-41d4-882b-75df0b1da3bb"
    east_west_placement = "false"
    id                  = "https://avi51.home.local/api/vsvip/vsvip-9ed7e1f9-1f0c-4d48-b04b-a56d53d4da7b"
    name                = "VIP_192.168.3.11"
    tenant_ref          = "https://avi51.home.local/api/tenant/admin"
    uuid                = "vsvip-9ed7e1f9-1f0c-4d48-b04b-a56d53d4da7b"
    vrf_context_ref     = "https://avi51.home.local/api/vrfcontext/vrfcontext-bf0c8942-5281-4505-8056-3b73119a0cab"

    vip {
        auto_allocate_floating_ip = "false"
        auto_allocate_ip          = "false"
        auto_allocate_ip_type     = "V4_ONLY"
        avi_allocated_fip         = "false"
        avi_allocated_vip         = "false"
        enabled                   = "true"
        prefix_length             = "32"
        vip_id                    = "0"

        ip_address {
            addr = "192.168.3.11"
            type = "V4"
        }
    }
}
PS C:\work\Terraform\alb> 
terraform fmt

「terraform fmt」コマンドは、コードの体裁を自動的に整えてくれます。
コマンド実行前後で比較してみましょう。

★コマンド実行前
resource "avi_vsvip" "vip01" {
  name             = "VIP_192.168.3.11"
  vip {
  vip_id = "0"
  ip_address {
  type = "V4"
  addr = "192.168.3.11"
  }
  auto_allocate_ip = false
  }
  tenant_ref      = data.avi_tenant.admin.id
  cloud_ref       = data.avi_cloud.default_cloud.id
  vrf_context_ref = data.avi_vrfcontext.vrf01.id
}

★コマンド実行後
resource "avi_vsvip" "vip01" {
  name = "VIP_192.168.3.11"
  vip {
    vip_id = "0"
    ip_address {
      type = "V4"
      addr = "192.168.3.11"
    }
    auto_allocate_ip = false
  }
  tenant_ref      = data.avi_tenant.admin.id
  cloud_ref       = data.avi_cloud.default_cloud.id
  vrf_context_ref = data.avi_vrfcontext.vrf01.id
}

私もTerraformを初めて1週間ですが、tfファイルの作成さえできてしまえば、コマンドの実行や切り戻しが非常に簡単で、かつコードの内容も理解がしやすいので、もっと早く身につけておけばよかったと思うほど便利です。
今回は、基本的なところの紹介でしたが、いろいろな製品でTerraformを利用していきたいと思います。
それでは、今回はこの辺で。

LDAP連携によりADアカウントでNSXALBへログインをしてみる

NSXALBでVSを作成する際に、複数のシステム管理者から依頼が来ますが、作成後の管理は各システム管理者に任せたいですよね。
ただし、admin権限を渡してしまうと、他のシステムやNSXALB自体の設定変更ができてしまうので、自分が担当しているシステムの設定に留めたいと思います。

そこで、NSXALBでは、「テナント」という機能を利用して、各システムを論理的に分割することが可能です。ここでは、「Red」と「Blue」の2つのテナントを作成してみましょう。
さらに、あらかじめADで、それぞれのグループ用にユーザーを作成しておき、LDAP連携によるログインをしてみたいと思います。

ADでユーザーとグループを作成

ユーザー作成

NSXALBにログインするためのユーザーを2つ作成します。
すでにADは構築済みの前提で進めていきます。
[Active Directoryユーザーとコンピュータ]から、ドメイン名の[homevm.local]内にある、Usersを右クリックし、「新規作成」ー「ユーザー」を選択します。

user01を作成します。

同様に、user02も作成します。

グループ作成

続いてグループを2つ作成します。
[Active Directoryユーザーとコンピュータ]から、ドメイン名の[homevm.local]内にある、Usersを右クリックし、「新規作成」ー「グループ」を選択します。

グループ名に「Red」と入力して、OKを選択します。

同様に、Blueも作成します。

グループにユーザーを登録

作成したユーザーとグループを紐づけます。
以下のグループにそれぞれユーザーを登録します。
Red:user01@homevm.local
Blue:user02@homevm.local


テナントの作成

まず最初にテナントを作成します。
[Administration]-[Accounts]-[Tenats]から「Create」を選択します。

テナント名を「Red」としてSAVEを選択します。


同様に「Blue」のテナントも作成します。


Auth Profileの作成

ここでは、LDAP接続するサーバの指定とUser、GroupのSearch DNの指定を行います。
[Templates]-[Security]-[Auth Profile]から「Create」を選択します。

以下の情報を入力し、SAVEを選択します。
Name:AD Profile
Type:LDAP
LDAP Servers:192.168.1.34(ADサーバ)
Port:389
Base DN:DC=homevm,DC=local(ADのドメインがhomevm.local)
Administrator Bind:有効
Admin Bind DN:administrator@homevm.local
User Search DN:CN=Users,DC=homevm,DC=local
User Search Scope:Scope One Level
User ID Attribute:UserPrincipalName
Group Search DN:DC=homevm,DC=local
Group Search Scope:Scope One Level
Group Filter:objectClass=*
Group member Attribute:member



SAVEが完了し、AD Profileが作成されたことを確認します。

Auth Mapping Profileの作成

続いて、ログインするユーザとテナントの紐づけを行います。
今回は、ADで作成しているグループ名とテナント名を同じにしているので、ここでマッピングしていきます。
[Templates]-[Security]-[Auth Mapping Profile]から「Create」を選択します。

Nameを「AD Mapping Profile」、Typeを「LDAP」とし、Mapping RulesのADDを選択します。

Redルールの作成

Match項目内にあるLDAP Match Groupで「member」を選択し、Groupに「Red」を指定します。
ここで指定するのは、ADで作成したグループ名になります。
Action項目内のCustom Mappingを選択し、ADDから、「User Tenant」を押下します。

User Tenat内にあるTypeは「selected」、Tenatsは「Red」を選択し、Default Tenatは「Red」と入力します。

続いて、再度ADDから、「User Role」を押下します。

User Role内にあるTypeは「selected」、Rolesは「Application-Admin」を選択し、SAVEを押下します。

SAVEが完了し、ルール1が作成されたことを確認します。
このルール1という名前は、分かりづらいですが変更ができません。

Blueルールの作成

同様にBlueのルールを作成していくので、Mapping RulesのADDを選択します。

Match項目内にあるLDAP Match Groupで「member」を選択し、Groupに「Blue」を指定します。
Action項目内のCustom Mappingを選択し、ADDから、「User Tenant」を押下します。

User Tenat内にあるTypeは「selected」、Tenatsは「Blue」を選択し、Default Tenatは「Blue」と入力します。

続いて、再度ADDから、「User Role」を押下します。

User Role内にあるTypeは「selected」、Rolesは「Application-Operator」を選択し、SAVEを押下します。
ここでは、あえて参照権限のRoleを付け、Redテナントに割り当てたRoleと異なる動作をするよう設定します。

SAVEが完了し、ルール2が作成されたことを確認し、SAVEを選択します。

SAVEが完了し、AD Mapping Profileが作成されたことを確認します。

動作確認

ユーザー情報のVerify

設定が完了したので、NSXALBで動作確認してみましょう。
実際にログインしなくても、NSXALBでVerifyすることができます。
[Templates]-[Security]-[Auth Profile]内に作成したAD Profileがありますが、右端に✅マークがあるので、これを押下します。

Test User Entryを選択し、Usernameに「user01@homevm.local」を入力して、Verifyを押下します。
ADに接続して、ユーザーが存在していると、以下のように情報を取得できます。

同様に、「user02@homevm.local」も情報取得できることを確認します。

ログイン確認

NSXALBのログイン画面で、「user01@homevm.local」でログインしてみます。

ログインすると、ユーザーが「user01」、テナントが「Red」、Roleが「Application-Admin」であるため、Administrationの設定項目が制限されていることがわかります。

さらに、「Application-Admin」では、テナント内でApplicationの編集権限があるため、「CREATE Pool」のボタンが表示されていることがわかります。

同様に、「user02@homevm.local」でログインしてみます。

ログインすると、ユーザーが「user02」、テナントが「Blue」、Roleが「Application-Operator」であるため、Administrationの設定項目が制限されていることがわかります。

さらに、「Application-Operator」では、テナント内でApplicationの参照権限しかないため、「CREATE Pool」のボタンが表示されていないことがわかります。

テナントとロールを適切に紐づけることにより、ADで作成した個人ユーザーは編集権限を持たせ、参照用アカウントは共有して利用するようにすることで、いつ誰がどのような操作をしたのか、ユーザー単位で識別できるようになり、かつNSXALBでアカウント管理をしなくて済むので運用としてはスマートになるかと思います。

以上で、LDAP連携によるログイン検証は終了です。

ホームラボ環境再構築 ⑩Segment、Tier0、Tier-1作成

NSXのインストールも完了したので、最後にSegment、Tier0、Tier-1を作成していきます。

ネットワーク構成


アンダーレイ接続のSegment作成

まずはTier-0とアンダーレイを接続する192.168.13.0/24のセグメントを作成します。
[ネットワーク]ー[セグメント]ー[NSX]から「セグメントの追加」を選択します。

以下の情報を入力し、保存を選択します。
名前:vlan-seg13
接続されたゲートウェイ:なし
トランスポートゾーン:tz-vlan01
サブネット:なし
VLAN:0
管理状態:有効

セグメントが正常に作成されたことを確認して「はい」を選択します。

状態が「成功」となっていれば、作成は完了です。

Tier-0ゲートウェイの作成

Tier-0ゲートウェイの作成

続いてTier-0ゲートウェイを作成します。
[ネットワーク]ー[Tier-0ゲートウェイ]ー[ゲートウェイの追加]から「Tier-0」を選択します。

名前に「t0gw01」、HAモードを「アクティブ/アクティブ」、Edgeクラスタを「Cluster62-63」を選び、保存を選択します。

Tier-0ゲートウェイが正常に作成されたことを確認して「はい」を選択します。

インターフェイスの作成

アンダーレイと接続するインターフェイスを作成します。
Tier-0ゲートウェイの編集画面から「インターフェイスGREトンネル」内にある「外部インターフェイスおよびサービスインターフェイス」の設定を選択します。

インターフェイスの設定から「インターフェイスの追加」を選択します。

以下の情報を入力し、保存を選択します。
名前:if-underlay62
タイプ:外部
IPアドレス/マスク:192.168.13.62/24
接続先(セグメント):vlan-seg13
Edgeノード:edge62

作成されたら、もう一つのインターフェイスを作成します。

以下の情報を入力し、保存を選択します。
名前:if-underlay63
タイプ:外部
IPアドレス/マスク:192.168.13.63/24
接続先(セグメント):vlan-seg13
Edgeノード:edge63

作成が完了すると以下のように表示されます。


スタティックルートの設定

続いて、Tier-0からアンダーレイのC2960-L3に向かってスタティックルートを設定します。
Tier-0ゲートウェイの編集画面から「ルーティング」内にある「スタティックルート」の設定を選択します。

スタティックルートの設定から「スタティックルートの追加」を選択します。

名前を「default-route」、ネットワークを「0.0.0.0/0」とし、ネクストホップを選択します。

ネクストホップの設定から「ネクストホップの設定」を選択します。

IPアドレスにC2960-L3のインターフェイスである「192.168.13.1」、管理ディスタンスを「1」とし、追加を選択します。

内容を確認して適用を選択します。

ネクストホップが設定されたことを確認して、保存を選択します。

状態が「成功」であることを確認して、閉じるを選択します。

HA VIPの設定

アンダーレイ接続用インターフェイスを2つ作成しましたが、対抗のC2960-L3からするとネクストホップが2つあり、片方しか選択できないため、VIPを用意します。
Tier-0ゲートウェイの編集画面から「HA VIP 構成」の設定を選択します。

HA VIPの構成から「HA VIP 構成の追加」を選択します。

IPアドレス/マスクに「192.168.13.254/24」、有効を「はい」、インターフェイスで「if-underlay62」と「if-underlay63」の2つを選び、追加を選択します。

作成されたことを確認して、適用を選択します。

HA VIP 構成が作成されたことを確認して、保存を選択後、編集の終了を押下します。

Tier-1ゲートウェイの作成

Tier-1ゲートウェイの作成

続いてTier-1ゲートウェイを作成します。
[ネットワーク]ー[Tier-1ゲートウェイ]ー[TIER-1ゲートウェイの追加]を選択します。

以下の情報を入力し、保存を選択します。
名前:t1gw01
HAモード:アクティブ/スタンバイ
リンクされたTier-0ゲートウェイ:t0gw01
Edgeクラスタ:Cluster62-63
Edgeの自動割り当て:はい
フェイルオーバー:非プリエンプティブ
接続されているすべてのセグメントおよびサービスポート:有効
すべてのIPsecローカルドンドポイント:有効

設定内容を確認して、はいを選択します。


Segmentを作成しTier-1ゲートウェイへ接続

セグメントを作成して、Tier-1ゲートウェイに接続していきます。
[ネットワーク]ー[セグメント]ー[NSX]から「セグメントの追加」を選択します。

以下の情報を入力し、保存を選択します。
名前:seg151
接続されたゲートウェイ:t1gw01 | Tier-1
トランスポートゾーン:tz-overlay01
サブネット:192.168.151.1/24
管理状態:有効

設定内容を確認して、はいを選択します。


C2960-L3のスタティックルート設定

C2960-L3で192.168.151.0/24宛のスタティックルートを追加します。

★追加するスタティックルート
ip route 192.168.151.0 255.255.255.0 192.168.13.254
★追加後のコンフィグ
!
ip route 0.0.0.0 0.0.0.0 192.168.100.1
ip route 192.168.4.0 255.255.255.0 192.168.3.254
ip route 192.168.131.0 255.255.255.0 192.168.3.254
ip route 192.168.151.0 255.255.255.0 192.168.13.254
!

疎通確認

C2960-L3からオーバーレイの192.168.151.1に対してpingが通ることを確認しました。

C2960-L3#
C2960-L3#ping 192.168.151.1
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 192.168.151.1, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/1 ms
C2960-L3#


以上で、vSphere7+NSX-V6.4.14+NSX4.1.2の再構築が完了となります。

途中、原因不明でvCenterやNSX Managerが何度もハングアップするような事象が発生し、再構築を何度も実施しましたが、ある時、サーバーが起動しなくなってしまい、原因を調べたところ、メモリが故障していることが判明しました。
メモリ交換後に、再構築したらすんなりと終わり、ほっと一息といったところです。
ホント、年内に復旧できてよかったです。

来年も引き続き、いろいろな検証をしていきたいと思います。
目標としては、VMC on AWSの環境を利用したHCX、SiteRecovery、NSXALBなどができればなぁと。
それでは、皆さん良いお年を!!