👋
NAT Gateway極力使わず、IPv6でEgress only internet gatewayのみの構成で実現したい
Community Builder 12/13の記事になります
IPv4環境から外部に通信する場合、NAT Gatewayを介して通信するのが一般的かと思われる。NAT Gatewayはトラフィックに応じて課金が発生することから、IPv6も利用できるところは使いたい。
Interface型VPCEndpointも散り積もればバカにならないコストになります
IPv6で扱えるようにIPv4、IPv6 dualstack VPC環境を作成
- IPv6 Amazon Provied
- Egress only internet gateway
- VPCEndpoint (Gateway/Interface)未設定
VPCv2 Constructsで作成
vpc Constructs
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { SubnetType, CfnEIP, CfnSubnet } from 'aws-cdk-lib/aws-ec2';
import * as aws_ec2 from '@aws-cdk/aws-ec2-alpha';
import { IpAddressType } from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import { AvailabilityZoneRebalancing } from 'aws-cdk-lib/aws-ecs';
export class VPCSubnet extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);
const myVpc = new aws_ec2.VpcV2(this, 'Vpc', {
primaryAddressBlock: aws_ec2.IpAddresses.ipv4('10.1.0.0/16'),
vpcName: 'vpc-Develop-Network',
secondaryAddressBlocks: [aws_ec2.IpAddresses.amazonProvidedIpv6({
cidrBlockName: 'AmazonProvided'
})]
});
// Internet Gateway for Public Subnet
const igw = new aws_ec2.InternetGateway(this, 'IGW', {
vpc: myVpc
});
// Egress-Only Internet Gateway for IPv6
const eigw = new aws_ec2.EgressOnlyInternetGateway(this, 'EIGW', {
vpc: myVpc
});
// Public Subnets (Multi-AZ)
const publicRouteTable = new aws_ec2.RouteTable(this, 'PublicRouteTable', {
vpc: myVpc
});
publicRouteTable.addRoute('PublicIGWRoute', '0.0.0.0/0', { gateway: igw });
const publicSubnetAZ1Name = cdk.Fn.join('', ['sub-public-', cdk.Fn.select(0, cdk.Fn.getAzs())]);
const publicSubnetAZ1 = new aws_ec2.SubnetV2(this, 'PublicSubnetAZ1', {
vpc: myVpc,
subnetName: publicSubnetAZ1Name,
availabilityZone: cdk.Fn.select(0, cdk.Fn.getAzs()),
ipv4CidrBlock: new aws_ec2.IpCidr('10.1.0.0/24'),
//ipv6CidrBlock: new aws_ec2.IpCidr(cdk.Fn.select(0, cdk.Fn.cidr(cdk.Fn.select(0, myVpc.ipv6CidrBlocks), 4, '64'))),
subnetType: SubnetType.PUBLIC,
routeTable: publicRouteTable
});
// Explicitly set Name tag
const cfnPublicSubnetAZ1 = publicSubnetAZ1.node.defaultChild as CfnSubnet;
cdk.Tags.of(cfnPublicSubnetAZ1).add('Name', publicSubnetAZ1Name);
const publicSubnetAZ2Name = cdk.Fn.join('', ['sub-public-', cdk.Fn.select(1, cdk.Fn.getAzs())]);
const publicSubnetAZ2 = new aws_ec2.SubnetV2(this, 'PublicSubnetAZ2', {
vpc: myVpc,
subnetName: publicSubnetAZ2Name,
availabilityZone: cdk.Fn.select(1, cdk.Fn.getAzs()),
ipv4CidrBlock: new aws_ec2.IpCidr('10.1.1.0/24'),
//ipv6CidrBlock: new aws_ec2.IpCidr(cdk.Fn.select(1, cdk.Fn.cidr(cdk.Fn.select(0, myVpc.ipv6CidrBlocks), 4, '64'))),
subnetType: SubnetType.PUBLIC,
routeTable: publicRouteTable
});
// Explicitly set Name tag
const cfnPublicSubnetAZ2 = publicSubnetAZ2.node.defaultChild as CfnSubnet;
cdk.Tags.of(cfnPublicSubnetAZ2).add('Name', publicSubnetAZ2Name);
// Elastic IP for NAT Gateway
// const eip = new CfnEIP(this, 'NatEIP', {
// domain: 'vpc'
// });
// NAT Gateway in Public Subnet
// const natGateway = new aws_ec2.NatGateway(this, 'NatGateway', {
// subnet: publicSubnetAZ1,
// allocationId: eip.attrAllocationId
// });
// Private Subnets (Multi-AZ) with NAT Gateway
const privateRouteTable = new aws_ec2.RouteTable(this, 'PrivateRouteTable', {
vpc: myVpc
});
// privateRouteTable.addRoute('PrivateNatRoute', '0.0.0.0/0', { gateway: natGateway });
privateRouteTable.addRoute('PrivateEIGWRoute', '::/0', { gateway: eigw });
const privateSubnetAZ1Name = cdk.Fn.join('', ['sub-private-', cdk.Fn.select(0, cdk.Fn.getAzs())]);
const privateSubnetAZ1 = new aws_ec2.SubnetV2(this, 'PrivateSubnetAZ1', {
vpc: myVpc,
subnetName: privateSubnetAZ1Name,
availabilityZone: cdk.Fn.select(0, cdk.Fn.getAzs()),
ipv4CidrBlock: new aws_ec2.IpCidr('10.1.2.0/24'),
ipv6CidrBlock: new aws_ec2.IpCidr(cdk.Fn.select(2, cdk.Fn.cidr(cdk.Fn.select(0, myVpc.ipv6CidrBlocks), 4, '64'))),
subnetType: SubnetType.PRIVATE_WITH_EGRESS,
routeTable: privateRouteTable
});
// Explicitly set Name tag
const cfnPrivateSubnetAZ1 = privateSubnetAZ1.node.defaultChild as CfnSubnet;
cdk.Tags.of(cfnPrivateSubnetAZ1).add('Name', privateSubnetAZ1Name);
const privateSubnetAZ2Name = cdk.Fn.join('', ['sub-private-', cdk.Fn.select(1, cdk.Fn.getAzs())]);
const privateSubnetAZ2 = new aws_ec2.SubnetV2(this, 'PrivateSubnetAZ2', {
vpc: myVpc,
subnetName: privateSubnetAZ2Name,
availabilityZone: cdk.Fn.select(1, cdk.Fn.getAzs()),
ipv4CidrBlock: new aws_ec2.IpCidr('10.1.3.0/24'),
ipv6CidrBlock: new aws_ec2.IpCidr(cdk.Fn.select(3, cdk.Fn.cidr(cdk.Fn.select(0, myVpc.ipv6CidrBlocks), 4, '64'))),
subnetType: SubnetType.PRIVATE_WITH_EGRESS,
routeTable: privateRouteTable
});
// Explicitly set Name tag
const cfnPrivateSubnetAZ2 = privateSubnetAZ2.node.defaultChild as CfnSubnet;
cdk.Tags.of(cfnPrivateSubnetAZ2).add('Name', privateSubnetAZ2Name);
}
}
LambdaでIPv6疎通確認
Allow IPv6 traffic for dual-stack subnetsを有効にした、VPC Lambdaで確認
ifconfig.meからLambdaからアクセスする送信元IPv6アドレスを取得する簡単なFunctionsになります。
import json
import urllib.request
import json
import urllib.request
def lambda_handler(event, context):
"""
http://ifconfig.me/ にリクエストして、パブリックIPアドレスを取得するLambda関数
"""
try:
# ifconfig.me にリクエストを送信
with urllib.request.urlopen('http://ifconfig.me/', timeout=10) as response:
ip_address = response.read().decode('utf-8').strip()
return {
'statusCode': 200,
'body': json.dumps({
'message': 'Successfully retrieved IP address',
'ip_address': ip_address
})
}
except urllib.error.URLError as e:
# ネットワークエラーの処理
return {
'statusCode': 500,
'body': json.dumps({
'message': 'Error retrieving IP address',
'error': str(e)
})
}
except Exception as e:
# その他のエラー処理
return {
'statusCode': 500,
'body': json.dumps({
'message': 'Unexpected error occurred',
'error': str(e)
})
}
# ローカルテスト用
if __name__ == '__main__':
# テストイベントとコンテキスト
test_event = {}
test_context = {}
result = lambda_handler(test_event, test_context)
print(json.dumps(result, indent=2, ensure_ascii=False))
Response:
{
"statusCode": 200,
"body": "{\"message\": \"Successfully retrieved IP address\", \"ip_address\": \"2600:1f13:94:1a02:ef65:ac4:50ab:5e1\"}"
}
AWSサービス
use_dualstack_endpoint True
S3
my_config = Config(
use_dualstack_endpoint=True,
retries={'max_attempts': 3, 'mode': 'standard'}
)
bucket_name = ''
try:
s3_resource = boto3.resource("s3",config=my_config)
buckets = list(s3_resource.buckets.all())
for bucket in buckets:
bucket_name = bucket.name
logger.info(bucket_name)
except ClientError:
logger.exception("Couldn't get buckets.")
S3 Bucket取得成功
DynamoDB
try:
client = boto3.client('dynamodb',config=my_config)
response = client.get_item(
TableName='testTable',
Key={
'id': {
'S': '1'
},
}
)
print(response['Item'])
S3 Vectors
try:
bedrock = boto3.client("bedrock-runtime");
client = boto3.client('s3vectors',config=my_config)
response = bedrock.invoke_model(
modelId="amazon.titan-embed-text-v2:0",
body=json.dumps({"inputText": 'サンプル'})
)
# Extract embedding from response.
model_response = json.loads(response["body"].read())
embedding = model_response["embedding"]
response = client.query_vectors(
vectorBucketName='my-s3-vector-bucket',
indexName='my-s3-vector-index',
topK=3,
queryVector={
'float32': embedding
},
returnMetadata=True,
returnDistance=True
)
print(json.dumps(response["vectors"], indent=2))
except ClientError:
logger.exception("")
Bedrock Runtime
dualstack endpoint未対応。
ECS
ECS FargateはデフォルトでIPv4を優先するため、IPv6
dual-stack環境でもIPv4で接続を試みています。
ResourceInitializationError: unable to pull secrets or registry auth: The task
cannot pull registry auth from Amazon ECR: There is a connection issue between the
task and Amazon ECR. Check your task network configuration. operation error ECR:
GetAuthorizationToken, exceeded maximum number of attempts, 3, https response error
StatusCode: 0, RequestID: , request send failed, Post
"https://api.ecr.us-west-2.amazonaws.com/": dial tcp 34.223.26.183:443: i/o timeout
EKS Kubernetes環境
ECR dualstack endpointは存在するので、Egress out internet Gateway経由でイメージがプルできる。
最初pullできない事象があった。
KubernetesはIPv4/IPv6 dualstackに対応しているが、EKSはIPv6 only clusterしか設定できない
まとめ
IPv6を使えるサービスが多くなってきる。ただ、まだ使えないサービスもあるので、VPC Endpointと併用していくのが現状と思います
Discussion