一次 AWS EC2 网络输出异常排查:Java Docker 服务误连公网 IP 导致流量回环

一次 AWS EC2 网络输出异常排查:Docker 服务误连公网 IP 导致流量回环

1. 问题背景

最近在 AWS EC2 服务器上查看 CloudWatch 监控时,发现实例的 NetworkOut 网络输出流量偏大 ,并且几乎和 NetworkIn 网络输入流量一致

从 1 天维度的监控曲线看,大致表现为:

text 复制代码
NetworkIn  ≈ NetworkOut

如果是普通 Web 服务,输入和输出接近并不一定异常,因为服务端收到请求后会返回响应。但本次服务器业务访问量并不高,且后台主要运行 Java 服务、Redis、MySQL、MQTT 等组件,因此需要进一步排查:到底是谁在产生这些网络流量。


2. 服务器环境概况

本次服务器环境大致如下:

text 复制代码
云平台:AWS EC2
系统:Linux / Ubuntu
主业务网卡:ens5

EC2 私网 IP:PRIVATE_IP
EC2 公网 IP:PUBLIC_IP

Java 服务:Docker 容器部署
Redis:部署在宿主机
MySQL:部署在宿主机
MQTT Broker:部署在本机或容器中

通过以下命令查看网卡信息:

bash 复制代码
ip addr

可以看到类似输出:

text 复制代码
2: ens5: <BROADCAST,MULTICAST,UP,LOWER_UP>
    inet PRIVATE_IP/20

因此可以确认,AWS EC2 的主业务网卡是:

text 复制代码
ens5

后续排查网络流量时,重点观察 ens5 即可。


3. 初步排查:查看主网卡实时流量

安装并使用 iftop 查看实时流量:

bash 复制代码
sudo apt update
sudo apt install -y iftop
sudo iftop -i ens5 -nP

参数说明:

text 复制代码
-i ens5  指定监听 ens5 网卡
-n       不解析域名,直接显示 IP
-P       显示端口号

排查时发现,iftop 中出现大量类似连接:

text 复制代码
PRIVATE_IP:redis  => PUBLIC_IP:xxxxx
PUBLIC_IP:xxxxx   => PRIVATE_IP:redis

其中:

text 复制代码
PRIVATE_IP 是 EC2 私网 IP
PUBLIC_IP 是 EC2 公网 IP
redis 通常代表 6379 端口

这说明:服务器上有程序正在通过 公网 IP 访问本机 Redis

实际访问路径变成了:

text 复制代码
Java Docker 容器
    ↓
PUBLIC_IP:6379
    ↓
AWS 公网地址映射
    ↓
宿主机 Redis

这类访问会同时产生网络输出和网络输入,因此 CloudWatch 上就会看到:

text 复制代码
NetworkOut 增加
NetworkIn 也同步增加

这就是 NetworkIn 和 NetworkOut 几乎一致的原因之一。


4. 根因分析:Docker 容器误用公网 IP 访问宿主机服务

本次 Java 服务运行在 Docker 容器中,而 Redis 和 MySQL 部署在宿主机上。

原配置中,Java 服务连接 Redis / MySQL 时使用了公网 IP:

properties 复制代码
spring.redis.host=PUBLIC_IP
spring.redis.port=6379

spring.datasource.url=jdbc:mysql://PUBLIC_IP:3306/your_database

这个配置虽然能连通,但访问路径不合理。

如果 Java 服务、Redis、MySQL 都在同一台服务器上,应用不应该通过公网 IP 访问本机服务。这样会带来几个问题:

text 复制代码
1. 产生不必要的公网网卡流量
2. CloudWatch NetworkIn / NetworkOut 被放大
3. Redis / MySQL 可能被迫暴露到公网
4. 安全组配置容易留下安全隐患

但也不能简单改成:

properties 复制代码
spring.redis.host=127.0.0.1
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/your_database

因为在 Docker 容器内部:

text 复制代码
127.0.0.1 = 容器自己
不是宿主机

