Dual-stack IPv6 networking for Amazon ECS and AWS Fargate
Terminology
Amazon Elastic Container Service (Amazon ECS) is a serverless orchestrator that manages container deployments on your behalf. As an orchestrator it not only launches application containers for you, but also configures various connectivity aspects, including networking, load balancer attachments, and other AWS integrations.
IPv4 is the most widely adopted Internet Protocol. It provides address space for up to 4,294,967,296 devices on the internet, however large portions of the address space are reserved and therefore not usable. As a result, there are not enough IP addresses available to give every device in the world it's own unique IPv4 address. This necessitates the use of more complex networking setups such as Network Address Translation (NAT) gateways that allow multiple devices to share a single public IPv4 address that is used for internet communications.
IPv6 is the most recent Internet Protocol, with approximately 3.4×1038 available addresses. This protocol will enable greatly simplified internet networking. Unfortunately IPv6 rollout is still only partially completed. This means that not every internet user can actually use IPv6 yet.
A dual-stack deployment is a deployment in which your networked cloud resources have both IPv4 addresses and IPv6 addresses. This transitional networking approach allows you to make use of both IPv4 and IPv6 networking.
This pattern will demonstrate how to setup a dual-stack deployment using Amazon ECS, and deploy a sample application that verifies that you can use an IPv6 egress only gateway to make requests to AWS services that have dual-stack endpoints that support IPv6.
Why?
IPv6 rollout is an ongoing project. At this time many internet service providers do not yet support IPv6. Therefore your architecture must still support IPv4 for many of your own users. Additionally, at this time in order to use Amazon ECS and many other AWS services you will still need to make use of IPv4 for some resources. You can find a list of AWS services that support IPv6, in the official AWS documentation.
Despite limited support for IPv6 you will likely want to begin testing IPv6 support for the AWS services that do have IPv6 support. A dual-stack deployment allows you to have the best of both worlds: IPv4 and IPv6.
WARNING
If you are seeking an IPv6 only deployment for Amazon ECS, this is not possible at this time. At this time Amazon ECS still has dependencies on IPv4 only resources, and therefore has only partial support for IPv6. It is not yet possible to completely avoid IPv4 usage. Further updates will be made to this pattern as additional IPv6 support is released.
Architecture
The following diagram depicts what will be created when you deploy this pattern:
- An ECS task is deployed into AWS Fargate capacity, in a VPC subnet that is dual-stack enabled. As a result the task and it's container are reachable via an IPv4 address as well as an IPv6 address.
- Ingress from the internet is via an Application Load Balancer that is configured for dual-stack mode. As a result both IPv4 and IPv6 clients can talk to the dual stack endpoint for the load balancer.
- Due to current Amazon ECS limitations, only the task's IPv4 address is registered into the ALB target group. Therefore, traffic from the ALB to the task is always over IPv4.
- Due to current Amazon ECS and AWS Fargate limitations, several supporting dependencies such as Amazon Elastic Container Registry, Amazon S3 (for container image layers), and Amazon ECS, are accessed over IPv4, via AWS PrivateLink endpoints.
- The application is able to use it's IPv6 support, to make request to the public internet and to dual-stack AWS services via an egress only gateway. As a verification, the application uses the Amazon EC2 dual-stack API endpoint, and the Amazon S3 dual-stack API endpoint.
Dependencies
This pattern requires the following local dependencies:
- Docker or similar OCI compatible image builder.
- Amazon ECR Credential Helper - This credential helper makes it easier to automatically authenticate when building and pushing container images to Amazon ECR.
- AWS SAM CLI for deploying CloudFormation stacks on your AWS account. You should follow the appropriate steps for installing SAM CLI.
Define the Amazon VPC
Download the following ipv6-vpc.yml
file which defines a dual-stack VPC with both IPv4 and IPv6 support:
AWSTemplateFormatVersion: '2010-09-09'
Description: This stack deploys an dual stack VPC designed for IPv6 usage.
Parameters:
DeployingToEC2:
Type: String
Default: false
AllowedValues:
- true
- false
Description: Set value to "true" in order to create additional ECS endpoints
to enable ECS on EC2 usage.
Conditions:
CreateEcsOnEc2Resources: !Equals [ !Ref "DeployingToEC2", true ]
Mappings:
# Hard values for the subnet masks. These masks define
# the range of internal IP addresses that can be assigned.
# The VPC can have all IP's from 10.0.0.0 to 10.0.255.255
# There are four subnets which cover the ranges:
#
# 10.0.128.0 - 10.0.191.255 (16384 IP addresses)
# 10.0.192.0 - 10.0.255.0 (16384 IP addresses)
#
# This template leaves some unutilized IP address space in the following
# ranges in case you need to add more subnets in the future:
#
# 10.0.0.0 - 10.0.63.255 (16384 IP addresses)
# 10.0.64.0 - 10.0.127.255 (16384 IP addresses)
SubnetConfig:
VPC:
CIDR: '10.0.0.0/16'
PublicOneIpv4:
CIDR: '10.0.128.0/18'
PublicTwoIpv4:
CIDR: '10.0.192.0/18'
Resources:
# VPC in which containers will be networked.
VPC:
Type: AWS::EC2::VPC
Properties:
EnableDnsSupport: true
EnableDnsHostnames: true
CidrBlock: !FindInMap ['SubnetConfig', 'VPC', 'CIDR']
# This allocates two blocks of IPv6 capacity from the Amazon owned IP space
CidrBlockOne:
Type: AWS::EC2::VPCCidrBlock
Properties:
AmazonProvidedIpv6CidrBlock: true
VpcId: !Ref VPC
CidrBlockTwo:
Type: AWS::EC2::VPCCidrBlock
Properties:
AmazonProvidedIpv6CidrBlock: true
VpcId: !Ref VPC
# Two dual stack subnets where containers can have both IPv4 and IPv6 addresses
SubnetOne:
Type: AWS::EC2::Subnet
DependsOn: CidrBlockOne
Properties:
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: {Ref: 'AWS::Region'}
VpcId: !Ref VPC
CidrBlock: !FindInMap ['SubnetConfig', 'PublicOneIpv4', 'CIDR']
Ipv6CidrBlock: !Select [0, !GetAtt VPC.Ipv6CidrBlocks]
AssignIpv6AddressOnCreation: true
SubnetTwo:
Type: AWS::EC2::Subnet
DependsOn: CidrBlockTwo
Properties:
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: {Ref: 'AWS::Region'}
VpcId: !Ref VPC
CidrBlock: !FindInMap ['SubnetConfig', 'PublicTwoIpv4', 'CIDR']
Ipv6CidrBlock: !Select [1, !GetAtt VPC.Ipv6CidrBlocks]
AssignIpv6AddressOnCreation: true
# Egress only gateway for IPv6
EgressOnlyGateway:
Type: AWS::EC2::EgressOnlyInternetGateway
Properties:
VpcId: !Ref VPC
# Internet gateway, allows inbound and outbound for IPv4
InternetGateway:
Type: AWS::EC2::InternetGateway
GatewayAttachement:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref 'VPC'
InternetGatewayId: !Ref 'InternetGateway'
# The route table describes how resources in the VPC will be able to reach
# various internet endpoints or address ranges.
RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
IPv6PublicRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref RouteTable
DestinationIpv6CidrBlock: '::/0'
EgressOnlyInternetGatewayId: !Ref EgressOnlyGateway
IPv4PublicRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref RouteTable
DestinationCidrBlock: '0.0.0.0/0'
GatewayId: !Ref InternetGateway
SubnetOneRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SubnetOne
RouteTableId: !Ref RouteTable
SubnetTwoRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SubnetTwo
RouteTableId: !Ref RouteTable
# PrivateLink endpoints that enable access over IPv4, for the dualstack deployment
# Note that we share one security group for all of the PrivateLink endpoints.
# This is in order to more easily grant ECS managed infrastructure permissions
# to utilize all of the endpoints.
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Shared security group.
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-shared
SecurityGroupAccessRule:
Type: AWS::EC2::SecurityGroupIngress
Properties:
IpProtocol: -1
SourceSecurityGroupId: !Ref SecurityGroup
GroupId: !Ref SecurityGroup
# The PrivateLink endpoints that provide access to required AWS services
S3Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcEndpointType: Gateway
RouteTableIds:
- !Ref RouteTable
ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
VpcId: !Ref VPC
CloudWatchLogsEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref SubnetOne
- !Ref SubnetTwo
SecurityGroupIds:
- !Ref SecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.logs
VpcId: !Ref VPC
SsmEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref SubnetOne
- !Ref SubnetTwo
SecurityGroupIds:
- !Ref SecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
VpcId: !Ref VPC
SsmMessagesEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref SubnetOne
- !Ref SubnetTwo
SecurityGroupIds:
- !Ref SecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
VpcId: !Ref VPC
EcrApiEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref SubnetOne
- !Ref SubnetTwo
SecurityGroupIds:
- !Ref SecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ecr.api
VpcId: !Ref VPC
EcrDkrEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref SubnetOne
- !Ref SubnetTwo
SecurityGroupIds:
- !Ref SecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ecr.dkr
VpcId: !Ref VPC
SecretsManagerEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref SubnetOne
- !Ref SubnetTwo
SecurityGroupIds:
- !Ref SecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.secretsmanager
VpcId: !Ref VPC
# The following endpoints with the Condition: CreateEcsOnEc2Resources
# are not necessary for ECS on AWS Fargate, but are needed for
# ECS on EC2
EcsAgentEndpoint:
Type: AWS::EC2::VPCEndpoint
Condition: CreateEcsOnEc2Resources
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref SubnetOne
- !Ref SubnetTwo
SecurityGroupIds:
- !Ref SecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ecs-agent
VpcId: !Ref VPC
EcsTelemetryEndpoint:
Type: AWS::EC2::VPCEndpoint
Condition: CreateEcsOnEc2Resources
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref SubnetOne
- !Ref SubnetTwo
SecurityGroupIds:
- !Ref SecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ecs-telemetry
VpcId: !Ref VPC
EcsEndpoint:
Type: AWS::EC2::VPCEndpoint
Condition: CreateEcsOnEc2Resources
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref SubnetOne
- !Ref SubnetTwo
SecurityGroupIds:
- !Ref SecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ecs
VpcId: !Ref VPC
Outputs:
VpcId:
Description: The ID of the VPC that this stack is deployed in
Value: !Ref VPC
SubnetIds:
Description: Comma seperated list of subnets with IPv6 egress gateway based internet access
Value: !Sub '${SubnetOne},${SubnetTwo}'
PrivateLinkEndpointSecurityGroup:
Description: The shared security group for all of the PrivateLink
endpoints. The ECS services and/or EC2 instances that host
those services must have permission to talk to this security group
Value: !Ref SecurityGroup
Things to note in this template:
AWS::EC2::VPCCidrBlock
- We request two blocks of Amazon provided IPv6 address spaceAWS::EC2::Subnet
- VPC subnets are configured to use the IPv6 blocks, and to assign IPv6 addresses to network interfaces in the subnet.AWS::EC2::InternetGateway
- The internet gateway provides both inbound and outbound access for IPv4 based internet communicationsAWS::EC2::EgressOnlyInternetGateway
- The egress only gateway serves a similar role compared to a NAT Gateway in a traditional IPv4 deployment. It allows resources in a private VPC subnet to communicate to the internet over IPv6.IPv6PublicRoute
- This route sends IPv6 traffic out through the egress only internet gateway.IPv4PublicRoute
- This route allows resources that have a public IPv4 address to communicate with the internet via the internet gateway.AWS::EC2::VPCEndpoint
- Because we intend to avoid using public IPv4 addresses as much as possible, these VPC endpoints grant workloads hosted in the VPC the ability to communicate with AWS services privately.
Define the ECS Cluster
Download the following cluster.yml
file which defines the Amazon ECS cluster that will run container tasks.
AWSTemplateFormatVersion: '2010-09-09'
Description: Empty ECS cluster that has no EC2 instances. It is designed
to be used with AWS Fargate serverless capacity
Resources:
# Cluster that keeps track of container deployments
ECSCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterSettings:
- Name: containerInsights
Value: enabled
# This is a role which is used within Fargate to allow the Fargate agent
# to download images, and upload logs.
ECSTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [ecs-tasks.amazonaws.com]
Action: ['sts:AssumeRole']
Condition:
ArnLike:
aws:SourceArn: !Sub arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:*
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
Path: /
# This role enables basic features of ECS. See reference:
# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/security-iam-awsmanpol.html#security-iam-awsmanpol-AmazonECSTaskExecutionRolePolicy
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
Outputs:
ClusterName:
Description: The ECS cluster into which to launch resources
Value: !Ref ECSCluster
ECSTaskExecutionRole:
Description: The role used to start up a task
Value: !Ref ECSTaskExecutionRole
Define the ECS Service
Download the following service.yml
file which defines a container deployment in AWS Fargate, orchestrated by Amazon ECS:
AWSTemplateFormatVersion: '2010-09-09'
Description: An example service that deploys in AWS VPC networking mode
on AWS Fargate. Service runs with networking in public
subnets and public IP addresses
Parameters:
VpcId:
Type: String
Description: The VPC that the service is running inside of
SubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: List of public subnet ID's to put the load balancer and tasks in
ClusterName:
Type: String
Description: The name of the ECS cluster into which to launch capacity.
ECSTaskExecutionRole:
Type: String
Description: The role used to start up an ECS task
ServiceName:
Type: String
Default: web
Description: A name for the service
ImageUri:
Type: String
Description: The url of a container image that contains the application process
ContainerCpu:
Type: Number
Default: 256
Description: How much CPU to give the container. 1024 is 1 CPU
ContainerMemory:
Type: Number
Default: 512
Description: How much memory in megabytes to give the container
ContainerPort:
Type: Number
Default: 3000
Description: What port that the application expects traffic on
DesiredCount:
Type: Number
Default: 1
Description: How many copies of the service task to run
PrivateLinkEndpointSecurityGroup:
Type: String
Description: The security group on the PrivateLink endpoints. It must accept traffic from the service's SG.
Resources:
# This a role used by the application itself
ApplicationRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [ecs-tasks.amazonaws.com]
Action: ['sts:AssumeRole']
Condition:
ArnLike:
aws:SourceArn: !Sub arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:*
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
Path: /
ManagedPolicyArns:
# This managed role allows the application to read Amazon S3
# See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/security-iam-awsmanpol.html#security-iam-awsmanpol-amazons3readonlyaccess
- arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
# This managed role allows the application to read Amazon EC2
# See: https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonEC2ReadOnlyAccess.html
- arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess
# The task definition. This is a simple metadata description of what
# container to run, and what resource requirements it has.
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Ref ServiceName
Cpu: !Ref ContainerCpu
Memory: !Ref ContainerMemory
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
TaskRoleArn: !Ref ApplicationRole
ExecutionRoleArn: !Ref ECSTaskExecutionRole
ContainerDefinitions:
- Name: !Ref ServiceName
Cpu: !Ref ContainerCpu
Memory: !Ref ContainerMemory
Image: !Ref ImageUri
PortMappings:
- ContainerPort: !Ref ContainerPort
HostPort: !Ref ContainerPort
Environment:
- Name: PORT
Value: !Ref ContainerPort
LogConfiguration:
LogDriver: 'awslogs'
Options:
mode: non-blocking
max-buffer-size: 25m
awslogs-group: !Ref LogGroup
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: !Ref ServiceName
# The service. The service is a resource which allows you to run multiple
# copies of a type of task, and gather up their logs and metrics, as well
# as monitor the number of running tasks and replace any that have crashed
Service:
Type: AWS::ECS::Service
# Avoid race condition between ECS service creation and associating
# the target group with the LB
DependsOn: PublicLoadBalancerListener
Properties:
ServiceName: !Ref ServiceName
Cluster: !Ref ClusterName
LaunchType: FARGATE
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: DISABLED
SecurityGroups:
- !Ref ServiceSecurityGroup
Subnets: !Ref SubnetIds
DeploymentConfiguration:
MaximumPercent: 200
MinimumHealthyPercent: 75
DesiredCount: !Ref DesiredCount
TaskDefinition: !Ref TaskDefinition
LoadBalancers:
- ContainerName: !Ref ServiceName
ContainerPort: !Ref ContainerPort
TargetGroupArn: !Ref ServiceTargetGroup
# Security group that limits network access
# to the task
ServiceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for service
VpcId: !Ref VpcId
# Keeps track of the list of tasks for the service
ServiceTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: 6
HealthCheckPath: /
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
TargetType: ip
Port: !Ref ContainerPort
Protocol: HTTP
UnhealthyThresholdCount: 10
VpcId: !Ref VpcId
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: 0
# A public facing load balancer, this is used as ingress for
# public facing internet traffic.
PublicLoadBalancerSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Access to the public facing load balancer
VpcId: !Ref VpcId
SecurityGroupIngress:
# Allow access to public facing ALB from any IP address
- CidrIp: 0.0.0.0/0
IpProtocol: -1
PublicLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Scheme: internet-facing
IpAddressType: dualstack
LoadBalancerAttributes:
- Key: idle_timeout.timeout_seconds
Value: '30'
Subnets: !Ref SubnetIds
SecurityGroups:
- !Ref PublicLoadBalancerSG
PublicLoadBalancerListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: 'forward'
ForwardConfig:
TargetGroups:
- TargetGroupArn: !Ref ServiceTargetGroup
Weight: 100
LoadBalancerArn: !Ref 'PublicLoadBalancer'
Port: 80
Protocol: HTTP
# Open up the service's security group to traffic originating
# from the security group of the load balancer.
ServiceIngressfromLoadBalancer:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: Ingress from the public ALB
GroupId: !Ref ServiceSecurityGroup
IpProtocol: -1
SourceSecurityGroupId: !Ref 'PublicLoadBalancerSG'
# Open up the PrivateLink endpoints to accepting inbound traffic
# from the service deploying in AWS Fargate.
PrivateLinkIngressFromService:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: Ingress from the services deployed in AWS Fargate
GroupId: !Ref PrivateLinkEndpointSecurityGroup
IpProtocol: -1
SourceSecurityGroupId: !Ref ServiceSecurityGroup
# This log group stores the stdout logs from this service's containers
LogGroup:
Type: AWS::Logs::LogGroup
Outputs:
PublicURI:
Description: The public URI of the service on the internet
Value: !GetAtt PublicLoadBalancer.DNSName
A few things to note in this template:
- The
AWS::ECS::Service
has the settingAssignPublicIp: DISABLED
. This disables the ability for the task to communicate directly via the internet gateway. All communications from this service will be over IPv6, or via an AWS PrivateLink endpoint. PrivateLinkIngressFromService
- This security group ingress rule is what allows the deployed container service to make use of the PrivateLink endpoints.
Build and push the test image
In order to test dual-stack, this pattern provides a small test application that uses the AWS SDK to make API calls to two different dual-stack service endpoints: S3 and EC2.
- app/index.js
- app/package.json
- app/Dockerfile
import { S3Client, ListBucketsCommand } from "@aws-sdk/client-s3";
import { EC2Client, DescribeInstancesCommand } from "@aws-sdk/client-ec2";
import express from 'express';
const app = express()
const PORT = process.env.PORT || 3000;
// Use the dual stack endpoint for S3 to verify we can make API calls over IPv6
const s3 = new S3Client({
useDualstackEndpoint: true
});
// Use the dual stack endpoint for EC2 to verify IPv6 egress gateway is working
const ec2 = new EC2Client({
useDualstackEndpoint: true
});
app.get('/list-buckets', async function (req, res) {
const command = new ListBucketsCommand({});
const response = await s3.send(command);
res.send(response.Buckets)
})
app.get('/list-ec2', async function (req, res) {
const command = new DescribeInstancesCommand({});
const response = await ec2.send(command);
res.send(response)
})
app.get('/', async function (req, res) {
res.send('OK')
})
app.listen(PORT)
console.log(`Listening on http://localhost:${PORT}`);
TIP
The sample application toggles useDualstackEndpoint: true
when defining the AWS service client. This is what enables you to use IPv6 AWS endpoints from inside of a dual-stack VPC.
Download all three files and place them into a folder named app
. The folder structure should look like this:
app
- Folder containing the sample applicationapp/index.js
- The sample application codeapp/package.json
- Defines dependencies for the sample applicationapp/Dockerfile
- Defines how to build the sample application
Now build and push the sample application to a private Amazon ECR container registry using the following command:
REPO_URI=$(aws ecr create-repository --repository-name sample-app-repo --query 'repository.repositoryUri' --output text)
if [ -z "${REPO_URI}" ]; then
REPO_URI=$(aws ecr describe-repositories --repository-names sample-app-repo --query 'repositories[0].repositoryUri' --output text)
fi
docker build -t ${REPO_URI}:ipv6-app ./app
docker push ${REPO_URI}:ipv6-app
Deploy everything
Now download the following parent.yml
file that deploy the previous three templates:
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Parent stack that deploys VPC, Amazon ECS cluster for AWS Fargate,
and a serverless Amazon ECS service deployment that hosts
the task containers on AWS Fargate
Parameters:
ImageUri:
Type: String
Description: The URI of the private container image to deploy
Resources:
# The networking configuration. This creates an isolated
# network specific to this particular environment
VpcStack:
Type: AWS::Serverless::Application
Properties:
Location: ipv6-vpc.yml
# This stack contains the Amazon ECS cluster itself
ClusterStack:
Type: AWS::Serverless::Application
Properties:
Location: cluster.yml
# This stack contains the container deployment
ServiceStack:
Type: AWS::Serverless::Application
Properties:
Location: service.yml
Parameters:
ImageUri: !Ref ImageUri
VpcId: !GetAtt VpcStack.Outputs.VpcId
SubnetIds: !GetAtt VpcStack.Outputs.SubnetIds
ClusterName: !GetAtt ClusterStack.Outputs.ClusterName
ECSTaskExecutionRole: !GetAtt ClusterStack.Outputs.ECSTaskExecutionRole
PrivateLinkEndpointSecurityGroup: !GetAtt VpcStack.Outputs.PrivateLinkEndpointSecurityGroup
Outputs:
PublicURI:
Description: Public URI of the service on the internet
Value: !GetAtt ServiceStack.Outputs.PublicURI
Your overall folder structure should look like:
parent.yml
- Top level index file that defines the overall application deploymentipv6-vpc.yml
- Defines the dual-stack networking configurationcluster.yml
- Standard boilerplate for an Amazon ECS clusterservice.yml
- The container deployment itselfapp
- Folder that holds the sample application codeapp/index.js
- The sample application codeapp/package.json
- Defines dependencies for the sample applicationapp/Dockerfile
- Defines how to build the sample application
Now you can use the following command to deploy the application:
sam deploy \
--template-file parent.yml \
--stack-name ipv6-environment \
--capabilities CAPABILITY_IAM \
--parameter-overrides ImageUri=${REPO_URI}:ipv6-app \
--resolve-s3
Test it Out
First let's make sure that the load balancer has a dual-stack endpoint that supports both IPv4 and IPv6:
PUBLIC_URI=$(aws cloudformation describe-stacks --stack-name ipv6-environment --query "Stacks[0].Outputs[?OutputKey=='PublicURI'].OutputValue" --output text)
# Lookup the IPv4 address of the service
dig A $PUBLIC_URI
# Lookup the IPv6 address of the service
dig AAAA $PUBLIC_URI
Now test two different endpoints in the service. Both endpoints internally make use of IPv6 via the egress only gateway:
# Verify that the application is able to communiate to the S3 dual-stack endpoint
curl $PUBLIC_URI/list-buckets
# Verify that the application is able to communciate to the EC2 dual-stack endpoint
curl $PUBLIC_URI/list-ec2
Tear it Down
You can destroy your test deployment using the following commands:
# Delete the Amazon ECS deployment
sam delete --stack-name ipv6-environment --no-prompts
# Empty and delete the Amazon ECR container registry we created
aws ecr delete-repository --repository-name sample-app-repo --force