1.30增加K8S支持

This commit is contained in:
dengqichen 2025-12-12 18:26:31 +08:00
parent 4c461ea42a
commit 0ed6c1e126
39 changed files with 1902 additions and 29 deletions

View File

@ -281,6 +281,13 @@
<artifactId>HikariCP</artifactId> <artifactId>HikariCP</artifactId>
<version>5.0.1</version> <version>5.0.1</version>
</dependency> </dependency>
<!-- Kubernetes Java Client (支持K8S 1.23-1.28) -->
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java</artifactId>
<version>18.0.1</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -0,0 +1,106 @@
package com.qqchen.deploy.backend.deploy.api;
import com.qqchen.deploy.backend.deploy.dto.K8sDeploymentDTO;
import com.qqchen.deploy.backend.deploy.entity.K8sDeployment;
import com.qqchen.deploy.backend.deploy.query.K8sDeploymentQuery;
import com.qqchen.deploy.backend.deploy.service.IK8sDeploymentService;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.controller.BaseController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Slf4j
@RestController
@RequestMapping("/api/v1/k8s-deployment")
@Tag(name = "K8S Deployment管理", description = "K8S Deployment管理相关接口")
public class K8sDeploymentApiController extends BaseController<K8sDeployment, K8sDeploymentDTO, Long, K8sDeploymentQuery> {
@Resource
private IK8sDeploymentService k8sDeploymentService;
@Override
public Response<K8sDeploymentDTO> create(@Validated @RequestBody K8sDeploymentDTO dto) {
return super.create(dto);
}
@Override
public Response<K8sDeploymentDTO> update(@PathVariable Long id, @Validated @RequestBody K8sDeploymentDTO dto) {
return super.update(id, dto);
}
@Override
public Response<Void> delete(@PathVariable Long id) {
return super.delete(id);
}
@Override
public Response<K8sDeploymentDTO> findById(@PathVariable Long id) {
return super.findById(id);
}
@Override
public Response<List<K8sDeploymentDTO>> findAll() {
return super.findAll();
}
@Override
public Response<Page<K8sDeploymentDTO>> page(K8sDeploymentQuery query) {
return super.page(query);
}
@Override
public Response<List<K8sDeploymentDTO>> findAll(K8sDeploymentQuery query) {
return super.findAll(query);
}
@Override
public CompletableFuture<Response<Void>> batchProcess(List<K8sDeploymentDTO> dtos) {
return super.batchProcess(dtos);
}
@Operation(summary = "同步K8S Deployment", description = "异步同步支持两种模式1)只传externalSystemId-全量同步 2)传externalSystemId+namespaceId-同步指定命名空间")
@PostMapping("/sync")
public Response<Void> sync(
@Parameter(description = "K8S集群ID外部系统ID", required = true) @RequestParam Long externalSystemId,
@Parameter(description = "命名空间ID可选", required = false) @RequestParam(required = false) Long namespaceId
) {
if (namespaceId != null) {
k8sDeploymentService.syncDeployments(externalSystemId, namespaceId);
} else {
k8sDeploymentService.syncDeployments(externalSystemId);
}
return Response.success();
}
@Operation(summary = "根据集群ID查询Deployment", description = "查询指定K8S集群的所有Deployment")
@GetMapping("/by-system/{externalSystemId}")
public Response<List<K8sDeploymentDTO>> findByExternalSystemId(
@Parameter(description = "K8S集群ID", required = true) @PathVariable Long externalSystemId
) {
return Response.success(k8sDeploymentService.findByExternalSystemId(externalSystemId));
}
@Operation(summary = "根据命名空间ID查询Deployment", description = "查询指定命名空间的所有Deployment")
@GetMapping("/by-namespace/{namespaceId}")
public Response<List<K8sDeploymentDTO>> findByNamespaceId(
@Parameter(description = "命名空间ID", required = true) @PathVariable Long namespaceId
) {
return Response.success(k8sDeploymentService.findByNamespaceId(namespaceId));
}
@Override
protected void exportData(HttpServletResponse response, List<K8sDeploymentDTO> data) {
log.info("导出K8S Deployment数据数据量{}", data.size());
}
}

View File

@ -0,0 +1,85 @@
package com.qqchen.deploy.backend.deploy.api;
import com.qqchen.deploy.backend.deploy.dto.K8sNamespaceDTO;
import com.qqchen.deploy.backend.deploy.entity.K8sNamespace;
import com.qqchen.deploy.backend.deploy.query.K8sNamespaceQuery;
import com.qqchen.deploy.backend.deploy.service.IK8sNamespaceService;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.controller.BaseController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Slf4j
@RestController
@RequestMapping("/api/v1/k8s-namespace")
@Tag(name = "K8S命名空间管理", description = "K8S命名空间管理相关接口")
public class K8sNamespaceApiController extends BaseController<K8sNamespace, K8sNamespaceDTO, Long, K8sNamespaceQuery> {
@Resource
private IK8sNamespaceService k8sNamespaceService;
@Override
public Response<K8sNamespaceDTO> create(@Validated @RequestBody K8sNamespaceDTO dto) {
return super.create(dto);
}
@Override
public Response<K8sNamespaceDTO> update(@PathVariable Long id, @Validated @RequestBody K8sNamespaceDTO dto) {
return super.update(id, dto);
}
@Override
public Response<Void> delete(@PathVariable Long id) {
return super.delete(id);
}
@Override
public Response<K8sNamespaceDTO> findById(@PathVariable Long id) {
return super.findById(id);
}
@Override
public Response<List<K8sNamespaceDTO>> findAll() {
return super.findAll();
}
@Override
public Response<Page<K8sNamespaceDTO>> page(K8sNamespaceQuery query) {
return super.page(query);
}
@Override
public Response<List<K8sNamespaceDTO>> findAll(K8sNamespaceQuery query) {
return super.findAll(query);
}
@Override
public CompletableFuture<Response<Void>> batchProcess(List<K8sNamespaceDTO> dtos) {
return super.batchProcess(dtos);
}
@Operation(summary = "同步K8S命名空间", description = "异步同步指定K8S集群的命名空间")
@PostMapping("/sync")
public Response<Void> sync(
@Parameter(description = "K8S集群ID外部系统ID", required = true) @RequestParam Long externalSystemId
) {
k8sNamespaceService.syncNamespaces(externalSystemId);
return Response.success();
}
@Override
protected void exportData(HttpServletResponse response, List<K8sNamespaceDTO> data) {
log.info("导出K8S命名空间数据数据量{}", data.size());
}
}

View File

@ -0,0 +1,84 @@
package com.qqchen.deploy.backend.deploy.api;
import com.qqchen.deploy.backend.deploy.dto.K8sSyncHistoryDTO;
import com.qqchen.deploy.backend.deploy.entity.K8sSyncHistory;
import com.qqchen.deploy.backend.deploy.query.K8sSyncHistoryQuery;
import com.qqchen.deploy.backend.deploy.service.IK8sSyncHistoryService;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.controller.BaseController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Slf4j
@RestController
@RequestMapping("/api/v1/k8s-sync-history")
@Tag(name = "K8S同步历史管理", description = "K8S同步历史管理相关接口")
public class K8sSyncHistoryApiController extends BaseController<K8sSyncHistory, K8sSyncHistoryDTO, Long, K8sSyncHistoryQuery> {
@Resource
private IK8sSyncHistoryService k8sSyncHistoryService;
@Override
public Response<K8sSyncHistoryDTO> create(@Validated @RequestBody K8sSyncHistoryDTO dto) {
return super.create(dto);
}
@Override
public Response<K8sSyncHistoryDTO> update(@PathVariable Long id, @Validated @RequestBody K8sSyncHistoryDTO dto) {
return super.update(id, dto);
}
@Override
public Response<Void> delete(@PathVariable Long id) {
return super.delete(id);
}
@Override
public Response<K8sSyncHistoryDTO> findById(@PathVariable Long id) {
return super.findById(id);
}
@Override
public Response<List<K8sSyncHistoryDTO>> findAll() {
return super.findAll();
}
@Override
public Response<Page<K8sSyncHistoryDTO>> page(K8sSyncHistoryQuery query) {
return super.page(query);
}
@Override
public Response<List<K8sSyncHistoryDTO>> findAll(K8sSyncHistoryQuery query) {
return super.findAll(query);
}
@Override
public CompletableFuture<Response<Void>> batchProcess(List<K8sSyncHistoryDTO> dtos) {
return super.batchProcess(dtos);
}
@Operation(summary = "根据集群ID查询同步历史", description = "查询指定K8S集群的同步历史记录")
@GetMapping("/by-system/{externalSystemId}")
public Response<List<K8sSyncHistoryDTO>> findByExternalSystemId(
@Parameter(description = "K8S集群ID", required = true) @PathVariable Long externalSystemId
) {
return Response.success(k8sSyncHistoryService.findByExternalSystemId(externalSystemId));
}
@Override
protected void exportData(HttpServletResponse response, List<K8sSyncHistoryDTO> data) {
log.info("导出K8S同步历史数据数据量{}", data.size());
}
}

