HAProxy的配置和应用

说完了Nginx, 接下来就来看看另外一个性能优越的load balancer – HAProxy吧.

HAProxy

首先要说一下啊, 这个玩意虽然叫做HA, 但是他不是任何高可用服务器, 归结到底 HAProxy只是一个代理服务器罢了. 同时, 也是http层的一个负载均衡调度器. 既然这样, HAProxy为什么要叫做HA呢? 原因很简单, HAProxy能够在http层实现健康状态检测, 从而认为这也是能够提供高可用的一个能力, 从而将自己命名为HAProxy ( 我猜的hhh )

说到代理, 我们说过有正向和反向两种. 由于之前解释过了, 所以这里就不再展开了. 来着重介绍今天的主角-HAProxy. 这个玩意是个非常纯正的http反向代理服务器, 不能进行缓存. 但是也额外支持对TCP通信的负载均衡.

继续说说HAProxy的特性, 由于Haproxy是基于事件驱动和单一进程模式的, 所以省去了进程间切换, 使得支持的并发连接数非常大 ( 但是用户也可以调整成为多进程的 ). 不过, Haproxy对于应用层的内容处理和动态资源和静态资源分离是做不到了.

但是由于是单一进程, 所以所有的连接会话都由一个进程维护, 如果会话非常多, 那么存储的数据结构就是一个很重要的考虑部分. 不同的数据结构会很大程度上影响到检索存储在内存中的会话节点, 而且还会有增加, 删除, 修改的操作. HAProxy选择的数据结构是弹性二叉树. 由HAProxy作者自己研发的数据结构.

还是直接装上看看吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@VM-node1 ~]# rpm -ql haproxy
/etc/haproxy
/etc/haproxy/haproxy.cfg
/etc/logrotate.d/haproxy
/etc/sysconfig/haproxy
/usr/bin/halog
/usr/bin/iprange
/usr/lib/systemd/system/haproxy.service
/usr/sbin/haproxy
/usr/sbin/haproxy-systemd-wrapper
...(omitted)
/usr/share/haproxy
/usr/share/haproxy/400.http
/usr/share/haproxy/403.http
/usr/share/haproxy/408.http
/usr/share/haproxy/500.http
/usr/share/haproxy/502.http
/usr/share/haproxy/503.http
/usr/share/haproxy/504.http
/usr/share/haproxy/README
/usr/share/man/man1/halog.1.gz
/usr/share/man/man1/haproxy.1.gz
/var/lib/haproxy

从生成的文件也可以看出, haproxy是一个十分轻量化的程序, 除了配置文件和主程序其他就没有什么了.

先来从配置文件入手,

1
2
3
4
5
6
[root@VM-node1 haproxy]# grep "^[^#][^[:space:]].*$" haproxy.cfg
global
defaults
frontend main *:5000
backend static
backend app

这就是配置文件的组成部分.

其实很好理解层次结构, 例如前端(也就是自己了), 后端静态服务器组, 后端动态服务器组啥啥的.

我们在Nginx中 是用了什么proxy_pass配合upstream来进行的调度, 而haproxy更好理解了, 直接使用frontend, backend来定义. 通过在前端中定义使用那些后端服务器组来建立联系, 另外这种联系还支持条件式建立, 还可以配置默认连接. default就是为frontend和backend设置的各个选项了. 总之, 熟悉了Nginx的我们, 现在肯定能炒鸡快速的上手Haproxy的.

现在就直接来试试吧, 还是想之前那样, 我们开启两台Web服务器.

1
2
3
4
5
6
7
frontend  main *:80
default_backend webservers

backend webservers
balance roundrobin
server static 192.168.206.21:80 check
server static 192.168.206.22:80 check

这就是最简单的一个配置了, 我们启动haproxy服务, 访问试试.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
C:\Users\lenovo
λ curl http://192.168.206.9/
<h1>This is node2(NEW)</h1>

C:\Users\lenovo
λ curl http://192.168.206.9/
<h1>It works! (From node3)</h1>

C:\Users\lenovo
λ curl http://192.168.206.9/
<h1>This is node2(NEW)</h1>

