所有托管服务节省 15%

测试技能,享折扣

使用代码: Skills 开始使用
China
安全 管理

如何在 Ubuntu VPS 上使用 Docker Compose 安装 HAProxy

在 VPS 上直接暴露一个网络服务很容易——直到你想要一个干净的公共入口、稍后交换后端的自由,或者一个更安全的方式来停止向故障的东西发送流量。这就是代理从”大型基础设施团队的东西”变成实用工具的时刻。

intro

HAProxy 非常适合这个角色。可以把它看作坐在你的应用程序前面的流量管理器:请求首先到达 HAProxy,然后 HAProxy 决定它们应该去哪里。你不需要一个大型集群来从中受益。即使在一个 Ubuntu 24.04 VPS 上,它也能给你一个更干净的互联网和你实际运行的服务之间的边界。

本指南有意保持第一次部署的结构化:一个 Ubuntu 24.04 VPS、Docker Compose、一个 HAProxy 容器、一个演示后端,以及路由确实有效的证明。

为什么在需要 HAProxy 之前就应该了解它

想象一个小型 VPS 运行一个应用,现在工作得很好。它在一个端口上响应,网站加载,一切看起来都不错。当你想要一个稳定的公共入口点、稍后替换后端而不改变公共地址的选项,或者一个可以停止向故障服务发送流量的前层时,摩擦就开始了。直接暴露应用很快就会开始感觉脆弱。

whymatters

这些需求都指向同一个缺失的层:互联网和你的应用之间的受控入口点。HAProxy 提供了这一层。客户端首先连接到 HAProxy,然后 HAProxy 决定每个请求接下来去哪里。

即使在你有多个服务器之前,这种分离也很有用。它现在为你提供了一个更清晰的公共边界,以及一条更安全的路径来进行后续更改,例如后端替换、健康感知路由和 HTTPS。本指南的其余部分以其最简单的工作形式展示了这种模式,并通过真实的请求路径验证了它。

快速 HAProxy 术语,让本指南的其余部分更容易理解

quick

您只需要一个小的词汇集就能自信地遵循第一次 HAProxy 部署。下表涵盖了本指南中重要的术语。

术语简明英文含义
🌐 反向代理一个面向前端的服务,首先接收请求并将其传递给另一个内部服务。
⚖️ 负载均衡器一个前层,可以将请求分配到多个后端目标。
🚪 前端客户端连接到 HAProxy 的地方。
🧩 后端HAProxy 接下来将请求发送到的服务或服务器。
❤️ 健康检查HAProxy 用来检测后端是否应该继续接收流量的一种方式。
🐳 镜像用于创建容器的打包应用程序模板。
📦 容器镜像的运行实例。

对于本指南,反向代理是首先要记住的心智模型。HAProxy 位于其他东西的前面并控制交接。负载均衡是当您稍后添加多个后端服务器时变得有用的扩展功能。

打开配置后最重要的两个术语是 前端后端前端 是客户端到达的地方。后端 是 HAProxy 接下来发送请求的地方。健康检查 很重要,因为它让 HAProxy 能够注意到何时目标应该停止接收流量。

HAProxy 的优势——以及本指南有意跳过的内容

whatgood

如果你把你的堆栈想象成一座办公楼,HAProxy 就是前台:流量首先到达那里,被引导到正确的房间,并停止被发送到明显不可用的房间。

在本指南中,这转化为三个初学者相关的工作:

  1. 接受传入的 HTTP 请求
  2. 将它们转发到演示后端
  3. 监控该后端是否足够健康以继续接收流量

即使只有一个后端,这也很有用,因为它在应用程序前面为你提供了一个受控的公共边界。

稍后,相同的模式可以干净地扩展。你可以替换后端、添加更多后端、引入 HTTPS,或让 HAProxy 将流量分散到多个目标而不仅仅是一个。为了保持第一遍的可教性,本指南停留在 HTTP 模式,有意跳过 TLS 终止、ACL、速率限制、粘性表和 HA 对。这些都是真实的 HAProxy 主题。它们只是不是第一次工作部署的正确起点。

你正在构建什么以及首先需要什么

buildingsetup

在创建文件之前,查看堆栈的最终形状会很有帮助。本指南中的部署如下所示:

Client browser or curl
        |
        v
HAProxy frontend (:80)
        |
        v
demo backend service (demo:5678)

Optional local-only validation:
HAProxy stats frontend (127.0.0.1:8404/stats)

