记一次nginx中proxy_pass的使用问题

最近排查一个web服务的问题,webserver使用的nginx,最终发现是踩了nginx中proxy_pass的一个坑,这里记录下来。

踩坑经过

一个线上的http服务,示例nginx关键配置如下:

server {
  listen 80;
  server_name ligang.gdemo.com;
  server_tokens off;

  keepalive_timeout 5;

  charset utf-8;

  include /home/ligang/devspace/gobox-demo/conf/http/general/gzip.conf;

  access_log logs/ligang.gdemo.com.log combinedio buffer=1k;
  error_log logs/ligang.gdemo.com.log.err;

  location / {
    include /home/ligang/devspace/gobox-demo/conf/http/general/http_proxy.conf;

    proxy_intercept_errors on;
    proxy_pass http://ligang.proxy.gdemo.com;
  }
}

这里可以看到,请求 ligang.gdemo.com 时,nginx把请求反向代理到 ligang.proxy.gdemo.com 去做处理。

ligang.proxy.gdemo.com 这个服务在线上部署并解析到了A、B、C这3个机房,现在我想调整解析,去掉C机房,仅留A、B两个机房。

调整解析后,查看新的解析已经生效,但观察C机房的请求量,发现和之前一样,没有任何变化。

于是我观察C机房的nginx的log,发现请求来源还是 ligang.gdemo.com 的机器,域名解析调整后nginx那边依旧使用之前的IP。

于是我将 ligang.gdemo.com 的机器上的nginx全部reload后,C机房的请求终于没有了。

问题说明

上面的问题,说明在nginx的proxy_pass中如果使用了域名,那么nginx会把解析的结果缓存下来,貌似不会更新,因为上面的例子中,我调整解析后是几乎是隔了一天去看C机房的log发现流量没有任何变化的。

这样的话,如果你配置一个反向代理服务器,如果上游调整了域名,而你又没有得到通知,那么你的代理服务相当于不可用了。

从代码中看下nginx是如何解析主机ip的

有点好奇nginx是如何解析主机ip的,所以追踪下代码:

proxy_pass指令定义的地方(http/modules/ngx_http_proxy_module.c):

{ ngx_string("proxy_pass"),
   NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1,
   ngx_http_proxy_pass,    //处理方法
   NGX_HTTP_LOC_CONF_OFFSET,
   0,
   NULL },

ngx_http_proxy_pass方法(http/modules/ngx_http_proxy_module.c):

static char *
ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
  ngx_http_proxy_loc_conf_t *plcf = conf;

  size_t           add;
  u_short           port;
  ngx_str_t         *value, *url;
  ngx_url_t          u;
  ngx_uint_t         n;
  ngx_http_core_loc_conf_t  *clcf;
  ngx_http_script_compile_t  sc;

 ......

  url = &value[1];

 ......

 ngx_memzero(&u, sizeof(ngx_url_t));

  u.url.len = url->len - add;
  u.url.data = url->data + add;
  u.default_port = port;
  u.uri_part = 1;
  u.no_resolve = 1;

 plcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
}

这里继续追踪ngx_http_upstream_add方法(http/ngx_http_upstream.c):

