github.com/oam-dev/kubevela@v1.9.11/pkg/resourcekeeper/gc_rev.go (about) 1 /* 2 Copyright 2021 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package resourcekeeper 18 19 import ( 20 "context" 21 "encoding/json" 22 "sort" 23 "time" 24 25 "github.com/pkg/errors" 26 appsv1 "k8s.io/api/apps/v1" 27 kerrors "k8s.io/apimachinery/pkg/api/errors" 28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 ktypes "k8s.io/apimachinery/pkg/types" 30 "k8s.io/klog/v2" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 33 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1" 34 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 35 "github.com/oam-dev/kubevela/pkg/auth" 36 "github.com/oam-dev/kubevela/pkg/cache" 37 "github.com/oam-dev/kubevela/pkg/monitor/metrics" 38 "github.com/oam-dev/kubevela/pkg/multicluster" 39 "github.com/oam-dev/kubevela/pkg/oam" 40 "github.com/oam-dev/kubevela/pkg/oam/util" 41 "github.com/oam-dev/kubevela/pkg/utils" 42 ) 43 44 // GarbageCollectApplicationRevision execute garbage collection functions, including: 45 // - clean up app revisions 46 // - clean up legacy component revisions 47 func (h *gcHandler) GarbageCollectApplicationRevision(ctx context.Context) error { 48 t := time.Now() 49 defer func() { 50 metrics.AppReconcileStageDurationHistogram.WithLabelValues("gc-app-rev").Observe(time.Since(t).Seconds()) 51 }() 52 if err := cleanUpComponentRevision(ctx, h); err != nil { 53 return err 54 } 55 return cleanUpApplicationRevision(ctx, h) 56 } 57 58 // cleanUpApplicationRevision check all appRevisions of the application, remove them if the number of them exceed the limit 59 func cleanUpApplicationRevision(ctx context.Context, h *gcHandler) error { 60 if h.cfg.disableApplicationRevisionGC { 61 return nil 62 } 63 t := time.Now() 64 defer func() { 65 metrics.AppReconcileStageDurationHistogram.WithLabelValues("gc-rev.apprev").Observe(time.Since(t).Seconds()) 66 }() 67 sortedRevision, err := getSortedAppRevisions(ctx, h.Client, h.app.Name, h.app.Namespace) 68 if err != nil { 69 return err 70 } 71 appRevisionInUse := gatherUsingAppRevision(h.app) 72 appRevisionLimit := getApplicationRevisionLimitForApp(h.app, h.cfg.appRevisionLimit) 73 needKill := len(sortedRevision) - appRevisionLimit - len(appRevisionInUse) 74 if h._rootRT == nil && h._currentRT == nil && len(h._historyRTs) == 0 && h._crRT == nil && h.app.DeletionTimestamp != nil { 75 needKill = len(sortedRevision) 76 appRevisionInUse = nil 77 } 78 if needKill <= 0 { 79 return nil 80 } 81 klog.InfoS("Going to garbage collect app revisions", "limit", h.cfg.appRevisionLimit, 82 "total", len(sortedRevision), "using", len(appRevisionInUse), "kill", needKill) 83 84 for _, rev := range sortedRevision { 85 if needKill <= 0 { 86 break 87 } 88 // don't delete app revision in use 89 if appRevisionInUse[rev.Name] { 90 continue 91 } 92 if err := h.Client.Delete(ctx, rev.DeepCopy()); err != nil && !kerrors.IsNotFound(err) { 93 return err 94 } 95 needKill-- 96 } 97 return nil 98 } 99 100 func cleanUpComponentRevision(ctx context.Context, h *gcHandler) error { 101 if h.cfg.disableComponentRevisionGC { 102 return nil 103 } 104 t := time.Now() 105 defer func() { 106 metrics.AppReconcileStageDurationHistogram.WithLabelValues("gc-rev.comprev").Observe(time.Since(t).Seconds()) 107 }() 108 // collect component revision in use 109 compRevisionInUse := map[string]map[string]struct{}{} 110 ctx = auth.ContextWithUserInfo(ctx, h.app) 111 for i, resource := range h.app.Status.AppliedResources { 112 compName := resource.Name 113 ns := resource.Namespace 114 r := &unstructured.Unstructured{} 115 r.GetObjectKind().SetGroupVersionKind(resource.GroupVersionKind()) 116 _ctx := multicluster.ContextWithClusterName(ctx, resource.Cluster) 117 err := h.Client.Get(_ctx, ktypes.NamespacedName{Name: compName, Namespace: ns}, r) 118 notFound := kerrors.IsNotFound(err) 119 if err != nil && !notFound { 120 return errors.WithMessagef(err, "get applied resource index=%d", i) 121 } 122 if compRevisionInUse[compName] == nil { 123 compRevisionInUse[compName] = map[string]struct{}{} 124 } 125 if notFound { 126 continue 127 } 128 compRevision, ok := r.GetLabels()[oam.LabelAppComponentRevision] 129 if ok { 130 compRevisionInUse[compName][compRevision] = struct{}{} 131 } 132 } 133 134 for _, curComp := range h.app.Status.AppliedResources { 135 crList := &appsv1.ControllerRevisionList{} 136 listOpts := []client.ListOption{client.MatchingLabels{ 137 oam.LabelControllerRevisionComponent: utils.EscapeResourceNameToLabelValue(curComp.Name), 138 }, client.InNamespace(h.getComponentRevisionNamespace(ctx))} 139 _ctx := multicluster.ContextWithClusterName(ctx, curComp.Cluster) 140 if err := h.Client.List(_ctx, crList, listOpts...); err != nil { 141 return err 142 } 143 needKill := len(crList.Items) - h.cfg.appRevisionLimit - len(compRevisionInUse[curComp.Name]) 144 if needKill < 1 { 145 continue 146 } 147 sortedRevision := crList.Items 148 sort.Sort(historiesByComponentRevision(sortedRevision)) 149 for _, rev := range sortedRevision { 150 if needKill <= 0 { 151 break 152 } 153 if _, inUse := compRevisionInUse[curComp.Name][rev.Name]; inUse { 154 continue 155 } 156 _rev := rev.DeepCopy() 157 oam.SetCluster(_rev, curComp.Cluster) 158 if err := h.resourceKeeper.DeleteComponentRevision(_ctx, _rev); err != nil { 159 return err 160 } 161 needKill-- 162 } 163 } 164 return nil 165 } 166 167 // gatherUsingAppRevision get all using appRevisions include app's status pointing to 168 func gatherUsingAppRevision(app *v1beta1.Application) map[string]bool { 169 usingRevision := map[string]bool{} 170 if app.Status.LatestRevision != nil && len(app.Status.LatestRevision.Name) != 0 { 171 usingRevision[app.Status.LatestRevision.Name] = true 172 } 173 return usingRevision 174 } 175 176 func getApplicationRevisionLimitForApp(app *v1beta1.Application, fallback int) int { 177 for _, p := range app.Spec.Policies { 178 if p.Type == v1alpha1.GarbageCollectPolicyType && p.Properties != nil && p.Properties.Raw != nil { 179 prop := &v1alpha1.GarbageCollectPolicySpec{} 180 if err := json.Unmarshal(p.Properties.Raw, prop); err == nil && prop.ApplicationRevisionLimit != nil && *prop.ApplicationRevisionLimit >= 0 { 181 return *prop.ApplicationRevisionLimit 182 } 183 } 184 } 185 return fallback 186 } 187 188 // getSortedAppRevisions get application revisions by revision number 189 func getSortedAppRevisions(ctx context.Context, cli client.Client, appName string, appNs string) ([]v1beta1.ApplicationRevision, error) { 190 revs, err := ListApplicationRevisions(ctx, cli, appName, appNs) 191 if err != nil { 192 return nil, err 193 } 194 sort.Slice(revs, func(i, j int) bool { 195 ir, _ := util.ExtractRevisionNum(revs[i].Name, "-") 196 ij, _ := util.ExtractRevisionNum(revs[j].Name, "-") 197 return ir < ij 198 }) 199 return revs, nil 200 } 201 202 // ListApplicationRevisions get application revisions by label 203 func ListApplicationRevisions(ctx context.Context, cli client.Client, appName string, appNs string) ([]v1beta1.ApplicationRevision, error) { 204 appRevisionList := new(v1beta1.ApplicationRevisionList) 205 var err error 206 if cache.OptimizeListOp { 207 err = cli.List(ctx, appRevisionList, client.MatchingFields{cache.AppIndex: appNs + "/" + appName}) 208 } else { 209 err = cli.List(ctx, appRevisionList, client.InNamespace(appNs), client.MatchingLabels{oam.LabelAppName: appName}) 210 } 211 if err != nil { 212 return nil, err 213 } 214 return appRevisionList.Items, nil 215 } 216 217 func (h *gcHandler) getComponentRevisionNamespace(ctx context.Context) string { 218 if ns, ok := ctx.Value(0).(string); ok && ns != "" { 219 return ns 220 } 221 return h.app.Namespace 222 } 223 224 type historiesByComponentRevision []appsv1.ControllerRevision 225 226 func (h historiesByComponentRevision) Len() int { return len(h) } 227 func (h historiesByComponentRevision) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 228 func (h historiesByComponentRevision) Less(i, j int) bool { 229 ir, _ := util.ExtractRevisionNum(h[i].Name, "-") 230 ij, _ := util.ExtractRevisionNum(h[j].Name, "-") 231 return ir < ij 232 }