news 2026/9/16 19:39:34

k-skill 生产部署实战:k-skill-proxy 在 gpu01 上的 systemd、自动发布与服务看门狗全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
k-skill 生产部署实战:k-skill-proxy 在 gpu01 上的 systemd、自动发布与服务看门狗全解析

k-skill 生产部署实战:k-skill-proxy 在 gpu01 上的 systemd、自动发布与服务看门狗全解析

【免费下载链接】k-skill한국인을 위한 스킬 모음집 - 에이전트를 한국인으로项目地址: https://gitcode.com/GitHub_Trending/ks/k-skill

本文基于仓库文档 docs/deploy-k-skill-proxy.md 与 部署脚本、systemd 单元文件、代理服务端实现 展开,讲解 k-skill-proxy 在生产机 gpu01 上的完整部署体系:单跳 Cloudflare Tunnel 拓扑下KSKILL_PROXY_TRUST_PROXY_HOPS=1的由来、每 5 分钟由 cron 驱动的自动发布与回滚流程、防止服务“安静死亡”的看门狗机制、运行时.env的密钥管理与轮换流程,以及 Loki/Promtail/Grafana 用量统计面板的接入方式。读完后你可以理解这套“cron + flock + systemd user service”架构的每个环节为什么这样设计,并能对同类 Fastify 公共代理做同样的部署审计。

生产部署布局:单跳 Tunnel 拓扑

k-skill-proxy的生产环境运行在gpu01上,而不是 Google Cloud Run。公共域名由 Cloudflare Tunnel 转发到绑定在127.0.0.1:8080的 Fastify 进程,整条链路只有“一个本地 Tunnel 跳”。生产布局的关键项如下(完整表格见 docs/deploy-k-skill-proxy.md):

项目
主机gpu01gpu01.nomadamas.orghostname -smarker-multi-gpu-server-01
公共 URLhttps://k-skill-proxy.nomadamas.org
应用目录/data/home/jeffrey/apps/k-skill-proxy
源码检出目录/data/home/jeffrey/apps/k-skill-proxy-repo
代理服务systemctl --user status k-skill-proxy.service
Tunnel 服务systemctl --user status k-skill-proxy-tunnel.service
运行时环境/data/home/jeffrey/apps/k-skill-proxy/.env
已部署版本/data/home/jeffrey/apps/k-skill-proxy/deployed-sha
部署脚本scripts/deploy-k-skill-proxy-gpu01.sh

生产监听器绑定127.0.0.1,公共流量只能通过这一个本地 Cloudflare Tunnel 跳进入。文档对此有一个明确的安全约束:只有当部署脚本以KSKILL_PROXY_DEPLOY_ENVIRONMENT=production且生产主机(KSKILL_PROXY_DEPLOY_HOST=gpu01,或hostname -sgpu01/marker-multi-gpu-server-01)运行时,脚本才会确保运行时环境包含KSKILL_PROXY_TRUST_PROXY_HOPS=1并重启服务;cron 中应显式设置KSKILL_PROXY_DEPLOY_HOST=gpu01。非 production 或非 gpu01 的执行保持应用默认值0。运维人员显式设置的值会被保留,KSKILL_PROXY_ENV_FILE可以把脚本指向非默认的环境文件路径。

文档还特别警告:不要对直接暴露的监听器使用此设置。信任一跳之所以在这里安全,是因为 Fastify 端口只监听 loopback;一旦该端口对外可达,客户端就能提供伪造的X-Forwarded-For值。

从源码看,这一约束是硬编码在脚本里的:deploy-k-skill-proxy-gpu01.sh 中的is_gpu01_production先检查KSKILL_PROXY_DEPLOY_ENVIRONMENT是否为production,再用case匹配gpu01|marker-multi-gpu-server-01两种主机名;只有通过双重校验,ensure_gpu01_production_defaults才会调用ensure_env_default.env追加缺失的KSKILL_PROXY_TRUST_PROXY_HOPS=1ensure_env_default(第 36-52 行)先grep检查键是否已存在,存在则直接返回——这正是“显式运维值被保留”的实现。

