Haproxy及安装配置

简介

  1. HAProxy 提供高可用性、负载均衡以及基于 TCP 和 HTTP 应用的代理,支持 http 的虚拟主机,它是免费、快速并且可靠的一种解决方案。HAProxy 特别适用于那些负载特大的 web 站点,这些站点通常又需要会话保持或七层处理 HAProxy 运行在时下的硬件上,完全可以支持数以万计的并发连接。并且它的运行模式使得它可以很简单安全的整合进您当前的架构中, 同时可以保护你的 Web 服务器不被暴露到网络上。
  2. HAProxy 实现了一种 ==事件驱动==、单一进程 模型此模型支持非常大的并发连接数。多进程或多线程模型受内存限制 、系统调度器限制以及无处不在的锁限制,很少能处理数千并发连接。事件驱动模型因为在有更好的资源和时间管理的用户端(User-Space) 实现所有这些任务,所以没有这些问题。此模型的弊端是,在多核系统上,这些程序通常扩展性较差。这就是为什么他们必须进行优化以 使每个 CPU 时间片(Cycle)做更多的工作。
  3. HAProxy 支持连接拒绝 : 因为维护一个连接的打开的开销是很低的,有时我们很需要限制攻击蠕虫(attack bots),也就是说限制它们的连接打开从而限制它们的危害。 这个已经为一个陷于小型 DDoS 攻击的网站开发了而且已经拯救了很多站点,这个优点也是其它负载均衡器没有的。
  4. HAProxy 支持全透明代理(已具备硬件防火墙的典型特点): 可以用客户端IP地址或者任何其他地址来连接后端服务器. 这个特性仅在 Linux 2.4/2.6 内核打了 cttproxy 补丁后才可以使用。这个特性也使得为某特殊服务器处理部分流量同时又不修改服务器的地址成为可能。

主要版本

1.4:提供较好的弹性:衍生于1.2版本,并提供了额外的新特性,其中大多数是期待已久的。

  • 客户端侧的长连接(client-side keep-alive)
  • TCP加速(TCP speedups)
  • 响应池(response buffering)
  • RDP协议
  • 基于源的粘性(source-based stickiness)
  • 更好的统计数据接口(a much better stats interfaces)
  • 更详细的健康状态检测机制(more verbose health checks)
  • 基于流量的健康评估机制(traffic-based health)
  • 支持HTTP认证
  • 服务器管理命令行接口(server management from the CLI)
  • 基于ACL的持久性(ACL-based persistence)
  • 日志分析器

1.3:内容交换和超强负载:衍生于1.2版本,并提供了额外的新特性。

  • 内容交换(content switching):基于任何请求标准挑选服务器池;
  • ACL:编写内容交换规则;
  • 负载均衡算法(load-balancing algorithms):更多的算法支持;
  • 内容探测(content inspection):阻止非授权协议;
  • 透明代理(transparent proxy):在Linux系统上允许使用客户端IP直接连入服务器;
  • 内核TCP拼接(kernel TCP splicing):无copy方式在客户端和服务端之间转发数据以实现数G级别的数据速率;
  • 分层设计(layered design):分别实现套接字、TCP、HTTP处理以提供更好的健壮性、更快的处理机制及便捷的演进能力;
  • 快速、公平调度器(fast and fair scheduler):为某些任务指定优先级可实现理好的QoS;
  • 会话速率限制(session rate limiting):适用于托管环境;

性能

可以从三个因素来评估负载均衡器的性能:

1、会话率
2、会话并发能力
3、数据率

HAProxy 借助于 OS 上几种常见的技术来实现性能的最大化。

1、单进程、事件驱动模型显著降低了上下文切换的开销及内存占用。

2、O(1)事件检查器(event checker)允许其在高并发连接中对任何连接的任何事件实现即时探测。

3、在任何可用的情况下,单缓冲(single buffering)机制能以不复制任何数据的方式完成读写操作,这会节约大量的CPU时钟周期及内存带宽;

4、借助于Linux 2.6 (>= 2.6.27.19)上的splice()系统调用,HAProxy可以实现零复制转发(Zero-copy forwarding),在Linux 3.5及以上的OS中还可以实现零复制启动(zero-starting);

5、MRU内存分配器在固定大小的内存池中可实现即时内存分配,这能够显著减少创建一个会话的时长;

6、树型存储:侧重于使用作者多年前开发的弹性二叉树,实现了以O(log(N))的低开销来保持计时器命令、保持运行队列命令及管理轮询及最少连接队列;

7、优化的HTTP首部分析:优化的首部分析功能避免了在HTTP首部分析过程中重读任何内存区域;

8、精心地降低了昂贵的系统调用,大部分工作都在用户空间完成,如时间读取、缓冲聚合及文件描述符的启用和禁用等;

所有的这些细微之处的优化实现了在中等规模负载之上依然有着相当低的 CPU 负载,甚至于在非常高的负载场景中, 5% 的用户空间占用率和 95% 的系统空间占用率也是非常普遍的现象,这意味着 HAProxy 进程消耗比系统空间消耗低 20 倍以上。因此,对 OS 进行性能调优是非常重要的。即使用户空间的占用率提高一倍,其 CPU 占用率也仅为 10% ,这也解释了为何 7 层处理对性能影响有限这一现象。由此,在高端系统上 HAProxy 的 7 层性能可轻易超过硬件负载均衡设备。

