把 DeepSeek Harness 装进 K8s

背景

我一直想把 DSH(DeepSeek Harness)运行在我的 homelab2 k8s集群里(此前一直运行在本机的 ~/.dsh 里。),这样我就可以 7*24 随时随地使用了。

之前是我自己被 AI Sandbox 洗脑了,想着等 AI Sandbox 开源生态成熟了,再搞。

但是今天突然想明白了,AI Sandbox 说白了是给企业/云厂商/模型厂大规模使用准备的,普通人其实用不到这么复杂。

想明白后,那就基于成熟的 k8s 原语:statefulset, secret, cm, PVC, Ingress来做,再用 Helm Chart包装起来。这次把它搬进 homelab2 的 k3s,并接入既有的 Helm、ArgoCD 与 CalVer 发布流程。

以下是具体实践过程:

交付形态:vendored 扁平 Chart

我先是搜了下 DSH 相关开源 repo, 找到了一个上游runzhliu/deepseek-harness-docker。它已经有了镜像和 Helm Chart 的相关积累,这次在它的肩膀上继续。

选择了 runzhliu/deepseek-harness-docker 的当时最新 commit 04371405,Chart 版本 0.1.29。

上游没有 Helm 仓库,只能从 Git 目录取。所以做了 vendored 扁平 Chart:

  • version: 0.1.29-homelab.1
  • appVersion: 0.2.2-rc.2
  • 镜像固定 0.2.2-rc.2-r1-market.1

上游的 values 命名和安全上下文原样保留,新增的部分是四块 PVC、一个 ClusterIP Service、两条 Ingress、NetworkPolicy 放行规则和 resources.limits。

主线取舍:不用 initContainer

这是整个方案里最有争议的一条。

我的规划如下:

  • DSH 的配置需要从我电脑同步过来并持久化
  • Skills 也要从我电脑同步过来并持久化
  • uv 工具集需要安装并持久化
  • mise 工具集需要安装并持久化

关于工具集,我现在工具集基本都使用 uv 和 mise 管理:

  • uv 管理 python 相关(如:pre-commit)
  • mise 管理 非python的其他(如:kubectl)

工具安装要装 uv、mise,再加 4 个 uv 工具和 9 个 mise 工具。

uv mise 的 global 工具清单如下(很多 repo 都用得上):

shell 复制代码
$ uv tool list
pre-commit v4.7.0
- pre-commit
pyrefly v1.4.1
- pyrefly
specify-cli v1.4.0
- specify
yamllint v1.38.0
- yamllint

$ mise list
Tool                                                Version  Source                      Requested 
aqua:atuinsh/atuin                                  18.24.6  ~/.config/mise/config.toml  latest
argocd                                              3.6.1    ~/.config/mise/config.toml  latest
gh                                                  2.103.3  ~/.config/mise/config.toml  latest
github:Dicklesworthstone/destructive_command_guard  0.16.0   ~/.config/mise/config.toml  latest
github:harehare/mq                                  0.10.1   ~/.config/mise/config.toml  latest
github:mikefarah/yq                                 4.54.3   ~/.config/mise/config.toml  latest
github:yusukebe/ax                                  0.1.25   ~/.config/mise/config.toml  latest 
jq                                                  1.8.2    ~/.config/mise/config.toml  latest
kubectl                                             1.37.2   ~/.config/mise/config.toml  latest
npm:@colbymchenry/codegraph                         1.6.2    ~/.config/mise/config.toml  latest
npm:clawhub                                         0.25.1   ~/.config/mise/config.toml  latest

关于 DSH 配置,配置同步要取一份配置,然后进容器跑 pnpm install。skills 同步同理。

这些如果塞进 initContainer,Pod 每次启动都要重跑一遍,或者得额外做一套缓存判断。装挂了 Pod 起不来,日志还埋在 init 阶段。而且额外引入了很多的复杂性。

所以最终放弃 initContainer方案,做简化: Pod 直接启动 DSH。(🧠可能后续我又会纠结,然后考虑 sidecar 等方案🤣)

