如何使用HAProxy在UbuntuVPS上设置HTTP负载平衡

来自菜鸟教程
跳转至:导航、​搜索

关于 HAProxy


HAProxy(High Availability Proxy)是一个开源负载均衡器,可以对任何 TCP 服务进行负载均衡。 它特别适合 HTTP 负载平衡,因为它支持会话持久性和第 7 层处理。

借助 DigitalOcean Private Networking,可以将 HAProxy 配置为前端,通过专用网络连接对两个 VPS 进行负载平衡。

序幕


我们将在这里使用三个 VPS(液滴):

Droplet 1 - 负载均衡器主机名:haproxy 操作系统:Ubuntu 公共 IP:1.1.1.1 私有 IP:10.0.0.100

Droplet 2 - 节点 1 主机名:lamp1 操作系统:Ubuntu 上的 LAMP 私有 IP:10.0.0.1

Droplet 2 - 节点 2 主机名:lamp2 操作系统:Ubuntu 上的 LAMP 私有 IP:10.0.0.2

安装 HAProxy


使用 apt-get 命令安装 HAProxy。

apt-get install haproxy

我们需要启用 HAProxy 以通过 init 脚本启动。

nano /etc/default/haproxy

将 ENABLED 选项设置为 1

ENABLED=1

要检查此更改是否正确执行,请执行不带任何参数的 HAProxy 的初始化脚本。 您应该看到以下内容。

root@haproxy:~# service haproxy
Usage: /etc/init.d/haproxy {start|stop|reload|restart|status}

配置 HAProxy


我们将移动默认配置文件并创建我们自己的配置文件。

mv /etc/haproxy/haproxy.cfg{,.original}

创建并编辑一个新的配置文件:

nano /etc/haproxy/haproxy.cfg

让我们首先将配置逐块添加到此文件中:

global
    log 127.0.0.1 local0 notice
    maxconn 2000
    user haproxy
    group haproxy

log 指令提到将发送日志消息的系统日志服务器。 在 Ubuntu 上,rsyslog 已经安装并正在运行,但它不会监听任何 IP 地址。 稍后我们将修改 rsyslog 的配置文件。

maxconn 指令指定前端的并发连接数。 默认值为 2000,应根据您的 VPS 配置进行调整。

user 和 group 指令将 HAProxy 进程更改为指定的用户/组。 这些不应该改变。

defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    retries 3
    option redispatch
    timeout connect  5000
    timeout client  10000
    timeout server  10000

我们在本节中指定默认值。 要修改的值是各种 timeout 指令。 connect 选项指定等待与 VPS 的连接尝试成功的最长时间。

client 和 server 超时适用于客户端或服务器应在 TCP 过程中确认或发送数据时。 HAProxy 建议将 client 和 server 超时设置为相同的值。

retries 指令设置连接失败后在 VPS 上执行的重试次数。

option redispatch 在连接失败的情况下启用会话重新分配。 因此,如果 VPS 出现故障,会话粘性将被覆盖。

listen appname 0.0.0.0:80
    mode http
    stats enable
    stats uri /haproxy?stats
    stats realm Strictly\ Private
    stats auth A_Username:YourPassword
    stats auth Another_User:passwd
    balance roundrobin
    option httpclose
    option forwardfor
    server lamp1 10.0.0.1:80 check
    server lamp2 10.0.0.2:80 check

这包含前端和后端的配置。 我们将 HAProxy 配置为在端口 80 上侦听 appname,这只是用于识别应用程序的名称。 stats 指令启用连接统计页面,并使用 stats auth 指令指定的凭据通过 HTTP Basic authentication 保护它。

这个页面可以用stats uri中提到的URL查看,所以在这种情况下,它是http://1.1.1.1/haproxy?stats; 此页面的演示可以在这里查看。

balance 指令指定要使用的负载平衡算法。 可用选项包括循环 (roundrobin)、静态循环 (static-rr)、最少连接 (leastconn)、源 (source)、URI ([ X135X])和 URL 参数(url_param)。

各个算法的信息可以从【X58X】官方文档【X84X】中获取。

server 指令声明一个后端服务器,语法是:

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

我们在此处提到的名称将出现在日志和警报中。 该指令支持许多参数,我们将在本文中使用check和cookie参数。 check 选项启用 VPS 上的健康检查,否则,VPS 始终被视为可用。

完成配置后,启动 HAProxy 服务:

service haproxy start

测试负载平衡和故障转移


要测试此设置,请在所有 Web 服务器(后端服务器 - 此处为 LAMP1 和 LAMP2)上创建一个 PHP 脚本。

