1.30 k8s pods查询

This commit is contained in:
dengqichen 2025-12-13 15:54:35 +08:00
parent 55c8428454
commit 81294bc1dc
14 changed files with 616 additions and 112 deletions

View File

@ -121,6 +121,37 @@ public class K8sDeploymentApiController extends BaseController<K8sDeployment, K8
return Response.success(k8sPodService.getPodDetailByDeployment(deploymentId, podName));
}
@Operation(summary = "查询Pod日志", description = "查询指定Pod的日志内容")
@GetMapping("/{deploymentId}/pods/{podName}/logs")
public Response<String> getPodLogs(
@Parameter(description = "Deployment ID", required = true) @PathVariable Long deploymentId,
@Parameter(description = "Pod名称", required = true) @PathVariable String podName,
@Parameter(description = "容器名称(可选,默认第一个容器)") @RequestParam(required = false) String container,
@Parameter(description = "返回最后N行日志可选") @RequestParam(required = false) Integer tail,
@Parameter(description = "返回最近N秒的日志可选") @RequestParam(required = false) Integer sinceSeconds
) {
return Response.success(k8sPodService.getPodLogs(deploymentId, podName, container, tail, sinceSeconds));
}
@Operation(summary = "重启Deployment", description = "通过更新annotation触发Deployment滚动重启")
@PostMapping("/{id}/restart")
public Response<Void> restartDeployment(
@Parameter(description = "Deployment ID", required = true) @PathVariable Long id
) {
k8sDeploymentService.restartDeployment(id);
return Response.success();
}
@Operation(summary = "扩缩容Deployment", description = "修改Deployment的副本数")
@PostMapping("/{id}/scale")
public Response<Void> scaleDeployment(
@Parameter(description = "Deployment ID", required = true) @PathVariable Long id,
@Validated @RequestBody com.qqchen.deploy.backend.deploy.dto.ScaleDeploymentRequest request
) {
k8sDeploymentService.scaleDeployment(id, request.getReplicas());
return Response.success();
}
@Override
protected void exportData(HttpServletResponse response, List<K8sDeploymentDTO> data) {
log.info("导出K8S Deployment数据数据量{}", data.size());

View File

@ -31,6 +31,9 @@ public class K8sDeploymentDTO extends BaseDTO {
@Schema(description = "就绪副本数")
private Integer readyReplicas;
@Schema(description = "总重启次数所有Pod的重启次数总和")
private Integer totalRestartCount;
@Schema(description = "容器镜像")
private String image;

View File

@ -0,0 +1,19 @@
package com.qqchen.deploy.backend.deploy.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* Deployment扩缩容请求
*/
@Data
@Schema(description = "Deployment扩缩容请求")
public class ScaleDeploymentRequest {
@NotNull(message = "副本数不能为空")
@Min(value = 0, message = "副本数不能小于0")
@Schema(description = "目标副本数", example = "3", required = true)
private Integer replicas;
}

View File

@ -38,6 +38,9 @@ public class K8sDeployment extends Entity<Long> {
@Column(name = "ready_replicas")
private Integer readyReplicas;
@Column(name = "total_restart_count")
private Integer totalRestartCount;
@Column(name = "image")
private String image;

View File

@ -0,0 +1,56 @@
package com.qqchen.deploy.backend.deploy.enums;
import lombok.Getter;
/**
* 容器状态枚举
*/
@Getter
public enum ContainerStateEnum {
/**
* 运行中
*/
RUNNING("running", "运行中"),
/**
* 等待中
*/
WAITING("waiting", "等待中"),
/**
* 已终止
*/
TERMINATED("terminated", "已终止"),
/**
* 未知状态
*/
UNKNOWN("unknown", "未知状态");
private final String code;
private final String description;
ContainerStateEnum(String code, String description) {
this.code = code;
this.description = description;
}
/**
* 根据code获取枚举
*
* @param code 状态码
* @return 容器状态枚举
*/
public static ContainerStateEnum fromCode(String code) {
if (code == null) {
return UNKNOWN;
}
for (ContainerStateEnum state : values()) {
if (state.code.equalsIgnoreCase(code)) {
return state;
}
}
return UNKNOWN;
}
}

View File

@ -0,0 +1,61 @@
package com.qqchen.deploy.backend.deploy.enums;
import lombok.Getter;
/**
* Pod阶段枚举
*/
@Getter
public enum PodPhaseEnum {
/**
* 等待中
*/
PENDING("Pending", "等待中"),
/**
* 运行中
*/
RUNNING("Running", "运行中"),
/**
* 成功
*/
SUCCEEDED("Succeeded", "成功"),
/**
* 失败
*/
FAILED("Failed", "失败"),
/**
* 未知状态
*/
UNKNOWN("Unknown", "未知状态");
private final String code;
private final String description;
PodPhaseEnum(String code, String description) {
this.code = code;
this.description = description;
}
/**
* 根据code获取枚举
*
* @param code 阶段码
* @return Pod阶段枚举
*/
public static PodPhaseEnum fromCode(String code) {
if (code == null) {
return UNKNOWN;
}
for (PodPhaseEnum phase : values()) {
if (phase.code.equalsIgnoreCase(code)) {
return phase;
}
}
return UNKNOWN;
}
}

View File

@ -75,6 +75,50 @@ public interface IK8sServiceIntegration extends IExternalSystemIntegration {
*/
K8sPodResponse getPod(ExternalSystem externalSystem, String namespace, String podName);
/**
* 查询Pod日志
*
* @param externalSystem K8S系统配置
* @param namespace 命名空间名称
* @param podName Pod名称
* @param container 容器名称可选默认第一个容器
* @param tail 返回最后N行日志可选
* @param sinceSeconds 返回最近N秒的日志可选
* @param follow 是否持续输出日志可选默认false
* @return Pod日志内容
*/
String getPodLogs(ExternalSystem externalSystem, String namespace, String podName,
String container, Integer tail, Integer sinceSeconds, Boolean follow);
/**
* 重启Deployment通过更新annotation触发滚动更新
*
* @param externalSystem K8S系统配置
* @param namespace 命名空间名称
* @param deploymentName Deployment名称
*/
void restartDeployment(ExternalSystem externalSystem, String namespace, String deploymentName);
/**
* 扩缩容Deployment
*
* @param externalSystem K8S系统配置
* @param namespace 命名空间名称
* @param deploymentName Deployment名称
* @param replicas 目标副本数
*/
void scaleDeployment(ExternalSystem externalSystem, String namespace, String deploymentName, Integer replicas);
/**
* 计算Deployment下所有Pod的总重启次数
*
* @param externalSystem K8S系统配置
* @param namespace 命名空间名称
* @param deploymentName Deployment名称
* @return 总重启次数
*/
Integer calculateTotalRestartCount(ExternalSystem externalSystem, String namespace, String deploymentName);
/**
* 获取系统类型
*

View File

@ -1,6 +1,8 @@
package com.qqchen.deploy.backend.deploy.integration.impl;
import com.qqchen.deploy.backend.deploy.entity.ExternalSystem;
import com.qqchen.deploy.backend.deploy.enums.ContainerStateEnum;
import com.qqchen.deploy.backend.deploy.enums.PodPhaseEnum;
import com.qqchen.deploy.backend.framework.utils.JsonUtils;
import com.qqchen.deploy.backend.deploy.integration.IK8sServiceIntegration;
import com.qqchen.deploy.backend.deploy.integration.response.K8sDeploymentResponse;
@ -38,6 +40,7 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
// K8S ApiClient缓存 - 线程安全
private static final Map<Long, K8sApiClientCache> API_CLIENT_CACHE = new ConcurrentHashMap<>();
private static final long CACHE_EXPIRE_TIME = 30 * 60 * 1000; // 30分钟过期
/**
@ -45,6 +48,7 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
*/
private static class K8sApiClientCache {
final ApiClient apiClient;
final long expireTime;
K8sApiClientCache(ApiClient apiClient) {
@ -93,24 +97,24 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
@Override
public boolean testConnection(ExternalSystem system) {
log.info("测试K8S连接集群: {}", system.getName());
try {
String config = system.getConfig();
if (config == null || config.trim().isEmpty()) {
throw new BusinessException(ResponseCode.K8S_CONFIG_EMPTY);
}
// 创建K8S ApiClient并测试连接直接使用config作为kubeconfig
ApiClient client = Config.fromConfig(new StringReader(config));
client.setConnectTimeout(15000); // 15秒连接超时
client.setReadTimeout(30000); // 30秒读取超时
VersionApi versionApi = new VersionApi(client);
VersionInfo version = versionApi.getCode();
log.info("K8S集群连接成功版本: {}", version.getGitVersion());
return true;
} catch (Exception e) {
log.error("K8S连接测试失败集群: {}, 错误: {}", system.getName(), e.getMessage(), e);
return false;
@ -123,26 +127,26 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
@Override
public List<K8sNamespaceResponse> listNamespaces(ExternalSystem externalSystem) {
log.info("查询K8S命名空间集群: {}", externalSystem.getName());
try {
K8sApiClientCache cache = getApiClientCache(externalSystem);
CoreV1Api api = new CoreV1Api(cache.apiClient);
V1NamespaceList namespaceList = api.listNamespace(
null, null, null, null, null, null, null, null, null, null
);
List<K8sNamespaceResponse> namespaces = new ArrayList<>();
for (V1Namespace ns : namespaceList.getItems()) {
K8sNamespaceResponse response = new K8sNamespaceResponse();
response.setName(ns.getMetadata().getName());
if (ns.getStatus() != null && ns.getStatus().getPhase() != null) {
response.setStatus(ns.getStatus().getPhase());
}
response.setLabels(ns.getMetadata().getLabels());
if (ns.getMetadata().getCreationTimestamp() != null) {
response.setCreationTimestamp(
LocalDateTime.ofInstant(
@ -151,20 +155,20 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
)
);
}
// 序列化为YAML配置
try {
response.setYamlConfig(Yaml.dump(ns));
} catch (Exception e) {
log.warn("序列化Namespace为YAML失败: {}", ns.getMetadata().getName(), e);
}
namespaces.add(response);
}
log.info("查询到 {} 个命名空间", namespaces.size());
return namespaces;
} catch (Exception e) {
log.error("查询K8S命名空间失败集群: {}, 错误: {}", externalSystem.getName(), e.getMessage(), e);
throw new BusinessException(ResponseCode.K8S_NAMESPACE_SYNC_FAILED);
@ -177,45 +181,45 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
@Override
public List<K8sDeploymentResponse> listDeployments(ExternalSystem externalSystem, String namespace) {
log.info("查询K8S Deployment集群: {}, 命名空间: {}", externalSystem.getName(), namespace);
try {
K8sApiClientCache cache = getApiClientCache(externalSystem);
AppsV1Api api = new AppsV1Api(cache.apiClient);
V1DeploymentList deploymentList = api.listNamespacedDeployment(
namespace, null, null, null, null, null, null, null, null, null, null
);
List<K8sDeploymentResponse> deployments = new ArrayList<>();
for (V1Deployment deployment : deploymentList.getItems()) {
K8sDeploymentResponse response = new K8sDeploymentResponse();
response.setName(deployment.getMetadata().getName());
response.setNamespace(deployment.getMetadata().getNamespace());
if (deployment.getSpec() != null) {
response.setReplicas(deployment.getSpec().getReplicas());
}
if (deployment.getStatus() != null) {
response.setAvailableReplicas(deployment.getStatus().getAvailableReplicas());
response.setReadyReplicas(deployment.getStatus().getReadyReplicas());
}
response.setLabels(deployment.getMetadata().getLabels());
if (deployment.getSpec() != null && deployment.getSpec().getSelector() != null) {
response.setSelector(deployment.getSpec().getSelector().getMatchLabels());
}
// 获取第一个容器的镜像
if (deployment.getSpec() != null
if (deployment.getSpec() != null
&& deployment.getSpec().getTemplate() != null
&& deployment.getSpec().getTemplate().getSpec() != null
&& deployment.getSpec().getTemplate().getSpec().getContainers() != null
&& deployment.getSpec().getTemplate().getSpec().getContainers() != null
&& !deployment.getSpec().getTemplate().getSpec().getContainers().isEmpty()) {
response.setImage(deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage());
}
if (deployment.getMetadata().getCreationTimestamp() != null) {
response.setCreationTimestamp(
LocalDateTime.ofInstant(
@ -224,22 +228,22 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
)
);
}
// 序列化为YAML配置
try {
response.setYamlConfig(Yaml.dump(deployment));
} catch (Exception e) {
log.warn("序列化Deployment为YAML失败: {}", deployment.getMetadata().getName(), e);
}
deployments.add(response);
}
log.info("查询到 {} 个Deployment", deployments.size());
return deployments;
} catch (Exception e) {
log.error("查询K8S Deployment失败集群: {}, 命名空间: {}, 错误: {}",
log.error("查询K8S Deployment失败集群: {}, 命名空间: {}, 错误: {}",
externalSystem.getName(), namespace, e.getMessage(), e);
throw new BusinessException(ResponseCode.K8S_DEPLOYMENT_SYNC_FAILED);
}
@ -251,45 +255,45 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
@Override
public List<K8sDeploymentResponse> listAllDeployments(ExternalSystem externalSystem) {
log.info("查询所有K8S Deployment集群: {}", externalSystem.getName());
try {
K8sApiClientCache cache = getApiClientCache(externalSystem);
AppsV1Api api = new AppsV1Api(cache.apiClient);
V1DeploymentList deploymentList = api.listDeploymentForAllNamespaces(
null, null, null, null, null, null, null, null, null, null
);
List<K8sDeploymentResponse> deployments = new ArrayList<>();
for (V1Deployment deployment : deploymentList.getItems()) {
K8sDeploymentResponse response = new K8sDeploymentResponse();
response.setName(deployment.getMetadata().getName());
response.setNamespace(deployment.getMetadata().getNamespace());
if (deployment.getSpec() != null) {
response.setReplicas(deployment.getSpec().getReplicas());
}
if (deployment.getStatus() != null) {
response.setAvailableReplicas(deployment.getStatus().getAvailableReplicas());
response.setReadyReplicas(deployment.getStatus().getReadyReplicas());
}
response.setLabels(deployment.getMetadata().getLabels());
if (deployment.getSpec() != null && deployment.getSpec().getSelector() != null) {
response.setSelector(deployment.getSpec().getSelector().getMatchLabels());
}
// 获取第一个容器的镜像
if (deployment.getSpec() != null
if (deployment.getSpec() != null
&& deployment.getSpec().getTemplate() != null
&& deployment.getSpec().getTemplate().getSpec() != null
&& deployment.getSpec().getTemplate().getSpec().getContainers() != null
&& deployment.getSpec().getTemplate().getSpec().getContainers() != null
&& !deployment.getSpec().getTemplate().getSpec().getContainers().isEmpty()) {
response.setImage(deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage());
}
if (deployment.getMetadata().getCreationTimestamp() != null) {
response.setCreationTimestamp(
LocalDateTime.ofInstant(
@ -298,20 +302,20 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
)
);
}
// 序列化为YAML配置
try {
response.setYamlConfig(Yaml.dump(deployment));
} catch (Exception e) {
log.warn("序列化Deployment为YAML失败: {}", deployment.getMetadata().getName(), e);
}
deployments.add(response);
}
log.info("查询到 {} 个Deployment", deployments.size());
return deployments;
} catch (Exception e) {
log.error("查询所有K8S Deployment失败集群: {}, 错误: {}", externalSystem.getName(), e.getMessage(), e);
throw new BusinessException(ResponseCode.K8S_DEPLOYMENT_SYNC_FAILED);
@ -324,25 +328,25 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
@Override
public List<K8sPodResponse> listPods(ExternalSystem externalSystem, String namespace) {
log.info("查询K8S Pod集群: {}, 命名空间: {}", externalSystem.getName(), namespace);
try {
K8sApiClientCache cache = getApiClientCache(externalSystem);
CoreV1Api api = new CoreV1Api(cache.apiClient);
V1PodList podList = api.listNamespacedPod(
namespace, null, null, null, null, null, null, null, null, null, null
);
List<K8sPodResponse> pods = new ArrayList<>();
for (V1Pod pod : podList.getItems()) {
pods.add(convertPodToResponse(pod));
}
log.info("查询到 {} 个Pod", pods.size());
return pods;
} catch (Exception e) {
log.error("查询K8S Pod失败集群: {}, 命名空间: {}, 错误: {}",
log.error("查询K8S Pod失败集群: {}, 命名空间: {}, 错误: {}",
externalSystem.getName(), namespace, e.getMessage(), e);
throw new BusinessException(ResponseCode.K8S_OPERATION_FAILED);
}
@ -353,53 +357,53 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
*/
@Override
public List<K8sPodResponse> listPodsByDeployment(ExternalSystem externalSystem, String namespace, String deploymentName) {
log.info("查询K8S Deployment的Pod集群: {}, 命名空间: {}, Deployment: {}",
log.info("查询K8S Deployment的Pod集群: {}, 命名空间: {}, Deployment: {}",
externalSystem.getName(), namespace, deploymentName);
try {
K8sApiClientCache cache = getApiClientCache(externalSystem);
// 1. 先查询Deployment获取selector
AppsV1Api appsApi = new AppsV1Api(cache.apiClient);
V1Deployment deployment = appsApi.readNamespacedDeployment(deploymentName, namespace, null);
if (deployment.getSpec() == null || deployment.getSpec().getSelector() == null
if (deployment.getSpec() == null || deployment.getSpec().getSelector() == null
|| deployment.getSpec().getSelector().getMatchLabels() == null) {
log.warn("Deployment没有selector: {}/{}", namespace, deploymentName);
return new ArrayList<>();
}
// 2. 构建label selector
Map<String, String> matchLabels = deployment.getSpec().getSelector().getMatchLabels();
String labelSelector = matchLabels.entrySet().stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.reduce((a, b) -> a + "," + b)
.orElse("");
// 3. 使用label selector查询Pod
CoreV1Api coreApi = new CoreV1Api(cache.apiClient);
V1PodList podList = coreApi.listNamespacedPod(
namespace, null, null, null, null, labelSelector, null, null, null, null, null
);
List<K8sPodResponse> pods = new ArrayList<>();
for (V1Pod pod : podList.getItems()) {
pods.add(convertPodToResponse(pod));
}
log.info("查询到 {} 个Pod", pods.size());
return pods;
} catch (ApiException e) {
if (e.getCode() == 404) {
log.warn("Deployment不存在: {}/{}", namespace, deploymentName);
throw new BusinessException(ResponseCode.K8S_RESOURCE_NOT_FOUND);
}
log.error("查询K8S Deployment的Pod失败集群: {}, 命名空间: {}, Deployment: {}, 错误: {}",
log.error("查询K8S Deployment的Pod失败集群: {}, 命名空间: {}, Deployment: {}, 错误: {}",
externalSystem.getName(), namespace, deploymentName, e.getMessage(), e);
throw new BusinessException(ResponseCode.K8S_OPERATION_FAILED);
} catch (Exception e) {
log.error("查询K8S Deployment的Pod失败集群: {}, 命名空间: {}, Deployment: {}, 错误: {}",
log.error("查询K8S Deployment的Pod失败集群: {}, 命名空间: {}, Deployment: {}, 错误: {}",
externalSystem.getName(), namespace, deploymentName, e.getMessage(), e);
throw new BusinessException(ResponseCode.K8S_OPERATION_FAILED);
}
@ -410,27 +414,27 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
*/
@Override
public K8sPodResponse getPod(ExternalSystem externalSystem, String namespace, String podName) {
log.info("查询K8S Pod详情集群: {}, 命名空间: {}, Pod: {}",
log.info("查询K8S Pod详情集群: {}, 命名空间: {}, Pod: {}",
externalSystem.getName(), namespace, podName);
try {
K8sApiClientCache cache = getApiClientCache(externalSystem);
CoreV1Api api = new CoreV1Api(cache.apiClient);
V1Pod pod = api.readNamespacedPod(podName, namespace, null);
return convertPodToResponse(pod);
} catch (ApiException e) {
if (e.getCode() == 404) {
log.warn("Pod不存在: {}/{}", namespace, podName);
throw new BusinessException(ResponseCode.K8S_POD_NOT_FOUND);
}
log.error("查询K8S Pod详情失败集群: {}, 命名空间: {}, Pod: {}, 错误: {}",
log.error("查询K8S Pod详情失败集群: {}, 命名空间: {}, Pod: {}, 错误: {}",
externalSystem.getName(), namespace, podName, e.getMessage(), e);
throw new BusinessException(ResponseCode.K8S_OPERATION_FAILED);
} catch (Exception e) {
log.error("查询K8S Pod详情失败集群: {}, 命名空间: {}, Pod: {}, 错误: {}",
log.error("查询K8S Pod详情失败集群: {}, 命名空间: {}, Pod: {}, 错误: {}",
externalSystem.getName(), namespace, podName, e.getMessage(), e);
throw new BusinessException(ResponseCode.K8S_OPERATION_FAILED);
}
@ -441,13 +445,13 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
*/
private K8sPodResponse convertPodToResponse(V1Pod pod) {
K8sPodResponse response = new K8sPodResponse();
// 基本信息
response.setName(pod.getMetadata().getName());
response.setNamespace(pod.getMetadata().getNamespace());
response.setLabels(pod.getMetadata().getLabels());
response.setAnnotations(pod.getMetadata().getAnnotations());
// 时间信息
if (pod.getMetadata().getCreationTimestamp() != null) {
response.setCreationTimestamp(
@ -457,15 +461,15 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
)
);
}
// 状态信息
if (pod.getStatus() != null) {
response.setPhase(pod.getStatus().getPhase());
response.setPhase(PodPhaseEnum.fromCode(pod.getStatus().getPhase()));
response.setReason(pod.getStatus().getReason());
response.setMessage(pod.getStatus().getMessage());
response.setPodIP(pod.getStatus().getPodIP());
response.setHostIP(pod.getStatus().getHostIP());
if (pod.getStatus().getStartTime() != null) {
response.setStartTime(
LocalDateTime.ofInstant(
@ -474,13 +478,13 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
)
);
}
// 容器状态
if (pod.getStatus().getContainerStatuses() != null) {
List<K8sPodResponse.ContainerInfo> containers = new ArrayList<>();
int totalRestartCount = 0;
boolean allReady = true;
for (V1ContainerStatus containerStatus : pod.getStatus().getContainerStatuses()) {
K8sPodResponse.ContainerInfo containerInfo = new K8sPodResponse.ContainerInfo();
containerInfo.setName(containerStatus.getName());
@ -489,17 +493,17 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
containerInfo.setReady(containerStatus.getReady());
containerInfo.setRestartCount(containerStatus.getRestartCount());
containerInfo.setContainerID(containerStatus.getContainerID());
totalRestartCount += containerStatus.getRestartCount();
if (!containerStatus.getReady()) {
allReady = false;
}
// 容器状态
V1ContainerState state = containerStatus.getState();
if (state != null) {
if (state.getRunning() != null) {
containerInfo.setState("running");
containerInfo.setState(ContainerStateEnum.RUNNING);
if (state.getRunning().getStartedAt() != null) {
containerInfo.setStartedAt(
LocalDateTime.ofInstant(
@ -509,57 +513,57 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
);
}
} else if (state.getWaiting() != null) {
containerInfo.setState("waiting");
containerInfo.setState(ContainerStateEnum.WAITING);
containerInfo.setStateReason(state.getWaiting().getReason());
containerInfo.setStateMessage(state.getWaiting().getMessage());
} else if (state.getTerminated() != null) {
containerInfo.setState("terminated");
containerInfo.setState(ContainerStateEnum.TERMINATED);
containerInfo.setStateReason(state.getTerminated().getReason());
containerInfo.setStateMessage(state.getTerminated().getMessage());
}
}
containers.add(containerInfo);
}
response.setContainers(containers);
response.setRestartCount(totalRestartCount);
response.setReady(allReady);
}
}
// Spec信息
if (pod.getSpec() != null) {
response.setNodeName(pod.getSpec().getNodeName());
// 容器资源配置
if (pod.getSpec().getContainers() != null && response.getContainers() != null) {
for (int i = 0; i < pod.getSpec().getContainers().size() && i < response.getContainers().size(); i++) {
V1Container container = pod.getSpec().getContainers().get(i);
K8sPodResponse.ContainerInfo containerInfo = response.getContainers().get(i);
if (container.getResources() != null) {
if (container.getResources().getRequests() != null) {
containerInfo.setCpuRequest(
container.getResources().getRequests().get("cpu") != null
? container.getResources().getRequests().get("cpu").toSuffixedString()
container.getResources().getRequests().get("cpu") != null
? container.getResources().getRequests().get("cpu").toSuffixedString()
: null
);
containerInfo.setMemoryRequest(
container.getResources().getRequests().get("memory") != null
? container.getResources().getRequests().get("memory").toSuffixedString()
container.getResources().getRequests().get("memory") != null
? container.getResources().getRequests().get("memory").toSuffixedString()
: null
);
}
if (container.getResources().getLimits() != null) {
containerInfo.setCpuLimit(
container.getResources().getLimits().get("cpu") != null
? container.getResources().getLimits().get("cpu").toSuffixedString()
container.getResources().getLimits().get("cpu") != null
? container.getResources().getLimits().get("cpu").toSuffixedString()
: null
);
containerInfo.setMemoryLimit(
container.getResources().getLimits().get("memory") != null
? container.getResources().getLimits().get("memory").toSuffixedString()
container.getResources().getLimits().get("memory") != null
? container.getResources().getLimits().get("memory").toSuffixedString()
: null
);
}
@ -567,47 +571,202 @@ public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration imp
}
}
}
// Owner信息
if (pod.getMetadata().getOwnerReferences() != null && !pod.getMetadata().getOwnerReferences().isEmpty()) {
V1OwnerReference owner = pod.getMetadata().getOwnerReferences().get(0);
response.setOwnerKind(owner.getKind());
response.setOwnerName(owner.getName());
}
// 序列化为YAML
try {
response.setYamlConfig(Yaml.dump(pod));
} catch (Exception e) {
log.warn("序列化Pod为YAML失败: {}", pod.getMetadata().getName(), e);
}
return response;
}
/**
* 创建K8S ApiClient对外接口使用缓存
*
* @param externalSystem K8S系统配置
* @return ApiClient
* 查询Pod日志
*/
private ApiClient createApiClient(ExternalSystem externalSystem) {
return getApiClientCache(externalSystem).apiClient;
@Override
public String getPodLogs(ExternalSystem externalSystem, String namespace, String podName,
String container, Integer tail, Integer sinceSeconds, Boolean follow) {
log.info("查询K8S Pod日志集群: {}, 命名空间: {}, Pod: {}, 容器: {}",
externalSystem.getName(), namespace, podName, container);
try {
K8sApiClientCache cache = getApiClientCache(externalSystem);
CoreV1Api api = new CoreV1Api(cache.apiClient);
// 查询Pod日志
String logs = api.readNamespacedPodLog(
podName, // Pod名称
namespace, // 命名空间
container, // 容器名称可选
follow != null && follow, // 是否持续输出
null, // insecureSkipTLSVerifyBackend
null, // limitBytes
"false", // pretty
false, // previous是否查询上一个容器的日志
sinceSeconds, // sinceSeconds
tail, // tail
false // timestamps
);
log.info("查询Pod日志成功日志长度: {}", logs != null ? logs.length() : 0);
return logs != null ? logs : "";
} catch (ApiException e) {
if (e.getCode() == 404) {
log.warn("Pod不存在: {}/{}", namespace, podName);
throw new BusinessException(ResponseCode.K8S_POD_NOT_FOUND);
}
log.error("查询K8S Pod日志失败集群: {}, 命名空间: {}, Pod: {}, 错误: {}",
externalSystem.getName(), namespace, podName, e.getMessage(), e);
throw new BusinessException(ResponseCode.K8S_OPERATION_FAILED);
} catch (Exception e) {
log.error("查询K8S Pod日志失败集群: {}, 命名空间: {}, Pod: {}, 错误: {}",
externalSystem.getName(), namespace, podName, e.getMessage(), e);
throw new BusinessException(ResponseCode.K8S_OPERATION_FAILED);
}
}
/**
* 重启Deployment通过更新annotation触发滚动更新
*/
@Override
public void restartDeployment(ExternalSystem externalSystem, String namespace, String deploymentName) {
log.info("重启K8S Deployment集群: {}, 命名空间: {}, Deployment: {}",
externalSystem.getName(), namespace, deploymentName);
try {
K8sApiClientCache cache = getApiClientCache(externalSystem);
AppsV1Api api = new AppsV1Api(cache.apiClient);
// 构建patch内容更新spec.template.metadata.annotations添加重启时间戳
String patchBody = String.format(
"{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"kubectl.kubernetes.io/restartedAt\":\"%s\"}}}}}",
LocalDateTime.now().toString()
);
// 使用strategic merge patch更新Deployment
api.patchNamespacedDeployment(
deploymentName,
namespace,
new io.kubernetes.client.custom.V1Patch(patchBody),
null,
null,
null,
null,
null
);
log.info("重启K8S Deployment成功");
} catch (ApiException e) {
if (e.getCode() == 404) {
log.warn("Deployment不存在: {}/{}", namespace, deploymentName);
throw new BusinessException(ResponseCode.K8S_RESOURCE_NOT_FOUND);
}
log.error("重启K8S Deployment失败集群: {}, 命名空间: {}, Deployment: {}, 错误: {}",
externalSystem.getName(), namespace, deploymentName, e.getMessage(), e);
throw new BusinessException(ResponseCode.K8S_OPERATION_FAILED);
} catch (Exception e) {
log.error("重启K8S Deployment失败集群: {}, 命名空间: {}, Deployment: {}, 错误: {}",
externalSystem.getName(), namespace, deploymentName, e.getMessage(), e);
throw new BusinessException(ResponseCode.K8S_OPERATION_FAILED);
}
}
/**
* 扩缩容Deployment
*/
@Override
public void scaleDeployment(ExternalSystem externalSystem, String namespace, String deploymentName, Integer replicas) {
log.info("扩缩容K8S Deployment集群: {}, 命名空间: {}, Deployment: {}, 目标副本数: {}",
externalSystem.getName(), namespace, deploymentName, replicas);
try {
K8sApiClientCache cache = getApiClientCache(externalSystem);
AppsV1Api api = new AppsV1Api(cache.apiClient);
// 构建patch内容更新spec.replicas
String patchBody = String.format("{\"spec\":{\"replicas\":%d}}", replicas);
// 使用strategic merge patch更新Deployment的scale
api.patchNamespacedDeploymentScale(
deploymentName,
namespace,
new io.kubernetes.client.custom.V1Patch(patchBody),
null,
null,
null,
null,
null
);
log.info("扩缩容K8S Deployment成功");
} catch (ApiException e) {
if (e.getCode() == 404) {
log.warn("Deployment不存在: {}/{}", namespace, deploymentName);
throw new BusinessException(ResponseCode.K8S_RESOURCE_NOT_FOUND);
}
log.error("扩缩容K8S Deployment失败集群: {}, 命名空间: {}, Deployment: {}, 错误: {}",
externalSystem.getName(), namespace, deploymentName, e.getMessage(), e);
throw new BusinessException(ResponseCode.K8S_OPERATION_FAILED);
} catch (Exception e) {
log.error("扩缩容K8S Deployment失败集群: {}, 命名空间: {}, Deployment: {}, 错误: {}",
externalSystem.getName(), namespace, deploymentName, e.getMessage(), e);
throw new BusinessException(ResponseCode.K8S_OPERATION_FAILED);
}
}
/**
* 计算Deployment下所有Pod的总重启次数
*/
@Override
public Integer calculateTotalRestartCount(ExternalSystem externalSystem, String namespace, String deploymentName) {
log.debug("计算Deployment的总重启次数集群: {}, 命名空间: {}, Deployment: {}",
externalSystem.getName(), namespace, deploymentName);
try {
// 查询Deployment下的所有Pod
List<K8sPodResponse> pods = listPodsByDeployment(externalSystem, namespace, deploymentName);
// 累加所有Pod的重启次数
int totalRestartCount = pods.stream()
.mapToInt(pod -> pod.getRestartCount() != null ? pod.getRestartCount() : 0)
.sum();
log.debug("Deployment总重启次数: {}", totalRestartCount);
return totalRestartCount;
} catch (Exception e) {
log.warn("计算Deployment总重启次数失败集群: {}, 命名空间: {}, Deployment: {}, 错误: {}",
externalSystem.getName(), namespace, deploymentName, e.getMessage());
// 计算失败时返回null不影响同步流程
return null;
}
}
/**
* 创建K8S ApiClient内部实现不使用缓存
*
*
* @param externalSystem K8S系统配置
* @return ApiClient
*/
private ApiClient createApiClientInternal(ExternalSystem externalSystem) throws Exception {
String config = externalSystem.getConfig();
if (config == null || config.trim().isEmpty()) {
throw new BusinessException(ResponseCode.K8S_CONFIG_EMPTY);
}
// 直接使用config作为kubeconfig内容
ApiClient client = Config.fromConfig(new StringReader(config));
client.setConnectTimeout(15000); // 15秒连接超时

View File

@ -12,6 +12,7 @@ public class K8sDeploymentResponse {
private Integer replicas;
private Integer availableReplicas;
private Integer readyReplicas;
private Integer totalRestartCount;
private String image;
private Map<String, String> labels;
private Map<String, String> selector;

View File

@ -1,5 +1,7 @@
package com.qqchen.deploy.backend.deploy.integration.response;
import com.qqchen.deploy.backend.deploy.enums.ContainerStateEnum;
import com.qqchen.deploy.backend.deploy.enums.PodPhaseEnum;
import lombok.Data;
import java.time.LocalDateTime;
@ -23,9 +25,9 @@ public class K8sPodResponse {
private String namespace;
/**
* Pod状态阶段Running, Pending, Succeeded, Failed, Unknown
* Pod状态阶段
*/
private String phase;
private PodPhaseEnum phase;
/**
* Pod状态原因
@ -123,9 +125,9 @@ public class K8sPodResponse {
private String imageID;
/**
* 容器状态running, waiting, terminated
* 容器状态
*/
private String state;
private ContainerStateEnum state;
/**
* 是否就绪

View File

@ -20,4 +20,19 @@ public interface IK8sDeploymentService extends IBaseService<K8sDeployment, K8sDe
List<K8sDeploymentDTO> findByExternalSystemId(Long externalSystemId);
List<K8sDeploymentDTO> findByNamespaceId(Long namespaceId);
/**
* 重启Deployment
*
* @param deploymentId Deployment ID
*/
void restartDeployment(Long deploymentId);
/**
* 扩缩容Deployment
*
* @param deploymentId Deployment ID
* @param replicas 目标副本数
*/
void scaleDeployment(Long deploymentId, Integer replicas);
}

View File

@ -53,4 +53,16 @@ public interface IK8sPodService {
* @return Pod列表
*/
List<K8sPodResponse> listPodsByNamespace(Long namespaceId);
/**
* 查询Pod日志
*
* @param deploymentId Deployment ID
* @param podName Pod名称
* @param container 容器名称可选
* @param tail 返回最后N行日志可选
* @param sinceSeconds 返回最近N秒的日志可选
* @return Pod日志内容
*/
String getPodLogs(Long deploymentId, String podName, String container, Integer tail, Integer sinceSeconds);
}

View File

@ -99,6 +99,13 @@ public class K8sDeploymentServiceImpl extends BaseServiceImpl<K8sDeployment, K8s
deployment.setK8sCreateTime(response.getCreationTimestamp());
deployment.setK8sUpdateTime(response.getLastUpdateTime());
deployment.setYamlConfig(response.getYamlConfig());
// 计算并缓存总重启次数
Integer totalRestartCount = k8sServiceIntegration.calculateTotalRestartCount(
externalSystem, namespace.getNamespaceName(), response.getName()
);
deployment.setTotalRestartCount(totalRestartCount);
deploymentsToSave.add(deployment);
}
@ -222,5 +229,67 @@ public class K8sDeploymentServiceImpl extends BaseServiceImpl<K8sDeployment, K8s
List<K8sDeployment> deployments = k8sDeploymentRepository.findByNamespaceId(namespaceId);
return k8sDeploymentConverter.toDtoList(deployments);
}
@Override
public void restartDeployment(Long deploymentId) {
log.info("重启DeploymentdeploymentId: {}", deploymentId);
// 1. 查询K8sDeployment
K8sDeployment deployment = k8sDeploymentRepository.findById(deploymentId)
.orElseThrow(() -> new BusinessException(ResponseCode.K8S_RESOURCE_NOT_FOUND));
// 2. 查询K8sNamespace
K8sNamespace namespace = k8sNamespaceRepository.findById(deployment.getNamespaceId())
.orElseThrow(() -> new BusinessException(ResponseCode.K8S_RESOURCE_NOT_FOUND));
// 3. 查询ExternalSystem
ExternalSystem externalSystem = externalSystemRepository.findById(deployment.getExternalSystemId())
.orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND));
// 4. 调用K8s API重启Deployment
k8sServiceIntegration.restartDeployment(
externalSystem,
namespace.getNamespaceName(),
deployment.getDeploymentName()
);
log.info("Deployment重启成功deploymentId: {}", deploymentId);
}
@Override
public void scaleDeployment(Long deploymentId, Integer replicas) {
log.info("扩缩容DeploymentdeploymentId: {}, replicas: {}", deploymentId, replicas);
// 参数校验
if (replicas == null || replicas < 0) {
throw new BusinessException(ResponseCode.INVALID_PARAM);
}
// 1. 查询K8sDeployment
K8sDeployment deployment = k8sDeploymentRepository.findById(deploymentId)
.orElseThrow(() -> new BusinessException(ResponseCode.K8S_RESOURCE_NOT_FOUND));
// 2. 查询K8sNamespace
K8sNamespace namespace = k8sNamespaceRepository.findById(deployment.getNamespaceId())
.orElseThrow(() -> new BusinessException(ResponseCode.K8S_RESOURCE_NOT_FOUND));
// 3. 查询ExternalSystem
ExternalSystem externalSystem = externalSystemRepository.findById(deployment.getExternalSystemId())
.orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND));
// 4. 调用K8s API扩缩容Deployment
k8sServiceIntegration.scaleDeployment(
externalSystem,
namespace.getNamespaceName(),
deployment.getDeploymentName(),
replicas
);
// 5. 更新本地数据库记录
deployment.setReplicas(replicas);
k8sDeploymentRepository.save(deployment);
log.info("Deployment扩缩容成功deploymentId: {}, replicas: {}", deploymentId, replicas);
}
}

View File

@ -129,4 +129,33 @@ public class K8sPodServiceImpl implements IK8sPodService {
// 4. 调用K8s API查询Pod详情
return k8sServiceIntegration.getPod(externalSystem, namespace.getNamespaceName(), podName);
}
@Override
public String getPodLogs(Long deploymentId, String podName, String container, Integer tail, Integer sinceSeconds) {
log.info("查询Pod日志deploymentId: {}, podName: {}, container: {}, tail: {}, sinceSeconds: {}",
deploymentId, podName, container, tail, sinceSeconds);
// 1. 查询K8sDeployment
K8sDeployment deployment = k8sDeploymentRepository.findById(deploymentId)
.orElseThrow(() -> new BusinessException(ResponseCode.K8S_RESOURCE_NOT_FOUND));
// 2. 查询K8sNamespace
K8sNamespace namespace = k8sNamespaceRepository.findById(deployment.getNamespaceId())
.orElseThrow(() -> new BusinessException(ResponseCode.K8S_RESOURCE_NOT_FOUND));
// 3. 查询ExternalSystem
ExternalSystem externalSystem = externalSystemRepository.findById(deployment.getExternalSystemId())
.orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND));
// 4. 调用K8s API查询Pod日志
return k8sServiceIntegration.getPodLogs(
externalSystem,
namespace.getNamespaceName(),
podName,
container,
tail,
sinceSeconds,
false // follow参数设为false不持续输出
);
}
}