istio
424 строки · 15.8 Кб
1// Copyright Istio Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package helmreconciler16
17import (18"context"19"fmt"20"strings"21
22kerrors "k8s.io/apimachinery/pkg/api/errors"23"k8s.io/apimachinery/pkg/api/meta"24metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"25"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"26klabels "k8s.io/apimachinery/pkg/labels"27"k8s.io/apimachinery/pkg/runtime/schema"28"k8s.io/apimachinery/pkg/selection"29"sigs.k8s.io/controller-runtime/pkg/client"30
31"istio.io/api/label"32"istio.io/api/operator/v1alpha1"33iopv1alpha1 "istio.io/istio/operator/pkg/apis/istio/v1alpha1"34"istio.io/istio/operator/pkg/cache"35"istio.io/istio/operator/pkg/metrics"36"istio.io/istio/operator/pkg/name"37"istio.io/istio/operator/pkg/object"38"istio.io/istio/operator/pkg/translate"39"istio.io/istio/operator/pkg/util"40"istio.io/istio/pkg/config/constants"41"istio.io/istio/pkg/config/schema/gvk"42"istio.io/istio/pkg/kube"43"istio.io/istio/pkg/proxy"44)
45
46var (47// ClusterResources are resource types the operator prunes, ordered by which types should be deleted, first to last.48ClusterResources = []schema.GroupVersionKind{49{Group: "admissionregistration.k8s.io", Version: "v1", Kind: name.MutatingWebhookConfigurationStr},50{Group: "admissionregistration.k8s.io", Version: "v1", Kind: name.ValidatingWebhookConfigurationStr},51{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: name.ClusterRoleStr},52{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: name.ClusterRoleBindingStr},53// Cannot currently prune CRDs because this will also wipe out user config.54// {Group: "apiextensions.k8s.io", Version: "v1beta1", Kind: name.CRDStr},55}56// ClusterCPResources lists cluster scope resources types which should be deleted during uninstall command.57ClusterCPResources = []schema.GroupVersionKind{58{Group: "admissionregistration.k8s.io", Version: "v1", Kind: name.MutatingWebhookConfigurationStr},59{Group: "admissionregistration.k8s.io", Version: "v1", Kind: name.ValidatingWebhookConfigurationStr},60{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: name.ClusterRoleStr},61{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: name.ClusterRoleBindingStr},62}63// AllClusterResources lists all cluster scope resources types which should be deleted in purge case, including CRD.64AllClusterResources = append(ClusterResources,65schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: name.CRDStr},66schema.GroupVersionKind{Group: "k8s.cni.cncf.io", Version: "v1", Kind: name.NetworkAttachmentDefinitionStr},67)68)
69
70// NamespacedResources gets specific pruning resources based on the k8s version
71func NamespacedResources() []schema.GroupVersionKind {72res := []schema.GroupVersionKind{73{Group: "apps", Version: "v1", Kind: name.DeploymentStr},74{Group: "apps", Version: "v1", Kind: name.DaemonSetStr},75{Group: "", Version: "v1", Kind: name.ServiceStr},76{Group: "", Version: "v1", Kind: name.CMStr},77{Group: "", Version: "v1", Kind: name.PodStr},78{Group: "", Version: "v1", Kind: name.SecretStr},79{Group: "", Version: "v1", Kind: name.SAStr},80{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: name.RoleBindingStr},81{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: name.RoleStr},82{Group: "policy", Version: "v1", Kind: name.PDBStr},83{Group: "autoscaling", Version: "v2", Kind: name.HPAStr},84gvk.EnvoyFilter.Kubernetes(),85}86return res87}
88
89// Prune removes any resources not specified in manifests generated by HelmReconciler h.
90func (h *HelmReconciler) Prune(manifests name.ManifestMap, all bool) error {91return h.runForAllTypes(func(labels map[string]string, objects *unstructured.UnstructuredList) error {92var errs util.Errors93if all {94errs = util.AppendErr(errs, h.deleteResources(nil, labels, "", objects, all))95} else {96for cname, manifest := range manifests.Consolidated() {97errs = util.AppendErr(errs, h.deleteResources(object.AllObjectHashes(manifest), labels, cname, objects, all))98}99}100return errs.ToError()101})102}
103
104// PruneControlPlaneByRevisionWithController is called to remove specific control plane revision
105// during reconciliation process of controller.
106// It returns the install status and any error encountered.
107func (h *HelmReconciler) PruneControlPlaneByRevisionWithController(iopSpec *v1alpha1.IstioOperatorSpec) (*v1alpha1.InstallStatus, error) {108ns := iopv1alpha1.Namespace(iopSpec)109if ns == "" {110ns = constants.IstioSystemNamespace111}112errStatus := &v1alpha1.InstallStatus{Status: v1alpha1.InstallStatus_ERROR}113enabledComponents, err := translate.GetEnabledComponents(iopSpec)114if err != nil {115return errStatus,116fmt.Errorf("failed to get enabled components: %v", err)117}118pilotEnabled := false119// check whether the istiod is enabled120for _, c := range enabledComponents {121if c == string(name.PilotComponentName) {122pilotEnabled = true123break124}125}126// If istiod is enabled, check if it has any proxies connected.127if pilotEnabled {128cfg := h.kubeClient.RESTConfig()129kubeClient, err := kube.NewCLIClient(kube.NewClientConfigForRestConfig(cfg), iopSpec.Revision)130if err != nil {131return errStatus, err132}133
134pilotExists, err := h.pilotExists(kubeClient, ns)135if err != nil {136return errStatus, fmt.Errorf("failed to check istiod extist: %v", err)137}138
139if pilotExists {140// TODO(ramaraochavali): Find a better alternative instead of using debug interface141// of istiod as it is typically not recommended in production environments.142pids, err := proxy.GetIDsFromProxyInfo(kubeClient, ns)143if err != nil {144return errStatus, fmt.Errorf("failed to check proxy infos: %v", err)145}146if len(pids) != 0 {147msg := fmt.Sprintf("there are proxies still pointing to the pruned control plane: %s.",148strings.Join(pids, " "))149st := &v1alpha1.InstallStatus{Status: v1alpha1.InstallStatus_ACTION_REQUIRED, Message: msg}150return st, nil151}152}153}154
155for _, c := range enabledComponents {156uslist, err := h.GetPrunedResources(iopSpec.Revision, false, c)157if err != nil {158return errStatus, err159}160err = h.DeleteObjectsList(uslist, c)161if err != nil {162return errStatus, err163}164}165return &v1alpha1.InstallStatus{Status: v1alpha1.InstallStatus_HEALTHY}, nil166}
167
168func (h *HelmReconciler) pilotExists(cliClient kube.CLIClient, istioNamespace string) (bool, error) {169istiodPods, err := cliClient.GetIstioPods(context.TODO(), istioNamespace, metav1.ListOptions{170LabelSelector: "app=istiod",171FieldSelector: "status.phase=Running",172})173if err != nil {174return false, err175}176
177return len(istiodPods) > 0, nil178}
179
180// DeleteObjectsList removed resources that are in the slice of UnstructuredList.
181func (h *HelmReconciler) DeleteObjectsList(objectsList []*unstructured.UnstructuredList, componentName string) error {182var errs util.Errors183deletedObjects := make(map[string]bool)184for _, ul := range objectsList {185for _, o := range ul.Items {186obj := object.NewK8sObject(&o, nil, nil)187oh := obj.Hash()188
189// kube client does not differentiate API version when listing, added this check to deduplicate.190if deletedObjects[oh] {191continue192}193if err := h.deleteResource(obj, componentName, oh); err != nil {194errs = append(errs, err)195}196deletedObjects[oh] = true197}198}199
200return errs.ToError()201}
202
203// GetPrunedResources get the list of resources to be removed
204// 1. if includeClusterResources is false, we list the namespaced resources by matching revision and component labels.
205// 2. if includeClusterResources is true, we list the namespaced and cluster resources by component labels only.
206// If componentName is not empty, only resources associated with specific components would be returned
207// UnstructuredList of objects and corresponding list of name kind hash of k8sObjects would be returned
208func (h *HelmReconciler) GetPrunedResources(revision string, includeClusterResources bool, componentName string) (209[]*unstructured.UnstructuredList, error,210) {211var usList []*unstructured.UnstructuredList212labels := make(map[string]string)213if revision != "" {214labels[label.IoIstioRev.Name] = revision215}216if componentName != "" {217labels[IstioComponentLabelStr] = componentName218}219if h.iop.GetName() != "" {220labels[OwningResourceName] = h.iop.GetName()221}222if h.iop.GetNamespace() != "" {223labels[OwningResourceNamespace] = h.iop.GetNamespace()224}225selector := klabels.Set(labels).AsSelectorPreValidated()226resources := NamespacedResources()227gvkList := append(resources, ClusterCPResources...)228if includeClusterResources {229gvkList = append(resources, AllClusterResources...)230// Cleanup IstioOperator, which may be used with in-cluster operator.231if ioplist := h.getIstioOperatorCR(); ioplist != nil && len(ioplist.Items) > 0 {232usList = append(usList, ioplist)233}234}235for _, gvk := range gvkList {236objects := &unstructured.UnstructuredList{}237objects.SetGroupVersionKind(gvk)238componentRequirement, err := klabels.NewRequirement(IstioComponentLabelStr, selection.Exists, nil)239if err != nil {240return usList, err241}242if includeClusterResources {243s := klabels.NewSelector()244err = h.client.List(context.TODO(), objects,245client.MatchingLabelsSelector{Selector: s.Add(*componentRequirement)})246} else {247// do not prune base components or unknown components248includeCN := []string{249string(name.PilotComponentName),250string(name.IngressComponentName), string(name.EgressComponentName),251string(name.CNIComponentName), string(name.IstioOperatorComponentName),252string(name.IstiodRemoteComponentName),253string(name.ZtunnelComponentName),254}255includeRequirement, err := klabels.NewRequirement(IstioComponentLabelStr, selection.In, includeCN)256if err != nil {257return usList, err258}259if err = h.client.List(context.TODO(), objects,260client.MatchingLabelsSelector{261Selector: selector.Add(*includeRequirement, *componentRequirement),262},263); err != nil {264continue265}266}267if err != nil {268continue269}270for _, obj := range objects.Items {271objName := fmt.Sprintf("%s/%s", obj.GetNamespace(), obj.GetName())272metrics.AddResource(objName, gvk.GroupKind())273}274if len(objects.Items) == 0 {275continue276}277usList = append(usList, objects)278}279
280return usList, nil281}
282
283// getIstioOperatorCR is a helper function to get IstioOperator CR during purge,
284// otherwise the resources would be reconciled back later if there is in-cluster operator deployment.
285// And it is needed to remove the IstioOperator CRD.
286func (h *HelmReconciler) getIstioOperatorCR() *unstructured.UnstructuredList {287iopGVR := iopv1alpha1.IstioOperatorGVR288objects, err := h.kubeClient.Dynamic().Resource(iopGVR).List(context.TODO(), metav1.ListOptions{})289if err != nil {290if kerrors.IsNotFound(err) {291return nil292}293scope.Errorf("failed to list IstioOperator CR: %v", err)294}295return objects296}
297
298// runForAllTypes will collect all existing resource types we care about. For each type, the callback function
299// will be called with the labels used to select this type, and all objects.
300// This is in internal function meant to support prune and delete
301func (h *HelmReconciler) runForAllTypes(callback func(labels map[string]string, objects *unstructured.UnstructuredList) error) error {302var errs util.Errors303// Ultimately, we want to prune based on component labels. Each of these share a common set of labels304// Rather than do N List() calls for each component, we will just filter for the common subset here305// and each component will do its own filtering306// Because we are filtering by the core labels, List() will only return items that some components will care307// about, so we are not querying for an overly broad set of resources.308labels, err := h.getCoreOwnerLabels()309if err != nil {310return err311}312selector := klabels.Set(labels).AsSelectorPreValidated()313componentRequirement, err := klabels.NewRequirement(IstioComponentLabelStr, selection.Exists, nil)314if err != nil {315return err316}317selector = selector.Add(*componentRequirement)318
319resources := PrunedResourcesSchemas()320for _, gvk := range resources {321// First, we collect all objects for the provided GVK322objects := &unstructured.UnstructuredList{}323objects.SetGroupVersionKind(gvk)324if err := h.client.List(context.TODO(), objects, client.MatchingLabelsSelector{Selector: selector}); err != nil {325// we only want to retrieve resources clusters326if !(h.opts.DryRun && meta.IsNoMatchError(err)) {327scope.Debugf("retrieving resources to prune type %s: %s", gvk.String(), err)328}329continue330}331for _, obj := range objects.Items {332objName := fmt.Sprintf("%s/%s", obj.GetNamespace(), obj.GetName())333metrics.AddResource(objName, gvk.GroupKind())334}335errs = util.AppendErr(errs, callback(labels, objects))336}337return errs.ToError()338}
339
340func PrunedResourcesSchemas() []schema.GroupVersionKind {341return append(NamespacedResources(), ClusterResources...)342}
343
344// deleteResources delete any resources from the given component that are not in the excluded map. Resource
345// labels are used to identify the resources belonging to the component.
346func (h *HelmReconciler) deleteResources(excluded map[string]bool, coreLabels map[string]string,347componentName string, objects *unstructured.UnstructuredList, all bool,348) error {349var errs util.Errors350labels := h.addComponentLabels(coreLabels, componentName)351selector := klabels.Set(labels).AsSelectorPreValidated()352for _, o := range objects.Items {353obj := object.NewK8sObject(&o, nil, nil)354oh := obj.Hash()355if !all {356// Label mismatch. Provided objects don't select against the component, so this likely means the object357// is for another component.358if !selector.Matches(klabels.Set(o.GetLabels())) {359continue360}361if excluded[oh] {362continue363}364if o.GetLabels()[OwningResourceNotPruned] == "true" {365continue366}367}368if err := h.deleteResource(obj, componentName, oh); err != nil {369errs = append(errs, err)370}371}372if all {373cache.FlushObjectCaches()374}375
376return errs.ToError()377}
378
379func (h *HelmReconciler) deleteResource(obj *object.K8sObject, componentName, oh string) error {380if h.opts.DryRun {381h.opts.Log.LogAndPrintf("Not pruning object %s because of dry run.", oh)382return nil383}384u := obj.UnstructuredObject()385if u.GetKind() == name.IstioOperatorStr {386u.SetFinalizers([]string{})387if err := h.client.Patch(context.TODO(), u, client.Merge); err != nil {388scope.Errorf("failed to patch IstioOperator CR: %s, %v", u.GetName(), err)389}390}391err := h.client.Delete(context.TODO(), u, client.PropagationPolicy(metav1.DeletePropagationBackground))392scope.Debugf("Deleting %s (%s/%v)", oh, h.iop.Name, h.iop.Spec.Revision)393objGvk := u.GroupVersionKind()394if err != nil {395if !kerrors.IsNotFound(err) {396return err397}398// do not return error if resources are not found399h.opts.Log.LogAndPrintf("object: %s is not being deleted because it no longer exists", obj.Hash())400return nil401}402if componentName != "" {403h.removeFromObjectCache(componentName, oh)404} else {405cache.FlushObjectCaches()406}407metrics.ResourceDeletionTotal.408With(metrics.ResourceKindLabel.Value(util.GKString(objGvk.GroupKind()))).409Increment()410h.addPrunedKind(objGvk.GroupKind())411metrics.RemoveResource(obj.FullName(), objGvk.GroupKind())412h.opts.Log.LogAndPrintf(" Removed %s.", oh)413return nil414}
415
416// RemoveObject removes object with objHash in componentName from the object cache.
417func (h *HelmReconciler) removeFromObjectCache(componentName, objHash string) {418crHash, err := h.getCRHash(componentName)419if err != nil {420scope.Error(err.Error())421}422cache.RemoveObject(crHash, objHash)423scope.Infof("Removed object %s from Cache.", objHash)424}
425