为什么 TRUST_PROXY_HOPS=1:限流识别的源码原理

生产环境必须设置KSKILL_PROXY_TRUST_PROXY_HOPS=1的原因在 packages/k-skill-proxy/src/server.js 中可以找到:

  • 第 288 行将KSKILL_PROXY_TRUST_PROXY_HOPS解析为trustProxyHops(取Math.max(0, ...),非法值回退 0);
  • 第 2214 行把它传给 Fastify 构造参数trustProxy: config.trustProxyHops || false

如果不信任这一跳,Fastify 的request.ip会一直是127.0.0.1所有外部客户端会共享同一个限流桶,限流形同虚设。

更进一步的防伪造逻辑在clientRateLimitKey(server.js):当trustProxyHops > 0时,限流键优先取CF-Connecting-IP请求头,而不是X-Forwarded-For——X-Forwarded-For是客户端可以随意追加的,而CF-Connecting-IP由 Cloudflare 在出口重写,客户端无法通过伪造额外 XFF 条目来绕过限流。对应的行为测试在 server.test.js 中覆盖了KSKILL_PROXY_TRUST_PROXY_HOPS"1""2"的场景。

同样地,路由用量日志(routeUsage行)里的clientIp字段也仅在trustProxyHops > 0时写入(server.js),否则记录的全是127.0.0.1,统计失去意义。

默认值与限流相关参数的完整定义见 packages/k-skill-proxy/README.md:KSKILL_PROXY_RATE_LIMIT_WINDOW_MS默认60000KSKILL_PROXY_RATE_LIMIT_MAX默认60KSKILL_PROXY_RATE_LIMIT_MAX_CLIENTS默认10000KSKILL_PROXY_CACHE_TTL_MS默认300000KSKILL_PROXY_HOST默认127.0.0.1

自动发布流程:cron + flock + SHA 幂等

gpu01用户的 crontab 在flock保护下每 5 分钟运行一次部署脚本。脚本先fetch origin/main;如果记录的 SHA 与目标一致则直接退出,否则依次执行:

  1. 在源码检出目录 checkout 目标 SHA;
  2. 运行npm ci、proxy lint 和全部 proxy 测试;
  3. 为当前应用创建带时间戳的备份;
  4. 同步 proxy 及其本地 workspace 依赖;
  5. 安装生产依赖并重启 systemd user service;
  6. 检查本地与公共的/health/privacy
  7. 所有检查通过后才记录deployed-sha

只要main上有新合并、且新 proxy 测试与冒烟检查通过,就会在一个 cron 周期内完成部署。备份之后的任何失败都会触发自动回滚:恢复上一版文件并重启旧服务。

对照 deploy-k-skill-proxy-gpu01.sh 的实现,几个值得注意的细节:

  • cron 环境的 systemd 会话总线:第 4-6 行显式导出XDG_RUNTIME_DIRDBUS_SESSION_BUS_ADDRESS,因为 cron 没有 user-session bus,否则systemctl --user无法工作;
  • 验证先于变更:第 146-150 行在目标 SHA 上依次执行checkout --detach --forcenpm ci --no-audit --no-fundnpm run lint --workspace k-skill-proxynpm run test --workspace k-skill-proxy——测试失败发生在备份之前,此时还没有任何变更,无需回滚;
  • 备份内容与回滚陷阱:第 152-159 行用tar备份packages/k-skill-proxypackages/parking-lot-search(proxy 的本地 workspace 依赖)、deployed-sha和根package.json/package-lock.json;第 161-168 行用trap rollback ERR注册回滚,回滚动作是解压备份 +npm ci --omit=dev+systemctl --user restart
  • 同步范围:第 170-173 行rsync -a --delete --exclude node_modules同步 proxy 与parking-lot-search两个包,第 174-175 行用install -m 0644覆盖根 workspace 清单;
  • 带重试的冒烟检查:第 182-187 行对http://127.0.0.1:8080/health做 5 次、每次间隔 2 秒的重试,之后(第 188-191 行)对本地与公共两个端点各做一次/health/privacy检查。health_checknode -e断言响应 JSON 的ok === trueprivacy_check(第 115-124 行)则断言/privacy页面包含固定版本的 meta 标签name="k-skill-privacy-policy-version" content="2026-08-18"——这意味着隐私政策页面内容被纳入了发布冒烟测试;
  • 脚本自举:第 193 行把仓库中的部署脚本install -m 0755$APP_DIR/deploy-k-skill-proxy-gpu01.sh。因此首次包含该脚本的main部署之后,cron 实际执行的是应用目录里的自拷贝版本。