Docker Compose 是这里的主要路径,因为它保持首次安装的可重复性、可见性和易于编辑。与其在第一天构建自定义镜像,不如将 HAProxy 配置保留在主机上,将其挂载到容器中,并从一个文件启动整个堆栈。在自管理的 Ubuntu VPS 上(例如 AlexHost VPS),这是一个完美的契合,因为布局保持易于检查。

💡 提示:本指南故意使用 Docker Compose 加上绑定挂载的 haproxy.cfg。这是最透明的首次安装路径,因为你可以直接编辑代理配置,而无需添加镜像构建步骤。

在开始之前,请确保你已准备好以下基础:

  • Ubuntu 24.04 VPS
  • 已安装 Docker Engine
  • Docker Compose v2 可通过 docker compose 获得
  • 终端访问权限和运行 Docker 的权限
  • 主机上的端口 80 可用
  • 如果使用 UFW 或提供商端防火墙规则,允许入站 HTTP

首先,检查你的 Ubuntu 版本

lsb_release -a

ubuntu-version

接下来,确认 Docker 和现代 Compose 可用:

docker --version
docker compose version

docker-version

如果两个命令都返回版本信息,容器运行时端已准备好,你可以专注于 HAProxy,而不是绕道进行 Docker 安装。

接下来,确保端口 80 未被使用,然后检查 UFW 是否处于活动状态以及 HTTP 是否已被允许:

sudo ss -tlnp | grep -E ':(80)s' || true
sudo ufw status
sudo ufw allow 80/tcp

ufw-status

✏️ 注意:ss 检查没有输出通常意味着端口 80 是空闲的。如果你看到 nginxapache2caddy 或其他服务已在监听该端口,请先修复。这是一个十秒钟的预检步骤,可以节省大量后续的困惑。

在上面的示例中,sudo ufw status 显示 Status: active80/tcp 已在允许列表中。这就是为什么 sudo ufw allow 80/tcp 返回 Skipping adding existing rule 而不是添加新规则。该输出是正常的,只是意味着防火墙规则已经就位。

创建项目文件夹和 Compose 文件

首先为这个首次部署所需的两个文件创建一个小项目文件夹:

mkdir -p ~/haproxy-docker
cd ~/haproxy-docker

mkdir

之后,布局应该尽可能简洁:

~/haproxy-docker/
├── compose.yaml
└── haproxy.cfg

现在创建 compose.yaml 并使用以下确切内容:

services:
  demo:
    image: hashicorp/http-echo:1.0
    command: ["-listen=:5678", "-text=Hello from the HAProxy demo backend"]
    restart: unless-stopped

  haproxy:
    image: haproxy:3.4.1
    depends_on:
      - demo
    ports:
      - "80:80"
      - "127.0.0.1:8404:8404"
    volumes:
      - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
    sysctls:
      net.ipv4.ip_unprivileged_port_start: "0"
    restart: unless-stopped

此文件将容器连接在一起,但尚未定义 HAProxy 请求逻辑。它告诉 Docker 要运行哪些镜像、要发布哪些端口,以及 HAProxy 配置将从主机的何处挂载。

以下设置是对干净首次部署最重要的:

Compose 设置为什么在这里
hashicorp/http-echo:1.0提供一个微小、可预测的演示后端,同时不需要同时教授第二个 Web 服务器。
haproxy:3.4.1使用固定的稳定标签而不是 latest,这使指南随时间推移更加稳定。
depends_on在 HAProxy 之前启动 demo 服务,这对首次运行顺序很有帮助。
80:80在读者期望的标准 Web 端口上发布主 HTTP 监听器。
127.0.0.1:8404:8404保持统计页面可用于本地验证,默认情况下不公开暴露。
./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro将可见的主机端配置文件以只读方式挂载到官方 HAProxy 镜像中。
sysctlsnet.ipv4.ip_unprivileged_port_start: “0”让非 root HAProxy 容器能够绑定到 80 等低端口。
restart: unless-stopped提供实用的 VPS 默认值:在故障或重启后重启,但尊重有意的手动停止。

这里还有一个细节很重要:此文件中没有自定义 Docker 网络,因为 Docker Compose 会自动创建默认网络。这为您提供了项目内的服务名称 DNS,这就是为什么 HAProxy 将能够通过 demo:5678 到达后端,而无需额外的配置。

⚠️ 警告:端口 80 是特权端口,所以 sysctls 行不是装饰性的。将主机映射更改为 8080:80 不会移除特权端口要求,如果 HAProxy 仍在容器内绑定到 :80

编写并验证最小化的 haproxy.cfg