C:\Users\lenovo
λ curl http://192.168.206.9/
<h1>It works! (From node3)</h1>

由于我们使用了roundrobin算法, 所以是轮询访问.

HAProxy的配置

现在就来好好看看HAProxy的配置项吧, 首先第一行就是日志记录的配置. HAProxy和其他的不太一样, 他只能基于网络进行日志记录 :

1
2
global
log 127.0.0.1 local2

所以, 为了使得日志能够记录下去. 我们还要配置一下rsyslog才行:

1
2
3
4
5
# Provides UDP syslog reception
$ModLoad imudp
$UDPServerRun 514
...(omitted)
local2.* /var/log/haproxy.log

首先开启监听, 接着要把我们的haproxy配置日志路径.

接着重启服务, 访问一下.

1
2
3
4
5
6
7
[root@VM-node1 haproxy]# tail /var/log/haproxy.log 
Oct 19 23:09:12 localhost haproxy[2764]: Proxy main started.
Oct 19 23:09:12 localhost haproxy[2764]: Proxy webservers started.
Oct 19 23:09:25 localhost haproxy[2765]: 192.168.206.1:5842 [19/Oct/2017:23:09:25.979] main webservers/static 0/0/2/5/7 200 319 - - ---- 1/1/0/1/0 0/0 "GET / HTTP/1.1"
Oct 19 23:09:26 localhost haproxy[2765]: 192.168.206.1:5843 [19/Oct/2017:23:09:26.645] main webservers/static 0/0/1/2/3 200 298 - - ---- 1/1/0/0/0 0/0 "GET / HTTP/1.1"
Oct 19 23:09:27 localhost haproxy[2765]: 192.168.206.1:5844 [19/Oct/2017:23:09:27.228] main webservers/static 0/0/1/2/3 200 319 - - ---- 1/1/0/0/0 0/0 "GET / HTTP/1.1"
Oct 19 23:09:27 localhost haproxy[2765]: 192.168.206.1:5845 [19/Oct/2017:23:09:27.809] main webservers/static 0/0/1/3/4 200 298 - - ---- 1/1/0/0/0 0/0 "GET / HTTP/1.1"

这样就有日志了.

接着往后看一下:

1
2
3
4
5
6
7
8
9
10
11
global
log 127.0.0.1 local2 # 日志

chroot /var/lib/haproxy # 根切换, 这样更加安全
pidfile /var/run/haproxy.pid # pid文件
maxconn 4000 # 最大连接数
user haproxy # 运行用户
group haproxy # 运行组
daemon # 标记为后台守护进程

stats socket /var/lib/haproxy/stats # 访问本地的时候基于共享内存进行socket通信

但是, 可以使用的参数还有很多. 比如quiet, debug, 还可以调整连接参数, 运行参数等等. 但是很多参数都不建议修改, 可能会用到的参数很少, 例如: 上面列出来的这些, 还有spread-check: 这个是用来修改健康状态检查的延时的 ( 因为大量同时的检查可能会拥塞网络, 这个选项可以进行随机的后退. )

balance

首先来看balance关键字. 这个是用来指定调度算法的, HAProxy支持的调度算法也很多, 并且也分成动态和静态两类. HAProxy理解的动态静态是根据是否可进行权重的改变. 能够则被认为是动态的.

  • roundrobin: 轮询, 动态算法, 会追踪后端主机的连接, 所以每个主机最大支持4192条连接.
  • static-rr: 静态的轮询算法.
  • least-conn: 最小连接, 适合长连接场景.
  • source: 源地址哈希, 默认是静态算法. 但是可以根据哈希算法来改变
    • map-based 除模取余
    • consistent: 一致性哈希
  • url: 这是一个对HAProxy而言, 很有竞争力的算法. 这个算法就是将请求的URL的前半段或者全部进行Hash运算, 接着和服务器的总权重进行运算最终得到rs. 这样的一个应用就是缓存服务器. 不管来源是什么, 只要你请求那个缓存的内容, 我就把你调度到缓存服务器那里去. 这个算法也一样可以进行两种hash_type.

