服务连不上 Redis:VNet 集成改变了出站 IP
测试环境的推送服务连不上 Azure Redis。开启 VNet 集成和 route all 后,访问 Redis 的连接从 NAT gateway 的 IP 出去,而 Redis 防火墙只放行了 App Service 的默认出站 IP。
测试环境里有个推送服务连不上 Azure Redis。服务是跑在 Azure App Service 上的 Node.js 应用,用 BullMQ 做任务队列,队列数据存放在 Azure Cache for Redis。队列初始化失败导致依赖队列的推送发不出去。
这篇内容整理自我事后写的排查报告。这里不靠记忆去还原当时一步步排查的过程,只陈述记录下来的证据。
问题出在哪一层
连接失败通常分好几种情况:
- host 或 port 填错了。
- 密码或 TLS 配置不对,连接虽然建立成功,但握手或鉴权失败。
- 网络根本不通,连接直接被拒绝。
报告里记录当时的配置是 host 正常、port 为 6379 且关闭了 TLS。错误现象是网络阻断,也就是连接被拒绝,而不是鉴权报错。这表明问题出在网络层,Redis 没有接受请求。
Azure Redis 配了防火墙,只放行加白名单的 IP。因此核心问题在于:这个 Web App 到底是用哪个 IP 去连 Redis 的?
出站 IP 并不是看到的那个
App Service 的控制台页面上列有一组 outbound IP addresses,Redis 防火墙加的也正是这几个 IP。纸面上看完全对得上。
但是这个 Web App 开启了 VNet integration,并且打开了 route all(vnetRouteAllEnabled: true),子网里还挂了一台 NAT gateway。报告里指出,这意味着访问 Redis 的连接实际是从这台 NAT gateway 出去的。在 Redis 看来,请求来源是 NAT gateway 的公网 IP,根本不是 App Service 页面上列出的那些地址。
这个结论只针对访问 Redis 的这条路径,我没有去核对服务访问的所有外部目标或私网路由。
控制台页面上显示的 outbound IP 并没有错,只是在当前的网络拓扑下,这条连接使用的出口已经变了。
把下面三点对照来看,原因就很清楚了:
- Web App 开启了 VNet integration 和 route all,绑定了挂有 NAT gateway 的子网。
- NAT gateway 有一个固定的公网 IP。
- Redis 防火墙只加了 App Service 默认的 outbound IP,没有加 NAT 的公网 IP。
解决办法
网络层面的改法只有一步:把 NAT gateway 的公网 IP 加进 Redis 防火墙的白名单。
az redis firewall-rules create \
--resource-group <resource-group> --name <redis-name> \
--rule-name AllowNatGateway \
--start-ip <nat-public-ip> --end-ip <nat-public-ip>
改完后跑了服务的 health check,Redis、队列和应用自身全部通过,Redis 响应时间只有几毫秒。当时环境还在用非 TLS 端口,因此这次解决跟 TLS 没有关系。
顺带准备 TLS
当时测试环境用的是非 TLS 端口 6379,我顺便改了队列的连接配置,支持通过环境变量开启 TLS。在我们这里的配置中,开启 TLS 会走 6380 端口,并把 servername 设为 Redis 的 host(SNI)。我没测试过不配 servername 会怎样,所以不能说它是强制要求的。
const connection = {
host: process.env.REDIS_HOST ?? 'localhost',
port: Number(process.env.REDIS_PORT ?? 6379),
password: process.env.REDIS_PASSWORD,
...(process.env.REDIS_TLS_ENABLED === 'true'
? { tls: { servername: process.env.REDIS_HOST } } // SNI: how we connect to Azure Redis
: {}),
};
按计划,后续切 TLS 只需要改两个 App Settings(开关和端口号)并重启服务,不需要改代码,要回滚也是直接改回配置。这个方案写在报告里,切 TLS 的操作当时还没有实际跑过。
待收尾的事
报告里列了后续建议处理的事项,这些只是建议,还没有确认执行:
- 清理旧的防火墙规则。 如果这个服务发往 Redis 的流量全部走 NAT gateway,那之前配给 App Service 默认 outbound IP 的规则就没用了。报告里写的是“如果”,我没有确认是不是还有其他 client、deployment slots 或回滚路径在用这些 IP,单凭手头的证据不会直接删掉它们。
- 等 TLS 稳定后关闭非 TLS 端口。
- 加报警。 在 Application Insights 里配置 Redis 连接失败的报警,而不是等 QA 发现推送发不出去才发现问题。
- 把 NAT 出口补进文档。 要避免“一处改了网络配置,另一处的白名单没跟上”,最好的办法是把这种检查写进变更 checklist。
复盘
这个问题本身跟 Redis 关系不大,核心在于“出站 IP”的判定。只要网络路由有改动,比如开启 VNet integration、挂载 NAT gateway 或调整出口,下游依赖 IP 白名单的服务(数据库、Redis、第三方 API 等)就可能把请求拦在外面。而且控制台页面上标出的 outbound IP 并不会提醒你它已经不再是某条连接的实际出口。
遇到类似连不上的情况,最该问的还是那句话:这次连接到底来自哪个 IP,这个 IP 在不在白名单里。先看报错类型确定属于哪一层,再把实际的出口跟防火墙规则对一遍。