ones.pub

self-hostingnomad

OPC 要不要上 K8s?我换成 Nomad 两年半,一个服务一百行配置

OPC 自建服务器选 Nomad 还是 K8s?从 K8s 换到 Nomad 两年半,1333 行配置跑完全部服务,逐项对比部署、密钥、跨机房、路由、存储和代价。

服务一多,docker compose 就不够用了:数据库、反向代理、镜像仓库、本地大模型、定时备份,机器也从一台变成几台,家里和云上都有。哪个容器跑在哪台机器上、挂了谁来拉起、换机器怎么迁,全靠自己记。

下一步看起来只有 K8s。可一个人上 K8s,头一个月都在装插件:ingress controller、cert-manager、CSI、各种 operator,每个都要升级,每个都会坏。

2024 年 3 月我删了 K8s 的 manifests 换成 Nomad,用到今天两年半。全部服务 14 个文件、1333 行 HCL,平均一个不到 100 行。部署一条命令,没有 CI、Helm、operator。一个人管几台机器,Nomad 比 K8s 省得多,什么都没少。

K8s 重在概念,不在二进制

k3s 也是一个二进制,可 K8s 的重从来不在安装包。

在 K8s 上跑一个带域名、带证书、带持久存储的服务,要写 Deployment、Service、Ingress、Secret、ConfigMap、PVC、StorageClass,再装 ingress controller 和 cert-manager。每个对象一套字段,散在好几个文件里,或者塞进一个 Helm chart。

Nomad 只有三层:job → group → task,路由、密钥、存储、重启策略全是这一个文件里的块。

2024 年那次迁移留下一个数字:同样三个服务,K8s 是 9 个 YAML、226 行,Nomad 是 3 个文件、126 行。

一个服务一个文件

我的 Postgres 作业文件删掉细节后长这样,完整版 111 行,连备份都在里面。注释标的四件事,在 K8s 上各要一个对象加一个插件:

variable "pg_sec" { type = string }   # 密钥没有默认值,忘了传就提交失败

job "postgres" {
  constraint {                        # 只去能挂 Ceph 卷的节点
    attribute = "${meta.has_rbd}"
    value     = "true"
  }

  group "db" {
    reschedule { unlimited = true }   # 节点没了就换一台重来

    task "postgres" {
      driver = "docker"
      config {
        image = "pgvector/pgvector:pg18"
        mount {                       # 1. 存储:给个名字和大小,剩下驱动管
          type   = "volume"
          source = "postgres-data"
          target = "/var/lib/postgresql"
          volume_options {
            driver_config {
              name    = "wetopi/rbd"
              options = { size = "<MB>" }
            }
          }
        }
      }
      template {                      # 2. 密钥:进程起来就是环境变量
        data        = var.pg_sec
        destination = "secrets/pg.env"
        env         = true
      }
      service {                       # 3. 路由:Traefik 看到这几个 tag 就开一条 TCP 路由
        name = "postgres"
        port = "db"
        tags = [
          "traefik.enable=true",
          "traefik.tcp.routers.pg.entrypoints=postgres",
          "traefik.tcp.routers.pg.rule=HostSNI(`*`)",
        ]
      }
    }

    task "backup" {                   # 4. 备份:不是容器,每小时 pg_dumpall 进 restic
      driver = "exec"
      lifecycle {
        hook    = "poststart"
        sidecar = true
      }
    }
  }
}

部署是一条命令,密钥不落盘

nomad job run -var pg_sec="$(sops -d secrets/postgres.sec.env)" cluster/postgres.hcl

从笔记本直接打到集群,没有部署脚本,没有 CI。

密钥用 sops 加密放在 git 里,提交那一刻才解开传进去,节点上不留副本。忘了传,提交直接失败。

K8s 的 Secret 只是 base64,想放进 git 还得上 sealed-secrets 或 external-secrets,再配一套 GitOps。每一层都得拿着你的私钥才能干活,一个人一台笔记本,不如直接提交。

调度只认 4 个标签

每个节点打四个标签:哪个集群、哪个站点、是不是公网入口、能不能挂 Ceph 卷。作业只认这四个,affinity、spread 一个没用。

重装一台机器,打上同样的标签,作业自动回来。没有一个作业文件写过主机名。

这一项 K8s 用 nodeSelector 也做得到。

机器分散在多地,Nomad 当它是常态

一个人的机器天生分散:家里几台跑存储和显卡,云上一台有公网 IP。我要的是一个集群、一条部署路径。

Nomad 的客户端只要连得上服务端就能加入,隔着 NAT、隔着公网都行,任务之间不要求互通。我的节点用 Tailscale 连成一张网,Nomad 跑在上面。

站点就是一个标签。两个地方都要跑的服务,一个文件里开两个 group。我的 Traefik 就是这样:一个 job、两个 group、两个入口面,一面在云上,一面在家里,坏了各坏各的。Nomad 自带的 datacenter 和 region 我都没用上。

K8s 默认一个集群就是一个机房:所有 Pod 要在一张平坦网络里互通,跨机房就得把 overlay 铺过公网;Service 转发不分远近;持久卷绑死在一个站点。官方答案是一个机房一个集群,再加一层 Karmada 或 ClusterMesh 管起来。一个人维护 N 个集群加一层联邦,就是灾难。

路由跟着服务走

服务要暴露出去,在自己的 service 块加几个 tag:域名、用哪个证书。Traefik 读到 tag 路由就通了,证书它自己申请。

重新部署,路由跟着重新注册,Traefik 一行不改。

K8s 上同样的事:Ingress、ingress controller、cert-manager、ClusterIssuer、Certificate。

存储是一个 Docker volume 插件

骨架里那个 mount 块就是全部:卷名加大小。驱动第一次用时在 Ceph 上建卷,之后作业跑到哪个节点,卷就挂到哪个节点。没有 CSI,没有 StorageClass、PVC、PV。

节点丢了,作业带着卷换一台机器重启;第二个挂载会被拒绝,同一份数据不会被两个实例同时写。

不只跑容器

Postgres 的备份任务是 exec 驱动,一个进程,不是容器,每小时 pg_dumpall 进 restic。本地大模型是 llama.cpp 加一块显卡,作业里声明一个 GPU 设备。

K8s 只跑容器。Nomad 内置 docker、exec、java、qemu 几种驱动,混着跑。

代价

  • 没有 Helm chart。别人的部署说明是 compose 或 Helm,要自己翻成 HCL。
  • 多一个 Consul。我用它做服务发现,每台机器要跑两个 agent。Nomad 现在自带服务发现,新搭可以不装。
  • 东家换了。HashiCorp 2023 年改了 BSL 许可,2025 年被 IBM 收购。自己用不受影响,发版也正常,但生态不会像 K8s 那样滚起来。
  • 没有托管服务。云厂商都有托管 K8s,没有托管 Nomad。

什么时候还是选 K8s

  • 有团队,或者打算招人。K8s 是通用语言,新人不用学第二套。
  • 用云上的托管集群,不碰节点。
  • 要用某个只在 K8s 上有的 operator。

一个人、自己的机器、几十个以内的服务,选 Nomad。1333 行配置一次能全部塞进 AI 的上下文,改一个服务时它看得到全局;K8s 光 cert-manager 的 CRD 就比这长。


你一个人管几台机器,用的什么?换过的说说原因。