異なるVPC上のEC2からAmazon FSx for OpenZFS MultiAZをマウントしてみた
はじめに
みなさんこんにちは、クラウド事業本部コンサルティング部の浅野です。
いきなりですがVPCを跨いでAmazon FSx for OpenZFS にマウントする場合、SingleAZ構成の場合はVPC Peeringを利用して接続できますが、MultiAZ構成の場合はVPC Peeringは使用できず、基本的にTransit Gatewayが必要になります。
これら別VPCからのマルチAZファイルシステムへの接続仕様はFSx for NetApp ONTAPとほぼ同じであり、そちらの仕様は以下のブログが参考になります。
内部のENIやIPがどういう仕組みで動き、なぜTransit Gatewayが基本的に必要なのかはフェイルオーバー時の挙動と密接に関わっています。
今回はそれらを理解しながら、実際にMultiAZ構成のFSx for OpenZFSを作成して別VPC上のEC2からマウントしてみます。
公式の仕様を理解する
マルチAZファイルシステムへのVPC外からのアクセスに関する記述は以下の公式ドキュメントにあります。
長いのでそれぞれの観点から要点だけ箇条書きでまとめます。
別VPCからマルチAZファイルシステムへアクセスする場合はTransit Gatewayが必要
- FSx for OpenZFSのマルチAZファイルシステム上のNFSエンドポイントは「フローティングIPアドレス」を使用している
- 「フローティングIPアドレス」はファイルシステム作成時にエンドポイントIPアドレス範囲オプションで指定した範囲の中から選択される
- またルーティングルールを追加するためのルートテーブルオプションを指定しない場合はデフォルトルートテーブルが選択される
- Transit Gatewayは「推移的ピアリング」とも呼ばれる「フローティングIPアドレス」へのルーティングをサポートしています
- VPCピアリング、Direct Connect、およびSite-to-Site VPNは「推移的ピアリング」をサポートしていないので、マルチAZファイルシステムのVPC外のネットワークからアクセスするには、Transit Gatewayを使用する必要がある
エンドポイントIPアドレス範囲がVPCのCIDR範囲外にある場合はTransit Gatewayルーティングに追加設定が必要
また、別の注意点として、ファイルシステム作成時のエンドポイントIPアドレス範囲がVPCのCIDR範囲外の場合は、Transit Gatewayのルートテーブルに追加の設定が必要なようです。
こちらも上記ドキュメントに記載があるので要点だけまとめます。
- VPC外部からマルチAZファイルシステムにアクセスする場合、ファイルシステムのエンドポイントIPアドレス範囲がファイルシステム内のVPCのCIDR範囲内にあり、VPC内のどのサブネットのCIDR範囲とも重複しない場合のみFSx for OpenZFS はルーティング設定を管理する
- エンドポイントIPアドレス範囲がファイルシステムのVPCのCIDR範囲外にある場合は、Transit Gateway で追加のルーティングを設定する必要がある
- APIのデフォルトではVPCのCIDR範囲内から16個の利用可能なアドレスのCIDRブロックがエンドポイントIPアドレス範囲として選択される
上記記載の通り、デフォルトでマルチAZファイルシステムを作成した場合、エンドポイントIPアドレス範囲はVPCのCIDR範囲内にて選択してくれるので、Transit Gatewayのルートテーブルに明示的な追加設定は必要ありません。
しかし、オンプレミスからの接続やセキュリティの観点からVPC CIDR範囲外を指定せざるを得ない場合は下記ドキュメントに従って追加の設定を行う必要があります。
図解して理解する
公式ドキュメントを読んだだけでは「フローティングIPアドレス」や「推移的ピアリング」が理解しにくいため図解します。
今回実現したいネットワーク構成図はこちらです。

VPC2(CIDR: 10.1.0.0/16)上のEC2からVPC1(CIDR: 10.0.0.0/16)上のFSx for OpenZFS MultiAZシステムにTransit Gatewayを介してマウントします。
マルチAZファイルシステムを作成すると指定したサブネットにそれぞれENI(eni-aaaa & eni-bbbb)が作成されます。
これらの ENIは、AWS管理下で冗長構成を取る 2 台のファイルサーバー(Active / Standby)にそれぞれ接続されており、通常時はActive側が通信を受け持ち、障害発生時にはStandby側へフェイルオーバーします。
フローティングIPとフェイルオーバー時の挙動を理解するために重要なのは、ファイルシステムのENIが作成されるサブネットのルートテーブルです(図の「VPC1 Subnet1 Route Table」&「VPC1 Subnet2 Route Table」)。
フェイルオーバー時に各ルートテーブルの赤枠で囲っている行の「ターゲットENI」が自動で変更されます。
図中の「10.X.X.X/32」と記載しているのがフローティングIPアドレスになります。
赤枠で囲っている記載の意味は 「10.X.X.X/32宛のパケットはeni-*****へ転送する」
となり、通常時は Active 側のファイルサーバーに接続された ENI がターゲットとなり、障害発生時には Standby 側の ENI に切り替わることで、クライアントは同一の IP アドレスを使い続けたまま通信を継続できます。

