跳至内容

HAProxy 负载均衡 Apache 与 LXD 容器

简介

HAProxy 代表“高可用性代理”。此代理可以位于任何 TCP 应用程序(例如 Web 服务器)之前,但通常用作多个网站实例之间的负载均衡器。

这样做可能有很多原因。如果您有一个访问量很大的网站,添加另一个该网站的实例并在两者前面放置 HAProxy,可以将流量分布到各个实例之间。另一个原因可能是能够在不出现任何停机时间的情况下更新网站上的内容。HAProxy 还可以帮助缓解 DOS 和 DDOS 攻击。

本指南将探讨如何将 HAProxy 与两个网站实例一起使用,并在同一 LXD 主机上使用循环轮询进行负载均衡。这可能是一个确保更新可以执行而不会出现停机时间的完美解决方案。

但是,如果您的问题是网站性能,您可能需要将多个网站分布到实际的裸机或多个 LXD 主机上。当然可以在不使用 LXD 的情况下在裸机上执行所有这些操作。但是,LXD 提供了极大的灵活性和性能,也非常适合实验室测试。

先决条件和假设

  • 在 Linux 机器上对命令行的完全了解
  • 使用命令行编辑器(此处使用 vim)的经验
  • 使用 crontab 的经验
  • 了解 LXD。有关更多信息,您可能需要参考 LXD 服务器 文档。在笔记本电脑或工作站上安装 LXD 而不进行完整的服务器安装是可以的。本文档使用运行 LXD 的实验室机器编写,但没有像上述链接的文档中那样设置为完整的服务器。
  • 了解如何安装、配置和使用 Web 服务器。
  • 我们假设 LXD 已经安装并准备好了创建容器。

安装容器

在用于本指南的 LXD 主机上,您将需要三个容器。如果需要,可以有更多 Web 服务器容器。您将使用 web1web2 作为我们的网站容器,proxyha 作为我们的 HAProxy 容器。要在您的 LXD 主机上安装这些容器,请执行以下操作

lxc launch images:rockylinux/8 web1
lxc launch images:rockylinux/8 web2
lxc launch images:rockylinux/8 proxyha

运行 lxc list 应该返回类似以下内容

+---------+---------+----------------------+------+-----------+-----------+
|  NAME   |  STATE  |         IPV4         | IPV6 |   TYPE    | SNAPSHOTS |
+---------+---------+----------------------+------+-----------+-----------+
| proxyha | RUNNING | 10.181.87.137 (eth0) |      | CONTAINER | 0         |
+---------+---------+----------------------+------+-----------+-----------+
| web1    | RUNNING | 10.181.87.207 (eth0) |      | CONTAINER | 0         |
+---------+---------+----------------------+------+-----------+-----------+
| web2    | RUNNING | 10.181.87.34 (eth0)  |      | CONTAINER | 0         |
+---------+---------+----------------------+------+-----------+-----------+

创建和使用 macvlan 配置文件

容器在默认的桥接接口上运行,该接口使用桥接分配的 DHCP 地址。这些需要更改为我们本地局域网的 DHCP 地址。首先需要创建并分配 macvlan 配置文件。

首先创建配置文件

lxc profile create macvlan

确保将您的编辑器设置为您的首选编辑器,在本例中为 vim

export EDITOR=/usr/bin/vim

接下来,更改 macvlan 配置文件。在进行更改之前,您需要知道主机用于我们局域网的接口是什么。运行 ip addr 并查找具有局域网 IP 地址分配的接口

2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether a8:5e:45:52:f8:b6 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.141/24 brd 192.168.1.255 scope global dynamic noprefixroute eno1

注意

在本例中,您要查找的接口是“eno1”,它在您的系统上可能完全不同。使用**您的**接口信息!

现在您已经知道了局域网接口,您可以更改我们的 macvlan 配置文件。为此,在命令行中输入

lxc profile edit macvlan

编辑配置文件使其类似于以下内容。作者排除了文件顶部的注释,但如果您不熟悉 LXD,请检查这些注释

config: {}
description: ""
devices:
  eth0:
    name: eth0
    nictype: macvlan
    parent: eno1
    type: nic
name: macvlan

创建 macvlan 配置文件时,系统会复制 default 配置文件。无法更改 default 配置文件。

现在 macvlan 配置文件已经存在,您需要将其应用于我们的三个容器

lxc profile assign web1 default,macvlan
lxc profile assign web2 default,macvlan
lxc profile assign proxyha default,macvlan

