fix: 使用 HTTP API 代理方式调用 K8S

- 重写 k8s-client.ts,改用 HTTP API 代理方式而非 SDK 直连
- @kubernetes/client-node 仅用于类型定义
- 修复部署功能,正确调用 K8S API 代理服务

Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
D8D Developer
2026-03-20 12:01:19 +00:00
parent 35eb7f93eb
commit 77965b2c9f

View File

@@ -1,5 +1,12 @@
/**
* K8S 客户端 - 通过 HTTP API 代理调用 K8S
* @kubernetes/client-node 仅用于类型定义
*/
import axios from 'axios';
import * as k8s from '@kubernetes/client-node';
// 导出类型
export type V1Deployment = k8s.V1Deployment;
export type V1DeploymentList = k8s.V1DeploymentList;
export type V1Ingress = k8s.V1Ingress;
@@ -8,131 +15,303 @@ export type V1Pod = k8s.V1Pod;
export type V1Service = k8s.V1Service;
export type V1ServicePort = k8s.V1ServicePort;
// 构建 URL 路径
const buildUrl = (baseUrl: string, path: string, params?: Record<string, string>) => {
let url = baseUrl.replace(/\/+$/, '') + '/api/v1/kubernetes/' + path.replace(/^\/+/, '');
if (params) {
for (const [key, value] of Object.entries(params)) {
url = url.replace(`:${key}`, value);
}
}
return url;
};
// 构建查询字符串
const buildQuery = (query?: Record<string, string | number | boolean | undefined>) => {
if (!query) return '';
const params = new URLSearchParams();
for (const [key, value] of Object.entries(query)) {
if (value !== undefined) {
params.set(key, String(value));
}
}
return params.toString() ? '?' + params.toString() : '';
};
// HTTP 请求封装
const request = async (
baseUrl: string,
token: string,
method: string,
path: string,
args?: {
param?: Record<string, string>;
query?: Record<string, string | number | boolean | undefined>;
json?: unknown;
}
) => {
let url = buildUrl(baseUrl, path, args?.param);
url += buildQuery(args?.query);
const response = await axios.request({
url,
method,
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
data: args?.json ? JSON.stringify(args.json) : undefined,
validateStatus: () => true, // 不抛出 HTTP 错误
});
return response;
};
/**
* K8S 客户端类
*/
export class K8sClient {
private kubeConfig: k8s.KubeConfig;
private baseUrl: string;
private token: string;
constructor(baseUrl: string, token: string) {
this.kubeConfig = new k8s.KubeConfig();
this.kubeConfig.loadFromOptions({
clusters: [{ name: 'cluster', server: baseUrl, skipTLSVerify: true }],
users: [{ name: 'user', authProvider: { name: 'token', config: { token } } }],
contexts: [{ name: 'ctx', cluster: 'cluster', user: 'user' }],
currentContext: 'ctx',
});
this.baseUrl = baseUrl;
this.token = token;
}
private getCoreV1Api() {
return this.kubeConfig.makeApiClient(k8s.CoreV1Api);
}
// ========== Pod 操作 ==========
private getAppsV1Api() {
return this.kubeConfig.makeApiClient(k8s.AppsV1Api);
}
private getNetworkingV1Api() {
return this.kubeConfig.makeApiClient(k8s.NetworkingV1Api);
}
async getPods(args: { param: { namespace?: string }; query?: { labelSelector?: string; fieldSelector?: string; limit?: number; continue?: string } }): Promise<{ pods: k8s.V1Pod[] }> {
async getPods(args: {
param: { namespace?: string };
query?: { labelSelector?: string; fieldSelector?: string; limit?: number; continue?: string };
}): Promise<{ pods: V1Pod[] }> {
try {
const ns = args.param.namespace || 'default';
const res = await this.getCoreV1Api().listNamespacedPod({ namespace: ns, labelSelector: args.query?.labelSelector, fieldSelector: args.query?.fieldSelector, limit: args.query?.limit, _continue: args.query?.continue });
return { pods: res.items || [] };
} catch { return { pods: [] }; }
const namespace = args.param.namespace || 'default';
const res = await request(this.baseUrl, this.token, 'GET', 'pods/:namespace', {
param: { namespace },
query: args.query,
});
if (res.status !== 200) {
throw new Error(res.data?.message || '获取 Pods 失败');
}
return { pods: res.data?.pods || [] };
} catch (error) {
console.error('获取 Pods 失败:', error);
return { pods: [] };
}
}
async getPod(args: { param: { namespace: string; podName: string } }): Promise<k8s.V1Pod> {
return this.getCoreV1Api().readNamespacedPod({ name: args.param.podName, namespace: args.param.namespace });
async getPod(args: { param: { namespace: string; podName: string } }): Promise<V1Pod> {
const res = await request(this.baseUrl, this.token, 'GET', 'pods/:namespace/:podName', {
param: { namespace: args.param.namespace, podName: args.param.podName },
});
if (res.status !== 200) {
throw new Error(res.data?.message || '获取 Pod 失败');
}
return res.data;
}
async createPod(args: { json: { namespace: string; pod: V1Pod } }): Promise<{ success: boolean; message: string; pod?: V1Pod }> {
try {
const res = await this.getCoreV1Api().createNamespacedPod({ namespace: args.json.namespace, body: args.json.pod });
return { success: true, message: 'Pod 创建成功', pod: res };
} catch (e) { return { success: false, message: `创建 Pod 失败: ${e}` }; }
const res = await request(this.baseUrl, this.token, 'POST', 'pods', { json: args.json });
if (res.status !== 200) {
return { success: false, message: res.data?.message || '创建 Pod 失败' };
}
return { success: true, message: 'Pod 创建成功', pod: res.data };
} catch (error) {
return { success: false, message: `创建 Pod 失败: ${error}` };
}
}
async deletePod(args: { param: { namespace: string; podName: string } }): Promise<{ success: boolean; message: string }> {
try {
await this.getCoreV1Api().deleteNamespacedPod({ name: args.param.podName, namespace: args.param.namespace });
const res = await request(this.baseUrl, this.token, 'DELETE', 'pods/:namespace/:podName', {
param: { namespace: args.param.namespace, podName: args.param.podName },
});
if (res.status !== 200) {
return { success: false, message: res.data?.message || '删除 Pod 失败' };
}
return { success: true, message: 'Pod 删除成功' };
} catch (e) { return { success: false, message: `删除 Pod 失败: ${e}` }; }
} catch (error) {
return { success: false, message: `删除 Pod 失败: ${error}` };
}
}
async getDeployments(args: { param: { namespace: string }; query?: { limit?: number; continue?: string } }): Promise<V1DeploymentList> {
try {
const res = await this.getAppsV1Api().listNamespacedDeployment({ namespace: args.param.namespace, limit: args.query?.limit, _continue: args.query?.continue });
return { items: res.items || [], metadata: res.metadata || {} };
} catch { return { items: [], metadata: {} }; }
}
// ========== Deployment 操作 ==========
async getDeployment(args: { param: { namespace: string; name: string } }): Promise<V1Deployment> {
return this.getAppsV1Api().readNamespacedDeployment({ name: args.param.name, namespace: args.param.namespace });
const res = await request(this.baseUrl, this.token, 'GET', 'deployments/:namespace/:name', {
param: { namespace: args.param.namespace, name: args.param.name },
});
if (res.status !== 200) {
const error = new Error(res.data?.message || `Deployment ${args.param.name} not found`);
(error as any).code = res.status;
throw error;
}
return res.data;
}
async createDeployment(args: { json: { namespace: string; deployment: V1Deployment } }): Promise<{ success: boolean; message: string; deployment?: V1Deployment }> {
async getDeployments(args: {
param: { namespace: string };
query?: { limit?: number; continue?: string };
}): Promise<V1DeploymentList> {
try {
const res = await this.getAppsV1Api().createNamespacedDeployment({ namespace: args.json.namespace, body: args.json.deployment });
return { success: true, message: 'Deployment 创建成功', deployment: res };
} catch (e) { return { success: false, message: `创建 Deployment 失败: ${e}` }; }
const res = await request(this.baseUrl, this.token, 'GET', 'deployments/:namespace', {
param: { namespace: args.param.namespace },
query: args.query,
});
if (res.status !== 200) {
return { items: [], metadata: {} };
}
return { items: res.data?.items || [], metadata: res.data?.metadata || {} };
} catch (error) {
console.error('获取 Deployments 失败:', error);
return { items: [], metadata: {} };
}
}
async deleteDeployment(args: { param: { namespace: string; name: string } }): Promise<{ success: boolean; message: string }> {
async createDeployment(args: {
json: { namespace: string; deployment: V1Deployment };
}): Promise<{ success: boolean; message: string; deployment?: V1Deployment }> {
try {
await this.getAppsV1Api().deleteNamespacedDeployment({ name: args.param.name, namespace: args.param.namespace });
const res = await request(this.baseUrl, this.token, 'POST', 'deployments', { json: args.json });
if (res.status !== 200) {
return { success: false, message: res.data?.message || '创建 Deployment 失败' };
}
return { success: true, message: 'Deployment 创建成功', deployment: res.data };
} catch (error) {
return { success: false, message: `创建 Deployment 失败: ${error}` };
}
}
async deleteDeployment(args: {
param: { namespace: string; name: string };
}): Promise<{ success: boolean; message: string }> {
try {
const res = await request(this.baseUrl, this.token, 'DELETE', 'deployments/:namespace/:name', {
param: { namespace: args.param.namespace, name: args.param.name },
});
if (res.status !== 200) {
return { success: false, message: res.data?.message || '删除 Deployment 失败' };
}
return { success: true, message: 'Deployment 删除成功' };
} catch (e) { return { success: false, message: `删除 Deployment 失败: ${e}` }; }
} catch (error) {
return { success: false, message: `删除 Deployment 失败: ${error}` };
}
}
async restartDeployment(args: { param: { namespace: string; name: string } }): Promise<{ success: boolean; message: string }> {
async restartDeployment(args: {
param: { namespace: string; name: string };
}): Promise<{ success: boolean; message: string }> {
try {
const pods = await this.getPods({ param: { namespace: args.param.namespace }, query: { labelSelector: `app=${args.param.name}` } });
const pods = await this.getPods({
param: { namespace: args.param.namespace },
query: { labelSelector: `app=${args.param.name}` },
});
for (const pod of pods.pods) {
if (pod.metadata?.name) await this.deletePod({ param: { namespace: args.param.namespace, podName: pod.metadata.name } });
if (pod.metadata?.name) {
await this.deletePod({ param: { namespace: args.param.namespace, podName: pod.metadata.name } });
}
}
return { success: true, message: 'Deployment 重启成功' };
} catch (e) { return { success: false, message: `重启 Deployment 失败: ${e}` }; }
} catch (error) {
return { success: false, message: `重启 Deployment 失败: ${error}` };
}
}
async createService(args: { json: { namespace: string; service: V1Service } }): Promise<{ success: boolean; message: string; service?: V1Service }> {
// ========== Service 操作 ==========
async createService(args: {
json: { namespace: string; service: V1Service };
}): Promise<{ success: boolean; message: string; service?: V1Service }> {
try {
const res = await this.getCoreV1Api().createNamespacedService({ namespace: args.json.namespace, body: args.json.service });
return { success: true, message: 'Service 创建成功', service: res };
} catch (e) { return { success: false, message: `创建 Service 失败: ${e}` }; }
const res = await request(this.baseUrl, this.token, 'POST', 'services', { json: args.json });
if (res.status !== 200) {
return { success: false, message: res.data?.message || '创建 Service 失败' };
}
return { success: true, message: 'Service 创建成功', service: res.data };
} catch (error) {
return { success: false, message: `创建 Service 失败: ${error}` };
}
}
async deleteService(args: { param: { namespace: string; serviceName: string } }): Promise<{ success: boolean; message: string }> {
async deleteService(args: {
param: { namespace: string; serviceName: string };
}): Promise<{ success: boolean; message: string }> {
try {
await this.getCoreV1Api().deleteNamespacedService({ name: args.param.serviceName, namespace: args.param.namespace });
const res = await request(this.baseUrl, this.token, 'DELETE', 'services/:namespace/:serviceName', {
param: { namespace: args.param.namespace, serviceName: args.param.serviceName },
});
if (res.status !== 200) {
return { success: false, message: res.data?.message || '删除 Service 失败' };
}
return { success: true, message: 'Service 删除成功' };
} catch (e) { return { success: false, message: `删除 Service 失败: ${e}` }; }
} catch (error) {
return { success: false, message: `删除 Service 失败: ${error}` };
}
}
async getService(args: { param: { namespace: string; serviceName: string } }): Promise<{ service?: V1Service }> {
async getService(args: {
param: { namespace: string; serviceName: string };
}): Promise<{ service?: V1Service }> {
try {
const res = await this.getCoreV1Api().readNamespacedService({ name: args.param.serviceName, namespace: args.param.namespace });
return { service: res };
} catch { return {}; }
const res = await request(this.baseUrl, this.token, 'GET', 'services/:namespace/:serviceName', {
param: { namespace: args.param.namespace, serviceName: args.param.serviceName },
});
if (res.status !== 200) {
return {};
}
return { service: res.data };
} catch {
return {};
}
}
async createIngress(args: { json: { namespace: string; ingress: V1Ingress } }): Promise<{ success: boolean; message: string; ingress?: V1Ingress }> {
// ========== Ingress 操作 ==========
async createIngress(args: {
json: { namespace: string; ingress: V1Ingress };
}): Promise<{ success: boolean; message: string; ingress?: V1Ingress }> {
try {
const res = await this.getNetworkingV1Api().createNamespacedIngress({ namespace: args.json.namespace, body: args.json.ingress });
return { success: true, message: 'Ingress 创建成功', ingress: res };
} catch (e) { return { success: false, message: `创建 Ingress 失败: ${e}` }; }
const res = await request(this.baseUrl, this.token, 'POST', 'ingresses', { json: args.json });
if (res.status !== 200) {
return { success: false, message: res.data?.message || '创建 Ingress 失败' };
}
return { success: true, message: 'Ingress 创建成功', ingress: res.data };
} catch (error) {
return { success: false, message: `创建 Ingress 失败: ${error}` };
}
}
async deleteIngress(args: { param: { namespace: string; ingressName: string } }): Promise<{ success: boolean; message: string }> {
async deleteIngress(args: {
param: { namespace: string; ingressName: string };
}): Promise<{ success: boolean; message: string }> {
try {
await this.getNetworkingV1Api().deleteNamespacedIngress({ name: args.param.ingressName, namespace: args.param.namespace });
const res = await request(this.baseUrl, this.token, 'DELETE', 'ingresses/:namespace/:ingressName', {
param: { namespace: args.param.namespace, ingressName: args.param.ingressName },
});
if (res.status !== 200) {
return { success: false, message: res.data?.message || '删除 Ingress 失败' };
}
return { success: true, message: 'Ingress 删除成功' };
} catch (e) { return { success: false, message: `删除 Ingress 失败: ${e}` }; }
} catch (error) {
return { success: false, message: `删除 Ingress 失败: ${error}` };
}
}
async getIngress(args: { param: { namespace: string; ingressName: string } }): Promise<{ ingress?: V1Ingress }> {
async getIngress(args: {
param: { namespace: string; ingressName: string };
}): Promise<{ ingress?: V1Ingress }> {
try {
const res = await this.getNetworkingV1Api().readNamespacedIngress({ name: args.param.ingressName, namespace: args.param.namespace });
return { ingress: res };
} catch { return {}; }
const res = await request(this.baseUrl, this.token, 'GET', 'ingresses/:namespace/:ingressName', {
param: { namespace: args.param.namespace, ingressName: args.param.ingressName },
});
if (res.status !== 200) {
return {};
}
return { ingress: res.data };
} catch {
return {};
}
}
}