容器连接就位后,HAProxy 仍需要指令来说明流量在哪里到达、应该去哪里,以及如何检查后端健康状况。接下来创建 haproxy.cfg

global
    log stdout format raw local0

defaults
    mode http
    timeout connect 5s
    timeout client 30s
    timeout server 30s

frontend http
    bind :80
    default_backend demo_backend

backend demo_backend
    balance roundrobin
    server demo1 demo:5678 check

frontend stats
    bind :8404
    stats enable
    stats refresh 10s
    stats uri /stats

这是一个最小化配置,但不是一次性的。log stdout format raw local0 是容器友好的日志记录选择,因为 Docker 可以轻松显示 stdout,而 defaults 中的 mode http 使整个示例保持在 HTTP 模式,以便监听器和后端行为保持一致且易于阅读。

✏️ 注意: 在部分分解之前值得指出一个细节:balance roundrobin 被明确设置,因为较新的 HAProxy 版本将默认后端算法更改为 random,而 roundrobin 在第一次通过时更容易以可预测的方式教授。

以下是每个部分的纯英文分解:

部分关键行它的作用
globallog stdout format raw local0将日志发送到 stdout,以便 Docker 日志记录保持直接。
defaultsmode http、超时建立基线 HTTP 行为和合理的超时值。
frontend httpbind :80default_backend demo_backend创建公共监听器并将其连接到后端定义。
backend demo_backendbalance roundrobinserver demo1 demo:5678 check告诉 HAProxy 使用哪个服务并监控其健康状况。
frontend statsbind :8404stats enablestats uri /stats添加可选的本地验证页面,以便稍后查看运行时状态。

您可能会注意到一件事缺失:option forwardfor。这个遗漏在基本路径中是有意的。保留原始客户端 IP 稍后很有用,但这个第一次部署是关于证明路由和后端健康,而不是教授头部行为,使用不会使该信号特别有价值的演示容器。

💡 提示: 始终在启动完整堆栈之前验证 HAProxy 配置。因为此配置通过其 Compose 服务名称(demo)引用后端,请先启动该后端,以便 HAProxy 在验证期间可以解析它。

从同一项目目录运行验证:

docker compose up -d demo
docker compose run --rm --no-deps haproxy haproxy -V -c -f /usr/local/etc/haproxy/haproxy.cfg

success

如果第二个命令以 Configuration file is valid 结尾,您已经证明了 HAProxy 可以正确解析文件并在任何实时监听器启动之前解析后端目标。

启动堆栈并证明代理工作

配置验证后,以分离模式启动堆栈:

由于验证步骤已经启动了 demo,此命令主要启动 HAProxy 并协调完整的双服务堆栈:

docker compose up -d

start-cmpose

然后检查两个容器是否都在运行:

docker compose ps

compose-status

该进程视图只是第一个检查点。它确认 Docker 启动了容器,但还不能确认 HAProxy 是否成功将流量路由到后端。下一个请求验证实际的数据路径。

现在从 VPS 本身运行实际的路由测试:

curl -i http://127.0.0.1

valid

成功信号是 HTTP/1.1 200 OK 加上响应体包含 Hello from the HAProxy demo backend。某些 http-echo 构建会将该文本包装在一个小的 HTML 响应中,因此请关注响应体短语而不是确切的格式。

如果您想要浏览器级别的证明,请从另一台机器打开 http://YOUR_SERVER_IP

browser-valid

为了进行第二个验证,请从 VPS 检查仅本地的统计页面:

curl http://127.0.0.1:8404/stats

在统计页面上,最有用的信号是名为 http 的前端、名为 demo_backend 的后端、名为 demo1 的服务器行、显示为 UP 的状态,以及通常像 L4OK in 0ms 这样的最后检查值。还要记住一个小的 Docker 细节:简短语法 depends_on 控制启动顺序,但不会等待服务变为健康状态。如果启动后的第一个 curl 失败一次,请等待几秒钟后重试,然后再假设配置有误。

进程状态和实际成功之间的区别更容易以表格形式理解:

状态它告诉您什么
容器 正在运行Docker 启动了进程。
curl -i http://127.0.0.1 返回 200 OK 和演示短语HAProxy 实际上正在将流量路由到后端。
统计页面显示 demo1UPHAProxy 将后端视为健康。

常见首次运行错误和快速修复

mistakes

如果设置不能立即工作,请抵制住一次重写两个文件的冲动。此堆栈上的大多数首次运行失败都是可预测的,当您一次更改一个变量时,它们会变得更容易修复。

