Kubernetes已经成为容器编排领域的事实标准,越来越多的Node.js后端服务依赖k8s提供的API来完成服务发现、资源编排和状态查询。但在开发与测试阶段,直接连一个真实集群并不总是好选择:环境搭建繁琐、资源操作有风险、接口行为难以按需控制。这时候用Node.js实现一套Kubernetes API Mock服务就成了很实用的方案,它可以在本地快速启动,返回任意构造的模拟数据,甚至可以模拟watch事件流,让依赖k8s的代码在脱离集群的情况下完整跑通逻辑。

一、为什么需要Mock Kubernetes API
Kubernetes的API Server是整个集群的入口,客户端库(如官方的@kubernetes/client-node)本质上是向API Server发送HTTP请求。这意味着只要有一个HTTP服务能够按照k8s的REST规范返回正确的JSON结构,客户端库就能正常工作。理解这一点后,Mock的思路就清晰了:我们不需要模拟整个集群,只需要模拟客户端实际用到的那部分接口。
Mock的价值主要体现在三个场景。第一是单元测试,比如你的服务会根据Deployment的副本数做扩缩容判断,测试时构造一个副本数为0的响应比在真实集群里缩容要快得多。第二是本地开发,开发者机器上没有kubectl环境也没有kubeconfig,Mock服务可以零依赖启动。第三是CI流水线,集成测试不依赖外部集群,避免流水线因为集群不可用而频繁失败。
常见的做法有三种:使用kubectl proxy加本地转发、使用现成的Mock工具、或者自己用Express写一个轻量Mock服务。前两种要么依赖集群,要么定制性不足,自己实现虽然要写一些代码,但可控性最强,下面重点展开。
二、用Express搭建Mock服务核心框架
首先初始化项目并安装依赖:
mkdir k8s-mock-server && cd k8s-mock-server npm init -y npm install express
Kubernetes的API路径有固定规律,形如/api/v1/namespaces/{namespace}/pods或/apis/apps/v1/namespaces/{namespace}/deployments。我们按照这个规律注册路由即可。下面是一个最小可运行的Mock服务:
const express = require('express');
const app = express();
app.use(express.json());
// 模拟数据存储
const store = {
pods: [
{
metadata: { name: 'web-abc123', namespace: 'default' },
spec: { containers: [{ name: 'web', image: 'nginx:1.25' }] },
status: { phase: 'Running', podIP: '10.244.0.5' }
}
]
};
// 查询 Pod 列表
app.get('/api/v1/namespaces/:namespace/pods', (req, res) => {
const items = store.pods.filter(
p => p.metadata.namespace === req.params.namespace
);
res.json({
kind: 'PodList',
apiVersion: 'v1',
metadata: { resourceVersion: '1000' },
items
});
});
// 查询单个 Pod
app.get('/api/v1/namespaces/:namespace/pods/:name', (req, res) => {
const pod = store.pods.find(
p => p.metadata.name === req.params.name
);
if (!pod) {
return res.status(404).json({
kind: 'Status',
status: 'Failure',
reason: 'NotFound',
code: 404
});
}
res.json(pod);
});
const PORT = 18080;
app.listen(PORT, () => console.log(`k8s mock server on :${PORT}`));
这段代码有几个细节值得注意。第一,返回结构必须符合k8s的资源对象规范,kind、apiVersion、metadata这些字段不能少,否则客户端库解析时会抛异常。第二,404响应要用kind: 'Status'的结构,这是k8s API统一的错误格式。第三,列表接口的resourceVersion字段建议保留,后续实现watch时要用它做版本标记。
三、模拟watch长连接与动态数据
只支持GET查询还不够,很多客户端逻辑依赖watch机制做增量同步。watch请求带有?watch=true查询参数,服务端需要保持HTTP连接不关闭,并按行推送JSON事件。Express原生支持这种流式响应,核心是用res.write持续写入数据:
app.get('/api/v1/namespaces/:namespace/pods', (req, res) => {
if (req.query.watch === 'true') {
res.setHeader('Content-Type', 'application/json');
res.setHeader('Transfer-Encoding', 'chunked');
// 模拟每5秒新增一个Pod的事件
let count = 0;
const timer = setInterval(() => {
const podName = `web-${Date.now().toString(36)}`;
res.write(JSON.stringify({
type: 'ADDED',
object: {
metadata: { name: podName, namespace: req.params.namespace },
spec: { containers: [{ name: 'web', image: 'nginx:1.25' }] },
status: { phase: 'Running' }
}
}) + '\n');
if (++count >= 5) {
res.write(JSON.stringify({ type: 'BOOKMARK' }) + '\n');
clearInterval(timer);
res.end();
}
}, 5000);
req.on('close', () => clearInterval(timer));
return;
}
// 非watch走普通列表逻辑
});
事件类型包括ADDED、MODIFIED、DELETED三种,分别对应资源的增、改、删。每个事件后面跟换行符是k8s约定的行分隔JSON格式,客户端按行切割解析。客户端断开连接时要记得清理定时器,否则会造成内存泄漏。有了watch模拟,你就可以测试控制器、Informer这类依赖事件驱动的代码了。
四、让官方客户端库指向Mock服务
写好Mock之后,如何让@kubernetes/client-node连过来?最简单的方式是构造一个指向Mock地址的kubeconfig:
const k8s = require('@kubernetes/client-node');
const kc = new k8s.KubeConfig();
kc.loadFromOptions({
clusters: [{
name: 'mock',
server: 'http://127.0.0.1:18080',
skipTLSVerify: true
}],
users: [{ name: 'mock-user', token: 'fake-token' }],
contexts: [{ name: 'mock', cluster: 'mock', user: 'mock-user' }],
currentContext: 'mock'
});
const k8sApi = kc.makeApiClient(k8s.CoreV1Api);
(async () => {
const res = await k8sApi.listNamespacedPod('default');
res.body.items.forEach(p => console.log(p.metadata.name));
})();
这种写法的好处是完全不改客户端代码,只替换配置来源。在测试框架里可以把它封装成beforeEach钩子,每个用例启动一个独立的Mock实例,实现用例间的数据隔离。此外还可以结合环境变量切换:生产环境用loadFromDefault读真实kubeconfig,测试环境用Mock配置,代码只需一处判断。
五、把Mock服务本身部署进集群
有些场景需要在集群内部使用Mock,比如给联调环境提供一个假的k8s API端点。这时需要把Mock服务容器化并编写部署清单:
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8s-mock-server
labels:
app: k8s-mock-server
spec:
replicas: 1
selector:
matchLabels:
app: k8s-mock-server
template:
metadata:
labels:
app: k8s-mock-server
spec:
containers:
- name: mock
image: ipipp.com/k8s-mock-server:latest
ports:
- containerPort: 18080
---
apiVersion: v1
kind: Service
metadata:
name: k8s-mock-server
spec:
selector:
app: k8s-mock-server
ports:
- port: 80
targetPort: 18080
镜像构建只需一个简单的Dockerfile,基于node:20-alpine,复制package.json安装依赖后CMD ["node","server.js"]即可。进入集群后,其他Pod可以通过http://k8s-mock-server.default.svc.cluster.local这样的集群内DNS地址访问Mock服务,配置方式与上一节的kubeconfig替换完全一致。
六、方案对比与选型建议
自研Express Mock的优势是轻量、可编程、对数据完全可控,缺点是要自己维护接口覆盖度,k8s API版本升级时需要同步调整。如果只是做简单测试,也可以考虑envtest这类工具,它提供了带真实etcd的轻量控制面,行为更接近真实集群,但启动成本比Node.js Mock高。对于只需要验证客户端逻辑的场景,自研Mock依然是性价比最高的选择。
实践中有两点经验值得参考:一是把Mock数据做成JSON fixture文件按需加载,方便不同用例切换数据场景;二是给Mock服务加一个管理接口,允许测试代码在运行时动态注册或修改资源数据,这样可以在测试中模拟Pod被驱逐、Deployment回滚等复杂时序,覆盖更多边界情况。通过这些手段,你可以在不依赖真实集群的前提下,把依赖k8s的Node.js代码测试做到接近全覆盖。
Node.jsKubernetes Mockk8s API修改时间:2026-09-02 03:24:37