cron 条目(安装或修复时用):

*/5 * * * * KSKILL_PROXY_DEPLOY_HOST=gpu01 flock -n /tmp/k-skill-proxy-deploy.lock /data/home/jeffrey/apps/k-skill-proxy/deploy-k-skill-proxy-gpu01.sh >> /data/home/jeffrey/apps/k-skill-proxy/deploy.log 2>&1

注意flock -n:已有部署在跑时新触发的 cron 直接放弃,而不是排队。SSH 别名是gpu01,而机器上hostname -smarker-multi-gpu-server-01——部署脚本两者都视为生产,但文档仍要求在 cron 中显式设置KSKILL_PROXY_DEPLOY_HOST=gpu01,避免主机名变更悄悄禁用看门狗。自举之前需要手动安装一次:

install -m 0755 /data/home/jeffrey/apps/k-skill-proxy-repo/scripts/deploy-k-skill-proxy-gpu01.sh \ /data/home/jeffrey/apps/k-skill-proxy/deploy-k-skill-proxy-gpu01.sh

服务看门狗:一次重启事故催生的设计

脚本的第一件事(早于任何部署逻辑)是服务看门狗:每次 cron 运行——包括因deployed-sha已匹配而提前退出的运行——都会先确保生产服务处于 active 状态(第 130 行ensure_production_services)。已安装但不 active 的服务会被systemctl --user start拉起(第 82-99 行ensure_service_activelist-unit-files确认单元存在,再is-active --quiet判断)。

这个设计源于一次真实事故:2026-08-29 gpu01 重启后,Cloudflare tunnel 与 Loki/Grafana/Promtail 仪表盘栈挂了约 10 天才被人工发现。根因是这些单元使用Restart=on-failure——它不会恢复一次“干净的 SIGTERM”,而除了这个 cron 之外没有任何东西会再启动它们。proxy 之所以活了下来,纯属巧合:这个 cron 每次运行都会restart它。

被看门狗监视的服务(仅限生产 gpu01,可通过KSKILL_PROXY_WATCHED_SERVICES覆盖,默认列表见 脚本第 20 行):

服务角色
k-skill-proxy.serviceFastify 代理
k-skill-proxy-tunnel.service两个公共主机名的 cloudflared tunnel
k-skill-proxy-loki.service日志存储
k-skill-proxy-promtail.service日志采集
k-skill-proxy-grafana.service仪表盘 UI

所有 5 个单元都使用Restart=alwaysStartLimitIntervalSec=0,保证干净停止或反复失败都不会让重启机制被永久禁用。规范单元文件都在仓库 infra/k-skill-proxy-dashboard/systemd/ 目录下:

单元仓库来源
proxyinfra/k-skill-proxy-dashboard/systemd/k-skill-proxy.service
tunnelinfra/k-skill-proxy-dashboard/systemd/k-skill-proxy-tunnel.service
loki / promtail / grafanainfra/k-skill-proxy-dashboard/systemd/