最终敲定方案:工具安装、配置同步、skills 同步改成维护者(我)在 Pod Ready 之后通过 kubectl cp / kubectl exec 执行的一次性手动任务,只要结果落在 PVC 上持久化不丢就行。

失败可以重跑,不阻塞 Pod 启动。

代价也很直接:首次部署是「先上线一个空 DSH,再手动补齐工具与配置」。漏跑的话 DSH 照样能启动,但工具和插件不完整。

这个代价可以接受。至少在这一步,可诊断性比自动化程度值钱。

还有个代价就是:不够标准化,不够声明式配置,后续想重建估计又得想破脑袋回忆当初是怎么操作的来着...

四块 PVC

全部 RWO:

PVC 大小 承载
dsh-home 10Gi DSH_HOME
workspace 20Gi /workspace
tools 20Gi 工具与 XDG 目录
skills 1Gi skills

保留策略分两种写法:

  1. dsh-home 走上游的 volumeClaimTemplates + persistentVolumeClaimRetentionPolicy
  2. 另外三块是独立 PVC,带 helm.sh/resource-policy: keep 和 ArgoCD 的 Prune=false,Delete=false。目标只有一个:Pod 重建、Helm 卸载之后数据都还在。

📝Notes :

四块 PVC 后续都用 volsync 做了自动备份策略。RWO 卷是由集群上的 rook-ceph 提供。

配置同步

简单粗暴:从电脑本机拉,本地改写后,tar 传输进容器。

完整流程:

bash 复制代码
# 1. 本机取配置
scripts/sync-dsh-config --source ~/.dsh

# 2. 本地改写(这里是我自己的有些dsh hardcode了本地路径,规划了后续优化)
./transform-config.sh

# 3. tar + kubectl cp 到 Pod 的 /tmp
kubectl cp ./dsh-config.tar <ns>/<pod>:/tmp/dsh-config.tar

# 4. 容器内解包
kubectl exec -it -n <ns> <pod> -- \
  sh -c 'cd $DSH_HOME && tar xf /tmp/dsh-config.tar'

# 5. 容器内装依赖
kubectl exec -it -n <ns> <pod> -- \
  sh -c 'cd $DSH_HOME && CI=true pnpm install --no-frozen-lockfile'

# 6. 重启生效
kubectl rollout restart statefulset/deepseek-harness -n <ns>

复制走白名单,明确不带的:sessions/(后续再看有没有现成的会话同步方案。DSH SuperAgent 后续是不是会做个数据库来存 sessions?)、storages/、.credentials.yaml。

容器化改写清单

从本机 ~/.dsh 到容器,路径、认证、包管理器语义都有差异。

  1. 绝对路径:/home/casey/.dsh → /home/node/.dsh
  2. codegraph / atuin / dcg 指向 mise shims,电脑本机和容器的 mise shims 路径不同
  3. 删掉 profile 里重复的 dshmarket。镜像内置 1.66.9,重复会出问题
  4. atuin(shell 历史记录同步工具) 改由 mise(aqua)安装

另外发现镜像默认把 XDG_CACHE_HOME / XDG_CONFIG_HOME / XDG_DATA_HOME 指向 /tmp。只挂 PVC 不足以持久化工具。

覆盖成:

yaml 复制代码
env:
  - name: XDG_CACHE_HOME
    value: /workspace/.local/cache
  - name: XDG_DATA_HOME
    value: /workspace/.local/share
  - name: XDG_STATE_HOME
    value: /workspace/.local/state
  - name: XDG_CONFIG_HOME
    value: /workspace/.config

PATH 里把 /workspace/.local/bin 和 mise 的 shims 前置。如下:

shell 复制代码
$ echo $PATH
/workspace/.local/bin:/workspace/.local/share/mise/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

不覆盖这一条,Pod 重启一次工具就没了。

K8s 网络

Tailscale L7 Ingress 设置

