Nginx+Tomcat偶现502分析

问题

业务为了负载均衡,前面放了个 Nginx,但最近 502 报警有点频繁,影响了 SLA,因此对这个问题做了较深入的研究。

502 Bad Gateway

简单来说就是 Nginx 找不到一个可用的 upstream,可能的原因有:

  1. 压根是配置错误
  2. 连接 upstream server 发生错误/超时
  3. upstream server 到了处理瓶颈

还有一个重点是轮询了 upstream server 后任然没有一个可用的。但是不管什么原因,都能在 Nginx 的 error log 中找到报错详情。

upstream prematurely closed connection

在 Nginx 中找到错误日志(开了 debug):

看了下 Nginx 代码,发现是 c->recv(); 读到的内容为 0,日志中也有显示 recv: fd:65 0 of 4096,说明没有获取到 response

同时也查了 Tomcat access 日志,请求还没到 Tomcat。看情况就像日志里面说的,连接被断掉了。

一开始怀疑是网络原因导致连接断掉了,但出现较频繁,期间内网也无网络故障,应该和网络无关。

再看日志发现报错前后有很多 keepalive 信息,尝试关掉 keepalive,502 就没了,但这会对性能有一定影响,感觉有点因噎废食了,还得继续研究。

keepalive

HTTP 持久连接(HTTP persistent connection,也称作 HTTP keep-alive 或 HTTP connection reuse)是使用同一个 TCP 连接来发送和接收多个 HTTP 请求/应答,而不是为每一个新的请求/应答打开新的连接的方法。

一般 Nginx 会配置 keepalive 以提高性能:

Syntax: keepalive connections;

Activates the cache for connections to upstream servers.

The connections parameter sets the maximum number of idle keepalive connections to upstream servers that are preserved in the cache of each worker process. When this number is exceeded, the least recently used connections are closed.

可以配置每个 worker 对 upstream servers 最大长连接数量。同时这个长连接受 keepalive_requests(默认100) 和 keepalive_timeout(默认60s)配置的影响。

但 keepalive 也有缺陷,会加重 webserver 的负担,因为需要绑定一定数量的线程或者进程来维持长链接。注意 keepalive 并没有连接复用(即同一时间窗口不能处理多个请求,这个在 HTTP/2 中才实现),仅节省了新建/关闭连接的开销,类似连接池了。所以 webserver 一般都有类似 nginx 的 keepalive_requests、keepalive_timeout 配置,让空闲的连接断掉。

查看了 upstream(tomcat) 配置的 timeout 是20s,lighttpd 的 requests 是16,timeout 是 5s,都远小于 nginx 的配置。

原因就清楚了,upstream servers 先断了 keepalive 的长连接,但 nginx 仍使用了这个已经断掉的连接。

至于 nginx 为什么不主动检测一下连接是否可用呢?猜测应该是性能原因,一直检查连接池中的连接是否可用没必要,keepalive 协议本身也没业务心跳啥的。PS:商业版的 ngx_http_upstream_hc_module 可以主动监测(世界加钱可及)。

proxy_next_upstream

那么 Nginx 如何保证高可用呢?答案是重试。这就涉及另一个重要配置 proxy_next_upstream,在什么情况下进行重试,默认为 error timeout。按理说我们这个场景应该会重试,但没有重试。这是因为 Nginx 较新版本(1.9.13)多了个 non_idempotent 配置,默认 POSTLOCKPATCH 等请求不重试,因为这些操作是非幂等操作,会对服务端数据造成影响(比如发消息接口,重试可能会重复发消息)。日志中也发现 502 的全都是 POST 请求。

要完全解决这个问题就需要对 proxy_next_upstream 配置簇深入看看,我们先做个测试。 Continue Reading...

配置Nginx,开启HTTP/2

HTTP/2(超文本传输协议第2版,最初命名为 HTTP 2.0),是在 Google 之前提出的 SPDY 协议的基础上演变而来,相对 HTTP1.1 增加了连接复用、头部压缩、服务端 push 等特性。与 HTTP1.1 完全语义兼容,几乎可以无缝升级。目前主流浏览器都已经支持 HTTP/2 了(IE 自 IE 11 开始支持)。

