HAProxy 是一个高性能的负载均衡器,网上已经有不少介绍,也相信大家都有所耳闻,但是如何从 0 开始搭建一个 基础的 HAProxy 负载均衡器,并实现对应用服务的负载均衡的相关文章还是相对较少
这篇文章中,我们以一个简单的 Node.js IP 查询服务为例,展现了 HAProxy 如何搭建并实现对应用服务器的负载均衡,并展现了实现负载均衡后的具体效果。
应用代码:Node.js 查询 IP 服务
在以下说明中,我们将应用服务相关的配置文件放在项目下的 app 目录下。
我们对 Node.js 的 HTTP Hello World 服务 进行了一些修改,让他返回用户的请求 IP,以实现了一个略有一点用途的小服务,后面,我们再对这个服务进行负载均衡。
// app/server.js
const http = require("http");
const index = process.env.INDEX;
const port = 3000 + parseInt(index, 10);
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader("Content-Type", "text/plain");
// 获取客户端真实 IP
const ip = req.headers['x-forwarded-for'] ?? req.socket.remoteAddress;
res.end(JSON.stringify({ index, ip }));
});
server.listen(port, () => {
console.log(`Server running at ${port}`);
});
为了能够方便的同时部署多个服务,我们将这个应用服务打包为容器:
# app/Dockerfile
FROM node:lts
COPY server.js server.js
CMD node server.js
负载均衡:HAProxy
在以下说明中,我们将负载均衡相关的配置文件放在项目下的 loadbalance 目录下。
首先,我们先来看看我们整个体系的具体架构,也是负载均衡的具体原理:
- 用户访问 HAProxy 负载均衡器
- HAProxy 负载均衡器根据负载均衡规则将请求转发到应用服务器
- 例如,在轮询的情况下,对于每个不同的请求,依次访问不同的应用服务器
- 应用服务器进行请求的实际处理
在图中,可以看到 HAProxy 服务器本身,在 HAProxy 本身被称为 frontend,因为他是用户会直接访问的服务器,而在背后提供实际服务的应用服务器,则称为 backend。
fronend 负载均衡器前端配置
HAProxy 中前端的概念和一般的前端不一样,但也有一定的相似之处。HAProxy 的前端指的是暴露在外由用户直接访问的服务器,也就是 HAProxy 本身,HAProxy 通过下面的语法定义一个前端:
frontend <前端自定义名称>
bind *:80
option forwardfor
default_backend <后端自定义名称>
其中,当请求进入前端时(这里也就是我们的 80) 端口,default_backend 指示将请求转入一个后端,稍后我们进行后端的的定义
backend 负载均衡器后端配置
HAProxy 的后端中指经过负载均衡器转发后,最终发送到的应用服务器,一般来说,一个后端中定义了一组多个服务器,也就是我们的应用服务器,以实现负载均衡的作用。
backend <后端自定义名称>
balance roundrobin
server <后端 1 自定义名称> <后端 1 地址> check
server <后端 2 自定义名称> <后端 2 地址> check
server <后端 3 自定义名称> <后端 3 地址> check
option httpchk
完整配置
首先,只有服务运行起来后,我们才能确定后端的地址。
我们通过 docker-compose 将服务运行起来,虽然 docker-compose 本身也提供了负载均衡的功能,但是我们还是手动创建三个服务,以便观察 HAProxy 的工作效果。
我们在项目根目录下创建以下 docker-compose.yaml 文件:
# docker-compose.yaml
version: '3.1'
services:
# 负载均衡服务
loadbalance-service:
# 从 ./loadbalance/Dockerfile 构建
build: ./loadbalance
# 暴露在 18888 端口
ports:
- 18888:18888
networks:
haproxy-playground:
# APP 服务器 1
app-service-1:
build: ./app
environment:
INDEX: 1
networks:
haproxy-playground:
# APP 服务器 2
app-service-2:
build: ./app
environment:
INDEX: 2
networks:
haproxy-playground:
# APP 服务器 3
app-service-3:
build: ./app
environment:
INDEX: 3
networks:
haproxy-playground:
# 将负载均衡服务器和 APP 服务器放在一个虚拟网络中,以便可以互相通过服务名访问 1
networks:
haproxy-playground:
实际上,HAProxy 除了 frontend 和 backend,还有 global 和 default 两个配置项,用以配置例如 SSL 证书,超时时间,错误页面这些内容,在这里暂不进行详细配置,故我们只需要使用一些基础配置即可,如果需要更多的功能,也可以在网上查阅,这里是最终的完整配置:
# loadbalance/haproxy.cfg
global
log /dev/log local0
log /dev/log local1 notice
stats timeout 30s
user haproxy
group haproxy
daemon
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
frontend my-loadbanlance-server
bind *:18888
option forwardfor
default_backend app-server
backend app-server
balance roundrobin
server app-server-1 app-service-1:3001
server app-server-2 app-service-2:3002
server app-server-3 app-service-3:3003
option httpchk
最后,我们将 HAProxy 打包为 Docker 镜像
# loadbalance/Dockerfile
FROM haproxy:2.3
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
开始工作及工作效果
# 构建 Docker 镜像
docker-compose build
# 启用服务
docker-compose up -d
在服务启用后,我们在本地的 localhost:18888 即可访问到负载均衡服务,具体的,可以看到以下三个请求交替出现,这是因为我们使用了轮替的负载均衡算法 (roundrobin):
# 发送请求
curl 127.0.0.1:18888
# {"index":"1","ip":"172.19.0.1"}
# {"index":"2","ip":"172.19.0.1"}
# {"index":"3","ip":"172.19.0.1"}
可以动手试一下,我们的每次请求依次访问了不同的应用服务器(编号为 1,2,3),而请求 IP 为 172.19.0.1,这是 Docker Desktop 中主机的默认 IP。