ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Neon Safekeeper 租户目录清理脚本实战指南:基于 Console 校验的 `sk_cleanup_tenants` 全流程解析

Neon Safekeeper 租户目录清理脚本实战指南:基于 Console 校验的 `sk_cleanup_tenants` 全流程解析 Neon Safekeeper 租户目录清理脚本实战指南基于 Console 校验的sk_cleanup_tenants全流程解析【免费下载链接】neonNeon: Serverless Postgres. We separated storage and compute to offer autoscaling, code-like database branching, and scale to zero.项目地址: https://gitcode.com/GitHub_Trending/ne/neon在 Neon 的存储与计算分离架构中safekeeper 负责持久化 WAL 日志其数据目录会随着项目生命周期不断增长。当项目在 Console控制台中被删除后safekeeper 上的租户目录并不会自动回收长期积累会浪费磁盘空间。本文基于仓库中 scripts/sk_cleanup_tenants/readme.md 及其配套的script.py与remote.yaml完整讲解如何在单台 safekeeper 节点或通过 Ansible 批量执行租户清理包括危险操作前的多重校验、--dry-run演练、trash 目录安全落盘机制以及底层 safekeeper HTTP API 的实现原理。读完本文你将掌握一套“先校验、后迁移、再删除”的可审计清理流程并能根据生产环境自行调整。一、脚本的定位与适用场景Neon 的数据面由 compute、pageserver、safekeeper 等组件构成其中 safekeeper 以 WAL 日志的方式保存租户数据目录结构由 timeline.rs 中的get_tenant_dir定义conf.workdir.join(tenant_id.to_string())即默认数据目录如/storage/safekeeper/data/下按tenant_id命名的子目录每个租户目录内含若干 timeline 子目录及 WAL 段文件。本脚本解决的核心问题删除已经不再存在在 Console 中已删除的项目所对应的 safekeeper 租户目录。它适合以下场景线上存在大量已废弃项目磁盘空间告急需要批量在多台 safekeeper 上执行清理且要求有审计记录希望通过 Console 的管理接口二次确认租户确已删除避免误删活跃数据。脚本由三个文件组成文件作用script.py核心清理逻辑读取标准输入的 tenant_id逐一校验并清理remote.yamlAnsible playbook在目标 safekeeper 上准备目录、收集租户列表、异步执行脚本readme.md使用说明单机手动执行与 Ansiblestaging/prod两种运行方式二、单节点手动执行完整命令拆解文档给出的单节点运行流程分为“收集租户列表、准备 trash 目录、dry-run 演练、正式执行”四步。2.1 登录节点并收集租户列表zsh nsh safekeeper-0.us-east-2.aws.neon.build ls /storage/safekeeper/data/ | grep -v safekeeper tenants.txtnsh是 Neon 内部的 SSH 登录封装实际使用时请替换为你的节点地址。第二条命令列出 safekeeper 数据目录下的所有条目并通过grep -v safekeeper排除safekeeper.id、safekeeper.pid等元数据文件safekeeper.id文件在 safekeeper.rs 中以ID_FILE_NAME常量定义剩下的即为全部租户目录名写入tenants.txt作为待处理清单。2.2 准备 trash 目录mkdir -p /storage/neon-trash/2023-01-01--cleanup清理并非直接rm目录而是先把租户目录复制到 trash 目录再删除原目录类似“软删除”的容错设计。目录命名建议带上日期批次例如2023-01-01--cleanup便于后续审计与回收。trash 目录必须预先创建因为script.py启动时会执行assert trash_dir.is_dir()强制校验。2.3 设置 Console API Tokenexport CONSOLE_API_TOKENscript.py通过os.getenv(CONSOLE_API_TOKEN)script.py读取访问令牌用于调用 Console 管理 API 校验租户是否真的已被删除。该 Token 必须拥有管理权限因为脚本调用的是/v1/admin/projects管理端点。2.4 先做 dry-run 演练python3 script.py --trash-dir /storage/neon-trash/2023-01-01--cleanup \ --safekeeper-id $(cat /storage/safekeeper/data/safekeeper.id) \ --safekeeper-host $HOSTNAME --dry-run cat tenants.txt | python3 script.py --trash-dir /storage/neon-trash/2023-01-01--cleanup \ --safekeeper-id $(cat /storage/safekeeper/data/safekeeper.id) \ --safekeeper-host $HOSTNAME --dry-run脚本从标准输入逐行读取 tenant_idfor line in sys.stdin所以第一条不带输入的 dry-run 会立即结束只验证参数与启动流程第二条才是真正的全量演练。--dry-run模式下脚本会完成全部校验逻辑但跳过复制与删除动作script.py。2.5 正式执行cat tenants.txt | python3 script.py --trash-dir /storage/neon-trash/2023-01-01--cleanup \ --safekeeper-id $(cat /storage/safekeeper/data/safekeeper.id) \ --safekeeper-host $HOSTNAME | tee logs.txt|将 stdout 与 stderr 合并tee logs.txt同时输出到终端与日志文件便于事后核对每个租户的清理结果。2.6 命令行参数说明参数是否必填含义--trash-dir是requiredTruetrash 目录路径必须已存在--safekeeper-id是requiredTrueintsafekeeper 节点 ID来自/storage/safekeeper/data/safekeeper.id--safekeeper-host是requiredTruestrsafekeeper 节点主机名用于调用本机 HTTP 删除 API--dry-run否开启后只校验不执行默认关闭三、脚本执行流程与安全机制剖析script.py的总体流程可概括为启动保护 → 逐租户处理 → 异常隔离。3.1 单实例保护pid 文件脚本在启动时检查当前目录是否存在script.pidif os.path.exists(script.pid): logging.info(script is already running, ...) exit(1) with open(script.pid, w, encodingutf-8) as f: f.write(str(os.getpid()))若已有实例在运行则直接退出防止多实例并发操作同一批租户目录造成数据竞争正常结束时删除 pid 文件。注意若脚本中途被kill -9强杀残留的script.pid需要手动清理才能再次运行。3.2 核心清理函数cleanup_tenant对每一个从 stdin 读到的 tenant_id脚本执行如下四道关卡目录存在性预检/storage/safekeeper/data/{tenant_id}不存在则记录“already cleaned”并跳过Console 删除状态校验调用tenant_is_deleted_in_console只有确认租户已在 Console 中标记删除才继续否则记录“not deleted in console, skipping”dry-run 短路校验通过后若处于 dry-run 模式则直接返回复制到 trash 并删除原目录先shutil.copytree复制不跟随符号链接、目标不存在再调用 safekeeper 本地 HTTP API 删除最后断言原目录已消失。3.3 Console 校验防止误删的最后防线def tenant_is_deleted_in_console(tenant_id): r console_get(f/v1/admin/projects?search{tenant_id}show_deletedtrue) results r.json()[data] assert len(results) 1, ... assert r[tenant] tenant_id, ... assert r[safekeepers] is not None, ... assert any(sk[id] sk_id for sk in r[safekeepers]), ... assert deleted in r, ... return r[deleted] is True这段校验逻辑对待删除租户做了四重严格断言搜索结果必须恰好一条len(results) 1避免 tenant_id 不唯一或找不到返回记录的tenant字段必须与输入一致防止模糊搜索命中错误项目safekeepers列表不得为空且其中必须包含当前节点的sk_id确保该租户确实由这台 safekeeper 管理记录中必须包含deleted字段且其值为True。任何一条断言失败都会抛出异常该租户被记录为失败并继续处理下一个绝不会在信息不全时执行删除。这也是文档中特别指出的风险点Console 的search查询较慢且低效readme 明确注释 “Console queries to check that project is deleted are slow and inefficient”如果 safekeeper 上租户数量很大建议优先改进 Console 侧按 tenant_id 的检索性能再运行本脚本。3.4 复制到 trash可审计的软删除tenant_dir_in_trash trash_dir / tenant_dir.relative_to(/) tenant_dir_in_trash.parent.mkdir(parentsTrue, exist_okTrue) assert not tenant_dir_in_trash.exists(), ... shutil.copytree(srctenant_dir, dsttenant_dir_in_trash, symlinksFalse, dirs_exist_okFalse)复制逻辑设计严谨tenant_dir.relative_to(/)保留租户目录在数据目录下的相对层级复制前断言 trash 中目标不存在防止覆盖上次残留symlinksFalse不跟随符号链接避免复制到 WAL 文件等敏感外部路径。trash 目录保留着完整的租户数据快照即使删除后发现异常也可从 trash 恢复。3.5 调用 safekeeper 本地删除 API复制完成后脚本通过 HTTP 请求删除原目录def call_delete_tenant_api(tenant_id): r requests.delete(fhttp://{sk_host}:7676/v1/tenant/{tenant_id}) r.raise_for_status()端口 7676 正是 safekeeper 控制 API 的默认端口。该端点在 openapi_spec.yaml 中定义为DELETE /v1/tenant/{tenant_id}其处理函数tenant_delete_handler位于 routes.rs先校验请求权限再调用global_timelines.delete_all_for_tenant()停用该租户下所有 timeline 并移除数据目录返回被删除 timeline 及其状态的映射。删除完成后脚本断言not tenant_dir.exists()确认磁盘上目录确实已消失。3.6 单租户异常隔离主循环对每个租户做了try/except包裹单个租户失败只会记录failed to clean up tenant {tenant_id}异常堆栈logging.exception不会中断整个批次同时单独捕获KeyboardInterrupt实现安全中断。日志格式包含时间戳、级别、源文件与行号便于定位问题。四、Ansible 批量执行staging 与 prod对于多台 safekeeper 的场景仓库提供了 Ansible playbook remote.yamlreadme 分别给出了 staging 与 prod 两种运行方式。4.1 通用运行命令cd ~/neon/.github/ansible export AWS_DEFAULT_PROFILEdev # staging 用 devprod 用 prod ansible-playbook -i staging.us-east-2.hosts.yaml -e ssm_config \ ../../scripts/sk_cleanup_tenants/remote.yaml # 通过 --extra-vars 追加设置 Console API Token ansible-playbook -i staging.us-east-2.hosts.yaml -e ssm_config \ -e api_tokenYOUR_TOKEN \ ../../scripts/sk_cleanup_tenants/remote.yamlssm_config通过 AWS SSM 提供remote_user等连接变量若未在 SSM 配置中提供api_token可用--extra-vars api_token...显式注入。4.2 切换生产环境的两个关键点根据 readmeprod 环境与 staging 有两处差异修改 API endpoint将script.py中的endpoint常量由https://console-stage.neon.build/api改为https://console.neon.tech/api当前仓库代码默认指向 staging切换 AWS 配置export AWS_DEFAULT_PROFILEprod并使用prod.us-east-2.hosts.yaml作为 inventory。4.3 Playbook 任务分解remote.yaml的核心任务链如下步骤任务说明1创建脚本目录/storage/ansible_sk_cleanupmode 07552创建 trash 目录/storage/neon-trash/2023-01-01--changeme变量默认值正式执行前务必改为真实日期3收集租户清单ls /storage/safekeeper/data/ | grep -v safekeeper tenants.txt4统计租户数量wc -l tenants.txtdebug 输出便于人工核对5获取 safekeeper-idcat /storage/safekeeper/data/safekeeper.id6上传 script.py复制到脚本目录并赋予 0755 权限7异步执行清理通过环境变量注入CONSOLE_API_TOKEN后台运行并写时间戳日志8轮询任务状态async_status每 10 秒轮询最多 3000 次约 8 小时清理任务采用 Ansible 的async 模式async: 30000poll: 0因为租户较多时清理耗时长不适合同步阻塞执行日志输出到run-{时间戳}.log便于追溯。注意第 3 步在 playbook 中直接以 shell 形式重跑租户收集与手动流程一致保证清单新鲜度。4.4 执行建议务必先跑 dry-runplaybook 中没有 dry-run 开关可在手动模式下先用--dry-run演练一遍再上 Ansible修改 trash 变量trash_dir默认值带changeme字样生产执行前必须显式覆盖为真实路径分批执行租户量大时可先对tenants.txt抽样如head -n 50验证效果再全量跑关注日志任务失败项会在日志中以failed to clean up tenant标注需人工复核。五、与 pageserver 清理脚本的血缘关系readme 末尾注明该脚本“Heavily inspired with script for pageserver cleanup”深受 pageserver 清理脚本启发。两者的设计哲学一致都采用“先向控制面确认真实状态 → 复制到 trash → 调用组件本地删除 API → 断言目录消失”的可审计流程。理解这一点有助于将本文的清理思路推广到 Neon 其他数据组件的运维中。六、常见问题与注意事项Console Token 权限不足脚本调用/v1/admin/projects管理端点Token 需具备 admin 权限否则console_get会因 4xx/5xx 抛出raise_for_status异常assert trash_dir.is_dir()失败trash 目录未创建或路径写错脚本启动即退出残留script.pid上次强杀导致需手动删除后才能再次运行search查询缓慢租户量大时 Console 校验是性能瓶颈readme 建议优先优化 Console 侧按 tenant_id 的检索能力only_local参数底层 safekeeper 删除 API 支持only_local查询参数routes.rsscript.py未使用该参数默认走完整删除路径生产环境 endpoint当前仓库中script.py默认指向 stagingconsole-stage.neon.build部署生产前必须修改。七、总结sk_cleanup_tenants是一套完整、防御性极强的 safekeeper 租户清理方案通过 Console 管理 API 的多重断言确认租户确实已删除通过“复制到 trash 再删除”保证数据可恢复通过 pid 文件、dry-run、单租户异常隔离保证过程可控通过 Ansible async 模式支持跨节点批量执行。运维人员既可以单机手动操作也可以无缝切换到 Ansible 流水线而底层的DELETE /v1/tenant/{tenant_id}API 与 openapi_spec.yaml 及 routes.rs 的实现则为这套运维脚本提供了坚实的平台支撑。按照“先 dry-run、核对日志、再正式执行”的顺序操作即可安全地回收废弃项目占用的磁盘空间。【免费下载链接】neonNeon: Serverless Postgres. We separated storage and compute to offer autoscaling, code-like database branching, and scale to zero.项目地址: https://gitcode.com/GitHub_Trending/ne/neon创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表