不幸的是,内核中实现的 macvlan 的默认行为在 LXD 容器内莫名其妙地出现故障(请参阅 本文档)每个容器启动时的 dhclient

使用 DHCP 时,执行此操作非常简单。只需按照以下步骤操作每个容器即可

  • lxc exec web1 bash 这将把您带到 web1 容器的命令行
  • crontab -e 这将编辑容器中 root 的 crontab
  • 键入 I 进入插入模式。
  • 添加一行:@reboot /usr/sbin/dhclient
  • Esc 键退出插入模式。
  • 使用 Shift冒号+w+q++ 保存更改。
  • 键入 exit 退出容器

重复 web2proxyha 的步骤。

完成这些步骤后,重新启动容器

lxc restart web1
lxc restart web2
lxc restart proxyha

再次执行 lxc list 时,您会看到 DHCP 地址现在已从您的局域网分配

+---------+---------+----------------------+------+-----------+-----------+
|  NAME   |  STATE  |         IPV4         | IPV6 |   TYPE    | SNAPSHOTS |
+---------+---------+----------------------+------+-----------+-----------+
| proxyha | RUNNING | 192.168.1.149 (eth0) |      | CONTAINER | 0         |
+---------+---------+----------------------+------+-----------+-----------+
| web1    | RUNNING | 192.168.1.150 (eth0) |      | CONTAINER | 0         |
+---------+---------+----------------------+------+-----------+-----------+
| web2    | RUNNING | 192.168.1.101 (eth0) |      | CONTAINER | 0         |
+---------+---------+----------------------+------+-----------+-----------+

安装 Apache 并更改欢迎屏幕

我们的环境已准备就绪。接下来,在每个 Web 容器上安装 Apache (httpd)。您可以在不物理访问它们的情况下执行此操作

lxc exec web1 dnf install httpd
lxc exec web2 dnf install httpd

您将需要比 Apache 更多的组件才能运行任何现代 Web 服务器,但这足以进行一些测试。

接下来,启用 httpd、启动它并更改默认的欢迎屏幕。这样,您就可以在尝试通过代理访问时知道服务器正在响应。

启用并启动 httpd

lxc exec web1 systemctl enable httpd
lxc exec web1 systemctl start httpd
lxc exec web2 systemctl enable httpd
lxc exec web2 systemctl start httpd

更改欢迎屏幕。当未配置任何网站时,此屏幕会出现,本质上是一个默认页面,该页面会加载。在 Rocky Linux 中,此页面位于 /usr/share/httpd/noindex/index.html。更改该文件不需要直接访问容器。只需执行以下操作即可

lxc exec web1 vi /usr/share/httpd/noindex/index.html

搜索 <h1> 标签,它将显示以下内容

<h1>HTTP 服务器 <strong>测试页面</strong></h1>

将该行更改为

<h1>SITE1 HTTP 服务器 <strong>测试页面</strong></h1>

对 web2 重复该过程。现在,在浏览器中通过 IP 访问这些机器将返回每个机器的正确欢迎页面。还有更多关于 Web 服务器的工作要做,但现在离开它们,转到代理服务器。

在 proxyha 上安装 HAProxy 和 LXD 代理配置

在代理容器上安装 HAProxy 很简单。再次强调,不需要直接访问该容器。

lxc exec proxyha dnf install haproxy

接下来,您需要配置 haproxy 以监听端口 80 和 443 用于 web 服务。使用 lxc 的 configure 子命令来完成此操作。

lxc config device add proxyha http proxy listen=tcp:0.0.0.0:80 connect=tcp:127.0.0.1:80
lxc config device add proxyha https proxy listen=tcp:0.0.0.0:443 connect=tcp:127.0.0.1:443

在我们的测试中,您只使用端口 80 或 HTTP 流量,但这向您展示了如何配置容器以监听 HTTP 和 HTTPS 的默认 web 端口。使用此命令还可以确保重新启动 proxyha 容器将保留这些监听端口。

HAProxy 配置

您已经在容器上安装了 HAProxy,但还没有对配置进行任何操作。在配置之前,您需要做一些事情来解析您的主机。通常情况下,您会使用完全限定域名,但在本实验环境中,您使用 IP。为了让某些名称与机器相关联,您将向 proxyha 容器添加一些主机文件记录。

lxc exec proxyha vi /etc/hosts

将以下记录添加到文件末尾

192.168.1.150   site1.testdomain.com     site1
192.168.1.101   site2.testdomain.com     site2

这允许 proxyha 容器解析这些名称。

