ECS Service Connectで別VPCのECSサービスに接続してみた

ECS Service Connectで別VPCのECSサービスに接続してみた

2025.07.28

以前、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で作成して、疎通を確認しました。

ECS Service Connect(1).png

CDKでリソースを作成する

以下のコードを用意します。

ServerとClient用のECSサービスをそれぞれ別VPCにデプロイします。

ServerはNginxコンテナを起動します。

Client側では、NginxコンテナにService Connect経由で疎通確認を行う処理を入れています。

VPC間はVPCピアリングで疎通を確保しておきます。

lib/cross-vpc-ecs-service-connect-stack.ts
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名前空間を利用することで接続が可能でした。

この記事をシェアする

facebookのロゴhatenaのロゴtwitterのロゴ

© Classmethod, Inc. All rights reserved.