所以 Java 容器访问宿主机上的 Redis / MySQL,需要使用 Docker 网关地址,或者使用 host.docker.internal


5. 修复方式:使用 host.docker.internal 访问宿主机

如果使用 docker-compose,可以在 Java 服务中增加:

yaml 复制代码
services:
  java-service:
    image: your-java-image
    extra_hosts:
      - "host.docker.internal:host-gateway"

然后将 Java 配置改为:

properties 复制代码
spring.redis.host=host.docker.internal
spring.redis.port=6379

MySQL 配置改为:

properties 复制代码
spring.datasource.url=jdbc:mysql://host.docker.internal:3306/your_database?useSSL=false&serverTimezone=UTC

如果使用 docker run 启动容器,则可以增加参数:

bash 复制代码
docker run -d \
  --name java-service \
  --add-host=host.docker.internal:host-gateway \
  your-java-image

这样 Java 容器访问宿主机服务时,就不再经过 EC2 公网 IP,而是通过 Docker 内部网关访问宿主机。

修复后的访问路径为:

text 复制代码
Java Docker 容器
    ↓
host.docker.internal
    ↓
宿主机 Redis / MySQL

6. 验证 Redis 流量是否消失

重新部署 Java 服务后,再次执行:

bash 复制代码
sudo iftop -i ens5 -nP

如果修复成功,之前大量出现的以下流量应当消失:

text 复制代码
PRIVATE_IP:redis <=> PUBLIC_IP:xxxxx

也可以进一步检查 Redis / MySQL 当前连接:

bash 复制代码
sudo ss -ntp | grep -E ':6379|:3306'

如果仍然看到类似:

text 复制代码
PUBLIC_IP:xxxxx

说明仍然有程序在通过公网 IP 连接 Redis / MySQL。

如果看到的是 Docker 网段地址,例如:

text 复制代码
172.17.x.x
172.18.x.x
172.19.x.x

通常说明访问已经切换为 Docker 内部网络。


7. 二次发现:MQTT 1883 也存在类似问题

修复 Redis 后继续观察,发现 Redis 大流量下降,但仍然有类似连接:

text 复制代码
PRIVATE_IP:1883 <=> PUBLIC_IP:xxxxx

1883 是 MQTT 常用端口。

这说明还有某个服务配置了:

text 复制代码
mqtt.host = PUBLIC_IP
mqtt.port = 1883

也就是仍然通过公网 IP 访问本机 MQTT Broker。

继续检查配置文件:

bash 复制代码
grep -R "PUBLIC_IP" -n /opt /home /root /etc 2>/dev/null

同时检查 Docker 容器环境变量:

bash 复制代码
docker inspect $(docker ps -q) | grep -n "PUBLIC_IP"

如果发现类似配置:

text 复制代码
MQTT_HOST=PUBLIC_IP
mqtt.url=tcp://PUBLIC_IP:1883

则需要同样改成内部访问地址。

如果 MQTT Broker 部署在宿主机上,Java 容器中应配置为:

properties 复制代码
mqtt.host=host.docker.internal
mqtt.port=1883

如果 Java 和 MQTT Broker 都在同一个 Docker Compose 网络中,则更推荐使用服务名:

properties 复制代码
mqtt.host=mqtt-broker
mqtt.port=1883

8. 最终结果

经过调整后,再次观察 iftop,已经不再看到本机公网 IP 和 Redis / MQTT 之间的大量互连流量。

剩余流量主要是外部客户端访问服务器上的业务端口,例如:

text 复制代码
PRIVATE_IP:1883  <=> 外部客户端 IP
PRIVATE_IP:8084  <=> 外部客户端 IP
PRIVATE_IP:https <=> 外部客户端 IP
PRIVATE_IP:ssh   <=> 管理端 IP

整体流量从原先的持续高位下降到较低水平。

本次问题的核心原因可以总结为:

text 复制代码
Docker 容器内的 Java 服务误用 EC2 公网 IP 访问本机 Redis / MQTT
导致流量从公网网卡绕回本机
从而造成 CloudWatch 中 NetworkIn 和 NetworkOut 同步偏高