View File

@ -84,6 +84,29 @@ public class ThreadPoolConfig {
return executor; return executor;
} }
/**
* K8S资源同步线程池 - 使用虚拟线程Java 21+
*
* 为什么使用虚拟线程
* 1. K8S API调用是典型的**网络I/O密集型**任务
* 2. 等待K8S API响应时线程会长时间阻塞
* 3. 虚拟线程在阻塞时不占用OS线程资源消耗极低
* 4. 支持数百个并发K8S资源同步NamespaceDeploymentPod等
*
* 💡 场景
* - 定时同步K8S命名空间
* - 定时同步K8S Deployment
* - 实时查询Pod状态
* - 多集群并发同步
*/
@Bean("k8sTaskExecutor")
public SimpleAsyncTaskExecutor k8sTaskExecutor() {
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("k8s-virtual-");
executor.setVirtualThreads(true);
executor.setConcurrencyLimit(-1); // 无限制支持多集群并发
return executor;
}
/** /**
* 通用应用任务线程池 - 保留平台线程不使用虚拟线程 * 通用应用任务线程池 - 保留平台线程不使用虚拟线程
* *

View File

@ -0,0 +1,10 @@
package com.qqchen.deploy.backend.deploy.converter;
import com.qqchen.deploy.backend.deploy.dto.K8sDeploymentDTO;
import com.qqchen.deploy.backend.deploy.entity.K8sDeployment;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import org.mapstruct.Mapper;
@Mapper(config = BaseConverter.class)
public interface K8sDeploymentConverter extends BaseConverter<K8sDeployment, K8sDeploymentDTO> {
}

View File

@ -0,0 +1,10 @@
package com.qqchen.deploy.backend.deploy.converter;
import com.qqchen.deploy.backend.deploy.dto.K8sNamespaceDTO;
import com.qqchen.deploy.backend.deploy.entity.K8sNamespace;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import org.mapstruct.Mapper;
@Mapper(config = BaseConverter.class)
public interface K8sNamespaceConverter extends BaseConverter<K8sNamespace, K8sNamespaceDTO> {
}

View File

@ -0,0 +1,10 @@
package com.qqchen.deploy.backend.deploy.converter;
import com.qqchen.deploy.backend.deploy.dto.K8sSyncHistoryDTO;
import com.qqchen.deploy.backend.deploy.entity.K8sSyncHistory;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import org.mapstruct.Mapper;
@Mapper(config = BaseConverter.class)
public interface K8sSyncHistoryConverter extends BaseConverter<K8sSyncHistory, K8sSyncHistoryDTO> {
}

View File

@ -0,0 +1,51 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
import java.util.Map;
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "K8S Deployment DTO")
public class K8sDeploymentDTO extends BaseDTO {
@Schema(description = "K8S集群ID")
private Long externalSystemId;
@Schema(description = "命名空间ID")
private Long namespaceId;
@Schema(description = "Deployment名称")
private String deploymentName;
@Schema(description = "期望副本数")
private Integer replicas;
@Schema(description = "可用副本数")
private Integer availableReplicas;
@Schema(description = "就绪副本数")
private Integer readyReplicas;
@Schema(description = "容器镜像")
private String image;
@Schema(description = "标签")
private Map<String, String> labels;
@Schema(description = "选择器")
private Map<String, String> selector;
@Schema(description = "K8S中的创建时间")
private LocalDateTime k8sCreateTime;
@Schema(description = "K8S中的更新时间")
private LocalDateTime k8sUpdateTime;
@Schema(description = "YAML配置")
private String yamlConfig;
}

View File

@ -0,0 +1,32 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Map;
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "K8S命名空间DTO")
public class K8sNamespaceDTO extends BaseDTO {
@Schema(description = "K8S集群ID")
private Long externalSystemId;
@Schema(description = "命名空间名称")
private String namespaceName;
@Schema(description = "状态")
private String status;
@Schema(description = "标签")
private Map<String, String> labels;
@Schema(description = "YAML配置")
private String yamlConfig;
@Schema(description = "Deployment数量仅在列表和分页查询时填充")
private Long deploymentCount;
}

View File

@ -0,0 +1,37 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.deploy.enums.ExternalSystemSyncStatus;
import com.qqchen.deploy.backend.deploy.enums.K8sSyncType;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "K8S同步历史DTO")
public class K8sSyncHistoryDTO extends BaseDTO {
@Schema(description = "同步编号")
private String number;
@Schema(description = "同步类型")
private K8sSyncType syncType;
@Schema(description = "同步状态")
private ExternalSystemSyncStatus status;
@Schema(description = "开始时间")
private LocalDateTime startTime;
@Schema(description = "结束时间")
private LocalDateTime endTime;
@Schema(description = "错误信息")
private String errorMessage;
@Schema(description = "K8S集群ID")
private Long externalSystemId;
}

View File

@ -82,10 +82,10 @@ public class ExternalSystem extends Entity<Long> {
private LocalDateTime lastConnectTime; private LocalDateTime lastConnectTime;
/** /**
* 系统特有配置JSON格式 * 系统特有配置如kubeconfig等)
* 使用TEXT类型存储支持任意格式的配置内容
*/ */
@Column(columnDefinition = "JSON") @Column(columnDefinition = "TEXT")
private String config; private String config;
} }

View File