/var/www/file.php

<?php
header('Content-Type: text/plain');
echo "Server IP: ".$_SERVER['SERVER_ADDR'];
echo "\nClient IP: ".$_SERVER['REMOTE_ADDR'];
echo "\nX-Forwarded-for: ".$_SERVER['HTTP_X_FORWARDED_FOR'];
?>

现在我们将使用 curl 并多次请求该文件。

> curl http://1.1.1.1/file.php

Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.X.X

> curl http://1.1.1.1/file.php

Server IP: 10.0.0.2
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.X.X

> curl http://1.1.1.1/file.php

Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.X.X

请注意,HAProxy 如何交替切换 LAMP1 和 LAMP2 之间的连接,这就是 Round Robin 的工作原理。 我们在这里看到的客户端 IP 是负载均衡器的私有 IP 地址,而 X-Forwarded-For 标头是您的 IP。

要查看故障转移的工作原理,请转到 Web 服务器并停止服务:

lamp1@haproxy:~#service apache2 stop

再次使用 curl 发送请求,看看事情是如何工作的。

会话粘性


如果您的 Web 应用程序根据用户的登录会话提供动态内容(哪个应用程序没有),由于 VPS 之间的不断切换,访问者会遇到奇怪的事情。 会话粘性确保访问者坚持服务于他们的第一个请求的 VPS。 这可以通过使用 cookie 标记每个后端服务器来实现。

我们将使用以下 PHP 代码来演示会话粘性如何工作。

/var/www/session.php

<?php
header('Content-Type: text/plain');
session_start();
if(!isset($_SESSION['visit']))
{
        echo "This is the first time you're visiting this server";
        $_SESSION['visit'] = 0;
}
else
        echo "Your number of visits: ".$_SESSION['visit'];

$_SESSION['visit']++;

echo "\nServer IP: ".$_SERVER['SERVER_ADDR'];
echo "\nClient IP: ".$_SERVER['REMOTE_ADDR'];
echo "\nX-Forwarded-for: ".$_SERVER['HTTP_X_FORWARDED_FOR']."\n";
print_r($_COOKIE);
?>

此代码创建一个 PHP 会话 并显示单个会话中的页面浏览量。

Cookie插入方法

在此方法中,从 HAProxy 到客户端的所有响应都将包含一个 Set-Cookie: 标头,其中后端服务器的名称作为其 cookie 值。 因此,客户端(Web 浏览器)将在其所有请求中包含此 cookie,HAProxy 将根据 cookie 值将请求转发到正确的后端服务器。

对于此方法,您需要添加 cookie 指令并修改 listen 下的 server 指令

   cookie SRVNAME insert
   server lamp1 10.0.0.1:80 cookie S1 check
   server lamp2 10.0.0.2:80 cookie S2 check

这会导致 HAProxy 添加一个 Set-Cookie: 标头,其中包含一个名为 SRVNAME 的 cookie,其值为 S1 或 S2,具体取决于哪个后端响应了请求。 添加后重新启动服务:

service haproxy restart

并使用 curl 检查它是如何工作的。

> curl -i http://1.1.1.1/session.php
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2013 13:11:22 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Set-Cookie: PHPSESSID=l9haakejnvnat7jtju64hmuab5; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 143
Connection: close
Content-Type: text/plain
Set-Cookie: SRVNAME=S1; path=/

This is the first time you're visiting this server
Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.X.X
Array
(
)

这是我们提出的第一个请求,从 Set-Cookie: SRVNAME=S1; path=/ 中可以看出,LAMP1 已回答了它。 现在,为了模拟 Web 浏览器对下一个请求执行的操作,我们使用 curl 的 --cookie 参数 将这些 cookie 添加到请求中。

> curl -i http://1.1.1.1/session.php --cookie "PHPSESSID=l9haakejnvnat7jtju64hmuab5;SRVNAME=S1;"
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2013 13:11:45 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 183
Connection: close
Content-Type: text/plain

Your number of visits: 1
Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.87.127
Array
(
    [PHPSESSID] => l9haakejnvnat7jtju64hmuab5
    [SRVNAME] => S1
)

> curl -i http://1.1.1.1/session.php --cookie "PHPSESSID=l9haakejnvnat7jtju64hmuab5;SRVNAME=S1;"
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2013 13:11:45 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 183
Connection: close
Content-Type: text/plain

Your number of visits: 2
Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.87.127
Array
(
    [PHPSESSID] => l9haakejnvnat7jtju64hmuab5
    [SRVNAME] => S1
)

