fix(adminrpc): expose HTTP port 9091 in k8s deployment and service
Some checks are pending
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions

The adminrpc binary runs HTTP endpoints on port grpc_port+1 (9091),
but k8s deployment only exposed port 9090 (gRPC). The /api/admin/*
HTTP routes were unreachable from the admin dashboard frontend.

- Add http container port 9091 to Deployment
- Add http named port to k8s Service
- Point liveness/readiness probes to HTTP port 9091
- Add http_port: 9091 to Helm values.yaml
This commit is contained in:
ZhenYi 2026-04-22 23:56:38 +08:00
parent f125fb0c02
commit 38da729860
3 changed files with 65 additions and 57 deletions

View File

@ -8,123 +8,123 @@
* import { listWorkspaceSessions, kickUser } from "@/lib/admin-rpc"; * import { listWorkspaceSessions, kickUser } from "@/lib/admin-rpc";
*/ */
import { ADMIN_RPC_URL } from "./env"; import {ADMIN_RPC_URL} from "./env";
// Default to k8s internal service address; override via ADMIN_RPC_URL env var // Default to k8s internal service address; override via ADMIN_RPC_URL env var
const BASE_URL = ADMIN_RPC_URL || "http://adminrpc.admin.svc.cluster.local:9091"; const BASE_URL = ADMIN_RPC_URL || "http://adminrpc.gitdataai.svc.cluster.local:9091";
async function rpc<T>(path: string, options?: RequestInit): Promise<T> { async function rpc<T>(path: string, options?: RequestInit): Promise<T> {
const url = `${BASE_URL}${path}`; const url = `${BASE_URL}${path}`;
const res = await fetch(url, { const res = await fetch(url, {
...options, ...options,
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
...options?.headers, ...options?.headers,
}, },
}); });
if (!res.ok) { if (!res.ok) {
const body = await res.text(); const body = await res.text();
throw new Error(`adminrpc ${options?.method ?? "GET"} ${path} failed (${res.status}): ${body}`); throw new Error(`adminrpc ${options?.method ?? "GET"} ${path} failed (${res.status}): ${body}`);
} }
return res.json() as Promise<T>; return res.json() as Promise<T>;
} }
// ─── Types ──────────────────────────────────────────────────────────────────── // ─── Types ────────────────────────────────────────────────────────────────────
export interface UserSession { export interface UserSession {
session_id: string; session_id: string;
user_id: string; user_id: string;
workspace_id: string; workspace_id: string;
ip_address: string; ip_address: string;
user_agent: string; user_agent: string;
connected_at: string; connected_at: string;
last_heartbeat: string; last_heartbeat: string;
} }
export interface SessionInfo { export interface SessionInfo {
user_id: string; user_id: string;
session_count: number; session_count: number;
workspaces: string[]; workspaces: string[];
latest_session: UserSession | null; latest_session: UserSession | null;
} }
// ─── Sessions API ───────────────────────────────────────────────────────────── // ─── Sessions API ─────────────────────────────────────────────────────────────
/** List all active sessions for a workspace. */ /** List all active sessions for a workspace. */
export async function listWorkspaceSessions(workspaceId: string): Promise<UserSession[]> { export async function listWorkspaceSessions(workspaceId: string): Promise<UserSession[]> {
return rpc<UserSession[]>(`/api/admin/sessions/workspace/${encodeURIComponent(workspaceId)}`); return rpc<UserSession[]>(`/api/admin/sessions/workspace/${encodeURIComponent(workspaceId)}`);
} }
/** List all active sessions for a specific user. */ /** List all active sessions for a specific user. */
export async function listUserSessions(userId: string): Promise<UserSession[]> { export async function listUserSessions(userId: string): Promise<UserSession[]> {
return rpc<UserSession[]>(`/api/admin/sessions/user/${encodeURIComponent(userId)}`); return rpc<UserSession[]>(`/api/admin/sessions/user/${encodeURIComponent(userId)}`);
} }
/** Get the online status of a specific user. */ /** Get the online status of a specific user. */
export async function getUserStatus(userId: string): Promise<{ status: string }> { export async function getUserStatus(userId: string): Promise<{ status: string }> {
return rpc<{ status: string }>(`/api/admin/sessions/user/${encodeURIComponent(userId)}/status`); return rpc<{ status: string }>(`/api/admin/sessions/user/${encodeURIComponent(userId)}/status`);
} }
/** Get detailed info for a user (session count, workspaces, latest session). */ /** Get detailed info for a user (session count, workspaces, latest session). */
export async function getUserInfo(userId: string): Promise<SessionInfo | null> { export async function getUserInfo(userId: string): Promise<SessionInfo | null> {
return rpc<SessionInfo | null>(`/api/admin/sessions/user/${encodeURIComponent(userId)}/info`); return rpc<SessionInfo | null>(`/api/admin/sessions/user/${encodeURIComponent(userId)}/info`);
} }
/** List all online user IDs in a workspace. */ /** List all online user IDs in a workspace. */
export async function getWorkspaceOnlineUsers(workspaceId: string): Promise<{ user_ids: string[] }> { export async function getWorkspaceOnlineUsers(workspaceId: string): Promise<{ user_ids: string[] }> {
return rpc<{ user_ids: string[] }>(`/api/admin/sessions/workspace/${encodeURIComponent(workspaceId)}/online-users`); return rpc<{ user_ids: string[] }>(`/api/admin/sessions/workspace/${encodeURIComponent(workspaceId)}/online-users`);
} }
/** Check if a user is currently online. */ /** Check if a user is currently online. */
export async function isUserOnline(userId: string): Promise<{ online: boolean }> { export async function isUserOnline(userId: string): Promise<{ online: boolean }> {
return rpc<{ online: boolean }>(`/api/admin/sessions/user/${encodeURIComponent(userId)}/online`); return rpc<{ online: boolean }>(`/api/admin/sessions/user/${encodeURIComponent(userId)}/online`);
} }
/** Kick all sessions for a user. Returns the number of sessions kicked. */ /** Kick all sessions for a user. Returns the number of sessions kicked. */
export async function kickUser(userId: string): Promise<{ kicked_count: number }> { export async function kickUser(userId: string): Promise<{ kicked_count: number }> {
return rpc<{ kicked_count: number }>("/api/admin/sessions/kick", { return rpc<{ kicked_count: number }>("/api/admin/sessions/kick", {
method: "POST", method: "POST",
body: JSON.stringify({ user_id: userId }), body: JSON.stringify({user_id: userId}),
}); });
} }
/** Kick all sessions for a user in a specific workspace. */ /** Kick all sessions for a user in a specific workspace. */
export async function kickUserFromWorkspace( export async function kickUserFromWorkspace(
userId: string, userId: string,
workspaceId: string workspaceId: string
): Promise<{ kicked_count: number }> { ): Promise<{ kicked_count: number }> {
return rpc<{ kicked_count: number }>("/api/admin/sessions/kick-workspace", { return rpc<{ kicked_count: number }>("/api/admin/sessions/kick-workspace", {
method: "POST", method: "POST",
body: JSON.stringify({ user_id: userId, workspace_id: workspaceId }), body: JSON.stringify({user_id: userId, workspace_id: workspaceId}),
}); });
} }
// ─── Metrics API ───────────────────────────────────────────────────────────── // ─── Metrics API ─────────────────────────────────────────────────────────────
export interface InstanceMetrics { export interface InstanceMetrics {
instance_id: string; instance_id: string;
timestamp_secs: number; timestamp_secs: number;
http: Record<string, string>; http: Record<string, string>;
room: Record<string, string>; room: Record<string, string>;
} }
/** Get metrics across all app instances. */ /** Get metrics across all app instances. */
export async function getMetrics(instanceFilter = ""): Promise<InstanceMetrics[]> { export async function getMetrics(instanceFilter = ""): Promise<InstanceMetrics[]> {
const qs = instanceFilter ? `?instance_filter=${encodeURIComponent(instanceFilter)}` : ""; const qs = instanceFilter ? `?instance_filter=${encodeURIComponent(instanceFilter)}` : "";
return rpc<InstanceMetrics[]>(`/api/admin/metrics${qs}`); return rpc<InstanceMetrics[]>(`/api/admin/metrics${qs}`);
} }
/** Export all metrics as CSV string. */ /** Export all metrics as CSV string. */
export async function exportMetricsCsv(instanceFilter = ""): Promise<string> { export async function exportMetricsCsv(instanceFilter = ""): Promise<string> {
const qs = instanceFilter ? `?instance_filter=${encodeURIComponent(instanceFilter)}` : ""; const qs = instanceFilter ? `?instance_filter=${encodeURIComponent(instanceFilter)}` : "";
const res = await fetch(`${BASE_URL}/api/admin/metrics/export${qs}`); const res = await fetch(`${BASE_URL}/api/admin/metrics/export${qs}`);
if (!res.ok) { if (!res.ok) {
throw new Error(`adminrpc GET /metrics/export failed (${res.status})`); throw new Error(`adminrpc GET /metrics/export failed (${res.status})`);
} }
return res.text(); return res.text();
} }
/** Get adminrpc health status. */ /** Get adminrpc health status. */
export async function adminRpcHealth(): Promise<{ ok: boolean }> { export async function adminRpcHealth(): Promise<{ ok: boolean }> {
return rpc<{ ok: boolean }>("/health"); return rpc<{ ok: boolean }>("/health");
} }