つまり「フローティングIPアドレス」の「フローティング」とはIPアドレス自体は固定されたまま、フェイルオーバーに応じて通信の転送先となるENIが切り替わることを意味します。
VPC PeeringとTransit Gatewayの違い
公式の仕様からフローティングIPアドレスはどのENIにも直接アタッチされておらず、サブネットのルートテーブルエントリ(10.X.X.X/32 → eni-xxxx)によって間接的にENIへルーティングされていることがわかりました。
この仕組みを踏まえて、VPC PeeringとTransit Gatewayでパケットの流れがどう違うのかをルートテーブルを見ながら比較します。
VPC Peeringの場合
仮にVPC1とVPC2をVPC Peering(pcx-xxxx)で接続した場合、各ルートテーブルは以下のようになります。
VPC2 EC2サブネットのルートテーブル
| 送信先 | ターゲット |
|---|---|
| 10.1.0.0/16 | local |
| 10.0.0.0/16 | pcx-xxxx |
VPC1 Subnet1のルートテーブル(FSxのENIがあるサブネット)
| 送信先 | ターゲット |
|---|---|
| 10.0.0.0/16 | local |
| 10.X.X.X/32 | eni-aaaa |
| 10.1.0.0/16 | pcx-xxxx |
EC2からフローティングIP(10.X.X.X)宛にパケットを送信すると、以下の流れになります。
- EC2がパケット送信(宛先: 10.X.X.X)
- VPC2のルートテーブルで
10.0.0.0/16 → pcx-xxxxにマッチし、VPC Peering経由でVPC1に到着 - VPC1のネットワークは宛先IP(10.X.X.X)を持つENIを探す
- 10.X.X.XはどのENIにも直接アタッチされていないため、配送先が見つからない
- パケットはドロップされる
ポイントは、VPC Peeringではパケットが宛先VPCに到着した後、宛先IPアドレスを直接持つENIに配送するという動作になることです。
VPC1のSubnet1ルートテーブルに 10.X.X.X/32 → eni-aaaa というエントリが存在していますが、このルートテーブルはあくまでSubnet1内のENIから送信されるパケットに対して適用されるものです。VPC Peering経由で外部から到着したパケットに対しては適用されません。
つまり、VPC Peeringは宛先VPC内でルートテーブルを経由した「中間的なルーティング」をサポートしていないため、ルートテーブルでのみENIと紐づいているフローティングIPには到達できないということになります。
Transit Gatewayの場合
次にTransit Gateway(tgw-xxxx)で接続した場合です。
Transit GatewayのアタッチメントはVPC内の指定したサブネットにENIとして作成されます。ここが決定的な違いです。TGW経由のトラフィックはこのアタッチメントENIを通じてVPC1に入り、そのサブネットのルートテーブルが適用されます。
VPC2 EC2サブネットのルートテーブル
| 送信先 | ターゲット |
|---|---|
| 10.1.0.0/16 | local |
| 10.0.0.0/16 | tgw-xxxx |
Transit Gateway ルートテーブル
| 送信先 | ターゲット |
|---|---|
| 10.0.0.0/16 | VPC1 Attachment |
| 10.1.0.0/16 | VPC2 Attachment |
VPC1 Subnet1のルートテーブル(TGWアタッチメント & FSxのENIがあるサブネット)
| 送信先 | ターゲット |
|---|---|
| 10.0.0.0/16 | local |
| 10.X.X.X/32 | eni-aaaa |
| 10.1.0.0/16 | tgw-xxxx |
EC2からフローティングIP(10.X.X.X)宛にパケットを送信すると、以下の流れになります。
- EC2がパケット送信(宛先: 10.X.X.X)
- VPC2のルートテーブルで
10.0.0.0/16 → tgw-xxxxにマッチし、Transit Gatewayへ転送 - TGWルートテーブルで
10.0.0.0/16 → VPC1 Attachmentにマッチし、VPC1のアタッチメントサブネットへ転送 - VPC1のSubnet1にアタッチメントENI経由でパケットが到着
- Subnet1のルートテーブルが適用され
10.X.X.X/32 → eni-aaaaにマッチ - eni-aaaa(Active ファイルサーバー)にパケットが転送される → 通信成功
Transit Gatewayの場合、パケットはVPC1のサブネット内のアタッチメントENIを経由して入るため、そのサブネットのルートテーブル(FSxが管理するフローティングIPのルートエントリを含む)によるルーティングが行われます。
これが公式ドキュメントで「推移的ピアリング」と呼ばれている機能であり、Transit Gatewayという中間ノードのルートテーブルを「経由(推移)」することで、フローティングIPへの間接的なルーティングが機能する仕組みです。
やってみた
ここまでは仕様と理論の理解でしたが、実際に構築してみて各値を確認してみることでイメージを掴んでみます。
上記構成図のTransit Gateway以外のリソースを以下のCDKで用意しました。
- FSx用VPC(10.0.0.0/16、2AZ、Privateサブネット)
- FSx for OpenZFS マルチAZファイルシステム
- FSx用セキュリティグループ
- EC2用VPC(10.1.0.0/16、1AZ、Privateサブネット)
- EC2インスタンス(t3.micro、Amazon Linux 2023)
- EC2用IAMロール(SSM接続用)
- EC2 SSM用VPCエンドポイント
CDKスタック
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as fsx from 'aws-cdk-lib/aws-fsx';
import * as iam from 'aws-cdk-lib/aws-iam';
/**
* FSx for OpenZFS Multi-AZ クロスVPCマウント検証用スタック
*/
export class DemoFsxOpenzfsMultiazCrossVpcStack extends cdk.Stack {
public readonly vpc: ec2.Vpc;
public readonly fileSystem: fsx.CfnFileSystem;
public readonly fsxSg: ec2.SecurityGroup;
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// VPC(FSx用)- 2AZ構成
this.vpc = new ec2.Vpc(this, 'FsxVpc', {
maxAzs: 2,
natGateways: 0,
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
subnetConfiguration: [
{
cidrMask: 24,
name: 'Private',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
],
});
// FSx用セキュリティグループ
this.fsxSg = new ec2.SecurityGroup(this, 'FsxSg', {
vpc: this.vpc,
description: 'Security group for FSx for OpenZFS Multi-AZ',
allowAllOutbound: true,
});
// NFS用ポートを開放(EC2 VPC: 10.1.0.0/16からのアクセスを許可)
this.fsxSg.addIngressRule(
ec2.Peer.ipv4('10.1.0.0/16'),
ec2.Port.tcp(2049),
'NFS from EC2 VPC'
);
this.fsxSg.addIngressRule(
ec2.Peer.ipv4('10.1.0.0/16'),
ec2.Port.tcp(111),
'NFS RPC from EC2 VPC'
);
this.fsxSg.addIngressRule(
ec2.Peer.ipv4('10.1.0.0/16'),
ec2.Port.tcpRange(20001, 20003),
'NFS Mount from EC2 VPC'
);
// 各サブネットのルートテーブルIDを取得
const routeTableIds = this.vpc.isolatedSubnets.map(
(subnet) => subnet.routeTable.routeTableId
);
// FSx for OpenZFS ファイルシステム (Multi-AZ)
this.fileSystem = new fsx.CfnFileSystem(this, 'FileSystem', {
fileSystemType: 'OPENZFS',
subnetIds: this.vpc.isolatedSubnets.map((subnet) => subnet.subnetId),
securityGroupIds: [this.fsxSg.securityGroupId],
storageCapacity: 64,
openZfsConfiguration: {
deploymentType: 'MULTI_AZ_1',
throughputCapacity: 160,
preferredSubnetId: this.vpc.isolatedSubnets[0].subnetId,
// Floating IP用のルートテーブル関連付け
routeTableIds: routeTableIds,
rootVolumeConfiguration: {
nfsExports: [
{
clientConfigurations: [
{
clients: '*',
options: ['rw', 'crossmnt', 'no_root_squash'],
},
],
},
],
},
},
});
// EC2用 VPC
const ec2Vpc = new ec2.Vpc(this, 'Ec2Vpc', {
maxAzs: 1,
natGateways: 0,
ipAddresses: ec2.IpAddresses.cidr('10.1.0.0/16'),
subnetConfiguration: [
{
cidrMask: 24,
name: 'Private',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
],
});
// EC2用セキュリティグループ
const ec2Sg = new ec2.SecurityGroup(this, 'Ec2Sg', {
vpc: ec2Vpc,
description: 'Security group for EC2 instance',
allowAllOutbound: true,
});
// SSM用VPCエンドポイント
ec2Vpc.addInterfaceEndpoint('SsmEndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.SSM,
});
ec2Vpc.addInterfaceEndpoint('SsmMessagesEndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.SSM_MESSAGES,
});
// EC2用IAMロール (SSM用)
const ec2Role = new iam.Role(this, 'Ec2Role', {
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'),
],
});
// EC2インスタンス (t3.micro, Amazon Linux 2023)
const instance = new ec2.Instance(this, 'Instance', {
vpc: ec2Vpc,
vpcSubnets: {
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
machineImage: ec2.MachineImage.latestAmazonLinux2023({
cpuType: ec2.AmazonLinuxCpuType.X86_64,
}),
securityGroup: ec2Sg,
role: ec2Role,
});
}
}
エンドポイントIPアドレス範囲とフローティング(エンドポイント)IPアドレスの確認
上記CDKをデプロイ後、数十分後にFSx for OpenZFS MultiAZファイルシステムが作成されたので、エンドポイントIPアドレス範囲とフローティングIPアドレスを確認してみます。