从单元文件内容看两个关键实现:

  • k-skill-proxy.service 以EnvironmentFile=/data/home/jeffrey/apps/k-skill-proxy/.env注入运行时密钥,Restart=always+RestartSec=3,stdout/stderr 直接 append 到proxy.log
  • k-skill-proxy-tunnel.service 额外携带RequiresMountsFor=,分别指向 cloudflared 二进制、其配置和应用目录——这三者都在gpu02:/dataNFS 挂载上,必须等挂载就绪后才能 exec,否则开机时会直接 crash-loop。proxy 单元同样对应用目录设置了RequiresMountsFor

手动操作与验证命令

日常巡检与手动触发部署(完整命令清单见 docs/deploy-k-skill-proxy.md):

mosh gpu01 /data/home/jeffrey/apps/k-skill-proxy/deploy-k-skill-proxy-gpu01.sh curl -fsS http://127.0.0.1:8080/health curl -fsS https://k-skill-proxy.nomadamas.org/health curl -fsS http://127.0.0.1:8080/privacy curl -fsS https://k-skill-proxy.nomadamas.org/privacy cat /data/home/jeffrey/apps/k-skill-proxy/deployed-sha

日志与服务状态:

tail -f /data/home/jeffrey/apps/k-skill-proxy/deploy.log tail -f /data/home/jeffrey/apps/k-skill-proxy/proxy.log systemctl --user status k-skill-proxy.service systemctl --user status k-skill-proxy-tunnel.service

验证 trust-proxy 设置时,不要打印整个带密钥的 env 文件,只 grep 目标键:

grep '^KSKILL_PROXY_TRUST_PROXY_HOPS=' /data/home/jeffrey/apps/k-skill-proxy/.env

生产环境期望值为KSKILL_PROXY_TRUST_PROXY_HOPS=1。如果该键缺失,下一次 gpu01 生产部署会补上并重启服务;staging、本地或其他主机执行则不会添加。拓扑变化时,只有重新清点可信反向代理跳数之后才应更新显式值。.env文件留在gpu01上,绝不能复制进仓库。

必需运行时环境变量(gpu01)

由于 Cloudflare Tunnel 转发到127.0.0.1:8080,Fastify 必须信任这一跳,否则所有外部客户端共享同一个限流桶。以下键应保留在/data/home/jeffrey/apps/k-skill-proxy/.env中:

KSKILL_PROXY_TRUST_PROXY_HOPS=1 KSKILL_PROXY_RATE_LIMIT_WINDOW_MS=60000 KSKILL_PROXY_RATE_LIMIT_MAX=60 COUPANG_ACCESS_KEY=<coupang-partners-access-key> COUPANG_SECRET_KEY=<coupang-partners-secret-key> KOMSA_MTIS_API_KEY=<komsa-mtis-service-key>

可选的 BC Card eat.pl 中转键,仅在启用 Lightsail 固定出口中转时配置。同一个随机 token 在 Lightsail 侧存为EATPL_RELAY_TOKEN。token 永远不要放进 Git、聊天或客户端请求:

BCCARD_EATPL_INST_NM=<partner-institution-code> BCCARD_EATPL_API_BASE_URL=https://api.paybooc.ai/api/mer BCCARD_EATPL_RELAY_URL=https://eatpl-relay.nomadamas.org/v1/search BCCARD_EATPL_RELAY_TOKEN=<same-token-as-lightsail>

KSKILL_PROXY_TRUST_PROXY_HOPS=1在生产必需;只有“本地绑定、且不在反向代理之后”的进程才保持不设(默认0)。信任跳数大于 0 时,限流器优先取CF-Connecting-IP而非X-Forwarded-For,客户端无法伪造额外 XFF 条目(实现见 server.js 第 443-451 行)。