开源版本的 Nginx 从 1.9.5 版开始支持 HTTP/2,其实配置也很简单,升级 Nginx 到最新版本,然后把之前 HTTPS 配置中的 spdy 改成 http2 就行了(listen 443 ssl http2 default_server),但改了之后发现并没有生效,直接退回到了 HTTP1.1。nginx -V 查看编译参数也带有 --with-http_v2_module,应该没什么问题才对。Google 了一把发现是 OpenSSL 版本的问题,我使用的是 ubuntu 14.04 下的 ppa:nginx/stable 源,这个源上的 Nginx 是在 OpenSSL 1.0.1f 上编译而成的,而 OpenSSL 的这个版本不支持 ALPN,所以无法开启 HTTP/2。

Note that accepting HTTP/2 connections over TLS requires the “Application-Layer Protocol Negotiation” (ALPN) TLS extension support, which is available only since OpenSSL version 1.0.2. Using the “Next Protocol Negotiation” (NPN) TLS extension for this purpose (available since OpenSSL version 1.0.1) is not guaranteed.

知道原因就好办了,先升级本地的 OpenSSL到最新版:

然后替换 Nginx 源为 ppa:ondrej/nginx 重新安装 Nginx:

现在再打开网站,发现 HTTP/2 已经开启成功了。

2016-05-21_233215

当然如果你是 ubuntu 16.04,就没有那么麻烦了,直接安装 Nginx 配置一下就行:https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-with-http-2-support-on-ubuntu-16-04

推荐阅读:

Dropbox 迁移到 HTTP/2 的经验,满满都是干货:https://dropbox.tech/infrastructure/enabling-http2-for-dropbox-web-services-experiences-and-observations

检查网站是否支持 SPDY 或者 HTTP/2 的 Chrome 扩展:HTTP/2 and SPDY indicator

参考资料:

https://www.nginx.com/blog/nginx-1-9-5/

https://serverfault.com/questions/732474/nginx-configured-with-http2-doesnt-deliver-http-2

https://nginx.org/en/docs/http/ngx_http_v2_module.html

为什么我们应该尽快支持 ALPN?https://imququ.com/post/enable-alpn-asap.html

Nginx+PHP-FPM 500 504错误简析

最近新配置的 Nginx + PHP-FPM 环境遇到了几次 Nginx 500(Internal Server Error) 和 504(gateway timeout) 错误。就索性把配置修改的过程记录下来,已备后查。

先说一下 500 错误,这个一般是 PHP 代码错了,看一下 PHP 的 error 日志就清楚了。但有时候是 Nginx 和 PHP-FPM 配置不当导致的,比如说上传文件的场景。我们都知道 php.ini 文件里面 post_max_size 和 upload_max_filesize 两个配置项决定了文件上传的大小。但上调配置后发现大文件上传还是会 500,原来是因为 Nginx 也会限制上传文件的大小,有 client_max_body_size 配置项决定。这里还有一个比较重要的参数:client_body_buffer_size。为了节省资源 Nginx 不会把 request body 里面的数据一口气读到内存中,而是分片处理,这个参数就是设置缓存区的大小(一般也不用改),大于缓冲区大小的数据会以一个个小文件(buffer_size)形式存储到 client_body_temp_path,为了避免高并发下缓存文件冲突和一些文件系统限制(比如 ext3 磁盘格式文件夹下一级文件个数限制为 3.2w 个)以及提高性能可根据业务需要设置多级子目录。还需特别注意的是路径要有被 Nginx 进程写的权限,不然又会 500 错误了。

然后是 504 错误,除了业务代码本身超时更多是配置问题。还是先说 PHP 的配置,很多人都知道 PHP 脚本有执行时间限制(php.ini 中的 max_execution_time),但在 PHP-FPM 却是 php-fpm.conf 中的 request_terminate_timeout 配置项控制的。还有就是 max_input_time 配置项,值比较小的话在接收大文件或大数据量 POST 请求的时候可能会超时,一般不用做改动,因为大部分场景都是上游 web server 接收好数据后传给 PHP-FPM 进程的,传输的非常快,几乎不会超时,但也不能一概而论,要看具体业务场景。然后就是 Nginx 的锅了,刚也说到是 Nginx 接收好(或者说是转发)数据后传给下游,在这个承上启下的环节很容易超时。几个比较重要的配置项:fastcgi_send_timeout 控制向下游(这里是 PHP-FPM)发送数据的超时时间,与之对应的一个配置项是 fastcgi_read_timeout,表示读取下游 response 的超时时间,这里需要注意的是要和前面说的 request_terminate_timeout 保持一致(或大于)。还有一个配置项 fastcgi_connect_timeout,表示和下游 FastCGI 建立连接的超时时间,不过这个值就不用设置的太长了,不然下游某个节点挂了就会夯住 Nginx 的处理线程,影响业务。