CDKでは明示的にエンドポイントIPアドレス範囲を指定していないのですが、以下のようにVPC1 CIDR 10.0.0.0/16の中から選択されていることが確認できました。
- エンドポイントIPアドレス範囲:
10.0.255.224/28 - フローティングIPアドレス(エンドポイントIPアドレス):
10.0.255.231
また、フローティングIPアドレスとアクティブ、スタンバイ側のENIが所属するサブネットも確認できました。DNSエンドポイントも後で使用するので確認してコピーしておきましょう。
ルートテーブルの確認
次に理論通りに「フローティングIPアドレスからアクティブ側のENIへのルートエントリ」がSubnetのルートテーブルに作成されているか確認します。

上記のようにFSx側サブネットのルートテーブルとEC2側のルートテーブルが作成されていました。
FSx側サブネットである構成図に記載の「VPC1 Subnet1 Route Table」と「VPC1 Subnet2 Route Table」のエントリを確認します。


それぞれのエントリに「フローティングIPアドレスからアクティブ側のENIへのルートエントリ」が追加されていることが確認できました。
続いてENIも合わせて確認してみます。

各サブネットにFSx for OpenZFSによってENIが2つ作成されていました。
公式ドキュメントの以下の記述の構成を満たしています。
IPアドレス数「3」は各ENIのプライマリIPアドレス + フローティングIPアドレスという意味です。
ファイルシステムの展開タイプ サブネット数 ENI 数 IP アドレス数 マルチ AZ(HA) 2 2 3 シングル AZ(HA) 1 2 3 シングル AZ(非 HA) 1 1 1
Transit Gatewayの作成
現状はVPCを2つ作成しただけで、接続していないのでVPC2上のEC2からFSxのDNS名に名前解決できないことを先に確認しておきます。
EC2にSSM接続して以下のコマンドを実行してみます。
# FSxのDNSエンドポイント名で名前解決を試みます。実際のDNS名に置き換えてください
$ nslookup fs-068ca25566eb9738d.fsx.ap-northeast-1.amazonaws.com
Server: 10.1.0.2
Address: 10.1.0.2#53
** server cant find fs-068ca25566eb9738d.fsx.ap-northeast-1.amazonaws.com: NXDOMAIN
改めてフローティングIPアドレスでマウントを試してみます。
# フローティングIPアドレスに対してマウントを試します
$ sudo mkdir -p /mnt/fsx
$ sudo mount -t nfs 10.0.255.231:/fsx /mnt/fsx
mount.nfs: Connection timed out
当然ですがTransit Gatewayを作成していないのでマウントはできません。
これが接続できるようにするためにまず、Transit Gatewayを作成します。
マネジメントコンソール上の「VPC」> サイドバーの「Transit Gateway」> 「Transit Gatewayを作成」を選択します。名称だけ決めてあとはデフォルトで作成しました。
- 名称:
demo-fsx-openzfs-multiaz-crossvpc-tgw

