|
-
+
+
+
+
|
{c.jobType[job.type]}
@@ -1505,6 +1552,12 @@ export function JobDetailPage({
>;
}) {
const zh = c.nav.overview === "总览";
+ const [jobIdCopied, setJobIdCopied] = useState(false);
+ const handleCopyResourceId = async () => {
+ if (!(await copyText(job.id))) return;
+ setJobIdCopied(true);
+ window.setTimeout(() => setJobIdCopied(false), 1600);
+ };
const [activeTab, setActiveTab] = useState<"workers" | "logs" | "metrics">(
"workers",
);
@@ -1533,9 +1586,28 @@ export function JobDetailPage({
logs: string;
}>
>([]);
+ const [backendLogs, setBackendLogs] = useState<
+ Array<{
+ timestamp: string;
+ line: string;
+ labels?: Record;
+ fields?: Record;
+ }>
+ >([]);
const [logsLoading, setLogsLoading] = useState(false);
const [logsError, setLogsError] = useState(null);
- const [workerRoleFilter, setWorkerRoleFilter] = useState("All");
+ const [logsHasMore, setLogsHasMore] = useState(false);
+ const [logsNextCursor, setLogsNextCursor] = useState("");
+ const [logsLoadingMore, setLogsLoadingMore] = useState(false);
+ // 游标历史栈,用于上一页/下一页翻页。首页为空字符串,第一页查完后 push(nextCursor)。
+ const [logsCursorHistory, setLogsCursorHistory] = useState([""]);
+ const [logsPageIndex, setLogsPageIndex] = useState(0);
+ // 角色配置区块的角色选择(默认第一个角色)
+ const [workerRoleFilter, setWorkerRoleFilter] = useState(
+ job.resources.length > 0 ? job.resources[0].role : "All",
+ );
+ // Worker 列表的角色筛选(独立于角色配置区块,默认 All)
+ const [workerListRoleFilter, setWorkerListRoleFilter] = useState("All");
const [workerPage, setWorkerPage] = useState(1);
const [workerSort, setWorkerSort] = useState<{
key:
@@ -1545,6 +1617,8 @@ export function JobDetailPage({
| "node"
| "kind"
| "ip"
+ | "domainIP"
+ | "gpu"
| "createdAt"
| "phase";
direction: SortDirection;
@@ -1558,6 +1632,9 @@ export function JobDetailPage({
const [logWorkerFilter, setLogWorkerFilter] = useState("All");
const [logQuery, setLogQuery] = useState("");
const [logRange, setLogRange] = useState("1h");
+ const [logCustomRange, setLogCustomRange] = useState(false);
+ const [logCustomFrom, setLogCustomFrom] = useState("");
+ const [logCustomTo, setLogCustomTo] = useState("");
const [logStreamEnabled, setLogStreamEnabled] = useState(false);
// Aggregate Node CR pullProgress for the top StatusBadge hover. Uses Node CR
@@ -1625,10 +1702,18 @@ export function JobDetailPage({
});
const workerTaskNamesKey = workerTaskNames.join(",");
+ const handleWorkerRefresh = async () => {
+ if (workerRefreshing) return;
+ setWorkerRefreshing(true);
+ await refreshTasks();
+ setWorkerRefreshKey((key) => key + 1);
+ };
+
useEffect(() => {
if (!workerTaskNamesKey) {
setPods([]);
setDomainIPMap({});
+ setWorkerRefreshing(false);
return;
}
let cancelled = false;
@@ -1737,69 +1822,180 @@ export function JobDetailPage({
[pendingPodNamesKey],
);
- const fetchLogs = async (isInitial = true) => {
+ const getLogTimeRange = (): { from: string; to: string } => {
+ if (logCustomRange) {
+ if (logCustomFrom && logCustomTo) {
+ // Convert local datetime-local input (no timezone) to UTC ISO string
+ const fromDate = new Date(logCustomFrom);
+ const toDate = new Date(logCustomTo);
+ return { from: fromDate.toISOString(), to: toDate.toISOString() };
+ }
+ // Fallback to 1h if custom range is incomplete
+ }
+ const to = new Date();
+ const from = new Date();
+ switch (logRange) {
+ case "15m":
+ from.setMinutes(from.getMinutes() - 15);
+ break;
+ case "1h":
+ from.setHours(from.getHours() - 1);
+ break;
+ case "6h":
+ from.setHours(from.getHours() - 6);
+ break;
+ case "24h":
+ from.setHours(from.getHours() - 24);
+ break;
+ case "7d":
+ from.setDate(from.getDate() - 7);
+ break;
+ case "30d":
+ from.setDate(from.getDate() - 30);
+ break;
+ default:
+ from.setHours(from.getHours() - 1);
+ }
+ // Convert to UTC ISO string (new Date() is already local time)
+ return { from: from.toISOString(), to: to.toISOString() };
+ };
+
+ const fetchLogs = async (isInitial = true, cursor = "") => {
if (activeTab !== "logs") return;
- if (!isInitial && !logStreamEnabled) return;
if (isInitial) setLogsLoading(true);
+ if (cursor) setLogsLoadingMore(true);
setLogsError(null);
try {
+ const params = new URLSearchParams();
+ const { from, to } = getLogTimeRange();
+ params.set("from", from);
+ params.set("to", to);
+
+ // Always send the first task as the base filter (backend requires it)
+ let taskName = "";
+ if (logRoleFilter !== "All") {
+ const resource = job.resources.find((r) => r.role === logRoleFilter);
+ taskName = resource
+ ? taskResourceName(job.name, resource.role)
+ : logRoleFilter;
+ } else if (logWorkerFilter !== "All") {
+ const matchedPod = pods.find((p) => p.podName === logWorkerFilter);
+ if (matchedPod && matchedPod.taskName) {
+ taskName = matchedPod.taskName;
+ }
+ }
+
+ if (!taskName && job.resources.length > 0) {
+ // Default to the first resource's task if nothing selected
+ taskName = taskResourceName(job.name, job.resources[0].role);
+ }
+
+ if (taskName) {
+ params.set("task", taskName);
+ }
+
+ if (logWorkerFilter !== "All") {
+ params.set("pod", logWorkerFilter);
+ }
+
+ if (logQuery.trim()) {
+ params.set("query", logQuery.trim());
+ }
+
+ if (cursor) {
+ params.set("cursor", cursor);
+ }
+
const resp = await fetch(
- `/api/v1/rlinf.io/v1alpha1/jobs/${encodeURIComponent(job.name)}/logs`,
+ `/api/v1/rlinf.io/v1alpha1/jobs/${encodeURIComponent(job.name)}/logs?${params.toString()}`,
);
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const data = await resp.json();
- setPodLogs(Array.isArray(data.pods) ? data.pods : []);
+ if (data.source === "backend" && Array.isArray(data.entries)) {
+ // 分页模式:替换当前页数据,而不是追加
+ setBackendLogs(data.entries);
+ setPodLogs([]);
+ setLogsHasMore(Boolean(data.hasMore));
+ setLogsNextCursor(data.nextCursor || "");
+ } else {
+ setPodLogs(Array.isArray(data.pods) ? data.pods : []);
+ setBackendLogs([]);
+ setLogsHasMore(false);
+ setLogsNextCursor("");
+ }
} catch (e) {
- setPodLogs([]);
+ if (!cursor) {
+ setPodLogs([]);
+ setBackendLogs([]);
+ }
setLogsError(e instanceof Error ? e.message : String(e));
} finally {
setLogsLoading(false);
+ setLogsLoadingMore(false);
}
};
- useAutoRefresh(fetchLogs, 5000, [activeTab, job.name, logStreamEnabled]);
+ // 查询条件变化时重置到第一页
+ const resetLogsPagination = () => {
+ setLogsCursorHistory([""]);
+ setLogsPageIndex(0);
+ };
- const fallbackWorkers: WorkerItem[] =
- job.taskStatuses.length > 0
- ? job.taskStatuses.map((ts, i) => {
- const childTaskName = ts.name.toLowerCase().replace(/\s+/g, "-");
- const jobChildName = `${job.name}-${childTaskName}`
- .toLowerCase()
- .replace(/\s+/g, "-");
- return {
- id: `${job.id}-${i}`,
- name: ts.name,
- jobId: job.id,
- role: ts.name,
- node:
- taskNodes[jobChildName] ??
- taskNodes[ts.name] ??
- ts.observedNodes?.join(", ") ??
- "—",
- cluster: taskClusters[jobChildName] ?? taskClusters[ts.name] ?? "—",
- phase: (ts.phase || "Pending") as Phase,
- cpu: job.resources.find((item) => item.role === ts.name)?.cpu ?? "",
- memory:
- job.resources.find((item) => item.role === ts.name)?.memory ?? "",
- gpu: job.resources.find((item) => item.role === ts.name)?.gpu,
- logs: ts.message
- ? [ts.message]
- : [
- `${ts.name}: worker state synced`,
- `${ts.name}: waiting for runtime heartbeat`,
- ],
- statusMessage: ts.message || undefined,
- pullProgress:
- pullProgressMap[jobChildName] ??
- pullProgressMap[ts.name.toLowerCase()] ??
- [],
- events:
- taskEventsMap[jobChildName] ??
- taskEventsMap[ts.name.toLowerCase()] ??
- [],
- };
- })
- : [];
+ const goToNextLogPage = () => {
+ if (!logsHasMore || !logsNextCursor || logsLoadingMore) return;
+ const nextHistory = [...logsCursorHistory];
+ nextHistory[logsPageIndex + 1] = logsNextCursor;
+ setLogsCursorHistory(nextHistory);
+ setLogsPageIndex(logsPageIndex + 1);
+ fetchLogs(false, logsNextCursor);
+ };
+
+ const goToPrevLogPage = () => {
+ if (logsPageIndex === 0 || logsLoadingMore) return;
+ const prevIndex = logsPageIndex - 1;
+ const prevCursor = logsCursorHistory[prevIndex] || "";
+ setLogsPageIndex(prevIndex);
+ fetchLogs(false, prevCursor);
+ };
+
+ // 查询条件变化时重置分页到第一页
+ useEffect(() => {
+ resetLogsPagination();
+ }, [
+ logRange,
+ logCustomRange,
+ logCustomFrom,
+ logCustomTo,
+ logRoleFilter,
+ logWorkerFilter,
+ logQuery,
+ ]);
+
+ useAutoRefresh(fetchLogs, 5000, [
+ activeTab,
+ job.name,
+ logStreamEnabled,
+ logRange,
+ logCustomRange,
+ logCustomFrom,
+ logCustomTo,
+ logRoleFilter,
+ logWorkerFilter,
+ logQuery,
+ ]);
+
+ const schedulingSummary = (
+ message: string | undefined,
+ events: NodeEventEntry[],
+ ) =>
+ message === "FailedScheduling" ||
+ events.some((event) => event.reason === "FailedScheduling")
+ ? zh
+ ? "没有合适的节点可调度,资源可能被占用"
+ : "No suitable node is available; resources may be occupied"
+ : message;
+
+ const fallbackWorkers: WorkerItem[] = [];
const resourceForTask = (taskName: string) =>
job.resources.find(
(resource) => taskResourceName(job.name, resource.role) === taskName,
@@ -1815,7 +2011,9 @@ export function JobDetailPage({
// hover surfaces live image pull progress for this worker.
const nodePullProgress =
phase === "Pending" && pod.node
- ? (nodePullProgressMap[pod.node] ?? [])
+ ? (nodePullProgressMap[pod.node] ?? []).filter(
+ (progress) => progress.image === resource?.image,
+ )
: [];
// 同样从 Node.status.events 取节点 warning 事件;当 pod.node
// 缺失时回退到 Task.status.events,让 Pending worker 行 tooltip
@@ -1845,7 +2043,7 @@ export function JobDetailPage({
`${role}: worker state synced`,
`${role}: waiting for runtime heartbeat`,
],
- statusMessage: pod.message || undefined,
+ statusMessage: schedulingSummary(pod.message, workerEvents),
pullProgress: nodePullProgress,
events: workerEvents,
};
@@ -1854,10 +2052,7 @@ export function JobDetailPage({
const runningWorkerCount = jobWorkers.filter(
(worker) => worker.phase === "Running",
).length;
- const displayPhase = effectiveJobPhase(
- job,
- jobWorkers.map((worker) => worker.phase),
- );
+ const displayPhase = effectiveJobPhase(job);
const workerPodsByTask = new Map();
for (const worker of jobWorkers) {
workerPodsByTask.set(
@@ -1869,9 +2064,12 @@ export function JobDetailPage({
),
);
}
- const workerRoles = [...new Set(jobWorkers.map((worker) => worker.role))];
+ const workerRoles = [
+ ...new Set(job.resources.map((resource) => resource.role)),
+ ];
const filteredWorkers = jobWorkers.filter(
- (worker) => workerRoleFilter === "All" || worker.role === workerRoleFilter,
+ (worker) =>
+ workerListRoleFilter === "All" || worker.role === workerListRoleFilter,
);
const toggleWorkerSort = (key: typeof workerSort.key) => {
setWorkerSort((current) => ({
@@ -1899,6 +2097,14 @@ export function JobDetailPage({
return getNodeKindLabel(worker);
case "ip":
return pod?.ip ?? "";
+ case "domainIP":
+ return worker.id
+ ? (domainIPMap[
+ `${worker.id.split("/")[0]}/${worker.id.split("/")[1]}/${worker.id.split("/")[2]}`
+ ] ?? "")
+ : "";
+ case "gpu":
+ return worker.gpu ?? "";
case "createdAt":
return formatWorkerCreatedAt(job.startedAt, index);
case "phase":
@@ -1953,33 +2159,74 @@ export function JobDetailPage({
workerTableDrag.current.active = false;
setWorkerTableDragging(false);
};
- const logEntries = podLogs.flatMap((pod) => {
- const role =
- resourceForTask(pod.taskName)?.role ??
- job.resources.find((resource) => resource.role === pod.taskName)?.role ??
- pod.taskName;
- return pod.logs
- .split("\n")
- .filter(Boolean)
- .map((message, index) => ({
- id: `${pod.podName}-${index}-${message}`,
- worker: pod.podName,
- role,
- phase: pod.phase,
- node: pod.node,
- message,
- }));
- });
- const logRoles = [...new Set(logEntries.map((entry) => entry.role))];
- const logWorkers = [
- ...new Set(
- logEntries
- .filter(
- (entry) => logRoleFilter === "All" || entry.role === logRoleFilter,
- )
- .map((entry) => entry.worker),
- ),
- ];
+ const logEntries =
+ backendLogs.length > 0
+ ? backendLogs.map((entry, index) => {
+ const taskName = entry.labels?.task || entry.fields?.task || "";
+ const podName = entry.labels?.pod || entry.fields?.pod || "";
+ const node = entry.labels?.node || entry.fields?.node || "";
+ const role =
+ resourceForTask(taskName)?.role ??
+ job.resources.find((resource) => resource.role === taskName)
+ ?.role ??
+ taskName;
+ return {
+ id: `${podName}-${entry.timestamp}-${index}`,
+ worker: podName,
+ role,
+ phase: "Running", // Backend logs don't have phase, assume running
+ node,
+ message: entry.line,
+ timestamp: entry.timestamp,
+ };
+ })
+ : podLogs.flatMap((pod) => {
+ const role =
+ resourceForTask(pod.taskName)?.role ??
+ job.resources.find((resource) => resource.role === pod.taskName)
+ ?.role ??
+ pod.taskName;
+ return pod.logs
+ .split("\n")
+ .filter(Boolean)
+ .map((message, index) => ({
+ id: `${pod.podName}-${index}-${message}`,
+ worker: pod.podName,
+ role,
+ phase: pod.phase,
+ node: pod.node,
+ message,
+ timestamp: undefined,
+ }));
+ });
+ // Derive available roles and workers from Job metadata (resources and pods)
+ // so the dropdowns remain populated and selectable even when log query returns 0 entries.
+ const logRoles = useMemo(() => {
+ const rolesFromResources = job.resources.map((r) => r.role);
+ const rolesFromPods = pods.map((p) => {
+ const resource = resourceForTask(p.taskName);
+ return resource?.role ?? p.taskName;
+ });
+ return [...new Set([...rolesFromResources, ...rolesFromPods])].filter(
+ Boolean,
+ );
+ }, [job.resources, pods]);
+
+ const logWorkers = useMemo(() => {
+ return [
+ ...new Set(
+ pods
+ .filter((p) => {
+ if (logRoleFilter === "All") return true;
+ const resource = resourceForTask(p.taskName);
+ const role = resource?.role ?? p.taskName;
+ return role === logRoleFilter;
+ })
+ .map((p) => p.podName),
+ ),
+ ].filter(Boolean);
+ }, [pods, logRoleFilter]);
+
const filteredLogEntries = logEntries.filter(
(entry) =>
(logRoleFilter === "All" || entry.role === logRoleFilter) &&
@@ -1989,7 +2236,7 @@ export function JobDetailPage({
.includes(logQuery.toLowerCase()),
);
const tabs: Array<{ id: typeof activeTab; label: string }> = [
- { id: "workers", label: zh ? "Worker" : "Workers" },
+ { id: "workers", label: zh ? "详情" : "Details" },
{ id: "logs", label: c.common.logs },
{ id: "metrics", label: zh ? "监控" : "Metrics" },
];
@@ -2002,374 +2249,29 @@ export function JobDetailPage({
: undefined;
return (
-
-
- {tabs.map((tab) => (
-
- ))}
-
- {activeTab === "workers" && (
-
-
-
-
- {zh ? "运行实例" : "Runtime instances"}
-
- {zh ? "Worker 列表" : "Worker list"}
-
-
-
- {filteredWorkers.length} {zh ? "个 Worker" : "workers"}
-
-
-
-
- {
- setWorkerRoleFilter(role);
- setWorkerPage(1);
- }}
- />
-
-
-
-
- {(
- [
- ["name", zh ? "实例名称" : "Worker name"],
- ["role", zh ? "角色" : "Role"],
- ["cluster", zh ? "集群" : "Cluster"],
- ["node", zh ? "节点" : "Node"],
- ["kind", zh ? "节点类型" : "Node type"],
- ["ip", zh ? "实例 IP" : "Worker IP"],
- ["createdAt", zh ? "创建时间" : "Created"],
- ["phase", zh ? "状态" : "Status"],
- ] as const
- ).map(([key, label]) => (
- |
- toggleWorkerSort(key)}
- />
- |
- ))}
- |
-
-
-
- {visibleWorkers.map(({ worker, index }) => (
-
- ))}
-
-
-
-
-
- {zh
- ? `第 ${workerPage} / ${workerPageCount} 页`
- : `Page ${workerPage} of ${workerPageCount}`}
-
-
-
-
-
-
-
- )}
- {activeTab === "logs" && (
-
-
-
- {zh ? "任务日志" : "Job logs"}
- {zh ? "Worker 日志流" : "Worker log stream"}
-
-
- {logsLoading ? (
-
-
-
-
-
-
- {zh ? "正在连接 Worker 日志" : "Connecting to worker logs"}
-
-
- {zh
- ? "正在汇总各实例的最新输出…"
- : "Collecting the latest output from each instance…"}
-
-
-
-
- ) : logsError ? (
- {logsError}
- ) : podLogs.length === 0 ? (
- {zh ? "暂无日志" : "No logs available"}
- ) : (
- <>
-
-
-
-
-
-
-
-
-
- {filteredLogEntries.length > 0 ? (
- <>
-
- {zh ? "角色" : "Role"}
- Worker
- {zh ? "日志内容" : "Log message"}
-
- {filteredLogEntries.map((entry) => (
-
- {entry.role}
- {entry.worker}
- {entry.message}
-
- ))}
- >
- ) : (
-
- {zh ? "未找到匹配的日志。" : "No matching logs found."}
-
- )}
-
- >
- )}
-
- )}
- {activeTab === "metrics" && (
-
-
-
- {zh ? "任务监控" : "Job metrics"}
-
- {zh
- ? "Worker 资源与具身通道"
- : "Worker resources and live channel"}
-
-
-
-
-
- )}
-
- );
-}
-
-function JobPublicOverview({
- job,
- copy: c,
- onBack,
- runningWorkerCount,
- totalWorkers,
- displayPhase,
- tensorBoardProxy,
- onClone,
- lifecycleActions,
- jobPullProgress = [],
- jobEvents = [],
- jobFailedMessage,
-}: {
- job: Job;
- copy: CopyType;
- onBack: () => void;
- runningWorkerCount: number;
- totalWorkers: number;
- displayPhase: JobDisplayPhase;
- tensorBoardProxy?: string;
- onClone?: () => void;
- lifecycleActions: JobLifecycleActions;
- // Per-node pullProgress cache shared from JobsPage; surfaced next to the
- // top StatusBadge while the job is still Pending.
- jobPullProgress?: PullProgressEntry[];
- // 节点级 Warning 事件聚合,在 Pending 时与 pullProgress 一并展示。
- jobEvents?: NodeEventEntry[];
- // Failed 状态下聚合的异常原因(CrashLoopBackOff / ImagePullBackOff 等),
- // 供状态徽标 "i" tooltip 展示。
- jobFailedMessage?: string;
-}) {
- const zh = c.nav.overview === "总览";
- const baseConfigRows = [
- {
- label: zh ? "网络域" : "Network domain",
- value: job.domain || (zh ? "未配置" : "Not configured"),
- },
- {
- label: "TensorBoard",
- value: job.tensorBoardDir || (zh ? "未配置" : "Not configured"),
- },
- ];
- return (
-
+ {/* 顶部返回与操作栏 */}
{c.jobs.selected}
- {job.name}
-
- {c.jobType[job.type]} · {job.cluster}
-
+ {job.displayName}
+
+
+
+ {c.jobType[job.type]} · {job.cluster}
+
+
@@ -2474,114 +2376,608 @@ function JobPublicOverview({
-
-
-
-
-
-
-
-
-
- {zh ? "访问入口" : "Access"}
- Header Worker
-
- {tensorBoardProxy && (
-
+ {/* 第一块:角色配置信息(平铺展示,支持切换角色) */}
+ {
+ setWorkerRoleFilter(role);
+ setWorkerPage(1);
+ }}
+ />
+
+ {/* 第二块:公共配置 */}
+
+
+ {/* 第三块:Pod 实例列表 */}
+
+
+
+
+ {zh ? "Worker 列表" : "Runtime workers"}
+
+ {zh ? "Worker 维度查看实例信息" : "Worker list"}
+
+
+
+
+ {workerRoles.map((role) => (
+
+ ))}
+
+
+ {filteredWorkers.length} {zh ? "个 Pod" : "pods"}
+
+
+
+
+
-
-
- )}
-
-
- {job.headerRole || "—"}
- {job.headerWorker && job.headerWorker !== job.headerRole && (
- {job.headerWorker}
- )}
-
-
+
+
+
+ {(
+ [
+ ["name", zh ? "实例名称" : "Worker name"],
+ ["role", zh ? "角色" : "Role"],
+ ["cluster", zh ? "集群" : "Cluster"],
+ ["node", zh ? "节点" : "Node"],
+ ["kind", zh ? "节点类型" : "Node type"],
+ ["ip", zh ? "实例 IP" : "Worker IP"],
+ ["domainIP", zh ? "网络域 IP" : "Domain IP"],
+ ["gpu", zh ? "申请 GPU" : "GPU"],
+ ["createdAt", zh ? "创建时间" : "Created"],
+ ["phase", zh ? "状态" : "Status"],
+ ] as const
+ ).map(([key, label]) => (
+ |
+ toggleWorkerSort(key)}
+ />
+ |
+ ))}
+
+ {zh ? "操作" : "Actions"}
+ |
+
+
+
+ {visibleWorkers.length > 0 ? (
+ visibleWorkers.map(({ worker, index }) => (
+
+ ))
+ ) : (
+
+ |
+ {zh
+ ? "当前没有运行中的 Worker 实例"
+ : "No running worker instances"}
+ |
+
+ )}
+
+
+
+ {visibleWorkers.length > 0 && (
+
+
+ {zh
+ ? `第 ${workerPage} / ${workerPageCount} 页`
+ : `Page ${workerPage} of ${workerPageCount}`}
+
+
+
+
+
+
+ )}
+
+ >
+ )}
-
-
-
-
- {zh ? "启动命令" : "Start command"}
-
-
+ {activeTab === "logs" && (
+
+
+
+ {zh ? "任务日志" : "Job logs"}
+ {zh ? "Worker 日志流" : "Worker log stream"}
+
-
-
- {zh ? "公共配置" : "Shared settings"}
-
-
- {baseConfigRows.map((row) => (
-
- {row.label}
- {row.value}
+ {logsError ? (
+ {logsError}
+ ) : (
+ <>
+
+
+
+
+ {logCustomRange && (
+
+ setLogCustomFrom(e.target.value)}
+ />
+
+ 至
+
+ setLogCustomTo(e.target.value)}
+ />
+
+ )}
+
+
+
+
+
+ {logsLoading ? (
+
+
+
+
+
+
+ {zh
+ ? "正在连接 Worker 日志"
+ : "Connecting to worker logs"}
+
+
+ {zh
+ ? "正在汇总各实例的最新输出…"
+ : "Collecting the latest output from each instance…"}
+
+
+
- ))}
+ ) : (
+
+ {filteredLogEntries.length > 0 ? (
+ <>
+
+ {zh ? "角色" : "Role"}
+ Worker
+ {zh ? "日志内容" : "Log message"}
+ {zh ? "时间" : "Time"}
+
+ {filteredLogEntries.map((entry) => (
+
+ {entry.role}
+
+ {entry.worker}
+
+ {entry.message}
+
+ {entry.timestamp
+ ? new Date(entry.timestamp).toLocaleString(
+ zh ? "zh-CN" : "en-US",
+ {
+ year: "numeric",
+ month: "2-digit",
+ day: "2-digit",
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ hour12: false,
+ },
+ )
+ : ""}
+
+
+ ))}
+ {backendLogs.length > 0 && (
+
+
+
+ {zh
+ ? `第 ${logsPageIndex + 1} 页`
+ : `Page ${logsPageIndex + 1}`}
+
+
+
+ )}
+ >
+ ) : (
+
+ {zh ? "未找到匹配的日志。" : "No matching logs found."}
+
+ )}
+
+ )}
+ >
+ )}
+
+ )}
+
+ {activeTab === "metrics" && (
+
+
+
+
+ {zh ? "任务监控" : "Job metrics"}
+
+
+ {zh
+ ? "Worker 资源与具身通道"
+ : "Worker resources and live channel"}
+
-
-
- ({
- key: item.key,
- value: item.value,
- }))
- : []
- }
- empty={
- zh ? "未配置环境变量" : "No environment variables configured"
- }
+
+ )}
+
+
+ );
+}
+
+function JobPublicOverview({
+ job,
+ copy: c,
+ onBack,
+ runningWorkerCount,
+ totalWorkers,
+ displayPhase,
+ tensorBoardProxy,
+ onClone,
+ lifecycleActions,
+ jobPullProgress = [],
+ jobEvents = [],
+ jobFailedMessage,
+}: {
+ job: Job;
+ copy: CopyType;
+ onBack: () => void;
+ runningWorkerCount: number;
+ totalWorkers: number;
+ displayPhase: JobDisplayPhase;
+ tensorBoardProxy?: string;
+ onClone?: () => void;
+ lifecycleActions: JobLifecycleActions;
+ jobPullProgress?: PullProgressEntry[];
+ jobEvents?: NodeEventEntry[];
+ jobFailedMessage?: string;
+}) {
+ const zh = c.nav.overview === "总览";
+ const baseConfigRows = [
+ {
+ label: zh ? "Worker 数量" : "Worker count",
+ value: `${runningWorkerCount} / ${totalWorkers}`,
+ },
+ {
+ label: zh ? "创建时间" : "Created",
+ value: formatTaskTime(job.startedAt),
+ },
+ {
+ label: "Header Worker",
+ value: job.headerRole || "—",
+ },
+ {
+ label: zh ? "网络域" : "Network domain",
+ value: job.domain || (zh ? "未配置" : "Not configured"),
+ },
+ {
+ label: "TensorBoard",
+ value: job.tensorBoardDir || (zh ? "未配置" : "Not configured"),
+ },
+ {
+ label: zh ? "SSH 公钥" : "SSH Public Key",
+ value: job.sshPublicKey
+ ? `${job.sshPublicKey.slice(0, 32)}...`
+ : zh
+ ? "未配置"
+ : "Not configured",
+ fullValue: job.sshPublicKey,
+ },
+ ];
+ return (
+
+
+
+
+ {zh ? "公共配置" : "Shared configuration"}
+
+
+ {zh ? "任务维度查看公共配置" : "Job-level attributes configuration"}
+
+
+
+
+
+
+
+ {zh ? "启动命令" : "Start command"}
+
+
+
+
+
+ {zh ? "公共属性" : "Shared attributes"}
+
+
+ {baseConfigRows.map((row) => (
+
+ {row.label}
+ {row.value}
+
+ ))}
+
+
@@ -2742,7 +3138,6 @@ function RoleRuntimeConfig({
>;
}) {
const zh = c.nav.overview === "总览";
- const [expanded, setExpanded] = useState(false);
const resource = job.resources.find((item) => item.role === selectedRole);
const taskName = resource ? taskResourceName(job.name, resource.role) : "";
const taskStatus = job.taskStatuses.find(
@@ -2793,21 +3188,17 @@ function RoleRuntimeConfig({
.filter(Boolean)
.join(" / ") || (zh ? "未申请设备" : "No device requested")
: "";
- const nodeSelectors = resource?.nodeSelector
- ? resource.nodeSelector.split(",").map((selector) => {
- const [key, ...value] = selector.split("=");
- return { key: key.trim(), value: value.join("=").trim() };
- })
- : [];
return (
-
+
- {zh ? "Worker 角色" : "Worker roles"}
+
+ {zh ? "角色配置" : "Roles configuration"}
+
{zh
- ? "按角色查看实例与运行配置"
+ ? "角色维度查看运行配置"
: "Instances and configuration by role"}
@@ -2815,23 +3206,11 @@ function RoleRuntimeConfig({
className="role-runtime-tabs"
aria-label={zh ? "选择角色" : "Select role"}
>
-
{roles.map((role) => (
-
) : (
@@ -2888,7 +3250,7 @@ function RoleRuntimeConfig({
: `Showing workers from all ${roles.length} roles`}
)}
- {resource && expanded && (
+ {resource && (
@@ -2901,14 +3263,12 @@ function RoleRuntimeConfig({
/>
- {zh ? "节点选择" : "Node selectors"}
- {nodeSelectors.length ? (
+ {zh ? "节点选择" : "Node selection"}
+ {resourceNodes.length ? (
- {nodeSelectors.map((selector, index) => (
-
- {selector.key}
- =
- {selector.value || "—"}
+ {resourceNodes.map((node, index) => (
+
+ {node}
))}
@@ -3290,7 +3650,7 @@ function formatBytes(bytes: number): string {
// descendant). The icon's viewport position is measured on hover/focus and
// the tooltip is placed above the icon, or below if there isn't enough room
// above (e.g. when the icon sits in the first row of the Jobs list table).
-function PullProgressInfo({
+export function PullProgressInfo({
progress,
events = [],
zh,
@@ -3305,6 +3665,7 @@ function PullProgressInfo({
}) {
const wrapperRef = useRef(null);
const tooltipRef = useRef(null);
+ const closeTimerRef = useRef(null);
const [open, setOpen] = useState(false);
const [pos, setPos] = useState<{
top: number;
@@ -3358,14 +3719,36 @@ function PullProgressInfo({
});
};
+ const cancelClose = () => {
+ if (closeTimerRef.current !== null) {
+ window.clearTimeout(closeTimerRef.current);
+ closeTimerRef.current = null;
+ }
+ };
const show = () => {
+ cancelClose();
setPos(null);
setOpen(true);
};
const clear = () => {
+ cancelClose();
setOpen(false);
setPos(null);
};
+ const handleMouseLeave = (event: React.MouseEvent) => {
+ const nextTarget = event.relatedTarget;
+ if (
+ nextTarget instanceof Node &&
+ (wrapperRef.current?.contains(nextTarget) ||
+ tooltipRef.current?.contains(nextTarget))
+ ) {
+ return;
+ }
+ cancelClose();
+ closeTimerRef.current = window.setTimeout(clear, 250);
+ };
+
+ useEffect(() => () => cancelClose(), []);
useLayoutEffect(() => {
if (!open) return;
@@ -3404,7 +3787,7 @@ function PullProgressInfo({
tabIndex={0}
ref={wrapperRef}
onMouseEnter={show}
- onMouseLeave={clear}
+ onMouseLeave={handleMouseLeave}
onFocus={show}
onBlur={clear}
>
@@ -3417,6 +3800,8 @@ function PullProgressInfo({
className={`status-info-tooltip status-info-tooltip-open${pos && !pos.above ? " status-info-tooltip-below" : ""}`}
style={tooltipStyle}
role="status"
+ onMouseEnter={show}
+ onMouseLeave={handleMouseLeave}
>
(null);
useEffect(() => {
fetch("/api/v1/system-config")
.then((r) => (r.ok ? r.json() : null))
.then((d) => {
- if (d) setSSHConfig(d);
+ if (d) setSSHConfig(d.ssh);
})
.catch(() => {});
}, []);
- const sshJump = sshConfig?.sshJumpHost
- ? `${sshConfig.sshJumpHost}${sshConfig.sshJumpPort ? ":" + sshConfig.sshJumpPort : ""}`
+ const sshJump = sshConfig?.jumpHost
+ ? `${sshConfig.jumpHost}${sshConfig.jumpPort ? ":" + sshConfig.jumpPort : ""}`
+ : "";
+ // head 节点直连 Pod 的 22 端口;非 head 节点通过节点 sshd 的 2222 端口转发到目标 Pod。
+ const sshCommand = sshJump
+ ? isHeader
+ ? `ssh -J ${sshJump} root@${worker.name}`
+ : `ssh -J ${sshJump} root@${worker.name} -p 2222`
: "";
- const sshCommand = sshJump ? `ssh -J ${sshJump} root@${worker.name}` : "";
const handleCopy = async () => {
if (!(await copyText(sshCommand))) return;
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
const pod = pods[0];
+ const domainIP =
+ domainIPMap[`${pod?.namespace}/${pod?.podNamespace}/${pod?.podName}`] ??
+ "—";
return (
<>
@@ -3615,6 +4007,18 @@ function WorkerTableRow({
{pod?.ip || "—"}
|
+
+ {domainIP}
+ |
+
+
+ {worker.gpu && worker.gpu !== "0"
+ ? `${worker.gpu} GPU`
+ : zh
+ ? "未申请"
+ : "None"}
+
+ |
{createdAt}
|
@@ -3648,7 +4052,7 @@ function WorkerTableRow({
)}
-
+ |
-
-
-
|
- {expanded && (
-
-
-
-
-
-
- {zh ? "Worker 详情" : "Worker details"}
-
- {worker.name}
-
- {sshCommand && (
-
-
- {sshCommand}
-
-
- )}
-
-
-
- {zh ? "集群" : "Cluster"}
-
-
-
- {zh ? "角色" : "Role"}
- {worker.role}
-
-
- {zh ? "节点" : "Node"}
-
-
-
- {zh ? "申请 GPU" : "GPU request"}
-
- {worker.gpu && worker.gpu !== "0"
- ? worker.gpu
- : zh
- ? "未申请"
- : "Not requested"}
-
-
-
- {pods.length > 0 ? (
-
-
-
-
- | {zh ? "实例名称" : "Worker name"} |
- {zh ? "节点" : "Node"} |
- {zh ? "实例 IP" : "Worker IP"} |
- {zh ? "网络域" : "Domain"} |
- {zh ? "状态" : "Status"} |
-
-
-
- {pods.map((pod) => {
- const domainIP =
- domainIPMap[
- `${pod.namespace}/${pod.podNamespace}/${pod.podName}`
- ] ?? "";
- const podPhase = (pod.phase || "Pending") as Phase;
- // Mirror the worker-row tooltip aggregation so the pod
- // subtable surfaces the same failure reason / image-pull
- // progress / node events as the top-right status badge.
- const podPullProgress =
- podPhase === "Pending" && pod.node
- ? (nodePullProgressMap[pod.node] ?? [])
- : [];
- const podEvents =
- podPhase === "Pending"
- ? (podEventsMap[pod.name] ?? []).length > 0
- ? (podEventsMap[pod.name] ?? [])
- : pod.node &&
- (nodeEventsMap[pod.node] ?? []).length > 0
- ? (nodeEventsMap[pod.node] ?? [])
- : (taskEventsMap[
- pod.taskName?.toLowerCase() ?? ""
- ] ?? [])
- : [];
- return (
-
-
- {pod.podName}
- |
-
-
- |
- {pod.ip || "—"} |
-
- {pod.domain ? (
- <>
-
- {pod.domain}
- {domainIP && (
-
- {domainIP}
-
- )}
- >
- ) : (
- "—"
- )}
- |
-
-
-
- {podPhase !== "Running" &&
- (podPhase === "Pending" ||
- podPhase === "Failed" ||
- podPullProgress.length > 0 ||
- podEvents.length > 0) && (
-
- )}
-
- |
-
- );
- })}
-
-
-
- ) : (
-
- {zh ? "暂无 Pod 详情,等待任务同步。" : "No pod details yet."}
-
- )}
-
- |
-
- )}
>
);
}
diff --git a/apps/rlark-ui/src/pages/Overview.tsx b/apps/rlark-ui/src/pages/Overview.tsx
index 7d32597..de5a661 100644
--- a/apps/rlark-ui/src/pages/Overview.tsx
+++ b/apps/rlark-ui/src/pages/Overview.tsx
@@ -42,6 +42,7 @@ export function Overview({
const [realClusters, setRealClusters] = useState([]);
const [realNodes, setRealNodes] = useState([]);
const [realJobs, setRealJobs] = useState([]);
+ const [refreshing, setRefreshing] = useState(false);
const isZh = c.nav.overview === "总览";
const { refresh } = useAutoRefresh(async () => {
@@ -62,6 +63,16 @@ export function Overview({
setRealJobs(jobItems.map(crdToJob));
}, 15000);
+ const handleRefresh = async () => {
+ if (refreshing) return;
+ setRefreshing(true);
+ try {
+ await refresh();
+ } finally {
+ setRefreshing(false);
+ }
+ };
+
const displayNodes = realNodes;
const embodiedClusters = realClusters.filter((x) => x.type === "Embodied");
const runningJobs = realJobs.filter((x) => x.phase === "Running").length;
@@ -306,8 +317,18 @@ export function Overview({
{c.overview.recent}
{c.common.production}
-
diff --git a/apps/rlark-ui/src/pages/SSHKeys.tsx b/apps/rlark-ui/src/pages/SSHKeys.tsx
index c247f61..99e0dc8 100644
--- a/apps/rlark-ui/src/pages/SSHKeys.tsx
+++ b/apps/rlark-ui/src/pages/SSHKeys.tsx
@@ -162,8 +162,21 @@ export function SSHKeysPage({ copy: c }: { copy: Copy }) {
className="secondary-button"
onClick={fetchKeys}
title={zh ? "刷新" : "Refresh"}
+ aria-label={zh ? "刷新" : "Refresh"}
+ aria-busy={loading}
+ disabled={loading}
>
-
+
+ {loading
+ ? zh
+ ? "刷新中..."
+ : "Refreshing..."
+ : zh
+ ? "刷新"
+ : "Refresh"}
-
diff --git a/apps/rlark-ui/src/pages/SystemConfig.tsx b/apps/rlark-ui/src/pages/SystemConfig.tsx
index 4e541f0..ce141a4 100644
--- a/apps/rlark-ui/src/pages/SystemConfig.tsx
+++ b/apps/rlark-ui/src/pages/SystemConfig.tsx
@@ -1,10 +1,31 @@
import { useEffect, useState } from "react";
-import { Settings, Save, Check, Copy as CopyIcon } from "lucide-react";
+import {
+ Settings,
+ Save,
+ Check,
+ Copy as CopyIcon,
+ RefreshCw,
+} from "lucide-react";
import type { Copy } from "../i18n";
+interface LogBackendConfig {
+ endpoint: string;
+ project: string;
+ logstore: string;
+ accessKeyId: string;
+ accessKeySecret: string;
+}
+
+interface LogConfig {
+ backend: string;
+ config: LogBackendConfig;
+}
+
interface SystemConfig {
sshJumpHost: string;
sshJumpPort: string;
+ log: LogConfig;
+ isAccessKeySecretSet: boolean; // 标记 accessKeySecret 是否已设置(用于区分掩码和用户输入)
}
export function SystemConfigPage({ copy: c }: { copy: Copy }) {
@@ -12,12 +33,25 @@ export function SystemConfigPage({ copy: c }: { copy: Copy }) {
const [config, setConfig] = useState ({
sshJumpHost: "",
sshJumpPort: "",
+ log: {
+ backend: "sls",
+ config: {
+ endpoint: "",
+ project: "",
+ logstore: "",
+ accessKeyId: "",
+ accessKeySecret: "",
+ },
+ },
+ isAccessKeySecretSet: false,
});
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [error, setError] = useState("");
const [saved, setSaved] = useState(false);
const [copied, setCopied] = useState(false);
+ // 记录日志配置是否被修改过,用于决定是否在保存时发送 log 字段
+ const [isLogConfigDirty, setIsLogConfigDirty] = useState(false);
useEffect(() => {
fetchConfig();
@@ -30,10 +64,25 @@ export function SystemConfigPage({ copy: c }: { copy: Copy }) {
const resp = await fetch("/api/v1/system-config");
if (!resp.ok) throw new Error(await resp.text());
const data = await resp.json();
+ const secret = data.log?.config?.accessKeySecret || "";
setConfig({
- sshJumpHost: data.sshJumpHost || "",
- sshJumpPort: data.sshJumpPort || "",
+ sshJumpHost: data.ssh?.jumpHost || data.sshJumpHost || "",
+ sshJumpPort: data.ssh?.jumpPort || data.sshJumpPort || "",
+ log: {
+ backend: data.log?.backend || "sls",
+ config: {
+ endpoint: data.log?.config?.endpoint || "",
+ project: data.log?.config?.project || "",
+ logstore: data.log?.config?.logstore || "",
+ accessKeyId: data.log?.config?.accessKeyId || "",
+ // 后端返回掩码时展示掩码;用于让用户知道已设置
+ accessKeySecret: secret,
+ },
+ },
+ isAccessKeySecretSet: Boolean(secret),
});
+ // 加载完成后,重置脏标记
+ setIsLogConfigDirty(false);
} catch (e) {
setError(String(e));
} finally {
@@ -46,10 +95,22 @@ export function SystemConfigPage({ copy: c }: { copy: Copy }) {
setError("");
setSaved(false);
try {
+ // 构建请求体:如果日志配置没有被修改,则不包含 log 字段
+ const requestBody: any = {
+ ssh: {
+ jumpHost: config.sshJumpHost,
+ jumpPort: config.sshJumpPort,
+ },
+ };
+
+ if (isLogConfigDirty) {
+ requestBody.log = config.log;
+ }
+
const resp = await fetch("/api/v1/system-config", {
method: "PUT",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify(config),
+ body: JSON.stringify(requestBody),
});
if (!resp.ok) throw new Error(await resp.text());
setSaved(true);
@@ -92,8 +153,19 @@ export function SystemConfigPage({ copy: c }: { copy: Copy }) {
onClick={fetchConfig}
title={zh ? "刷新" : "Refresh"}
disabled={loading}
+ aria-busy={loading}
>
- {loading ? "…" : zh ? "刷新" : "Refresh"}
+
+ {loading
+ ? zh
+ ? "刷新中..."
+ : "Refreshing..."
+ : zh
+ ? "刷新"
+ : "Refresh"}
{sshCommand && (
-
+
{zh ? "预览 SSH 命令" : "SSH Command Preview"}
@@ -196,6 +268,160 @@ export function SystemConfigPage({ copy: c }: { copy: Copy }) {
)}
+
+
+
+
+ {zh ? "日志后端配置" : "Log Backend Configuration"}
+
+ {zh
+ ? "配置日志后端的连接参数,用于查询历史日志"
+ : "Configure log backend connection parameters for querying historical logs"}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/apps/rlark-ui/src/pages/Workflows.tsx b/apps/rlark-ui/src/pages/Workflows.tsx
index 2dcbc33..61261e3 100644
--- a/apps/rlark-ui/src/pages/Workflows.tsx
+++ b/apps/rlark-ui/src/pages/Workflows.tsx
@@ -20,6 +20,7 @@ import { useAutoRefresh } from "../hooks";
import { crdToWorkflow } from "../utils/crd";
import { hasCycle, makeDefaultRoleResources } from "../utils/dag";
import {
+ automaticNetworkDomain,
computePvcStorageMap,
generateJobCRD,
ROLE_TEMPLATES,
@@ -484,7 +485,7 @@ export function WorkflowsPage({
onChange={setQuery}
count={filteredItems.length}
copy={c}
- onRefresh={() => fetchWorkflows()}
+ onRefresh={() => fetchWorkflows(false)}
/>
{error && (
@@ -991,7 +992,7 @@ export function CreateWorkflowModal({
roles: job.roles,
roleResources: job.roleResources,
runScript: job.runScript,
- domain: job.domain,
+ domain: automaticNetworkDomain(domains),
});
return { domain: crd.spec.domain, tasks: crd.spec.tasks };
};
@@ -1020,8 +1021,29 @@ export function CreateWorkflowModal({
const yaml = toYaml(crd);
const handleSubmit = async () => {
- setSubmitting(true);
setError("");
+ for (const job of jobs) {
+ const normalizedRoles = job.roles.map((role) =>
+ role.trim().toLowerCase(),
+ );
+ if (normalizedRoles.some((role) => !role)) {
+ setError(zh ? "角色名称不能为空。" : "Role names cannot be empty.");
+ return;
+ }
+ if (job.roles.some((role) => role.trim().length > 50)) {
+ setError(
+ zh
+ ? "角色名称不能超过 50 个字符。"
+ : "Role names cannot exceed 50 characters.",
+ );
+ return;
+ }
+ if (new Set(normalizedRoles).size !== normalizedRoles.length) {
+ setError(zh ? "角色名称不能重复。" : "Role names must be unique.");
+ return;
+ }
+ }
+ setSubmitting(true);
try {
const resp = await fetch("/api/v1/rlinf.io/v1alpha1/workflows", {
method: "POST",
@@ -1140,8 +1162,8 @@ export function CreateWorkflowModal({
const updateRRMount = (
role: string,
index: number,
- field: "objectStorage" | "mountPath" | "type" | "hostPath",
- value: string,
+ field: "objectStorage" | "mountPath" | "type" | "hostPath" | "pvcSizeGb",
+ value: string | number,
) => {
if (!activeJob) return;
const rr = activeJob.roleResources[role];
@@ -1169,6 +1191,7 @@ export function CreateWorkflowModal({
objectStorage: "",
mountPath: "",
hostPath: "",
+ pvcSizeGb: 10,
},
];
const pvcStorageMap = computePvcStorageMap(role, newMounts, activeJob.name);
@@ -1235,6 +1258,7 @@ export function CreateWorkflowModal({
objectStorage: "",
mountPath: "/mnt/dataset",
hostPath: "/host/dataset",
+ pvcSizeGb: 10,
},
],
};
@@ -1255,7 +1279,14 @@ export function CreateWorkflowModal({
const renameRole = (old: string, newName: string) => {
if (!activeJob) return;
- if (!newName.trim()) return;
+ newName = newName.trim();
+ if (!newName || newName.length > 50 || old === newName) return;
+ if (
+ activeJob.roles.some(
+ (role) => role !== old && role.toLowerCase() === newName.toLowerCase(),
+ )
+ )
+ return;
const roles = activeJob.roles.map((r) => (r === old ? newName : r));
const rr: Record = {};
for (const [k, v] of Object.entries(activeJob.roleResources)) {
@@ -1936,6 +1967,33 @@ export function CreateWorkflowModal({
placeholder="/mnt/data"
/>
+ {mount.type === "storage" && (
+
+ )}
removeRRMount(role, index)}
@@ -1975,27 +2033,17 @@ export function CreateWorkflowModal({
-
- {zh
- ? "跨集群网络域 (可选)"
- : "Cross-cluster Network Domain (optional)"}
-
+ {zh ? "跨集群网络" : "Cross-cluster Network"}
+
+
+ {automaticNetworkDomain(domains)
+ ? zh
+ ? `系统已配置网络域,任务将默认启用跨集群网络(${automaticNetworkDomain(domains)})。`
+ : `A network domain is configured. Cross-cluster networking will be enabled automatically (${automaticNetworkDomain(domains)}).`
+ : zh
+ ? "系统尚未配置网络域,任务不会启用跨集群网络。"
+ : "No network domain is configured. Cross-cluster networking will not be enabled."}
-
diff --git a/apps/rlark-ui/src/styles.css b/apps/rlark-ui/src/styles.css
index 568e976..81da577 100644
--- a/apps/rlark-ui/src/styles.css
+++ b/apps/rlark-ui/src/styles.css
@@ -1299,9 +1299,7 @@ button {
.status-info-tooltip.status-info-tooltip-open {
opacity: 1;
- /* The tooltip is read-only. Let the icon retain hover ownership so a
- tooltip overlapping the icon cannot cause an enter/leave flicker loop. */
- pointer-events: none;
+ pointer-events: auto;
}
.status-info-tooltip .pull-entry {
@@ -2766,6 +2764,61 @@ button {
box-shadow: 0 2px 8px rgba(42, 52, 73, 0.09);
}
+/* Job 详情页:Tab 栏与下方内容面板融合为一个整体卡片 */
+.job-tab-panel {
+ border: 1px solid var(--line);
+ border-radius: 18px;
+ background: #fff;
+ box-shadow: var(--shadow-soft);
+ overflow: hidden;
+}
+
+.job-tab-panel > .sub-tabs {
+ border-radius: 0;
+ border-bottom: 1px solid var(--line);
+ margin: 0;
+ padding: 6px 12px;
+}
+
+/* 融合卡片内的子面板去掉自身边框/圆角/阴影,由外层统一承载 */
+.job-tab-panel .worker-primary-panel,
+.job-tab-panel .job-observe-panel,
+.job-tab-panel .job-detail-summary-card,
+.job-detail-page .job-tab-panel .role-runtime-config.job-detail-summary-card {
+ border: 0;
+ border-radius: 0;
+ box-shadow: none;
+ margin: 0;
+}
+
+/* 融合卡片内的三个区块之间用浅色背景带分隔,替代生硬的分界线 */
+.job-tab-panel .role-runtime-config.job-detail-summary-card,
+.job-tab-panel > .job-detail-summary-card {
+ padding-bottom: 20px;
+ margin-bottom: 4px;
+}
+
+.job-tab-panel .worker-primary-panel {
+ padding-top: 4px;
+}
+
+/* 每个区块之间用浅色背景条做视觉缓冲 */
+.job-tab-panel > section + section,
+.job-tab-panel > .worker-primary-panel {
+ position: relative;
+}
+
+.job-tab-panel > section + section::before,
+.job-tab-panel > .worker-primary-panel::before {
+ content: "";
+ display: block;
+ height: 6px;
+ background: #f7f8fb;
+ margin: 0 -16px;
+ border-top: 1px solid #eef0f4;
+ border-bottom: 1px solid #eef0f4;
+}
+
.dag-canvas {
height: 365px;
margin-top: 18px;
@@ -4350,6 +4403,8 @@ tbody tr:hover {
background: #fff;
box-shadow: var(--shadow-soft);
overflow: hidden;
+ position: relative;
+ z-index: 1;
}
.worker-panel-head,
@@ -4362,11 +4417,21 @@ tbody tr:hover {
border-bottom: 1px solid var(--line);
}
+.worker-panel-head {
+ flex-wrap: wrap;
+}
+
+.worker-list-role-tabs {
+ width: auto;
+ flex: 0 1 auto;
+}
+
.worker-panel-head h3,
.observe-panel-head h3 {
margin: 4px 0 0;
color: var(--text);
- font-size: 18px;
+ font-size: 14px;
+ font-weight: 600;
line-height: 1.3;
letter-spacing: 0;
}
@@ -5199,7 +5264,7 @@ tbody tr:hover {
.public-runtime-topology {
display: grid;
- grid-template-columns: minmax(0, 1.25fr) minmax(260px, 0.75fr);
+ grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
margin-top: 0;
}
@@ -5216,6 +5281,13 @@ tbody tr:hover {
.public-command-card,
.public-basic-config-card {
padding: 10px;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+.public-command-card .command-code-block {
+ flex: 1;
}
.public-config-title {
@@ -5233,6 +5305,7 @@ tbody tr:hover {
.command-code-block {
position: relative;
min-height: 66px;
+ height: 100%;
border: 1px solid #1f2a3d;
border-radius: 10px;
background: #0f172a;
@@ -5486,6 +5559,16 @@ tbody tr:hover {
padding: 14px 18px;
}
+.job-detail-page .role-runtime-config.job-detail-summary-card {
+ border: 1px solid var(--line);
+ border-radius: 18px;
+ background: #fff;
+ box-shadow: var(--shadow-soft);
+ padding: 16px;
+ position: relative;
+ z-index: 1;
+}
+
.role-runtime-heading {
display: flex;
flex-direction: column;
@@ -5501,7 +5584,8 @@ tbody tr:hover {
display: block;
margin-top: 3px;
color: var(--text);
- font-size: 12px;
+ font-size: 14px;
+ font-weight: 600;
}
.role-runtime-tabs {
@@ -5649,8 +5733,6 @@ tbody tr:hover {
grid-template-columns: minmax(0, 1fr);
gap: 8px;
margin-top: 10px;
- padding-top: 10px;
- border-top: 1px dashed var(--line);
align-items: stretch;
}
@@ -6102,7 +6184,8 @@ tbody tr:hover {
}
.worker-filter-bar select,
-.log-console-toolbar select {
+.log-console-toolbar select,
+.worker-role-filter-select {
height: 34px;
border: 1px solid var(--line);
border-radius: 9px;
@@ -6267,6 +6350,26 @@ tbody tr:hover {
justify-content: flex-end;
}
+.worker-sticky-header-col,
+.worker-sticky-actions {
+ position: sticky;
+ right: 0;
+ background: #fff;
+ z-index: 10;
+ box-shadow: -6px 0 8px -6px rgba(15, 23, 42, 0.1);
+ text-align: right;
+}
+
+.worker-sticky-header-col {
+ padding-right: 24px;
+}
+
+.theme-dark .worker-sticky-header-col,
+.theme-dark .worker-sticky-actions {
+ background: #151d2b;
+ box-shadow: -6px 0 8px -6px rgba(0, 0, 0, 0.5);
+}
+
.action-tooltip {
position: relative;
display: inline-flex;
@@ -6371,6 +6474,16 @@ tbody tr:hover {
overflow: hidden;
}
+.role-runtime-command-card {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+.role-runtime-command-card .command-code-block {
+ flex: 1;
+}
+
.role-runtime-command-card > span,
.role-runtime-selector-card > span {
display: grid;
@@ -6513,6 +6626,13 @@ tbody tr:hover {
text-align: center;
}
+.worker-console-table .empty-cell {
+ height: 120px;
+ color: var(--muted);
+ text-align: center;
+ font-size: 13px;
+}
+
.role-runtime-config-tables {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -6636,23 +6756,27 @@ tbody tr:hover {
.log-console-toolbar {
display: flex;
- align-items: flex-end;
- gap: 9px;
+ align-items: center;
+ gap: 12px;
flex-wrap: wrap;
margin: 16px 18px;
+ padding: 12px;
+ background: var(--bg-secondary, #f8f9fa);
+ border-radius: 8px;
+ border: 1px solid var(--line);
}
.log-search-field {
display: flex !important;
align-items: center;
gap: 7px;
- min-width: 220px;
- height: 33px;
- margin-top: 15px;
+ flex: 1;
+ min-width: 300px;
+ height: 36px;
border: 1px solid var(--line);
- border-radius: 9px;
+ border-radius: 6px;
background: #fff;
- padding: 0 9px;
+ padding: 0 12px;
color: #8793a6;
}
@@ -6663,22 +6787,38 @@ tbody tr:hover {
outline: 0;
background: transparent;
color: var(--text);
- font-size: 12px;
+ font-size: 13px;
+}
+
+.log-custom-datetime {
+ height: 36px;
+ border: 1px solid var(--line);
+ border-radius: 6px;
+ background: #fff;
+ padding: 0 12px;
+ font-size: 13px;
+ color: var(--text);
+ font-family: inherit;
+}
+
+.log-custom-datetime:focus {
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.1);
}
.stream-toggle {
display: inline-flex;
align-items: center;
gap: 6px;
- height: 33px;
- margin-top: 15px;
+ height: 36px;
border: 1px solid var(--line);
- border-radius: 9px;
+ border-radius: 6px;
background: #fff;
color: #788598;
- padding: 0 10px;
- font-size: 11px;
- font-weight: 800;
+ padding: 0 12px;
+ font-size: 12px;
+ font-weight: 600;
}
.stream-toggle i {
@@ -6700,10 +6840,9 @@ tbody tr:hover {
}
.log-export-button {
- height: 33px;
- margin-top: 15px;
- padding: 0 10px;
- font-size: 11px;
+ height: 36px;
+ padding: 0 12px;
+ font-size: 12px;
}
.log-list {
@@ -6717,7 +6856,7 @@ tbody tr:hover {
.log-list-head,
.log-list-row {
display: grid;
- grid-template-columns: 100px minmax(150px, 0.9fr) minmax(260px, 2.4fr);
+ grid-template-columns: 100px minmax(150px, 0.9fr) minmax(260px, 2.4fr) 140px;
gap: 12px;
}
@@ -6758,6 +6897,49 @@ tbody tr:hover {
line-height: 1.45;
}
+.log-timestamp {
+ color: var(--muted);
+ font-family: "JetBrains Mono", monospace;
+ font-size: 10px;
+ font-weight: 500;
+ text-align: right;
+}
+
+.log-load-more {
+ display: flex;
+ justify-content: center;
+ padding: 10px 12px;
+ border-bottom: 0;
+ background: #fafbfc;
+}
+
+.log-load-more .secondary-button {
+ height: 32px;
+ padding: 0 14px;
+ font-size: 12px;
+}
+
+.log-pagination {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ padding: 10px 12px;
+ background: #fafbfc;
+}
+
+.log-pagination .secondary-button {
+ height: 32px;
+ padding: 0 14px;
+ font-size: 12px;
+}
+
+.log-page-indicator {
+ font-size: 12px;
+ color: var(--muted);
+ font-family: "JetBrains Mono", monospace;
+}
+
.metrics-dashboard {
margin: 16px 18px 18px;
}
@@ -7743,6 +7925,54 @@ tbody tr:hover {
font-size: 11px;
}
+.ssh-key-select-list {
+ max-height: 216px;
+ overflow-y: auto;
+ border: 1px solid var(--line);
+ border-radius: 12px;
+ background: #fff;
+}
+
+.modal-body .ssh-key-select-option {
+ display: flex;
+ grid-template-columns: none;
+ align-items: center;
+ gap: 10px;
+ min-height: 42px;
+ margin: 0;
+ padding: 9px 12px;
+ cursor: pointer;
+ color: #4f5b6b;
+}
+
+.modal-body .ssh-key-select-option input[type="checkbox"] {
+ width: 17px;
+ height: 17px;
+ padding: 0;
+ flex: 0 0 17px;
+}
+
+.ssh-key-select-option + .ssh-key-select-option {
+ border-top: 1px solid var(--line);
+}
+
+.ssh-key-select-option.selected {
+ background: #f3eefe;
+ color: var(--blue);
+}
+
+.ssh-key-select-option input {
+ flex: 0 0 auto;
+ margin: 0;
+}
+
+.ssh-key-select-option span {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
.role-template.selectable {
display: flex;
flex-wrap: wrap;
@@ -8905,6 +9135,80 @@ tbody tr:hover {
font-weight: 650;
}
+.image-picker {
+ position: relative;
+}
+
+.image-picker-list {
+ position: absolute;
+ z-index: 20;
+ top: calc(100% + 4px);
+ left: 0;
+ right: 0;
+ max-height: 224px;
+ overflow-y: auto;
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: #fff;
+ box-shadow: 0 10px 24px rgba(31, 45, 65, 0.14);
+}
+
+.image-picker-option {
+ display: flex;
+ width: 100%;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 3px;
+ padding: 9px 12px;
+ border: 0;
+ border-bottom: 1px solid var(--line);
+ background: transparent;
+ cursor: pointer;
+ text-align: left;
+}
+
+.image-picker-option:last-child {
+ border-bottom: 0;
+}
+
+.image-picker-option:hover,
+.image-picker-option:focus-visible {
+ outline: none;
+ background: #f4f1ff;
+}
+
+.image-picker-option strong {
+ max-width: 100%;
+ overflow: hidden;
+ color: #202733;
+ font-size: 12px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.image-picker-option small {
+ color: #7f8998;
+ font-size: 11px;
+}
+
+.theme-dark .image-picker-list {
+ border-color: #485262;
+ background: #252a33;
+}
+
+.theme-dark .image-picker-option {
+ border-color: #485262;
+}
+
+.theme-dark .image-picker-option:hover,
+.theme-dark .image-picker-option:focus-visible {
+ background: #342b54;
+}
+
+.theme-dark .image-picker-option strong {
+ color: #e7edf7;
+}
+
.form-section input.input-invalid {
border-color: #d64d68;
box-shadow: 0 0 0 3px rgba(214, 77, 104, 0.1);
@@ -9174,12 +9478,18 @@ tbody tr:hover {
.mount-row {
display: grid;
- grid-template-columns: auto 1fr 1fr 32px;
+ grid-template-columns:
+ auto minmax(0, 1fr) minmax(0, 1fr) minmax(140px, 0.45fr)
+ 32px;
gap: 8px;
margin-top: 8px;
align-items: center;
}
+.mount-size-field {
+ max-width: 240px;
+}
+
.mount-field-box {
min-width: 0;
display: grid;
@@ -9310,6 +9620,7 @@ tbody tr:hover {
.theme-dark .subpage-tabs button,
.theme-dark .job-config-summary > div,
.theme-dark .form-section,
+.theme-dark .ssh-key-select-list,
.theme-dark .role-template.selectable button,
.theme-dark .env-row input {
background: #151d2b;
@@ -9366,8 +9677,13 @@ tbody tr:hover {
.jobs-table-panel th:first-child,
.jobs-table-panel td:first-child {
- width: 170px;
- min-width: 170px;
+ width: 240px;
+ min-width: 240px;
+}
+
+.job-id-cell-wrap {
+ display: grid;
+ gap: 3px;
}
.jobs-table-panel .job-id-cell {
@@ -9375,9 +9691,43 @@ tbody tr:hover {
min-width: 0;
}
+.jobs-table-panel .job-id-cell strong,
+.jobs-table-panel .job-id-copy small {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.job-id-copy {
+ width: fit-content;
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ color: #7a8494;
+ font-size: 11px;
+}
+
+.job-id-copy:hover {
+ color: var(--blue);
+}
+
+.job-id-copy small {
+ color: inherit;
+ font-size: inherit;
+}
+
+.job-detail-resource-line {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 8px;
+ color: var(--muted);
+ font-size: 13px;
+ line-height: 1.5;
+}
+
.jobs-table-panel .job-id-cell strong {
- overflow-wrap: anywhere;
- word-break: break-word;
line-height: 1.35;
}
diff --git a/apps/rlark-ui/src/types.ts b/apps/rlark-ui/src/types.ts
index 5548dba..ba5f525 100644
--- a/apps/rlark-ui/src/types.ts
+++ b/apps/rlark-ui/src/types.ts
@@ -49,6 +49,7 @@ export interface CRDWorkload {
kind: string;
replicas: number;
pvcStorageMap?: Record ;
+ pvcSizeGbMap?: Record;
template: {
spec: {
containers: Array<{
@@ -190,8 +191,10 @@ export interface RoleResource {
objectStorage: string;
mountPath: string;
hostPath: string;
+ pvcSizeGb: number;
}>;
pvcStorageMap?: Record;
+ pvcSizeGbMap?: Record;
}
export interface WorkflowJobDef {
diff --git a/apps/rlark-ui/src/utils/crd.ts b/apps/rlark-ui/src/utils/crd.ts
index e4e41ba..c6937f6 100644
--- a/apps/rlark-ui/src/utils/crd.ts
+++ b/apps/rlark-ui/src/utils/crd.ts
@@ -11,7 +11,11 @@ export function crdToJob(crd: CRDJob): Job {
(t) => t.phase === "Running",
).length;
const headerTask = tasks.find((t) => t.head) ?? tasks[0];
- const roles = tasks.map((t) => t.name);
+ const taskRoleName = (task: CRDJobTask) =>
+ task.kubernetes?.workload?.template.spec.containers?.[0]?.env?.find(
+ (env) => env.name === "RLARK_TASK_ROLE",
+ )?.value ?? task.name;
+ const roles = tasks.map(taskRoleName);
const roleCount = new Set(tasks.map((task) => task.role).filter(Boolean))
.size;
const displayName =
@@ -49,6 +53,7 @@ export function crdToJob(crd: CRDJob): Job {
objectStorage: storageClass,
mountPath: vm.mountPath,
hostPath: "",
+ pvcSizeGb: t.kubernetes?.workload?.pvcSizeGbMap?.[claimName] ?? 10,
};
}
const hostPath = vol?.hostPath?.path ?? "";
@@ -57,10 +62,11 @@ export function crdToJob(crd: CRDJob): Job {
objectStorage: "",
mountPath: vm.mountPath,
hostPath,
+ pvcSizeGb: 10,
};
});
return {
- role: t.name,
+ role: taskRoleName(t),
cluster: "",
nodeSelector: nsStr,
replicas: t.kubernetes?.workload?.replicas ?? 1,
@@ -95,6 +101,8 @@ export function crdToJob(crd: CRDJob): Job {
objectStorage: storageClass,
mountPath: vm.mountPath,
hostPath: "",
+ pvcSizeGb:
+ tasks[0]?.kubernetes?.workload?.pvcSizeGbMap?.[claimName] ?? 10,
};
}
const hostPath = vol?.hostPath?.path ?? "";
@@ -103,6 +111,7 @@ export function crdToJob(crd: CRDJob): Job {
objectStorage: "",
mountPath: vm.mountPath,
hostPath,
+ pvcSizeGb: 10,
};
});
return {
@@ -134,8 +143,8 @@ export function crdToJob(crd: CRDJob): Job {
tensorBoardDir: headerTask?.tensorBoardDir ?? "",
env,
mounts,
- headerRole: headerTask?.name ?? "",
- headerWorker: headerTask?.name ?? "",
+ headerRole: headerTask ? taskRoleName(headerTask) : "",
+ headerWorker: headerTask ? taskRoleName(headerTask) : "",
sshAddress: "",
stopped: crd.spec.stopped ?? false,
domain: crd.spec.domain ?? "",
diff --git a/apps/rlark-ui/src/utils/dag.ts b/apps/rlark-ui/src/utils/dag.ts
index 862acf8..f7bba7e 100644
--- a/apps/rlark-ui/src/utils/dag.ts
+++ b/apps/rlark-ui/src/utils/dag.ts
@@ -16,6 +16,7 @@ export function makeDefaultRoleResources(
objectStorage: "",
mountPath: "/mnt/dataset",
hostPath: "/host/dataset",
+ pvcSizeGb: 10,
},
];
rr[role] = {
diff --git a/apps/rlark-ui/src/utils/job.ts b/apps/rlark-ui/src/utils/job.ts
index 9004642..16a758d 100644
--- a/apps/rlark-ui/src/utils/job.ts
+++ b/apps/rlark-ui/src/utils/job.ts
@@ -95,6 +95,12 @@ export function selectorToStr(sel: Record): string {
.join(",");
}
+export function generateJobResourceName(): string {
+ const bytes = new Uint8Array(8);
+ crypto.getRandomValues(bytes);
+ return `jo-${Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
+}
+
export function toResourceName(value: string): string {
return Array.from(value.toLowerCase())
.map((char) =>
@@ -107,6 +113,36 @@ export function toResourceName(value: string): string {
.replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, "");
}
+// shortHash 返回输入字符串的短哈希(FNV-1a,6 位十六进制),用于让名字唯一。
+function shortHash(s: string): string {
+ let h = 0x811c9dc5;
+ for (let i = 0; i < s.length; i++) {
+ h ^= s.charCodeAt(i);
+ h = Math.imul(h, 0x01000193);
+ }
+ return (h >>> 0).toString(16).padStart(8, "0").slice(0, 6);
+}
+
+// toVolumeName 把 mount path 转成合法的 k8s volume 名(DNS-1123 label:
+// 小写字母/数字/`-`,最长 63)。非法字符替换为 `-`;如果发生过替换
+// (说明不同 path 可能清洗成同一个名字),追加原 path 的短哈希保证唯一。
+export function toVolumeName(mountPath: string): string {
+ const hadIllegal = /[^a-z0-9/-]/.test(mountPath);
+ let name =
+ mountPath
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, "-")
+ .replace(/^-+|-+$/g, "") || "vol";
+ if (hadIllegal) {
+ name = `${name}-${shortHash(mountPath)}`;
+ }
+ if (name.length > 63) {
+ const suffix = shortHash(mountPath);
+ name = `${name.slice(0, 56).replace(/-+$/g, "")}-${suffix}`;
+ }
+ return name;
+}
+
export function computePvcStorageMap(
role: string,
mounts: Array<{
@@ -114,6 +150,7 @@ export function computePvcStorageMap(
objectStorage: string;
mountPath: string;
hostPath: string;
+ pvcSizeGb: number;
}>,
jobName?: string,
): Record | undefined {
@@ -122,19 +159,51 @@ export function computePvcStorageMap(
if (storageMounts.length === 0) return undefined;
const map: Record = {};
const jobSlug = jobName ? toResourceName(jobName) : "";
+ storageMounts.forEach((m) => {
+ const volName = toVolumeName(m.mountPath);
+ const claimName = jobSlug
+ ? `pvc-${jobSlug}-${roleSlug}-${volName}`
+ : `pvc-${roleSlug}-${volName}`;
+ map[claimName] = m.objectStorage ?? "";
+ });
+ return map;
+}
+
+export function computePvcSizeGbMap(
+ role: string,
+ mounts: Array<{
+ type: "host" | "storage";
+ mountPath: string;
+ pvcSizeGb: number;
+ }>,
+ jobName?: string,
+): Record | undefined {
+ const roleSlug = toResourceName(role);
+ const storageMounts = mounts.filter((m) => m.type === "storage");
+ if (storageMounts.length === 0) return undefined;
+ const map: Record = {};
+ const jobSlug = jobName ? toResourceName(jobName) : "";
storageMounts.forEach((m) => {
const volName =
m.mountPath.replace(/\//g, "-").replace(/^-|-$/g, "") || "vol";
const claimName = jobSlug
? `pvc-${jobSlug}-${roleSlug}-${volName}`
: `pvc-${roleSlug}-${volName}`;
- map[claimName] = m.objectStorage ?? "";
+ map[claimName] = Math.min(200, Math.max(1, m.pvcSizeGb));
});
return map;
}
+export function automaticNetworkDomain(domains: Array<{ name: string }>) {
+ return (
+ [...domains].sort((left, right) => left.name.localeCompare(right.name))[0]
+ ?.name ?? ""
+ );
+}
+
export function generateJobCRD(opts: {
name: string;
+ displayName?: string;
type: JobType;
headerRole: string;
roles: string[];
@@ -164,13 +233,12 @@ export function generateJobCRD(opts: {
const storageMounts = roleMounts.filter((m) => m.type === "storage");
const containerVolumes = hostMounts.map((m) => ({
- name: m.mountPath.replace(/\//g, "-").replace(/^-|-$/g, "") || "vol",
+ name: toVolumeName(m.mountPath),
hostPath: { path: m.hostPath || m.objectStorage },
}));
const storageVolumes = storageMounts.map((m) => {
- const volName =
- m.mountPath.replace(/\//g, "-").replace(/^-|-$/g, "") || "vol";
+ const volName = toVolumeName(m.mountPath);
const claimName = `pvc-${jobSlug}-${taskName}-${volName}`;
return {
name: volName,
@@ -180,11 +248,15 @@ export function generateJobCRD(opts: {
};
});
- const pvcStorageMap =
- res?.pvcStorageMap ?? computePvcStorageMap(role, roleMounts, opts.name);
+ const pvcStorageMap = computePvcStorageMap(
+ taskName,
+ roleMounts,
+ opts.name,
+ );
+ const pvcSizeGbMap = computePvcSizeGbMap(taskName, roleMounts, opts.name);
const allVolumeMounts = roleMounts.map((m) => ({
- name: m.mountPath.replace(/\//g, "-").replace(/^-|-$/g, "") || "vol",
+ name: toVolumeName(m.mountPath),
mountPath: m.mountPath,
}));
@@ -204,6 +276,7 @@ export function generateJobCRD(opts: {
kind: "StatefulSet",
replicas: res ? Number(res.replicas) : 1,
...(pvcStorageMap ? { pvcStorageMap } : {}),
+ ...(pvcSizeGbMap ? { pvcSizeGbMap } : {}),
template: {
spec: {
containers: [
@@ -262,7 +335,12 @@ export function generateJobCRD(opts: {
return {
apiVersion: "rlinf.io/v1alpha1",
kind: "Job",
- metadata: { name: opts.name },
+ metadata: {
+ name: opts.name,
+ ...(opts.displayName
+ ? { annotations: { "rlark.io/display-name": opts.displayName } }
+ : {}),
+ },
spec: {
tasks,
...(opts.domain ? { domain: opts.domain } : {}),
diff --git a/apps/rlark-ui/src/utils/jobPhase.ts b/apps/rlark-ui/src/utils/jobPhase.ts
index 3e725f0..dd1d3ee 100644
--- a/apps/rlark-ui/src/utils/jobPhase.ts
+++ b/apps/rlark-ui/src/utils/jobPhase.ts
@@ -2,25 +2,11 @@ import type { Job, Phase } from "../data";
export type JobDisplayPhase = Phase | "Stopping";
-export function effectiveJobPhase(
- job: Job,
- workerPhases?: string[],
-): JobDisplayPhase {
- const phases = (
- workerPhases ?? job.taskStatuses.map((task) => task.phase)
- ).filter(Boolean);
- if (job.stopped) {
- if (job.phase === "Stopped") return "Stopped";
- return phases.length > 0 && phases.every((phase) => phase === "Stopped")
- ? "Stopped"
- : "Stopping";
+// 任务状态统一以 Job 自身的 phase 为准。
+// 当 job.stopped 已置位但 phase 尚未推进到 Stopped 时,展示过渡状态 Stopping。
+export function effectiveJobPhase(job: Job): JobDisplayPhase {
+ if (job.stopped && job.phase !== "Stopped") {
+ return "Stopping";
}
- if (phases.length === 0) return job.phase;
- if (phases.some((phase) => phase === "Failed")) return "Failed";
- if (phases.every((phase) => phase === "Succeeded")) return "Succeeded";
- if (phases.every((phase) => phase === "Running")) return "Running";
- if (job.phase === "Stopped" && phases.every((phase) => phase === "Stopped")) {
- return "Stopped";
- }
- return "Pending";
+ return job.phase || "Pending";
}
diff --git a/apps/rlark-ui/tests/job-actions.test.mjs b/apps/rlark-ui/tests/job-actions.test.mjs
index 344e93e..f598bf2 100644
--- a/apps/rlark-ui/tests/job-actions.test.mjs
+++ b/apps/rlark-ui/tests/job-actions.test.mjs
@@ -14,6 +14,10 @@ const createJobSource = await readFile(
new URL("../src/pages/CreateJob.tsx", import.meta.url),
"utf8",
);
+const sharedSource = await readFile(
+ new URL("../src/components/shared.tsx", import.meta.url),
+ "utf8",
+);
const clustersSource = await readFile(
new URL("../src/pages/Clusters.tsx", import.meta.url),
"utf8",
@@ -22,6 +26,45 @@ const appSource = await readFile(
new URL("../src/App.tsx", import.meta.url),
"utf8",
);
+const { crdToJob } = await import("../dist/test/utils/crd.js");
+
+test("cloning uses the user-facing task role instead of the resource name", () => {
+ const job = crdToJob({
+ apiVersion: "rlinf.io/v1alpha1",
+ kind: "Job",
+ metadata: { name: "jo-a746801868d9410e" },
+ spec: {
+ tasks: [
+ {
+ name: "65b0-89d2-8272",
+ head: true,
+ agentType: "Kubernetes",
+ role: "Actor",
+ nodeSelector: {},
+ kubernetes: {
+ workload: {
+ template: {
+ spec: {
+ containers: [
+ {
+ name: "main",
+ image: "busybox",
+ env: [{ name: "RLARK_TASK_ROLE", value: "Learner" }],
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ });
+
+ assert.deepEqual(job.defaultRoles, ["Learner"]);
+ assert.equal(job.resources[0].role, "Learner");
+ assert.equal(job.headerRole, "Learner");
+});
test("failed jobs clean residual workers before starting", () => {
assert.match(
@@ -46,23 +89,19 @@ test("failed jobs clean residual workers before starting", () => {
jobsSource,
/body: JSON\.stringify\(\{ spec: \{ stopped: false \} \}\)/,
);
- assert.match(jobsSource, /isStartable \? : {
- assert.match(jobsSource, /className="job-row-action"[\s\S]*?"复制"/);
- assert.match(jobsSource, /className="job-row-action"[\s\S]*?"重启"/);
- assert.match(jobsSource, /job-quick-lifecycle[\s\S]*?"停止"/);
assert.match(
jobsSource,
- /data-tooltip=\{zh \? "更多操作" : "More actions"\}/,
+ /isStartable \? : /,
);
+});
+
+test("job icon actions provide visible hover hints", () => {
+ assert.match(jobsSource, /title=\{lifecycleLabel\}/);
assert.match(
jobsSource,
- /className="action-dropdown-item danger"[\s\S]*?"删除"/,
+ /data-tooltip=\{zh \? "更多操作" : "More actions"\}/,
);
assert.match(jobsSource, /onRestart=\{\(\) => setRestartTarget\(job\)\}/);
- assert.match(jobsStyles, /\.job-row-action/);
});
test("job lifecycle actions use the shared in-app confirmation dialog", () => {
@@ -79,11 +118,22 @@ test("worker event tooltip stays compact and shows only recent events", () => {
assert.match(jobsStyles, /-webkit-line-clamp: 2/);
});
-test("long job IDs remain fully readable", () => {
+test("job IDs remain readable and can be copied from list and detail", () => {
assert.match(jobsSource, /job-id-cell/);
assert.match(jobsSource, /title=\{job\.id\}/);
- assert.match(jobsStyles, /overflow-wrap: anywhere/);
+ assert.match(jobsSource, /handleCopyJobId\(job\.id\)/);
+ assert.match(jobsSource, /handleCopyResourceId/);
+ assert.match(jobsSource, /复制资源 ID/);
+ assert.match(jobsStyles, /white-space: nowrap/);
+ assert.match(jobsStyles, /text-overflow: ellipsis/);
assert.match(jobsStyles, /\.job-id-cell\.is-long strong/);
+ assert.match(jobsStyles, /\.job-id-copy/);
+});
+
+test("switching worker roles scrolls back to the configuration header", () => {
+ assert.match(createJobSource, /const roleConfigTopRef = useRef/);
+ assert.match(createJobSource, /modalBody\.scrollTop = 0/);
+ assert.match(createJobSource, /selectRole\(roles\[idx \+ 1\], true\)/);
});
test("job deletion waits for worker cleanup before deleting", () => {
@@ -95,21 +145,27 @@ test("job deletion waits for worker cleanup before deleting", () => {
);
});
-test("job actions report success and return to the list", () => {
+test("job actions report success and keep the selected job detail", () => {
assert.match(jobsSource, /className="job-action-notice" role="status"/);
assert.match(
jobsSource,
/setActionNotice\(zh \? "任务已删除" : "Job deleted"\)/,
);
- assert.match(jobsSource, /if \(selectedName\) onSelect\(undefined\)/);
+ assert.match(jobsSource, /if \(selectedName\) onSelect\(job\.name\)/);
+ assert.match(jobsSource, /if \(succeeded\) onSelect\(job\.name\)/);
assert.match(jobsStyles, /\.job-action-notice/);
});
-test("job submission reports success and returns to the job list", () => {
- assert.match(createJobSource, /onSuccess: \(message: string\) => void/);
+test("job submission reports success and opens the saved job detail", () => {
+ assert.match(
+ createJobSource,
+ /onSuccess: \(message: string, jobName: string\) => void/,
+ );
+ assert.match(createJobSource, /const savedJob = await resp\.json\(\)/);
+ assert.match(createJobSource, /savedJob\.metadata\?\.name/);
assert.match(createJobSource, /\? "任务提交成功"/);
assert.match(appSource, /setJobSubmitNotice\(message\)/);
- assert.match(appSource, /navigate\("jobs", undefined, \{ replace: true \}\)/);
+ assert.match(appSource, /navigate\("jobs", jobName, \{ replace: true \}\)/);
assert.match(
appSource,
/className="job-action-notice app-job-submit-notice"/,
@@ -117,11 +173,38 @@ test("job submission reports success and returns to the job list", () => {
assert.match(appSource, /role="status"/);
});
-test("worker SSH copy is disabled when the jump host is unavailable", () => {
- assert.match(jobsSource, /if \(!value\) return false/);
- assert.match(jobsSource, /disabled=\{!sshCommand\}/);
- assert.match(jobsSource, /未配置 SSH 跳板地址/);
- assert.match(jobsSource, /\{sshCommand && \(/);
+test("job and worker refresh actions show progress", () => {
+ assert.match(jobsSource, /refreshing=\{listRefreshing\}/);
+ assert.match(jobsSource, /setWorkerRefreshing\(true\)/);
+ assert.match(jobsSource, /aria-busy=\{workerRefreshing\}/);
+ assert.match(jobsSource, /刷新中\.\.\./);
+ assert.match(sharedSource, /const \[localRefreshing, setLocalRefreshing\]/);
+ assert.match(sharedSource, /await onRefresh\(\)/);
+ assert.match(sharedSource, /disabled=\{isRefreshing\}/);
+ assert.match(sharedSource, /aria-busy=\{isRefreshing\}/);
+ assert.match(
+ sharedSource,
+ /className=\{isRefreshing \? "job-action-loading" : ""\}/,
+ );
+});
+
+test("stopping a job immediately uses the latest server status", () => {
+ const waitForStoppedSource = jobsSource.slice(
+ jobsSource.indexOf("const waitForJobWorkersStopped"),
+ jobsSource.indexOf("const handleSetStopped"),
+ );
+ assert.match(waitForStoppedSource, /return current;/);
+ assert.doesNotMatch(waitForStoppedSource, /return;\s*\n\s*}/);
+ assert.match(
+ jobsSource,
+ /const stoppedJob = stopped \? await waitForJobWorkersStopped\(job\) : null/,
+ );
+ assert.match(jobsSource, /stoppedJob \?\?/);
+});
+
+test("job storage mappings are derived from the generated resource ID", () => {
+ assert.doesNotMatch(createJobSource, /computePvcStorageMap/);
+ assert.match(createJobSource, /name: jobResourceName/);
});
test("worker details show cluster and link node names", () => {
@@ -130,11 +213,9 @@ test("worker details show cluster and link node names", () => {
/cluster: pod\.taskNamespace \|\| pod\.namespace \|\| "—"/,
);
assert.match(jobsSource, /zh \? "集群" : "Cluster"/);
- assert.doesNotMatch(jobsSource, /zh \? "申请 CPU" : "CPU request"/);
- assert.doesNotMatch(jobsSource, /zh \? "申请内存" : "Memory request"/);
assert.match(jobsSource, /function WorkerNodeLink/);
assert.match(jobsSource, /onClick=\{\(\) => onSelectNode\(node\)\}/);
- assert.match(jobsStyles, /\.worker-node-link/);
+ assert.match(jobsStyles, /\.worker-chip-link/);
assert.match(
clustersSource,
/realNodes\.find\(\(n\) => n\.metadata\.name === selectedNodeName\)/,
diff --git a/apps/rlark-ui/tests/job-network.test.mjs b/apps/rlark-ui/tests/job-network.test.mjs
new file mode 100644
index 0000000..9abacc2
--- /dev/null
+++ b/apps/rlark-ui/tests/job-network.test.mjs
@@ -0,0 +1,73 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+import {
+ automaticNetworkDomain,
+ generateJobCRD,
+} from "../dist/test/utils/job.js";
+
+function resource(cluster) {
+ return {
+ role: "worker",
+ cluster,
+ nodeSelector: "",
+ replicas: 1,
+ cpu: "1",
+ memory: "1Gi",
+ gpu: "0",
+ devices: [],
+ image: "busybox:latest",
+ prepareScript: "",
+ envs: [],
+ mounts: [],
+ };
+}
+
+const roles = ["actor", "worker"];
+
+const base = {
+ name: "jo-0123456789abcdef",
+ type: "Custom",
+ headerRole: "actor",
+ roles,
+ runScript: "echo ready",
+};
+
+test("selects the configured network domain regardless of task placement", () => {
+ assert.equal(automaticNetworkDomain([]), "");
+ assert.equal(
+ automaticNetworkDomain([{ name: "network-b" }, { name: "network-a" }]),
+ "network-a",
+ );
+});
+
+test("enables the configured network domain for same-cluster jobs", () => {
+ const sameCluster = {
+ actor: resource("cluster-a"),
+ worker: resource("cluster-a"),
+ };
+
+ assert.equal(
+ generateJobCRD({
+ ...base,
+ roleResources: sameCluster,
+ domain: "network-a",
+ }).spec.domain,
+ "network-a",
+ );
+});
+
+test("omits the network domain when none is configured", () => {
+ const differentClusters = {
+ actor: resource("cluster-a"),
+ worker: resource("cluster-b"),
+ };
+
+ assert.equal(
+ generateJobCRD({
+ ...base,
+ roleResources: differentClusters,
+ domain: "",
+ }).spec.domain,
+ undefined,
+ );
+});
diff --git a/apps/rlark-ui/tests/job-phase.test.mjs b/apps/rlark-ui/tests/job-phase.test.mjs
index 31cc9f1..c909030 100644
--- a/apps/rlark-ui/tests/job-phase.test.mjs
+++ b/apps/rlark-ui/tests/job-phase.test.mjs
@@ -10,18 +10,18 @@ function job(phase, stopped, taskPhases) {
};
}
-test("derives Running and Stopped only when every task matches", () => {
+test("uses the aggregate Job phase regardless of task phases", () => {
assert.equal(
effectiveJobPhase(job("Pending", false, ["Running", "Running"])),
- "Running",
+ "Pending",
);
assert.equal(
- effectiveJobPhase(job("Pending", true, ["Stopped", "Stopped"])),
+ effectiveJobPhase(job("Stopped", true, ["Stopped", "Stopped"])),
"Stopped",
);
assert.equal(
effectiveJobPhase(job("Running", false, ["Running", "Pending"])),
- "Pending",
+ "Running",
);
assert.equal(
effectiveJobPhase(job("Running", true, ["Stopped", "Running"])),
@@ -42,13 +42,13 @@ test("distinguishes stopping from normal Pending states", () => {
assert.equal(effectiveJobPhase(job("Pending", false, [])), "Pending");
});
-test("derives terminal states while the aggregate is Pending", () => {
+test("does not infer terminal states from task phases", () => {
assert.equal(
effectiveJobPhase(job("Pending", false, ["Pending", "Failed"])),
- "Failed",
+ "Pending",
);
assert.equal(
effectiveJobPhase(job("Pending", false, ["Succeeded", "Succeeded"])),
- "Succeeded",
+ "Pending",
);
});
diff --git a/apps/rlark-ui/tests/job-storage.test.mjs b/apps/rlark-ui/tests/job-storage.test.mjs
new file mode 100644
index 0000000..56e1467
--- /dev/null
+++ b/apps/rlark-ui/tests/job-storage.test.mjs
@@ -0,0 +1,86 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+import {
+ generateJobCRD,
+ generateJobResourceName,
+} from "../dist/test/utils/job.js";
+
+function storageResource(mounts) {
+ return {
+ role: "actor",
+ cluster: "cluster-a",
+ nodeSelector: "",
+ replicas: 1,
+ cpu: "1",
+ memory: "1Gi",
+ gpu: "0",
+ devices: [],
+ image: "busybox:latest",
+ prepareScript: "",
+ envs: [],
+ mounts,
+ };
+}
+
+test("generates browser-compatible job resource IDs", () => {
+ assert.match(generateJobResourceName(), /^jo-[0-9a-f]{16}$/);
+});
+
+test("caps generated PVC storage size at 200 Gi", () => {
+ const crd = generateJobCRD({
+ name: "jo-0123456789abcdef",
+ displayName: "Readable Job",
+ type: "Custom",
+ headerRole: "actor",
+ roles: ["actor"],
+ roleResources: {
+ actor: storageResource([
+ {
+ type: "storage",
+ objectStorage: "fast-storage",
+ mountPath: "/data",
+ hostPath: "",
+ pvcSizeGb: 201,
+ },
+ ]),
+ },
+ runScript: "echo ready",
+ domain: "",
+ });
+
+ const workload = crd.spec.tasks[0].kubernetes.workload;
+ const claimName =
+ workload.template.spec.volumes[0].persistentVolumeClaim.claimName;
+ assert.equal(workload.pvcSizeGbMap[claimName], 200);
+});
+
+test("maps a selected storage class to the generated PVC claim name", () => {
+ const resourceName = "jo-0123456789abcdef";
+ const mounts = [
+ {
+ type: "storage",
+ objectStorage: "fast-storage",
+ mountPath: "/data",
+ hostPath: "",
+ pvcSizeGb: 20,
+ },
+ ];
+ const crd = generateJobCRD({
+ name: resourceName,
+ displayName: "Readable Job",
+ type: "Custom",
+ headerRole: "actor",
+ roles: ["actor"],
+ roleResources: { actor: storageResource(mounts) },
+ runScript: "echo ready",
+ domain: "",
+ });
+
+ const workload = crd.spec.tasks[0].kubernetes.workload;
+ const claimName =
+ workload.template.spec.volumes[0].persistentVolumeClaim.claimName;
+
+ assert.equal(claimName, "pvc-jo-0123456789abcdef-actor-data");
+ assert.equal(workload.pvcStorageMap[claimName], "fast-storage");
+ assert.equal(workload.pvcSizeGbMap[claimName], 20);
+});
diff --git a/apps/rlark/docs/api/examples.md b/apps/rlark/docs/api/examples.md
index d57bd79..92a7b0b 100644
--- a/apps/rlark/docs/api/examples.md
+++ b/apps/rlark/docs/api/examples.md
@@ -41,10 +41,10 @@ curl -X PATCH \
## 2. Create and inspect a Job
-Users create Jobs with complete Task templates. The Job controller creates the corresponding namespaced Task resources and the Agent creates the downstream workload.
+Users create Jobs with complete Task templates. The Gateway stores the submitted `metadata.name` as the display name and returns a generated resource ID in `metadata.name`. API clients must retain that returned ID for later requests. The Job controller creates the corresponding namespaced Task resources and the Agent creates the downstream workload.
```bash
-curl -X POST "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs" \
+JOB_ID="$(curl -fsS -X POST "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs" \
-H "Content-Type: application/json" \
-d '{
"apiVersion": "rlinf.io/v1alpha1",
@@ -86,7 +86,9 @@ curl -X POST "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs" \
}
]
}
- }'
+ }' | jq -r '.metadata.name')"
+
+echo "$JOB_ID" # jo-<16 hexadecimal characters>
```
The image, command, environment, resources, and volumes belong under `kubernetes.workload.template.spec.containers`; they are not top-level Task fields.
@@ -96,16 +98,16 @@ The image, command, environment, resources, and volumes belong under `kubernetes
curl "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs?labelSelector=framework=ppo"
# Get the Job, including its status field.
-curl "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs/ppo-cartpole"
+curl "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs/$JOB_ID"
# Stop the Job.
curl -X PATCH \
- "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs/ppo-cartpole" \
+ "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs/$JOB_ID" \
-H "Content-Type: application/merge-patch+json" \
-d '{"spec":{"stopped":true}}'
# Delete the Job.
-curl -X DELETE "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs/ppo-cartpole"
+curl -X DELETE "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs/$JOB_ID"
```
A merge patch replaces arrays as a whole. When patching `tasks` or `jobTemplates`, send complete array elements, including required fields such as `role`, or use JSON Patch.
@@ -221,7 +223,7 @@ curl "$RLARK_GATEWAY/api/v1/storage/storageclass?clusters=cluster-a,cluster-b"
curl "$RLARK_GATEWAY/api/v1/storage/storageclass/provider"
# Read Job logs.
-curl "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs/ppo-cartpole/logs"
+curl "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs/$JOB_ID/logs"
# List SSH public keys for a user.
curl "$RLARK_GATEWAY/api/v1/ssh-user-keys?user=alice"
diff --git a/apps/rlark/docs/api/reference.md b/apps/rlark/docs/api/reference.md
index 9b8e52e..22c67e6 100644
--- a/apps/rlark/docs/api/reference.md
+++ b/apps/rlark/docs/api/reference.md
@@ -18,6 +18,8 @@ Path parameters are written as `{name}` below; Gin uses the equivalent `:name` s
| `pods` | Namespaced | `GET /api/v1/rlinf.io/v1alpha1/pods`; `GET, PATCH /api/v1/rlinf.io/v1alpha1/pods/{name}`; `GET /api/v1/rlinf.io/v1alpha1/pods/{name}/events`; `GET /api/v1/rlinf.io/v1alpha1/pods/{name}/terminal` |
| `domains` | Cluster | `GET, POST /api/v1/rlinf.io/v1alpha1/domains`; `GET, PUT, PATCH, DELETE /api/v1/rlinf.io/v1alpha1/domains/{name}` |
+When creating a Job, the Gateway stores the submitted `metadata.name` as its display name and returns a generated `jo-<16 hexadecimal characters>` resource ID in `metadata.name`. Use the returned ID for subsequent Job API requests.
+
The Gateway router does not expose CRD status subresource routes. Status is returned as part of the normal resource representation.
## Clusters and certificates
diff --git a/apps/rlark/docs/api/swagger.yaml b/apps/rlark/docs/api/swagger.yaml
index c23ec1b..fef62ef 100644
--- a/apps/rlark/docs/api/swagger.yaml
+++ b/apps/rlark/docs/api/swagger.yaml
@@ -31,6 +31,20 @@ tags:
description: Cluster-scoped Workflow resources
paths:
+ /api/v1/images:
+ get:
+ tags: [Image]
+ summary: List recently used images
+ description: Returns up to ten images used by the most recent jobs, ordered by last use time.
+ operationId: listImages
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ImageUsageList'
+
# ─── Job Collection ───────────────────────────────────────────────
/api/v1/rlinf.io/v1alpha1/jobs:
get:
@@ -795,6 +809,27 @@ components:
type: boolean
schemas:
+ ImageUsage:
+ type: object
+ required: [image, useCount, lastUsedAt]
+ properties:
+ image:
+ type: string
+ useCount:
+ type: integer
+ format: int32
+ lastUsedAt:
+ type: string
+ format: date-time
+ ImageUsageList:
+ type: object
+ required: [items]
+ properties:
+ items:
+ type: array
+ items:
+ $ref: '#/components/schemas/ImageUsage'
+
# ─── Common Kubernetes Schemas ──────────────────────────────────
ObjectMeta:
type: object
diff --git a/apps/rlark/docs/developer-guide/embodied-runtime-reference.md b/apps/rlark/docs/developer-guide/embodied-runtime-reference.md
index 09030f5..a5eb476 100644
--- a/apps/rlark/docs/developer-guide/embodied-runtime-reference.md
+++ b/apps/rlark/docs/developer-guide/embodied-runtime-reference.md
@@ -257,7 +257,7 @@ The webhook has **automatic CA management**: it loads the CA cert+key from a Sec
| `--webhook-mutating-config` | `MutatingWebhookConfiguration` name for auto CA management. |
| `--webhook-service-name` / `--webhook-service-namespace` | Service fronting the webhook. |
| `--webhook-ca-secret-name` / `--webhook-ca-secret-namespace` | Secret persisting the CA (empty = in-memory). |
-| `--webhook-devinit-image` | Injected init container image (default: auto-discovered). |
+| `--webhook-devinit-image` | Injected init container image (default: auto-discovered, then `busybox:latest`). The binary is mounted from the host, so the image does not need to contain devinit. |
Enable via Helm:
diff --git a/apps/rlark/docs/examples/quickstart.sh b/apps/rlark/docs/examples/quickstart.sh
index 2c587cf..acbcf91 100755
--- a/apps/rlark/docs/examples/quickstart.sh
+++ b/apps/rlark/docs/examples/quickstart.sh
@@ -29,7 +29,7 @@ for i in $(seq 1 $CLUSTER_COUNT); do
kind delete cluster --name "rlark-data-$i" 2>/dev/null || true
done
docker rm -f local-registry 2>/dev/null || true
-rm -rf /tmp/rlark /tmp/kind-kubeconfig-* /tmp/kind-config.yaml /tmp/Dockerfile.rlark /tmp/rlark-bin 2>/dev/null || true
+rm -rf /tmp/rlark /tmp/kind-kubeconfig-* /tmp/kind-config.yaml /tmp/rlark-bin 2>/dev/null || true
ok "Cleanup complete"
# =============================================================================
@@ -75,7 +75,7 @@ GOOS=linux CGO_ENABLED=0 go build -o /tmp/rlark-bin/gateway ./cmd/gateway/ &
GOOS=linux CGO_ENABLED=0 go build -o /tmp/rlark-bin/network-sidecar ./cmd/network-sidecar/ &
wait
-cat > /tmp/Dockerfile.rlark <<'DOCKERFILE'
+cat > /tmp/rlark-bin/Dockerfile <<'DOCKERFILE'
FROM scratch
COPY server /rlark-server
COPY agent /rlark-agent
@@ -84,7 +84,7 @@ COPY gateway /rlark-gateway
COPY network-sidecar /usr/local/bin/network-sidecar
DOCKERFILE
-docker build -t "$IMAGE" -f /tmp/Dockerfile.rlark /tmp/rlark-bin
+docker build -t "$IMAGE" /tmp/rlark-bin
docker push "$IMAGE"
# Pull busybox (try Docker Hub, then mirror, use local if available)
diff --git a/apps/rlark/docs/reference/configuration.md b/apps/rlark/docs/reference/configuration.md
index 03e08ee..6d936f7 100644
--- a/apps/rlark/docs/reference/configuration.md
+++ b/apps/rlark/docs/reference/configuration.md
@@ -5,7 +5,7 @@
PostgreSQL connection configuration loaded via `--db-config` flag. Used by rlark-server, rlark-gateway, and rlark-controller-manager.
| Field | Type | Default | Description |
-|-------|------|---------|-------------|
+| ------- | ------ | --------- | ------------- |
| `host` | string | `localhost` | PostgreSQL host |
| `port` | int | `5432` | PostgreSQL port |
| `database` | string | `rlark` | Database name |
@@ -21,6 +21,7 @@ PostgreSQL connection configuration loaded via `--db-config` flag. Used by rlark
Adjust `maxOpenConns` and `maxIdleConns` based on actual load. Increase for high-concurrency scenarios.
**Example:**
+
```yaml
host: postgresql
port: 5432
@@ -39,7 +40,7 @@ debug: false
Control plane server. Manages TLS/SSH certificates, agent registration, and the Gateway API.
| Flag | Type | Default | Description |
-|------|------|---------|-------------|
+| ------ | ------ | --------- | ------------- |
| `--https-port` | int | `8443` | HTTPS listen port |
| `--ssh-port` | int | `2222` | SSH listen port |
| `--unsafe-http-port` | int | `8888` | Internal HTTP for `/healthz`, `/readyz`, `/livez`, `/metrics`, and peer proxying |
@@ -60,6 +61,7 @@ Control plane server. Manages TLS/SSH certificates, agent registration, and the
Agents use this port for certificate signing. In production, only expose to internal networks.
**Example:**
+
```bash
rlark-server \
--https-port=8443 \
@@ -74,7 +76,7 @@ rlark-server \
API gateway. Handles all REST API requests including cluster management, job management, and storage operations.
| Flag | Type | Default | Description |
-|------|------|---------|-------------|
+| ------ | ------ | --------- | ------------- |
| `--addr` | string | `:8080` | API gateway bind address; `rlarkadm` overrides it to `:8090` |
| `--db-config` | string | `""` | Database configuration file path |
| `--server-address` | string | `https://rlark-server.rlark-system.svc:8443` | RLark server address for certificate signing |
@@ -87,6 +89,7 @@ API gateway. Handles all REST API requests including cluster management, job man
| `--kube-timeout` | duration | `0` | Kubernetes client request timeout |
**Example:**
+
```bash
rlark-gateway \
--addr=:8080 \
@@ -99,7 +102,7 @@ rlark-gateway \
Controller manager. Reconciles Jobs, Workflows, and Domain resources.
| Flag | Type | Default | Description |
-|------|------|---------|-------------|
+| ------ | ------ | --------- | ------------- |
| `--server-address` | string | `https://rlark-server.rlark-system.svc:8443` | RLark server address |
| `--db-config` | string | `""` | Database configuration file path |
| `--leader-elect` | bool | `true` | Enable leader election for HA |
@@ -119,6 +122,7 @@ Controller manager. Reconciles Jobs, Workflows, and Domain resources.
Set `--leader-elect=false` for single-instance deployments to avoid unnecessary election overhead.
**Example:**
+
```bash
rlark-controller-manager \
--server-address=https://rlark-server:8443 \
@@ -133,7 +137,7 @@ rlark-controller-manager \
Data plane agent. Deployed on each cluster or node. Manages node registration, Task execution, and cross-cluster networking.
| Flag | Type | Default | Description |
-|------|------|---------|-------------|
+| ------ | ------ | --------- | ------------- |
| `--server-address` | string | `https://localhost:8443` | RLark server address |
| `--server-hostname` | string | `""` | Expected server TLS hostname |
| `--client-cert` | string | `""` | Client TLS certificate path |
@@ -171,6 +175,7 @@ Data plane agent. Deployed on each cluster or node. Manages node registration, T
- `both`: Runs both cluster and node-level agents
**Example:**
+
```bash
rlark-agent \
--mode=both \
@@ -187,7 +192,7 @@ rlark-agent \
Network sidecar. Runs alongside each Task Pod to provide cross-cluster Pod-to-Pod networking via TUN device and gVisor netstack.
| Flag | Type | Default | Description |
-|------|------|---------|-------------|
+| ------ | ------ | --------- | ------------- |
| `--sidecar-unix-socket` | string | `/var/run/rlark/nodeserver.sock` | NodeServer Unix socket path |
| `--sidecar-tun-name` | string | `gnet0` | TUN device name |
| `--sidecar-tun-mtu` | int | `1500` | TUN device MTU |
@@ -197,6 +202,7 @@ Network sidecar. Runs alongside each Task Pod to provide cross-cluster Pod-to-Po
| `--sidecar-hosts-file` | string | `/etc/hosts` | Hosts file path |
**Example:**
+
```bash
rlark-network-sidecar \
--sidecar-unix-socket=/var/run/rlark/nodeserver.sock \
@@ -209,14 +215,14 @@ rlark-network-sidecar \
SSH daemon. Provides SSH access to running Task Pods. Integrated into rlark-server via `--ssh-port`.
| Flag | Type | Default | Description |
-|------|------|---------|-------------|
+| ------ | ------ | --------- | ------------- |
| `--port` | string | `22` | SSH listen port |
| `--shell` | string | `""` | Shell binary path (default: /bin/bash) |
**Environment variables:**
| Variable | Description |
-|----------|-------------|
+| ---------- | ------------- |
| `RLARK_SSH_PUBLIC_KEY` | SSH public key for authorized_keys |
| `RLARK_SSH_AUTHORIZED_KEYS_FILE` | Path to authorized_keys file |
@@ -225,7 +231,7 @@ SSH daemon. Provides SSH access to running Task Pods. Integrated into rlark-serv
Object storage backend configuration used by the gateway.
| Field | Type | Default | Description |
-|-------|------|---------|-------------|
+| ------- | ------ | --------- | ------------- |
| `accessKeyId` | string | `""` | Access key ID |
| `secretAccessKey` | string | `""` | Secret access key |
| `bucket` | string | `""` | Bucket name |
@@ -246,7 +252,7 @@ The YAML file passed to `rlarkadm install -f`. See [CLI Reference](cli.md#rlarka
These names are the exact YAML keys accepted by `rlarkadm`.
| Field | Type | Default | Description |
-|-------|------|---------|-------------|
+| ------- | ------ | --------- | ------------- |
| `apiVersion` | string | — | API version (required); maintained examples use `rlark.io/v1alpha1` |
| `kind` | string | — | Must be `DeployConfig` |
| `plane` | string | — | Required: `control` or `data` |
@@ -264,7 +270,7 @@ These names are the exact YAML keys accepted by `rlarkadm`.
### DBConfig
| Field | Type | Default | Description |
-|-------|------|---------|-------------|
+| ------- | ------ | --------- | ------------- |
| `host` | string | `postgresql` | PostgreSQL host |
| `port` | int | `5432` | PostgreSQL port |
| `database` | string | `rlark` | Database name |
@@ -274,7 +280,7 @@ These names are the exact YAML keys accepted by `rlarkadm`.
### KubernetesEnv
| Field | Type | Default | Description |
-|-------|------|---------|-------------|
+| ------- | ------ | --------- | ------------- |
| `kubeconfig` | string | `""` | kubeconfig file path; an empty value uses the normal client-go loading rules |
| `gateway-image` | string | `""` | Gateway image |
| `controller-manager-image` | string | `""` | Controller Manager image |
@@ -298,7 +304,7 @@ These names are the exact YAML keys accepted by `rlarkadm`.
### DockerEnv
| Field | Type | Description |
-|-------|------|-------------|
+| ------- | ------ | ------------- |
| `gateway-image` | string | Gateway image |
| `controller-manager-image` | string | Controller Manager image |
| `server-image` | string | Server image |
@@ -315,7 +321,7 @@ These names are the exact YAML keys accepted by `rlarkadm`.
Raw deployment is experimental. Prefer Kubernetes or Docker.
| Field | Type | Description |
-|-------|------|-------------|
+| ------- | ------ | ------------- |
| `gateway-artifact` | string | Gateway binary path |
| `controller-manager-artifact` | string | Controller Manager binary path |
| `server-artifact` | string | Server binary path |
@@ -330,7 +336,7 @@ These names are the exact YAML keys accepted by `rlarkadm`.
Required for data plane deployment.
| Field | Type | Description |
-|-------|------|-------------|
+| ------- | ------ | ------------- |
| `ca-cert` | string | Inline CA PEM or an existing file path |
| `agent-cert` | string | Inline Agent certificate PEM or an existing file path |
| `agent-key` | string | Inline Agent private key PEM or an existing file path |
@@ -338,7 +344,7 @@ Required for data plane deployment.
### StorageConfig
| Field | Type | Default | Description |
-|-------|------|---------|-------------|
+| ------- | ------ | --------- | ------------- |
| `type` | string | | Storage type: emptyDir, hostPath, pvc |
| `host-path` | string | `""` | Host path for hostPath type |
| `storage-class` | string | `""` | StorageClass for PVC type; empty uses the cluster default |
@@ -348,19 +354,20 @@ Required for data plane deployment.
### ComponentConfig
| Field | Type | Description |
-|-------|------|-------------|
+| ------- | ------ | ------------- |
| `replicas` | int | Number of replicas |
| `storage` | StorageConfig | Storage configuration |
### EtcdConfig
| Field | Type | Description |
-|-------|------|-------------|
+| ------- | ------ | ------------- |
| `address` | string | etcd address |
| `replicas` | int | Number of replicas |
| `storage` | StorageConfig | Storage configuration |
**Example (control plane):**
+
```yaml
apiVersion: rlark.io/v1alpha1
kind: DeployConfig
@@ -382,6 +389,7 @@ db:
```
**Example (data plane):**
+
```yaml
apiVersion: rlark.io/v1alpha1
kind: DeployConfig
diff --git a/apps/rlark/docs/reference/crd.md b/apps/rlark/docs/reference/crd.md
index ce981e8..c6082b4 100644
--- a/apps/rlark/docs/reference/crd.md
+++ b/apps/rlark/docs/reference/crd.md
@@ -1403,6 +1403,7 @@ Responses:
- `kubernetes`: `object`, optional
- `workload`: `object`, optional
- `kind`: `string`, optional
+ - `pvcSizeGbMap`: `object`, optional
- `pvcStorageMap`: `object`, optional
- `replicas`: `integer`, optional
- `template`: `object`, optional - PodTemplateSpec describes the data a pod should have when created from a template
diff --git a/apps/rlark/docs/user-guide/best-practices.md b/apps/rlark/docs/user-guide/best-practices.md
index 786694d..aa8b5ac 100644
--- a/apps/rlark/docs/user-guide/best-practices.md
+++ b/apps/rlark/docs/user-guide/best-practices.md
@@ -121,7 +121,7 @@ Before creating the job, gather the following from your existing RLinf setup:
1. Navigate to **Jobs** → **Create Job**
2. **Fill in basic info**:
- - Job name: lowercase, alphanumeric, hyphens (e.g., `my-training-run-001`)
+ - Job name: a display name of up to 128 characters; display names may be reused because RLark assigns a separate resource ID
- Job type: **Custom** for single-node, or pick a template for multi-role
3. **Configure Worker roles**:
@@ -150,7 +150,7 @@ Before creating the job, gather the following from your existing RLinf setup:
| Setting | Description |
|----------|-------------|
| **Header Role** | Must be the role with exactly 1 Worker |
- | **Network Domain** | Select if cross-cluster communication is needed |
+ | **Network Domain** | Automatically uses the first configured domain by name; omitted when none is configured |
| **SSH Key** | Select a key to inject into Workers |
| **Run Script** | The main command executed after Ray is ready |
| **TensorBoard Dir** | Path inside the container for TensorBoard logs |
@@ -220,4 +220,4 @@ Check that checkpoints and training outputs are written to the configured storag
- [Create a Training Job](jobs.md) — detailed field reference
- [Plan Multi-Node Jobs](workflows.md) — multi-role and heterogeneous Workers
- [Use Storage in Jobs](storage.md) — hostPath and object storage configuration
-- [Connect via SSH](ssh-keys.md) — SSH into running Workers
\ No newline at end of file
+- [Connect via SSH](ssh-keys.md) — SSH into running Workers
diff --git a/apps/rlark/docs/user-guide/jobs.md b/apps/rlark/docs/user-guide/jobs.md
index 0b6f3bc..40dfe96 100644
--- a/apps/rlark/docs/user-guide/jobs.md
+++ b/apps/rlark/docs/user-guide/jobs.md
@@ -56,7 +56,7 @@ For each worker role, configure the following:
- **Storage Mounts** — Attach storage to worker containers. Two types are supported:
- **hostPath**: Mount a directory from the host node's filesystem. Its data is not deleted by Job lifecycle actions.
- - **PVC** (PersistentVolumeClaim): Mount a Kubernetes persistent volume. Stopping, restarting, or deleting the Job deletes its task PVCs; starting or restarting creates empty PVCs.
+ - **PVC** (PersistentVolumeClaim): Mount a Kubernetes persistent volume using the selected storage class. Stopping, restarting, or deleting the Job deletes its task PVCs; starting or restarting creates empty PVCs.

@@ -66,7 +66,7 @@ Configure settings that apply to all workers in the job:
- **Header Role** — Select one role as the Header role. This role's first worker coordinates the distributed training, and its IP address is communicated to all other workers.
-- **Cross-Cluster Network Domain** — When the job spans multiple clusters, select the pre-configured network domain. This enables cross-cluster Pod-to-Pod networking via the RLark virtual network (TUN + gVisor + SSH tunnels).
+- **Cross-Cluster Network Domain** — If network domains are configured, the console automatically enables the first domain by name for every Job, regardless of Worker placement. No domain is added when none is configured.
- **SSH Public Keys** — Provide one or more SSH public keys that will be injected into the `~/.ssh/authorized_keys` of every worker container. This allows you to SSH into running containers for debugging.
@@ -80,7 +80,7 @@ Configure settings that apply to all workers in the job:
Open the Job Details page from the Jobs list to see a high-level summary:
-- **Name** — The job name (unique within the cluster)
+- **Name** — The user-facing display name. It may be reused; RLark assigns a separate `jo-<16 hexadecimal characters>` resource ID.
- **Type** — The job type (Reinforcement Learning, Data Collection, Evaluation, Custom)
- **Status** — Current state: Pending, Running, Succeeded, Failed, Stopped
- **Worker Count** — Total number of workers across all roles
diff --git a/apps/rlark/docs/zh/api/examples.md b/apps/rlark/docs/zh/api/examples.md
index cc90efa..2ab567c 100644
--- a/apps/rlark/docs/zh/api/examples.md
+++ b/apps/rlark/docs/zh/api/examples.md
@@ -41,10 +41,10 @@ curl -X PATCH \
## 2. 创建和查看 Job
-用户通过完整的 Task 模板创建 Job。Job 控制器创建对应的命名空间级 Task 资源,Agent 随后创建下层 workload。
+用户通过完整的 Task 模板创建 Job。Gateway 会将请求中的 `metadata.name` 保存为展示名,并在响应的 `metadata.name` 中返回系统生成的资源 ID;API 客户端必须保存该 ID,后续通过它访问任务。Job 控制器创建对应的命名空间级 Task 资源,Agent 随后创建下层 workload。
```bash
-curl -X POST "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs" \
+JOB_ID="$(curl -fsS -X POST "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs" \
-H "Content-Type: application/json" \
-d '{
"apiVersion": "rlinf.io/v1alpha1",
@@ -86,7 +86,9 @@ curl -X POST "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs" \
}
]
}
- }'
+ }' | jq -r '.metadata.name')"
+
+echo "$JOB_ID" # jo-<16 位十六进制字符>
```
镜像、命令、环境变量、资源和卷应放在 `kubernetes.workload.template.spec.containers` 下,而不是作为 Task 的顶层字段。
@@ -96,16 +98,16 @@ curl -X POST "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs" \
curl "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs?labelSelector=framework=ppo"
# 获取 Job,包括其 status 字段。
-curl "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs/ppo-cartpole"
+curl "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs/$JOB_ID"
# 停止 Job。
curl -X PATCH \
- "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs/ppo-cartpole" \
+ "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs/$JOB_ID" \
-H "Content-Type: application/merge-patch+json" \
-d '{"spec":{"stopped":true}}'
# 删除 Job。
-curl -X DELETE "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs/ppo-cartpole"
+curl -X DELETE "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs/$JOB_ID"
```
Merge Patch 会整体替换数组。修补 `tasks` 或 `jobTemplates` 时,应发送包含 `role` 等必填字段的完整数组元素,或者改用 JSON Patch。
@@ -221,7 +223,7 @@ curl "$RLARK_GATEWAY/api/v1/storage/storageclass?clusters=cluster-a,cluster-b"
curl "$RLARK_GATEWAY/api/v1/storage/storageclass/provider"
# 读取 Job 日志。
-curl "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs/ppo-cartpole/logs"
+curl "$RLARK_GATEWAY/api/v1/rlinf.io/v1alpha1/jobs/$JOB_ID/logs"
# 列出用户的 SSH 公钥。
curl "$RLARK_GATEWAY/api/v1/ssh-user-keys?user=alice"
diff --git a/apps/rlark/docs/zh/api/reference.md b/apps/rlark/docs/zh/api/reference.md
index dac00ba..439563d 100644
--- a/apps/rlark/docs/zh/api/reference.md
+++ b/apps/rlark/docs/zh/api/reference.md
@@ -18,6 +18,8 @@
| `pods` | Namespaced | `GET /api/v1/rlinf.io/v1alpha1/pods`;`GET, PATCH /api/v1/rlinf.io/v1alpha1/pods/{name}`;`GET /api/v1/rlinf.io/v1alpha1/pods/{name}/events`;`GET /api/v1/rlinf.io/v1alpha1/pods/{name}/terminal` |
| `domains` | Cluster | `GET, POST /api/v1/rlinf.io/v1alpha1/domains`;`GET, PUT, PATCH, DELETE /api/v1/rlinf.io/v1alpha1/domains/{name}` |
+创建 Job 时,Gateway 会将请求中的 `metadata.name` 保存为展示名,并在响应的 `metadata.name` 中返回系统生成的 `jo-<16 位十六进制字符>` 资源 ID。后续 Job API 请求应使用该返回 ID。
+
Gateway Router 未暴露 CRD status 子资源路由;状态随普通资源响应返回。
## 集群与证书
diff --git a/apps/rlark/docs/zh/developer-guide/embodied-runtime-reference.md b/apps/rlark/docs/zh/developer-guide/embodied-runtime-reference.md
index af539c7..0319478 100644
--- a/apps/rlark/docs/zh/developer-guide/embodied-runtime-reference.md
+++ b/apps/rlark/docs/zh/developer-guide/embodied-runtime-reference.md
@@ -255,7 +255,7 @@ camctr watch | ffplay -i - # 管道喂给 ffplay
| `--webhook-mutating-config` | 待自动管理 `caBundle` 的 `MutatingWebhookConfiguration` 名称。 |
| `--webhook-service-name` / `--webhook-service-namespace` | 前置 webhook 的 Service。 |
| `--webhook-ca-secret-name` / `--webhook-ca-secret-namespace` | 持久化 CA 的 Secret(留空 = 内存中生成)。 |
-| `--webhook-devinit-image` | 注入的 init 容器镜像(默认:自动发现)。 |
+| `--webhook-devinit-image` | 注入的 init 容器镜像(默认:自动发现,回退到 `busybox:latest`)。二进制从宿主挂载,镜像无需包含 devinit。 |
通过 Helm 启用:
diff --git a/apps/rlark/docs/zh/reference/configuration.md b/apps/rlark/docs/zh/reference/configuration.md
index f5ff4ab..29cb18e 100644
--- a/apps/rlark/docs/zh/reference/configuration.md
+++ b/apps/rlark/docs/zh/reference/configuration.md
@@ -5,7 +5,7 @@
通过 `--db-config` 参数加载的 PostgreSQL 连接配置。rlark-server、rlark-gateway 和 rlark-controller-manager 使用此配置。
| 字段 | 类型 | 默认值 | 说明 |
-|------|------|--------|------|
+| ------ | ------ | -------- | ------ |
| `host` | string | `localhost` | PostgreSQL 主机 |
| `port` | int | `5432` | PostgreSQL 端口 |
| `database` | string | `rlark` | 数据库名 |
@@ -21,6 +21,7 @@
根据实际负载调整 `maxOpenConns` 和 `maxIdleConns`。高并发场景建议增大这两个值。
**示例:**
+
```yaml
host: postgresql
port: 5432
@@ -39,7 +40,7 @@ debug: false
控制面服务器。管理 TLS/SSH 证书、Agent 注册和 Gateway API。
| 参数 | 类型 | 默认值 | 说明 |
-|------|------|--------|------|
+| ------ | ------ | -------- | ------ |
| `--https-port` | int | `8443` | HTTPS 监听端口 |
| `--ssh-port` | int | `2222` | SSH 监听端口 |
| `--unsafe-http-port` | int | `8888` | 内部 HTTP:`/healthz`、`/readyz`、`/livez`、`/metrics` 和 Peer 代理 |
@@ -60,6 +61,7 @@ debug: false
该端点没有认证。应保持内部可见;Agent TLS 连接和证书操作使用 8443 端口。
**示例:**
+
```bash
rlark-server \
--https-port=8443 \
@@ -74,7 +76,7 @@ rlark-server \
API 网关。处理所有 REST API 请求,包括集群管理、任务管理和存储操作。
| 参数 | 类型 | 默认值 | 说明 |
-|------|------|--------|------|
+| ------ | ------ | -------- | ------ |
| `--addr` | string | `:8080` | API 网关绑定地址;`rlarkadm` 会覆盖为 `:8090` |
| `--db-config` | string | `""` | 数据库配置文件路径 |
| `--server-address` | string | `https://rlark-server.rlark-system.svc:8443` | 证书签名的 RLark Server 地址 |
@@ -87,6 +89,7 @@ API 网关。处理所有 REST API 请求,包括集群管理、任务管理和
| `--kube-timeout` | duration | `0` | Kubernetes 客户端请求超时 |
**示例:**
+
```bash
rlark-gateway \
--addr=:8080 \
@@ -99,7 +102,7 @@ rlark-gateway \
控制器管理器。调和 Job、Workflow 和 Domain 资源。
| 参数 | 类型 | 默认值 | 说明 |
-|------|------|--------|------|
+| ------ | ------ | -------- | ------ |
| `--server-address` | string | `https://rlark-server.rlark-system.svc:8443` | RLark Server 地址 |
| `--db-config` | string | `""` | 数据库配置文件路径 |
| `--leader-elect` | bool | `true` | 启用 Leader Election(高可用) |
@@ -119,6 +122,7 @@ rlark-gateway \
单实例部署时建议设置 `--leader-elect=false` 以避免不必要的选举开销。
**示例:**
+
```bash
rlark-controller-manager \
--server-address=https://rlark-server:8443 \
@@ -133,7 +137,7 @@ rlark-controller-manager \
数据面 Agent。部署在每个集群或节点上。管理节点注册、Task 执行和跨集群网络。
| 参数 | 类型 | 默认值 | 说明 |
-|------|------|--------|------|
+| ------ | ------ | -------- | ------ |
| `--server-address` | string | `https://localhost:8443` | RLark Server 地址 |
| `--server-hostname` | string | `""` | 服务器 TLS 预期主机名 |
| `--client-cert` | string | `""` | 客户端 TLS 证书路径 |
@@ -171,6 +175,7 @@ rlark-controller-manager \
- `both`:同时运行集群和节点级 Agent
**示例:**
+
```bash
rlark-agent \
--mode=both \
@@ -187,7 +192,7 @@ rlark-agent \
网络 Sidecar。与每个 Task Pod 一起运行,通过 TUN 设备和 gVisor netstack 实现跨集群 Pod 到 Pod 网络通信。
| 参数 | 类型 | 默认值 | 说明 |
-|------|------|--------|------|
+| ------ | ------ | -------- | ------ |
| `--sidecar-unix-socket` | string | `/var/run/rlark/nodeserver.sock` | NodeServer Unix Socket 路径 |
| `--sidecar-tun-name` | string | `gnet0` | TUN 设备名称 |
| `--sidecar-tun-mtu` | int | `1500` | TUN 设备 MTU |
@@ -197,6 +202,7 @@ rlark-agent \
| `--sidecar-hosts-file` | string | `/etc/hosts` | hosts 文件路径 |
**示例:**
+
```bash
rlark-network-sidecar \
--sidecar-unix-socket=/var/run/rlark/nodeserver.sock \
@@ -209,14 +215,14 @@ rlark-network-sidecar \
SSH 守护进程。提供对运行中 Task Pod 的 SSH 访问。已集成到 rlark-server 中(通过 `--ssh-port` 参数)。
| 参数 | 类型 | 默认值 | 说明 |
-|------|------|--------|------|
+| ------ | ------ | -------- | ------ |
| `--port` | string | `22` | SSH 监听端口 |
| `--shell` | string | `""` | Shell 二进制路径(默认 /bin/bash) |
**环境变量:**
| 变量 | 说明 |
-|------|------|
+| ------ | ------ |
| `RLARK_SSH_PUBLIC_KEY` | 用于 authorized_keys 的 SSH 公钥 |
| `RLARK_SSH_AUTHORIZED_KEYS_FILE` | authorized_keys 文件路径 |
@@ -225,7 +231,7 @@ SSH 守护进程。提供对运行中 Task Pod 的 SSH 访问。已集成到 rla
Gateway 使用的对象存储后端配置。
| 字段 | 类型 | 默认值 | 说明 |
-|------|------|--------|------|
+| ------ | ------ | -------- | ------ |
| `accessKeyId` | string | `""` | 访问密钥 ID |
| `secretAccessKey` | string | `""` | 访问密钥 Secret |
| `bucket` | string | `""` | Bucket 名称 |
@@ -246,7 +252,7 @@ Gateway 使用的对象存储后端配置。
下表名称是 `rlarkadm` 接受的准确 YAML 键名。
| 字段 | 类型 | 默认值 | 说明 |
-|------|------|--------|------|
+| ------ | ------ | -------- | ------ |
| `apiVersion` | string | — | API 版本(必填);仓库示例使用 `rlark.io/v1alpha1` |
| `kind` | string | — | 必须为 `DeployConfig` |
| `plane` | string | — | 必填:`control`(控制面)或 `data`(数据面) |
@@ -264,7 +270,7 @@ Gateway 使用的对象存储后端配置。
### DBConfig
| 字段 | 类型 | 默认值 | 说明 |
-|------|------|--------|------|
+| ------ | ------ | -------- | ------ |
| `host` | string | `postgresql` | PostgreSQL 主机 |
| `port` | int | `5432` | PostgreSQL 端口 |
| `database` | string | `rlark` | 数据库名 |
@@ -274,7 +280,7 @@ Gateway 使用的对象存储后端配置。
### KubernetesEnv
| 字段 | 类型 | 默认值 | 说明 |
-|------|------|--------|------|
+| ------ | ------ | -------- | ------ |
| `kubeconfig` | string | `""` | kubeconfig 文件路径;空值使用 client-go 常规加载规则 |
| `gateway-image` | string | `""` | Gateway 镜像 |
| `controller-manager-image` | string | `""` | Controller Manager 镜像 |
@@ -298,7 +304,7 @@ Gateway 使用的对象存储后端配置。
### DockerEnv
| 字段 | 类型 | 说明 |
-|------|------|------|
+| ------ | ------ | ------ |
| `gateway-image` | string | Gateway 镜像 |
| `controller-manager-image` | string | Controller Manager 镜像 |
| `server-image` | string | Server 镜像 |
@@ -315,7 +321,7 @@ Gateway 使用的对象存储后端配置。
Raw 部署模式目前处于实验阶段,建议优先使用 Kubernetes 或 Docker 部署。
| 字段 | 类型 | 说明 |
-|------|------|------|
+| ------ | ------ | ------ |
| `gateway-artifact` | string | Gateway 二进制路径 |
| `controller-manager-artifact` | string | Controller Manager 二进制路径 |
| `server-artifact` | string | Server 二进制路径 |
@@ -330,7 +336,7 @@ Gateway 使用的对象存储后端配置。
数据面部署时必须提供。
| 字段 | 类型 | 说明 |
-|------|------|------|
+| ------ | ------ | ------ |
| `ca-cert` | string | 内联 CA PEM 或已存在的文件路径 |
| `agent-cert` | string | 内联 Agent 证书 PEM 或已存在的文件路径 |
| `agent-key` | string | 内联 Agent 私钥 PEM 或已存在的文件路径 |
@@ -338,7 +344,7 @@ Gateway 使用的对象存储后端配置。
### StorageConfig
| 字段 | 类型 | 默认值 | 说明 |
-|------|------|--------|------|
+| ------ | ------ | -------- | ------ |
| `type` | string | | 存储类型:emptyDir、hostPath、pvc |
| `host-path` | string | `""` | hostPath 类型的主机路径 |
| `storage-class` | string | `""` | PVC 类型的 StorageClass;空值使用集群默认值 |
@@ -348,19 +354,20 @@ Gateway 使用的对象存储后端配置。
### ComponentConfig
| 字段 | 类型 | 说明 |
-|------|------|------|
+| ------ | ------ | ------ |
| `replicas` | int | 副本数 |
| `storage` | StorageConfig | 存储配置 |
### EtcdConfig
| 字段 | 类型 | 说明 |
-|------|------|------|
+| ------ | ------ | ------ |
| `address` | string | etcd 地址 |
| `replicas` | int | 副本数 |
| `storage` | StorageConfig | 存储配置 |
**示例(控制面):**
+
```yaml
apiVersion: rlark.io/v1alpha1
kind: DeployConfig
@@ -382,6 +389,7 @@ db:
```
**示例(数据面):**
+
```yaml
apiVersion: rlark.io/v1alpha1
kind: DeployConfig
diff --git a/apps/rlark/docs/zh/user-guide/best-practices.md b/apps/rlark/docs/zh/user-guide/best-practices.md
index bb0f1dd..207b711 100644
--- a/apps/rlark/docs/zh/user-guide/best-practices.md
+++ b/apps/rlark/docs/zh/user-guide/best-practices.md
@@ -121,7 +121,7 @@ ssh-keygen -t ed25519 -C "rlark-training"
1. 进入 **任务** → **创建任务**
2. **填写基本信息**:
- - 任务名称:小写字母、数字、连字符(如 `my-training-run-001`)
+ - 任务名称:不超过 128 个字符的展示名;展示名可以重复,RLark 会另外分配系统资源 ID
- 任务类型:单节点选 **自定义任务**,多角色可选用模板
3. **配置 Worker 角色**:
@@ -150,7 +150,7 @@ ssh-keygen -t ed25519 -C "rlark-training"
| 设置 | 说明 |
|------|------|
| **Header 角色** | 必须是恰好有 1 个 Worker 的角色 |
- | **网络域** | 如需跨集群通信则选择 |
+ | **网络域** | 自动使用按名称排序后的第一个已配置网络域;未配置时不写入 |
| **SSH 公钥** | 选择注入 Worker 的公钥 |
| **运行脚本** | Ray 就绪后执行的主命令 |
| **TensorBoard 目录** | 容器内 TensorBoard 日志路径 |
@@ -220,4 +220,4 @@ ssh-keygen -t ed25519 -C "rlark-training"
- [创建训练任务](jobs.md) — 详细字段参考
- [工作流](workflows.md) — 多角色和异构 Worker
- [在任务中使用存储](storage.md) — hostPath 和对象存储配置
-- [通过 SSH 连接 Worker](ssh-keys.md) — SSH 到运行中的 Worker
\ No newline at end of file
+- [通过 SSH 连接 Worker](ssh-keys.md) — SSH 到运行中的 Worker
diff --git a/apps/rlark/docs/zh/user-guide/jobs.md b/apps/rlark/docs/zh/user-guide/jobs.md
index 9f51fc7..3e71444 100644
--- a/apps/rlark/docs/zh/user-guide/jobs.md
+++ b/apps/rlark/docs/zh/user-guide/jobs.md
@@ -54,7 +54,7 @@ RLark 支持以下任务类型,每种类型预配置了适合该工作负载
- **存储挂载** — 向 Worker 容器挂载存储。支持两种类型:
- **hostPath**:挂载宿主机节点上的目录,任务生命周期操作不会删除其中的数据。
- - **PVC**(PersistentVolumeClaim):挂载 Kubernetes 持久卷。停止、重启或删除任务会删除任务 PVC;启动或重启会新建空 PVC。
+ - **PVC**(PersistentVolumeClaim):使用所选存储类挂载 Kubernetes 持久卷。停止、重启或删除任务会删除任务 PVC;启动或重启会新建空 PVC。

@@ -64,7 +64,7 @@ RLark 支持以下任务类型,每种类型预配置了适合该工作负载
- **Header 角色** — 选择一个角色作为 Header。该角色的第一个 Worker 协调分布式训练,其 IP 地址会通知所有其他 Worker。
-- **跨集群网络域** — 当任务跨多个集群时,选择预先配置的网络域。这通过 RLark 虚拟网络(TUN + gVisor + SSH 隧道)实现跨集群 Pod 间网络通信。
+- **跨集群网络域** — 如果后台配置了网络域,控制台会按名称选择第一个网络域,并为所有任务自动启用,不受 Worker 是否跨集群影响;未配置时不会写入网络域。
- **SSH 公钥** — 提供一个或多个将被注入到每个 Worker 容器 `~/.ssh/authorized_keys` 的 SSH 公钥。这允许你通过 SSH 进入运行中的容器进行调试。
@@ -78,7 +78,7 @@ RLark 支持以下任务类型,每种类型预配置了适合该工作负载
从任务列表打开任务详情页,查看概况:
-- **名称** — 任务名称(集群内唯一)
+- **名称** — 面向用户的展示名,可以重复;RLark 会另外分配 `jo-<16 位十六进制字符>` 系统资源 ID。
- **类型** — 任务类型(强化学习、数据采集、评测、自定义)
- **状态** — 当前状态:Pending、Running、Succeeded、Failed、Stopped
- **Worker 数量** — 所有角色的 Worker 总数
diff --git a/apps/rlark/go.mod b/apps/rlark/go.mod
index 45b0bfe..a17dbad 100644
--- a/apps/rlark/go.mod
+++ b/apps/rlark/go.mod
@@ -3,6 +3,7 @@ module github.com/rlinf/rlark/apps/rlark
go 1.26.3
require (
+ github.com/aliyun/aliyun-log-go-sdk v0.1.127
github.com/aws/aws-sdk-go-v2 v1.43.3
github.com/aws/aws-sdk-go-v2/config v1.32.34
github.com/aws/aws-sdk-go-v2/credentials v1.19.33
@@ -12,6 +13,7 @@ require (
github.com/charmbracelet/ssh v0.0.0-20250128164007-98fd5ae11894
github.com/charmbracelet/wish v1.4.7
github.com/containerd/containerd v1.7.25
+ github.com/creack/pty v1.1.21
github.com/gin-gonic/gin v1.12.0
github.com/go-logr/logr v1.4.3
github.com/go-logr/zapr v1.3.0
@@ -27,6 +29,7 @@ require (
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.9
+ github.com/stretchr/testify v1.11.1
github.com/uptrace/bun v1.2.18
github.com/uptrace/bun/dialect/pgdialect v1.2.18
github.com/uptrace/bun/driver/pgdriver v1.2.18
@@ -73,6 +76,7 @@ require (
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
+ github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/keygen v0.5.3 // indirect
@@ -93,7 +97,6 @@ require (
github.com/containerd/platforms v0.2.1 // indirect
github.com/containerd/ttrpc v1.2.5 // indirect
github.com/containerd/typeurl/v2 v2.1.1 // indirect
- github.com/creack/pty v1.1.21 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
@@ -106,6 +109,7 @@ require (
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
+ github.com/go-kit/kit v0.10.0 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
@@ -118,6 +122,7 @@ require (
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
+ github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
@@ -151,6 +156,7 @@ require (
github.com/opencontainers/runtime-spec v1.1.0 // indirect
github.com/opencontainers/selinux v1.11.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
+ github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
@@ -175,6 +181,7 @@ require (
go.opentelemetry.io/otel v1.41.0 // indirect
go.opentelemetry.io/otel/metric v1.41.0 // indirect
go.opentelemetry.io/otel/trace v1.41.0 // indirect
+ go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.22.0 // indirect
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
@@ -189,6 +196,7 @@ require (
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
+ gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.36.0 // indirect
k8s.io/klog/v2 v2.140.0 // indirect
diff --git a/apps/rlark/go.sum b/apps/rlark/go.sum
index 153fbe9..cbcf357 100644
--- a/apps/rlark/go.sum
+++ b/apps/rlark/go.sum
@@ -1,19 +1,64 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA=
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ=
github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU=
+github.com/Netflix/go-env v0.0.0-20220526054621-78278af1949d h1:wvStE9wLpws31NiWUx+38wny1msZ/tm+eL5xmm4Y7So=
+github.com/Netflix/go-env v0.0.0-20220526054621-78278af1949d/go.mod h1:9XMFaCeRyW7fC9XJOWQ+NdAv8VLG7ys7l3x4ozEGLUQ=
+github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
+github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
+github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
+github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo=
+github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
+github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4 h1:7Q2FEyqxeZeIkwYMwRC3uphxV4i7O2eV4ETe21d6lS4=
+github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
+github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 h1:NqugFkGxx1TXSh/pBcU00Y6bljgDPaFdh5MUSeJ7e50=
+github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
+github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
+github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
+github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY=
+github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
+github.com/alibabacloud-go/sts-20150401/v2 v2.0.1 h1:CevZp0VdG7Q+1J3qwNj+JL7ztKxsL27+tknbdTK9Y6M=
+github.com/alibabacloud-go/sts-20150401/v2 v2.0.1/go.mod h1:8wJW1xC4mVcdRXzOvWJYfCCxmvFzZ0VB9iilVjBeWBc=
+github.com/alibabacloud-go/tea v1.1.19 h1:Xroq0M+pr0mC834Djj3Fl4ZA8+GGoA0i7aWse1vmgf4=
+github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
+github.com/alibabacloud-go/tea-utils v1.3.1 h1:iWQeRzRheqCMuiF3+XkfybB3kTgUXkXX+JMrqfLeB2I=
+github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.1 h1:K6kwgo+UiYx+/kr6CO0PN5ACZDzE3nnn9d77215AkTs=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.1/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
+github.com/alibabacloud-go/tea-xml v1.1.2 h1:oLxa7JUXm2EDFzMg+7oRsYc+kutgCVwm+bZlhhmvW5M=
+github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
+github.com/aliyun/aliyun-log-go-sdk v0.1.127 h1:+5OIyNoW+PZ1ap8EpM3HObu8ALy0q85QCdKCxc4PAFE=
+github.com/aliyun/aliyun-log-go-sdk v0.1.127/go.mod h1:eZJ4GntkHD89i+tdlW/5gvLkBw5QFaFfP9gG/5shj5E=
+github.com/aliyun/credentials-go v1.1.2 h1:qU1vwGIBb3UJ8BwunHDRFtAhS6jnQLnde/yk0+Ih2GY=
+github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
+github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
+github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
+github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
+github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/aws/aws-sdk-go-v2 v1.43.3 h1:XJIcfv8uDs2ukdQsoAC8/Ebu1ejxwzlayl2ZsiFns2A=
github.com/aws/aws-sdk-go-v2 v1.43.3/go.mod h1:70vwSy16txshwG+g55WkpgPKDIByzHI8ccBsOteo3bQ=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.16 h1:aiuaKlDweRC5qExJondpWjOgyzMHpofpwspGXUtwn4c=
@@ -52,15 +97,22 @@ github.com/aws/smithy-go v1.27.6 h1:0zjT8jgK3jbrTT7JJ3EE6JsMhX8JTrZ+f1sEndYDXrA=
github.com/aws/smithy-go v1.27.6/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
+github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
+github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
+github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI=
@@ -89,10 +141,15 @@ github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQ
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/charmbracelet/x/termios v0.1.0 h1:y4rjAHeFksBAfGbkRDmVinMg7x7DELIGAFbdNvxg97k=
github.com/charmbracelet/x/termios v0.1.0/go.mod h1:H/EVv/KRnrYjz+fCYa9bsKdqF3S8ouDK0AZEbG7r+/U=
+github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
+github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
+github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
+github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
github.com/containerd/containerd v1.7.25 h1:khEQOAXOEJalRO228yzVsuASLH42vT7DIo9Ss+9SMFQ=
@@ -113,7 +170,12 @@ github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oL
github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o=
github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4=
github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
+github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
@@ -121,12 +183,19 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
+github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -137,20 +206,32 @@ github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
+github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
+github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -176,20 +257,30 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
+github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
+github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -201,6 +292,9 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
@@ -219,25 +313,72 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
+github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
+github.com/jarcoal/httpmock v1.4.0 h1:BvhqnH0JAYbNudL2GMJKgOHe2CtKlzJ/5rWKyp+hc2k=
+github.com/jarcoal/httpmock v1.4.0/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -249,20 +390,36 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
+github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/looplab/fsm v1.0.3 h1:qtxBsa2onOs0qFOtkqwf5zE0uP0+Te+wlIvXctPKpcw=
github.com/looplab/fsm v1.0.3/go.mod h1:PmD3fFvQEIsjMEfvZdrCDZ6y8VwKTwWNjlpEr6IKPO4=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
+github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
+github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y=
@@ -280,6 +437,8 @@ github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcY
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
@@ -291,10 +450,26 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
+github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
+github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
+github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
+github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
+github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
+github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
+github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo/v2 v2.27.4 h1:fcEcQW/A++6aZAZQNUmNjvA9PSOzefMJBerHJ4t8v8Y=
github.com/onsi/ginkgo/v2 v2.27.4/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q=
github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
+github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
@@ -303,22 +478,57 @@ github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bl
github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
+github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
+github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
+github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
+github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
+github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
+github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
+github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
+github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
+github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
+github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
+github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
+github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
@@ -329,26 +539,48 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/rancher/remotedialer v0.6.1 h1:smq2sHKJn+NxxIeQ8To9CGGkz8l6Ir7EPzfAdjUTt2s=
github.com/rancher/remotedialer v0.6.1/go.mod h1:0+dmsw9TPjcqNUPrgAVFZpvbxy1r/fRaGPpVa84OMjU=
+github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
+github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
+github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
@@ -356,6 +588,9 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM=
+github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@@ -370,6 +605,8 @@ github.com/uptrace/bun/driver/pgdriver v1.2.18 h1:Zojuc83ulApocXomBLEcx1DqCZweRE
github.com/uptrace/bun/driver/pgdriver v1.2.18/go.mod h1:ZRJcARw93nxbQ5WawTrc5EO+F+GygkcYgDLEnT17CcE=
github.com/uptrace/bun/extra/bundebug v1.2.18 h1:5cgkqdvhpSHIEONazSytm4RWYFneNtcznaWLt6r8m4M=
github.com/uptrace/bun/extra/bundebug v1.2.18/go.mod h1:M+U9YJVJcmk0RrszCb2Q1oskJiJ0LuC44FxDhZLP1ws=
+github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
+github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
@@ -380,14 +617,20 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xjasonlyu/tun2socks/v2 v2.6.0 h1:gI9saJT3XgH4e6v9jBuHRLwK7l3aN9YFWec/SsDTDx4=
github.com/xjasonlyu/tun2socks/v2 v2.6.0/go.mod h1:35AwqxIxnMkfBfT0UJ1Lku7PZm2ZiZJ8sxHyp0gt1yw=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
+go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
@@ -404,12 +647,21 @@ go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4A
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
+go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
+go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
@@ -418,7 +670,11 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
@@ -428,35 +684,64 @@ golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1i
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -468,17 +753,28 @@ golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
+golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
@@ -491,18 +787,30 @@ gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
+google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg=
google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
@@ -520,20 +828,38 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=
+gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20250523182742-eede7a881b20 h1:0DxLu8hxI1OGp1qVRPqNd+2k1a7hMNUNqbZG0IrtKlM=
gvisor.dev/gvisor v0.0.0-20250523182742-eede7a881b20/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
+honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
k8s.io/api v0.36.3 h1:NxB+05W2UGqXWFXcLO0RB5cnqnUPP5v5sVlaOH0Iz4w=
k8s.io/api v0.36.3/go.mod h1:JzLQKqRHC5+I8RVj/lS3lCg0mg6nWI9Fo/Sk3ElxHzg=
k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0=
@@ -560,5 +886,7 @@ sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.3 h1:u08YRbVUi59ri4YD6cg0UqNM4Dimn0sIl+wldcx5PYw=
sigs.k8s.io/structured-merge-diff/v6 v6.3.3/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
+sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
+sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
diff --git a/apps/rlark/pkg/addons/catalog/csi-driver-rclone/addon.yaml b/apps/rlark/pkg/addons/catalog/csi-driver-rclone/addon.yaml
index 581c599..06ac933 100644
--- a/apps/rlark/pkg/addons/catalog/csi-driver-rclone/addon.yaml
+++ b/apps/rlark/pkg/addons/catalog/csi-driver-rclone/addon.yaml
@@ -43,6 +43,15 @@ parameters:
default: rclone.csi.veloxpack.io
required: true
+ - name: nodeSelector
+ displayName: 节点选择器
+ description: |
+ Node DaemonSet 的节点选择器(label=value 格式)。建议先指定单个灰度节点,验证通过后再扩展标签范围。
+ 示例: rlark.io/rclone-csi=enabled
+ type: string
+ default: ""
+ required: false
+
- name: controllerReplicas
displayName: Controller 副本数
description: Controller Deployment 副本数量
diff --git a/apps/rlark/pkg/addons/catalog/csi-driver-rclone/manifests/controller.yaml b/apps/rlark/pkg/addons/catalog/csi-driver-rclone/manifests/controller.yaml
index fe805c4..2cac72b 100644
--- a/apps/rlark/pkg/addons/catalog/csi-driver-rclone/manifests/controller.yaml
+++ b/apps/rlark/pkg/addons/catalog/csi-driver-rclone/manifests/controller.yaml
@@ -32,35 +32,39 @@ spec:
image: {{ .Values.rcloneImage }}
imagePullPolicy: Always
args:
- - "--driver-name=$(DRIVER_NAME)"
- - "--node-id=$(NODE_ID)"
- - "--endpoint=unix://csi/csi.sock"
- - "--v={{ .Values.controllerLogLevel }}"
+ - "-v={{ .Values.controllerLogLevel }}"
+ - "--nodeid=$(NODE_ID)"
+ - "--endpoint=$(CSI_ENDPOINT)"
env:
- - name: DRIVER_NAME
- value: {{ .Values.driverName }}
- name: NODE_ID
valueFrom:
fieldRef:
fieldPath: spec.nodeName
+ - name: CSI_ENDPOINT
+ value: unix:///csi/csi.sock
securityContext:
privileged: true
volumeMounts:
- name: socket-dir
mountPath: /csi
- - name: kubelet-dir
- mountPath: /var/lib/kubelet
- mountPropagation: Bidirectional
- name: csi-provisioner
image: {{ .Values.csiProvisionerImage }}
imagePullPolicy: Always
args:
+ - "-v={{ .Values.controllerLogLevel }}"
- "--csi-address=$(ADDRESS)"
- - "--v={{ .Values.controllerLogLevel }}"
- - "--feature-gates=Topology=true"
+ - "--leader-election"
+ - "--leader-election-namespace=$(POD_NAMESPACE)"
+ - "--extra-create-metadata=true"
+ - "--timeout=1200s"
+ - "--retry-interval-max=30m"
env:
- name: ADDRESS
value: /csi/csi.sock
+ - name: POD_NAMESPACE
+ valueFrom:
+ fieldRef:
+ fieldPath: metadata.namespace
volumeMounts:
- name: socket-dir
mountPath: /csi
@@ -69,7 +73,8 @@ spec:
imagePullPolicy: Always
args:
- "--csi-address=/csi/csi.sock"
- - "--health-timeout=30s"
+ - "--probe-timeout=3s"
+ - "--http-endpoint=localhost:29652"
- "--v={{ .Values.controllerLogLevel }}"
volumeMounts:
- name: socket-dir
@@ -77,7 +82,3 @@ spec:
volumes:
- name: socket-dir
emptyDir: {}
- - name: kubelet-dir
- hostPath:
- path: /var/lib/kubelet
- type: Directory
diff --git a/apps/rlark/pkg/addons/catalog/csi-driver-rclone/manifests/node.yaml b/apps/rlark/pkg/addons/catalog/csi-driver-rclone/manifests/node.yaml
index 19f52c7..825d78f 100644
--- a/apps/rlark/pkg/addons/catalog/csi-driver-rclone/manifests/node.yaml
+++ b/apps/rlark/pkg/addons/catalog/csi-driver-rclone/manifests/node.yaml
@@ -6,6 +6,10 @@ metadata:
labels:
app: csi-driver-rclone-node
spec:
+ updateStrategy:
+ type: RollingUpdate
+ rollingUpdate:
+ maxUnavailable: 1
selector:
matchLabels:
app: csi-driver-rclone-node
@@ -14,8 +18,17 @@ spec:
labels:
app: csi-driver-rclone-node
spec:
+ hostNetwork: true
+ dnsPolicy: ClusterFirstWithHostNet
serviceAccountName: csi-driver-rclone
priorityClassName: system-cluster-critical
+ {{- if .Values.nodeSelector }}
+ nodeSelector:
+ {{- $parts := splitList "=" .Values.nodeSelector }}
+ {{- if eq (len $parts) 2 }}
+ {{ index $parts 0 }}: {{ index $parts 1 }}
+ {{- end }}
+ {{- end }}
tolerations:
- operator: Exists
containers:
@@ -23,28 +36,29 @@ spec:
image: {{ .Values.rcloneImage }}
imagePullPolicy: Always
args:
- - "--driver-name=$(DRIVER_NAME)"
- - "--node-id=$(NODE_ID)"
- - "--endpoint=unix://csi/csi.sock"
- - "--v={{ .Values.nodeLogLevel }}"
+ - "-v={{ .Values.nodeLogLevel }}"
+ - "--nodeid=$(NODE_ID)"
+ - "--endpoint=$(CSI_ENDPOINT)"
env:
- - name: DRIVER_NAME
- value: {{ .Values.driverName }}
- name: NODE_ID
valueFrom:
fieldRef:
fieldPath: spec.nodeName
+ - name: CSI_ENDPOINT
+ value: unix:///csi/csi.sock
securityContext:
privileged: true
+ livenessProbe:
+ httpGet:
+ host: localhost
+ path: /healthz
+ port: 29653
+ initialDelaySeconds: 30
+ timeoutSeconds: 10
+ periodSeconds: 30
volumeMounts:
- - name: socket-dir
- mountPath: /csi
- - name: kubelet-dir
- mountPath: /var/lib/kubelet
- mountPropagation: Bidirectional
- name: plugin-dir
- mountPath: /var/lib/kubelet/plugins
- mountPropagation: Bidirectional
+ mountPath: /csi
- name: mount-dir
mountPath: /var/lib/kubelet/pods
mountPropagation: Bidirectional
@@ -61,7 +75,7 @@ spec:
- name: DRIVER_REG_SOCK_PATH
value: /var/lib/kubelet/plugins/{{ .Values.driverName }}/csi.sock
volumeMounts:
- - name: socket-dir
+ - name: plugin-dir
mountPath: /csi
- name: registration-dir
mountPath: /registration
@@ -70,21 +84,16 @@ spec:
imagePullPolicy: Always
args:
- "--csi-address=/csi/csi.sock"
- - "--health-timeout=30s"
+ - "--probe-timeout=3s"
+ - "--http-endpoint=localhost:29653"
- "--v={{ .Values.nodeLogLevel }}"
volumeMounts:
- - name: socket-dir
+ - name: plugin-dir
mountPath: /csi
volumes:
- - name: socket-dir
- emptyDir: {}
- - name: kubelet-dir
- hostPath:
- path: /var/lib/kubelet
- type: Directory
- name: plugin-dir
hostPath:
- path: /var/lib/kubelet/plugins
+ path: /var/lib/kubelet/plugins/{{ .Values.driverName }}
type: DirectoryOrCreate
- name: mount-dir
hostPath:
diff --git a/apps/rlark/pkg/addons/catalog/csi-driver-rclone/manifests/rbac.yaml b/apps/rlark/pkg/addons/catalog/csi-driver-rclone/manifests/rbac.yaml
index d3ec745..2914785 100644
--- a/apps/rlark/pkg/addons/catalog/csi-driver-rclone/manifests/rbac.yaml
+++ b/apps/rlark/pkg/addons/catalog/csi-driver-rclone/manifests/rbac.yaml
@@ -21,6 +21,9 @@ rules:
- apiGroups: ["storage.k8s.io"]
resources: ["volumeattachments/status"]
verbs: ["get", "list", "watch", "update", "patch"]
+ - apiGroups: ["coordination.k8s.io"]
+ resources: ["leases"]
+ verbs: ["get", "list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
diff --git a/apps/rlark/pkg/addons/catalog/fluent-bit/addon.yaml b/apps/rlark/pkg/addons/catalog/fluent-bit/addon.yaml
new file mode 100644
index 0000000..97026c6
--- /dev/null
+++ b/apps/rlark/pkg/addons/catalog/fluent-bit/addon.yaml
@@ -0,0 +1,101 @@
+name: fluent-bit
+displayName: Fluent Bit 日志采集
+category: logging
+version: v0.1.0
+description: |
+ 部署 Fluent Bit DaemonSet,采集集群内 Pod 的 stdout/stderr 日志并写入外部日志后端。
+ 日志会自动带上 cluster_id / job / task / pod / namespace 等标签,便于在 rlark 平台或后端控制台查询。
+ 目前支持阿里云 SLS,后续会扩展到 Loki / Elasticsearch 等后端。
+icon: file-text
+
+parameters:
+ - name: backend
+ displayName: 日志后端
+ description: 日志写入的后端类型。目前仅支持 sls(阿里云日志服务),后续会支持 loki / es。
+ type: enum
+ options: ["sls"]
+ default: sls
+ required: true
+
+ # ---- 通用日志后端配置 ----
+ - name: endpoint
+ displayName: 接入地址 (Endpoint/Brokers)
+ description: |
+ 日志后端的连接地址。
+ - SLS: Kafka 接入地址,格式:.:(公网 10012,私网 10011)。
+ - Loki/ES: HTTP Endpoint(如 http://loki:3100 或 https://es:9200)。
+ type: string
+ default: ""
+ required: true
+
+ - name: project
+ displayName: 项目/组织名 (Project/Tenant)
+ description: |
+ 资源所属域或租户标识。
+ - SLS: Project 名称(作为 Kafka SASL 认证的 username)。
+ - Loki: Tenant ID。
+ - ES: 组织/命名空间前缀(可选)。
+ type: string
+ default: ""
+ required: false
+
+ - name: logstore
+ displayName: 日志库/索引名 (Logstore/Index)
+ description: |
+ 具体存放日志的库、表或索引。
+ - SLS: Logstore 名称(作为 Kafka topic)。
+ - Loki/ES: Index 名称。
+ type: string
+ default: ""
+ required: true
+
+ - name: accessKeyId
+ displayName: 认证 ID (Access Key ID / Username)
+ description: |
+ 认证用户名或 Access Key ID。
+ - SLS: 阿里云 AK(需有 SLS 写入权限)。
+ - Loki/ES: Basic Auth Username 或 API Key ID。
+ type: string
+ default: ""
+ required: false
+
+ - name: accessKeySecret
+ displayName: 认证 Secret (Access Key Secret / Password)
+ description: |
+ 认证密码或 Access Key Secret。
+ - SLS: 阿里云 SK(需有 SLS 写入权限)。
+ - Loki/ES: Basic Auth Password 或 API Key Secret。
+ type: string
+ default: ""
+ required: false
+
+ # ---- 公共配置 ----
+ - name: clusterId
+ displayName: 集群 ID
+ description: |
+ 写入日志的 cluster_id 标签值,用于多集群日志区分。
+ 通常填集群在 rlark 平台上的唯一标识。
+ type: string
+ default: ""
+ required: true
+
+ - name: image
+ displayName: Fluent Bit 镜像
+ description: Fluent Bit 镜像地址。官方 fluent/fluent-bit 镜像自带 out_kafka 插件即可。
+ type: string
+ default: fluent/fluent-bit:3.1.8
+ required: true
+
+ - name: cpuLimit
+ displayName: CPU 限额
+ description: Fluent Bit 容器 CPU 限额
+ type: string
+ default: 200m
+ required: false
+
+ - name: memoryLimit
+ displayName: 内存限额
+ description: Fluent Bit 容器内存限额
+ type: string
+ default: 256Mi
+ required: false
\ No newline at end of file
diff --git a/apps/rlark/pkg/addons/catalog/fluent-bit/manifests/configmap.yaml b/apps/rlark/pkg/addons/catalog/fluent-bit/manifests/configmap.yaml
new file mode 100644
index 0000000..8cd2a54
--- /dev/null
+++ b/apps/rlark/pkg/addons/catalog/fluent-bit/manifests/configmap.yaml
@@ -0,0 +1,91 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: fluent-bit-config
+ namespace: {{ .Namespace }}
+ labels:
+ app.kubernetes.io/name: fluent-bit
+ {{- range $k, $v := .AddonLabels }}
+ {{ $k }}: "{{ $v }}"
+ {{- end }}
+data:
+ fluent-bit.conf: |
+ [SERVICE]
+ Flush 5
+ Log_Level info
+ Daemon off
+ Parsers_File parsers.conf
+ HTTP_Server On
+ HTTP_Listen 0.0.0.0
+ HTTP_Port 2020
+
+ [INPUT]
+ Name tail
+ Tag kube.*
+ Path /var/log/containers/*_rlark-system_*.log
+ Exclude_Path /var/log/containers/fluent-bit-*.log
+ multiline.parser docker, cri
+ DB /fluent-bit/db/flb_kube.db
+ Mem_Buf_Limit 50MB
+ Skip_Long_Lines On
+ Refresh_Interval 10
+ Read_From_Head On
+
+ [FILTER]
+ Name kubernetes
+ Match kube.*
+ Kube_URL https://kubernetes.default.svc:443
+ Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token
+ Merge_Log On
+ Merge_Log_Key log_processed
+ K8S-Logging.Parser On
+ K8S-Logging.Exclude Off
+ Annotations On
+ Labels On
+ Buffer_Size 0
+
+ # Use Lua to accurately extract fields (including task annotation) and clean up raw structures
+ [FILTER]
+ Name lua
+ Match kube.*
+ Code function process_k8s(tag, timestamp, record) if record["kubernetes"] then local k8s = record["kubernetes"]; record["pod"] = k8s["pod_name"] or ""; record["namespace"] = k8s["namespace_name"] or ""; record["container"] = k8s["container_name"] or ""; record["node"] = k8s["host"] or ""; if k8s["annotations"] then record["task"] = k8s["annotations"]["rlark.io/management-task-name"] or "" end; record["kubernetes"] = nil; end; if record["log"] then record["content"] = record["log"]; record["log"] = nil; end; record["cluster_id"] = "{{ .Values.clusterId }}"; record["_p"] = nil; record["log_processed"] = nil; return 2, timestamp, record; end
+ Call process_k8s
+
+ [OUTPUT]
+ Name stdout
+ Match kube.*
+
+ {{ if eq .Values.backend "sls" }}
+ # Upload logs to Aliyun SLS via its Kafka-compatible endpoint.
+ # Endpoint format: .:. Public: port 10012,
+ # private (VPC): port 10011. Username is the SLS project name;
+ # password is "#" (see SLS docs:
+ # "使用Kafka协议上传日志").
+ [OUTPUT]
+ Name kafka
+ Match kube.*
+ Brokers {{ .Values.endpoint }}
+ Topics {{ .Values.logstore }}
+ Format json
+ Retry_Limit False
+ rdkafka.security.protocol SASL_SSL
+ rdkafka.sasl.mechanism PLAIN
+ rdkafka.sasl.username {{ .Values.project }}
+ rdkafka.sasl.password {{ .Values.accessKeyId }}#{{ .Values.accessKeySecret }}
+ {{ end }}
+
+ parsers.conf: |
+ [PARSER]
+ Name docker
+ Format json
+ Time_Key time
+ Time_Format %Y-%m-%dT%H:%M:%S.%L
+ Time_Keep On
+
+ [PARSER]
+ Name cri
+ Format regex
+ Regex ^(? |