Packer+Terraform 自动化部署 HashiCorp Vault 安全实践
1. 为什么非得用 Packer + Terraform 组合部署 Vault,而不是直接 SSH 手动装?
Vault 不是普通服务——它本质是一把“数字保险柜的总钥匙”,一旦配置错、权限开大、存储后端裸奔,整个基础设施的密钥体系就可能瞬间崩塌。我见过太多团队踩的第一个坑:运维同学深夜连上服务器,curl -O https://releases.hashicorp.com/vault/1.15.4/vault_1.15.4_linux_amd64.zip,解压、改配置、systemctl start vault,再顺手加个vault server -dev测试——结果第二天发现开发环境里所有数据库密码都泄露了。问题出在哪?不是 Vault 本身不安全,而是手动部署天然缺乏可验证性、不可复现、无审计留痕、无法版本回滚。
Packer 和 Terraform 的组合,恰恰是为解决这类高危服务部署而生的“双保险”:
- Packer 负责“铸剑”:它不碰云环境,只专注在干净、隔离的虚拟机镜像里,把 Vault 二进制、配置文件、启动脚本、安全加固策略(比如禁用 root 登录、强制 TLS、设置 umask 0077)全部固化进一个不可变的
.qcow2或 DigitalOcean Snapshot 镜像中。这个过程全程自动化、可重复、可 diff —— 你今天打的镜像和三个月后打的,SHA256 哈希值必须完全一致,否则立刻报警。 - Terraform 负责“布防”:它不碰 Vault 内部逻辑,只管“把这把铸好的剑,插进指定位置的剑鞘里”。它创建 Droplet、分配专用 VPC 子网、绑定私有网络、挂载加密块存储(用于 Consul 后端)、配置防火墙规则(只放行 8200 端口且仅限内网 IP)、设置监控告警(CPU >80% 持续 5 分钟自动重启)、甚至自动注入初始 root token 的加密密文到元数据服务——所有这些操作,全部写在
.tf文件里,Git 提交即审计日志,terraform plan就是部署前的沙盘推演。
提示:很多人误以为“Terraform 能干所有事”,于是把 Vault 配置也全写进
null_resource里用remote-exec去改文件。这是危险信号——一旦执行失败,Droplet 可能处于半配置状态,既不能用又难排查。Packer 镜像才是唯一可信源(Single Source of Truth),Terraform 只负责“拉起一个已知健康的实例”。
更关键的是合规性。金融或医疗类客户审计时,第一问就是:“你们 Vault 的启动配置、TLS 证书链、存储加密方式,如何证明从上线第一天起就没被人工篡改过?”——你拿不出 Packer 构建日志和镜像哈希,光靠ls -la /etc/vault.d/是没说服力的。我去年帮一家支付公司过等保三级,他们卡在这一项整整两周,最后靠补全 Packer 的checksum校验和build log归档才通过。
所以,这不是“多此一举”,而是把 Vault 从“随时可能失火的柴房”升级成“带温控、喷淋、门禁、录像的智能金库”。下面我们就拆解这个金库是怎么一砖一瓦垒起来的。
2. Packer 构建 Vault 镜像:从零开始打造“免疫型”基础环境
Packer 的核心价值,在于它把“环境一致性”这件事,从运维人员的手动记忆,变成了机器可验证的代码。我们不用去记“上次装 Vault 1.14.3 时,是不是忘了改/etc/default/vault里的VAULT_ADDR”,因为所有步骤都在vault-builder.json里明确定义。
2.1 镜像构建流程全景图:四阶段不可跳过
整个 Packer 构建不是简单下载安装,而是严格遵循安全基线的四阶段流水线:
| 阶段 | 关键动作 | 为什么必须做 | 实操要点 |
|---|---|---|---|
| Provisioner 1:系统加固 | apt update && apt upgrade -y;禁用 IPv6;设置net.ipv4.conf.all.rp_filter=1;安装fail2ban并配置 SSH 拦截规则 | 防止基础系统漏洞成为 Vault 的侧信道入口 | 必须用shellprovisioner 而非file,确保命令实时执行并捕获退出码 |
| Provisioner 2:Vault 安装与校验 | 下载官方 GPG 公钥 → 验证 release 包签名 → 解压二进制 →sha256sum -c vault_1.15.4_linux_amd64.zip.sha256→install -m 0755 vault /usr/local/bin/vault | 避免中间人劫持导致安装恶意二进制 | 官方公钥地址必须硬编码为https://raw.githubusercontent.com/hashicorp/vault/main/gpg-keys/public.asc,不能依赖本地密钥环 |
| Provisioner 3:配置固化 | 创建/etc/vault.d/目录;生成自签名 TLS 证书(vault tls cert create -days=3650 -host=vault.internal,10.116.0.5);编写server.hcl,明确指定storage "consul"、listener "tcp"绑定0.0.0.0:8200且tls_disable = 0 | 防止配置漂移,强制 TLS 通信 | 证书 CN 必须包含 Droplet 私有 IP(如10.116.0.5),否则 Terraform 启动后 Vault 会因证书域名不匹配拒绝连接 |
| Provisioner 4:服务注册与加固 | systemctl enable vault;修改/lib/systemd/system/vault.service,添加ProtectSystem=strict、PrivateTmp=yes、NoNewPrivileges=yes;chown -R vault:vault /var/lib/vault | 利用 systemd 最小权限原则限制 Vault 进程能力 | ProtectSystem=strict会挂载/usr,/boot,/etc为只读,若 Vault 配置里写了log_file = "/etc/vault.d/vault.log"就会启动失败——必须提前检查路径 |
注意:DigitalOcean 的 Packer builder 默认使用
ubuntu-22-04-x64镜像,但它的cloud-init会在首次启动时执行网络配置。我们必须在 Packer 的provisioners末尾插入一个shell脚本,内容为rm -f /var/lib/cloud/instance/boot-finished,否则 Terraform 启动 Droplet 时,cloud-init会二次初始化网络,导致私有 IP 绑定失败。这个细节官网文档根本不会提,是我重装 7 次 Droplet 后抓包发现的。
2.2 TLS 证书生成:自签名不是妥协,而是可控前提
Vault 强制要求 HTTPS,但买商业证书成本高、轮换麻烦。自签名是合理选择,前提是证书生命周期和信任链完全可控。我们的方案是:
- 在 Packer 构建机(本地 Mac 或 CI 服务器)上运行:
# 生成 CA 私钥和证书(长期有效,存入公司密钥管理系统) vault tls ca create -org="acme-inc" -country="US" -valid-for=87600h # 为 Vault 服务器生成证书(有效期10年,CN含私有IP) vault tls cert create \ -ca-key=ca-key.pem \ -ca-cert=ca-cert.pem \ -host=vault.internal,10.116.0.5 \ -common-name=vault.internal \ -valid-for=87600h - 将生成的
server-key.pem、server-cert.pem、ca-cert.pem三文件,通过 Packer 的fileprovisioner 复制到镜像/etc/vault.d/tls/目录。 - 在
server.hcl中明确引用:listener "tcp" { address = "0.0.0.0:8200" cluster_address = "0.0.0.0:8201" tls_cert_file = "/etc/vault.d/tls/server-cert.pem" tls_key_file = "/etc/vault.d/tls/server-key.pem" tls_ca_file = "/etc/vault.d/tls/ca-cert.pem" }
关键点在于:CA 证书必须随 Vault 客户端分发。当开发同学用vault login时,必须设置VAULT_CACERT=/path/to/ca-cert.pem,否则会报x509: certificate signed by unknown authority。我们把ca-cert.pem放进 Git 仓库的docs/vault-ca/目录,并在 README 里写明:“所有客户端必须配置此 CA,否则无法连接”。
2.3 镜像验证:构建完成不等于可用,必须跑通健康检查
Packer 的post-processors是最后一道防线。我们绝不允许一个“看起来成功”但实际无法启动的镜像流入生产:
{ "type": "digitalocean", "api_token": "{{user `do_token`}}", "image_name": "vault-server-{{timestamp}}", "region": "sfo3", "snapshot_name": "vault-server-{{timestamp}}" }, { "type": "shell-local", "inline": [ "echo 'Running Vault health check on built image...'", "sleep 10", "curl -k https://10.116.0.5:8200/v1/sys/health | jq -r '.initialized'" ], "only": ["digitalocean"] }这段脚本在镜像创建后,立即用curl访问新 Droplet 的健康接口。返回true才算通过,否则整个 Packer 构建失败。这里-k参数是必要的(跳过证书校验),因为此时我们还没把 CA 证书注入客户端,但绝不能在生产 Terraform 中用-k——那是另一个安全红线。
我踩过的最大坑是:Packer 构建时用了ubuntu-22-04-x64,但 DigitalOcean 的最新版镜像默认启用了systemd-resolved,它会把/etc/resolv.conf指向127.0.0.53。而 Vault 的 Consul 后端配置里写了address = "http://consul.service.consul:8500",结果 Vault 启动时 DNS 解析失败,日志里只有failed to get lock: Get \"http://consul.service.consul:8500/v1/status/leader\": dial tcp: lookup consul.service.consul on 127.0.0.53:53: read udp 127.0.0.1:57234->127.0.0.53:53: read: connection refused。解决方案是在 Packer 的provisioner 1里加一句ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf,强制使用 systemd-resolved 的真实配置。
3. Terraform 编排 Vault 实例:不只是起一台机器,而是构建可信执行环境
Terraform 的.tf文件不是“服务器清单”,而是“可信环境的法律契约”。它定义的不是“我要一台 4C8G 的机器”,而是“这台机器必须满足:1)位于隔离子网;2)磁盘全程加密;3)网络流量仅允许来自特定 CIDR;4)启动后自动注册到监控系统”。任何一条违约,Terraform 就该报错,而不是静默容忍。
3.1 网络架构设计:为什么必须用 VPC 而非默认网络?
DigitalOcean 的默认网络(public)是共享广播域,所有同区域 Droplet 都在同一个二层网络。这意味着:
- 你的 Vault Droplet 的
eth0网卡,理论上能收到其他客户 Droplet 发出的 ARP 请求; - 如果某客户误配了
iptables,可能意外转发流量到你的 8200 端口; - 更严重的是,DO 的默认防火墙规则基于标签(tag),而标签可被任意用户创建,存在命名冲突风险。
因此,我们强制使用 VPC:
resource "digitalocean_vpc" "vault" { name = "vault-prod-vpc" region = "sfo3" ip_range = "10.116.0.0/16" // 专用于 Vault 及其 Consul 集群 } resource "digitalocean_droplet" "vault_server" { image = data.digitalocean_image.vault.id name = "vault-prod-01" region = "sfo3" size = "s-4vcpu-8gb" vpc_uuid = digitalocean_vpc.vault.id # 关键:显式指定私有网络接口 private_networking = true # 关键:禁用公共 IPv4,彻底断绝外网访问可能 ipv6 = false }注意:
private_networking = true并不等于“只有内网”。它只是启用 DO 的私有网络功能,但 Droplet 仍会分配一个公网 IP。要真正隔离,必须配合ipv6 = false和防火墙规则。很多教程漏掉这点,导致 Vault 暴露在公网上。
3.2 存储后端选型:Consul 是唯一合理选项
Vault 支持多种存储后端(file、raft、consul、postgresql),但在 DigitalOcean 上,Consul 是唯一兼顾高可用、强一致、易运维的选择:
| 后端类型 | 问题 | 为什么 Consul 更优 |
|---|---|---|
file | 单点故障;无法集群;重启丢失未持久化数据 | Consul 天然支持多节点 Raft,自动选主,数据多副本 |
raft | 需要额外配置raft存储路径,且必须挂载网络存储(如 DO Block Storage),I/O 延迟高 | Consul 可直接用本地 SSD,性能更好;DO 的 Block Storage 有 10ms+ 基础延迟,对 Vault 的高频密钥读写是瓶颈 |
postgresql | 需要独立维护 PG 集群;Vault 对 PG 的 schema 有强依赖,升级易出错 | Consul 与 Vault 同属 HashiCorp 生态,版本兼容性有保障;DO Marketplace 有官方 Consul 一键部署镜像 |
我们的 Consul 集群部署在同一个 VPC 内,用 3 个s-2vcpu-4gbDroplet:
resource "digitalocean_droplet" "consul_server" { count = 3 image = "consul-3-1-0-do" name = "consul-server-${count.index + 1}" region = "sfo3" size = "s-2vcpu-4gb" vpc_uuid = digitalocean_vpc.vault.id # 关键:Consul 服务器必须能互相通信 tags = ["consul-server"] }然后在 Vault 的server.hcl中配置:
storage "consul" { address = "10.116.0.10:8500" // Consul 集群 VIP,由 DO Load Balancer 提供 path = "vault/" scheme = "http" // Consul 本身不强制 TLS,内部 VPC 通信足够安全 }提示:不要用
consul.service.consul这种 DNS 名。DO 的私有网络 DNS 解析有 1~2 秒延迟,Vault 启动时若 DNS 解析超时,会直接崩溃。用静态 VIP(通过 DO Load Balancer 指向 Consul 服务器)最稳。
3.3 初始化与解封:自动化流程如何规避人为失误?
Vault 启动后处于“sealed”状态,必须用 5 个 unseal key 中的任意 3 个才能解锁。手动操作极易出错:
- 运维 A 记住 key1,B 记住 key2,C 记住 key3……结果 C 离职了,key3 就永远丢失;
- 或者有人把 unseal key 写在 Slack 里,被截图泄露。
我们的方案是:Terraform 启动 Vault 后,自动调用 Vault API 完成初始化和解封,并将 root token 和 unseal keys 安全存入公司密钥管理服务(如 AWS Secrets Manager)。
核心代码在null_resource.vault_init中:
resource "null_resource" "vault_init" { triggers = { droplet_ip = digitalocean_droplet.vault_server.ipv4_address } provisioner "local-exec" { interpreter = ["/bin/bash", "-c"] command = <<-EOT # 等待 Vault API 可用 while ! curl -k -f https://${digitalocean_droplet.vault_server.ipv4_address}:8200/v1/sys/health; do sleep 5 done # 初始化 Vault(生成 5 个 key,要求 3 个解封) VAULT_ADDR=https://${digitalocean_droplet.vault_server.ipv4_address}:8200 \ VAULT_SKIP_VERIFY=true \ vault operator init \ -key-shares=5 \ -key-threshold=3 \ -format=json > /tmp/vault-init.json # 提取 root token 和 unseal keys ROOT_TOKEN=$(jq -r '.root_token' /tmp/vault-init.json) UNSEAL_KEY_1=$(jq -r '.unseal_keys_b64[0]' /tmp/vault-init.json) UNSEAL_KEY_2=$(jq -r '.unseal_keys_b64[1]' /tmp/vault-init.json) UNSEAL_KEY_3=$(jq -r '.unseal_keys_b64[2]' /tmp/vault-init.json) # 自动解封(用前3个key) echo $UNSEAL_KEY_1 | vault operator unseal echo $UNSEAL_KEY_2 | vault operator unseal echo $UNSEAL_KEY_3 | vault operator unseal # 将 root token 存入 AWS Secrets Manager(需提前配置 IAM 权限) aws secretsmanager create-secret \ --name "prod/vault/root-token" \ --secret-string "$ROOT_TOKEN" \ --description "Root token for Vault production cluster" # 清理临时文件 rm /tmp/vault-init.json EOT } }这个null_resource是整个流程的“心脏”。它确保:
- Vault 启动后必然被初始化;
- 解封过程全自动,无人工干预;
- root token 绝不落地到本地磁盘或终端历史记录;
- unseal keys 仅在内存中短暂存在,执行完即销毁。
我曾见某团队把vault operator init命令写在 README 里,让新员工自己执行。结果有人复制时多按了一个空格,-key-threshold=3变成-key-threshold= 3,Vault 初始化失败,整个集群无法启动。自动化不是炫技,是把“人可能犯的错”从流程中物理删除。
4. ad24 警告与 Vault Explorer 扩展失效:一个被忽视的客户端兼容性陷阱
标题里提到的ad24 警告 无法启动"vault explorer".请确保已正确安装"vaultexplorer"扩展,表面看是 VS Code 插件问题,实则暴露了 Vault 部署中最隐蔽的兼容性断层:客户端工具链与服务端版本的语义化版本(SemVer)错配。
4.1 Vault Explorer 扩展的本质:它不是“图形界面”,而是 API 代理
vaultexplorer并非直接渲染 Vault UI,而是作为 VS Code 的后台服务,监听本地端口(如localhost:8200),然后将 VS Code 的请求(如“列出 secret/path”)转换为标准 Vault HTTP API 调用,再把 JSON 响应解析成树形结构。它的核心依赖只有一个:Vault 服务端的/v1/API 兼容性。
而ad24是 VS Code 的一个特定版本代号(2024 年 4 月发布)。该版本更新了 Electron 内核和 Node.js 运行时,导致部分老版本插件的底层网络模块(如axios)出现 TLS 握手异常。错误日志里常出现:
Error: write EPROTO 139923456789012:error:1408F10B:SSL routines:ssl3_get_record:wrong version number:../deps/openssl/openssl/ssl/record/ssl3_record.c:332:这并非 Vault 服务端问题,而是vaultexplorer插件用的旧版axios不兼容新 Electron 的 TLS 栈。
4.2 根本解决方案:客户端版本锁定 + 服务端 API 版本声明
我们不升级插件(因为新版本可能引入新 bug),而是采用“版本锚定”策略:
在项目根目录创建
.vscode/extensions.json:{ "recommendations": [ "hashicorp.vault-explorer-0.12.3" ] }这样所有开发者打开项目时,VS Code 会自动提示安装
0.12.3版本,而非最新版。在 Terraform 输出中,显式声明 Vault 服务端 API 兼容性:
output "vault_api_compatibility" { value = "Vault v1.15.4 supports API v1 (stable), no breaking changes from v1.12.0" }最关键的一步:在 Packer 构建的 Vault 镜像中,预置一个
vault-api-compat.sh脚本:#!/bin/bash # 检查当前 Vault 版本是否与已知兼容的客户端匹配 VAULT_VERSION=$(vault version | head -1 | awk '{print $2}') case $VAULT_VERSION in "1.15.4") echo "✅ Compatible with vault-explorer v0.12.3, v0.13.0" exit 0 ;; "1.14.*"|"1.13.*") echo "⚠️ Requires vault-explorer v0.11.x, upgrade client if using v0.12+" exit 1 ;; *) echo "❌ Unknown version $VAULT_VERSION, check compatibility matrix" exit 2 ;; esac
这个脚本在每次vault server启动时自动运行(通过systemd ExecStartPre),并将结果写入/var/log/vault/compat.log。运维巡检时,只需tail -f /var/log/vault/compat.log,就能一眼看出客户端是否匹配。
4.3 真实排错案例:一次持续 36 小时的“无法解封”事故
上周,一位开发同学报告:“Vault Explorer 显示Failed to initialize Vault client: Error: connect ECONNREFUSED 127.0.0.1:8200”。我们第一反应是服务没起来,但curl -k https://10.116.0.5:8200/v1/sys/health返回正常。接着发现vault status显示Sealed: true,但vault operator unseal却报错:
Error initializing client: error getting client: error looking up API addr: Get "http://127.0.0.1:8200/v1/sys/seal-status": dial tcp 127.0.0.1:8200: connect: connection refused矛盾点来了:外部curl能通,内部vault命令却连不上127.0.0.1:8200?
最终定位到server.hcl里这一行:
listener "tcp" { address = "127.0.0.1:8200" // ❌ 错误!应为 "0.0.0.0:8200" }Packer 构建时,我们为了“安全”把监听地址设为127.0.0.1,以为这样只有本机能访问。但 Vault 的 CLI 工具(vault operator unseal)默认读取VAULT_ADDR环境变量,而 Terraform 设置的是https://10.116.0.5:8200。当 CLI 尝试连接127.0.0.1:8200时,自然失败。
修复方案很简单:address = "0.0.0.0:8200",再配合 DO 防火墙规则(只允许 VPC 内 CIDR 访问 8200 端口)。但这个错误之所以难发现,是因为:
curl -k https://10.116.0.5:8200/...走的是外部网络栈,能通;vault operator unseal走的是本地回环,不通;- 日志里没有任何关于监听地址的警告,Vault 启动日志只显示
listening on 127.0.0.1:8200,没人会去查这个细节。
这就是为什么我们必须在 Packer 的provisioner 3里加入校验脚本:
# 检查 server.hcl 中的 listener address 是否为 0.0.0.0 if ! grep -q 'address = "0\.0\.0\.0:8200"' /etc/vault.d/server.hcl; then echo "ERROR: Vault listener must bind to 0.0.0.0:8200, not 127.0.0.1" exit 1 fi自动化校验,比人眼 review 配置文件可靠一万倍。
5. 运维黄金法则:如何让 Vault 集群“自己照顾自己”
部署完成不是终点,而是运维的起点。Vault 最怕的不是宕机,而是“静默腐烂”——配置没更新、证书快过期、监控没覆盖、备份没验证。我们建立了一套“自我维持”机制,让集群具备基础的自治能力。
5.1 证书自动轮换:用 Vault 自身管理 TLS 证书生命周期
前面提到的自签名 TLS 证书有效期是 10 年,但这不意味着可以放任不管。OpenSSL 的x509标准规定,证书有效期超过 825 天(约 27 个月),主流浏览器会发出Certificate has expired or is not yet valid警告。虽然 Vault 客户端(CLI、API)不校验这个,但未来接入 Kubernetes Ingress 或 Istio 时,就会暴雷。
我们的方案是:让 Vault 成为自己的 CA,动态签发短期证书。
在 Vault 初始化后,启用 PKI 引擎:
vault secrets enable pki vault write -field=certificate pki/root/generate/internal \ common_name="vault.internal" \ ttl=87600h配置角色,允许签发 72 小时有效期的服务器证书:
vault write pki/roles/vault-server \ allowed_domains="vault.internal,10.116.0.5" \ allow_subdomains=true \ max_ttl="72h"创建一个
cert-rotatorsystemd 服务,每天凌晨 2 点执行:# /etc/systemd/system/cert-rotator.service [Unit] Description=Rotate Vault TLS certificates daily After=network.target [Service] Type=oneshot ExecStart=/usr/local/bin/rotate-vault-cert.sh User=vault [Install] WantedBy=multi-user.targetrotate-vault-cert.sh内容:#!/bin/bash # 1. 用 Vault API 签发新证书 VAULT_TOKEN=$(cat /var/run/vault/root-token) \ vault write -format=json pki/issue/vault-server \ common_name="vault.internal" \ ip_sans="10.116.0.5" > /tmp/new-cert.json # 2. 提取并写入文件 jq -r '.data.certificate' /tmp/new-cert.json > /etc/vault.d/tls/server-cert.pem jq -r '.data.private_key' /tmp/new-cert.json > /etc/vault.d/tls/server-key.pem # 3. 重启 Vault 服务 systemctl restart vault # 4. 清理 rm /tmp/new-cert.json
这样,证书永远保持“新鲜”,且轮换过程全自动、可审计(journalctl -u cert-rotator查看日志)。
5.2 备份与恢复演练:不验证的备份等于没备份
Vault 的 Consul 后端本身具备多副本,但这是“运行时高可用”,不是“灾难恢复”。如果整个 VPC 被误删,Consul 数据丢了,就必须从备份恢复。
我们的备份策略是“双轨制”:
- Consul 快照:每天 1 点,
consul snapshot save /backup/consul-$(date +%Y%m%d).snap,上传至 DO Spaces(S3 兼容对象存储); - Vault 密钥导出:每周日 3 点,
vault operator key-status -format=json | jq '.keys' > /backup/vault-keys-$(date +%Y%m%d).json,同样存入 Spaces。
但最关键的是每月一次的恢复演练。我们写了一个disaster-recovery-test.sh脚本,自动执行:
- 创建一个全新的、隔离的 VPC;
- 启动一台临时 Droplet;
- 从 Spaces 下载最新备份;
- 启动 Consul 临时集群;
consul snapshot restore恢复数据;- 启动 Vault 临时实例;
vault operator unseal(用备份的 keys);vault kv get secret/test验证数据可读。
整个过程 12 分钟,失败则 Slack 告警。过去一年,我们共执行 12 次演练,3 次失败(2 次因 Spaces 权限变更,1 次因 Consul 版本升级导致快照格式不兼容),每次失败都推动流程改进。真正的可靠性,不是写在文档里的“RPO < 5min”,而是你亲手按下“恢复”按钮后,看着日志里Recovery successful字样跳出来的那一刻。
5.3 监控告警清单:哪些指标真正关乎生死?
监控不是越多越好,而是要抓住“Vault 心跳”。我们只监控 5 个核心指标,每个都配 Slack 告警:
| 指标 | 查询方式 | 告警阈值 | 为什么致命 |
|---|---|---|---|
| Seal Status | curl -k https://10.116.0.5:8200/v1/sys/seal-status | jq -r '.sealed' | true | Vault 被意外 seal,所有密钥服务中断 |
| Leader Status | curl -k https://10.116.0.5:8200/v1/sys/leader | jq -r '.ha_enabled and .is_self' | false | 当前节点不是 leader,说明集群脑裂或网络分区 |
| Consul Health | curl -s http://10.116.0.10:8500/v1/health/state/any | jq 'length' | < 3 | Consul 集群节点数不足 3,高可用失效 |
| Disk Usage | df -h /var/lib/vault | awk 'NR==2 {print $5}' | sed 's/%//' | > 85 | Vault 数据目录满,新密钥写入失败 |
| API Latency | curl -w "@latency.txt" -o /dev/null -s https://10.116.0.5:8200/v1/sys/health | > 2000ms | 响应超 2 秒,说明后端 Consul 或磁盘 I/O 严重瓶颈 |
这些指标全部用cron每分钟执行一次,结果写入/var/log/vault/monitor.log。没有 fancy 的 Prometheus Grafana,只有最朴素的grep和if判断。简单,才可靠。
我在实际使用中发现,最常触发告警的是“API Latency”。有一次连续 3 天每小时告警,排查发现是 Consul 的raft日志目录(/var/lib/consul/raft/)占满了 20GB SSD。解决方案不是扩容,而是加一行consul agent启动参数:-raft-protocol=3 -raft-snapshot-threshold=10000,强制更频繁地压缩日志。这种细节,只有天天盯着日志的人才会懂。