数分後、状態が「Available」になっていればOK

続いて、構成図のようにTransit Gateway AttachmentをそれぞれのVPCに作成します。
「VPC」> サイドバーの「Transit Gatewayアタッチメント」> 「Transit Gatewayアタッチメントを作成」を選択します。
まずはFSx側のVPC1用に以下の設定で作成します。
- 名称:
demo-fsx-openzfs-multiaz-crossvpc-tgw-attachment - Transit Gateway ID: 上記で作成したTGWのID
- VPC ID: CDKで作成したFSx側のVPC IDを選択
- サブネット ID: CDKで作成したFSx側の各AZのPrivate Subnet2つを選択

次に、EC2側のVPC2用に以下の設定で作成します。
- 名称:
demo-fsx-openzfs-multiaz-crossvpc-tgw-attachment-2 - Transit Gateway ID: 上記で作成したTGWのID
- VPC ID: CDKで作成したEC2側のVPC IDを選択
- サブネット ID: CDKで作成したEC2側のPrivate Subnet1つを選択

以下のように2つのTransit Gateway アタッチメントが作成できました。

アタッチメントが作成されたら、Transit Gatewayのルートテーブルを確認します。
以下のようにデフォルトで各アタッチメントへのルートエントリが追加されていることが確認できました。

また、各サブネットへTransit Gateway Attachment用のENIも追加されていました。

