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,用户工作负载不受影响。