在生产环境中,在 7 层处理上使用 HAProxy 作为昂贵的高端硬件负载均衡设备故障故障时的紧急解决方案也时长可见。硬件负载均衡设备在“报文”级别处理请求,这在支持跨报文请求(request across multiple packets)有着较高的难度,并且它们不缓冲任何数据,因此有着较长的响应时间。对应地,软件负载均衡设备使用 TCP 缓冲,可建立极长的请求,且有着较大的响应时间。

安装配置 HAProxy

版本信息:

  • 系统:CentOS Linux release 7.4.1708 (Core)
  • 内核:4.14.8-1.el7.elrepo.x86_64 并启用了 BBR 算法
  • Haproxy:haproxy-1.7.9

编译安装

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
yum -y install openssl openssl-devel
cd ~
wget http://www.haproxy.org/download/1.7/src/haproxy-1.7.9.tar.gz
tar xf haproxy-1.7.9.tar.gz -C /usr/local/src/
cd /usr/local/src/haproxy-1.7.9/
cat README

make TARGET=linux2628 USE_OPENSSL=1 ARCH=x86_64 PREFIX=/opt/haproxy
make install PREFIX=/opt/haproxy
# 编译安装

install -d /usr/sbin
install haproxy /usr/sbin/
install haproxy-systemd-wrapper /usr/sbin
# 将make好的haproxy拷贝到指定目录

install -d /usr/local/share/man/man1
install -m 644 doc/haproxy.1 /usr/local/share/man/man1
# 生成 man 文档

cp -ar /opt/haproxy/doc/haproxy /usr/local/doc/haproxy
# 生成doc

mkdir -pv /var/lib/haproxy
# 这个目录是Haproxy配置中chroot要用到的配置

运行 haproxy 命令测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
HA-Proxy version 1.7.9 2017/08/18
Copyright 2000-2017 Willy Tarreau <willy@haproxy.org>

Usage : haproxy [-f <cfgfile|cfgdir>]* [ -vdVD ] [ -n <maxconn> ] [ -N <maxpconn> ]
[ -p <pidfile> ] [ -m <max megs> ] [ -C <dir> ] [-- <cfgfile>*]
-v displays version ; -vv shows known build options.
-d enters debug mode ; -db only disables background mode.
-dM[<byte>] poisons memory with <byte> (defaults to 0x50)
-V enters verbose mode (disables quiet mode)
-D goes daemon ; -C changes to <dir> before loading files.
-q quiet mode : don't display messages
-c check mode : only check config files and exit
-n sets the maximum total # of connections (2000)
-m limits the usable amount of memory (in MB)
-N sets the default, per-proxy maximum # of connections (2000)
-L set local peer name (default to hostname)
-p writes pids of all children to this file
-de disables epoll() usage even when available
-dp disables poll() usage even when available
-dS disables splice usage (broken on old kernels)
-dR disables SO_REUSEPORT usage
-dr ignores server address resolution failures
-dV disables SSL verify on servers side
-sf/-st [pid ]* finishes/terminates old pids.

环境与配置

在编译后的源码包目录下的 examples 目录下有一些可供参考的 init 脚本、配置文件、vim 配置等。我们来创建主配置文件 /etc/haproxyhaproxy.cfg

1
2
mkdir /etc/haproxy/
vim /etc/haproxy/haproxy.cfg

沿用旧版本的配置模板

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#---------------------------------------------------------------------
# Example configuration for a possible web application. See the
# full configuration options online.
#
# http://haproxy.1wt.eu/download/1.4/doc/configuration.txt
#
#---------------------------------------------------------------------

#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
# to have these messages end up in /var/log/haproxy.log you will
# need to:
#
# 1) configure syslog to accept network log events. This is done
# by adding the '-r' option to the SYSLOGD_OPTIONS in
# /etc/sysconfig/syslog
#
# 2) configure local2 events to go to the /var/log/haproxy.log
# file. A line like the following can be added to
# /etc/sysconfig/syslog
#
# local2.* /var/log/haproxy.log
#
log 127.0.0.1 local2

chroot /var/lib/haproxy
# 这个目录需要手动创建
pidfile /var/run/haproxy.pid
maxconn 4000
user nobody
group nobody
# 用户和组使用nobody,省去创建系统用户
daemon

# turn on stats unix socket
stats socket /var/lib/haproxy/stats

#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout http-keep-alive 10s
timeout check 10s
maxconn 3000

#---------------------------------------------------------------------
# main frontend which proxys to the backends
#---------------------------------------------------------------------
frontend main *:5000
acl url_static path_beg -i /static /images /javascript /stylesheets
acl url_static path_end -i .jpg .gif .png .css .js

use_backend static if url_static
default_backend app

#---------------------------------------------------------------------
# static backend for serving up images, stylesheets and such
#---------------------------------------------------------------------
backend static
balance roundrobin
server static 127.0.0.1:4331 check

