feat(k8s): M5 Deployment 暂停更新 + CI/CD 集成

- 后端: PauseDeployment/ResumeDeployment 客户端方法 (PATCH spec.paused)
- 后端: ID路由 + Kuboard code路由 pause/resume handlers
- 后端: Deployment 列表 summary 增加 paused 字段
- 前端: 操作菜单增加「暂停更新/恢复更新」动态切换 + 暂停 Badge
- 前端: CI/CD 发布按钮跳转流水线页面 (namespace+deployment 上下文)
- 前端: api.ts 增加 pauseDeployment/resumeDeployment 方法
- 验证: 5项端到端测试全部通过 (Python urllib)
This commit is contained in:
attacker committed 2026-06-23 03:06:13 +08:00
1 parent 23f237288d
commit 2e66908e20
8 files changed
+194 -8

No files matched your search

@@ -1163,6 +1163,46 @@ func (h *KubernetesHandler) RestartDeployment(c *gin.Context) {
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "重启指令已下发"})
}
// PauseDeployment 暂停 Deployment 滚动更新(spec.paused=true)
func (h *KubernetesHandler) PauseDeployment(c *gin.Context) {
clusterID, _ := strconv.Atoi(c.Param("id"))
namespace := c.Param("namespace")
name := c.Param("name")
client, _, err := h.getK8sClient(clusterID)
if err != nil {
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: err.Error()})
return
}
if err := client.PauseDeployment(namespace, name); err != nil {
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "暂停更新失败: " + err.Error()})
return
}
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "已暂停滚动更新"})
}
// ResumeDeployment 恢复 Deployment 滚动更新(spec.paused=false)
func (h *KubernetesHandler) ResumeDeployment(c *gin.Context) {
clusterID, _ := strconv.Atoi(c.Param("id"))
namespace := c.Param("namespace")
name := c.Param("name")
client, _, err := h.getK8sClient(clusterID)
if err != nil {
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: err.Error()})
return
}
if err := client.ResumeDeployment(namespace, name); err != nil {
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "恢复更新失败: " + err.Error()})
return
}
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "已恢复滚动更新"})
}
// ============================================================================
// HPA 管理
// ============================================================================
@@ -1543,6 +1583,12 @@ func (h *KubernetesHandler) RestartDeploymentNS(c *gin.Context) {
func (h *KubernetesHandler) UpdateDeploymentImageNS(c *gin.Context) {
h.updateDeploymentImageWithNS(c, c.Param("namespace"))
}
func (h *KubernetesHandler) PauseDeploymentNS(c *gin.Context) {
h.pauseDeploymentWithNS(c, c.Param("namespace"))
}
func (h *KubernetesHandler) ResumeDeploymentNS(c *gin.Context) {
h.resumeDeploymentWithNS(c, c.Param("namespace"))
}
func (h *KubernetesHandler) GetPodLogsNS(c *gin.Context) {
h.getPodLogsWithNS(c, c.Param("namespace"))
}
@@ -1565,6 +1611,7 @@ func (h *KubernetesHandler) listDeploymentsWithNS(c *gin.Context, ns string) {
AvailableReplicas int `json:"available_replicas"`
ReadyReplicas int `json:"ready_replicas"`
Image string `json:"image"`
Paused bool `json:"paused"`
}
result := []DepSummary{}
for _, d := range deps.Items {
@@ -1577,6 +1624,7 @@ func (h *KubernetesHandler) listDeploymentsWithNS(c *gin.Context, ns string) {
AvailableReplicas: d.Status.AvailableReplicas,
ReadyReplicas: d.Status.ReadyReplicas,
Image: image,
Paused: d.Spec.Paused,
})
}
c.JSON(200, models.Response{Code: 200, Data: result})
@@ -1816,6 +1864,22 @@ func (h *KubernetesHandler) updateDeploymentImageWithNS(c *gin.Context, ns strin
if err := client.UpdateDeploymentImage(ns, c.Param("name"), req.ContainerName, req.Image); err != nil { c.JSON(500, models.Response{Code: 500, Message: "更新镜像失败: " + err.Error()}); return }
c.JSON(200, models.Response{Code: 200, Message: fmt.Sprintf("镜像已更新为 %s", req.Image)})
}
func (h *KubernetesHandler) pauseDeploymentWithNS(c *gin.Context, ns string) {
clusterID, _ := strconv.Atoi(c.Param("id"))
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
client, _, err := h.getK8sClient(clusterID)
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
if err := client.PauseDeployment(ns, c.Param("name")); err != nil { c.JSON(500, models.Response{Code: 500, Message: "暂停更新失败: " + err.Error()}); return }
c.JSON(200, models.Response{Code: 200, Message: "已暂停滚动更新"})
}
func (h *KubernetesHandler) resumeDeploymentWithNS(c *gin.Context, ns string) {
clusterID, _ := strconv.Atoi(c.Param("id"))
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
client, _, err := h.getK8sClient(clusterID)
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
if err := client.ResumeDeployment(ns, c.Param("name")); err != nil { c.JSON(500, models.Response{Code: 500, Message: "恢复更新失败: " + err.Error()}); return }
c.JSON(200, models.Response{Code: 200, Message: "已恢复滚动更新"})
}
func (h *KubernetesHandler) getPodLogsWithNS(c *gin.Context, ns string) {
clusterID, _ := strconv.Atoi(c.Param("id"))
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
+5
View File
@@ -298,6 +298,9 @@ func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config, c
// Deployment 扩缩容 & 重启 // Logan.Li
clusters.POST("/:id/namespaces/:namespace/deployments/:name/scale", k8sHandler.ScaleDeployment)
clusters.POST("/:id/namespaces/:namespace/deployments/:name/restart", k8sHandler.RestartDeployment)
// Deployment 暂停/恢复滚动更新 // Logan.Li M5
clusters.POST("/:id/namespaces/:namespace/deployments/:name/pause", k8sHandler.PauseDeployment)
clusters.POST("/:id/namespaces/:namespace/deployments/:name/resume", k8sHandler.ResumeDeployment)
// Deployment CI 集成 (Webhook 更新镜像) // Logan.Li
clusters.POST("/:id/namespaces/:namespace/deployments/:name/update-image", k8sHandler.UpdateDeploymentImage)
// HPA 管理 // Logan.Li
@@ -337,6 +340,8 @@ func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config, c
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/deployments", k8sHandler.ListDeploymentsNS)
kubernetesRouter.POST("/:clusterCode/namespace/:namespace/deployments/:name/scale", k8sHandler.ScaleDeploymentNS)
kubernetesRouter.POST("/:clusterCode/namespace/:namespace/deployments/:name/restart", k8sHandler.RestartDeploymentNS)
kubernetesRouter.POST("/:clusterCode/namespace/:namespace/deployments/:name/pause", k8sHandler.PauseDeploymentNS)
kubernetesRouter.POST("/:clusterCode/namespace/:namespace/deployments/:name/resume", k8sHandler.ResumeDeploymentNS)
kubernetesRouter.POST("/:clusterCode/namespace/:namespace/deployments/:name/update-image", k8sHandler.UpdateDeploymentImageNS)
// Pods