9. 安全收口建议

虽然本次主要问题是应用配置错误,但排查过程中也暴露出基础服务端口可能公网暴露的风险。

建议对 AWS Security Group 做以下收口。

9.1 Redis 和 MySQL 不应开放公网

检查安全组入站规则中是否存在:

text 复制代码
6379    0.0.0.0/0
3306    0.0.0.0/0

如果存在,建议删除。

Redis 和 MySQL 一般只允许:

text 复制代码
本机访问
Docker 内网访问
指定业务服务器私网 IP 访问

不建议直接暴露公网。


9.2 SSH 只允许固定 IP

SSH 端口不建议开放:

text 复制代码
22    0.0.0.0/0

推荐改为:

text 复制代码
22    管理员固定公网 IP/32

9.3 MQTT 端口需要认证和限制

如果业务确实需要公网设备接入 MQTT,可以开放 1883 / 8883,但必须做好以下措施:

text 复制代码
禁止匿名访问
启用用户名密码认证
配置 ACL 权限控制
尽量限制客户或设备固定 IP
生产环境优先使用 8883 TLS
MQTT 管理后台不要暴露公网

如果 MQTT 1883 无认证暴露公网,很容易被扫描、连接或滥用。


10. 常用排查命令总结

查看主网卡

bash 复制代码
ip addr
ip route
ip route get 8.8.8.8

实时查看网卡流量

bash 复制代码
sudo iftop -i ens5 -nP

查看进程级网络流量

bash 复制代码
sudo apt install -y nethogs
sudo nethogs ens5

查看监听端口

bash 复制代码
sudo ss -tunlp

查看 Redis / MySQL / MQTT 连接

bash 复制代码
sudo ss -ntp | grep -E ':6379|:3306|:1883'

查看 Docker 容器端口映射

bash 复制代码
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}"

查看 Docker 容器流量

bash 复制代码
docker stats --no-stream

搜索配置里是否残留公网 IP

bash 复制代码
grep -R "PUBLIC_IP" -n /opt /home /root /etc 2>/dev/null

搜索 Docker 容器环境变量中的公网 IP

bash 复制代码
docker inspect $(docker ps -q) | grep -n "PUBLIC_IP"

11. 排查经验总结

这次问题并不是 AWS 本身异常,也不是服务器被打满,而是应用配置中使用了错误的连接地址。

在 Docker 场景下,需要特别注意:

text 复制代码
容器内的 127.0.0.1 不是宿主机
宿主机公网 IP 不适合作为本机服务访问地址
容器访问宿主机服务应使用 host.docker.internal 或 Docker 网关
同一 Compose 网络内的容器应使用服务名互相访问

错误访问路径:

text 复制代码
Java Docker 容器
    ↓
EC2 公网 IP
    ↓
AWS 公网地址映射
    ↓
宿主机 Redis / MQTT

正确访问路径:

text 复制代码
Java Docker 容器
    ↓
host.docker.internal
    ↓
宿主机 Redis / MySQL / MQTT

或者在同一 Docker Compose 网络中:

text 复制代码
Java 容器
    ↓
Docker Compose 服务名
    ↓
Redis / MySQL / MQTT 容器

最终,通过修正应用配置、重新部署服务、观察 iftop 和 CloudWatch,确认 NetworkIn / NetworkOut 同步偏高的问题得到解决。


12. 后续优化方向

为了以后更快定位类似问题,可以考虑:

text 复制代码
开启 VPC Flow Logs,用于分析源 IP、目标 IP、端口和流量大小
开启 CloudWatch Agent,用于监控内存、磁盘、进程和日志
为 NetworkOut 设置异常告警
为 Redis / MySQL / MQTT 连接数设置监控
定期检查安全组公网暴露端口

其中:

text 复制代码
VPC Flow Logs 更适合排查网络连接来源、目标、端口和流量大小
CloudWatch Agent 更适合长期监控 CPU、内存、磁盘、进程状态和日志

两者不是互相替代关系,而是分别解决不同层面的监控和排查问题。