编辑 haproxy.cfg 文件。您没有使用原始文件的很多内容。您需要先将文件备份到另一个名称。

lxc exec proxyha mv /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.orig

创建一个新的配置文件

lxc exec proxyha vi /etc/haproxy/haproxy.cfg

注意,现在将 HTTPS 协议行注释掉了。在生产环境中,您需要使用一个通配符证书来覆盖您的 web 服务器并启用 HTTPS。

global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon

# For now, all https is remarked out
#
#ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
#ssl-default-bind-ciphers EECDH+AESGCM:EDH+AESGCM
#tune.ssl.default-dh-param 2048

defaults
log global
mode http
option httplog
option dontlognull
option forwardfor
option http-server-close
timeout connect 5000
timeout client 50000
timeout server 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http

# For now, all https is remarked out
# frontend www-https
# bind *:443 ssl crt /etc/letsencrypt/live/example.com/example.com.pem
# reqadd X-Forwarded-Proto:\ https

# acl host_web1 hdr(host) -i site1.testdomain.com
# acl host_web2 hdr(host) -i site2.testdomain.com

# use_backend subdomain1 if host_web1
# use_backend subdomain2 if host_web2

frontend http_frontend
bind *:80

acl web_host1 hdr(host) -i site1.testdomain.com
acl web_host2 hdr(host) -i site2.testdomain.com

use_backend subdomain1 if web_host1
use_backend subdomain2 if web_host2

backend subdomain1
# balance leastconn
  balance roundrobin
  http-request set-header X-Client-IP %[src]
# redirect scheme https if !{ ssl_fc }
     server site1 site1.testdomain.com:80 check
     server site2 web2.testdomain.com:80 check

backend subdomain2
# balance leastconn
  balance roundrobin
  http-request set-header X-Client-IP %[src]
# redirect scheme https if !{ ssl_fc }
     server site2 site2.testdomain.com:80 check
     server site1 site1.testdomain.com:80 check

以上内容的简单解释。当您进行测试时,您应该在测试部分(如下)看到这一点。

site1site2 的定义位于“acl”部分。每个站点都在彼此各自的后端的循环中。当您在测试中访问 site1.testdomain.com 时,URL 不会更改,但内部页面将在每次访问 site1site2 测试页面时切换。site2.testdomain.com 也是如此。

这样做可以向您展示切换正在发生,但在现实中,无论您访问的是哪台服务器,您的网站内容看起来都完全相同。请注意,该文档演示了您可能希望如何将流量分布到多个主机之间。您也可以在 balance 行中使用“leastcon”,它将不再根据之前的命中来切换,而是将加载连接数最少的站点。

错误文件

一些版本的 HAProxy 带有一组标准的 web 错误文件,但是 Rocky Linux(以及上游供应商)提供的版本没有这些文件。您可能 确实 希望创建它们,因为它们可以帮助您排除任何问题。这些文件位于目录 /etc/haproxy/errors 中,该目录不存在。

首先,创建该目录

lxc exec proxyha mkdir /etc/haproxy/errors

在该目录中创建每个文件。请注意,您可以使用您的 LXD 主机上的每个文件名来执行此操作,命令为 lxc exec proxyha vi /etc/haproxy/errors/filename.http,其中“filename.http”引用以下文件名之一。在生产环境中,您的公司可能具有他们想要使用的更具体的错误

文件名 400.http

HTTP/1.0 400 Bad request
Cache-Control: no-cache
Connection: close
Content-Type: text/html

<html><body><h1>400 Bad request</h1>
Your browser sent an invalid request.
</body></html>

文件名 403.http

HTTP/1.0 403 Forbidden
Cache-Control: no-cache
Connection: close
Content-Type: text/html

<html><body><h1>403 Forbidden</h1>
Request forbidden by administrative rules.
</body></html>

文件名 408.http

HTTP/1.0 408 Request Time-out
Cache-Control: no-cache
Connection: close
Content-Type: text/html

<html><body><h1>408 Request Time-out</h1>
Your browser didn't send a complete request in time.
</body></html>

文件名 500.http

HTTP/1.0 500 Internal Server Error
Cache-Control: no-cache
Connection: close
Content-Type: text/html

<html><body><h1>500 Internal Server Error</h1>
An internal server error occurred.
</body></html>

文件名 502.http

HTTP/1.0 502 Bad Gateway
Cache-Control: no-cache
Connection: close
Content-Type: text/html

<html><body><h1>502 Bad Gateway</h1>
The server returned an invalid or incomplete response.
</body></html>