使用此矩阵作为快速诊断层:

  • HAProxy 立即退出

    可能原因: haproxy.cfg 缺失。

    快速修复: 确保 haproxy.cfg 存在于 compose.yaml 旁边。

    发生原因: 官方镜像不附带现成可用的配置。


  • 错误提示无法打开 /usr/local/etc/haproxy/haproxy.cfg

    可能原因: 绑定挂载路径错误。

    快速修复: 验证 ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro 完全匹配。

    发生原因: HAProxy 没有有效的配置文件无法启动。


  • 错误提示端口 80 上的 Permission denied

    可能原因: 特权端口绑定问题。

    快速修复: 在 Compose 中保留 net.ipv4.ip_unprivileged_port_start: “0”,或将 HAProxy 和发布端口都移至 8080

    发生原因: 容器以非 root haproxy 用户身份运行。


  • 您将映射更改为 8080:80 但仍然收到绑定错误

    可能原因: HAProxy 仍在容器内绑定到 :80

    快速修复: 如果您离开端口 80,请同时更改主机映射和内部 bind 行。

    发生原因: 特权端口规则也适用于容器内部。


  • 端口 80 已被占用

    可能原因: 另一个服务占用了主机端口。

    快速修复: 重新运行 ss 检查并停止或移动冲突的服务。

    发生原因: 只有一个进程可以监听同一主机端口。


  • 语法检查报告 unknown keyword 或特定行错误

    可能原因: HAProxy 配置拼写错误。

    快速修复: 重新运行语法检查并修复它报告的确切行。

    发生原因: HAProxy 的解析器很严格,一旦您有意使用它就会很有帮助。


  • 容器已启动,但 curl 不返回演示响应

    可能原因: 路由路径错误。

    快速修复: 重新检查 default_backend demo_backendserver demo1 demo:5678 checkdemo 服务名称。

    发生原因: 运行中的容器不能证明前端到后端路径正确。


  • 本地 curl 有效,但从外部无法访问该站点

    可能原因: 防火墙或提供商安全规则。

    快速修复: 在 UFW 和任何提供商端防火墙中打开端口 80

    发生原因: 本地发布可以工作,即使公共访问仍被阻止。

⚠️ 警告: 一次更改一个内容。如果您盲目编辑 compose.yamlhaproxy.cfg,您会更难判断失败是文件路径问题、端口问题还是路由问题。

当您需要快速证据时,请将这些命令放在附近:

docker compose logs haproxy
docker compose ps
docker compose up -d demo
docker compose run --rm --no-deps haproxy haproxy -V -c -f /usr/local/etc/haproxy/haproxy.cfg
sudo ss -tlnp | grep -E ':(80|8404)s' || true

这是最值得一眼识别的三种警报模式:

[ALERT] ... Cannot open configuration file /usr/local/etc/haproxy/haproxy.cfg : No such file or directory
[ALERT] ... Starting frontend http: cannot bind socket (Permission denied) [0.0.0.0:80]
[ALERT] ... parsing [/usr/local/etc/haproxy/haproxy.cfg:12] : unknown keyword 'chekc'; did you mean 'check' maybe?

这是小型首次部署令人放心的部分:失败形状通常也很小。您不需要重新开始。您需要识别哪一层在抱怨,然后首先更正那一个内容。

安装后的后续步骤

一旦单后端演示工作正常,该架构已经很有用了。下一个真正的步骤是用你的实际应用程序替换演示容器,同时保持相同的HAProxy结构。之后,将HTTPS/TLS作为专门的后续步骤添加,并将基于域的路由和ACL视为单独的主题,而不是在第一次安装中仓促处理它们。

end

当你准备好使用多个后端时,相同的模式变得明显更有意义:

backend app_backend
    balance roundrobin
    server app1 app1:8080 check
    server app2 app2:8080 check

对于对绑定挂载配置的安全编辑,请先验证,然后优雅地重新加载HAProxy:

docker compose up -d demo
docker compose run --rm --no-deps haproxy haproxy -V -c -f /usr/local/etc/haproxy/haproxy.cfg
docker compose kill -s HUP haproxy

📝 注意:在本指南中,统计页面故意仅限本地访问。如果你曾经公开暴露它,请先添加身份验证和访问控制。

这将你带回最初的问题:你想要一个干净的前门来处理服务,而不是将第一次设置变成一个完整的运维项目。你现在有了一个可行的路径。更重要的是,你还拥有正确的心智模型:HAProxy首先接收流量,将其转发到应该去的地方,并为你提供了一种更干净的方式来在自管理VPS上扩展堆栈,而不会失去对配置的控制。