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 }