这里我做了个测试, 我们在后端的两个主机都新建了10个页面, 接着访问几个, 会发现一旦第一次被定向到了哪一个主机, 就会被绑定到那一台. 这个时候我们关闭Web2的httpd服务, 于是再次访问, 一开始堵塞了一会, 接着就访问到了Web1的页面, 另外其他原本是Web2的页面也立即就被转向到了Web1的页面. 然而, 当服务恢复了之后, 立即被定向到了原本的页面, 恢复绑定了.

1
[root@VM-node3 ~]# for n in [1..10]; do echo "<h1>Page $n on Web2</h1>" > /var/www/html/test$n.html ; done
  • url_param: 也是一个从URL中计算哈希的方法, 通过取得URL中被赋值的键的值进行运算接着除以总权重得到的结果. 同样也是支持两种哈希类型.
  • hdr(<name>): 可以根据请求报文中的指定首部进行调度, 强大吧. 比如host, referer, user-agent等等.

做个实验吧, 配置成

1
2
3
4
5
backend webservers
balance hdr(User-Agent)
hash-type consistent
server static 192.168.206.21:80 check
server static 192.168.206.22:80 check

接着访问:

1
2
3
4
5
6
7
8
9
10
11
12
[root@VM-node1 haproxy]# curl http://192.168.206.9
<h1>This is node2(NEW)</h1>
[root@VM-node1 haproxy]# curl http://192.168.206.9 -A "Hello"
<h1>This is node2(NEW)</h1>
[root@VM-node1 haproxy]# curl http://192.168.206.9 -A "Hello Hi"
<h1>It works! (From node3)</h1>
[root@VM-node1 haproxy]# curl http://192.168.206.9 -A "Hello Hi1"
<h1>It works! (From node3)</h1>
[root@VM-node1 haproxy]# curl http://192.168.206.9 -A "Hello Hi~~"
<h1>This is node2(NEW)</h1>
[root@VM-node1 haproxy]# curl http://192.168.206.9 -A "Hello hhh"
<h1>It works! (From node3)</h1>

bind

bind就是监听的套接字位置, 其实和我们在frontend后面加上的那个玩意是一个东西, bind可以出现多次.

1
2
3
4
frontend  main 
bind *:80
bind 192.168.206.9:8080
default_backend webservers

mode

指明haproxy运行的模式, 一共支持三种: { http | tcp | health }

1
2
3
defaults
mode http
...(omitted)

log

定义日志, 单个实例最多定义两个. 而且如果在global中定义了两个, 后面的都会被忽略.

1
log <address><facility>[<level>[<minlevel>]]

也可以在后面导向global的定义:

1
log global

maxconn

设定前端的最大连接数, 如果在global中写, 那就说明整个HAProxy的前端最大并发, 如果在单个实例中写, 最终就会加起来算作整个服务器的前端最大并发. 每个连接大致是17Kb的大小

default_backend

默认的后端服务器. 一个使用案例:

1
2
3
use_backend dynamic if url_dyn
use_backend static if url_img url_css
default_backend dynamic

这里的use_backend是另外一个指令, 和后面的连用. 只要匹配到请求的URL符合后面定义的ACL中, 就会使用那个后端服务器组. 如果都没有匹配的到, 就会使用最后的那个后端服务器组.

server

这个server参数非常重要, 支持超多的选项, 格式如下:

1
server <name> <address>[:port] [param*...]

前面都很好理解, 主要是后面的参数, 我们提取几个重要的来看一下:

backup:设定为备用服务器,仅在负载均衡场景中的其它server均不可用于启用此server;
check:启动对此server执行健康状态检查,其可以借助于额外的其它参数完成更精细的设定,如:

  • inter <delay>:设定健康状态检查的时间间隔,单位为毫秒,默认为2000;也可以使用fastinter和downinter来根据服务器端状态优化此时间延迟;
  • rise <count>:设定健康状态检查中,某离线的server从离线状态转换至正常状态需要成功检查的次数;
  • fall <count>:确认server从正常状态转换为不可用状态需要检查的次数;

