BlueXIII's Blog

热爱技术,持续学习

0%

通过Cookie Insertion实现Nginx会话保持

Nginx负载均衡的几种策略

使用开源版本的Nginx可以轻松实现7层(HTTP)或4层(TCP)的负载均衡。

当做为7层负载时,最常用的有以下几种策略:

  • 轮询 round-robin — requests to the application servers are distributed in a round-robin fashion 依次把客户端的请求分发到不同的后端服务器上
  • 最少连接 least-connected — next request is assigned to the server with the least number of active connections 请求会被转发到连接数最少的服务器上
  • IP哈希 ip-hash — a hash-function is used to determine what server should be selected for the next request (based on the client’s IP address) 同一客户端(IP)的连续的请求都会被分发到固定的一台后端服务器上

前两种方案(轮询和最少连接)同一客户端的请求会被随机分配后不同的后端服务器上,如果后端是传统的有状态的WEB应用,则需要在后端WEB容器上配置Session共享,非常麻烦。

后一种方案(IP哈希)可以识别请求端的IP,固定将其分配到某一台后端服务器上,轻松解决了会话保持的问题。但IP哈希存在一个比较严重缺陷,即:客户端必须能够直连Nginx服务器,他们之间不能再插入其它层级,否则Nginx就识别不到客户端的IP了。

那除了IP HASH外,还有没有其它方式能够进行会话保持呢?

以下是官网的关于Cookie Insertion的介绍:

Cookie Insertion:NGINX Plus adds a session cookie to the first response from the upstream group to a given client, identifying which server generated the response (in an encoded fashion). Subsequent requests from the client include the cookie value and NGINX Plus uses it to route the request to the same upstream server:

Cookie植入:

  1. 客户端第一次访问时,负载均衡服务在返回请求中植入cookie(在HTTP/HTTPS响应报文中插入)
  2. 下次客户端携带此cookie访问,负载均衡服务会将请求定向转发给之前记录到的后端服务器上。

Cookie植入原理上就是劫持HTTP请求,伪造cookie,它可以在Nginx无法获取客户端IP时,也依然能够完成会话保持的功能,只要客户端支持Cookie即可。

购买并使用Nginx Plus

要使用Cookie Insertion,最简便的方式是使用商业版本的Nginx Plus,默认支持。

以下是官网关于会话保持的介绍:
https://www.nginx.com/products/session-persistence/

Cookie Insertion的配置非常简单:

1
2
3
4
5
upstream backend {
server webserver1;
server webserver2;
sticky cookie srv_id expires=1h domain=.example.com path=/;
}

于是顺道了解了一下Nginx Plus的来龙去脉:

  • 2002年,来自俄罗斯的Igor Sysoev使用C语言开发了NGINX;
  • 2004年,NGINX开放源码,基于BSD开源许可。
  • 2006年 - 2016年,NIGNX Rlease版本历经0.5, 0.6, 0.7, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 1.9, 1.10, 1.11, 当前最新版本为1.11.3;
  • 2011年,Sysoev成立了NGINX公司,NGINX PLUS是其第一款产品,也是NGINX的商业版本;
  • 2013年 - 2016年,自NGINX Plus Initial Release (R1)版本发布以来,NGINX商业版本已更新至NGINX Plus Release 9 (R9);

NGINX Plus is the all-in-one application delivery platform for the modern web.
All-In-One的NGINX PLUS对比开源版本重点增加了若干企业特性,包括更完善的七层、四层负载均衡,会话保持,健康检查,实时监控和管理等。

可看了一下最便宜的BASIC版本也要2500刀每年,泪奔,果断PASS。
https://www.nginx.com/products/buy-nginx-plus/

使用第三方模块nginx-sticky-module

阿里云SLB的Cookie植入功能,猜测应该是自已在Nginx基础上实现的,貌似没有开源。

流传最广的是一个名为 nginx-sticky-module 的第3方的模块 ,不过已经有多年没有维护。

GoogleCode页面(需扶墙): https://code.google.com/archive/p/nginx-sticky-module/downloads

下载 nginx-sticky-module-1.1.tar.gz ,看看能否配合较新的 nginx-1.12.1 使用。

Nginx下载地址: https://nginx.org/en/download.html