View File

@ -35,6 +35,9 @@ spec:
- name: grpc - name: grpc
containerPort: {{ .Values.adminrpc.service.port }} containerPort: {{ .Values.adminrpc.service.port }}
protocol: TCP protocol: TCP
- name: http
containerPort: {{ .Values.adminrpc.service.http_port }}
protocol: TCP
args: args:
- --bind - --bind
- "0.0.0.0:{{ .Values.adminrpc.service.port }}" - "0.0.0.0:{{ .Values.adminrpc.service.port }}"
@ -43,12 +46,12 @@ spec:
name: {{ include "gitdata.fullname" . }}-config name: {{ include "gitdata.fullname" . }}-config
livenessProbe: livenessProbe:
tcpSocket: tcpSocket:
port: {{ .Values.adminrpc.service.port }} port: {{ .Values.adminrpc.service.http_port }}
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
tcpSocket: tcpSocket:
port: {{ .Values.adminrpc.service.port }} port: {{ .Values.adminrpc.service.http_port }}
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 5 periodSeconds: 5
{{- if .Values.adminrpc.resources }} {{- if .Values.adminrpc.resources }}
@ -83,6 +86,10 @@ spec:
targetPort: grpc targetPort: grpc
protocol: TCP protocol: TCP
name: grpc name: grpc
- port: {{ .Values.adminrpc.service.http_port }}
targetPort: http
protocol: TCP
name: http
selector: selector:
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-adminrpc app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-adminrpc
app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/instance: {{ .Release.Name }}

View File

@ -401,6 +401,7 @@ adminrpc:
service: service:
port: 9090 port: 9090
http_port: 9091
readinessProbe: readinessProbe:
tcpSocket: tcpSocket: