NAT loopback
Connections time out for requests from a target to its load balancer.
Check whether client IP preservation is enabled on your target group. NAT loopback, also known as hairpinning, is not supported when client IP preservation is enabled.
If an instance is a client of a load balancer that it's registered with and it has client IP preservation enabled, the connection succeeds only if the request is routed to a different instance. If the request is routed to the same instance it was sent from, the connection times out because the source and destination IP addresses are the same. Note that this applies to Amazon EKS pods running in the same EC2 worker node instance, even though they have different IP addresses.
If an instance must send requests to a load balancer that it's registered with, do one of the following:
Disable client IP preservation. Instead, use Proxy Protocol v2 to get the client IP address.
Ensure that containers that must communicate are on different container instances.
Example
It will timeout on the following situation.
- A and B are on the same node.
- A send request to B through NLB.
- NLB enabled client ip preservation.
TIP
最常见的例子是 A 服务通过外网的域名请求一个接口。 这个域名解释就会走 NLB 的公网 IP。 当这个接口最终由 B 服务(和 A 服务在同一个节点)处理的时候, 就会因为 NLB 开启了真实 IP 而导致连接超时。
Explaination
当 NLB 启用了客户端 IP 保留,NLB 不会对源 IP 做 SNAT,意味着:
- ServerA 发出请求,源 IP 是自己的真实 IP;
- NLB 将请求转发给 ServerB,目标 IP 是 ServerB 的 IP;
- 如果 ServerB 在 同一个节点,那么这个节点内核需要处理:
- 源 IP = ServerA(本节点 IP)
- 目标 IP = ServerB(同样是本节点 IP)
这时候 Linux 网络栈的连接跟踪(conntrack)会出现问题, 因为这是一个“hairpin”(发出的包又回来)行为,且源 IP 没有被 SNAT 改写,conntrack 无法匹配这个连接。
Solutions
- 关闭 client IP 保留
- 使用 SNAT,让 LB 修改源 IP;
- ServerB 收到的数据包来源变成 LB,不会触发 NAT loopback 问题。
- 启用 Proxy Protocol v2
- 即使 SNAT 了源 IP,也能通过 Proxy Protocol 拿到真实客户端 IP。
- 避免 ServerA 和 ServerB 落在同一节点
- Kubernetes 层通过
podAntiAffinity
或 node affinity 控制。
- Kubernetes 层通过
AWS NLB Model
用户请求 → DNS 解析域名 → NLB(L4) → EC2(K8s Node) → NodePort → kube-proxy / CNI → Pod(你的服务)
https://api.example.com
↓
DNS 解析
↓
AWS NLB (TCP/UDP, L4)
↓
AWS Target Groups
↓
EC2 (K8s NodePort)
↓
Istio Ingress Gateway(Pod,监听 NodePort)
↓
Istio 路由规则(VirtualService / Gateway)
↓
具体服务(Pod)
访问域名
- 浏览器首先查询 DNS 来解析 Uapi.example.com`。
- 这个域名通常是解析到 NLB 的公网 IP 地址。
api.example.com → A 记录 → NLB 的公网 IP
NLB 转发
- NLB 是四层(L4)负载均衡器。
- 它可以接收 TCP 连接(如 443 端口)
- 并转发到 Target Group 的 EC2 中(K8s Node 上的 NodePort)。
- 它并不解析 TLS,不理解 HTTP,也不知道域名是什么。
节点 kube-proxy/CNI 转发
- 节点可通过 NodePort 查到对应的 SVC。
- kube proxy 会根据 SVC 的 Endpoint 做转发。
- 最终发送到
istio-ingressgateway
的 Pod 中。
istio-ingressgateway 转发
- 根据
Gateway
和VirtualService
发送到对应服务的 Pod 中。
Linux NLB
NLB 的端口转发功能,在 linux 上如果自己实现的话,是用什么?
iptables / nftables
用于实现 四层端口转发(TCP/UDP),即 DNAT:
# 将公网 IP:80 转发到内网 192.168.1.100:8080
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 \
-j DNAT --to-destination 192.168.1.100:8080
# 启用 IP 转发功能(系统级)
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
这样就模拟了 NLB 的 Listener + Target Group + 转发规则。
conntrack
Linux 内核的连接跟踪模块,用于记录 TCP 会话、NAT 映射、处理回包方向等。
- NLB 的「Client IP 保留」就类似于:
- 不做 SNAT,只使用 DNAT
- 所以回包路径要依赖 conntrack 识别连接关系
你可以用它调试连接追踪状态:
sudo conntrack -L # 列出 NAT 连接状态
示例:模拟 NLB 的完整脚本
你有公网 IP(或 loopback 上添加一个),然后将 80 转发给某个本地服务:
# 添加一个公网 IP(模拟 NLB IP)
sudo ip addr add 203.0.113.10/32 dev lo
# 转发到本地服务 127.0.0.1:8080
sudo iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 80 \
-j DNAT --to-destination 127.0.0.1:8080
# 启用内核转发
sudo sysctl -w net.ipv4.ip_forward=1
这样,就能转发到你本地的服务了。
curl http://203.0.113.10
Question
- 虽然 Source IP 和 Dest IP 是同一个 IP,但直接根据 Dest IP 和端口,不是能直接发过去 B 所在的节点吗?