ECS Service Connectで別VPCのECSサービスに接続してみた
以前、ECS Service Connectで異なるECSクラスターのECSサービスとの接続を試してみました。(同一VPC)
ECS Service Connectで別クラスターのECSサービスに接続できるか試してみた | DevelopersIO
今回は、別VPCのECSサービスへ接続できるか試してみました。
結論
可能です。
別ECSクラスターのときと同様、同一のCloudMap名前空間を利用すれば接続できます。
VPC間ではVPCピアリング等で疎通を確保しておく必要があります。
例えば、2 つのクラスターは異なる VPC でタスクを作成します。各クラスターのサービスは、同じ名前空間を使用するように設定されています。これら 2 つのサービスのアプリケーションは、VPC DNS 設定なしで名前空間のすべてのエンドポイントを解決できます。ただし、VPC ピアリング、VPC またはサブネットのルートテーブル、そして VPC ネットワーク ACL により、containerPort と ingressPortOverride のポート番号のトラフィックが許可されない限り、プロキシは接続できません。
Amazon ECS Service Connect コンポーネント - Amazon Elastic Container Serviceから引用
やってみた
実際に以下の構成をCDKで作成して、疎通を確認しました。
CDKでリソースを作成する
以下のコードを用意します。
ServerとClient用のECSサービスをそれぞれ別VPCにデプロイします。
ServerはNginxコンテナを起動します。
Client側では、NginxコンテナにService Connect経由で疎通確認を行う処理を入れています。
VPC間はVPCピアリングで疎通を確保しておきます。
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as servicediscovery from 'aws-cdk-lib/aws-servicediscovery';
import * as logs from 'aws-cdk-lib/aws-logs';
import { Construct } from 'constructs';
export class CrossVpcServiceConnectStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Server用VPC作成
const serverVpc = new ec2.Vpc(this, 'ServerVpc', {
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
maxAzs: 2,
natGateways: 1,
subnetConfiguration: [
{
cidrMask: 24,
name: 'ServerPublic',
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'ServerPrivate',
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
],
});
// Client用VPC作成
const clientVpc = new ec2.Vpc(this, 'ClientVpc', {
ipAddresses: ec2.IpAddresses.cidr('10.1.0.0/16'),
maxAzs: 2,
natGateways: 1,
subnetConfiguration: [
{
cidrMask: 24,
name: 'ClientPublic',
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'ClientPrivate',
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
],
});
// VPC Peering接続
const vpcPeering = new ec2.CfnVPCPeeringConnection(this, 'VpcPeering', {
vpcId: serverVpc.vpcId,
peerVpcId: clientVpc.vpcId,
});
// Server VPCのルートテーブルにClient VPCへのルートを追加
serverVpc.privateSubnets.forEach((subnet, index) => {
new ec2.CfnRoute(this, `ServerToClientRoute${index}`, {
routeTableId: subnet.routeTable.routeTableId,
destinationCidrBlock: clientVpc.vpcCidrBlock,
vpcPeeringConnectionId: vpcPeering.ref,
});
});
// Client VPCのルートテーブルにServer VPCへのルートを追加
clientVpc.privateSubnets.forEach((subnet, index) => {
new ec2.CfnRoute(this, `ClientToServerRoute${index}`, {
routeTableId: subnet.routeTable.routeTableId,
destinationCidrBlock: serverVpc.vpcCidrBlock,
vpcPeeringConnectionId: vpcPeering.ref,
});
});
// Service Connect用のCloud Mapネームスペース作成
const namespace = new servicediscovery.HttpNamespace(this, 'ServiceConnectNamespace', {
name: 'cross-vpc-service-connect'
});
// Server用のECSクラスター
const serverCluster = new ecs.Cluster(this, 'ServerCluster', {
vpc: serverVpc,
clusterName: 'cross-vpc-server',
});
// Client用のECSクラスター
const clientCluster = new ecs.Cluster(this, 'ClientCluster', {
vpc: clientVpc,
clusterName: 'cross-vpc-client',
});
// サーバータスク定義
const serverTaskDefinition = new ecs.FargateTaskDefinition(this, 'ServerTaskDef', {
memoryLimitMiB: 512,
cpu: 256,
});
const serverContainer = serverTaskDefinition.addContainer('ServerContainer', {
image: ecs.ContainerImage.fromRegistry('nginx:latest'),
logging: ecs.LogDrivers.awsLogs({
streamPrefix: 'cross-vpc-server',
logGroup: new logs.LogGroup(this, 'VpcServerLogGroup', {
logGroupName: '/aws/ecs/cross-vpc-server-service-connect',
retention: logs.RetentionDays.ONE_WEEK,
removalPolicy: cdk.RemovalPolicy.DESTROY,
}),
}),
});
serverContainer.addPortMappings({
containerPort: 80,
protocol: ecs.Protocol.TCP,
name: 'web',
});
// クライアントタスク定義
const clientTaskDefinition = new ecs.FargateTaskDefinition(this, 'ClientTaskDef', {
memoryLimitMiB: 512,
cpu: 256,
});
clientTaskDefinition.addContainer('ClientContainer', {
image: ecs.ContainerImage.fromRegistry('curlimages/curl:latest'),
command: ['sh', '-c', 'while true; do curl -s http://server.cross-vpc-service-connect:80 && sleep 30; done'],
logging: ecs.LogDrivers.awsLogs({
streamPrefix: 'cross-vpc-client',
logGroup: new logs.LogGroup(this, 'ClientLogGroup', {
logGroupName: '/aws/ecs/cross-vpc-client-service-connect',
retention: logs.RetentionDays.ONE_WEEK,
removalPolicy: cdk.RemovalPolicy.DESTROY,
}),
}),
});
// Server用のSecurity Group
const serverSecurityGroup = new ec2.SecurityGroup(this, 'ServerSecurityGroup', {
vpc: serverVpc,
description: 'Security group for Cross VPC Server service',
allowAllOutbound: true,
});
// Client用のSecurity Group
const clientSecurityGroup = new ec2.SecurityGroup(this, 'ClientSecurityGroup', {
vpc: clientVpc,
description: 'Security group for Cross VPC Client service',
allowAllOutbound: true,
});
// Server Security GroupでClient VPCからのアクセスを許可
serverSecurityGroup.addIngressRule(
ec2.Peer.ipv4(clientVpc.vpcCidrBlock),
ec2.Port.tcp(80),
'Allow HTTP access from Client VPC'
);
// サーバーサービス(Service Connect有効)
const serverService = new ecs.FargateService(this, 'ServerService', {
cluster: serverCluster,
serviceName: 'cross-vpc-server',
taskDefinition: serverTaskDefinition,
desiredCount: 1,
enableExecuteCommand: true,
securityGroups: [serverSecurityGroup],
serviceConnectConfiguration: {
namespace: namespace.namespaceArn,
services: [
{
portMappingName: 'web',
discoveryName: 'server',
dnsName: 'server',
port: 80,
},
],
},
});
// クライアントサービス(Service Connect有効、クライアントとして設定)
const clientService = new ecs.FargateService(this, 'ClientService', {
cluster: clientCluster,
serviceName: 'cross-vpc-client',
taskDefinition: clientTaskDefinition,
desiredCount: 1,
enableExecuteCommand: true,
securityGroups: [clientSecurityGroup],
serviceConnectConfiguration: {
namespace: namespace.namespaceArn,
services: [],
},
});
// サーバーサービスデプロイ後にクライアントサービスを起動
clientService.node.addDependency(serverService);
}
}
動作確認
手順は冒頭の記事と同様です。
ECS ExecでClient用のECSサービスに接続して、Server用のECSサービスにリクエストを送るとレスポンスが返ってきました。
# タスクIDを取得
CLIENT_TASK_ID=$(aws ecs list-tasks --cluster cross-vpc-client --query 'taskArns[0]' --output text)
# Consumerコンテナに接続
aws ecs execute-command \
--cluster cross-vpc-client \
--task $CLIENT_TASK_ID \
--container ClientContainer \
--interactive \
--command "/bin/sh"
curl server.cross-vpc-service-connect
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
/etc/hosts
の設定も、同一VPCのときと変わりません。
(127.255.0.1
でサイドカーのエージェントに向かっている。)
cat /etc/hosts
127.0.0.1 localhost
10.1.3.129 ip-10-1-3-129.ap-northeast-1.compute.internal
127.255.0.1 server.cross-vpc-service-connect
2600:f0f0:0:0:0:0:0:1 server.cross-vpc-service-connect
おわりに
ECS Service Connectで別VPCのECSサービスへの接続を試してみました。
別VPCの場合もECS Service Connect側では、特別な設定をする必要はありませんでした。
VPCピアリング等でVPC間の疎通を確保して、同じCloud Map名前空間を利用することで接続が可能でした。