上游 Service 是 clusterIP: None 的 Headless Service。

我的期望是通过 Tailscale Ingress 暴露,这样我就可以通过手机/电脑/平板等直接访问。

Tailscale Operator 的 L7 Ingress 在 addIngressBackend 里要求后端有 ClusterIP。Headless 直接不行。

所以新增 deepseek-harness-tailscale,ClusterIP,两个端口:http 3080、desktop 6080。Headless 只留给 StatefulSet 的 serviceName。

两条 Ingress:

  • dsh.west-beta.ts.net → 3080
  • dsh-desktop.west-beta.ts.net → 6080 用于打开上游镜像里带的noVNC的浏览器界面。

Web 那条同时把 /novnc- 前缀路由到 6080,并把 DSH_DESKTOP_PUBLIC_PORT 设成 443,UI 里的桌面入口才点得开。

Cilium 设置

上游的 helm chart 设置了 NetworkPolicy。我需要放行 tailscale ingress 的流量,NetworkPolicy 按 tailscale 或 ingress-proxies 标签放行,也试了 ipBlock。结果都不生效。

原因是 Cilium 配了 ipv4NativeRoutingCIDR=192.168.3.0/24。跨节点 Pod 流量被 SNAT 成节点 IP,源身份变成 world。策略 match none,连接直接超时。

最后退化成只按端口放行 3080 和 6080,其余入站继续拒绝:

yaml 复制代码
ingress:
  - ports:
      - port: 3080
        protocol: TCP
      - port: 6080
        protocol: TCP

安全边界

  • 非 root,UID/GID 1000
  • 只读根文件系统
  • drop ALL

noVNC 没有认证。这是显式接受的设计,不是遗漏。只有 tailnet Ingress 能到,Tailscale ACL 是唯一网络边界。反正这个 Tailnet 是我自己一个人用的。

Secret 里配置了两个 key:

  • DEEPSEEK_API_KEY
  • GITHUB_API_TOKEN: 给 mise 用,绕过 GitHub 下载二进制文件的限流

总结

🎉🎉🎉

最终效果如下:

📔Notes:

哦,差点忘了说,记得 DSH 启动时需要加这 2 个 flags:--host <your-domain> --trusted-host <your-domain>,这样才能通过域名访问。

这次落地真正花时间的地方,是把能自动化的和不该自动化的分开:

  • PVC、Service、Ingress、NetworkPolicy 归 Helm 和 ArgoCD。工具安装与配置同步留在 Pod Ready 之后手动做。
  • 平台约束那部分没得选。Tailscale 要 ClusterIP 就给 ClusterIP,Cilium SNAT 让源选择器失效就退到端口级。

先用起来,再根据真实需求逐步迭代优化。

以上。

📚️参考文档

相关推荐
我才是银古15 小时前
把视频生产做成一条可验证的流水线
视频·视频制作·deepseek·dsh·dsh插件
_砖家1 天前
DeepSeek Harness 原来还是我们熟悉的 Webpack 和中间件模型
agent·ai编程·deepseek
NingBo1 天前
别再让 AI「心算」CSV:为 DeepSeek Harness 增加精确的数据分析工具
deepseek
宅小年1 天前
DeepSeek Harness 完全指南,打造你的专属工作台
deepseek
mengge.cloud1 天前
prometheus+k8s+弹性伸缩,综合项目
云原生·容器·kubernetes·prometheus
腾飞开源1 天前
03_Kubernetes 核心概念拆解:从 Spec/Status 收敛到工作负载控制器的设计逻辑
云原生·容器·kubernetes
逻辑与温度2 天前
花了三毛六,我把 Claude Code 和 DSH 测了三轮:同一个 DeepSeek,两个壳差距有多大
claude·deepseek
kuroomi2 天前
Git入门
git·kubernetes
king王一帅2 天前
当 DSH 适配了 Quirekit md-editor: 你的笔记软件为何不能是 DSH(MIT开源)
agent·markdown·deepseek