前言
呵呵 最近出现了这样的一个问题, 我们有多个前端服务, 分别连接了对应的后端服务, 前端A -> 后端A, 前端B -> 后端B
但是 最近的时候 却会出现一种情况就是, 有些时候 前端A 连接到了 后端B, 前端B 连接到了 后端A
我们 前端服务使用 nginx 提供前端 html, js, css 的服务, 对于前端业务中的请求, 也是使用的 nginx 来代理到真实的后端服务上面, 后端服务 我们就假定为普通的 tomcat 服务器, 前后端服务都是部署在 docker 上面
然后 就给人造成一种 数据跑掉了的错觉, 呵呵 也是引起了我们同事 使用系统的很大的疑惑, 然后 搞出了一些 "我的数据 再和我做迷藏", "我的数据从成都跑到北京去了" 之类的笑话
然后 你一去检查服务的配置, 等等, 你发现 前端配置, 后端配置 都没得问题, 我最开始以为是 nginx 运行时加载的配置 被修改了?, 加载在内存的配置 和 实际的配置文件不一致?, 但是 实际 看了一下, 发现配置是 没有问题的
对于这个问题 还是很好奇的, 当然 后面也做了一些 探索, 也有一些 初步的推断, 然后 今天的时候[1114] 尝试在本地复现一下这个问题, 也发现了一些 和自己开始的判断 不一样的一些地方
let's go
问题特征
这个问题的出现 和 消失有这样的一些特征
1. 所有的前端, 后端配置都是正常的, 但是 实际接口返回的数据 就是不一样
2. 出现了这个问题之后, 重启一下 前端服务 之后, 前端服务就正常了
3. 一般是后端代码更新了之后, jenkins 自动发版之后 会出现这个问题
前端容器中 也缺少 ping 之类的网络工具, 造成排查的时候 还是存在一些 障碍
问题inspect
最开始 想要处理这个问题, 我得看一下 nginx 最终吧服务转发到了 那一台后端服务器
因此, 在 nginx 配置的地方, 增加了一个 add_header backendIP $upstream_addr; 来获取实际处理 后端请求的服务的信息
然后 后来一次 是因为 后台发版了, 之后 这个问题就复现了, 这也使我观察到了 上面的 特征3
然后 看一下 backendIP, 然后 再 inspect 对应的额后端服务, 发现后端服务的 ip 和这个 backendIP 对不上 ??
所以 问题的大致原因 也就出现了, 发版之后 后端服务的 ip 变化了, 然后 前端服务里面访问的 ip 还是之前的 ip
当然 最开始, 我以为是 前端服务里面的 dns 缓存之类的, 直到今天 测试了一下, 发现 似乎是其他的原因
接下来 我们便来以一个 demo 实际的走一下 这个流程
构造问题
首先 我们需要 一个前端服务, 和 两个后端服务, 前端服务A 只连接 其中的一个 后端服务A
然后 之后我们同时重启 两个后端服务, 然后 再来访问这个 前端服务A, 可能会存在 前端服务A 访问到了 后端服务B 的情况
1. 看下后端服务
一个简单的 SpringBoot 项目, 其中开放了一些简单的接口, 比如这里的 /hello/context, 可以输出 当前应用名称 和 一些上下文的信息
2. 部署后端服务
首先来部署两个后端服务, docker-compose 大致如下
两个服务使用 同一个镜像, 只是传递的环境变量的 APP_NAME 有一些区别, 因此 访问 /hello/context 的时候 appName 的输出会有一些区别
另外使用同一个镜像的原因在于, 更加简单的构造 两个镜像同时重启
version: "2"
networks:
fuzzy:
external: true
services:
app0:
container_name: app0
image: app0:0.0.1
ports:
- "7901:7901"
environment:
APP_NAME: app0
networks:
- fuzzy
app1:
container_name: app1
image: app0:0.0.1
ports:
- "7902:7901"
environment:
APP_NAME: app1
networks:
- fuzzy
服务起起来之后 测试一下, 这里把服务映射到了宿主机的端口, 根据这个来测试一下
访问情况如下, 可以看到是 符合我们的预期的, 不是很意外
http://localhost:7901/hello/context
http://localhost:7902/hello/context
3. 部署前端服务
前端服务我们这里 为了简单, 是直接使用的 nginx 镜像, 没有业务, 连接 app0 的服务
前端服务的 docker-compose 如下
version: "2"
networks:
fuzzy:
external: true
services:
nginx:
container_name: nginx
image: nginx:latest
ports:
- "80:80"
volumes:
- ./data:/etc/nginx
networks:
- fuzzy
nginx 配置大致如下
可以看到 /hello 开头的这部分请求, 我们把它 转发到了 app0 的服务上面, 使用 add_header backendIP $upstream_addr; 输出了一下 具体的处理业务的后端服务器的信息
server {
listen 80;
listen [::]:80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location ~ /hello {
proxy_pass http://app0:7901;
add_header backendIP $upstream_addr;
proxy_set_header SSL-Client-Cert $ssl_client_cert;
proxy_set_header name jerry;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
4. 正常情况 和 出现问题的情况
正常的情况
出现问题
问题细节
正常情况
首先我们看一下 app0, app1, nginx 的 ip 情况
从下面可以整理出 网路情况如下
app0 | 192.168.80.3 |
app1 | 192.168.80.2 |
nginx | 192.168.80.4 |
然后我们来看一下 正常的情况下 处理的后端服务的情况
可以看到的是 访问的是 ip 是 192.168.80.3 对应于上面的 app0
我们重启 app0, app1 直到复现问题
我们可以看到 此时处理服务的容器 的ip还是 192.168.80.3
但是 返回的数据, 却已经是 app1 应该返回的数据了
我们再来看一下 app0, app1, nginx 的 ip 情况
从下面可以整理出 网路情况如下
app0 | 192.168.80.2 |
app1 | 192.168.80.3 |
nginx | 192.168.80.4 |
可以看到的是 app0 和 app1 的 ip 变了, 两个容器的 ip 交换了一下, 但是 前端容器 还是将服务委托给了 192.168.80.3 的这个容器, 而不是 app0 这个服务
是前端容器的 dns 的缓存么?
为了 验证这个问题, 呵呵 可以使用 ping 或者 nslookup 之类的网络工具, 但是 在 nginx 容器里面这两个都没得
我今天 还想了一阵子, 怎么验证这个问题, 呵呵 curl 有一个 -v, verbose 展示出请求的更详细的信息
curl -v http://app0:7901/hello/context
从下面的日志信息可以看出, 从 前端服务的容器中访问 app0 访问的是 最新的app0[192.168.80.2], 因此可以排除 容器的 dns缓存的猜测
master:nginx jerry$ docker exec -it nginx /bin/sh
# curl -v http://app0:7901/hello/context
* Expire in 0 ms for 6 (transfer 0x55712a1dff50)
* Expire in 1 ms for 1 (transfer 0x55712a1dff50)
* Expire in 0 ms for 1 (transfer 0x55712a1dff50)
* Expire in 1 ms for 1 (transfer 0x55712a1dff50)
* Expire in 0 ms for 1 (transfer 0x55712a1dff50)
* Expire in 0 ms for 1 (transfer 0x55712a1dff50)
* Expire in 2 ms for 1 (transfer 0x55712a1dff50)
* Expire in 0 ms for 1 (transfer 0x55712a1dff50)
* Expire in 0 ms for 1 (transfer 0x55712a1dff50)
* Expire in 2 ms for 1 (transfer 0x55712a1dff50)
* Expire in 0 ms for 1 (transfer 0x55712a1dff50)
* Expire in 0 ms for 1 (transfer 0x55712a1dff50)
* Expire in 2 ms for 1 (transfer 0x55712a1dff50)
* Expire in 0 ms for 1 (transfer 0x55712a1dff50)
* Expire in 0 ms for 1 (transfer 0x55712a1dff50)
* Expire in 2 ms for 1 (transfer 0x55712a1dff50)
* Expire in 0 ms for 1 (transfer 0x55712a1dff50)
* Expire in 0 ms for 1 (transfer 0x55712a1dff50)
* Expire in 0 ms for 1 (transfer 0x55712a1dff50)
* Trying 192.168.80.2...
* TCP_NODELAY set
* Expire in 200 ms for 4 (transfer 0x55712a1dff50)
* Connected to app0 (192.168.80.2) port 7901 (#0)
> GET /hello/context HTTP/1.1
> Host: app0:7901
> User-Agent: curl/7.64.0
> Accept: */*
>
< HTTP/1.1 200
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 135
< Date: Sat, 14 Nov 2020 09:24:12 GMT
<
* Connection #0 to host app0 left intact
context info as follow!, APP_NAME : app0<br/> headers : {"host":"app0:7901","user-agent":"curl/7.64.0","accept":"*/*"}<br/> params : {}
nginx 的 dns 缓存
nginx -s reload 一下
# nginx -s reload
2020/11/14 09:27:13 [notice] 33#33: signal process started
看一下 /hello/context 的情况, 返回的数据也是 app0 处理之后返回的数据, backendIP 也更新成了 192.168.80.2[app0对应的ip]
所以之前的处理方式 : 重启前端服务, 实际上真正解决问题的是 nginx 的重新加载
完
参考
nginx查看请求被转发到哪台服务器