@ -0,0 +1,60 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.framework.domain.Entity;
import com.vladmihalcea.hibernate.type.json.JsonType;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import java.time.LocalDateTime;
import java.util.Map;
/**
* K8S Deployment实体
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "deploy_k8s_deployment")
public class K8sDeployment extends Entity<Long> {
@Column(name = "external_system_id", nullable = false)
private Long externalSystemId;
@Column(name = "namespace_id", nullable = false)
private Long namespaceId;
@Column(name = "deployment_name", nullable = false)
private String deploymentName;
@Column(name = "replicas")
private Integer replicas;
@Column(name = "available_replicas")
private Integer availableReplicas;
@Column(name = "ready_replicas")
private Integer readyReplicas;
@Column(name = "image")
private String image;
@Type(JsonType.class)
@Column(name = "labels", columnDefinition = "JSON")
private Map<String, String> labels;
@Type(JsonType.class)
@Column(name = "selector", columnDefinition = "JSON")
private Map<String, String> selector;
@Column(name = "k8s_create_time")
private LocalDateTime k8sCreateTime;
@Column(name = "k8s_update_time")
private LocalDateTime k8sUpdateTime;
@Column(name = "yaml_config", columnDefinition = "TEXT")
private String yamlConfig;
}

View File

@ -0,0 +1,37 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.framework.domain.Entity;
import com.vladmihalcea.hibernate.type.json.JsonType;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import java.util.Map;
/**
* K8S命名空间实体
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "deploy_k8s_namespace")
public class K8sNamespace extends Entity<Long> {
@Column(name = "external_system_id", nullable = false)
private Long externalSystemId;
@Column(name = "namespace_name", nullable = false)
private String namespaceName;
@Column(name = "status")
private String status;
@Type(JsonType.class)
@Column(name = "labels", columnDefinition = "JSON")
private Map<String, String> labels;
@Column(name = "yaml_config", columnDefinition = "TEXT")
private String yamlConfig;
}

View File

@ -0,0 +1,48 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.deploy.enums.ExternalSystemSyncStatus;
import com.qqchen.deploy.backend.deploy.enums.K8sSyncType;
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.Column;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* K8S同步历史实体
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "deploy_k8s_sync_history")
@LogicDelete
public class K8sSyncHistory extends Entity<Long> {
@Column(name = "sync_history_number", nullable = false)
private String number;
@Column(name = "sync_type", nullable = false)
@Enumerated(EnumType.STRING)
private K8sSyncType syncType;
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private ExternalSystemSyncStatus status;
@Column(name = "start_time", nullable = false)
private LocalDateTime startTime;
@Column(name = "end_time")
private LocalDateTime endTime;
@Column(name = "error_message", columnDefinition = "TEXT")
private String errorMessage;
@Column(name = "external_system_id", nullable = false)
private Long externalSystemId;
}

View File

@ -0,0 +1,6 @@
package com.qqchen.deploy.backend.deploy.enums;
public enum K8sSyncType {
NAMESPACE, // 同步命名空间
DEPLOYMENT // 同步Deployment
}

View File

@ -0,0 +1,57 @@
package com.qqchen.deploy.backend.deploy.integration;
import com.qqchen.deploy.backend.deploy.entity.ExternalSystem;
import com.qqchen.deploy.backend.deploy.integration.response.K8sDeploymentResponse;
import com.qqchen.deploy.backend.deploy.integration.response.K8sNamespaceResponse;
import com.qqchen.deploy.backend.system.enums.ExternalSystemTypeEnum;
import java.util.List;
/**
* K8S集成服务接口
*/
public interface IK8sServiceIntegration extends IExternalSystemIntegration {
/**
* 测试K8S连接
*
* @param system K8S系统配置
* @return 连接是否成功
*/
boolean testConnection(ExternalSystem system);
/**
* 查询所有命名空间
*
* @param externalSystem K8S系统配置
* @return 命名空间列表
*/
List<K8sNamespaceResponse> listNamespaces(ExternalSystem externalSystem);
/**
* 查询指定命名空间下的所有Deployment
*
* @param externalSystem K8S系统配置
* @param namespace 命名空间名称
* @return Deployment列表
*/
List<K8sDeploymentResponse> listDeployments(ExternalSystem externalSystem, String namespace);
/**
* 查询所有Deployment跨命名空间
*
* @param externalSystem K8S系统配置
* @return Deployment列表
*/
List<K8sDeploymentResponse> listAllDeployments(ExternalSystem externalSystem);
/**
* 获取系统类型
*
* @return K8S系统类型
*/
@Override
default ExternalSystemTypeEnum getSystemType() {
return ExternalSystemTypeEnum.K8S;
}
}

View File

