fix: 删除操作清理远端资源并拦截悬空引用
- 部署删除: k8s 模式清理 StatefulSet/Service/ConfigMap 与 PVC,先卸载关联采集器 - 采集器删除: 同步卸载成功后再删记录,失败保留避免孤儿 - 集群/存储/主机删除: 有部署或采集器引用时拒绝,避免悬空外键 - Token RBAC 补 services/proxy,保证 cluster-svc 数据源可查询
This commit is contained in:
1 parent
56e1245b70
commit
b433b5f9dd
7 files changed
+88
-17
No files matched your search
@@ -211,6 +211,11 @@ func (s *ClusterService) Delete(id uint) error {
|
|||||||
defer db.Unlock()
|
defer db.Unlock()
|
||||||
for i, c := range db.Clusters {
|
for i, c := range db.Clusters {
|
||||||
if c.ID == id {
|
if c.ID == id {
|
||||||
|
for _, d := range db.Deployments {
|
||||||
|
if d.ClusterID != nil && *d.ClusterID == id {
|
||||||
|
return fmt.Errorf("仍有部署使用该集群,请先删除部署")
|
||||||
|
}
|
||||||
|
}
|
||||||
if c.KubeConfigPath != "" {
|
if c.KubeConfigPath != "" {
|
||||||
_ = os.Remove(c.KubeConfigPath)
|
_ = os.Remove(c.KubeConfigPath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -488,30 +488,59 @@ func (s *CollectorService) Update(id uint, updates map[string]interface{}) error
|
|||||||
return utils.ErrNotFound
|
return utils.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UninstallCollectorsForDeployment 卸载并删除某部署下的所有采集器。
|
||||||
|
// 卸载失败时保留记录,避免远端资源失去跟踪。
|
||||||
|
func UninstallCollectorsForDeployment(ctx context.Context, deploymentID uint) {
|
||||||
|
db := repository.GetDB()
|
||||||
|
db.RLock()
|
||||||
|
var targets []models.Collector
|
||||||
|
for _, c := range db.Collectors {
|
||||||
|
if c.LokiDeploymentID == deploymentID {
|
||||||
|
targets = append(targets, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.RUnlock()
|
||||||
|
|
||||||
|
for i := range targets {
|
||||||
|
c := targets[i]
|
||||||
|
if err := NewCollectorDeployer(&c, c.LokiDeployment, nil).Uninstall(ctx); err != nil {
|
||||||
|
log.Printf("[deploy-delete] 采集器 %q 卸载失败,保留记录: %v", c.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
db.Lock()
|
||||||
|
for j := range db.Collectors {
|
||||||
|
if db.Collectors[j].ID == c.ID {
|
||||||
|
db.Collectors = append(db.Collectors[:j], db.Collectors[j+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.RequestSave()
|
||||||
|
db.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *CollectorService) Delete(id uint) error {
|
func (s *CollectorService) Delete(id uint) error {
|
||||||
collector, err := s.Get(id)
|
collector, err := s.Get(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return utils.ErrNotFound
|
return utils.ErrNotFound
|
||||||
}
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
// 先卸载远端资源,成功后再删记录;卸载失败直接返回,避免「界面删了、远端还在」
|
||||||
|
if err := NewCollectorDeployer(collector, collector.LokiDeployment, nil).Uninstall(ctx); err != nil {
|
||||||
|
return fmt.Errorf("卸载采集器失败,未删除记录: %w", err)
|
||||||
|
}
|
||||||
db := repository.GetDB()
|
db := repository.GetDB()
|
||||||
db.Lock()
|
db.Lock()
|
||||||
|
defer db.Unlock()
|
||||||
for i, c := range db.Collectors {
|
for i, c := range db.Collectors {
|
||||||
if c.ID == id {
|
if c.ID == id {
|
||||||
db.Collectors = append(db.Collectors[:i], db.Collectors[i+1:]...)
|
db.Collectors = append(db.Collectors[:i], db.Collectors[i+1:]...)
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
db.Unlock()
|
|
||||||
db.RequestSave()
|
db.RequestSave()
|
||||||
// 异步卸载(用拷贝的 collector,避免删除后取不到;失败留日志,远端不留孤儿容器也无提示)
|
|
||||||
go func() {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
|
||||||
defer cancel()
|
|
||||||
if err := NewCollectorDeployer(collector, collector.LokiDeployment, nil).Uninstall(ctx); err != nil {
|
|
||||||
log.Printf("[collector] 删除后卸载 %q(目标 %s)失败: %v", collector.Name, collector.Name, err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return nil
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return utils.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install 异步部署采集器
|
// Install 异步部署采集器
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package services
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -315,18 +316,39 @@ func (s *DeploymentService) Delete(id uint) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if deployment.Mode == "host-docker" {
|
|
||||||
if deployment.Status == "deploying" {
|
if deployment.Status == "deploying" {
|
||||||
return fmt.Errorf("部署正在进行中,请等待完成后删除")
|
return fmt.Errorf("部署正在进行中,请等待完成后删除")
|
||||||
}
|
}
|
||||||
if deployment.Status != "pending" {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
// 先卸载该部署下的采集器:它们依赖这个 Loki 实例,留着会指向不存在的服务
|
||||||
|
UninstallCollectorsForDeployment(ctx, id)
|
||||||
|
|
||||||
|
switch deployment.Mode {
|
||||||
|
case "host-docker":
|
||||||
|
if deployment.Status != "pending" {
|
||||||
if err := deleteDockerLoki(ctx, deployment); err != nil {
|
if err := deleteDockerLoki(ctx, deployment); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "k8s-yaml":
|
||||||
|
// 停止/重启走 CleanupLoki 时保留 PVC;这里是显式删除,连数据卷一起清理
|
||||||
|
CleanupLoki(ctx, deployment)
|
||||||
|
if deployment.Cluster != nil {
|
||||||
|
ns := deployment.Namespace
|
||||||
|
if ns == "" {
|
||||||
|
ns = "log"
|
||||||
}
|
}
|
||||||
|
if err := deleteK8sResource(ctx, deployment.Cluster, "persistentvolumeclaim", ns, "loki-data"); err != nil {
|
||||||
|
log.Printf("[deploy-delete] 清理 PVC %s/loki-data 失败: %v", ns, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "host-binary":
|
||||||
|
CleanupLoki(ctx, deployment)
|
||||||
|
}
|
||||||
|
|
||||||
db := repository.GetDB()
|
db := repository.GetDB()
|
||||||
db.Lock()
|
db.Lock()
|
||||||
removedName := ""
|
removedName := ""
|
||||||
|
|||||||
@@ -186,6 +186,16 @@ func (s *HostService) Delete(id uint) error {
|
|||||||
defer db.Unlock()
|
defer db.Unlock()
|
||||||
for i, h := range db.Hosts {
|
for i, h := range db.Hosts {
|
||||||
if h.ID == id {
|
if h.ID == id {
|
||||||
|
for _, d := range db.Deployments {
|
||||||
|
if d.HostID != nil && *d.HostID == id {
|
||||||
|
return fmt.Errorf("仍有部署使用该主机,请先删除部署")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, c := range db.Collectors {
|
||||||
|
if c.HostID != nil && *c.HostID == id {
|
||||||
|
return fmt.Errorf("仍有采集器使用该主机,请先删除采集器")
|
||||||
|
}
|
||||||
|
}
|
||||||
db.Hosts = append(db.Hosts[:i], db.Hosts[i+1:]...)
|
db.Hosts = append(db.Hosts[:i], db.Hosts[i+1:]...)
|
||||||
db.RequestSave()
|
db.RequestSave()
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -151,6 +151,11 @@ func (s *StorageService) Delete(id uint) error {
|
|||||||
defer db.Unlock()
|
defer db.Unlock()
|
||||||
for i, st := range db.Storages {
|
for i, st := range db.Storages {
|
||||||
if st.ID == id {
|
if st.ID == id {
|
||||||
|
for _, d := range db.Deployments {
|
||||||
|
if d.StorageConfigID == id {
|
||||||
|
return fmt.Errorf("仍有部署使用该存储配置,请先删除部署或改用其他存储")
|
||||||
|
}
|
||||||
|
}
|
||||||
db.Storages = append(db.Storages[:i], db.Storages[i+1:]...)
|
db.Storages = append(db.Storages[:i], db.Storages[i+1:]...)
|
||||||
db.RequestSave()
|
db.RequestSave()
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ metadata:
|
|||||||
namespace: log
|
namespace: log
|
||||||
rules:
|
rules:
|
||||||
- apiGroups: [""]
|
- apiGroups: [""]
|
||||||
resources: ["pods", "pods/log", "services", "configmaps", "secrets", "persistentvolumeclaims", "events", "serviceaccounts"]
|
resources: ["pods", "pods/log", "services", "services/proxy", "configmaps", "secrets", "persistentvolumeclaims", "events", "serviceaccounts"]
|
||||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||||
- apiGroups: ["apps"]
|
- apiGroups: ["apps"]
|
||||||
resources: ["statefulsets", "daemonsets", "deployments"]
|
resources: ["statefulsets", "daemonsets", "deployments"]
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ metadata:
|
|||||||
namespace: log
|
namespace: log
|
||||||
rules:
|
rules:
|
||||||
- apiGroups: [""]
|
- apiGroups: [""]
|
||||||
resources: ["pods", "pods/log", "services", "configmaps", "secrets", "persistentvolumeclaims", "events", "serviceaccounts"]
|
resources: ["pods", "pods/log", "services", "services/proxy", "configmaps", "secrets", "persistentvolumeclaims", "events", "serviceaccounts"]
|
||||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||||
- apiGroups: ["apps"]
|
- apiGroups: ["apps"]
|
||||||
resources: ["statefulsets", "daemonsets", "deployments"]
|
resources: ["statefulsets", "daemonsets", "deployments"]
|
||||||
|
|||||||
Reference in new issue
Block a user