两类密钥的启用方式与验证路径:

  • CoupangCOUPANG_ACCESS_KEY+COUPANG_SECRET_KEY启用GET /v1/coupang/products/search。密钥只存在 gpu01 运行时.env,绝不经过 query string、shell 参数、issue 或日志。通过/healthupstreams.coupangConfigured=true验证启用。
  • KOMSA MTISKOMSA_MTIS_API_KEY启用GET /v1/komsa/ferry/:dataset。同样只存 gpu01 运行时.env,通过/healthupstreams.komsaConfigured=true验证后,再执行一次只读渡轮查询演练。

ASK Seoul 天气风险路由的密钥交接

部署seoul-weather-risk代理路由前,ASK Seoul 必须先签发专用的、可吊销的服务密钥,不得使用维护者或贡献者的个人 Marketplace 密钥。Marketplace 将该密钥注册为以下固定服务主体与作用域:

字段固定值
service principalk-skill-proxy:seoul-weather-risk
scopeskill:seoul-weather-risk:read
允许的 Marketplace APIGET /skill/v1/bundles/seoul-weather-riskGET /skill/v1/products/weather_place_risk_windowGET /skill/v1/products/weather_place_risk_window/data
排除的 API所有/api/v1/*API 与其他所有/skill/v1/*产品

只在既有 gpu01 运行时.env中存放这两个值:

ASK_SEOUL_SKILL_API_BASE_URL=https://<ask-seoul-skill-origin> ASK_SEOUL_KSKILL_API_KEY=<dedicated-service-key>

该密钥不是用户凭据:它只从代理进程发往固定的 ASK Seoul/skill/v1路径。在 Marketplace 完成服务密钥 scope 迁移并注册该主体之前,不要部署这条路由(代理 README 中askSeoulWeatherRiskConfigured健康标志的含义——仅当 origin 与专用服务键同时设置才为true——见 packages/k-skill-proxy/README.md)。

密钥轮换流程:注册同 scope 的替代密钥 → 只替换 gpu01 运行时密钥 → 重启/冒烟测试代理 → 吊销旧密钥。若代理被禁用或疑似失陷,立即吊销其密钥;Marketplace 必须在任何每日配额核算之前拒绝该密钥。永远不要把密钥粘贴进 issue、PR、shell 参数、URL 或日志。

用量统计面板:Loki + Promtail + Grafana

端点调用统计(routeUsage日志行)由 Promtail 采集进 Loki,再在 Grafana 中可视化。从 server.js 第 2233-2266 行 可以看到日志生产端的设计:onResponsehook 对每个匹配路由计数并输出一条结构化日志,/health被排除(健康检查是运维噪声而非使用量);所有未匹配请求聚合到单一__unmatched__键下,404 的原始路径先经过normalizeUnmatchedPath归一化且 map 上限 100 条——文档注释明确说明这是为了防止任意输入无限撑大内存 map 和 Loki 标签基数。

面板栈的细节(90 天日志保留、{job="k-skill-proxy", route!=""}查询约定、setup-gpu01.sh安装步骤、Grafana 绑定127.0.0.1:3200并复用同一个 cloudflared tunnel 暴露k-skill-proxy-dashboard.nomadamas.org)见 infra/k-skill-proxy-dashboard/README.md。栈以 systemd user service 形式运行(k-skill-proxy-{loki,promtail,grafana}.service),由infra/k-skill-proxy-dashboard/setup-gpu01.sh安装。Grafana 登录即访问控制(禁用匿名、仅管理员账户),管理凭据存放在grafana.env(mode 600,绝不提交)。

一个时序注意点:route标签(以及按端点划分的面板)只有在产出routeUsage日志行的代理构建部署到main之后才会出现——即部署了带routeUsage统计逻辑的版本前,面板上不会有按端点的数据。

小结:这套部署体系的三条经验

  1. 幂等 + 看门狗合并进同一条 cron:部署脚本既是发布器又是保活器,flock -n防并发、SHA 对比防重复、每次运行先拉起死掉的服务,用一个 5 分钟周期同时解决“部署”和“活着”两个问题;
  2. 安全默认值写进发布流程而非文档口头约定TRUST_PROXY_HOPS=1由脚本在严格的双条件(production + gpu01 主机)下自动补齐,且保留运维显式值,防止误配在 staging/本地扩散;
  3. 发布冒烟测试覆盖到隐私政策版本/privacy的 meta 标签版本被privacy_check断言,内容回归同样会阻断发布并触发回滚。

延伸阅读:packages/k-skill-proxy/README.md(端点与全部环境变量、/health上游标志语义)、infra/k-skill-proxy-dashboard/README.md(仪表盘栈完整布局)、docs/deploy-eatpl-relay-lightsail.md(BC Card eat.pl Lightsail 中转的部署文档)。

【免费下载链接】k-skill한국인을 위한 스킬 모음집 - 에이전트를 한국인으로项目地址: https://gitcode.com/GitHub_Trending/ks/k-skill

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 19:39:17

自考学习AI工具全攻略:8大高效应用与避坑指南

1. 自考学习中的AI工具应用现状作为一名自考过来人&#xff0c;我深刻理解自考生面临的三大困境&#xff1a;时间碎片化、资料繁杂、缺乏学习监督。近年来AI工具的爆发式发展为自考学习带来了全新可能&#xff0c;但同时也出现了工具选择困难、使用效率低下等新问题。根据2023年…

作者头像 李华
网站建设 2026/9/16 19:39:13

HBase Shell命令全攻略:从建表、数据读写到运维实战

做HBase开发或者运维的人&#xff0c;应该都有过这种经历&#xff1a;查资料看官方文档&#xff0c;满屏的英文命令看得头晕&#xff1b;要么就是上网搜教程&#xff0c;结果搜出来的文章要么太浅&#xff0c;只贴个命令不给解释&#xff0c;要么太散&#xff0c;东一块西一块根…

作者头像 李华
网站建设 2026/9/16 19:37:54

Typora个性化设置全攻略:主题换肤、CSS定制与图片路径管理

Typora 的默认界面&#xff0c;第一眼确实干净&#xff0c;但真拿它当主力写作工具用上两三个月&#xff0c;你多半会跟我一样冒出几个念头&#xff1a;代码块背景能不能再深一点&#xff1f;标题颜色这个蓝色实在晃眼&#xff1b;插图老被复制到乱七八糟的目录里&#xff1b;导…

作者头像 李华
网站建设 2026/9/16 19:37:20

BWAPP靶场SQL注入通关实战:从联合查询到盲注绕过

提起Web安全入门&#xff0c;绕不开的就是SQL注入&#xff1b;绕不开SQL注入练习&#xff0c;自然就绕不开BWAPP这个靶场。我刚入行那会儿&#xff0c;白天看了一堆SQL注入的原理文章&#xff0c;晚上回去就想找个地方练手。DVWA的界面太老&#xff0c;Pikachu的题目量少&#…

作者头像 李华
网站建设 2026/9/16 19:36:42

AI教材编写工具:高效低查重的技术实现与应用

1. AI教材编写工具的核心价值解析在传统教材编写过程中&#xff0c;教育工作者常常面临三大痛点&#xff1a;内容创作周期长、查重率控制困难、知识体系更新滞后。我作为从事教育信息化十余年的从业者&#xff0c;见证了AI技术如何逐步改变这一局面。当前主流的AI教材编写工具&…

作者头像 李华
网站建设 2026/9/16 19:36:09

四维混沌电路仿真:从数学模型到LTspice稳定实现

简介&#xff1a;本资源是一份面向电子工程、非线性系统与混沌理论研究者的四维混沌电路仿真实践材料&#xff0c;聚焦于通过数值建模直观呈现x-y、x-z、x-w等多维相空间中的混沌轨迹&#xff0c;助力理解高维混沌系统的动力学特性及在密码学、随机信号生成等领域的应用潜力。压…

作者头像 李华