1.30 k8s pods查询

This commit is contained in:
dengqichen 2025-12-14 00:25:33 +08:00
parent daeb880bff
commit 2a9b896f3f
5 changed files with 67 additions and 30 deletions

View File

@ -123,11 +123,24 @@ public class K8sDeploymentApiController extends BaseController<K8sDeployment, K8
@Operation( @Operation(
summary = "查询Pod日志引用点模式", summary = "查询Pod日志引用点模式",
description = "基于Kubernetes Dashboard的引用点系统查询日志支持无重复轮询和前后翻页。\n" + description = "基于Kubernetes Dashboard的引用点系统查询日志支持无重复轮询和前后翻页。\n\n" +
"初始加载referenceTimestamp=newest, offsetFrom=-100, offsetTo=1\n" + "**初始加载获取最新100行**\n" +
"轮询刷新使用上次返回的selection作为参数\n" + "- referenceTimestamp=newest\n" +
"向前翻页offsetFrom=-200, offsetTo=-100\n" + "- offsetFrom=-100\n" +
"向后翻页offsetFrom=100, offsetTo=200" "- offsetTo=0\n\n" +
"**轮询刷新(获取新日志)**\n" +
"- 使用上次返回的 referenceForNext.referenceTimestamp\n" +
"- 使用上次返回的 referenceForNext.offsetFrom (固定为0)\n" +
"- 使用上次返回的 referenceForNext.offsetTo (固定为100)\n\n" +
"**向前翻页(获取更早的日志)**\n" +
"- 使用上次返回的 referenceForPrevious.referenceTimestamp\n" +
"- 使用上次返回的 referenceForPrevious.offsetFrom (固定为-100)\n" +
"- 使用上次返回的 referenceForPrevious.offsetTo (固定为0)\n\n" +
"**响应字段说明**\n" +
"- referenceForPrevious: 用于向前翻页的选择器\n" +
"- referenceForNext: 用于向后翻页/轮询的选择器\n" +
"- logs: 当前返回的日志行列表\n" +
"- truncated: 是否因边界限制被截断"
) )
@GetMapping("/{deploymentId}/pods/{podName}/logs") @GetMapping("/{deploymentId}/pods/{podName}/logs")
public Response<com.qqchen.deploy.backend.deploy.dto.K8sPodLogsResponse> getPodLogs( public Response<com.qqchen.deploy.backend.deploy.dto.K8sPodLogsResponse> getPodLogs(

View File

@ -25,9 +25,16 @@ public class K8sPodLogsResponse {
private String containerName; private String containerName;
/** /**
* 日志选择器用于下次请求 * 用于向前翻页的引用点选择器
* 使用此选择器可以获取更早的日志
*/ */
private K8sLogSelection selection; private K8sLogSelection referenceForPrevious;
/**
* 用于向后翻页/轮询的引用点选择器
* 使用此选择器可以获取更新的日志轮询时使用
*/
private K8sLogSelection referenceForNext;
/** /**
* 日志行列表 * 日志行列表

View File

@ -627,7 +627,7 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
false, // previous是否查询上一个容器的日志 false, // previous是否查询上一个容器的日志
effectiveSinceSeconds, // sinceSeconds使用智能默认值 effectiveSinceSeconds, // sinceSeconds使用智能默认值
effectiveTail, // tail使用智能默认值 effectiveTail, // tail使用智能默认值
false // timestamps true // timestamps必须启用用于引用点系统
); );
int logLength = logs != null ? logs.length() : 0; int logLength = logs != null ? logs.length() : 0;

View File

@ -151,8 +151,8 @@ public class K8sPodServiceImpl implements IK8sPodService {
.orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND)); .orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND));
// 4. 调用K8s API获取原始日志获取足够多的日志以支持切片 // 4. 调用K8s API获取原始日志获取足够多的日志以支持切片
// 使用较大的tail值确保能够覆盖请求的范围 // 使用较大的tail值和缓冲区确保能够覆盖请求的范围
Integer effectiveTail = Math.max(1000, Math.abs(offsetFrom) + Math.abs(offsetTo) + 100); Integer effectiveTail = Math.max(2000, Math.abs(offsetFrom) + Math.abs(offsetTo) + 500);
String rawLogs = k8sServiceIntegration.getPodLogs( String rawLogs = k8sServiceIntegration.getPodLogs(
externalSystem, externalSystem,
namespace.getNamespaceName(), namespace.getNamespaceName(),
@ -180,7 +180,8 @@ public class K8sPodServiceImpl implements IK8sPodService {
return new com.qqchen.deploy.backend.deploy.dto.K8sPodLogsResponse( return new com.qqchen.deploy.backend.deploy.dto.K8sPodLogsResponse(
podName, podName,
container != null ? container : "default", container != null ? container : "default",
result.getSelection(), result.getReferenceForPrevious(),
result.getReferenceForNext(),
result.getLogs(), result.getLogs(),
result.isTruncated() result.isTruncated()
); );

View File

@ -23,11 +23,11 @@ public class K8sLogParser {
/** /**
* K8s日志时间戳正则表达式 * K8s日志时间戳正则表达式
* 匹配RFC3339格式2025-12-13T23:27:18.124567890Z * 匹配RFC3339格式2025-12-13T23:27:18.124567890Z
* 或常规格式2025-12-13 23:27:18.124 * 这是K8s API返回的标准格式当timestamps=true时
*/ */
private static final Pattern TIMESTAMP_PATTERN = Pattern.compile( private static final Pattern TIMESTAMP_PATTERN = Pattern.compile(
"^(\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}\\.\\d+Z?)\\s" "^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d+Z)\\s"
); );
/** /**
@ -131,14 +131,16 @@ public class K8sLogParser {
*/ */
public static LogSliceResult selectLogs(List<K8sLogLine> lines, K8sLogSelection selection) { public static LogSliceResult selectLogs(List<K8sLogLine> lines, K8sLogSelection selection) {
if (lines == null || lines.isEmpty()) { if (lines == null || lines.isEmpty()) {
return new LogSliceResult(Collections.emptyList(), selection, false); K8sLogSelection emptySelection = new K8sLogSelection("newest", -100, 0);
return new LogSliceResult(Collections.emptyList(), emptySelection, emptySelection, false);
} }
// 查找引用点索引 // 查找引用点索引
int referenceIndex = findReferenceIndex(lines, selection.getReferenceTimestamp()); int referenceIndex = findReferenceIndex(lines, selection.getReferenceTimestamp());
if (referenceIndex == -1) { if (referenceIndex == -1) {
log.warn("引用点未找到: {}", selection.getReferenceTimestamp()); log.warn("引用点未找到: {}", selection.getReferenceTimestamp());
return new LogSliceResult(Collections.emptyList(), selection, false); K8sLogSelection emptySelection = new K8sLogSelection("newest", -100, 0);
return new LogSliceResult(Collections.emptyList(), emptySelection, emptySelection, false);
} }
// 计算切片范围 // 计算切片范围
@ -164,22 +166,29 @@ public class K8sLogParser {
// 确保范围有效 // 确保范围有效
if (fromIndex >= toIndex || fromIndex < 0 || toIndex > lines.size()) { if (fromIndex >= toIndex || fromIndex < 0 || toIndex > lines.size()) {
log.warn("无效的切片范围: fromIndex={}, toIndex={}, size={}", fromIndex, toIndex, lines.size()); log.warn("无效的切片范围: fromIndex={}, toIndex={}, size={}", fromIndex, toIndex, lines.size());
return new LogSliceResult(Collections.emptyList(), selection, false); K8sLogSelection emptySelection = new K8sLogSelection("newest", -100, 0);
return new LogSliceResult(Collections.emptyList(), emptySelection, emptySelection, false);
} }
// 切片 // 切片
List<K8sLogLine> result = lines.subList(fromIndex, toIndex); List<K8sLogLine> result = lines.subList(fromIndex, toIndex);
// 创建新的引用点使用返回结果的中间行 // 创建两个引用点选择器
// 参考Kubernetes Dashboard: 使用实际返回日志的中间位置 // 1. 用于向前翻页使用返回结果的第一行作为引用点
int resultMiddleIndex = fromIndex + (toIndex - fromIndex) / 2; K8sLogSelection referenceForPrevious = new K8sLogSelection(
K8sLogSelection newSelection = new K8sLogSelection( lines.get(fromIndex).getTimestamp(),
lines.get(resultMiddleIndex).getTimestamp(), -100, // 向前100行
fromIndex - resultMiddleIndex, 0 // 到引用点不包含
toIndex - resultMiddleIndex
); );
return new LogSliceResult(new ArrayList<>(result), newSelection, truncated); // 2. 用于向后翻页/轮询使用返回结果的最后一行作为引用点
K8sLogSelection referenceForNext = new K8sLogSelection(
lines.get(toIndex - 1).getTimestamp(),
1, // 从引用点的下一行开始不包含引用点避免重复
101 // 向后100行
);
return new LogSliceResult(new ArrayList<>(result), referenceForPrevious, referenceForNext, truncated);
} }
/** /**
@ -187,12 +196,15 @@ public class K8sLogParser {
*/ */
public static class LogSliceResult { public static class LogSliceResult {
private final List<K8sLogLine> logs; private final List<K8sLogLine> logs;
private final K8sLogSelection selection; private final K8sLogSelection referenceForPrevious;
private final K8sLogSelection referenceForNext;
private final boolean truncated; private final boolean truncated;
public LogSliceResult(List<K8sLogLine> logs, K8sLogSelection selection, boolean truncated) { public LogSliceResult(List<K8sLogLine> logs, K8sLogSelection referenceForPrevious,
K8sLogSelection referenceForNext, boolean truncated) {
this.logs = logs; this.logs = logs;
this.selection = selection; this.referenceForPrevious = referenceForPrevious;
this.referenceForNext = referenceForNext;
this.truncated = truncated; this.truncated = truncated;
} }
@ -200,8 +212,12 @@ public class K8sLogParser {
return logs; return logs;
} }
public K8sLogSelection getSelection() { public K8sLogSelection getReferenceForPrevious() {
return selection; return referenceForPrevious;
}
public K8sLogSelection getReferenceForNext() {
return referenceForNext;
} }
public boolean isTruncated() { public boolean isTruncated() {