...

Source file src/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns/dns.go

Documentation: k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns

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

View as plain text