cookie <value>:为指定server设定cookie值,此处指定的值将在请求入站时被检查,第一次为此值挑选的server将在后续的请求中被选中,其目的在于实现持久连接的功能;
maxconn <maxconn>:指定此服务器接受的最大并发连接数;如果发往此服务器的连接数目高于此处指定的值,其将被放置于请求队列,以等待其它连接被释放;
maxqueue <maxqueue>:设定请求队列的最大长度;
observe <mode>:通过观察服务器的通信状况来判定其健康状态,默认为禁用,其支持的类型有“layer4”和“layer7”,“layer7”仅能用于http代理场景;
redir <prefix>:启用重定向功能,将发往此服务器的GET和HEAD请求均以302状态码响应;需要注意的是,在prefix后面不能使用/,且不能使用相对地址,以免造成循环;例如:

1
server srv1 172.16.100.6:80 redir http://imageserver.justin.com check

weight <weight>:权重,默认为1,最大值为256,0表示不参与负载均衡;

另外 在进行健康检查的时候, 我们还可以更加细致的定义:

1
2
3
4
backend https_relay
mode tcp
option httpchk OPITONS * HTTP/1.1\r\nHost:\ www.yaoxuannn.com
server httpd1 192.168.1.1:443 check port 80 inter 1000

option

httplog, 如果使用的模式是httpd, 就可以开启这个选项. 该选项会将日志的记录格式变得十分详细.

logasap, 用于如果传输的数据较大, 日志记录会有延时, 这个选项可以提前记录日志,

forwardfor [except <network>][header <name>][if-none], 在首部中添加X-Forwarded-For

http-server-close 可以允许服务器端(HAProxy)主动关闭连接, 会在头部加入Connection: close的标识, 有些服务器见到这个标识 会返回一个不可用的数据. 这里是指HAProxy -> 后端服务器.

http-pretend-keepalive 字面意思了, 在头部加入Connection: Keep-Alive的标识. 经常和上面的选项连用.

redisatch 如果一台服务器宕机, 会进行重新分发

接下来我们说说访问控制:

http-request

格式如下:

1
http-request { allow | deny | auth [realm <realm>]} [ {if | unless} <condition> ]

不过这里的定义会用到acl指令, 上面也略微提过, 其实很好理解的: [ src就是说源地址嘛 ]

1
2
3
4
5
6
acl nagios src 192.168.129.3
acl local_net src 192.168.0.0/16
acl auth_ok http_auth(L1)
http-request allow if nagios
http-request allow if local_net auth_ok
http-request deny

redirect & acl

除了指明来源, acl还可以这么写:

1
2
3
4
5
6
acl login dst_port 8080
acl secure dst_port 80
acl login_page url_beg /login
acl logout url_beg /logout
acl uid_given url_reg /login?uid=[^&]+
acl cookie_set hdr_sub(cookie) SEEN=1

最后一个的意思是说, 取头部的cookie字串.

结合这些我们就可以做到灵活的重定向请求:

1
2
3
redirect prefix https://yaoxuannn.com set-cookie SEEN=1 if !cookie_set
redirect prefix https://yaoxuannn.com if login_page !secure
redirect location / clear-cookie USERID= if logout

不止这些, acl还可以更加强大. 我们可以使用下面的acl进行会话速率的检测:

1
2
acl begin_scanned be_sess_rate gt 50
redirect locaiton /error_pages/denied.html if begin_scanned

be_sess_rate就是说每秒创建的会话数.

还可以使用method匹配方法, 还可以仅匹配

reqadd rspadd

这两个选项可以在头部添加内容, 同样支持if条件判断.

另外, 还有很多超时选项:

timeout

http-request

connect

http-keep-alive

…(omitted)

基于Cookie实现单客户端绑定

现在我们来使用cookie+HAProxy实现一下单客户端绑定.

首先我们做如下配置:

1
2
3
4
5
backend webservers
balance roundrobin
cookie SERVERID insert
server static1 192.168.206.21:80 check cookie web1
server static2 192.168.206.22:80 check cookie web2

然后我们重启服务, 第一次访问:

cookiereq

可以看到服务器返回了一个设置Cookie的字段, 接着第二次访问:

cookieres

这个时候, 我们无论访问什么URL都会被带到这个服务器上, 因为我们是带着Cookie访问的.

很简单吧.

HAProxy_mind.png

AProxy_mind.png)