这两个请求都由 LAMP1 提供服务,并且会话得到了适当的维护。 如果您希望 Web 服务器上的所有文件具有粘性,则此方法很有用。

Cookie 前缀方法


另一方面,如果您只希望特定 cookie 具有粘性,或者不想为会话粘性设置单独的 cookie,则 prefix 选项适合您。

要使用此方法,请使用以下 cookie 指令:

cookie PHPSESSID prefix

PHPSESSID 可以替换为您自己的 cookie 名称。 server 指令与之前的配置相同。

现在让我们看看它是如何工作的。

> curl -i http://1.1.1.1/session.php
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2013 13:36:27 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Set-Cookie: PHPSESSID=S1~6l2pou1iqea4mnhenhkm787o56; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 143
Content-Type: text/plain

This is the first time you're visiting this server
Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.X.X
Array
(
)

请注意 server cookie S1 如何作为会话 cookie 的前缀。 现在,让我们用这个 cookie 再发送两个请求。

> curl -i http://1.1.1.1/session.php --cookie "PHPSESSID=S1~6l2pou1iqea4mnhenhkm787o56;"
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2013 13:36:45 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 163
Content-Type: text/plain

Your number of visits: 1
Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.X.X
Array
(
    [PHPSESSID] => 6l2pou1iqea4mnhenhkm787o56
)

> curl -i http://1.1.1.1/session.php --cookie "PHPSESSID=S1~6l2pou1iqea4mnhenhkm787o56;"
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2013 13:36:54 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 163
Content-Type: text/plain

Your number of visits: 2
Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.X.X
Array
(
    [PHPSESSID] => 6l2pou1iqea4mnhenhkm787o56
)

我们可以清楚地看到,这两个请求都由 LAMP1 处理,并且会话运行良好。

为 HAProxy 配置日志记录


当我们开始配置 HAProxy 时,我们添加了一行:log 127.0.0.1 local0 notice,它将 syslog 消息发送到 localhost IP 地址。 但默认情况下,Ubuntu 上的 rsyslog 不会监听任何地址。 所以我们必须让它这样做。

编辑 rsyslog 的配置文件。

nano /etc/rsyslog.conf

添加/编辑/取消注释以下行:

$ModLoad imudp
$UDPServerAddress 127.0.0.1
$UDPServerRun 514

现在 rsyslog 将在地址 127.0.0.1 上的 UDP 端口 514 上工作,但所有 HAProxy 消息都将发送到 /var/log/syslog,因此我们必须将它们分开。

为 HAProxy 日志创建规则。

nano /etc/rsyslog.d/haproxy.conf

将以下行添加到它。

if ($programname == 'haproxy') then -/var/log/haproxy.log

现在重新启动 rsyslog 服务:

service rsyslog restart

这会将所有 HAProxy 消息和访问日志写入 /var/log/haproxy.log。

HAProxy 中的 Keepalive


在 listen 指令下,我们使用了 option httpclose 添加了 Connection: close 标头。 这告诉客户端(Web 浏览器)在收到响应后关闭连接。

如果要在 HAProxy 上启用 keep-alives,请将 option httpclose 行替换为:

option http-server-close
timeout http-keep-alive 3000

明智地设置保持活动超时,这样一些连接就不会耗尽负载均衡器的所有资源。

测试 Keepalive

可以使用 curl 通过同时发送多个请求来测试 Keepalive。 以下示例中将省略不必要的输出:

> curl -v http://1.1.1.1/index.html http://1.1.1.1/index.html
* About to connect() to 1.1.1.1 port 80 (#0)
*   Trying 1.1.1.1... connected
> GET /index.html HTTP/1.1
> User-Agent: curl/7.23.1 (x86_64-pc-win32) libcurl/7.23.1 OpenSSL/0.9.8r zlib/1.2.5
> Host: 1.1.1.1
> Accept: */*
>
......[Output omitted].........
* Connection #0 to host 1.1.1.1 left intact
* Re-using existing connection! (#0) with host 1.1.1.1
* Connected to 1.1.1.1 (1.1.1.1) port 80 (#0)
> GET /index.html HTTP/1.1
> User-Agent: curl/7.23.1 (x86_64-pc-win32) libcurl/7.23.1 OpenSSL/0.9.8r zlib/1.2.5
> Host: 1.1.1.1
> Accept: */*
>
.......[Output Omitted].........
* Connection #0 to host 1.1.1.1 left intact
* Closing connection #0

在此输出中,我们必须查找以下行:Re-using existing connection! (#0) with host 1.1.1.1,这表明 curl 使用相同的连接来发出后续请求。