diff --git a/cluster/addons.go b/cluster/addons.go index ec37fa81..457d8239 100644 --- a/cluster/addons.go +++ b/cluster/addons.go @@ -43,6 +43,7 @@ const ( CoreDNSProvider = "coredns" KubeDNSProvider = "kube-dns" + Nodelocal = "nodelocal" ) var DNSProviders = []string{KubeDNSProvider, CoreDNSProvider} @@ -101,6 +102,16 @@ type KubeDNSOptions struct { LinearAutoscalerParams string } +type NodelocalOptions struct { + RBACConfig string + NodelocalImage string + ClusterDomain string + ClusterDNSServer string + IPAddress string + NodeSelector map[string]string + UpdateStrategy *appsv1.DaemonSetUpdateStrategy +} + type addonError struct { err string isCritical bool @@ -329,7 +340,7 @@ func (c *Cluster) deployCoreDNS(ctx context.Context, data map[string]interface{} if err := c.doAddonDeploy(ctx, coreDNSYaml, getAddonResourceName(c.DNS.Provider), false); err != nil { return err } - log.Infof(ctx, "[addons] CoreDNS deployed successfully..") + log.Infof(ctx, "[addons] CoreDNS deployed successfully") return nil } @@ -587,7 +598,6 @@ func (c *Cluster) deployDNS(ctx context.Context, data map[string]interface{}) er log.Warnf(ctx, "Failed to deploy addon execute job [%s]: %v", getAddonResourceName(c.DNS.Provider), err) } log.Infof(ctx, "[dns] DNS provider %s deployed successfully", c.DNS.Provider) - return nil case CoreDNSProvider: if err := c.deployCoreDNS(ctx, data); err != nil { if err, ok := err.(*addonError); ok && err.isCritical { @@ -596,11 +606,62 @@ func (c *Cluster) deployDNS(ctx context.Context, data map[string]interface{}) er log.Warnf(ctx, "Failed to deploy addon execute job [%s]: %v", getAddonResourceName(c.DNS.Provider), err) } log.Infof(ctx, "[dns] DNS provider %s deployed successfully", c.DNS.Provider) - return nil case "none": return nil default: log.Warnf(ctx, "[dns] No valid DNS provider configured: %s", c.DNS.Provider) return nil } + // Check for nodelocal DNS + if c.DNS.Nodelocal == nil { + AddonJobExists, err := addons.AddonJobExists(getAddonResourceName(Nodelocal)+"-deploy-job", c.LocalKubeConfigPath, c.K8sWrapTransport) + if err != nil { + return err + } + if AddonJobExists { + log.Infof(ctx, "[dns] removing %s", Nodelocal) + if err := c.doAddonDelete(ctx, getAddonResourceName(Nodelocal), false); err != nil { + return err + } + + log.Infof(ctx, "[dns] %s removed successfully", Nodelocal) + return nil + } + } + if c.DNS.Nodelocal != nil && c.DNS.Nodelocal.IPAddress != "" { + if err := c.deployNodelocal(ctx, data); err != nil { + if err, ok := err.(*addonError); ok && err.isCritical { + return err + } + log.Warnf(ctx, "Failed to deploy addon execute job [%s]: %v", getAddonResourceName(Nodelocal), err) + } + return nil + } + return nil +} + +func (c *Cluster) deployNodelocal(ctx context.Context, data map[string]interface{}) error { + log.Infof(ctx, "[dns] Setting up %s", Nodelocal) + NodelocalConfig := NodelocalOptions{ + NodelocalImage: c.SystemImages.Nodelocal, + RBACConfig: c.Authorization.Mode, + ClusterDomain: c.ClusterDomain, + ClusterDNSServer: c.ClusterDNSServer, + IPAddress: c.DNS.Nodelocal.IPAddress, + NodeSelector: c.DNS.Nodelocal.NodeSelector, + UpdateStrategy: c.DNS.Nodelocal.UpdateStrategy, + } + tmplt, err := templates.GetVersionedTemplates(kdm.Nodelocal, data, c.Version) + if err != nil { + return err + } + nodelocalYaml, err := templates.CompileTemplateFromMap(tmplt, NodelocalConfig) + if err != nil { + return err + } + if err := c.doAddonDeploy(ctx, nodelocalYaml, getAddonResourceName(Nodelocal), false); err != nil { + return err + } + log.Infof(ctx, "[dns] %s deployed successfully", Nodelocal) + return nil } diff --git a/cluster/cluster.go b/cluster/cluster.go index 0a7287b7..ecb60aff 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -108,6 +108,7 @@ const ( monitoringAddon = "monitoring" dnsAddon = "dns" networkAddon = "network" + nodelocalAddon = "nodelocal" ) func (c *Cluster) DeployControlPlane(ctx context.Context, svcOptionData map[string]*v3.KubernetesServicesOptions, reconcileCluster bool) (string, error) { @@ -420,9 +421,16 @@ func parseAddonConfig(clusterFile string, rkeConfig *v3.RancherKubernetesEngineC networkAddon: daemonsetType, monitoringAddon: deploymentType, dnsAddon: deploymentType, + nodelocalAddon: daemonsetType, } for addonName, addonType := range addonsResourceType { - updateStrategyField := values.GetValueN(r, addonName, "update_strategy") + var updateStrategyField interface{} + // nodelocal is a field under dns + if addonName == nodelocalAddon { + updateStrategyField = values.GetValueN(r, "dns", addonName, "update_strategy") + } else { + updateStrategyField = values.GetValueN(r, addonName, "update_strategy") + } if updateStrategyField == nil { continue } @@ -437,6 +445,8 @@ func parseAddonConfig(clusterFile string, rkeConfig *v3.RancherKubernetesEngineC rkeConfig.Ingress.UpdateStrategy = updateStrategy case networkAddon: rkeConfig.Network.UpdateStrategy = updateStrategy + case nodelocalAddon: + rkeConfig.DNS.Nodelocal.UpdateStrategy = updateStrategy } case deploymentType: updateStrategy, err := parseDeploymentUpdateStrategy(updateStrategyField) diff --git a/cluster/defaults.go b/cluster/defaults.go index c747f594..ee0695ae 100644 --- a/cluster/defaults.go +++ b/cluster/defaults.go @@ -453,6 +453,7 @@ func (c *Cluster) setClusterImageDefaults() error { &c.SystemImages.Ingress: d(imageDefaults.Ingress, privRegURL), &c.SystemImages.IngressBackend: d(imageDefaults.IngressBackend, privRegURL), &c.SystemImages.MetricsServer: d(imageDefaults.MetricsServer, privRegURL), + &c.SystemImages.Nodelocal: d(imageDefaults.Nodelocal, privRegURL), // this's a stopgap, we could drop this after https://github.com/kubernetes/kubernetes/pull/75618 merged &c.SystemImages.WindowsPodInfraContainer: d(imageDefaults.WindowsPodInfraContainer, privRegURL), } diff --git a/cluster/plan.go b/cluster/plan.go index b976695e..8b34184b 100644 --- a/cluster/plan.go +++ b/cluster/plan.go @@ -571,6 +571,11 @@ func (c *Cluster) BuildKubeletProcess(host *hosts.Host, prefixPath string, servi } } + // If nodelocal DNS is configured, set cluster-dns to local IP + if c.DNS.Nodelocal != nil && c.DNS.Nodelocal.IPAddress != "" { + CommandArgs["cluster-dns"] = c.DNS.Nodelocal.IPAddress + } + for arg, value := range CommandArgs { cmd := fmt.Sprintf("--%s=%s", arg, value) Command = append(Command, cmd) diff --git a/cluster/validation.go b/cluster/validation.go index 9561d349..a9a5d503 100644 --- a/cluster/validation.go +++ b/cluster/validation.go @@ -2,6 +2,7 @@ package cluster import ( "context" + "errors" "fmt" "strings" @@ -122,16 +123,16 @@ func validateServicesOptions(c *Cluster) error { // Validate external etcd information if len(c.Services.Etcd.ExternalURLs) > 0 { if len(c.Services.Etcd.CACert) == 0 { - return fmt.Errorf("External CA Certificate for etcd can't be empty") + return errors.New("External CA Certificate for etcd can't be empty") } if len(c.Services.Etcd.Cert) == 0 { - return fmt.Errorf("External Client Certificate for etcd can't be empty") + return errors.New("External Client Certificate for etcd can't be empty") } if len(c.Services.Etcd.Key) == 0 { - return fmt.Errorf("External Client Key for etcd can't be empty") + return errors.New("External Client Key for etcd can't be empty") } if len(c.Services.Etcd.Path) == 0 { - return fmt.Errorf("External etcd path can't be empty") + return errors.New("External etcd path can't be empty") } } @@ -147,10 +148,10 @@ func validateEtcdBackupOptions(c *Cluster) error { if c.Services.Etcd.BackupConfig != nil { if c.Services.Etcd.BackupConfig.S3BackupConfig != nil { if len(c.Services.Etcd.BackupConfig.S3BackupConfig.Endpoint) == 0 { - return fmt.Errorf("etcd s3 backup backend endpoint can't be empty") + return errors.New("etcd s3 backup backend endpoint can't be empty") } if len(c.Services.Etcd.BackupConfig.S3BackupConfig.BucketName) == 0 { - return fmt.Errorf("etcd s3 backup backend bucketName can't be empty") + return errors.New("etcd s3 backup backend bucketName can't be empty") } if len(c.Services.Etcd.BackupConfig.S3BackupConfig.CustomCA) != 0 { if isValid, err := pki.IsValidCertStr(c.Services.Etcd.BackupConfig.S3BackupConfig.CustomCA); !isValid { @@ -188,10 +189,10 @@ func ValidateHostCount(c *Cluster) error { } return fmt.Errorf("Cluster must have at least one etcd plane host: failed to connect to the following etcd host(s) %v", failedEtcdHosts) } - return fmt.Errorf("Cluster must have at least one etcd plane host: please specify one or more etcd in cluster config") + return errors.New("Cluster must have at least one etcd plane host: please specify one or more etcd in cluster config") } if len(c.EtcdHosts) > 0 && len(c.Services.Etcd.ExternalURLs) > 0 { - return fmt.Errorf("Cluster can't have both internal and external etcd") + return errors.New("Cluster can't have both internal and external etcd") } return nil } @@ -255,25 +256,25 @@ func validateSystemImages(c *Cluster) error { func validateKubernetesImages(c *Cluster) error { if len(c.SystemImages.Etcd) == 0 { - return fmt.Errorf("etcd image is not populated") + return errors.New("etcd image is not populated") } if len(c.SystemImages.Kubernetes) == 0 { - return fmt.Errorf("kubernetes image is not populated") + return errors.New("kubernetes image is not populated") } if len(c.SystemImages.PodInfraContainer) == 0 { - return fmt.Errorf("pod infrastructure container image is not populated") + return errors.New("pod infrastructure container image is not populated") } if len(c.SystemImages.Alpine) == 0 { - return fmt.Errorf("alpine image is not populated") + return errors.New("alpine image is not populated") } if len(c.SystemImages.NginxProxy) == 0 { - return fmt.Errorf("nginx proxy image is not populated") + return errors.New("nginx proxy image is not populated") } if len(c.SystemImages.CertDownloader) == 0 { - return fmt.Errorf("certificate downloader image is not populated") + return errors.New("certificate downloader image is not populated") } if len(c.SystemImages.KubernetesServicesSidecar) == 0 { - return fmt.Errorf("kubernetes sidecar image is not populated") + return errors.New("kubernetes sidecar image is not populated") } return nil } @@ -282,40 +283,40 @@ func validateNetworkImages(c *Cluster) error { // check network provider images if c.Network.Plugin == FlannelNetworkPlugin { if len(c.SystemImages.Flannel) == 0 { - return fmt.Errorf("flannel image is not populated") + return errors.New("flannel image is not populated") } if len(c.SystemImages.FlannelCNI) == 0 { - return fmt.Errorf("flannel cni image is not populated") + return errors.New("flannel cni image is not populated") } } else if c.Network.Plugin == CanalNetworkPlugin { if len(c.SystemImages.CanalNode) == 0 { - return fmt.Errorf("canal image is not populated") + return errors.New("canal image is not populated") } if len(c.SystemImages.CanalCNI) == 0 { - return fmt.Errorf("canal cni image is not populated") + return errors.New("canal cni image is not populated") } if len(c.SystemImages.CanalFlannel) == 0 { - return fmt.Errorf("flannel image is not populated") + return errors.New("flannel image is not populated") } } else if c.Network.Plugin == CalicoNetworkPlugin { if len(c.SystemImages.CalicoCNI) == 0 { - return fmt.Errorf("calico cni image is not populated") + return errors.New("calico cni image is not populated") } if len(c.SystemImages.CalicoCtl) == 0 { - return fmt.Errorf("calico ctl image is not populated") + return errors.New("calico ctl image is not populated") } if len(c.SystemImages.CalicoNode) == 0 { - return fmt.Errorf("calico image is not populated") + return errors.New("calico image is not populated") } if len(c.SystemImages.CalicoControllers) == 0 { - return fmt.Errorf("calico controllers image is not populated") + return errors.New("calico controllers image is not populated") } } else if c.Network.Plugin == WeaveNetworkPlugin { if len(c.SystemImages.WeaveCNI) == 0 { - return fmt.Errorf("weave cni image is not populated") + return errors.New("weave cni image is not populated") } if len(c.SystemImages.WeaveNode) == 0 { - return fmt.Errorf("weave image is not populated") + return errors.New("weave image is not populated") } } return nil @@ -325,25 +326,28 @@ func validateDNSImages(c *Cluster) error { // check dns provider images if c.DNS.Provider == "kube-dns" { if len(c.SystemImages.KubeDNS) == 0 { - return fmt.Errorf("kubedns image is not populated") + return errors.New("kubedns image is not populated") } if len(c.SystemImages.DNSmasq) == 0 { - return fmt.Errorf("dnsmasq image is not populated") + return errors.New("dnsmasq image is not populated") } if len(c.SystemImages.KubeDNSSidecar) == 0 { - return fmt.Errorf("kubedns sidecar image is not populated") + return errors.New("kubedns sidecar image is not populated") } if len(c.SystemImages.KubeDNSAutoscaler) == 0 { - return fmt.Errorf("kubedns autoscaler image is not populated") + return errors.New("kubedns autoscaler image is not populated") } } else if c.DNS.Provider == "coredns" { if len(c.SystemImages.CoreDNS) == 0 { - return fmt.Errorf("coredns image is not populated") + return errors.New("coredns image is not populated") } if len(c.SystemImages.CoreDNSAutoscaler) == 0 { - return fmt.Errorf("coredns autoscaler image is not populated") + return errors.New("coredns autoscaler image is not populated") } } + if c.DNS.Nodelocal != nil && len(c.SystemImages.Nodelocal) == 0 { + return errors.New("nodelocal image is not populated") + } return nil } @@ -351,7 +355,7 @@ func validateMetricsImages(c *Cluster) error { // checl metrics server image if c.Monitoring.Provider != "none" { if len(c.SystemImages.MetricsServer) == 0 { - return fmt.Errorf("metrics server images is not populated") + return errors.New("metrics server images is not populated") } } return nil @@ -361,10 +365,10 @@ func validateIngressImages(c *Cluster) error { // check ingress images if c.Ingress.Provider != "none" { if len(c.SystemImages.Ingress) == 0 { - return fmt.Errorf("ingress image is not populated") + return errors.New("ingress image is not populated") } if len(c.SystemImages.IngressBackend) == 0 { - return fmt.Errorf("ingress backend image is not populated") + return errors.New("ingress backend image is not populated") } } return nil