nginx proxy模式下502 bad gateway 问题

并发测试的时候发现nginx 502 bad gateway 了,看了下日志发现很多upstream Cannot assign requested address的记录
connect() to 192.168.89.170:80 failed (99: Cannot assign requested address) while connecting to upstream

正常判断应该是端口不够用了
不过,我确实开启了: net.ipv4.tcp_tw_recycle = 1 和 net.ipv4.tcp_tw_reuse = 1两个参数
理论上应该可以把timewait 端口重用,查了下这个参数跟tcp_timestamps有关(http://blog.sina.com.cn/s/blog_781b0c850100znjd.html)

if (tmp_opt.saw_tstamp &&
tcp_death_row.sysctl_tw_recycle &&
(dst = inet_csk_route_req(sk, req)) != NULL &&
(peer = rt_get_peer((struct rtable *)dst)) != NULL &&
peer->v4daddr == saddr) {
if (get_seconds() < peer->tcp_ts_stamp + TCP_PAWS_MSL &&
(s32)(peer->tcp_ts – req->ts_recent) >
TCP_PAWS_WINDOW) {
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
goto drop_and_release;
}
}

tmp_opt.saw_tstamp:该socket支持tcp_timestamp
sysctl_tw_recycle:本机系统开启tcp_tw_recycle选项
TCP_PAWS_MSL:60s,该条件判断表示该源ip的上次tcp通讯发生在60s内
TCP_PAWS_WINDOW:1,该条件判断表示该源ip的上次tcp通讯的timestamp 大于 本次tcp

因此: 应该在proxy端和后端都开启net.ipv4.tcp_timestamps=1

nginx with static libcurl

场景是这样子的: 这边有个nginx 模块 include curl/curl.h,而我的编译参数–with-openssl使用了最新的openssl 1.0.1g,编译出来的nginx直接segfault

去除这个模块或者去掉–with-openssl都能正常使用,推测是系统的libcurl(https)包含了libssl的依赖,与内嵌的openssl产生冲突

于是解决办法就是把libcurl也编译到nginx里边,绕开冲突和依赖

1. 静态编译libssl
cd openssl-1.0.1g
./config –prefix=/usr/src/redhat/BUILD/nginx-1.4.7/openssl-1.0.1g/.openssl no-shared no-threads
make
make install
make install LIBDIR=lib

2. 静态编译libcurl
cd curl-7.36.0
./configure –prefix=/usr/src/redhat/BUILD/nginx-1.4.7/curl-7.36.0/.curl –with-ssl=/usr/src/redhat/BUILD/nginx-1.4.7/openssl-1.0.1g/.openssl/lib/ –disable-ldap –disable-ldaps –without-libidn –enable-static=yes –enable-shared=no

#去除对librt.so的依赖,不介意可以不修改
sed -i /HAVE_CLOCK_GETTIME_MONOTONIC/d lib/curl_config.h

make
make install

3. 修改nginx的Makefile
#替换libcurl.so(lcurl)为静态编译的libcurl.a
sed -i ‘s#-lcurl#curl-7.36.0/.curl/lib/libcurl.a -Lopenssl-1.0.1g/.openssl/lib -lcrypto -lz#g’ objs/Makefile
make
make install

做完这步,就生成了包含libcurl和libssl的nginx了

nginx request line parsing vulnerability

CVE-2013-4547

nginx1.4.4和1.5.7版本之前有安全漏洞,会导致可能的绕开防护或者php解析攻击

比如
location ~ \.php$ {
fastcgi_pass …
}

by requesting a file as “/file /0.php”.

实地测试了一番,这个攻击在我们环境中比较难重现,需要以下条件同时成立
1.需要上传了一个带空格的文件(含攻击程序)
2.需要php设置了fix_pathinfo=1(默认为1)
3.需要php版本低于5.3.10,或者允许了所有的security.limit_extensions

因此,基于安全理由,建议升级并且升级php到5.3序列的最新版本

nginx 嵌套 error_page

nginx有时候希望嵌套的处理error_page
比如 location / {
error_page 404 @fetch
}

location @fetch {
error_page 404 @fetch2
proxy_pass http://backend1/one/;
}
location @fetch2 {
proxy_pass http://backend2/two/;
return 200 “xxx”;
}

这是个简单的例子,如果需要实现功能需要两个参数: proxy_intercept_errors on; recursive_error_pages on;
syntax: recursive_error_pages on | off;
default:
recursive_error_pages off;
context: http, server, location
Enables or disables doing several redirects using the error_page directive. The number of such redirects is limited.

syntax: proxy_intercept_errors on | off;
default:
proxy_intercept_errors off;
context: http, server, location
Determines whether proxied responses with codes greater than or equal to 300 should be passed to a client or be redirected to nginx for processing with the error_page directive.

近期协助排查一个故障发现有50x错误,开error日志发现有malloc或者crash work process的记录

[emerg] 5309#0: *288 malloc(808334101) failed (12: Cannot allocate memory) while sending to client
[notice] 4579#0: signal 18 (SIGCHLD) received
[notice] 4579#0: worker process 5211 exited with code 0

这其实是lua模块导致的问题,在0.7.5之前,nginx lua module存在 ngx.req.clear_header 导致的内存溢出问题,需要升级到最新版
that is all,完毕

介绍下igor职业打手Maxim Dounin写的一个gunzip模块

Gunzip module for nginx.
This module allows gunzipping responses returned with Content-Encoding: gzip
for clients that doesn’t support it. It may be usefull if you prefer to store
data compressed (to save space or disk/network IO) but do not want to penalize
clients without gzip support.

Note well: only responses with Content-Encoding set to gzip before this module
are handled (e.g. using “add_header Content-Encoding gzip;” isn’t enough as it
happens after). As of now only proxy and fastcgi are able to do so.

这个模块能针对不支持gzip编码的客户端,直接解压gzip格式的内容
好处:
1.跟源站直接请求压缩的内容,减少回源带宽,提高响应速度
2.只保留一份压缩的内容,减少缓存的大小,相同的cache能放更多的内容
配置格式比较简单:

Configuration directives:

gunzip (on|off)

Context: http, server, location
Default: off

Switches gunzip.

gunzip_buffers

Context: http, server, location
Default: 32 4k/16 8k

Specifies number and size of buffers available for decompression.

Usage:

location /storage/ {
gunzip on;

}

需要特别指出:
1.客户端不支持gzip编码,那么gunzip模块就返回解压的内容
2.客户端支持gzip编码,那么gunzip就自动不起作用,返回原始内容

某些特殊需求,比如addition_filter,我们知道这些filter在非压缩的内容才能正常工作,要结合这个模块就不是那么方便了
因此可以简单修改代码,让它不理会客户端的header,总是返回非压缩内容,注释掉这一整块即可:
ngx_http_gunzip_filter_module.c

141 #if (nginx_version >= 8025 || (nginx_version >= 7065 && nginx_version < 8000)) 142 143 r->gzip_vary = 1;
144
145 if (!r->gzip_tested) {
146 if (ngx_http_gzip_ok(r) == NGX_OK) {
147 return ngx_http_next_header_filter(r);
148 }
149
150 } else if (!r->gzip_ok) {
151 return ngx_http_next_header_filter(r);
152 }
153
154 #else
155
156 if (ngx_http_gzip_ok(r) == NGX_OK) {
157 return ngx_http_next_header_filter(r);
158 }
159
160 #endif

有人写了个gunzip_always的开关补丁,可以控制这个属性,patch暂时不放出来

模块地址: http://mdounin.ru/hg/ngx_http_gunzip_filter_module/

本文已经过时并且存在大量不安全,请参阅最新文档   https://www.4os.org/index.php/category/https/
nginx 默认编译就是支持https的,只需要开启ssl就好
配置如下:
                listen                  443 ;
                server_name             4os.org *.4os.org;

                ssl                     on;
                ssl_certificate         gz.crt;
                ssl_certificate_key     gz.key;
                ssl_session_timeout     5m;
                ssl_protocols           SSLv2 SSLv3 TLSv1;
                ssl_ciphers             ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
                ssl_prefer_server_ciphers       on;
....

其中:
gz.crt是证书颁发机构给的证书(免费ssl-证书/)
gz.key是解密后的私钥

上文中提到的私钥是加密的,可以在startssl的工具箱里边解密,也可以自己做:

openssl  rsa -in gz.pri -out gz.key,输入私钥生成时设置的密码,出来的就是不加密的私钥了,nginx启动也不会要你输入密码了

补充:

1. “SSL_CTX_use_PrivateKey_file fail”之类的错误,通常都是私钥和证书不匹配造成的,请确认你生成证书与私钥匹配

2.firefox证书需要根证书信息:

wget http://www.startssl.com/certs/sub.class1.server.ca.pem

cat sub.class1.server.ca.pem >> gz.crt

停止和重启apache与nginx有些许不同,彼此经验不能照搬

TERM:

两者相同,都是发指令给父进程,父进程立刻尝试杀死所有的子进程并退出

USR1:

nginx的文档说得很简单:reopen the logfile,实际上的操作是master重新打开日志文件,并改变日志文件权限,是worker进程有读写权限,然后发USR1给worker进程重新打开日志文件,这完全不涉及任何worker进程的重新启动

apache则不同,它的父进程会”建议”所有子进程完成当前请求后退出,父进程将重新读取配置文件和日志文件,每个子进程退出后父进程将生成新的子进程

HUP:

nginx收到这个信号会做3个事情:

1.重新读取配置文件

2.使用新的配置启动新的worker进程

3.旧的worker完成当前请求后退出

apache的做法不同,父进程接收到该信号后会跟TERM信号杀掉所有子进程,重新读取配置文件,重新打开日志文件,并声称新的子进程来服务.与TERM信号不同的是,父进程不退出,服务不会中止

这边一直有个php cache的应用,原理是发起一个对自身的请求,保存成静态文件
之前这个应用在apache worker模式跑得很好,换到nginx的fast-cgi 后一直不是很正常

这个问题跟spawn-fcgi的工作原理有关: spawn-fcgi起N个进程,然后FIFO排队处理请求
当有并发>N个PHP CACHE的应用请求过来的时候,php cache的应用会再对自身发起请求,这个请求排在了这些请求的后边,而实际上不会有新的进程来处理这些请求,从而形成死锁,日志中会充斥”upstream timed out” “no live upstreams”

因此,工作模式的排队理论本身决定了这个故障不可避免

解决办法:使用nginx proxy_cache 绕开这个问题,把php cache这个功能交给proxy_cache来完成,nginx的work process遇到阻塞等待的情况,会把这个请求的工作sleep一段时间处理接下来的请求,这个特点明显优于spawn-fcgi的行为

1.建立 /cache/打头的可缓存php location

location ~* ^/cache/(.*\.php)${

}

2. 设置相关的proxy_cache:

1) CACHEZONE:

proxy_cache_path /dev/shm/cache/app.gd.sohu.com levels=1:2 keys_zone=default:100m max_size=4g inactive=20m;

2) CACHE目录:

proxy_read_timeout 5s;
proxy_connect_timeout 5s;
proxy_set_header Host $host;
proxy_cache_use_stale updating;
proxy_cache_key “$host$uri$is_args$args”;
proxy_cache default;

proxy_ignore_headers “Cache-Control”;
proxy_hide_header “Cache-Control”;

proxy_ignore_headers “Expires”;
proxy_hide_header “Expires”;

proxy_hide_header “Set-Cookie”;
proxy_ignore_headers “Set-Cookie”;

add_header Cache-Control max-age=60;

proxy_cache_valid 200 3m;
proxy_cache_valid any 0m;
proxy_temp_path /dev/shm/cache/tmp;

proxy_pass http://serverIP/$1$is_args$args;

3) PURGE设置:

location ~* ^/purge/(.*\.php)$ {
access_log logs/purge_app_access.log sohu;

allow 10.10.0.0/16;
deny all;
set $purge_key “$host/$1$is_args$args”;
proxy_cache_purge default $purge_key;

}

3.需要注意的地方:


1) 影响动态应用能否cache的有几个header:一般是”X-Accel-Redirect”, “X-Accel-Expires”, “Expires” or “Cache-Control” ,跟某些文档提到的cookie是没有关系的,我这里根据自己的应用ignore了 “Expires” 和”Cache-Control” 的header
2) 需要注意,php使用cookie(session也是cookie header实现的一种)来识别用户,如果是私密应用,需要把cookie加入cachekey里边,开放式应用则不必如此
3)设置完毕后可以在访问路径的前边加上/cache/实现php内容的缓存,再加上/purge/就能清除其缓存
4) 需要特别说明,我这边的版本是0.7系列,从测试看1.x系列是会forward cookie的,所以需要抹掉:

proxy_hide_header Set-Cookie;
proxy_ignore_headers Set-Cookie;

今天刚好有人提起limit zone 和limit request 两个模块的问题,这里就顺便讲讲个人的一些看法:

1.limit zone:

This module makes it possible to limit the number of simultaneous connections for the assigned session or as a special case, from one address.

这个模块能针对指定的session限制并发连接数,比如限制来源地址这个特别例子

http {
limit_zone one $binary_remote_addr 10m;

server {
location /download/ {
limit_conn one 1;
}
}
}

这个配置针对 $binary_remote_addr 这个对象做了个10M的limitzone(limitzone用于存储这些变量用于判断是否超过了limit限制,这里是并发1)

2.limit request:

This module allows you to limit the number of requests for a given session, or as a special case, with one address

这个模块允许你限制指定session的请求数量,比如限制来源地址这个特别例子

limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

server {
location /search/ {
limit_req zone=one burst=5;
}

看模块的说明不好懂,看这个配置就明白了,它是控制请求的频率,而不是并发的数量,单个IP burst并发进入请求是5,每秒请求限制是1

需要补充说明的是,这里的例子都是以来源IP来设置的,并非只能使用该变量.
其实指定设置其他的变量,也有不同的效果,比如limitzone,针对localtion 设置个静态变量,就相当于设置了某个路径的总体并发访问量了

朋友测试的报告地址是这个:http://storysky.blog.51cto.com/628458/642970
这个报告有什么问题呢:
1. 首先测试使用的是ab和test.php,这会导致无法正确的看到请求的结果(可以看到测试结果的503和200无规律分布)
2. 把”看来也不一定能限制的住1秒钟1个并发连接”和并发1个链接搞混了,limitzone是并发,不是频率,测试文件的size太小会导致看不出问题
因此,我这边做了个简单的DEMO说明下吧:
1. 测试文件使用了个3.9M的文件,避免第一个请求迅速完成导致并发数无法精确达到预期
2. 对location 限速,limit_rate 10k,理由也同上
测试情况:
1.第一个请求发起:

curl -H”Referer:http://www.sohu.com/” http://www.4os.org/video/other/bnx2.tar.gz -o /dev/null
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
4 3927k 4 168k 0 0 11283 0 0:05:56 0:00:15 0:05:41 11931^C

2.在第一个请求未完成的时候发起第二个请求:毫无疑问的503了

curl -H”Referer:http://www.sohu.com/” http://www.4os.org/video/other/bnx2.tar.gz
503 Service Temporarily Unavailable…

至于limitRequests的测试和说明,那篇文章说得挺好的,burst的解释我也是看了之后才清楚的,表示感谢

PS.最近该把http://wiki.nginx.org/HttpLimitReqModulehttp://wiki.nginx.org/HttpLimitZoneModule
这两个模块的中文WIKI补上…