ngx_http_upstream_srv_conf_t *
ngx_http_upstream_add(ngx_conf_t *cf, ngx_url_t *u, ngx_uint_t flags)
{
  ngx_uint_t           i;
  ngx_http_upstream_server_t   *us;
  ngx_http_upstream_srv_conf_t  *uscf, **uscfp;
  ngx_http_upstream_main_conf_t *umcf;

  if (!(flags & NGX_HTTP_UPSTREAM_CREATE)) {

    if (ngx_parse_url(cf->pool, u) != NGX_OK) {
      if (u->err) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                  "%s in upstream \"%V\"", u->err, &u->url);
      }

继续追踪ngx_parse_url方法(core/ngx_inet.c):

ngx_int_t
ngx_parse_url(ngx_pool_t *pool, ngx_url_t *u)
{
  u_char *p;           

  p = u->url.data;

  if (ngx_strncasecmp(p, (u_char *) "unix:", 5) == 0) {
    return ngx_parse_unix_domain_url(pool, u);
  }

  if (p[0] == '[') {
    return ngx_parse_inet6_url(pool, u);
  }               

  return ngx_parse_inet_url(pool, u);
}

然后是ngx_parse_inet_url方法(core/ngx_inet.c):

static ngx_int_t
ngx_parse_inet_url(ngx_pool_t *pool, ngx_url_t *u)
{
......

  if (ngx_inet_resolve_host(pool, u) != NGX_OK) {
    return NGX_ERROR;
  }

......
}

然后是ngx_inet_resolve_host方法(core/ngx_inet.c):

#if (NGX_HAVE_GETADDRINFO && NGX_HAVE_INET6)                    

ngx_int_t
ngx_inet_resolve_host(ngx_pool_t *pool, ngx_url_t *u)
{
......
  if (getaddrinfo((char *) host, NULL, &hints, &res) != 0) {
    u->err = "host not found";
    ngx_free(host);
    return NGX_ERROR;
  }
......
}

#else /* !NGX_HAVE_GETADDRINFO || !NGX_HAVE_INET6 */

ngx_int_t
ngx_inet_resolve_host(ngx_pool_t *pool, ngx_url_t *u)
{
......
    h = gethostbyname((char *) host);
......
}

思考下如何解决这个问题

最简单的解决方法,我想到如下几种:

执行 nginx reload

这种方法优缺点都很明显:

优点:操作简单。

缺点:属于我们常说的后手,需要做好监控。

配置resolver

可以通过在nginx中配置resolver来动态更新解析,大致做法如下:

server {
    listen   80;
    server_name ligang.gdemo.com;

    resolver 8.8.8.8 valid=60s;
    resolver_timeout 3s;

    set $gproxy "ligang.proxy.gdemo.com";

    location / {
     proxy_pass http://$gproxy;
    }
  }

这个方法优缺点如下:

优点:解析地址每隔一段时间自动更新,无需人工做 nginx reload 。

缺点:需要指定DNS服务器地址,如果这个服务器挂了,或是地址变了,则需要修改nginx配置后reload。

结束语

上面这两个方法是无须额外开发,直接简单可用的,成本上比较低,但都有不完美的地方。

这里我想到是否可以自行开发一个nginx扩展,用来动态更新从DNS获取的IP地址,这样就能解决这个问题了,但有一定的开发成本,但个人觉得对提升技术能力又很有价值。

如果大家有什么好方法,也欢迎来一起讨论。以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Nginx服务器的反向代理proxy_pass配置方法讲解

    就普通的反向代理来讲 Nginx的配置还是比较简单的,如: location ~ /* { proxy_pass http://127.0.0.1:8008; } 或者可以 location / { proxy_pass http://127.0.0.1:8008; } Apache2的反向代理的配置是: ProxyPass /ysz/ http://localhost:8080/ 然而,如果要配置一个相对复杂的反向代理 Nginx相对Apache2就要麻烦一些了 比如,将url中以/wap/开

  • nginx proxy_pass反向代理配置中url后加不加/的区别介绍

    前言 nginx作为web服务器一个重要的功能就是反向代理.nginx反向代理的指令不需要新增额外的模块,默认自带proxy_pass指令,只需要修改配置文件就可以实现反向代理. 而在日常的web网站部署中,经常会用到nginx的proxy_pass反向代理,有一个配置需要弄清楚:配置proxy_pass时,当在后面的url加上了/,相当于是绝对根路径,则nginx不会把location中匹配的路径部分代理走;如果没有/,则会把匹配的路径部分也给代理走(这样配置可以参考这篇文章). 下面举个小实

  • nginx proxy_pass指令’/’使用注意事项

    1. proxy_pass配置说明 不带/ 复制代码 代码如下: location /test/ { proxy_pass http://t6:8300;  } 带/ 复制代码 代码如下: location /test/  {                  proxy_pass http://t6:8300/;   } 上面两种配置,区别只在于proxy_pass转发的路径后是否带 "/" 针对情况1,如果访问url = http://server/test/test.jsp,则被

  • 记一次nginx中proxy_pass的使用问题

    最近排查一个web服务的问题,webserver使用的nginx,最终发现是踩了nginx中proxy_pass的一个坑,这里记录下来. 踩坑经过 一个线上的http服务,示例nginx关键配置如下: server { listen 80; server_name ligang.gdemo.com; server_tokens off; keepalive_timeout 5; charset utf-8; include /home/ligang/devspace/gobox-demo/con

  • nginx中proxy_pass各种用法详解

    目录 代理转发规则 第一种: 第二种: 第三种: 第四种: 第五种: 代理转发规则 nginx中配置location代理转发规则的时候不同写法对应不同转发规则. 如果proxy_pass使用了URI,当传送请求到后端服务器时,规范化以后的请求路径与配置中的路径的匹配部分将被替换为指令中定义的URI(对应下文第一种情况). 如果proxy_pass没有使用URI,传送到后端服务器的请求URI一般客户端发起的原始URI(下文第二种情况). 访问地址:http://localhost/proxy/ab

  • nginx配置proxy_pass中url末尾带/与不带/的区别详解

    nginx配置proxy_pass时url末尾带"/"与不带"/"的区别如下: 注意:当location为正则表达式匹配模式时,proxy_pass中的url末尾是不允许有"/"的,因此正则表达式匹配模式不在讨论范围内.  proxy_pass配置中url末尾带/时,nginx转发时,会将原uri去除location匹配表达式后的内容拼接在proxy_pass中url之后. 测试地址:http://192.168.171.129/test/tes

  • nginx中的两个模块的proxy_pass的区别解析

    1.ngx_stream_proxy_module模块的proxy_pass指令 只能在server段使用使用, 只需要提供域名或ip地址和端口. 可以理解为端口转发,可以是tcp端口,也可以是udp端口. server { listen 127.0.0.1:12345; proxy_pass 127.0.0.1:8080; } server { listen 12345; proxy_connect_timeout 1s; proxy_timeout 1m; proxy_pass exampl

  • Nginx中if语句的判断条件与多条件判断详解

    一.if语句中的判断条件(nginx)介绍 1.正则表达式匹配: ==:等值比较; ~:与指定正则表达式模式匹配时返回"真",判断匹配与否时区分字符大小写: ~*:与指定正则表达式模式匹配时返回"真",判断匹配与否时不区分字符大小写: !~:与指定正则表达式模式不匹配时返回"真",判断匹配与否时区分字符大小写: !~*:与指定正则表达式模式不匹配时返回"真",判断匹配与否时不区分字符大小写: 2.文件及目录匹配判断: -f,

  • 解读nginx中limit配置参数

    本文主要解析一下ngx_http_core_module.ngx_http_limit_conn_module以及ngx_http_limit_req_module中的limit相关配置参数. limit_rate 名称 默认配置 作用域 官方说明 中文解读 模块 limit_rate limit_rate 0; http, server, location, if in location Limits the rate of response transmission to a client.

  • 详解Nginx中的Rewrite的重定向配置与实践

    一:理解地址重写 与 地址转发的含义. 地址重写与地址转发是两个不同的概念. 地址重写 是为了实现地址的标准化,比如我们可以在地址栏中中输入 www.baidu.com. 我们也可以输入 www.baidu.cn. 最后都会被重写到 www.baidu.com 上.浏览器的地址栏也会显示www.baidu.com. 地址转发:它是指在网络数据传输过程中数据分组到达路由器或桥接器后,该设备通过检查分组地址并将数据转发到最近的局域网的过程. 因此地址重写和地址转发有以下不同点: 1. 地址重写会改变

  • 浅谈Nginx 中的两种限流方式

    系统设计时一般会预估负载,当系统暴露在公网中时,恶意攻击或正常突发流量等都可能导致系统被压垮,而限流就是保护措施之一.限流即控制流量,本文将记录 Nginx 的二种限流设置. 生活中的 "限流"? 限流并非新鲜事,在生活中亦无处不在,下面例举一二: 博物馆:限制每天参观总人数以保护文物 高铁安检:有若干安检口,旅客依次排队,工作人员根据安检快慢决定是否放人进去.遇到节假日,可以增加安检口来提高处理能力(横向拓展),同时增加排队等待区长度(缓存待处理任务). 办理银行业务:所有人先领号,

  • 解决Nginx 配置 proxy_pass 后 返回404问题

    一. Nginx 配置 proxy_pass 后 返回404问题 故障解决和定位 1.1. 问题 在一次生产涉及多次转发的配置中, 需求是下面的图: 在配置好了 proxy_pass 之后,请求 www.djx.com 直接返回 404,没有什么其他的异常. 但是我们直接请求后端 www.baidu.com 是正常响应的.这就很怪异的. 看日志请求也是转发到了 www.baidu.com 的.但是请求响应就是404. 1.2. 寻找问题原因 我们的默认的 Nginx的 proxy_set_hea

  • nginx中斜杠(/)详解

    不知大家日常在nginx配置时,是不是会对是否加斜杠充满疑惑? 配置location.proxy_pass时,加“/”与不加“/”的区别,今天我们通过实操去验证下. 通过nginx代理访问地址:http://127.0.0.1/v1/pt/apply/page location.proxy_pass都不加斜杠 location /v1 {     proxy_pass http://127.0.0.1:8899; } 实际访问代理地址:http://127.0.0.1:8899/v1/pt/ap

随机推荐