#---------------------------------------------------------------------
# round robin balancing between the various backends
#---------------------------------------------------------------------
backend app
balance roundrobin
server app1 127.0.0.1:5001 check
server app2 127.0.0.1:5002 check
server app3 127.0.0.1:5003 check
server app4 127.0.0.1:5004 check

服务控制

CentOS 7 系统可以使用 systemd 的方式来管理服务,为了管理更灵活,我们选择使用控制脚本而不是 systemd,这里只是提供一个 systemd 范例。如果生产环境没有特殊要求则可以直接使用这种控制方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@m1 ~]# cat /usr/lib/systemd/system/haproxy.service 
[Unit]
Description=HAProxy Load Balancer
After=syslog.target network.target

[Service]
EnvironmentFile=/etc/sysconfig/haproxy
ExecStart=/usr/sbin/haproxy-systemd-wrapper -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid $OPTIONS
ExecReload=/bin/kill -USR2 $MAINPID
KillMode=mixed

[Install]
WantedBy=multi-user.target

因为生产环境需要,需要监听私网地址并使用 iptables 做相应的地址转换,因此使用 init 形式的服务控制脚本,这个脚本基于官方的服务脚本做了二次修改。

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#!/bin/bash
#
# chkconfig: - 85 15
# description: HA-Proxy is a TCP/HTTP reverse proxy which is particularly suited \
# for high availability environments.
# processname: haproxy
# config: /etc/haproxy/haproxy.cfg
# pidfile: /var/run/haproxy.pid

# Script Author: Simon Matter <simon.matter@invoca.ch>
# Version: 2004060600

# Source function library.
if [ -f /etc/init.d/functions ]; then
. /etc/init.d/functions
elif [ -f /etc/rc.d/init.d/functions ] ; then
. /etc/rc.d/init.d/functions
else
exit 0
fi

# Source networking configuration.
. /etc/sysconfig/network

# Check that networking is up.
[ "${NETWORKING}" = "no" ] && exit 0

# This is the path of our program
EXEC='/usr/sbin/haproxy'
if test -e ${EXEC};then
if test -s ${EXEC};then
if test ! -x ${EXEC};then
echo "${EXEC} : Permission denied"
exit 1
fi
else
echo "${EXEC} : Can not run the empty binary file"
exit 1
fi
else
echo "${EXEC} : No such file or directory"
exit 1
fi

# This is our service name
prog=$(basename $EXEC)

# This is the path of configuration file
cfgfile=/etc/haproxy/haproxy.cfg
[ -f ${cfgfile} ] || {
echo "Cannot load ${cfgfile}: No such file or directory"
exit 1
}

pidfile=/var/run/${prog}.pid
lockfile=/var/lock/subsys/${prog}

RETVAL=0

check() {
${EXEC} -c -q -V -f ${cfgfile} ${OPTIONS}
}

quiet_check() {
${EXEC} -c -q -f ${cfgfile} ${OPTIONS}
if [ $? -ne 0 ]; then
echo "Errors found in configuration file, check it with '${prog} check'."
exit 1
fi
}

start() {
quiet_check

rh_status_q && {
echo -n "Starting ${prog}: "
echo_failure
echo
echo $"Cannot start ${prog}: ${prog} is already running . ";
exit 0
}

echo -n "Starting ${prog}: "
# start it up here, usually something like "daemon $exec"
daemon ${EXEC} -D -f ${cfgfile} -p ${pidfile}
RETVAL=$?
echo
[ ${RETVAL} -eq 0 ] && touch ${lockfile}
return ${RETVAL}
}

stop() {

rh_status_q || {
echo -n "Stopping ${prog}: "
echo_failure
echo
echo $"Cannot stop ${prog}: ${prog} is not running . ";
return 0
}

echo -n "Stopping ${prog}: "
# stop it here, often "killproc $prog"
killproc ${prog} -USR1
RETVAL=$?
echo
[ ${RETVAL} -eq 0 ] && {
rm -f ${lockfile} ${pidfile}
}
return ${RETVAL}
}

restart() {
quiet_check
stop
start
}

reload() {
quiet_check

test -s ${pidfile} || {
echo -n "Reloading ${prog}: "
echo_failure
echo
echo $"Cannot reload ${prog}: ${prog} is not running . ";
return 1
}

echo -n "Reloading ${prog}: "
${EXEC} -D -f ${cfgfile} -p ${pidfile} -sf $(cat ${pidfile})
RETVAL=$?
[ ${RETVAL} -eq 0 ] && echo_success || echo_failure
echo
return ${RETVAL}

}

rh_status() {
status ${prog}
}

rh_status_q() {
rh_status >/dev/null 2>&1
}

condrestart() {
[ -e ${lockfile} ] && restart || :
}

# See how we were called.
case "$1" in
start|stop|restart|reload|check|condrestart)
$1
;;
status)
rh_status
;;
*)
echo
echo -e $"\tUsage: ${prog} { check | start | stop | restart | reload | condrestart | status }"
echo
exit 1
;;
esac

exit $?
有钱任性,请我吃包辣条
0%