文件名 503.http

HTTP/1.0 503 Service Unavailable
Cache-Control: no-cache
Connection: close
Content-Type: text/html

<html><body><h1>503 Service Unavailable</h1>
No server is available to handle this request.
</body></html>

文件名 504.http

HTTP/1.0 504 Gateway Time-out
Cache-Control: no-cache
Connection: close
Content-Type: text/html

<html><body><h1>504 Gateway Time-out</h1>
The server didn't respond in time.
</body></html>

运行代理

在启动服务之前,为 haproxy 创建一个“run”目录

lxc exec proxyha mkdir /run/haproxy

接下来,启用服务并启动它

lxc exec proxyha systemctl enable haproxy
lxc exec proxyha systemctl start haproxy

如果出现任何错误,请使用以下命令研究原因

lxc exec proxyha systemctl status haproxy

如果一切顺利启动并运行,您就可以开始进行测试了。

测试代理

与用于使我们的 proxyha 容器能够解析 web 服务器的主机(/etc/hosts)设置一样,由于我们的实验环境没有运行本地 DNS 服务器,因此在我们的本地机器上设置每个网站的 IP 值,以对应于我们的 haproxy 容器。

为此,请更改本地机器上的 /etc/hosts 文件。将这种域名解析方法视为“穷人的 DNS”。

sudo vi /etc/hosts

添加这两行

192.168.1.149   site1.testdomain.com     site1
192.168.1.149   site2.testdomain.com     site2

现在,如果您在本地机器上 ping site1site2,您将收到来自 proxyha 的响应

PING site1.testdomain.com (192.168.1.149) 56(84) bytes of data.
64 bytes from site1.testdomain.com (192.168.1.149): icmp_seq=1 ttl=64 time=0.427 ms
64 bytes from site1.testdomain.com (192.168.1.149): icmp_seq=2 ttl=64 time=0.430 ms

打开您的 web 浏览器,在地址栏中输入 site1.testdomain.com(或 site2.testdomain.com)。您将收到来自两个测试页面之一的响应,如果您再次加载页面,您将收到下一个服务器的测试页面。请注意,URL 不会更改,但返回的页面将在服务器之间交替更改。

screenshot of web1 being loaded and showing the second server test message

日志记录

即使我们的配置文件已正确设置为进行日志记录,您也需要两件事:首先,在 /var/lib/haproxy/ 中创建一个名为“dev”的目录

lxc exec proxyha mkdir /var/lib/haproxy/dev

接下来,创建一个系统进程,让 rsyslogd 从套接字(在本例中为 /var/lib/haproxy/dev/log)中获取实例,并将这些实例存储在 /var/log/haproxy.log

lxc exec proxyha vi /etc/rsyslog.d/99-haproxy.conf

将以下内容添加到该文件中

$AddUnixListenSocket /var/lib/haproxy/dev/log

# Send HAProxy messages to a dedicated logfile
:programname, startswith, "haproxy" {
  /var/log/haproxy.log
  stop
}

保存文件并退出,然后重新启动 rsyslog

lxc exec proxyha systemctl restart rsyslog

为了立即用一些内容填充该日志文件,请再次重新启动 haproxy

lxc exec proxyha systemctl restart haproxy

查看创建的日志文件

lxc exec proxyha more /var/log/haproxy.log

这将向您显示类似以下内容

Sep 25 23:18:02 proxyha haproxy[4602]: Proxy http_frontend started.
Sep 25 23:18:02 proxyha haproxy[4602]: Proxy http_frontend started.
Sep 25 23:18:02 proxyha haproxy[4602]: Proxy subdomain1 started.
Sep 25 23:18:02 proxyha haproxy[4602]: Proxy subdomain1 started.
Sep 25 23:18:02 proxyha haproxy[4602]: Proxy subdomain2 started.
Sep 25 23:18:02 proxyha haproxy[4602]: Proxy subdomain2 started.

结论

HAProxy 是一款强大的代理引擎,用于许多用途。它是一款高性能的开源负载均衡器和反向代理,用于 TCP 和 HTTP 应用程序。本文档演示了如何使用两个 web 服务器实例的负载均衡。

将其用于其他应用程序(包括数据库)也是可能的。它可以在 LXD 容器内和独立服务器上工作。

本文档没有涵盖许多用途。请查看 此处提供的 HAProxy 官方手册。

作者:Steven Spencer

贡献者:Ezequiel Bruni、Antoine Le Morvan、Ganna Zhyrnova