@ -0,0 +1,349 @@
package com.qqchen.deploy.backend.deploy.integration.impl;
import com.qqchen.deploy.backend.deploy.entity.ExternalSystem;
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;
import com.qqchen.deploy.backend.deploy.integration.response.K8sNamespaceResponse;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.system.enums.ExternalSystemTypeEnum;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.apis.AppsV1Api;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.apis.VersionApi;
import io.kubernetes.client.openapi.models.*;
import io.kubernetes.client.util.Config;
import io.kubernetes.client.util.Yaml;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.StringReader;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* K8S集成服务实现
*/
@Slf4j
@Service
public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration implements IK8sServiceIntegration {
// K8S ApiClient缓存 - 线程安全
private static final Map<Long, K8sApiClientCache> API_CLIENT_CACHE = new ConcurrentHashMap<>();
private static final long CACHE_EXPIRE_TIME = 30 * 60 * 1000; // 30分钟过期
/**
* K8S ApiClient缓存内部类
*/
private static class K8sApiClientCache {
final ApiClient apiClient;
final long expireTime;
K8sApiClientCache(ApiClient apiClient) {
this.apiClient = apiClient;
this.expireTime = System.currentTimeMillis() + CACHE_EXPIRE_TIME;
}
boolean isExpired() {
return System.currentTimeMillis() > expireTime;
}
}
/**
* 线程安全地获取K8S ApiClient缓存
* 如果缓存不存在或已过期会重新创建
*/
private synchronized K8sApiClientCache getApiClientCache(ExternalSystem system) {
Long systemId = system.getId();
K8sApiClientCache cache = API_CLIENT_CACHE.get(systemId);
if (cache == null || cache.isExpired()) {
log.debug("K8S ApiClient缓存失效重新创建: systemId={}", systemId);
try {
ApiClient apiClient = createApiClientInternal(system);
cache = new K8sApiClientCache(apiClient);
API_CLIENT_CACHE.put(systemId, cache);
log.debug("K8S ApiClient缓存已更新: systemId={}, expireTime={}", systemId, cache.expireTime);
} catch (Exception e) {
log.error("创建K8S ApiClient失败: systemId={}", systemId, e);
throw new BusinessException(ResponseCode.K8S_CONNECTION_FAILED);
}
}
return cache;
}
@Override
public ExternalSystemTypeEnum getSystemType() {
return ExternalSystemTypeEnum.K8S;
}
/**
* 测试K8S连接
*/
@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;
}
}
/**
* 查询所有命名空间
*/
@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(
ns.getMetadata().getCreationTimestamp().toInstant(),
ZoneId.systemDefault()
)
);
}
// 序列化为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);
}
}
/**
* 查询指定命名空间下的所有Deployment
*/
@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
&& deployment.getSpec().getTemplate() != null
&& deployment.getSpec().getTemplate().getSpec() != 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(
deployment.getMetadata().getCreationTimestamp().toInstant(),
ZoneId.systemDefault()
)
);
}
// 序列化为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(), namespace, e.getMessage(), e);
throw new BusinessException(ResponseCode.K8S_DEPLOYMENT_SYNC_FAILED);
}
}
/**
* 查询所有Deployment跨命名空间
*/
@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
&& deployment.getSpec().getTemplate() != null
&& deployment.getSpec().getTemplate().getSpec() != 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(
deployment.getMetadata().getCreationTimestamp().toInstant(),
ZoneId.systemDefault()
)
);
}
// 序列化为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);
}
}
/**
* 创建K8S ApiClient对外接口使用缓存
*
* @param externalSystem K8S系统配置
* @return ApiClient
*/
private ApiClient createApiClient(ExternalSystem externalSystem) {
return getApiClientCache(externalSystem).apiClient;
}
/**
* 创建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秒连接超时
client.setReadTimeout(30000); // 30秒读取超时
return client;
}
}

View File

@ -0,0 +1,21 @@
package com.qqchen.deploy.backend.deploy.integration.response;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Map;
@Data
public class K8sDeploymentResponse {
private String name;
private String namespace;
private Integer replicas;
private Integer availableReplicas;
private Integer readyReplicas;
private String image;
private Map<String, String> labels;
private Map<String, String> selector;
private LocalDateTime creationTimestamp;
private LocalDateTime lastUpdateTime;
private String yamlConfig;
}

View File

@ -0,0 +1,15 @@
package com.qqchen.deploy.backend.deploy.integration.response;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Map;
@Data
public class K8sNamespaceResponse {
private String name;
private String status;
private Map<String, String> labels;
private LocalDateTime creationTimestamp;
private String yamlConfig;
}

View File

@ -0,0 +1,24 @@
package com.qqchen.deploy.backend.deploy.query;
import com.qqchen.deploy.backend.framework.annotation.QueryField;
import com.qqchen.deploy.backend.framework.enums.QueryType;
import com.qqchen.deploy.backend.framework.query.BaseQuery;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class K8sDeploymentQuery extends BaseQuery {
@QueryField(field = "externalSystemId")
private Long externalSystemId;
@QueryField(field = "namespaceId")
private Long namespaceId;
@QueryField(field = "deploymentName", type = QueryType.LIKE)
private String deploymentName;
@QueryField(field = "image", type = QueryType.LIKE)
private String image;
}

View File

@ -0,0 +1,21 @@
package com.qqchen.deploy.backend.deploy.query;
import com.qqchen.deploy.backend.framework.annotation.QueryField;
import com.qqchen.deploy.backend.framework.enums.QueryType;
import com.qqchen.deploy.backend.framework.query.BaseQuery;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class K8sNamespaceQuery extends BaseQuery {
@QueryField(field = "externalSystemId")
private Long externalSystemId;
@QueryField(field = "namespaceName", type = QueryType.LIKE)
private String namespaceName;
@QueryField(field = "status")
private String status;
}

View File

@ -0,0 +1,22 @@
package com.qqchen.deploy.backend.deploy.query;
import com.qqchen.deploy.backend.deploy.enums.ExternalSystemSyncStatus;
import com.qqchen.deploy.backend.deploy.enums.K8sSyncType;
import com.qqchen.deploy.backend.framework.annotation.QueryField;
import com.qqchen.deploy.backend.framework.query.BaseQuery;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class K8sSyncHistoryQuery extends BaseQuery {
@QueryField(field = "externalSystemId")
private Long externalSystemId;
@QueryField(field = "syncType")
private K8sSyncType syncType;
@QueryField(field = "status")
private ExternalSystemSyncStatus status;
}

View File

@ -26,6 +26,14 @@ public interface IJenkinsJobRepository extends IBaseRepository<JenkinsJob, Long>
*/ */
Optional<JenkinsJob> findByExternalSystemIdAndViewIdAndJobName(Long externalSystemId, Long viewId, String jobName); Optional<JenkinsJob> findByExternalSystemIdAndViewIdAndJobName(Long externalSystemId, Long viewId, String jobName);
/**
* 根据外部系统ID查询所有任务
*
* @param externalSystemId 外部系统ID
* @return 任务列表
*/
List<JenkinsJob> findByExternalSystemId(Long externalSystemId);
/** /**
* 根据外部系统ID和视图ID查询所有任务 * 根据外部系统ID和视图ID查询所有任务
* *

View File

@ -0,0 +1,29 @@
package com.qqchen.deploy.backend.deploy.repository;
import com.qqchen.deploy.backend.deploy.entity.K8sDeployment;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@Repository
public interface IK8sDeploymentRepository extends IBaseRepository<K8sDeployment, Long> {
Optional<K8sDeployment> findByNamespaceIdAndDeploymentName(Long namespaceId, String deploymentName);
List<K8sDeployment> findByExternalSystemId(Long externalSystemId);
List<K8sDeployment> findByNamespaceId(Long namespaceId);
Long countByNamespaceIdAndDeletedFalse(Long namespaceId);
@Query("SELECT d.namespaceId as namespaceId, COUNT(d.id) as count " +
"FROM K8sDeployment d " +
"WHERE d.namespaceId IN :namespaceIds AND d.deleted = false " +
"GROUP BY d.namespaceId")
List<Object[]> countByNamespaceIds(@Param("namespaceIds") Collection<Long> namespaceIds);
}

View File

@ -0,0 +1,18 @@
package com.qqchen.deploy.backend.deploy.repository;
import com.qqchen.deploy.backend.deploy.entity.K8sNamespace;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface IK8sNamespaceRepository extends IBaseRepository<K8sNamespace, Long> {
Optional<K8sNamespace> findByExternalSystemIdAndNamespaceName(Long externalSystemId, String namespaceName);
List<K8sNamespace> findByExternalSystemId(Long externalSystemId);
Long countByExternalSystemIdAndDeletedFalse(Long externalSystemId);
}

View File

@ -0,0 +1,13 @@
package com.qqchen.deploy.backend.deploy.repository;
import com.qqchen.deploy.backend.deploy.entity.K8sSyncHistory;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface IK8sSyncHistoryRepository extends IBaseRepository<K8sSyncHistory, Long> {
List<K8sSyncHistory> findByExternalSystemIdOrderByCreateTimeDesc(Long externalSystemId);
}

View File

@ -0,0 +1,23 @@
package com.qqchen.deploy.backend.deploy.service;
import com.qqchen.deploy.backend.deploy.dto.K8sDeploymentDTO;
import com.qqchen.deploy.backend.deploy.entity.ExternalSystem;
import com.qqchen.deploy.backend.deploy.entity.K8sDeployment;
import com.qqchen.deploy.backend.deploy.entity.K8sNamespace;
import com.qqchen.deploy.backend.deploy.query.K8sDeploymentQuery;
import com.qqchen.deploy.backend.framework.service.IBaseService;
import java.util.List;
public interface IK8sDeploymentService extends IBaseService<K8sDeployment, K8sDeploymentDTO, K8sDeploymentQuery, Long> {
Integer syncDeployments(ExternalSystem externalSystem, K8sNamespace namespace);
void syncDeployments(Long externalSystemId);
void syncDeployments(Long externalSystemId, Long namespaceId);
List<K8sDeploymentDTO> findByExternalSystemId(Long externalSystemId);
List<K8sDeploymentDTO> findByNamespaceId(Long namespaceId);
}

View File

@ -0,0 +1,18 @@
package com.qqchen.deploy.backend.deploy.service;
import com.qqchen.deploy.backend.deploy.dto.K8sNamespaceDTO;
import com.qqchen.deploy.backend.deploy.entity.ExternalSystem;
import com.qqchen.deploy.backend.deploy.entity.K8sNamespace;
import com.qqchen.deploy.backend.deploy.query.K8sNamespaceQuery;
import com.qqchen.deploy.backend.framework.service.IBaseService;
import java.util.List;
public interface IK8sNamespaceService extends IBaseService<K8sNamespace, K8sNamespaceDTO, K8sNamespaceQuery, Long> {
Integer syncNamespaces(ExternalSystem externalSystem);
void syncNamespaces(Long externalSystemId);
List<K8sNamespaceDTO> findByExternalSystemId(Long externalSystemId);
}

View File

@ -0,0 +1,13 @@
package com.qqchen.deploy.backend.deploy.service;
import com.qqchen.deploy.backend.deploy.dto.K8sSyncHistoryDTO;
import com.qqchen.deploy.backend.deploy.entity.K8sSyncHistory;
import com.qqchen.deploy.backend.deploy.query.K8sSyncHistoryQuery;
import com.qqchen.deploy.backend.framework.service.IBaseService;
import java.util.List;
public interface IK8sSyncHistoryService extends IBaseService<K8sSyncHistory, K8sSyncHistoryDTO, K8sSyncHistoryQuery, Long> {
List<K8sSyncHistoryDTO> findByExternalSystemId(Long externalSystemId);
}

View File

@ -29,11 +29,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.OptionalDouble;
import java.util.Map;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -150,21 +146,32 @@ public class JenkinsJobServiceImpl extends BaseServiceImpl<JenkinsJob, JenkinsJo
return; return;
} }
// 4. 遍历视图同步每个视图下的任务 // 4. 两阶段同步先收集所有Job再批量更新避免并发冲突
int totalSyncedJobs = 0; Map<String, JenkinsJobResponse> allJobResponses = new LinkedHashMap<>();
Map<String, Set<Long>> jobViewMapping = new HashMap<>();
StringBuilder errorMessages = new StringBuilder(); StringBuilder errorMessages = new StringBuilder();
// 阶段1收集所有View的Job信息
for (JenkinsView view : views) { for (JenkinsView view : views) {
try { try {
Integer syncedJobs = syncJobsByView(externalSystem, view); List<JenkinsJobResponse> jobResponses = jenkinsServiceIntegration.listJobs(externalSystem, view.getViewName());
totalSyncedJobs += syncedJobs; for (JenkinsJobResponse jobResponse : jobResponses) {
log.info("Successfully synchronized {} jobs for view: {}", syncedJobs, view.getViewName()); String jobName = jobResponse.getName();
// 保存Job信息同一Job只保留一份
allJobResponses.putIfAbsent(jobName, jobResponse);
// 记录Job-View关系
jobViewMapping.computeIfAbsent(jobName, k -> new HashSet<>()).add(view.getId());
}
log.debug("Collected {} jobs from view: {}", jobResponses.size(), view.getViewName());
} catch (Exception e) { } catch (Exception e) {
// 记录错误但继续同步其他视图 String errorMessage = String.format("Failed to fetch jobs for view %s: %s", view.getViewName(), e.getMessage());
String errorMessage = String.format("Failed to sync jobs for view %s: %s", view.getViewName(), e.getMessage());
log.error(errorMessage, e); log.error(errorMessage, e);
errorMessages.append(errorMessage).append("\n"); errorMessages.append(errorMessage).append("\n");
} }
} }
// 阶段2批量更新Job每个Job只更新一次避免并发冲突
int totalSyncedJobs = syncJobsInBatch(externalSystem, allJobResponses, jobViewMapping);
// 5. 更新同步历史状态 // 5. 更新同步历史状态
if (errorMessages.length() > 0) { if (errorMessages.length() > 0) {
@ -185,6 +192,62 @@ public class JenkinsJobServiceImpl extends BaseServiceImpl<JenkinsJob, JenkinsJo
} }
} }
/**
* 批量同步Job避免并发冲突
*
* @param externalSystem 外部系统
* @param allJobResponses 所有Job的响应数据去重后
* @param jobViewMapping Job与View的映射关系
* @return 同步的任务数量
*/
private int syncJobsInBatch(ExternalSystem externalSystem,
Map<String, JenkinsJobResponse> allJobResponses,
Map<String, Set<Long>> jobViewMapping) {
if (allJobResponses.isEmpty()) {
log.debug("No jobs to sync for external system: {}", externalSystem.getId());
return 0;
}
// 1. 查询所有现有Job按externalSystemId不按viewId
List<JenkinsJob> existingJobs = jenkinsJobRepository.findByExternalSystemId(externalSystem.getId());
Map<String, JenkinsJob> existingJobMap = existingJobs.stream()
.collect(Collectors.toMap(JenkinsJob::getJobName, Function.identity(), (old, newVal) -> old));
// 2. 批量更新/新增Job
List<JenkinsJob> jobsToSave = new ArrayList<>();
for (Map.Entry<String, JenkinsJobResponse> entry : allJobResponses.entrySet()) {
String jobName = entry.getKey();
JenkinsJobResponse jobResponse = entry.getValue();
Set<Long> viewIds = jobViewMapping.get(jobName);
JenkinsJob jenkinsJob = existingJobMap.get(jobName);
if (jenkinsJob == null) {
// 新Job使用第一个View作为主View
jenkinsJob = new JenkinsJob();
jenkinsJob.setExternalSystemId(externalSystem.getId());
jenkinsJob.setJobName(jobName);
jenkinsJob.setViewId(viewIds.iterator().next()); // 设置第一个View
log.debug("Creating new Jenkins job: {}", jobName);
} else {
// 已存在的Job保持原viewId或更新为第一个View
if (!viewIds.contains(jenkinsJob.getViewId())) {
jenkinsJob.setViewId(viewIds.iterator().next());
}
log.debug("Updating existing Jenkins job: {}", jobName);
}
// 更新Job信息
updateJobFromResponse(jenkinsJob, jobResponse);
jobsToSave.add(jenkinsJob);
}
// 3. 批量保存
jenkinsJobRepository.saveAll(jobsToSave);
log.info("Successfully synchronized {} unique jobs (avoiding duplicates)", jobsToSave.size());
return jobsToSave.size();
}
/** /**
* 同步指定视图下的Jenkins任务 * 同步指定视图下的Jenkins任务
* *

View File

@ -0,0 +1,226 @@
package com.qqchen.deploy.backend.deploy.service.impl;
import com.qqchen.deploy.backend.deploy.converter.K8sDeploymentConverter;
import com.qqchen.deploy.backend.deploy.dto.K8sDeploymentDTO;
import com.qqchen.deploy.backend.deploy.dto.K8sSyncHistoryDTO;
import com.qqchen.deploy.backend.deploy.entity.ExternalSystem;
import com.qqchen.deploy.backend.deploy.entity.K8sDeployment;
import com.qqchen.deploy.backend.deploy.entity.K8sNamespace;
import com.qqchen.deploy.backend.deploy.enums.ExternalSystemSyncStatus;
import com.qqchen.deploy.backend.deploy.enums.K8sSyncType;
import com.qqchen.deploy.backend.deploy.integration.IK8sServiceIntegration;
import com.qqchen.deploy.backend.deploy.integration.response.K8sDeploymentResponse;
import com.qqchen.deploy.backend.deploy.query.K8sDeploymentQuery;
import com.qqchen.deploy.backend.deploy.repository.IExternalSystemRepository;
import com.qqchen.deploy.backend.deploy.repository.IK8sDeploymentRepository;
import com.qqchen.deploy.backend.deploy.repository.IK8sNamespaceRepository;
import com.qqchen.deploy.backend.deploy.service.IK8sDeploymentService;
import com.qqchen.deploy.backend.deploy.service.IK8sSyncHistoryService;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@Slf4j
@Service
public class K8sDeploymentServiceImpl extends BaseServiceImpl<K8sDeployment, K8sDeploymentDTO, K8sDeploymentQuery, Long>
implements IK8sDeploymentService {
@Resource
private IK8sDeploymentRepository k8sDeploymentRepository;
@Resource
private IK8sNamespaceRepository k8sNamespaceRepository;
@Resource
private IExternalSystemRepository externalSystemRepository;
@Resource
private K8sDeploymentConverter k8sDeploymentConverter;
@Resource
private IK8sServiceIntegration k8sServiceIntegration;
@Resource
private IK8sSyncHistoryService k8sSyncHistoryService;
@Override
@Transactional(rollbackFor = Exception.class)
public Integer syncDeployments(ExternalSystem externalSystem, K8sNamespace namespace) {
log.info("开始同步K8S Deployment集群: {}, Namespace: {}", externalSystem.getName(), namespace.getNamespaceName());
try {
List<K8sDeploymentResponse> deploymentResponses = k8sServiceIntegration.listDeployments(
externalSystem, namespace.getNamespaceName()
);
// 性能优化批量查询现有数据避免N+1问题
List<K8sDeployment> existingDeployments = k8sDeploymentRepository.findByNamespaceId(namespace.getId());
Map<String, K8sDeployment> existingMap = existingDeployments.stream()
.collect(java.util.stream.Collectors.toMap(K8sDeployment::getDeploymentName, d -> d));
// 收集K8S中存在的Deployment名称
Set<String> k8sDeploymentNames = deploymentResponses.stream()
.map(K8sDeploymentResponse::getName)
.collect(java.util.stream.Collectors.toSet());
List<K8sDeployment> deploymentsToSave = new ArrayList<>();
// 1. 更新/新增K8S中存在的资源
for (K8sDeploymentResponse response : deploymentResponses) {
K8sDeployment deployment = existingMap.get(response.getName());
if (deployment == null) {
deployment = new K8sDeployment();
deployment.setExternalSystemId(externalSystem.getId());
deployment.setNamespaceId(namespace.getId());
deployment.setDeploymentName(response.getName());
} else {
// 如果之前被软删除恢复它
deployment.setDeleted(false);
}
deployment.setReplicas(response.getReplicas());
deployment.setAvailableReplicas(response.getAvailableReplicas());
deployment.setReadyReplicas(response.getReadyReplicas());
deployment.setImage(response.getImage());
deployment.setLabels(response.getLabels());
deployment.setSelector(response.getSelector());
deployment.setK8sCreateTime(response.getCreationTimestamp());
deployment.setK8sUpdateTime(response.getLastUpdateTime());
deployment.setYamlConfig(response.getYamlConfig());
deploymentsToSave.add(deployment);
}
// 2. 软删除K8S中不存在但数据库中存在的资源
for (K8sDeployment existing : existingDeployments) {
if (!k8sDeploymentNames.contains(existing.getDeploymentName()) && !existing.getDeleted()) {
existing.setDeleted(true);
deploymentsToSave.add(existing);
log.info("标记删除Deployment: {}", existing.getDeploymentName());
}
}
// 性能优化批量保存
k8sDeploymentRepository.saveAll(deploymentsToSave);
int syncCount = (int) deploymentResponses.size();
log.info("K8S Deployment同步完成集群: {}, Namespace: {}, 同步数量: {}",
externalSystem.getName(), namespace.getNamespaceName(), syncCount);
return syncCount;
} catch (Exception e) {
log.error("K8S Deployment同步失败集群: {}, Namespace: {}, 错误: {}",
externalSystem.getName(), namespace.getNamespaceName(), e.getMessage(), e);
throw new BusinessException(ResponseCode.K8S_DEPLOYMENT_SYNC_FAILED);
}
}
@Override
@Async("k8sTaskExecutor")
@Transactional(rollbackFor = Exception.class)
public void syncDeployments(Long externalSystemId) {
log.info("异步同步K8S Deployment所有命名空间集群ID: {}", externalSystemId);
K8sSyncHistoryDTO syncHistory = new K8sSyncHistoryDTO();
syncHistory.setExternalSystemId(externalSystemId);
syncHistory.setSyncType(K8sSyncType.DEPLOYMENT);
syncHistory.setStatus(ExternalSystemSyncStatus.RUNNING);
syncHistory.setStartTime(LocalDateTime.now());
syncHistory.setNumber("K8S-DEPLOY-SYNC-" + System.currentTimeMillis());
syncHistory = k8sSyncHistoryService.create(syncHistory);
try {
ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId)
.orElseThrow(() -> new BusinessException(ResponseCode.K8S_CLUSTER_NOT_FOUND));
List<K8sNamespace> namespaces = k8sNamespaceRepository.findByExternalSystemId(externalSystemId);
int totalSyncCount = 0;
for (K8sNamespace namespace : namespaces) {
int count = syncDeployments(externalSystem, namespace);
totalSyncCount += count;
}
syncHistory.setStatus(ExternalSystemSyncStatus.SUCCESS);
syncHistory.setEndTime(LocalDateTime.now());
k8sSyncHistoryService.update(syncHistory.getId(), syncHistory);
log.info("K8S Deployment异步同步成功集群ID: {}, 总同步数量: {}", externalSystemId, totalSyncCount);
} catch (Exception e) {
log.error("K8S Deployment异步同步失败集群ID: {}, 错误: {}", externalSystemId, e.getMessage(), e);
syncHistory.setStatus(ExternalSystemSyncStatus.FAILED);
syncHistory.setEndTime(LocalDateTime.now());
syncHistory.setErrorMessage(e.getMessage());
k8sSyncHistoryService.update(syncHistory.getId(), syncHistory);
}
}
@Override
@Async("k8sTaskExecutor")
@Transactional(rollbackFor = Exception.class)
public void syncDeployments(Long externalSystemId, Long namespaceId) {
log.info("异步同步K8S Deployment集群ID: {}, 命名空间ID: {}", externalSystemId, namespaceId);
K8sSyncHistoryDTO syncHistory = new K8sSyncHistoryDTO();
syncHistory.setExternalSystemId(externalSystemId);
syncHistory.setSyncType(K8sSyncType.DEPLOYMENT);
syncHistory.setStatus(ExternalSystemSyncStatus.RUNNING);
syncHistory.setStartTime(LocalDateTime.now());
syncHistory.setNumber("K8S-DEPLOY-SYNC-" + System.currentTimeMillis());
syncHistory = k8sSyncHistoryService.create(syncHistory);
try {
ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId)
.orElseThrow(() -> new BusinessException(ResponseCode.K8S_CLUSTER_NOT_FOUND));
K8sNamespace namespace = k8sNamespaceRepository.findById(namespaceId)
.orElseThrow(() -> new BusinessException(ResponseCode.K8S_NAMESPACE_NOT_FOUND));
int syncCount = syncDeployments(externalSystem, namespace);
syncHistory.setStatus(ExternalSystemSyncStatus.SUCCESS);
syncHistory.setEndTime(LocalDateTime.now());
k8sSyncHistoryService.update(syncHistory.getId(), syncHistory);
log.info("K8S Deployment异步同步成功集群ID: {}, 命名空间ID: {}, 同步数量: {}",
externalSystemId, namespaceId, syncCount);
} catch (Exception e) {
log.error("K8S Deployment异步同步失败集群ID: {}, 命名空间ID: {}, 错误: {}",
externalSystemId, namespaceId, e.getMessage(), e);
syncHistory.setStatus(ExternalSystemSyncStatus.FAILED);
syncHistory.setEndTime(LocalDateTime.now());
syncHistory.setErrorMessage(e.getMessage());
k8sSyncHistoryService.update(syncHistory.getId(), syncHistory);
}
}
@Override
public List<K8sDeploymentDTO> findByExternalSystemId(Long externalSystemId) {
List<K8sDeployment> deployments = k8sDeploymentRepository.findByExternalSystemId(externalSystemId);
return k8sDeploymentConverter.toDtoList(deployments);
}
@Override
public List<K8sDeploymentDTO> findByNamespaceId(Long namespaceId) {
List<K8sDeployment> deployments = k8sDeploymentRepository.findByNamespaceId(namespaceId);
return k8sDeploymentConverter.toDtoList(deployments);
}
}

View File

@ -0,0 +1,194 @@
package com.qqchen.deploy.backend.deploy.service.impl;
import com.qqchen.deploy.backend.deploy.converter.K8sNamespaceConverter;
import com.qqchen.deploy.backend.deploy.dto.K8sNamespaceDTO;
import com.qqchen.deploy.backend.deploy.dto.K8sSyncHistoryDTO;
import com.qqchen.deploy.backend.deploy.entity.ExternalSystem;
import com.qqchen.deploy.backend.deploy.entity.K8sNamespace;
import com.qqchen.deploy.backend.deploy.enums.ExternalSystemSyncStatus;
import com.qqchen.deploy.backend.deploy.enums.K8sSyncType;
import com.qqchen.deploy.backend.deploy.integration.IK8sServiceIntegration;
import com.qqchen.deploy.backend.deploy.integration.response.K8sNamespaceResponse;
import com.qqchen.deploy.backend.deploy.query.K8sNamespaceQuery;
import com.qqchen.deploy.backend.deploy.repository.IExternalSystemRepository;
import com.qqchen.deploy.backend.deploy.repository.IK8sNamespaceRepository;
import com.qqchen.deploy.backend.deploy.service.IK8sNamespaceService;
import com.qqchen.deploy.backend.deploy.service.IK8sSyncHistoryService;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@Slf4j
@Service
public class K8sNamespaceServiceImpl extends BaseServiceImpl<K8sNamespace, K8sNamespaceDTO, K8sNamespaceQuery, Long>
implements IK8sNamespaceService {
@Resource
private IK8sNamespaceRepository k8sNamespaceRepository;
@Resource
private IExternalSystemRepository externalSystemRepository;
@Resource
private K8sNamespaceConverter k8sNamespaceConverter;
@Resource
private IK8sServiceIntegration k8sServiceIntegration;
@Resource
private IK8sSyncHistoryService k8sSyncHistoryService;
@Resource
private com.qqchen.deploy.backend.deploy.repository.IK8sDeploymentRepository k8sDeploymentRepository;
@Override
@Transactional(rollbackFor = Exception.class)
public Integer syncNamespaces(ExternalSystem externalSystem) {
log.info("开始同步K8S命名空间集群: {}", externalSystem.getName());
try {
List<K8sNamespaceResponse> namespaceResponses = k8sServiceIntegration.listNamespaces(externalSystem);
// 性能优化批量查询现有数据避免N+1问题
List<K8sNamespace> existingNamespaces = k8sNamespaceRepository.findByExternalSystemId(externalSystem.getId());
Map<String, K8sNamespace> existingMap = existingNamespaces.stream()
.collect(java.util.stream.Collectors.toMap(K8sNamespace::getNamespaceName, ns -> ns));
// 收集K8S中存在的命名空间名称
Set<String> k8sNamespaceNames = namespaceResponses.stream()
.map(K8sNamespaceResponse::getName)
.collect(java.util.stream.Collectors.toSet());
List<K8sNamespace> namespacesToSave = new ArrayList<>();
// 1. 更新/新增K8S中存在的资源
for (K8sNamespaceResponse response : namespaceResponses) {
K8sNamespace namespace = existingMap.get(response.getName());
if (namespace == null) {
namespace = new K8sNamespace();
namespace.setExternalSystemId(externalSystem.getId());
namespace.setNamespaceName(response.getName());
} else {
// 如果之前被软删除恢复它
namespace.setDeleted(false);
}
namespace.setStatus(response.getStatus());
namespace.setLabels(response.getLabels());
namespace.setYamlConfig(response.getYamlConfig());
namespacesToSave.add(namespace);
}
// 2. 软删除K8S中不存在但数据库中存在的资源
for (K8sNamespace existing : existingNamespaces) {
if (!k8sNamespaceNames.contains(existing.getNamespaceName()) && !existing.getDeleted()) {
existing.setDeleted(true);
namespacesToSave.add(existing);
log.info("标记删除命名空间: {}", existing.getNamespaceName());
}
}
// 性能优化批量保存
k8sNamespaceRepository.saveAll(namespacesToSave);
int syncCount = (int) namespaceResponses.size();
log.info("K8S命名空间同步完成集群: {}, 同步数量: {}", externalSystem.getName(), syncCount);
return syncCount;
} catch (Exception e) {
log.error("K8S命名空间同步失败集群: {}, 错误: {}", externalSystem.getName(), e.getMessage(), e);
throw new BusinessException(ResponseCode.K8S_NAMESPACE_SYNC_FAILED);
}
}
@Override
@Async("k8sTaskExecutor")
@Transactional(rollbackFor = Exception.class)
public void syncNamespaces(Long externalSystemId) {
log.info("异步同步K8S命名空间集群ID: {}", externalSystemId);
K8sSyncHistoryDTO syncHistory = new K8sSyncHistoryDTO();
syncHistory.setExternalSystemId(externalSystemId);
syncHistory.setSyncType(K8sSyncType.NAMESPACE);
syncHistory.setStatus(ExternalSystemSyncStatus.RUNNING);
syncHistory.setStartTime(LocalDateTime.now());
syncHistory.setNumber("K8S-NS-SYNC-" + System.currentTimeMillis());
syncHistory = k8sSyncHistoryService.create(syncHistory);
try {
ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId)
.orElseThrow(() -> new BusinessException(ResponseCode.K8S_CLUSTER_NOT_FOUND));
int syncCount = syncNamespaces(externalSystem);
syncHistory.setStatus(ExternalSystemSyncStatus.SUCCESS);
syncHistory.setEndTime(LocalDateTime.now());
k8sSyncHistoryService.update(syncHistory.getId(), syncHistory);
log.info("K8S命名空间异步同步成功集群ID: {}, 同步数量: {}", externalSystemId, syncCount);
} catch (Exception e) {
log.error("K8S命名空间异步同步失败集群ID: {}, 错误: {}", externalSystemId, e.getMessage(), e);
syncHistory.setStatus(ExternalSystemSyncStatus.FAILED);
syncHistory.setEndTime(LocalDateTime.now());
syncHistory.setErrorMessage(e.getMessage());
k8sSyncHistoryService.update(syncHistory.getId(), syncHistory);
}
}
@Override
public List<K8sNamespaceDTO> findByExternalSystemId(Long externalSystemId) {
List<K8sNamespace> namespaces = k8sNamespaceRepository.findByExternalSystemId(externalSystemId);
return k8sNamespaceConverter.toDtoList(namespaces);
}
@Override
public org.springframework.data.domain.Page<K8sNamespaceDTO> page(K8sNamespaceQuery query) {
org.springframework.data.domain.Page<K8sNamespaceDTO> page = super.page(query);
fillDeploymentCounts(page.getContent());
return page;
}
@Override
public List<K8sNamespaceDTO> findAll(K8sNamespaceQuery query) {
List<K8sNamespaceDTO> list = super.findAll(query);
fillDeploymentCounts(list);
return list;
}
private void fillDeploymentCounts(List<K8sNamespaceDTO> namespaces) {
if (namespaces.isEmpty()) {
return;
}
List<Long> namespaceIds = namespaces.stream()
.map(K8sNamespaceDTO::getId)
.collect(java.util.stream.Collectors.toList());
List<Object[]> countResults = k8sDeploymentRepository.countByNamespaceIds(namespaceIds);
Map<Long, Long> deploymentCountMap = countResults.stream()
.collect(java.util.stream.Collectors.toMap(
arr -> (Long) arr[0],
arr -> (Long) arr[1]
));
namespaces.forEach(namespace -> {
Long deploymentCount = deploymentCountMap.getOrDefault(namespace.getId(), 0L);
namespace.setDeploymentCount(deploymentCount);
});
}
}

View File

@ -0,0 +1,32 @@
package com.qqchen.deploy.backend.deploy.service.impl;
import com.qqchen.deploy.backend.deploy.converter.K8sSyncHistoryConverter;
import com.qqchen.deploy.backend.deploy.dto.K8sSyncHistoryDTO;
import com.qqchen.deploy.backend.deploy.entity.K8sSyncHistory;
import com.qqchen.deploy.backend.deploy.query.K8sSyncHistoryQuery;
import com.qqchen.deploy.backend.deploy.repository.IK8sSyncHistoryRepository;
import com.qqchen.deploy.backend.deploy.service.IK8sSyncHistoryService;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Service
public class K8sSyncHistoryServiceImpl extends BaseServiceImpl<K8sSyncHistory, K8sSyncHistoryDTO, K8sSyncHistoryQuery, Long>
implements IK8sSyncHistoryService {
@Resource
private IK8sSyncHistoryRepository k8sSyncHistoryRepository;
@Resource
private K8sSyncHistoryConverter k8sSyncHistoryConverter;
@Override
public List<K8sSyncHistoryDTO> findByExternalSystemId(Long externalSystemId) {
List<K8sSyncHistory> histories = k8sSyncHistoryRepository.findByExternalSystemIdOrderByCreateTimeDesc(externalSystemId);
return k8sSyncHistoryConverter.toDtoList(histories);
}
}

View File

@ -270,7 +270,26 @@ public enum ResponseCode {
JENKINS_QUEUE_TIMEOUT(3205, "jenkins.queue.timeout"), JENKINS_QUEUE_TIMEOUT(3205, "jenkins.queue.timeout"),
JENKINS_BUILD_TIMEOUT(3206, "jenkins.build.timeout"), JENKINS_BUILD_TIMEOUT(3206, "jenkins.build.timeout"),
JENKINS_API_ERROR(3207, "jenkins.api.error"), JENKINS_API_ERROR(3207, "jenkins.api.error"),
JENKINS_RESPONSE_PARSE_ERROR(3208, "jenkins.response.parse.error"); JENKINS_RESPONSE_PARSE_ERROR(3208, "jenkins.response.parse.error"),
// K8S集成错误码 (3220-3239)
K8S_CLUSTER_NOT_FOUND(3220, "k8s.cluster.not.found"),
K8S_CONNECTION_FAILED(3221, "k8s.connection.failed"),
K8S_AUTH_FAILED(3222, "k8s.auth.failed"),
K8S_CONFIG_INVALID(3223, "k8s.config.invalid"),
K8S_CONFIG_EMPTY(3224, "k8s.config.empty"),
K8S_KUBECONFIG_INVALID(3225, "k8s.kubeconfig.invalid"),
K8S_KUBECONFIG_EMPTY(3226, "k8s.kubeconfig.empty"),
K8S_NAMESPACE_NOT_FOUND(3227, "k8s.namespace.not.found"),
K8S_DEPLOYMENT_NOT_FOUND(3228, "k8s.deployment.not.found"),
K8S_API_ERROR(3229, "k8s.api.error"),
K8S_SERVER_ERROR(3230, "k8s.server.error"),
K8S_SYNC_FAILED(3231, "k8s.sync.failed"),
K8S_NAMESPACE_SYNC_FAILED(3232, "k8s.namespace.sync.failed"),
K8S_DEPLOYMENT_SYNC_FAILED(3233, "k8s.deployment.sync.failed"),
K8S_POD_NOT_FOUND(3234, "k8s.pod.not.found"),
K8S_RESOURCE_NOT_FOUND(3235, "k8s.resource.not.found"),
K8S_OPERATION_FAILED(3236, "k8s.operation.failed");
private final int code; private final int code;
private final String messageKey; // 国际化消息key private final String messageKey; // 国际化消息key

View File

@ -6,5 +6,6 @@ package com.qqchen.deploy.backend.system.enums;
public enum ExternalSystemAuthTypeEnum { public enum ExternalSystemAuthTypeEnum {
BASIC, BASIC,
TOKEN, TOKEN,
OAUTH OAUTH,
KUBECONFIG
} }

View File

@ -6,5 +6,6 @@ package com.qqchen.deploy.backend.system.enums;
public enum ExternalSystemTypeEnum { public enum ExternalSystemTypeEnum {
JENKINS, JENKINS,
GIT, GIT,
ZENTAO ZENTAO,
K8S
} }

File diff suppressed because one or more lines are too long

View File

@ -283,7 +283,7 @@ CREATE TABLE sys_external_system
sync_status VARCHAR(50) NULL COMMENT '同步状态SUCCESS/FAILED/RUNNING', sync_status VARCHAR(50) NULL COMMENT '同步状态SUCCESS/FAILED/RUNNING',
last_sync_time DATETIME(6) NULL COMMENT '最后同步时间', last_sync_time DATETIME(6) NULL COMMENT '最后同步时间',
last_connect_time DATETIME(6) NULL COMMENT '最近连接成功时间', last_connect_time DATETIME(6) NULL COMMENT '最近连接成功时间',
config JSON NULL COMMENT '系统特有配置', config TEXT NULL COMMENT '系统特有配置如kubeconfig等',
CONSTRAINT UK_external_system_name UNIQUE (name), CONSTRAINT UK_external_system_name UNIQUE (name),
CONSTRAINT UK_external_system_type_url UNIQUE (type, url) CONSTRAINT UK_external_system_type_url UNIQUE (type, url)
@ -1449,3 +1449,86 @@ CREATE TABLE deploy_team_bookmark
CONSTRAINT fk_bookmark_team FOREIGN KEY (team_id) REFERENCES deploy_team (id), CONSTRAINT fk_bookmark_team FOREIGN KEY (team_id) REFERENCES deploy_team (id),
CONSTRAINT fk_bookmark_category FOREIGN KEY (category_id) REFERENCES deploy_team_bookmark_category (id) CONSTRAINT fk_bookmark_category FOREIGN KEY (category_id) REFERENCES deploy_team_bookmark_category (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='团队导航书签表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='团队导航书签表';
-- --------------------------------------------------------------------------------------
-- K8S同步表
-- --------------------------------------------------------------------------------------
-- K8S命名空间表
CREATE TABLE deploy_k8s_namespace
(
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
create_by VARCHAR(50) NULL COMMENT '创建人',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(50) NULL COMMENT '更新人',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
version INT DEFAULT 1 COMMENT '版本号',
deleted BOOLEAN DEFAULT FALSE COMMENT '是否删除',
external_system_id BIGINT NOT NULL COMMENT 'K8S集群ID外部系统ID',
namespace_name VARCHAR(255) NOT NULL COMMENT '命名空间名称',
status VARCHAR(50) NULL COMMENT '状态',
labels JSON NULL COMMENT '标签',
yaml_config TEXT NULL COMMENT '完整的YAML配置',
UNIQUE KEY uk_system_namespace (external_system_id, namespace_name),
INDEX idx_external_system (external_system_id),
INDEX idx_deleted (deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='K8S命名空间表';
-- K8S Deployment表
CREATE TABLE deploy_k8s_deployment
(
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
create_by VARCHAR(50) NULL COMMENT '创建人',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(50) NULL COMMENT '更新人',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
version INT DEFAULT 1 COMMENT '版本号',
deleted BOOLEAN DEFAULT FALSE COMMENT '是否删除',
external_system_id BIGINT NOT NULL COMMENT 'K8S集群ID外部系统ID',
namespace_id BIGINT NOT NULL COMMENT '命名空间ID',
deployment_name VARCHAR(255) NOT NULL COMMENT 'Deployment名称',
replicas INT NULL COMMENT '期望副本数',
available_replicas INT NULL COMMENT '可用副本数',
ready_replicas INT NULL COMMENT '就绪副本数',
image VARCHAR(500) NULL COMMENT '容器镜像',
labels JSON NULL COMMENT '标签',
selector JSON NULL COMMENT '选择器',
yaml_config TEXT NULL COMMENT '完整的YAML配置',
k8s_create_time DATETIME NULL COMMENT 'K8S中的创建时间',
k8s_update_time DATETIME NULL COMMENT 'K8S中的更新时间',
UNIQUE KEY uk_namespace_deployment (namespace_id, deployment_name),
INDEX idx_external_system (external_system_id),
INDEX idx_namespace (namespace_id),
INDEX idx_deleted (deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='K8S Deployment表';
-- K8S同步历史表
CREATE TABLE deploy_k8s_sync_history
(
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
create_by VARCHAR(50) NULL COMMENT '创建人',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(50) NULL COMMENT '更新人',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
version INT DEFAULT 1 COMMENT '版本号',
deleted BOOLEAN DEFAULT FALSE COMMENT '是否删除',
sync_history_number VARCHAR(100) NOT NULL COMMENT '同步编号',
sync_type VARCHAR(50) NOT NULL COMMENT '同步类型NAMESPACE/DEPLOYMENT',
status VARCHAR(50) NOT NULL COMMENT '同步状态SUCCESS/FAILED/RUNNING',
start_time DATETIME NOT NULL COMMENT '开始时间',
end_time DATETIME NULL COMMENT '结束时间',
error_message TEXT NULL COMMENT '错误信息',
external_system_id BIGINT NOT NULL COMMENT 'K8S集群ID外部系统ID',
INDEX idx_external_system (external_system_id),
INDEX idx_sync_time (create_time),
INDEX idx_deleted (deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='K8S同步历史表';