在分布式基础设施管理中,工程师经常面临一个普遍痛点:shell 历史记录的碎片化。当你在数十甚至上百台服务器间跳转时,那些精心调试的命令、复杂的一行脚本往往随着临时服务器的销毁而消失。传统的.bash_history文件本质上是 "本地记忆",在服务器生命周期短暂的环境中几乎无用。更糟糕的是,集中式日志收集方案通常需要在每台目标机器上安装代理,增加了部署复杂性和攻击面。
hc(History Collector)项目正是为解决这一问题而生。它提出了一个 "无聊但实用" 的设计理念:构建一个无代理、多租户的 shell 历史记录收集系统,通过网关级别的零接触捕获,实现跨基础设施的命令历史集中化存储与检索。本文将深入剖析 hc 的架构设计,探讨其工程实现中的关键技术决策。
架构设计理念:从复杂到简单
hc 的核心设计哲学可以概括为三个词:无代理、多租户、grep 友好。与传统的集中式日志系统不同,hc 不追求成为功能完备的 SIEM(安全信息与事件管理)系统,也不试图构建复杂的实时分析引擎。相反,它专注于做好一件事:高效、可靠地收集和检索 shell 命令历史。
项目作者在 Hacker News 上分享道:" 这个工具为那些生活在终端中的工程师而建,他们厌倦了因为临时服务器或碎片化的.bash_history文件而丢失命令历史。" 这种用户中心的定位决定了 hc 的设计方向 —— 它必须足够轻量,部署简单,且与现有工作流无缝集成。
无代理捕获机制:网关级别的零接触设计
传统 shell 历史记录收集方案通常需要在每台目标服务器上安装代理(如 Promtail、Fluentd 等),配置复杂的日志转发规则。这不仅增加了部署和维护成本,还可能引入安全风险。hc 采用了完全不同的思路:在连接网关层面进行被动捕获。
工作原理
hc 的捕获机制基于一个关键观察:大多数工程师通过 SSH 网关或跳板机访问生产服务器。通过在网关层面拦截终端会话,hc 能够重建完整的命令历史,而无需在目标机器上进行任何配置。这种 "飞行中捕获"(in-flight capture)方式提供了高保真的击键和输出记录。
技术实现上,hc 利用了 Bash 的PROMPT_COMMAND环境变量。当工程师通过 SSH 连接到目标服务器时,网关上的脚本会设置如下环境变量:
export SESSION_ID_HC=$(date +%Y%m%d.%H%M%S | sha1sum | cut -c1-8)
export PROMPT_COMMAND='echo "$(date +%Y%m%d.%H%M%S) - ${SESSION_ID_HC} - $(hostname --fqdn) [cwd=$(pwd)] > $(history -w /dev/stdout | tail -n1)" | nc hc.example.com 12345'
这个简单的脚本在每次命令执行后触发,将格式化的命令历史发送到 hc 收集器。关键在于,这个配置只需要在网关(跳板机)上设置一次,所有通过该网关访问的服务器都会自动继承这一行为。
格式标准化
hc 定义了严格的命令格式规范,确保数据的一致性和可解析性:
YYYYMMDD.HHMMSS - SESSIONID - host.example.com [cwd=/path] > command args...
每个字段都有明确含义:
YYYYMMDD.HHMMSS:时间戳,精确到秒SESSIONID:8 字符会话标识符,客户端生成host.example.com:完全限定域名[cwd=/path]:当前工作目录(可选)command args...:实际执行的命令
这种标准化格式不仅便于解析,也为后续的查询和过滤奠定了基础。
多租户隔离:API 密钥驱动的身份管理
在团队协作环境中,不同项目或客户的 shell 历史需要严格隔离。hc 通过 API 密钥机制实现了灵活的多租户支持,每个租户对应一个逻辑隔离单元。
API 密钥架构
hc 的 API 密钥采用两级结构:hc_<key_id>.<secret>。这种设计既保证了安全性,又便于管理。密钥嵌入命令行的方式如下:
]apikey[hc_9f3a1c2d.QmFzZTY0U2VjcmV0] make build
关键设计决策:
- 密钥剥离:API 密钥仅用于认证,在存储前从命令中移除,永远不会出现在数据库或 spool 文件中
- 认证顺序:支持多种认证方式(API 密钥、客户端证书),按配置顺序尝试,第一个成功的认证方式决定租户身份
- 失败回退:如果所有认证方式都失败,命令行被丢弃,避免未授权数据污染
租户数据隔离
在数据库层面,hc 通过tenant_id字段实现物理隔离。所有查询都自动包含租户过滤条件,确保用户只能访问自己租户的数据。这种设计既满足了多团队协作的需求,又符合最小权限原则。
安全传输与存储:TLS 原生与防御性设计
安全是 shell 历史记录收集系统的核心关切。hc 在传输和存储两个层面都采取了防御性设计。
传输安全
hc 支持三种传输模式,按安全性递增:
- 明文 TCP:仅限受信任网络环境使用
- TLS 加密:推荐的生产环境配置,支持服务器证书验证
- TLS 双向认证:最高安全级别,客户端也需要提供证书
对于 TLS 传输,hc 建议使用socat工具而非传统的netcat:
echo "...command..." | socat - OPENSSL:hc.example.com:1235,verify=0
这种设计确保了即使在不可信网络中传输,命令内容也不会被窃听。
存储架构
hc 采用三层存储架构,兼顾性能与可靠性:
shell -> hc -> PostgreSQL
|
+-- spool file (append-only, per-tenant)
- PostgreSQL 权威存储:所有命令最终持久化到数据库,作为单一事实来源
- Spool 文件安全网:每个租户的追加日志文件,在数据库故障时作为临时缓冲区
- 无内存缓存:hc 不维护内存中的历史记录表示,避免数据丢失风险
这种设计体现了 "故障安全" 原则:即使数据库完全不可用,数据也不会丢失,只是暂时缓存在 spool 文件中,待数据库恢复后自动同步。
查询优化:为 grep 而生的检索接口
hc 的查询设计体现了其 "无聊但实用" 的哲学。与大多数日志系统追求复杂的查询语言不同,hc 的接口完全围绕grep优化。
HTTP/HTTPS 导出接口
hc 通过简单的 HTTP 端点提供历史记录导出功能:
wget "http://hc.example.com:8080/export?grep1=qemu&grep2=aarch64&session=8f7f1b24&color=always" -O -
查询参数设计直观:
grep1,grep2,grep3:顺序应用的正则表达式过滤器session:限制到特定会话 IDcolor:ANSI 颜色控制(always/never/auto)limit:结果数量限制order:排序方向(asc/desc)
输出格式与输入格式完全一致,确保了用户的熟悉度。更重要的是,输出是纯文本格式,可以直接管道传输到grep、awk、sed等标准 Unix 工具。
性能考虑
为了优化查询性能,hc 在数据库层面采取了几个关键策略:
- 按时间分区:历史记录表按时间范围分区,加速时间范围查询
- 会话 ID 索引:为会话过滤提供快速路径
- 命令文本全文索引:支持高效的关键词搜索
- 租户前缀索引:确保多租户查询的性能隔离
部署与运维实践
防火墙穿透策略
在实际生产环境中,目标服务器可能位于严格防火墙或 NAT 之后。hc 提供了创新的 SSH 隧道解决方案:
# 通过SSH反向隧道,让远程服务器能够"回拨"到收集器
ssh -R 12345:localhost:12345 user@remote-server
这种技术允许从完全无法直接访问收集器的服务器收集历史记录,只要能够通过 SSH 连接到这些服务器。网关脚本会自动设置隧道并配置PROMPT_COMMAND,整个过程对远程主机零配置。
配置管理
hc 使用单一的 JSON 配置文件(hc-config.json)管理所有运行时参数:
{
"server": {
"listener_clear": {"enabled": true, "bind": ":12345"},
"listener_tls": {"enabled": true, "bind": ":12346"},
"http": {"enabled": true, "bind": ":8080"},
"https": {"enabled": true, "bind": ":8443"}
},
"tenants": [...],
"db": {"type": "postgres", "dsn": "..."},
"tls": {"cert_file": "...", "key_file": "..."},
"limits": {"max_line_size": 8192}
}
这种显式配置风格虽然冗长,但提供了完全的透明度和可预测性,符合基础设施即代码的最佳实践。
监控与告警
虽然 hc 本身不提供复杂的监控功能,但它设计了良好的可观测性接口:
- 健康检查端点:
/health端点提供基本的服务状态 - 指标导出:通过 Prometheus 格式导出关键指标(连接数、吞吐量、错误率)
- 结构化日志:所有操作都记录结构化日志,便于集成到现有监控栈
局限性与未来方向
当前限制
hc 项目目前处于 v0.3 阶段,存在一些已知限制:
- 手动租户管理:需要直接操作数据库创建租户和用户 ID
- SQLite 支持待完成:目前仅支持 PostgreSQL,轻量级后端仍在开发
- Web UI 非优先:项目专注于 CLI 体验,图形界面不是开发重点
- 外部工具依赖:TLS 传输依赖
socat,增加了部署复杂度
工程权衡
hc 的设计体现了几个关键的工程权衡:
- 简单性 vs 功能性:选择做好核心功能,而非构建全能系统
- 零配置 vs 控制粒度:牺牲细粒度控制以换取部署简便性
- 文本接口 vs 图形界面:优先终端用户体验,符合目标用户的工作习惯
- PostgreSQL vs 其他存储:选择成熟的关系数据库而非 NoSQL,确保数据一致性
这些权衡反映了项目对目标场景的深刻理解:工程师需要的是可靠、简单、与现有工具链集成的解决方案,而不是功能臃肿的 "瑞士军刀"。
总结
hc 项目展示了如何通过精心的架构设计解决一个看似简单但实际复杂的问题。其无代理、多租户的 shell 历史记录收集方案,在简化部署、增强安全性和提高可用性之间找到了优雅的平衡点。
从工程角度看,hc 的成功源于几个关键洞察:
- 利用现有模式:在网关层面捕获,而非改造每台服务器
- 尊重用户习惯:为
grep优化的接口,而非发明新查询语言 - 防御性设计:多层存储、密钥剥离、故障安全机制
- 明确的范围:专注于核心功能,避免功能蔓延
正如项目文档所述,hc 是 "无聊的"—— 它不做花哨的事情,只是可靠地完成工作。但在基础设施工程中,这种 "无聊" 往往是最珍贵的品质。当你在凌晨三点调试生产问题时,能够快速找到六个月前在已销毁服务器上运行的那个复杂命令,这种可靠性远比任何华丽的界面更有价值。
hc 的架构为类似的数据收集问题提供了可借鉴的模式:通过巧妙的协议设计减少部署负担,通过严格的数据隔离支持多租户,通过简单的接口降低使用门槛。这些原则不仅适用于 shell 历史记录收集,也适用于更广泛的日志和遥测数据管理场景。
资料来源:
- Hacker News 帖子:Show HN: Hc: an agentless, multi-tenant shell history sink
- GitHub 仓库:alessandrocarminati/hc - History Collector
- 集中式日志系统设计模式相关技术文章