如何将NodeJS/Express REST API接入K8s自定义资源并关联部署、成为API端点?
将NodeJS/Express API集成到Kubernetes API并关联自定义资源
核心思路
要把你的Express API变成Kubernetes API的一部分,核心是利用**Kubernetes聚合层(Aggregation Layer)**扩展API,同时通过自定义资源定义(CRD)+ 控制器实现CR与应用部署的关联。以下是分步实现方案:
1. 确认集群已启用聚合层
Kubernetes聚合层默认可能未开启,需检查kube-apiserver的启动参数:
- 确保存在
--enable-aggregator-routing=true - 确认kube-apiserver有权限访问集群内服务(默认配置通常满足,若有问题需调整RBAC规则)
2. 改造Express API适配Kubernetes规范
你的API需要遵循Kubernetes的API格式,同时处理K8s的身份认证:
2.1 遵循K8s API响应格式
所有返回的资源需包含kind、apiVersion、metadata、spec、status字段,示例结构:
// GET /apis/myapp.example.com/v1/myapps/{name} 返回示例 res.json({ kind: "MyApp", apiVersion: "myapp.example.com/v1", metadata: { name: "demo-app", namespace: "default", uid: "xxxx-xxxx-xxxx" }, spec: { image: "node:18-alpine", replicas: 2, port: 3000 }, status: { readyReplicas: 2, phase: "Running" } });
2.2 处理Kubernetes身份认证
K8s API要求请求携带Bearer Token,需在Express中解析并验证Token:
const express = require('express'); const { KubeConfig, TokenReview } = require('@kubernetes/client-node'); const app = express(); app.use(express.json()); // Token验证中间件 async function authenticate(req, res, next) { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).send('Missing or invalid authorization header'); } const token = authHeader.split(' ')[1]; const kc = new KubeConfig(); kc.loadFromDefault(); const k8sApi = kc.makeApiClient(TokenReview); try { const review = await k8sApi.createTokenReview({ spec: { token } }); if (review.body.status.authenticated) { req.user = review.body.status.user; next(); } else { res.status(401).send('Invalid token'); } } catch (err) { res.status(500).send('Failed to verify token'); } } // 给API路由应用认证中间件 app.use('/apis/myapp.example.com/v1', authenticate); // 实现CRUD路由 app.get('/apis/myapp.example.com/v1/myapps', (req, res) => { res.json({ kind: "MyAppList", apiVersion: "myapp.example.com/v1", items: [] }); }); // POST/PUT/DELETE路由逻辑类似,此处省略
3. 部署Express API到Kubernetes
3.1 编写Deployment和Service YAML
# myapp-api-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: myapp-api namespace: default spec: replicas: 2 selector: matchLabels: app: myapp-api template: metadata: labels: app: myapp-api spec: serviceAccountName: myapp-api-sa containers: - name: myapp-api image: your-nodejs-image:latest # 替换为你的镜像地址 ports: - containerPort: 3000 --- # myapp-api-service.yaml apiVersion: v1 kind: Service metadata: name: myapp-api namespace: default spec: selector: app: myapp-api ports: - port: 443 targetPort: 3000
3.2 创建ServiceAccount及权限
给API服务账号分配调用TokenReview的权限:
# myapp-api-sa.yaml apiVersion: v1 kind: ServiceAccount metadata: name: myapp-api-sa namespace: default --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: myapp-api-clusterrole rules: - apiGroups: ["authentication.k8s.io"] resources: ["tokenreviews"] verbs: ["create"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: myapp-api-binding subjects: - kind: ServiceAccount name: myapp-api-sa namespace: default roleRef: kind: ClusterRole name: myapp-api-clusterrole apiGroup: rbac.authorization.k8s.io
4. 注册API到Kubernetes聚合层
创建APIService资源,告知K8s你的API位置:
# myapp-api-service-registration.yaml apiVersion: apiregistration.k8s.io/v1 kind: APIService metadata: name: v1.myapp.example.com spec: group: myapp.example.com version: v1 service: name: myapp-api namespace: default groupPriorityMinimum: 1000 versionPriority: 10
5. 关联CR与应用部署(实现控制器逻辑)
用NodeJS编写控制器,监听CR变化,自动创建/更新应用部署:
5.1 定义CRD
# myapp-crd.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: myapps.myapp.example.com spec: group: myapp.example.com versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: image: type: string replicas: type: integer port: type: integer status: type: object properties: readyReplicas: type: integer phase: type: string scope: Namespaced names: plural: myapps singular: myapp kind: MyApp shortNames: - ma
5.2 NodeJS控制器代码示例
const { KubeConfig, AppsV1Api, CustomObjectsApi, Watch } = require('@kubernetes/client-node'); const kc = new KubeConfig(); kc.loadFromDefault(); const appsApi = kc.makeApiClient(AppsV1Api); const customApi = kc.makeApiClient(CustomObjectsApi); // 监听MyApp CR的变化 async function watchMyApps() { const watch = new Watch(kc); await watch.watch( '/apis/myapp.example.com/v1/namespaces/default/myapps', {}, (type, obj) => { switch (type) { case 'ADDED': createDeployment(obj); break; case 'MODIFIED': updateDeployment(obj); break; case 'DELETED': deleteDeployment(obj); break; } }, (err) => { console.error('Watch error:', err); setTimeout(watchMyApps, 5000); // 出错后重试 } ); } // 根据CR创建Deployment async function createDeployment(myApp) { const deploymentName = `myapp-${myApp.metadata.name}`; try { await appsApi.createNamespacedDeployment('default', { metadata: { name: deploymentName, labels: { app: deploymentName } }, spec: { replicas: myApp.spec.replicas, selector: { matchLabels: { app: deploymentName } }, template: { metadata: { labels: { app: deploymentName } }, spec: { containers: [{ name: 'app-container', image: myApp.spec.image, ports: [{ containerPort: myApp.spec.port }] }] } } } }); // 更新CR状态 await customApi.patchNamespacedCustomObject( 'myapp.example.com', 'v1', 'default', 'myapps', myApp.metadata.name, [{ op: 'replace', path: '/status', value: { phase: 'Running', readyReplicas: myApp.spec.replicas } }] ); } catch (err) { console.error('Failed to create deployment:', err); } } // 更新/删除Deployment逻辑类似,此处省略 // 启动监听 watchMyApps();
验证
部署所有资源后,可通过Kubectl直接访问你的API:
kubectl get myapps # 创建一个MyApp CR实例 kubectl create -f - <<EOF apiVersion: myapp.example.com/v1 kind: MyApp metadata: name: demo spec: image: node:18-alpine replicas: 2 port: 3000 EOF # 检查是否自动创建了对应Deployment kubectl get deployments
内容的提问来源于stack exchange,提问作者Lady Sonia
相关产品推荐
相关产品推荐

