k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kubeadm/app/phases/addons/dns/dns.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes 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 dns
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  	"strings"
    24  
    25  	"github.com/coredns/corefile-migration/migration"
    26  	"github.com/pkg/errors"
    27  
    28  	apps "k8s.io/api/apps/v1"
    29  	v1 "k8s.io/api/core/v1"
    30  	rbac "k8s.io/api/rbac/v1"
    31  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	kuberuntime "k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/types"
    35  	clientset "k8s.io/client-go/kubernetes"
    36  	clientsetscheme "k8s.io/client-go/kubernetes/scheme"
    37  	"k8s.io/klog/v2"
    38  	"sigs.k8s.io/yaml"
    39  
    40  	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
    41  	kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
    42  	"k8s.io/kubernetes/cmd/kubeadm/app/images"
    43  	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
    44  	"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
    45  	"k8s.io/kubernetes/cmd/kubeadm/app/util/image"
    46  	"k8s.io/kubernetes/cmd/kubeadm/app/util/patches"
    47  )
    48  
    49  const (
    50  	unableToDecodeCoreDNS = "unable to decode CoreDNS"
    51  	coreDNSReplicas       = 2
    52  )
    53  
    54  // DeployedDNSAddon returns the image tag of the DNS addon currently deployed
    55  func DeployedDNSAddon(client clientset.Interface) (string, error) {
    56  	deploymentsClient := client.AppsV1().Deployments(metav1.NamespaceSystem)
    57  	deployments, err := deploymentsClient.List(context.TODO(), metav1.ListOptions{LabelSelector: "k8s-app=kube-dns"})
    58  	if err != nil {
    59  		return "", errors.Wrap(err, "couldn't retrieve DNS addon deployments")
    60  	}
    61  
    62  	switch len(deployments.Items) {
    63  	case 0:
    64  		return "", nil
    65  	case 1:
    66  		return image.TagFromImage(deployments.Items[0].Spec.Template.Spec.Containers[0].Image), nil
    67  	default:
    68  		return "", errors.Errorf("multiple DNS addon deployments found: %v", deployments.Items)
    69  	}
    70  }
    71  
    72  // deployedDNSReplicas returns the replica count for the current DNS deployment
    73  func deployedDNSReplicas(client clientset.Interface, replicas int32) (*int32, error) {
    74  	deploymentsClient := client.AppsV1().Deployments(metav1.NamespaceSystem)
    75  	deployments, err := deploymentsClient.List(context.TODO(), metav1.ListOptions{LabelSelector: "k8s-app=kube-dns"})
    76  	if err != nil {
    77  		return &replicas, errors.Wrap(err, "couldn't retrieve DNS addon deployments")
    78  	}
    79  	switch len(deployments.Items) {
    80  	case 0:
    81  		return &replicas, nil
    82  	case 1:
    83  		return deployments.Items[0].Spec.Replicas, nil
    84  	default:
    85  		return &replicas, errors.Errorf("multiple DNS addon deployments found: %v", deployments.Items)
    86  	}
    87  }
    88  
    89  // EnsureDNSAddon creates the CoreDNS addon
    90  func EnsureDNSAddon(cfg *kubeadmapi.ClusterConfiguration, client clientset.Interface, patchesDir string, out io.Writer, printManifest bool) error {
    91  	var replicas *int32
    92  	var err error
    93  	if !printManifest {
    94  		replicas, err = deployedDNSReplicas(client, coreDNSReplicas)
    95  		if err != nil {
    96  			return err
    97  		}
    98  	} else {
    99  		var defaultReplicas int32 = coreDNSReplicas
   100  		replicas = &defaultReplicas
   101  	}
   102  	return coreDNSAddon(cfg, client, replicas, patchesDir, out, printManifest)
   103  }
   104  
   105  func coreDNSAddon(cfg *kubeadmapi.ClusterConfiguration, client clientset.Interface, replicas *int32, patchesDir string, out io.Writer, printManifest bool) error {
   106  	// Get the YAML manifest
   107  	coreDNSDeploymentBytes, err := kubeadmutil.ParseTemplate(CoreDNSDeployment, struct {
   108  		DeploymentName, Image, ControlPlaneTaintKey string
   109  		Replicas                                    *int32
   110  	}{
   111  		DeploymentName:       kubeadmconstants.CoreDNSDeploymentName,
   112  		Image:                images.GetDNSImage(cfg),
   113  		ControlPlaneTaintKey: kubeadmconstants.LabelNodeRoleControlPlane,
   114  		Replicas:             replicas,
   115  	})
   116  	if err != nil {
   117  		return errors.Wrap(err, "error when parsing CoreDNS deployment template")
   118  	}
   119  
   120  	// Apply patches to the CoreDNS Deployment
   121  	if len(patchesDir) != 0 {
   122  		coreDNSDeploymentBytes, err = applyCoreDNSDeploymentPatches(coreDNSDeploymentBytes, patchesDir, out)
   123  		if err != nil {
   124  			return errors.Wrap(err, "could not apply patches to the CoreDNS Deployment")
   125  		}
   126  	}
   127  
   128  	// Get the config file for CoreDNS
   129  	coreDNSConfigMapBytes, err := kubeadmutil.ParseTemplate(CoreDNSConfigMap, struct{ DNSDomain, UpstreamNameserver, StubDomain string }{
   130  		DNSDomain: cfg.Networking.DNSDomain,
   131  	})
   132  	if err != nil {
   133  		return errors.Wrap(err, "error when parsing CoreDNS configMap template")
   134  	}
   135  
   136  	dnsip, err := kubeadmconstants.GetDNSIP(cfg.Networking.ServiceSubnet)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	coreDNSServiceBytes, err := kubeadmutil.ParseTemplate(CoreDNSService, struct{ DNSIP string }{
   142  		DNSIP: dnsip.String(),
   143  	})
   144  
   145  	if err != nil {
   146  		return errors.Wrap(err, "error when parsing CoreDNS service template")
   147  	}
   148  
   149  	if printManifest {
   150  		fmt.Fprint(out, "---")
   151  		fmt.Fprintf(out, "%s", coreDNSDeploymentBytes)
   152  		fmt.Fprint(out, "---")
   153  		fmt.Fprintf(out, "%s", coreDNSConfigMapBytes)
   154  		fmt.Fprint(out, "---")
   155  		fmt.Fprintf(out, "%s", coreDNSServiceBytes)
   156  		fmt.Fprint(out, "---")
   157  		fmt.Fprintf(out, "%s", []byte(CoreDNSClusterRole))
   158  		fmt.Fprint(out, "---")
   159  		fmt.Fprintf(out, "%s", []byte(CoreDNSClusterRoleBinding))
   160  		fmt.Fprint(out, "---")
   161  		fmt.Fprintf(out, "%s", []byte(CoreDNSServiceAccount))
   162  		return nil
   163  	}
   164  
   165  	if err := createCoreDNSAddon(coreDNSDeploymentBytes, coreDNSServiceBytes, coreDNSConfigMapBytes, client); err != nil {
   166  		return err
   167  	}
   168  	fmt.Fprintln(out, "[addons] Applied essential addon: CoreDNS")
   169  	return nil
   170  }
   171  
   172  func createCoreDNSAddon(deploymentBytes, serviceBytes, configBytes []byte, client clientset.Interface) error {
   173  	coreDNSConfigMap := &v1.ConfigMap{}
   174  	if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), configBytes, coreDNSConfigMap); err != nil {
   175  		return errors.Wrapf(err, "%s ConfigMap", unableToDecodeCoreDNS)
   176  	}
   177  
   178  	// Create the ConfigMap for CoreDNS or update/migrate it in case it already exists
   179  	_, corefile, currentInstalledCoreDNSVersion, err := GetCoreDNSInfo(client)
   180  	if err != nil {
   181  		return errors.Wrap(err, "unable to fetch CoreDNS current installed version and ConfigMap.")
   182  	}
   183  
   184  	corefileMigrationRequired, err := isCoreDNSConfigMapMigrationRequired(corefile, currentInstalledCoreDNSVersion)
   185  	if err != nil {
   186  		return err
   187  	}
   188  
   189  	// Assume that migration is always possible, rely on migrateCoreDNSCorefile() to fail if not.
   190  	canMigrateCorefile := true
   191  
   192  	if corefile == "" || migration.Default("", corefile) {
   193  		// If the Corefile is empty or default, the latest default Corefile will be applied
   194  		if err := apiclient.CreateOrUpdateConfigMap(client, coreDNSConfigMap); err != nil {
   195  			return err
   196  		}
   197  	} else if corefileMigrationRequired {
   198  		// If migration is required, try and migrate the Corefile
   199  		if err := migrateCoreDNSCorefile(client, coreDNSConfigMap, corefile, currentInstalledCoreDNSVersion); err != nil {
   200  			// Errors in Corefile Migration is verified during preflight checks. This part will be executed when a user has chosen
   201  			// to ignore preflight check errors.
   202  			canMigrateCorefile = false
   203  			klog.Warningf("the CoreDNS Configuration was not migrated: %v. The existing CoreDNS Corefile configuration has been retained.", err)
   204  			if err := apiclient.CreateOrRetainConfigMap(client, coreDNSConfigMap, kubeadmconstants.CoreDNSConfigMap); err != nil {
   205  				return err
   206  			}
   207  		}
   208  	} else {
   209  		// If the Corefile is modified and doesn't require any migration, it'll be retained for the benefit of the user
   210  		if err := apiclient.CreateOrRetainConfigMap(client, coreDNSConfigMap, kubeadmconstants.CoreDNSConfigMap); err != nil {
   211  			return err
   212  		}
   213  	}
   214  
   215  	coreDNSClusterRoles := &rbac.ClusterRole{}
   216  	if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), []byte(CoreDNSClusterRole), coreDNSClusterRoles); err != nil {
   217  		return errors.Wrapf(err, "%s ClusterRole", unableToDecodeCoreDNS)
   218  	}
   219  
   220  	// Create the Clusterroles for CoreDNS or update it in case it already exists
   221  	if err := apiclient.CreateOrUpdateClusterRole(client, coreDNSClusterRoles); err != nil {
   222  		return err
   223  	}
   224  
   225  	coreDNSClusterRolesBinding := &rbac.ClusterRoleBinding{}
   226  	if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), []byte(CoreDNSClusterRoleBinding), coreDNSClusterRolesBinding); err != nil {
   227  		return errors.Wrapf(err, "%s ClusterRoleBinding", unableToDecodeCoreDNS)
   228  	}
   229  
   230  	// Create the Clusterrolebindings for CoreDNS or update it in case it already exists
   231  	if err := apiclient.CreateOrUpdateClusterRoleBinding(client, coreDNSClusterRolesBinding); err != nil {
   232  		return err
   233  	}
   234  
   235  	coreDNSServiceAccount := &v1.ServiceAccount{}
   236  	if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), []byte(CoreDNSServiceAccount), coreDNSServiceAccount); err != nil {
   237  		return errors.Wrapf(err, "%s ServiceAccount", unableToDecodeCoreDNS)
   238  	}
   239  
   240  	// Create the ConfigMap for CoreDNS or update it in case it already exists
   241  	if err := apiclient.CreateOrUpdateServiceAccount(client, coreDNSServiceAccount); err != nil {
   242  		return err
   243  	}
   244  
   245  	coreDNSDeployment := &apps.Deployment{}
   246  	if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), deploymentBytes, coreDNSDeployment); err != nil {
   247  		return errors.Wrapf(err, "%s Deployment", unableToDecodeCoreDNS)
   248  	}
   249  
   250  	// Create the deployment for CoreDNS or retain it in case the CoreDNS migration has failed during upgrade
   251  	if !canMigrateCorefile {
   252  		if err := apiclient.CreateOrRetainDeployment(client, coreDNSDeployment, kubeadmconstants.CoreDNSDeploymentName); err != nil {
   253  			return err
   254  		}
   255  	} else {
   256  		// Create the Deployment for CoreDNS or update it in case it already exists
   257  		if err := apiclient.CreateOrUpdateDeployment(client, coreDNSDeployment); err != nil {
   258  			return err
   259  		}
   260  	}
   261  
   262  	coreDNSService := &v1.Service{}
   263  	return createDNSService(coreDNSService, serviceBytes, client)
   264  }
   265  
   266  func createDNSService(dnsService *v1.Service, serviceBytes []byte, client clientset.Interface) error {
   267  	if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), serviceBytes, dnsService); err != nil {
   268  		return errors.Wrap(err, "unable to decode the DNS service")
   269  	}
   270  
   271  	// Can't use a generic apiclient helper func here as we have to tolerate more than AlreadyExists.
   272  	if _, err := client.CoreV1().Services(metav1.NamespaceSystem).Create(context.TODO(), dnsService, metav1.CreateOptions{}); err != nil {
   273  		// Ignore if the Service is invalid with this error message:
   274  		// 	Service "kube-dns" is invalid: spec.clusterIP: Invalid value: "10.96.0.10": provided IP is already allocated
   275  
   276  		if !apierrors.IsAlreadyExists(err) && !apierrors.IsInvalid(err) {
   277  			return errors.Wrap(err, "unable to create a new DNS service")
   278  		}
   279  
   280  		if _, err := client.CoreV1().Services(metav1.NamespaceSystem).Update(context.TODO(), dnsService, metav1.UpdateOptions{}); err != nil {
   281  			return errors.Wrap(err, "unable to create/update the DNS service")
   282  		}
   283  	}
   284  	return nil
   285  }
   286  
   287  // isCoreDNSConfigMapMigrationRequired checks if a migration of the CoreDNS ConfigMap is required.
   288  func isCoreDNSConfigMapMigrationRequired(corefile, currentInstalledCoreDNSVersion string) (bool, error) {
   289  	var isMigrationRequired bool
   290  
   291  	// Current installed version is expected to be empty for init
   292  	if currentInstalledCoreDNSVersion == "" {
   293  		return isMigrationRequired, nil
   294  	}
   295  	currentInstalledCoreDNSVersion = strings.TrimLeft(currentInstalledCoreDNSVersion, "v")
   296  	targetCoreDNSVersion := strings.TrimLeft(kubeadmconstants.CoreDNSVersion, "v")
   297  	if currentInstalledCoreDNSVersion == targetCoreDNSVersion {
   298  		return isMigrationRequired, nil
   299  	}
   300  	deprecated, err := migration.Deprecated(currentInstalledCoreDNSVersion, targetCoreDNSVersion, corefile)
   301  	if err != nil {
   302  		return isMigrationRequired, errors.Wrap(err, "unable to get list of changes to the configuration.")
   303  	}
   304  
   305  	// Check if there are any plugins/options which needs to be removed or is a new default
   306  	for _, dep := range deprecated {
   307  		if dep.Severity == "removed" || dep.Severity == "newdefault" {
   308  			isMigrationRequired = true
   309  		}
   310  	}
   311  
   312  	return isMigrationRequired, nil
   313  }
   314  
   315  func migrateCoreDNSCorefile(client clientset.Interface, cm *v1.ConfigMap, corefile, currentInstalledCoreDNSVersion string) error {
   316  	// Since the current configuration present is not the default version, try and migrate it.
   317  	updatedCorefile, err := migration.Migrate(currentInstalledCoreDNSVersion, strings.TrimLeft(kubeadmconstants.CoreDNSVersion, "v"), corefile, false)
   318  	if err != nil {
   319  		return errors.Wrap(err, "unable to migrate CoreDNS ConfigMap")
   320  	}
   321  
   322  	// Take a copy of the existing Corefile data as `Corefile-backup` and update the ConfigMap
   323  	if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Update(context.TODO(), &v1.ConfigMap{
   324  		ObjectMeta: metav1.ObjectMeta{
   325  			Name:      kubeadmconstants.CoreDNSConfigMap,
   326  			Namespace: metav1.NamespaceSystem,
   327  		},
   328  		Data: map[string]string{
   329  			"Corefile":        updatedCorefile,
   330  			"Corefile-backup": corefile,
   331  		},
   332  	}, metav1.UpdateOptions{}); err != nil {
   333  		return errors.Wrap(err, "unable to update the CoreDNS ConfigMap")
   334  	}
   335  
   336  	// Point the CoreDNS deployment to the `Corefile-backup` data.
   337  	if err := setCorefile(client, "Corefile-backup"); err != nil {
   338  		return err
   339  	}
   340  
   341  	fmt.Println("[addons] Migrating CoreDNS Corefile")
   342  	changes, err := migration.Deprecated(currentInstalledCoreDNSVersion, strings.TrimLeft(kubeadmconstants.CoreDNSVersion, "v"), corefile)
   343  	if err != nil {
   344  		return errors.Wrap(err, "unable to get list of changes to the configuration.")
   345  	}
   346  	// show the migration changes
   347  	klog.V(2).Infof("the CoreDNS configuration has been migrated and applied: %v.", updatedCorefile)
   348  	klog.V(2).Infoln("the old migration has been saved in the CoreDNS ConfigMap under the name [Corefile-backup]")
   349  	klog.V(2).Infoln("The changes in the new CoreDNS Configuration are as follows:")
   350  	for _, change := range changes {
   351  		klog.V(2).Infof("%v", change.ToString())
   352  	}
   353  	return nil
   354  }
   355  
   356  // GetCoreDNSInfo gets the current CoreDNS installed and the current Corefile Configuration of CoreDNS.
   357  func GetCoreDNSInfo(client clientset.Interface) (*v1.ConfigMap, string, string, error) {
   358  	coreDNSConfigMap, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(context.TODO(), kubeadmconstants.CoreDNSConfigMap, metav1.GetOptions{})
   359  	if err != nil {
   360  		if apierrors.IsNotFound(err) {
   361  			return nil, "", "", nil
   362  		}
   363  		return nil, "", "", err
   364  	}
   365  	corefile, ok := coreDNSConfigMap.Data["Corefile"]
   366  	if !ok {
   367  		return nil, "", "", errors.New("unable to find the CoreDNS Corefile data")
   368  	}
   369  
   370  	currentCoreDNSversion, err := DeployedDNSAddon(client)
   371  	if err != nil {
   372  		return nil, "", "", err
   373  	}
   374  
   375  	return coreDNSConfigMap, corefile, currentCoreDNSversion, nil
   376  }
   377  
   378  func setCorefile(client clientset.Interface, coreDNSCorefileName string) error {
   379  	dnsDeployment, err := client.AppsV1().Deployments(metav1.NamespaceSystem).Get(context.TODO(), kubeadmconstants.CoreDNSDeploymentName, metav1.GetOptions{})
   380  	if err != nil {
   381  		return err
   382  	}
   383  	patch := fmt.Sprintf(`{"spec":{"template":{"spec":{"volumes":[{"name": "config-volume", "configMap":{"name": "coredns", "items":[{"key": "%s", "path": "Corefile"}]}}]}}}}`, coreDNSCorefileName)
   384  
   385  	if _, err := client.AppsV1().Deployments(dnsDeployment.ObjectMeta.Namespace).Patch(context.TODO(), dnsDeployment.Name, types.StrategicMergePatchType, []byte(patch), metav1.PatchOptions{}); err != nil {
   386  		return errors.Wrap(err, "unable to patch the CoreDNS deployment")
   387  	}
   388  	return nil
   389  }
   390  
   391  // applyCoreDNSDeploymentPatches reads patches from a directory and applies them over the input coreDNSDeploymentBytes
   392  func applyCoreDNSDeploymentPatches(coreDNSDeploymentBytes []byte, patchesDir string, output io.Writer) ([]byte, error) {
   393  	patchManager, err := patches.GetPatchManagerForPath(patchesDir, patches.KnownTargets(), output)
   394  	if err != nil {
   395  		return nil, err
   396  	}
   397  
   398  	patchTarget := &patches.PatchTarget{
   399  		Name:                      patches.CoreDNSDeployment,
   400  		StrategicMergePatchObject: apps.Deployment{},
   401  		Data:                      coreDNSDeploymentBytes,
   402  	}
   403  	if err := patchManager.ApplyPatchesToTarget(patchTarget); err != nil {
   404  		return nil, err
   405  	}
   406  
   407  	coreDNSDeploymentBytes, err = yaml.JSONToYAML(patchTarget.Data)
   408  	if err != nil {
   409  		return nil, err
   410  	}
   411  
   412  	return coreDNSDeploymentBytes, nil
   413  }