选择Stable version中的nginx-1.12.1

简单编译一下试试:

1
2
./configure --prefix=/nginx/nginx-1.12.1 --add-module=/nginx/src/nginx-sticky-module-1.1  
make && make install

果然报错:

1
2
3
4
5
/nginx/src/nginx-sticky-module-1.1/ngx_http_sticky_module.c: In function ‘ngx_http_get_sticky_peer’:
/nginx/src/nginx-sticky-module-1.1/ngx_http_sticky_module.c:333: error: assignment makes pointer from integer without a cast
make[1]: *** [objs/addon/nginx-sticky-module-1.1/ngx_http_sticky_module.o] Error 1
make[1]: Leaving directory `/nginx/src/nginx-1.12.1'
make: *** [build] Error 2

代码太老旧,没有细查的必要了,弃用。

使用第三方模块nginx-sticky-module-ng

另外还有一个比较新的模块 nginx-sticky-module-ng ,源码托管在BitBucket上:
https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng/

在Downloads/Tags下,找到1.2.6的源码并下载,或者直接下载Master分支上最新的代码。

在编译之前,首先来动手修复一个BUG。。。
找到ngx_http_sticky_misc.c,添加以下两个include:

1
2
#include <openssl/sha.h>
#include <openssl/md5.h>

第三方的东西凑和着用吧。。。

编译一下试试:

1
2
./configure --prefix=/nginx/nginx-1.12.1 --add-module=/nginx/src/nginx-sticky-module-ng
make && make install

编译很顺利,配置一下试试吧:
配置上非常简单,只需要在upstream{}中加入sticky即可。

1
2
3
upstream {
sticky;
}

Sticky的详细配置说明:
sticky [name=route] [domain=.foo.bar] [path=/] [expires=1h]
[hash=index|md5|sha1] [no_fallback] [secure] [httponly];

  • name: the name of the cookies used to track the persistant upstream srv; default: route
  • domain: the domain in which the cookie will be valid default: nothing. Let the browser handle this.
  • path: the path in which the cookie will be valid default: /
  • expires: the validity duration of the cookie default: nothing. It’s a session cookie. restriction: must be a duration greater than one second
  • hash: the hash mechanism to encode upstream server. It cant’ be used with hmac. default: md5
  • md5|sha1: well known hash
  • index: it’s not hashed, an in-memory index is used instead, it’s quicker and the overhead is shorter Warning: the matching against upstream servers list is inconsistent. So, at reload, if upstreams servers has changed, index values are not guaranted to correspond to the same server as before! USE IT WITH CAUTION and only if you need to!
  • hmac: the HMAC hash mechanism to encode upstream server It’s like the hash mechanism but it uses hmac_key to secure the hashing. It can’t be used with hash. md5|sha1: well known hash default: none. see hash.
  • hmac_key: the key to use with hmac. It’s mandatory when hmac is set default: nothing.
  • no_fallback: when this flag is set, nginx will return a 502 (Bad Gateway or Proxy Error) if a request comes with a cookie and the corresponding backend is unavailable.
  • secure enable secure cookies; transferred only via https
  • httponly enable cookies not to be leaked via js

下面给出一个完整的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
worker_processes  4;

events {
worker_connections 1024;
}

http {
upstream proxy_admin {
#ip_hash;
sticky;
server 192.168.1.61:19022 weight=10 max_fails=3 fail_timeout=20s;
server 192.168.1.62:19022 weight=10 max_fails=3 fail_timeout=20s;
}

server {
listen 29022;
server_name proxy_admin;
location / {
root html;
proxy_pass http://proxy_admin;
proxy_next_upstream error timeout invalid_header http_500 http_503 http_404;
proxy_redirect off;
proxy_ignore_client_abort on;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 60;
proxy_send_timeout 60;
proxy_read_timeout 60;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
client_body_buffer_size 128k;
}
}
}

测试,用浏览器访问一下试试:
http://IP:29022/mobagent-admin/
如果在控制台中的Cookie中看到一个名为route的键,就说明基本上成功了。
然后多请求几次,看看后端服务的日志,最终确认一下,是不是只指向了同一台服务器。

注意事项

最后,请注意,因为nginx-sticky-module-ng的可靠性未经长时间验证,请勿直接用于生产系统。