Skip to content
Architecture

bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8): No such file or directory

title: 架构 weight: 6 cascade: type: docs

np 架构

np 将四个成熟的开源项目整合为一个统一平台。

┌─────────────────────────────────────────────────┐
│                    np CLI                        │
│         (单一命令界面,统一所有操作)              │
├──────────┬──────────┬──────────┬────────────────┤
│  Nomad   │  Consul  │ Traefik  │  OpenObserve   │
│ 调度器    │ 服务发现  │ 入口网关  │  可观测性       │
├──────────┼──────────┼──────────┼────────────────┤
│                 Linux 主机                       │
└─────────────────────────────────────────────────┘

组件详解

Nomad — 调度器

负责服务编排的核心引擎:

  • 任务调度和工作负载分发
  • 滚动更新和蓝绿部署
  • 健康检查和自动重启
  • 资源感知型装箱

Consul — 服务发现

服务网络层:

  • 服务注册和发现
  • 分布式健康检查
  • DNS 和 API 查询接口
  • 键值存储(配置共享)

Traefik — 入口网关

流量管理层:

  • 反向代理和负载均衡
  • 自动 TLS(Let’s Encrypt)
  • 基于服务标签的动态路由
  • HTTP 到 HTTPS 自动重定向

OpenObserve — 可观测性

日志和指标的一体化平台:

  • 集中日志聚合
  • 指标收集和可视化
  • 内置仪表盘
  • 日志搜索和告警

Podman + crun — 容器运行时

无守护进程、rootless 优先、OCI 原生的容器运行时:

  • 通过 systemd socket 激活按需启动,空闲时零常驻内存
  • 每个容器独立 fork-exec,崩溃不互相影响
  • 原生 rootless 模式,逃逸后无 root 权限

数据流

用户 → np CLI
         │
         ├─ deploy/stop/scale → Nomad API
         │                         │
         │                  Consul(服务注册)
         │                         │
         │                  Traefik(路由更新)
         │
         ├─ status → Nomad API + Consul API
         │
         ├─ logs → OpenObserve API
         │
         └─ config → Consul KV

设计决策

决策 原因
单二进制 CLI 运维简单,无依赖冲突
Nomad 而非 K8s 更低的操作复杂度,单二进制调度器
Consul 而非 etcd 服务发现不限于配置存储
Traefik 而非 Nginx 原生支持 Nomad/Consul 集成
OpenObserve 而非 ELK 更低的资源开销,socket 按需激活(0 MB 空闲),一体化方案
Podman + crun 而非 Docker 无守护进程,省 8%+ 节点内存,原生 rootless

容器运行时:为什么选 Podman + crun

np 默认使用 Podman + crun 作为容器运行时。这不是拍脑袋的决定,而是从第一性原理出发的推理。

容器运行时实际上只做四件事

1. 解压 OCI 镜像
2. 创建 namespace(pid, net, mnt, uts, ipc, user, cgroup)
3. 配置 cgroup 资源限制
4. 执行 entrypoint

就这些。Docker 在这四步之上包装了一个 2013 年诞生的守护进程架构——dockerd(~150 MB 常驻)、containerd(~80 MB)和 containerd-shim(每个容器 ~10 MB)。在现代 Linux 环境下,这些都不是结构上必需的。

Podman + crun vs Docker

Docker Podman + crun
空闲内存 ~230 MB(守护进程) 0 MB(无守护进程)
每容器开销 ~10 MB(containerd-shim) ~1 MB(conmon)
启动链路 client → dockerd → containerd → runc client → podman → crun
Rootless 需 rootless kit,非默认 原生 rootless,默认启用
二进制体积 ~200 MB podman ~50 MB + crun ~3 MB
OCI 合规 兼容但封装层厚 纯 OCI,无中间层
故障隔离 守护进程崩溃 = 所有容器受影响 每个容器独立 fork-exec

对 SME 节点的实际意义

在一台 8 GB 内存的典型节点上运行 50 个服务:

运行时 内存消耗
Docker 230 MB 守护进程 + 50 × 10 MB shim = 730 MB
Podman 0 MB 守护进程 + 50 × 1 MB conmon = 50 MB

净节省 ~680 MB,占节点总内存的 8.5%。 这些内存可以多跑 10-15 个服务,而不是消耗在基础设施本身。

安全退路

np 不锁定运行时。设置 NP_CONTAINER_RUNTIME=docker 即可切回 Docker。np.yaml 中支持对单个服务指定运行时:

services:
  - name: legacy-app
    type: container
    source: my-image:latest
    runtime: docker   # 对此服务使用 Docker

安全层面的深层意义

Podman 默认 rootless 改变了威胁模型。Docker 下的容器逃逸 = 主机 root 权限。Podman rootless 下,攻击者只能进入非特权用户命名空间。对于没有专职安全团队的 SME,“默认安全"比"可配置安全"重要得多。

依赖权衡

Podman 引入了一个外部依赖:nomad-driver-podman,这是 Nomad 的第三方插件。这是一个真实的代价。但风险可控:如果该插件未来失去维护或与新版 Nomad 不兼容,np 只需一个环境变量即可回退到 Docker,用户工作负载不受影响。