はじめに
AWS PrivateLink(以下 PrivateLink)が進化し、NLB やGateway Load Balancer なしで、VPC リソースへの直接的なプライベートアクセスが可能になったというアップデートが 2024 年の AWS re:Invent 2024 で発表されましたが、その比較を今更ですがしていきましょうという内容です。
主要な違い
項目 | 従来パターン(NLB使用) | 新しいパターン(VPCリソース直接アクセス) |
---|---|---|
必要なコンポーネント | エンドポイントサービス + NLB/GLB | リソースゲートウェイ + リソース設定 |
ロードバランサー | 必須(NLB または GLB) | 不要 |
対象リソース | 負荷分散可能なサービス | 単一リソース(DB、IP、ドメイン等) |
アクセス方法 | インターフェースVPCエンドポイント | リソースVPCエンドポイント |
詳細比較
アーキテクチャ構成
従来パターン(NLB使用)
[Consumer VPC]
↓ (Interface VPC Endpoint)
[AWS PrivateLink]
↓
[Provider VPC]
↓ (Endpoint Service)
[Network Load Balancer]
↓
[Target Group]
↓
[バックエンドサービス/リソース]
新しいパターン(直接アクセス)
[Consumer VPC]
↓ (Resource VPC Endpoint)
[AWS PrivateLink]
↓
[Provider VPC]
↓ (Resource Gateway)
[Resource Configuration]
↓
[VPCリソース(RDS、IP、ドメイン等)]
設定手順
従来パターンの設定手順
プロバイダー側:
- Network Load BalancerまたはGateway Load Balancerの作成
- ターゲットグループの設定
- エンドポイントサービスの作成(ロードバランサーを指定)
- 接続許可の設定
コンシューマー側:
- インターフェースVPCエンドポイントの作成
- サービス名の指定
- セキュリティグループの設定
新しいパターンの設定手順
プロバイダー側:
- リソースゲートウェイの作成
- リソース設定の作成(単一リソースまたはグループ)
- AWS RAMでのリソース共有設定
コンシューマー側:
- リソースVPCエンドポイントの作成
- 共有リソースの指定
- セキュリティグループの設定
適用ユースケース
従来パターン | 新しいパターンが |
---|---|
- 負荷分散が必要なサービス - 高可用性が必要なアプリケーション - 従来のSaaS |
- 単一リソースへの直接アクセス - 負荷分散不要なサービス - リソース粒度での共有が必要 |
技術的特徴
特徴 | 従来パターン | 新しいパターン |
---|---|---|
接続方向 | 単方向 | 単方向 |
プロトコル | TCP/UDP(NLB)、任意(GLB) | TCP/UDP |
DNS解決 | サポート | サポート |
セキュリティ | セキュリティグループ、NACLs | セキュリティグループ、NACLs |
可用性 | NLB/GLBによる冗長化 | リソース自体の冗長化に依存 |
コスト比較
従来パターン | 新しいパターン |
---|---|
- VPCエンドポイント料金(時間単位) - データ処理料金(GB単位) - Network Load Balancer料金(時間単位 + LCU) - データ転送料金 |
- VPCエンドポイント料金(時間単位) - データ処理料金(GB単位) - データ転送料金 |
コストメリット: NLB料金が不要になることで、シンプルなリソースアクセスにおいてコスト削減が期待できます。
可用性とスケーラビリティ
従来パターン | 新しいパターン |
---|---|
- 可用性: NLB/GLBの冗長化機能を活用 - スケーラビリティ: 自動スケーリング、ターゲット追加/削除 |
- 可用性: リソース自体の冗長化設計に依存 - スケーラビリティ: リソース固有のスケーリング方法 |
運用・管理
従来パターン | 新しいパターン |
---|---|
- NLBのヘルスチェック監視 - ターゲットグループの管理 - ロードバランサーメトリクスの監視 |
- リソースゲートウェイの監視 - リソース設定の管理 - AWS RAMを通じた共有管理 |
選択指針
従来パターンを選ぶべき場合 | 新しいパターンを選ぶべき場合 |
---|---|
- 複数のバックエンドサービスへの負荷分散が必要 - 既存のPrivateLinkベースのアーキテクチャを維持したい - 高可用性とスケーラビリティが最重要 |
- データベースや特定リソースへの直接アクセスが必要 - 負荷分散機能が不要 よりシンプルなアーキテクチャとコスト削減を重視 - 細かいリソース単位での共有制御が必要 |
軽く体験してみる
構成は以下のクラスメソッドさんの「試してみた」にあるシングルアカウントの構成で実施します。(複数アカウントは所持していないので、マルチアカウントでのアクセスはごめんなさい)
ポイントは、Resource gateway と Resource Config で後他は基本なのでそれ程難しくなさそうですね。
今回は、AWS CDK で作成しました。なお、あまりコストをかけずやりたかったので、NATGateway などは配置していない点ご了承下さい。
lib/privatelink-vpc-resources-stack.ts
import * as cdk from 'aws-cdk-lib';
import { TcpRetryEvent } from 'aws-cdk-lib/aws-appmesh';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as vpclattice from 'aws-cdk-lib/aws-vpclattice';
import { Construct } from 'constructs';
export class PrivateLinkVpcResourcesStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// ==============================================
// プロバイダー側(リソース提供側)の設定
// ==============================================
// プロバイダーVPCの作成
const providerVpc = new ec2.Vpc(this, 'ProviderVpc', {
vpcName: 'PrivateLink-Provider-VPC',
ipAddresses: ec2.IpAddresses.cidr('10.1.0.0/16'),
maxAzs: 2,
natGateways: 0,
subnetConfiguration: [
{
cidrMask: 24,
name: 'Private',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
}
]
});
// プロバイダー側のセキュリティグループ
const providerSecurityGroup = new ec2.SecurityGroup(this, 'ProviderSecurityGroup', {
vpc: providerVpc,
description: 'Security group for provider resources',
});
// プロバイダー側EC2インスタンス(共有リソース)
const providerInstance = new ec2.Instance(this, 'ProviderInstance', {
vpc: providerVpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO),
machineImage: ec2.MachineImage.latestAmazonLinux2023(),
securityGroup: providerSecurityGroup,
userData: ec2.UserData.custom(`#!/bin/bash
sudo dnf update -y
sudo dnf install -y httpd
sudo systemctl start httpd
sudo systemctl enable httpd
`),
});
// リソースゲートウェイ用のセキュリティグループ
const resourceGatewaySecurityGroup = new ec2.SecurityGroup(this, 'ResourceGatewaySecurityGroup', {
vpc: providerVpc,
description: 'Security group for Resource Gateway',
});
// コンシューマーからのアクセスを許可
resourceGatewaySecurityGroup.addIngressRule(
ec2.Peer.ipv4('10.0.0.0/16'),
ec2.Port.tcp(80),
);
// リソースゲートウェイからのアクセスを許可
providerSecurityGroup.addIngressRule(
resourceGatewaySecurityGroup,
ec2.Port.tcp(80),
);
// リソースゲートウェイの作成
const resourceGateway = new vpclattice.CfnResourceGateway(this, 'ResourceGateway', {
name: 'test-resource-gateway',
vpcIdentifier: providerVpc.vpcId,
subnetIds: providerVpc.isolatedSubnets.map(subnet => subnet.subnetId),
securityGroupIds: [resourceGatewaySecurityGroup.securityGroupId],
ipAddressType: 'IPV4'
});
// リソース設定の作成(EC2インスタンスをリソースとして指定)
const resourceConfiguration = new vpclattice.CfnResourceConfiguration(this, 'ResourceConfiguration', {
name: 'ec2-b-resource-config',
resourceConfigurationType: 'SINGLE',
resourceConfigurationDefinition: {
ipResource: providerInstance.instancePrivateIp
},
resourceGatewayId: resourceGateway.attrId
});
// ==============================================
// コンシューマー側(リソース利用側)の設定
// ==============================================
// コンシューマーVPCの作成
const consumerVpc = new ec2.Vpc(this, 'ConsumerVpc', {
vpcName: 'PrivateLink-Consumer-VPC',
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
maxAzs: 2,
natGateways: 0,
subnetConfiguration: [
{
cidrMask: 24,
name: 'Public',
subnetType: ec2.SubnetType.PUBLIC,
}
]
});
// コンシューマー側のセキュリティグループ
const consumerSecurityGroup = new ec2.SecurityGroup(this, 'ConsumerSecurityGroup', {
vpc: consumerVpc,
description: 'Security group for consumer resources',
});
// SessionManager用IAMロールの作成
const sessionManagerRole = new iam.Role(this, 'SessionManagerRole', {
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore')
]
});
// コンシューマー側EC2インスタンス
const consumerInstance = new ec2.Instance(this, 'ConsumerInstance', {
vpc: consumerVpc,
vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC },
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO),
machineImage: ec2.MachineImage.latestAmazonLinux2023(),
securityGroup: consumerSecurityGroup,
role: sessionManagerRole,
});
// VPCエンドポイント用のセキュリティグループ
const vpcEndpointSecurityGroup = new ec2.SecurityGroup(this, 'VpcEndpointSecurityGroup', {
vpc: consumerVpc,
description: 'Security group for VPC Endpoint to access PrivateLink resources',
});
// コンシューマーインスタンスからのアクセスを許可
vpcEndpointSecurityGroup.addIngressRule(
consumerSecurityGroup,
ec2.Port.tcp(80),
);
// リソースVPCエンドポイントの作成
const resourceVpcEndpoint = new ec2.CfnVPCEndpoint(this, 'ResourceVpcEndpoint', {
vpcId: consumerVpc.vpcId,
vpcEndpointType: 'Resource',
subnetIds: consumerVpc.publicSubnets.map(subnet => subnet.subnetId),
securityGroupIds: [vpcEndpointSecurityGroup.securityGroupId],
resourceConfigurationArn: resourceConfiguration.attrArn,
policyDocument: {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Principal: '*',
Action: [
'vpc-lattice:Invoke'
],
Resource: '*'
}
]
}
});
}
}
AWS マネージメントコンソールから、VPC > エンドポイント > vpce-XXX の VPC エンドポイントを開き、IPv4 アドレスをメモしておき、curl でアクセスできると成功です。