JumpServer多节点高可用实战:把PAM堡垒机从单点故障中彻底救出来
【免费下载链接】jumpserverJumpServer is an open-source Privileged Access Management (PAM) platform that provides DevOps and IT teams with on-demand and secure access to SSH, RDP, Kubernetes, Database and RemoteApp endpoints through a web browser.项目地址: https://gitcode.com/GitHub_Trending/ju/jumpserver
JumpServer作为开源特权访问管理(PAM)平台,承担着SSH、RDP、Kubernetes、数据库等资产的统一登录审计入口。当运维团队把全部服务器密钥都托付给一套单机部署的JumpServer时,一个很现实的焦虑是:它宕机的那一天,等于整个堡垒机系统失守——不仅运维人员登不上任何资产,审计记录也瞬间断档。你是否有过深夜被"JumpServer挂了"的电话叫醒的经历?本文不按"步骤1234"的套路讲,而是从三个真实故障场景出发,倒推出高可用集群的每一项架构决策,最终给出可照抄的落地配置。
三个真实故障,逼出高可用集群的三条铁律
第一个场景:数据库成为隐形单点。单机部署时PostgreSQL与JumpServer跑在同一台机器,一次磁盘打满或内存耗尽,Web界面、连接会话、任务调度全部瘫痪,连排查的入口都没有。第二个场景:升级变成赌博。为了打一个安全补丁,需要重启整个服务,运维窗口内业务完全停摆,改密、批量授权这些自动化任务全部积压。第三个场景:会话状态不共享。如果只靠"多开几台机器",用户在一号机登录后,负载均衡把下一次请求转发到二号机,登录态失效,连接直接中断——多节点反而制造了新故障。
这三个痛点分别指向三条铁律:存储层必须与计算层解耦(数据库、缓存独立成集群)、任意单点都必须有冗余与自动转移、会话与任务状态必须能被所有节点共享。下面每一节都在回答其中一条。
先看结果:高可用架构全景图与每个组件的职责
最终目标是"负载均衡 + 多个无状态应用节点 + 主从数据库 + 集群缓存 + 共享存储"。这里的"无状态"是关键:应用节点本身不保存任何业务数据,所有数据落在下层,因此任何一台应用节点宕机,其余节点都能无缝接管。
图注:请求层只与应用节点打交道,应用节点不落任何本地数据,全部依赖下层的数据库、Redis 与共享存储,这是"故障自动切换"能成立的根本前提。
部署前先按下面的表格把家底盘清楚,小规模团队可从"最小可用集"起步,业务增长后再横向扩应用节点:
| 节点类型 | 数量 | 参考配置 | 承载职责 |
|---|---|---|---|
| 负载均衡 | 2 | 2C4G | 流量分发、健康检查、VIP漂移 |
| 应用节点 | 2 | 4C8G | 运行JumpServer核心服务与任务调度 |
| PostgreSQL | 1主1从 | 4C16G | 资产、用户、授权等核心业务数据 |
| Redis | 3主3从 | 2C4G | 会话缓存、Celery消息队列 |
| NFS共享存储 | 1 | 100G+ | 录像文件、备份、部分配置文件 |
项目根目录的 config_example.yml 是所有配置的权威入口,数据库、Redis、LDAP、MFA 等参数都能在其中找到对应项,后文所有改动最终都落回这个文件。
存储层解耦:为什么数据库和缓存必须"搬出去"
单机模式下,JumpServer 的数据库和 Redis 与应用进程同吃一份资源,这是故障场景一的病根。集群化第一步就是把它们拆成独立服务。数据库选型上,PostgreSQL 主从流复制配合项目的 Django 数据层,是最稳妥的组合。config_example.yml 中与数据库相关的核心配置如下:
# 所有应用节点填写同一个主库地址 DB_ENGINE: postgresql DB_HOST: 192.168.1.6 DB_PORT: 5432 DB_USER: jumpserver DB_PASSWORD: your_secure_password DB_NAME: jumpserver # Redis 作为 Celery broker 与缓存,集群填法见下节 REDIS_HOST: 192.168.1.7 REDIS_PORT: 7000 REDIS_PASSWORD: your_redis_password主库上执行pg_basebackup或流复制工具拉起从库后,用pg_stat_replication确认复制延迟在毫秒级。从库平时不承担读写,它的价值在于:主库硬件故障时手动提升为新的主库,或用于备份、报表等只读场景。
Redis 直接上 3 主 3 从集群,理由是它同时承担两类职责:一是 WebSocket 与 Celery 的 broker,二是 Django 的会话与缓存后端。这两条链路中任意一条断掉,应用节点都会"假活"——页面能打开但登录即失败。构建集群的命令如下(假设三个节点分别为 10、11、12,每节点跑两个端口):
# 三个物理节点上各启动两个 Redis 实例(端口 7000/7001) for port in 7000 7001; do docker run -d --name redis-${port} \ -v /data/redis/${port}:/data \ -p ${port}:${port} \ redis:6 redis-server --port ${port} \ --cluster-enabled yes --appendonly yes done # 在任一节点上执行集群创建,--cluster-replicas 1 表示每个主节点配一个从节点 redis-cli --cluster create \ 192.168.1.10:7000 192.168.1.10:7001 \ 192.168.1.11:7000 192.168.1.11:7001 \ 192.168.1.12:7000 192.168.1.12:7001 \ --cluster-replicas 1对应的 JumpServer 配置只需把REDIS_HOST指向集群中的任一节点,端口指向其对应的REDIS_PORT,密码保持一致即可。这里强调一个常被忽略的坑:Redis 密码必须与配置文件严格一致,否则应用节点能连通却认证失败,健康检查会一直报 redis 异常。
应用层无状态化:多节点之间如何保持会话与任务一致
应用节点要"无状态",靠的不是玄学,而是三样东西的统一:会话状态在 Redis、定时任务在 Celery 分布式锁、文件在共享存储。
先看配置。每个应用节点从同一份 config_example.yml 复制配置,修改SECRET_KEY与BOOTSTRAP_TOKEN为一致的随机串,并保证DB_HOST、REDIS_HOST指向同一套底层服务。镜像构建直接用项目自带的脚本,避免手写 Dockerfile 出错:
# 在源码根目录执行,version 替换为你需要的版本号 bash utils/build_docker.sh v3.10.0构建完成后启动容器,把配置目录、录像存储目录都挂到 NFS 共享路径上,确保两个节点看到的是同一份文件:
docker run -d --name jumpserver \ -v /data/jumpserver/config:/opt/jumpserver/config \ -v /data/jumpserver/share:/opt/jumpserver/share \ -e DB_HOST=192.168.1.6 -e DB_PORT=5432 \ -e REDIS_HOST=192.168.1.7 -e REDIS_PORT=7000 \ -p 8080:8080 \ jumpserver/jumpserver:v3.10.0Celery 是 JumpServer 自动化能力的引擎,改密、批量授权、资产巡检都跑在它上面。多节点下最怕的是同一个定时任务被多个节点重复执行。项目在 apps/ops/celery/utils.py 中实现了基于 Redis 的beat-distribute-start-lock分布式锁,确保全局只有一个 beat 实例在派发任务,worker 则分散在各节点并行消费。你可以直接用同文件里的get_celery_status()逻辑做一次自检:它逐个 ping 所有 worker,只有全部存活才返回 True,这在扩容后值得作为验收脚本跑一遍。
负载层接管流量:健康检查端点这样配置
负载均衡层选择 Nginx + Keepalived:Nginx 负责把请求分发到多个应用节点,Keepalived 提供 VIP,两台负载机之间互相探活,主负载机宕机后 VIP 自动漂移,负载层自身不再是单点。
健康检查是这套架构的灵魂,而 JumpServer 恰好内置了现成的端点。查看 apps/jumpserver/api/health.py 可知,/api/health/接口会真实地做两件事:向数据库发起一次查询、向 Redis 写入并回读一个测试键,两者都成功才返回status: true。这是"真健康检查"而不是"TCP 通就行"——数据库或 Redis 挂了,接口立即返回异常,负载均衡就能及时摘除这台假活的节点。
Nginx 配置要点如下,注意健康检查要打在/api/health/上,而不是网站首页:
upstream jumpserver_backend { server 192.168.1.10:8080 max_fails=3 fail_timeout=30s; server 192.168.1.11:8080 max_fails=3 fail_timeout=30s; } server { listen 80; server_name jumpserver.example.com; # WebSocket 升级支持:JumpServer 的 Web 终端强依赖 location / { proxy_pass http://jumpserver_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 主动健康检查:请求打到内置接口,而非只探 TCP 端口 location = /api/health/ { proxy_pass http://jumpserver_backend/api/health/; proxy_next_upstream error timeout http_500 http_502 http_503 http_504; } }Nginx 层只能感知"HTTP 层面是否正常",感知不到 Celery 是否健康。项目在 utils/check_celery.sh 里给出了一个轻量方案:celery worker 会持续刷新心跳文件,脚本检查心跳文件是否存在且最后修改时间不超过 20 秒,若超时则判定该节点的任务执行能力已丧失。建议把这段检查接到监控系统中,作为应用节点摘除的辅助依据,弥补 HTTP 健康检查的盲区。
故障注入实验:关掉一个节点,验证流量真的自动切换了
光说不练等于没搭。做一次可控的故障注入实验来证明这套架构成立,整个流程建议在业务低峰期进行:
# 1. 先确认两个节点都在负载均衡的后端池中 curl -s http://127.0.0.1/api/health/ | jq .status # 期望 true # 2. 模拟故障:停掉应用节点1 docker stop jumpserver # 3. 持续观察访问日志与健康检查,确认节点1被摘除、节点2接管全部流量 tail -f /var/log/nginx/access.log随后做数据一致性验证:在主库插入一条资产记录,再从另一个会话查询,确认从库已同步;用浏览器在节点2上重新发起一次 Web 终端连接,确认会话能正常建立——这一步验证的是"跨节点会话共享"是否真的打通。
压测方面,用ab对健康检查接口做轻量压测即可(ab -n 1000 -c 100 http://jumpserver.example.com/api/health/),验证负载均衡在并发下没有出现连接堆积。注意压测对象不要选 Web 终端这类长连接接口,避免误判。
监控与告警:盯住这几个指标,把事故消灭在发生前
高可用不是"配完就完",监控是它持续有效的保证。JumpServer 的资源告警模型在 apps/ops/notifications.py 中有现成参考,内置了组件离线、磁盘、内存、CPU 负载四类检查,可据此规划告警阈值:
| 监控对象 | 关键指标 | 建议告警阈值 |
|---|---|---|
| 应用节点 | CPU 平均负载 | 超过 5 告警 |
| 应用节点 | 内存使用率 | 超过 85% 告警 |
| 应用节点 | 磁盘使用率 | 超过 80% 告警 |
| 数据库 | 连接数、复制延迟 | 复制延迟 > 5s 告警 |
| Redis | 内存使用、集群节点存活 | 单节点内存 > 80% 告警 |
| Celery | worker 心跳 | 心跳中断 20s 告警 |
另外把/api/health/的返回值纳入外部监控探针(例如 Prometheus 的 blackbox exporter),一旦返回status: false立即触发 PagerDuty 或企业微信通知。项目还内置了 Prometheus 指标导出端点,配合PROMETHEUS_METRICS_TOKEN可接入完整的指标采集体系。
生产环境避坑清单:备份、升级与灾备演练的实战建议
最后用一张清单收尾,这些都是我在生产环境踩过的坑:
- 备份要独立于集群本身。utils/backup_db.sh 展示了备份脚本的写法,生产环境建议把备份文件放在 NFS 之外的另一台机器或对象存储,避免"集群一起挂、备份一起没"。
- 升级走滚动发布。先停一台应用节点做升级,验证通过后再升级另一台,全程业务不中断;数据库升级务必先提升从库验证,再切换主从。
SECRET_KEY与BOOTSTRAP_TOKEN必须全节点一致。这两者不一致会导致节点间认证错乱,表现为"登录偶尔失败、组件注册异常",排查时极容易误判为网络问题。- 共享存储的读写延迟要纳入验收。录像文件是持续写入的,NFS 网络抖动会导致录像录制中断,建议用
dd实测挂载点的写吞吐后再正式上线。 - 定期做灾备演练。每季度挑一个低峰时段重复上文第三节的故障注入实验,把切换时间记录在案,确保故障恢复流程不是纸面流程。
回到开篇的三个场景:数据库独立成主从后,单机磁盘打满不再拖垮整个平台;节点冗余让升级从"停服赌博"变成"滚动替换";Redis 共享会话让多节点真正成为"一个系统"而不是"多套孤儿"。这套架构的每一块砖,都是从真实故障里敲定的。如果你正打算从单机迁到集群,建议按"存储解耦 → 应用无状态 → 负载接管 → 故障演练"的节奏推进,先在测试环境完整跑一遍故障注入实验,再上生产——高可用不是配置出来的,是验证出来的。
【免费下载链接】jumpserverJumpServer is an open-source Privileged Access Management (PAM) platform that provides DevOps and IT teams with on-demand and secure access to SSH, RDP, Kubernetes, Database and RemoteApp endpoints through a web browser.项目地址: https://gitcode.com/GitHub_Trending/ju/jumpserver
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考