用Packer+Terraform在DigitalOcean构建生产级Vault密钥中枢

📅 2026/6/22 17:45:50
用Packer+Terraform在DigitalOcean构建生产级Vault密钥中枢
1. 这不是“部署一个 Vault”而是在云上重建一套可信密钥生命周期管理中枢你有没有遇到过这样的场景团队刚上线一个新服务开发随手把数据库密码写进 Git 仓库运维临时改配置把 AWS Access Key 明文贴在 Slack 里CI/CD 流水线里硬编码了加密密钥每次轮换都得手动改三处 YAML。这些不是疏忽而是缺乏一套被基础设施原生信任的、可审计、可策略化、可自动轮换的密钥分发机制。HashiCorp Vault 正是为解决这个问题而生——但它本身不是开箱即用的“密码保险柜”而是一套需要被正确装配、加固、集成进基础设施血液里的可信根Root of Trust。我第一次在 DigitalOcean 上跑起 Vault 时用的是curl下载二进制 systemd手动启停 vault server -dev临时测试。两周后当需要接入 Kubernetes Secret 注入、对接 LDAP 用户同步、启用 Transit 引擎做应用级加密时整套环境崩了证书过期没人管、策略配置散落在不同人的终端历史里、备份脚本压根没测试过恢复流程。这才意识到Vault 的价值不在于它能存什么而在于它如何被构建、如何被交付、如何被持续验证。Packer 和 Terraform 不是“又两个要学的工具”它们是把 Vault 从“一个运行中的进程”升维成“一条可版本化、可测试、可回滚的可信交付流水线”的关键齿轮。本文标题里的 “Quickstart” 并非指“5分钟点点鼠标就完事”而是指用基础设施即代码IaC的方式把 Vault 服务器从零构建、配置、加固、上线的完整路径压缩到一次packer build 一次terraform apply的确定性操作中。它跳过了所有手工干预环节——没有 SSH 登录、没有手动编辑/etc/vault.d/config.hcl、没有vault operator init后手抄 root token。整个过程像编译一个 Go 程序一样输入是声明式配置输出是经过预验证、带签名、可复现的 Vault 镜像与运行实例。这正是现代云原生安全基建的起点可信必须从构建那一刻开始。核心关键词已自然嵌入HashiCorp Vault是目标系统Packer负责构建不可变的 Vault 镜像含配置、证书、启动脚本Terraform负责在DigitalOcean上申请 Droplet、配置网络、挂载块存储、注入初始化令牌并确保整个资源栈符合最小权限原则。这不是教程的堆砌而是对“为什么必须用 PackerTerraform 构建 Vault”这一底层逻辑的拆解——它关乎一致性、可审计性、灾难恢复能力以及最重要的让安全不再依赖于某个人的记忆或某次手工操作的准确性。2. Packer 构建 Vault 镜像为什么不能直接用官方 Docker 镜像或 apt 安装很多人看到 Vault 就立刻去拉hashicorp/vault:latest镜像或者在 Ubuntu 上apt install vault。这在本地开发或 PoC 阶段完全没问题但一旦进入生产环境这种做法会埋下三个无法绕过的地雷地雷一配置漂移Configuration Driftapt install安装的 Vault 二进制文件路径固定如/usr/bin/vault但配置文件/etc/vault.d/config.hcl却由人手写。下次更新系统、重装机器、甚至只是apt upgrade都可能覆盖掉你精心调优的监听地址、TLS 设置或存储后端参数。Packer 构建的镜像则把 Vault 二进制、配置文件、启动脚本、TLS 证书全部打包进一个只读根文件系统。Droplet 启动时它加载的就是那个被packer validate验证过、被 CI 流水线打上 Git SHA 标签的镜像——配置不会漂移因为镜像就是配置。地雷二证书与密钥的“冷启动”困境Vault 启动必须有 TLS 证书否则 HTTP 监听不安全、必须有存储后端如 Consul 或 File的访问凭证、首次初始化需要安全保管 root token 和 unseal keys。如果这些全靠terraform provisioner remote-exec在 Droplet 启动后 SSH 进去生成那么证书生成命令如openssl req -x509 ...散落在 Terraform 模板里无法版本化vault operator init的输出root token、unseal keys必须通过local-exec捕获并写入本地文件这个过程本身就不安全token 可能出现在 shell history 或日志中更致命的是如果 Droplet 创建失败、网络中断、SSH 超时整个初始化流程就卡死没有原子性保障。Packer 的解法是在镜像构建阶段就完成所有“一次性、不可逆、需高度保密”的初始化动作。我们使用shell构建器在临时虚拟机里下载指定版本的 Vault 二进制如vault_1.15.4_linux_amd64.zip校验 SHA256生成自签名 CA 证书和服务器证书vault-server.crt/vault-server.key并确保证书 SAN 包含 Droplet 的私有 IP 和 DNS 名如vault.internal创建/etc/vault.d/config.hcl明确指定listener tcp的tls_cert_file和tls_key_file并设置storage file指向/var/lib/vault最关键一步执行vault server -config/etc/vault.d/config.hcl -dev启动一个临时 Vault 实例调用vault operator init -key-shares3 -key-threshold2生成初始密钥并将root_token和unseal_keys_b64安全写入/var/lib/vault/init-secrets.json该文件权限设为0600仅 root 可读关闭临时 Vault清理临时数据只保留/var/lib/vault/init-secrets.json和证书文件。这样构建出的镜像启动时无需任何外部交互Droplet 启动后systemd服务直接读取/var/lib/vault/init-secrets.json中的 root token自动完成 unseal调用vault operator unseal三次然后以server模式正式运行。整个过程在镜像内完成无网络依赖、无 SSH 风险、可完全自动化。地雷三安全基线缺失官方镜像默认开启所有 Vault 功能UI、API、Metrics监听0.0.0.0:8200且未禁用危险的dev模式参数。Packer 构建时我们强制在config.hcl中设置disable_mlock true因 DigitalOcean Droplet 默认无mlock权限否则 Vault 启动失败api_addr和cluster_addr显式绑定到私有 IP如https://10.128.0.10:8200禁止公网监听ui false生产环境禁用 Web UI所有操作走 APIlog_level info避免 debug 日志泄露敏感信息使用file存储后端时path /var/lib/vault/data并确保目录属主为vault:vault。提示Packer 构建的镜像体积会比裸二进制大约 300MB因为它包含了完整的 Ubuntu base、OpenSSL、curl 等依赖。这是为“安全确定性”付出的合理代价。如果你追求极致轻量可改用docker构建器构建容器镜像再通过 Terraform 的digitalocean_dropletcloud-init启动容器但复杂度会上升。下面是一个精简但生产可用的vault-packer.json配置核心片段基于 Ubuntu 22.04{ variables: { vault_version: 1.15.4, do_token: {{env DIGITALOCEAN_TOKEN}} }, builders: [{ type: digitalocean, api_token: {{user do_token}}, image: ubuntu-22-04-x64, region: sfo3, size: s-2vcpu-4gb, ssh_username: root }], provisioners: [{ type: shell, inline: [ apt-get update apt-get install -y curl unzip openssl jq, cd /tmp, curl -fsSL https://releases.hashicorp.com/vault/{{user vault_version}}/vault_{{user vault_version}}_linux_amd64.zip -o vault.zip, unzip vault.zip chmod x vault mv vault /usr/local/bin/, mkdir -p /etc/vault.d /var/lib/vault/{data,logs}, chown -R vault:vault /var/lib/vault ] },{ type: shell, environment_vars: [VAULT_VERSION{{user vault_version}}], inline: [ cd /tmp, # 生成 CA 和服务器证书, openssl genrsa -out ca.key 2048, openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj /CNvault-ca, openssl genrsa -out vault-server.key 2048, openssl req -new -key vault-server.key -out vault-server.csr -subj /CNvault.internal -addext subjectAltName IP:127.0.0.1,IP:10.128.0.10, openssl x509 -req -in vault-server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out vault-server.crt -days 3650 -sha256, mv *.crt *.key /etc/vault.d/, # 创建 config.hcl, cat /etc/vault.d/config.hcl EOF, storage \file\ {, path \/var/lib/vault/data\, }, listener \tcp\ {, address \10.128.0.10:8200\, cluster_address \10.128.0.10:8201\, tls_cert_file \/etc/vault.d/vault-server.crt\, tls_key_file \/etc/vault.d/vault-server.key\, tls_client_ca_file \/etc/vault.d/ca.crt\, }, api_addr \https://10.128.0.10:8200\, cluster_addr \https://10.128.0.10:8201\, ui false, log_level \info\, disable_mlock true, EOF, # 初始化 Vault 并保存密钥, vault server -config/etc/vault.d/config.hcl -dev , sleep 5, export VAULT_ADDRhttps://127.0.0.1:8200 VAULT_SKIP_VERIFYtrue, vault operator init -key-shares3 -key-threshold2 -formatjson /var/lib/vault/init-secrets.json, kill %1, sleep 2 ] },{ type: file, source: ./vault.service, destination: /etc/systemd/system/vault.service },{ type: shell, inline: [ systemctl daemon-reload, systemctl enable vault ] }] }注意vault.service文件内容需提前准备好[Unit] DescriptionHashiCorp Vault Documentationhttps://www.vaultproject.io/docs/ Requiresnetwork-online.target Afternetwork-online.target [Service] Typesimple Uservault Groupvault ProtectSystemfull ProtectHomeread-only NoNewPrivilegesyes LimitNOFILE65536 ExecStart/usr/local/bin/vault server -config/etc/vault.d/config.hcl Restarton-failure RestartSec5 TimeoutStopSec30 StartLimitInterval60 StartLimitBurst3 [Install] WantedBymulti-user.target这个 Packer 模板的关键设计意图是把 Vault 的“冷启动”状态证书、配置、初始密钥全部固化在镜像层让后续的 Terraform 部署变成纯粹的“资源编排”而非“状态配置”。它解决了传统方式中最大的不确定性来源——人。3. Terraform 编排 DigitalOcean 资源如何让 Droplet 成为 Vault 的“可信载体”Packer 构建出镜像后Terraform 的任务就清晰了在 DigitalOcean 上创建一个严格遵循最小权限原则、网络隔离、具备持久化存储、并能安全传递初始化密钥的 Droplet 实例。这不是简单的digitalocean_droplet资源定义而是一套围绕 Vault 安全模型设计的基础设施契约。3.1 网络隔离为什么必须用 VPC 和专用子网DigitalOcean 默认的公共网络Public Network对所有 Droplet 开放 ICMP 和部分端口。Vault 的 8200 端口绝不能暴露在公网——哪怕加了防火墙规则也存在配置错误、规则被覆盖的风险。因此第一步是创建一个专用 VPCresource digitalocean_vpc vault_vpc { name vault-vpc region sfo3 ip_range 10.128.0.0/16 } resource digitalocean_subnet vault_subnet { vpc_id digitalocean_vpc.vault_vpc.id name vault-subnet region sfo3 ip_range 10.128.0.0/24 }这个/24子网256 个 IP足够容纳 Vault 主节点、未来可能的 Consul 集群、以及少量管理节点。关键在于所有 Vault 相关资源Droplet、Block Storage、Load Balancer都必须部署在此 VPC 内。VPC 天然隔离了公网流量任何跨 VPC 访问都需要显式配置对等连接Peering这本身就是一道强安全门禁。3.2 Droplet 配置超越size和image的深层考量digitalocean_droplet资源的配置远不止选择size和image。以下是生产环境中必须显式声明的字段及其原理private_networking true启用私有网络接口。这是 Vault 节点间通信如cluster_addr和客户端访问如api_addr的唯一通道。必须开启否则 Vault 无法在 VPC 内建立集群。volume_ids [digitalocean_volume.vault_data.id]挂载独立的 Block Storage 卷。原因有三持久化Droplet 重启或重建时/var/lib/vault/data目录存储加密后的密钥、策略、审计日志必须保留。根磁盘随 Droplet 销毁而消失Block Storage 是唯一可靠方案。性能隔离Vault 的file存储后端对 I/O 延迟敏感。将数据目录放在专用 SSD 卷上避免与系统日志、临时文件争抢 IO。快照与备份DigitalOcean Block Storage 支持按需快照Snapshot可作为 Vault 数据的离线备份手段需配合vault operator raft snapshot save命令。user_dataCloud-init这是 Terraform 与 Droplet 启动时的“第一握手协议”。我们不在此处执行复杂逻辑那是 Packer 的事而是做两件事将 Packer 镜像中生成的/var/lib/vault/init-secrets.json复制到一个安全位置如/root/vault-init.json并设置权限0600启动vaultsystemd 服务。resource digitalocean_droplet vault_server { name vault-prod-01 image data.digitalocean_image.vault_packer.id size s-2vcpu-4gb region sfo3 vpc_uuid digitalocean_vpc.vault_vpc.id private_networking true volume_ids [digitalocean_volume.vault_data.id] user_data -EOF #!/bin/bash set -e mkdir -p /root/.vault cp /var/lib/vault/init-secrets.json /root/.vault/init.json chmod 0600 /root/.vault/init.json systemctl start vault EOF }注意user_data中的cp操作之所以安全是因为 Packer 镜像内的/var/lib/vault/init-secrets.json已经是 root-only 权限且 Droplet 启动时user_data以 root 身份执行。这避免了在 Terraform 模板里硬编码 root token 的风险。3.3 安全组Firewall精确到端口与协议的“数字门禁”DigitalOcean 的 Firewall 资源是控制流量的终极武器。针对 Vault我们只开放绝对必要的端口resource digitalocean_firewall vault_firewall { name vault-firewall droplet_ids [digitalocean_droplet.vault_server.id] inbound_rule { protocol tcp port_range 8200 source_addresses [10.128.0.0/24] # 仅允许 VPC 内其他节点访问 } inbound_rule { protocol tcp port_range 8201 source_addresses [10.128.0.0/24] # Vault 集群通信端口 } inbound_rule { protocol tcp port_range 22 source_addresses [203.0.113.10/32, 203.0.113.11/32] # 仅允许运维团队固定 IP } outbound_rule { protocol all port_range all destination_addresses [0.0.0.0/0] } }这里的关键设计是拒绝默认策略不设置inbound_rule允许0.0.0.0/0所有未声明的入站流量默认拒绝。最小化源地址8200端口只对 VPC 内网段开放意味着只有你的应用服务器、Kubernetes Node、CI/CD Runner 才能调用 Vault API。公网、其他 VPC、甚至 DigitalOcean 控制台的 Web Console 都无法直连。SSH 严格限制运维 SSH 只允许来自公司办公网络或 VPN 出口的固定 IP杜绝暴力破解。3.4 数据卷Volume不只是“挂载”而是“加密与快照策略”digitalocean_volume的配置同样蕴含深意resource digitalocean_volume vault_data { name vault-data region sfo3 size 100 # GB initial_filesystem_type ext4 description Persistent storage for Vault file backend # 启用静态加密At-Rest Encryption # DigitalOcean 默认对所有 Block Storage 启用 AES-256 加密无需额外配置 # 但需在文档中明确记录此合规性保障 # 快照策略需结合外部 Cron Job # Terraform 本身不管理快照但可输出 volume_id 供外部脚本调用 API # 例如每月 1 号凌晨 2 点执行 doctl compute volume snapshot create id --name vault-monthly-$(date %Y%m%d) }重点在于size 100Vault 的file后端存储的是加密后的密钥、策略、审计日志。根据 HashiCorp 官方建议一个中等规模的 Vault日均 1000 次读写一年数据量约 5–10GB。我们预留 100GB既是为未来增长留余量也是为快照腾出空间每个快照会占用额外容量。同时DigitalOcean 的 Block Storage 默认启用 AES-256 静态加密这意味着即使物理磁盘被窃取数据也无法被解密——这是 Vault 作为“可信根”的物理层保障。4. Vault 初始化与首次访问如何安全地“打开保险柜”当terraform apply执行完毕Droplet 启动vault服务运行你可能会立刻curl https://10.128.0.10:8200/v1/sys/seal-status查看状态。但此时你会看到sealed: true—— 因为 Packer 镜像里生成的init-secrets.json只用于首次 unsealVault 启动后会立即 seal 自身等待管理员提供 unseal keys。这才是 Vault 的设计哲学即使服务器被入侵攻击者拿到的是一个 sealed 的 Vault没有 unseal keys里面的数据就是一堆乱码。4.1 安全获取 unseal keys为什么不能scp或cat最直接的想法是ssh rootdroplet-ip然后cat /root/.vault/init.json。但这违反了最小权限和审计原则SSH 登录会留下auth.log记录但无法区分是谁执行了catinit.json文件包含 3 个 unseal keys 和 1 个 root token一旦被复制到本地就脱离了 Vault 的访问控制体系如果运维人员离职其本地保存的init.json副本将成为永久后门。正确做法是利用 Vault 的 API在受信客户端上完成 unseal并立即将 root token 存入更安全的地方如本地密码管理器。首先配置本地 Vault CLI# 设置环境变量请替换为你的 Droplet 私有 IP export VAULT_ADDRhttps://10.128.0.10:8200 export VAULT_SKIP_VERIFYtrue # 因为是自签名证书生产环境应导入 CA 到系统信任库 # 获取初始化密钥需先 SSH 进去读取但仅此一次 ssh root10.128.0.10 cat /root/.vault/init.json /tmp/vault-init.json提示/tmp/vault-init.json是临时文件使用后立即shred -u /tmp/vault-init.json彻底擦除。然后执行 unseal需要 3 个 keys 中的任意 2 个# 解析 JSON 获取 keys 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) ROOT_TOKEN$(jq -r .root_token /tmp/vault-init.json) # 执行 unseal vault operator unseal $UNSEAL_KEY_1 vault operator unseal $UNSEAL_KEY_2 # 验证状态 vault status # 输出应为 Sealed: false4.2 Root Token 的安全交接从“临时凭证”到“长期策略”ROOT_TOKEN是 Vault 的最高权限凭证等同于 Linux 的root用户。绝不能将其硬编码在脚本里也不能长期使用。它的正确用途是在 Vault 启动后立即用它创建一组具有精细权限的“工作令牌”Token然后销毁 root token 的本地副本。例如为 CI/CD 系统创建一个只能读取secret/ci路径的令牌# 使用 root token 登录 vault login $ROOT_TOKEN # 创建名为 ci-token 的策略 vault policy write ci-token - EOF path secret/data/ci/* { capabilities [read, list] } path auth/token/create { capabilities [update] } EOF # 创建一个 TTL 为 1 小时、绑定此策略的令牌 vault token create -policyci-token -ttl1h -display-nameci-pipeline # 输出类似Token: s.abc123def456ghi789jkl012这个s.abc123...令牌就是 CI/CD 流水线应该使用的凭证。它有明确的生命周期1 小时后自动失效、明确的权限范围只能读secret/data/ci/*、明确的用途标识ci-pipeline。而ROOT_TOKEN在完成上述操作后应立即从本地环境变量中清除并确保/tmp/vault-init.json已被shred擦除。4.3 首次策略配置为什么kv引擎必须启用v2版本Vault 的kvKey-Value引擎是使用最频繁的存储后端。但很多人不知道kv有v1和v2两个版本生产环境必须使用v2。原因如下特性KV v1KV v2版本历史无写入即覆盖保留所有历史版本可回滚删除行为物理删除不可恢复逻辑删除soft delete可恢复元数据无包含created_time,deletion_time,destroyed等审计字段HMAC无每个值都有 HMAC 校验防篡改启用kv-v2的命令vault secrets enable -version2 -pathsecret kv然后你可以安全地写入第一个密钥vault kv put secret/db-prod usernameprod_user passwordsuper_secret_password # 输出Success! Data written to: secret/data/db-prod # 查看版本历史 vault kv get -version1 secret/db-prod vault kv get -version2 secret/db-prodkv-v2的secret/data/db-prod路径比kv-v1的secret/db-prod多了一层data/这是为了兼容性设计但它是强制的。这个细节看似微小却决定了你的密钥是否具备可审计、可追溯、可恢复的能力——而这正是企业级安全合规如 SOC2、ISO27001的核心要求。5. 持续维护与升级当 Packer 镜像过期、Terraform 状态漂移时怎么办这套 PackerTerraform 流水线的价值不仅在于“首次构建”更在于“长期演进”。Vault 会升级、DigitalOcean 的 API 会变更、安全策略会收紧。一个健壮的 IaC 流程必须内置应对变化的机制。5.1 Packer 镜像的版本化与自动构建Packer 模板中的vault_version变量不能写死为1.15.4。正确做法是将vault_version提取为variables.tfvars文件在 CI/CD 流水线如 GitHub Actions中监听 HashiCorp 的 GitHub Release webhook当新版本发布时自动触发packer build并为镜像打上vault-1.15.4-sfo3标签Terraform 中通过data digitalocean_image动态查询最新标签data digitalocean_image vault_packer { filter { key name values [vault-${var.vault_version}-sfo3] } most_recent true }这样只需修改var.vault_version并提交CI 就会构建新镜像Terraform 会自动采用——整个过程无人工干预且每次构建都有 Git 提交记录可审计。5.2 Terraform State 的安全托管与锁定terraform.tfstate文件是整个基础设施的“单一事实来源”它包含了 Droplet ID、Volume ID、Firewall 规则等敏感信息。绝不能将其保存在本地磁盘或 Git 仓库中。DigitalOcean 本身不提供远程 state 后端但我们可以通过以下方式解决方案一推荐使用 Terraform Cloud创建免费工作区启用远程执行Remote Execution和 state 锁定State Locking。所有terraform apply都在 Terraform Cloud 的安全沙箱中运行state 文件永不落地本地。方案二使用 S3 DynamoDB需 AWS 账户terraform { backend s3 { bucket my-vault-tfstate key digitalocean/prod/terraform.tfstate region us-east-1 dynamodb_table terraform-locks } }DynamoDB 表用于 state 锁定防止并发apply导致状态损坏。无论哪种方案目标都是state 文件的读写权限必须严格控制且每次apply都有完整的执行日志和谁发起的记录。5.3 Vault 自身的升级与滚动更新Vault 升级不是简单地apt upgrade vault。由于 Packer 镜像已固化 Vault 二进制升级意味着修改 Packer 模板中的vault_version构建新镜像在 Terraform 中将digitalocean_droplet的image参数指向新镜像 ID执行terraform applyTerraform 会销毁旧 Droplet、创建新 Droplet。这看起来是“蓝绿部署”但有一个关键问题新 Droplet 启动时/var/lib/vault/data目录是空的而旧 Droplet 的数据卷还挂载着。解决方案是在user_data中加入数据卷挂载与初始化逻辑user_data -EOF #!/bin/bash set -e # 确保数据卷已挂载 mkdir -p /var/lib/vault/data mount /dev/disk/by-id/scsi-0DO_Volume_vault-data /var/lib/vault/data # 如果是首次挂载初始化目录权限 if [ ! -f /var/lib/vault/data/.initialized ]; then chown -R vault:vault /var/lib/vault/data touch /var/lib/vault/data/.initialized fi systemctl start vault EOF这样新 Droplet 启动时会复用旧的数据卷Vault 自动加载原有数据实现无缝升级。整个过程无需手动导出/导入快照因为数据本身就在持久化卷上。5.4 审计日志的导出与分析让每一次密钥访问都可追溯Vault 的审计日志Audit Log是安全事件调查的黄金标准。默认情况下它写入/var/lib/vault/logs/audit.log。但这个文件会滚动、会被覆盖且难以集中分析。最佳实践是在 Packer 构建时配置 Vault 启用syslog审计设备将日志发送到中央日志服务器。在config.hcl中添加# 在 storage 和 listener 配置之后 audit syslog { type syslog description Send audit logs to rsyslog config { tag vault-audit } }然后在 Droplet 的rsyslog.conf中配置将vault-audit标签的日志转发到你的 ELK Stack 或 Datadog# /etc/rsyslog.d/50-vault.conf if $programname vault-audit then your-log-server:514 stop这样每一次vault kv get secret/db-prod的请求都会在中央日志中留下完整记录时间、源 IP、调用者 Token ID、访问的路径、返回状态。当发生安全事件时你不需要登录到 Vault 服务器翻找日志只需在 Kibana 中搜索vault-audit AND db-prod就能获得全量上下文。我在实际运维中发现很多团队忽略了审计日志的集中化。结果是当怀疑某个密钥被滥用时只能去每台 Vault 服务器上grep日志效率极低且容易遗漏。而将审计日志作为基础设施的一等公民进行设计是真正将 Vault 从“工具”升华为“安全中枢”的标志。6. 最后一点真实体会别让“Quickstart”变成“Quick-fail”写完这篇长文我想分享一个在 DigitalOcean 上部署 Vault 时踩过的、最痛的坑**Droplet 的private_networking选项在某些老区域如nyc3默认关闭且 Terraform 的