You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无负载均衡时VPC内ECS结合AWS ServiceDiscovery通信问题

AWS ECS EC2服务通过CloudMap VPC DNS实现VPC内通信问题排查与解决方案

问题背景

已通过AWS CDK完成以下部署:

  1. 创建VPC
self.vpc = ec2.Vpc(
    self, 
    "rcs-vpc",
    vpc_name="rcs-vpc",
    max_azs=2,
    ip_addresses=ec2.IpAddresses.cidr("10.180.0.0/16"),
    subnet_configuration=[
        ec2.SubnetConfiguration(
            subnet_type=ec2.SubnetType.PUBLIC,
            name="public",
            cidr_mask=24
        ),
        ec2.SubnetConfiguration(
            subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS,
            name="private",
            cidr_mask=24
        )
    ],
    nat_gateway_provider=ec2.NatProvider.gateway(),
    nat_gateways=1
)
  1. 在VPC内创建PrivateDNSNamespace
self.rcs_sd_namespace = servicediscovery.PrivateDnsNamespace(
    self, 
    "rcs-sd-private-namespace",
    name="rcs.internal",
    vpc=vpc_stack.vpc
)
  1. 创建ECS EC2集群
self.ecs_cluster = ecs.Cluster(
    self,
    "rcs-ecs-cluster",
    cluster_name="rcs-cluster",
    vpc=vpc_stack.vpc
)
  1. 部署EC2类型的ECS容器服务并配置CloudMap(A记录关联PrivateDNSNamespace)
from aws_cdk import CfnOutput, Stack, Duration, Lazy
import aws_cdk.aws_ec2 as ec2
import aws_cdk.aws_elasticloadbalancingv2 as elbv2
import aws_cdk.aws_ecs as ecs
import aws_cdk.aws_ecr as ecr
import aws_cdk.aws_ssm as ssm
import aws_cdk.aws_servicediscovery as servicediscovery
from constructs import Construct


class DSERootStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)
        self.rcs_sd_namespace = servicediscovery.PrivateDnsNamespace.from_private_dns_namespace_attributes(
            self,
            "rcs-sd-namespace",
            namespace_arn=ssm.StringParameter.value_from_lookup(self, parameter_name="rcs-sd-namespace-arn"),
            namespace_id=ssm.StringParameter.value_from_lookup(self, parameter_name="rcs-sd-namespace-id"),
            namespace_name=ssm.StringParameter.value_from_lookup(self, parameter_name="rcs-sd-namespace-name")
        )
        self.dse_vpc = ec2.Vpc.from_lookup(
            self,
            "dse_vpc",
            vpc_id=ssm.StringParameter.value_from_lookup(self, parameter_name="rcs-vpc-id")
        )
        self.dse_security_group = ec2.SecurityGroup(
            self,
            "dse-security-group",
            security_group_name="dse-security-group",
            vpc=self.dse_vpc,
            description="Allow access to DSE",
            allow_all_outbound=True,
            disable_inline_rules=False
        )
        self.dse_security_group.add_ingress_rule(
            peer=ec2.Peer.any_ipv4(),
            connection=ec2.Port.all_traffic()
        )
        self.dse_security_group.add_egress_rule(
            peer=ec2.Peer.any_ipv4(),
            connection=ec2.Port.all_traffic()
        )
        self.dse_task_definition = ecs.TaskDefinition(
            self,
            "dse-task-definition",
            compatibility=ecs.Compatibility.EC2,
            network_mode=ecs.NetworkMode.AWS_VPC
        )
        self.dse_container = self.dse_task_definition.add_container(
            "dse-container",
            image=ecs.ContainerImage.from_ecr_repository(
                ecr.Repository.from_repository_name(
                    self,
                    "enter",
                    repository_name="test"
                )
            ),
            memory_limit_mib=512,
            port_mappings=[
                ecs.PortMapping(
                    name="http_port_mapping",
                    container_port=8000
                )
            ],
            logging=ecs.AwsLogDriver.aws_logs(
                mode=ecs.AwsLogDriverMode.NON_BLOCKING,
                stream_prefix="TEST"
            )
        )
        self.dse_service = ecs.Ec2Service(
            self,
            "dse-service",
            service_name="dse-service",
            assign_public_ip=False,
            cluster=ecs.Cluster.from_cluster_attributes(
                self,
                "dse-cluster",
                cluster_name=ssm.StringParameter.from_string_parameter_name(self, "rcs-cluster-name", string_parameter_name="rcs-cluster-name").string_value,
                vpc=self.dse_vpc
            ),
            cloud_map_options=ecs.CloudMapOptions(
                cloud_map_namespace=self.rcs_sd_namespace,
                name="dse",
                container=self.dse_container,
                container_port=8000,
                dns_record_type=servicediscovery.DnsRecordType.A,
                dns_ttl=Duration.seconds(10)
            ),
            security_groups=[
                self.dse_security_group
            ],
            task_definition=self.dse_task_definition
        )

当前问题

ECS Agent API和CloudMap返回的容器IP一致,但从其他容器执行wget dse.rcs.internal:8000提示连接拒绝,堡垒主机也无法ping通该服务。

需求

希望该容器服务能与VPC内的其他实例、其他容器服务以及Lambda函数通信,询问是否可以不使用负载均衡,仅通过ServiceDiscovery的VPC内部DNS实现。


核心问题解答

完全可以。CloudMap的Private DNS Namespace就是为VPC内部服务发现设计的,无需负载均衡,就能让VPC内的EC2实例、ECS服务、VPC专属Lambda通过服务域名(如dse.rcs.internal)直接访问目标ECS服务。


连接失败的原因排查与修复步骤

1. 安全组规则的隐性限制

虽然你为任务配置的dse-security-group允许所有IPv4流量,但需注意以下几点:

  • ECS集群EC2实例的安全组:需要允许与任务ENI的通信(VPC CIDR 10.180.0.0/16的TCP 8000端口、ICMP协议),因为任务运行在EC2实例上,实例安全组会过滤进出实例的流量。
  • 堡垒主机的安全组:确认其出站规则允许访问目标端口(TCP 8000)和ICMP协议,即使任务安全组允许所有入站,堡垒主机自身的出站限制也会导致连接失败。

2. 容器应用的监听地址问题

检查容器内的应用是否监听在0.0.0.0而非127.0.0.1:

  • 进入容器执行命令:netstat -tulpn
  • 如果应用仅绑定localhost,外部(包括VPC内其他节点)无法访问容器端口,需修改应用配置为监听所有网卡。

3. VPC DNS解析配置

确保VPC的DHCP选项集使用默认的AmazonProvidedDNS:

  • 进入VPC控制台,查看目标VPC的DHCP选项集,确认域名服务器配置为AmazonProvidedDNS,否则VPC内的节点无法解析CloudMap私有域名。

4. ECS任务网络模式的细节验证

使用AWS_VPC网络模式时,任务拥有独立ENI,需确认:

  • 任务所在私有子网有足够的可用IP地址分配给ENI
  • 子网路由表允许VPC内流量互通(私有子网默认路由到NAT网关,但VPC内的区域间流量无需经过NAT,只要安全组允许即可正常通信)

验证步骤

  1. 从堡垒主机执行nslookup dse.rcs.internal,确认返回的IP与ECS Agent API返回的任务IP一致
  2. 执行telnet dse.rcs.internal 8000,若能连通说明端口访问正常
  3. 从同VPC内的其他ECS容器或EC2实例重复上述验证操作

内容的提问来源于stack exchange,提问作者Zu Jiry

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 00:20:56