我所遇到的场景就是上面这些,可能还有其它一些情况,具体问题具体分析应该也很好解决。

参考资料:

https://nginx.org/en/docs/http/ngx_http_core_module.html

https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html

《PHP-CGI 进程 CPU 100% 与 file_get_contents 函数的关系》 http://zyan.cc/tags/request_terminate_timeout/1/

《PHP超时处理全面总结》 https://segmentfault.com/a/1190000000313184

《PHP file upload affected or not by max_input_time?》 https://stackoverflow.com/questions/11387113/php-file-upload-affected-or-not-by-max-input-time

Nginx HTTPS 配置实践

前言

博客使用 HTTPS 已经有一段时间了,最近把当时的配置过程梳理了一遍,希望能对有需要的同学有所帮助。

概念介绍

最近 HTTPS 是越来越流行了,前一阵子的百度,最近的淘宝、知乎,都已经开启了全站 HTTPS 的时代。HTTPS 即 Hypertext Transfer Protocol Secure,简单理解就是在 HTTP 协议上加了一层加密。具体的细节以后弄透彻了再写写。

无利不起早,那么 HTTPS 的好处是什么呢?我个人觉得有以下几点:

  1. 数据传输加密,防止信息被窃取。使用 HTTP 协议时,用户的密码、银行账户、隐私信息等都是在网络上明文传输,很容易被中间人截取。
  2. 防欺诈。当你使用 EV 级别证书(https://www.wosign.com/EVSSL/index.htm)时,浏览器网址显示和一般 SSL 证书不一样。可以对比下:https://www.paypal.com/signin/ 和 https://www.baidu.com/。使用此类证书的网址,主流的浏览器都会在地址栏显示企业的名称(支持中文),增加了网站信任度,进一步防止用户被钓鱼网站欺骗,当然证书的价格也更贵一些。
  3. 防止劫持。这点可以说是国内各大公司下决心支持 HTTPS 的主要原因了。一旦被劫持,用户访问速度会变慢,看到的内容会被篡改,比如商家在百度投放的广告被替换成竞品的广告,淘宝的商品被带上小尾巴(返利链接),甚至直接跳转到x东。这些不光是损害用户的利益,更是直接影响公司收入。
  4. 使用新的技术。比如 SPDY/HTTP2(头部压缩、连接复用、Server Push等) 的基础都是 HTTPS,新协议对移动端 APP 性能提升帮助很大:《双11手淘前端技术: H5性能最佳实践》

当然 HTTPS 不是银弹,不是用了就保险了,安全是一个整体,这是典型的水桶场景,往往一个系统被攻破是在其薄弱的环节。

证书申请

证书申请的大概流程是,首先在自己的服务器使用 openssl 命令(尽量使用高版本的 OpenSSL,除了能规避漏洞,还能使用更高级的密钥加密/交换算法,提升安全性和性能)生成 csr 和 key 文件,命令如下:

如命令参数所示,私钥 key RSA 的加密强度是 2048 位(rsa:2048)。CSR 文件使用了更安全的 sha256(SHA-2)摘要算法(SHA-1算法在2016年将不被证书厂商和现代浏览器支持)。

-subj 参数指定证书申请者的信息,如果不直接使用此参数,也会有交互式的命令提示填写这些参数。此部分具体参数释义:

Country Name (2 letter code): 输入国家地区代码,例如中国的 CN

State or Province Name (full name): 地区省份

Locality Name (eg, city): 城市名称

Organization Name (eg, company): 公司名称

Organizational Unit Name (eg, section): 部门名称

Common Name (e.g. server FQDN or YOUR name): 申请证书域名,如果是泛域名证书,则应该填写 *.example.com

Email Address: 电子邮箱

命令还会提示输入challenge password,这个不是加密证书,而是相当于一个二次认证密码,如果厂商证书支持challenge password,而且你也设置了的话,后续证书操作需要验证这个密码,提高安全性。

生成的 CSR(Certificate Signing Request,即证书签名请求,不是证书)文件包含了证书的主要信息和公钥,同时生成了私钥 key 文件(注意保存)。然后把 CSR 文件提交给 CA 厂商(不用提交 key 文件),一般提交的时候会让你选择服务器类型,本文指选的 Nginx。中间还会有一个域名所有者验证过程,一般几分钟后(根据证书类型不同,时间不一样)就会发一份邮件给你。

邮件附件一般会带有厂商用 CA 私钥签好的证书文件 example.com.cert.pem(包含 域名、CA 信息,key 对应的公钥,CA 的签名)。同时会给你 CA 的证书链 example.com.cert.ca.pem,如果 CA 证书是多个文件(通常包括 1-2 个中间证书和 1 个根证书),那么需要按照证书信任链由下游到上游,把文件合并成 example.com.cert.ca.pem(cat COMODOECCDomainValidationSecureServerCA.crt COMODOECCAddTrustCA.crt AddTrustExternalCARoot.crt > example.com.cert.ca.pem)。不用把根证书放进 CA 证书链文件里面,因为浏览器和操作系统都内置了根证书,也只认自己内置的根证书。

不过现在更流行的是使用 acme.sh 自动申请和续签 Let’s Encrypt 证书,即免费又好用。acme.sh 提供的方式有很多种,大家按需配置即可,我是 DNSPod + Nginx,仅供参考:

证书处理

后面的步骤各个 HTTP Server 就有点不一样了:

tomcat 则比较特别,证书编码格式需要的不是 pem 而是 JKS,得转换一下。

到这里为止出现了好几个文件,可能有点迷糊,这里总结一下

文件名
释义
example.com.csr
Certificate Signing Request,证书签名请求,不是证书
example.com.key.pem
证书私钥(key),不能泄露(即使是对 CA 厂商),和CSR文件成对存在
example.com.cert.pem
自己的域名证书(pem格式),包含 key 对应的公钥
example.com.cert.ca.pem
CA厂商证书链(pem格式)
example.com.cert.full.pem
合并了自己证书和CA证书的完整证书链(pem格式)
example.com.key.cert.pem
合并了key和域名证书的文件(pem格式)
example.com.cert.pfx
pfx/p12格式的完整证书链(包含了key)
example.com.cert.jks
jks格式(JAVA)的完整证书链(包含了key)

对于这几种文件的区别,可以参考:https://www.cnblogs.com/yjmyzz/p/openssl-tutorial.htmlhttps://blog.freessl.cn/ssl-cert-format-introduce/。实际不用那么麻烦,一般证书厂商会提供不同 webserver 的配置文件,如果没有则需要动手一下,或者使用证书厂商提供的转换工具

TLS握手简述

借由这几个证书文件,简单介绍一下 TLS 的握手过程(RSA 密钥交换方式):

客户端
服务端
ClientHello
明文发送握手请求(上报支持的能力)
生成一个随机数r1
ServerHello
明文返回域名证书(example.com.cert.full.pem)
还会返回一个随机数r2
这里服务端也可以要求客户端提供证书进行校验(流程会有所不同,省略)
对证书进行验证,前面有介绍,证书中包含 CA 信息和签名信息,逐级向上验证即可
ClientKeyExchange
验证通过后从域名证书中拿出公钥,然后使用公钥加密PreMasterSecret(随机生成),发送给服务端(密文)
服务端使用私钥(example.com.key.pem)解密得出PreMasterSecret
客户端使用r1、r2、PreMasterSecret生成session key(会话密钥)
服务端也用同样的方式生成session key,生成算法相同(PRF
使用session key进行对称加密

可以看出核心就是 PreMasterSecret 是密文,其它都是明文。如果中间人想要截取就只能替换证书中的公钥(用自己私钥解密),因为证书本身也有完整性签名,就只能替换整个证书,但证书又是一级级到 Root CA 校验,所以 TLS 握手的结果是安全的。

证书逐级验证的过程(来源:https://security.stackexchange.com/questions/56389/ssl-certificate-framework-101-how-does-the-browser-actually-verify-the-validity

才疏学浅,介绍的非常简陋,建议阅读 https://halfrost.com/https_tls1-2_handshake/ 及系列文章

配置详解

下面正式开始 Nginx 的配置详解。 Continue Reading...