Transit Gatewayへのルートエントリ追加
Transit Gateway & Attachmentが作成できたので各VPC内のサブネットのルートテーブルへTransit Gatewayへの宛先ルートを追加します。
以下の例ではVPC1からVPC2へ接続するためのルート編集例を示しています。「送信先VPC CIDR」と先ほど作成した「Transit Gateway ID」を選択してあげる必要があります。

各ルートテーブルが構成図通り最終的に以下のようになればOKです。
-
FSx側 VPC1 Subnet1 RouteTable

-
FSx側 VPC1 Subnet2 RouteTable

-
EC2側 VPC2 Subnet RouteTable

これにてネットワーク周りの追加設定が全て終了しました。
別VPC上のEC2からマウント確認
これで別VPC上のEC2からFSx for OpenZFSへの名前解決とマウントが可能になっているはずです。
以下のコマンドで試します。
# FSxのフローティングIPアドレスで名前解決を試みる。実際の値に置き換えてください
$ nslookup 10.0.255.231
231.255.0.10.in-addr.arpa name = ip-10-0-255-231.ap-northeast-1.compute.internal.
Authoritative answers can be found from:
# マウント成功
$ sudo mount -t nfs 10.0.255.231:/fsx /mnt/fsx
$ df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 4.0M 0 4.0M 0% /dev
tmpfs 459M 0 459M 0% /dev/shm
tmpfs 184M 416K 183M 1% /run
/dev/nvme0n1p1 8.0G 1.5G 6.5G 19% /
tmpfs 459M 0 459M 0% /tmp
/dev/nvme0n1p128 10M 1.3M 8.7M 13% /boot/efi
tmpfs 92M 0 92M 0% /run/user/0
10.0.255.231:/fsx 64G 0 64G 0% /mnt/fsx
無事マウントに成功しました。
フェイルオーバー時のルートテーブルエントリの変更確認
フェイルオーバー時にルートテーブルエントリのターゲットがアクティブENIからスタンバイENIへ変更されるか確認します。
フェイルオーバーを試すにはファイルシステムのスループット容量を変更するのが手っ取り早いです。
今回は「160MB/s」→「320MB/s」へ変更しました。

ファイルシステムの「更新」タブで進行中が追加されました。

スタンバイサブネットのENIを確認しておきます。

更新から10分ほどしてルートテーブルのエントリが以下のように2つともスタンバイサブネットのENIに切り替わっていることが確認できました。


さらに数十分後フェイルバックされ、元のENIに戻っていることも確認できました。
これにてフローティングIPアドレスの挙動を詳細に確認することができました。
最後に
今回はFSx for OpenZFS MultiAZ構成のファイルシステムに別VPCのEC2からTransit Gateway経由でマウントする検証を行いました。
フローティングIPアドレスの仕組みとVPC Peeringでは到達できない理由を理解しておくと、なぜTransit Gatewayが必要なのかが腑に落ちると思います。
また、別VPCからの接続ではDNS名が使用できずフローティングIPアドレスでの直接指定が必要になる点も見落としがちなので注意が必要です。
この記事がどなたかの参考になれば幸いです。今回は以上です。






