mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 13:50:01 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			415 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			415 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2017 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package dns
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"io/ioutil"
 | |
| 	"net"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 
 | |
| 	"k8s.io/api/core/v1"
 | |
| 	utilfeature "k8s.io/apiserver/pkg/util/feature"
 | |
| 	"k8s.io/client-go/tools/record"
 | |
| 	"k8s.io/kubernetes/pkg/apis/core/validation"
 | |
| 	"k8s.io/kubernetes/pkg/features"
 | |
| 	runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
 | |
| 	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
 | |
| 	"k8s.io/kubernetes/pkg/kubelet/util/format"
 | |
| 
 | |
| 	"github.com/golang/glog"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	// The default dns opt strings.
 | |
| 	defaultDNSOptions = []string{"ndots:5"}
 | |
| )
 | |
| 
 | |
| type podDNSType int
 | |
| 
 | |
| const (
 | |
| 	podDNSCluster podDNSType = iota
 | |
| 	podDNSHost
 | |
| 	podDNSNone
 | |
| )
 | |
| 
 | |
| // Configurer is used for setting up DNS resolver configuration when launching pods.
 | |
| type Configurer struct {
 | |
| 	recorder record.EventRecorder
 | |
| 	nodeRef  *v1.ObjectReference
 | |
| 	nodeIP   net.IP
 | |
| 
 | |
| 	// If non-nil, use this for container DNS server.
 | |
| 	clusterDNS []net.IP
 | |
| 	// If non-empty, use this for container DNS search.
 | |
| 	ClusterDomain string
 | |
| 	// The path to the DNS resolver configuration file used as the base to generate
 | |
| 	// the container's DNS resolver configuration file. This can be used in
 | |
| 	// conjunction with clusterDomain and clusterDNS.
 | |
| 	ResolverConfig string
 | |
| }
 | |
| 
 | |
| // NewConfigurer returns a DNS configurer for launching pods.
 | |
| func NewConfigurer(recorder record.EventRecorder, nodeRef *v1.ObjectReference, nodeIP net.IP, clusterDNS []net.IP, clusterDomain, resolverConfig string) *Configurer {
 | |
| 	return &Configurer{
 | |
| 		recorder:       recorder,
 | |
| 		nodeRef:        nodeRef,
 | |
| 		nodeIP:         nodeIP,
 | |
| 		clusterDNS:     clusterDNS,
 | |
| 		ClusterDomain:  clusterDomain,
 | |
| 		ResolverConfig: resolverConfig,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func omitDuplicates(strs []string) []string {
 | |
| 	uniqueStrs := make(map[string]bool)
 | |
| 
 | |
| 	var ret []string
 | |
| 	for _, str := range strs {
 | |
| 		if !uniqueStrs[str] {
 | |
| 			ret = append(ret, str)
 | |
| 			uniqueStrs[str] = true
 | |
| 		}
 | |
| 	}
 | |
| 	return ret
 | |
| }
 | |
| 
 | |
| func (c *Configurer) formDNSSearchFitsLimits(composedSearch []string, pod *v1.Pod) []string {
 | |
| 	limitsExceeded := false
 | |
| 
 | |
| 	if len(composedSearch) > validation.MaxDNSSearchPaths {
 | |
| 		composedSearch = composedSearch[:validation.MaxDNSSearchPaths]
 | |
| 		limitsExceeded = true
 | |
| 	}
 | |
| 
 | |
| 	if resolvSearchLineStrLen := len(strings.Join(composedSearch, " ")); resolvSearchLineStrLen > validation.MaxDNSSearchListChars {
 | |
| 		cutDomainsNum := 0
 | |
| 		cutDomainsLen := 0
 | |
| 		for i := len(composedSearch) - 1; i >= 0; i-- {
 | |
| 			cutDomainsLen += len(composedSearch[i]) + 1
 | |
| 			cutDomainsNum++
 | |
| 
 | |
| 			if (resolvSearchLineStrLen - cutDomainsLen) <= validation.MaxDNSSearchListChars {
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		composedSearch = composedSearch[:(len(composedSearch) - cutDomainsNum)]
 | |
| 		limitsExceeded = true
 | |
| 	}
 | |
| 
 | |
| 	if limitsExceeded {
 | |
| 		log := fmt.Sprintf("Search Line limits were exceeded, some search paths have been omitted, the applied search line is: %s", strings.Join(composedSearch, " "))
 | |
| 		c.recorder.Event(pod, v1.EventTypeWarning, "DNSConfigForming", log)
 | |
| 		glog.Error(log)
 | |
| 	}
 | |
| 	return composedSearch
 | |
| }
 | |
| 
 | |
| func (c *Configurer) formDNSNameserversFitsLimits(nameservers []string, pod *v1.Pod) []string {
 | |
| 	if len(nameservers) > validation.MaxDNSNameservers {
 | |
| 		nameservers = nameservers[0:validation.MaxDNSNameservers]
 | |
| 		log := fmt.Sprintf("Nameserver limits were exceeded, some nameservers have been omitted, the applied nameserver line is: %s", strings.Join(nameservers, " "))
 | |
| 		c.recorder.Event(pod, v1.EventTypeWarning, "DNSConfigForming", log)
 | |
| 		glog.Error(log)
 | |
| 	}
 | |
| 	return nameservers
 | |
| }
 | |
| 
 | |
| func (c *Configurer) formDNSConfigFitsLimits(dnsConfig *runtimeapi.DNSConfig, pod *v1.Pod) *runtimeapi.DNSConfig {
 | |
| 	dnsConfig.Servers = c.formDNSNameserversFitsLimits(dnsConfig.Servers, pod)
 | |
| 	dnsConfig.Searches = c.formDNSSearchFitsLimits(dnsConfig.Searches, pod)
 | |
| 	return dnsConfig
 | |
| }
 | |
| 
 | |
| func (c *Configurer) generateSearchesForDNSClusterFirst(hostSearch []string, pod *v1.Pod) []string {
 | |
| 	if c.ClusterDomain == "" {
 | |
| 		return hostSearch
 | |
| 	}
 | |
| 
 | |
| 	nsSvcDomain := fmt.Sprintf("%s.svc.%s", pod.Namespace, c.ClusterDomain)
 | |
| 	svcDomain := fmt.Sprintf("svc.%s", c.ClusterDomain)
 | |
| 	clusterSearch := []string{nsSvcDomain, svcDomain, c.ClusterDomain}
 | |
| 
 | |
| 	return omitDuplicates(append(clusterSearch, hostSearch...))
 | |
| }
 | |
| 
 | |
| // CheckLimitsForResolvConf checks limits in resolv.conf.
 | |
| func (c *Configurer) CheckLimitsForResolvConf() {
 | |
| 	f, err := os.Open(c.ResolverConfig)
 | |
| 	if err != nil {
 | |
| 		c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", err.Error())
 | |
| 		glog.V(4).Infof("CheckLimitsForResolvConf: " + err.Error())
 | |
| 		return
 | |
| 	}
 | |
| 	defer f.Close()
 | |
| 
 | |
| 	_, hostSearch, _, err := parseResolvConf(f)
 | |
| 	if err != nil {
 | |
| 		c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", err.Error())
 | |
| 		glog.V(4).Infof("CheckLimitsForResolvConf: " + err.Error())
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	domainCountLimit := validation.MaxDNSSearchPaths
 | |
| 
 | |
| 	if c.ClusterDomain != "" {
 | |
| 		domainCountLimit -= 3
 | |
| 	}
 | |
| 
 | |
| 	if len(hostSearch) > domainCountLimit {
 | |
| 		log := fmt.Sprintf("Resolv.conf file '%s' contains search line consisting of more than %d domains!", c.ResolverConfig, domainCountLimit)
 | |
| 		c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", log)
 | |
| 		glog.V(4).Infof("CheckLimitsForResolvConf: " + log)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if len(strings.Join(hostSearch, " ")) > validation.MaxDNSSearchListChars {
 | |
| 		log := fmt.Sprintf("Resolv.conf file '%s' contains search line which length is more than allowed %d chars!", c.ResolverConfig, validation.MaxDNSSearchListChars)
 | |
| 		c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", log)
 | |
| 		glog.V(4).Infof("CheckLimitsForResolvConf: " + log)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // parseResolveConf reads a resolv.conf file from the given reader, and parses
 | |
| // it into nameservers, searches and options, possibly returning an error.
 | |
| func parseResolvConf(reader io.Reader) (nameservers []string, searches []string, options []string, err error) {
 | |
| 	file, err := ioutil.ReadAll(reader)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, nil, err
 | |
| 	}
 | |
| 
 | |
| 	// Lines of the form "nameserver 1.2.3.4" accumulate.
 | |
| 	nameservers = []string{}
 | |
| 
 | |
| 	// Lines of the form "search example.com" overrule - last one wins.
 | |
| 	searches = []string{}
 | |
| 
 | |
| 	// Lines of the form "option ndots:5 attempts:2" overrule - last one wins.
 | |
| 	// Each option is recorded as an element in the array.
 | |
| 	options = []string{}
 | |
| 
 | |
| 	lines := strings.Split(string(file), "\n")
 | |
| 	for l := range lines {
 | |
| 		trimmed := strings.TrimSpace(lines[l])
 | |
| 		if strings.HasPrefix(trimmed, "#") {
 | |
| 			continue
 | |
| 		}
 | |
| 		fields := strings.Fields(trimmed)
 | |
| 		if len(fields) == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		if fields[0] == "nameserver" && len(fields) >= 2 {
 | |
| 			nameservers = append(nameservers, fields[1])
 | |
| 		}
 | |
| 		if fields[0] == "search" {
 | |
| 			searches = fields[1:]
 | |
| 		}
 | |
| 		if fields[0] == "options" {
 | |
| 			options = fields[1:]
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nameservers, searches, options, nil
 | |
| }
 | |
| 
 | |
| func (c *Configurer) getHostDNSConfig(pod *v1.Pod) (*runtimeapi.DNSConfig, error) {
 | |
| 	var hostDNS, hostSearch, hostOptions []string
 | |
| 	// Get host DNS settings
 | |
| 	if c.ResolverConfig != "" {
 | |
| 		f, err := os.Open(c.ResolverConfig)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		defer f.Close()
 | |
| 
 | |
| 		hostDNS, hostSearch, hostOptions, err = parseResolvConf(f)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 	return &runtimeapi.DNSConfig{
 | |
| 		Servers:  hostDNS,
 | |
| 		Searches: hostSearch,
 | |
| 		Options:  hostOptions,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func getPodDNSType(pod *v1.Pod) (podDNSType, error) {
 | |
| 	dnsPolicy := pod.Spec.DNSPolicy
 | |
| 	switch dnsPolicy {
 | |
| 	case v1.DNSNone:
 | |
| 		if utilfeature.DefaultFeatureGate.Enabled(features.CustomPodDNS) {
 | |
| 			return podDNSNone, nil
 | |
| 		}
 | |
| 		// This should not happen as kube-apiserver should have rejected
 | |
| 		// setting dnsPolicy to DNSNone when feature gate is disabled.
 | |
| 		return podDNSCluster, fmt.Errorf(fmt.Sprintf("invalid DNSPolicy=%v: custom pod DNS is disabled", dnsPolicy))
 | |
| 	case v1.DNSClusterFirstWithHostNet:
 | |
| 		return podDNSCluster, nil
 | |
| 	case v1.DNSClusterFirst:
 | |
| 		if !kubecontainer.IsHostNetworkPod(pod) {
 | |
| 			return podDNSCluster, nil
 | |
| 		}
 | |
| 		// Fallback to DNSDefault for pod on hostnetowrk.
 | |
| 		fallthrough
 | |
| 	case v1.DNSDefault:
 | |
| 		return podDNSHost, nil
 | |
| 	}
 | |
| 	// This should not happen as kube-apiserver should have rejected
 | |
| 	// invalid dnsPolicy.
 | |
| 	return podDNSCluster, fmt.Errorf(fmt.Sprintf("invalid DNSPolicy=%v", dnsPolicy))
 | |
| }
 | |
| 
 | |
| // Merge DNS options. If duplicated, entries given by PodDNSConfigOption will
 | |
| // overwrite the existing ones.
 | |
| func mergeDNSOptions(existingDNSConfigOptions []string, dnsConfigOptions []v1.PodDNSConfigOption) []string {
 | |
| 	optionsMap := make(map[string]string)
 | |
| 	for _, op := range existingDNSConfigOptions {
 | |
| 		if index := strings.Index(op, ":"); index != -1 {
 | |
| 			optionsMap[op[:index]] = op[index+1:]
 | |
| 		} else {
 | |
| 			optionsMap[op] = ""
 | |
| 		}
 | |
| 	}
 | |
| 	for _, op := range dnsConfigOptions {
 | |
| 		if op.Value != nil {
 | |
| 			optionsMap[op.Name] = *op.Value
 | |
| 		} else {
 | |
| 			optionsMap[op.Name] = ""
 | |
| 		}
 | |
| 	}
 | |
| 	// Reconvert DNS options into a string array.
 | |
| 	options := []string{}
 | |
| 	for opName, opValue := range optionsMap {
 | |
| 		op := opName
 | |
| 		if opValue != "" {
 | |
| 			op = op + ":" + opValue
 | |
| 		}
 | |
| 		options = append(options, op)
 | |
| 	}
 | |
| 	return options
 | |
| }
 | |
| 
 | |
| // appendDNSConfig appends DNS servers, search paths and options given by
 | |
| // PodDNSConfig to the existing DNS config. Duplicated entries will be merged.
 | |
| // This assumes existingDNSConfig and dnsConfig are not nil.
 | |
| func appendDNSConfig(existingDNSConfig *runtimeapi.DNSConfig, dnsConfig *v1.PodDNSConfig) *runtimeapi.DNSConfig {
 | |
| 	existingDNSConfig.Servers = omitDuplicates(append(existingDNSConfig.Servers, dnsConfig.Nameservers...))
 | |
| 	existingDNSConfig.Searches = omitDuplicates(append(existingDNSConfig.Searches, dnsConfig.Searches...))
 | |
| 	existingDNSConfig.Options = mergeDNSOptions(existingDNSConfig.Options, dnsConfig.Options)
 | |
| 	return existingDNSConfig
 | |
| }
 | |
| 
 | |
| // GetPodDNS returns DNS settings for the pod.
 | |
| func (c *Configurer) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error) {
 | |
| 	dnsConfig, err := c.getHostDNSConfig(pod)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	dnsType, err := getPodDNSType(pod)
 | |
| 	if err != nil {
 | |
| 		glog.Errorf("Failed to get DNS type for pod %q: %v. Falling back to DNSClusterFirst policy.", format.Pod(pod), err)
 | |
| 		dnsType = podDNSCluster
 | |
| 	}
 | |
| 	switch dnsType {
 | |
| 	case podDNSNone:
 | |
| 		// DNSNone should use empty DNS settings as the base.
 | |
| 		dnsConfig = &runtimeapi.DNSConfig{}
 | |
| 	case podDNSCluster:
 | |
| 		if len(c.clusterDNS) != 0 {
 | |
| 			// For a pod with DNSClusterFirst policy, the cluster DNS server is
 | |
| 			// the only nameserver configured for the pod. The cluster DNS server
 | |
| 			// itself will forward queries to other nameservers that is configured
 | |
| 			// to use, in case the cluster DNS server cannot resolve the DNS query
 | |
| 			// itself.
 | |
| 			dnsConfig.Servers = []string{}
 | |
| 			for _, ip := range c.clusterDNS {
 | |
| 				dnsConfig.Servers = append(dnsConfig.Servers, ip.String())
 | |
| 			}
 | |
| 			dnsConfig.Searches = c.generateSearchesForDNSClusterFirst(dnsConfig.Searches, pod)
 | |
| 			dnsConfig.Options = defaultDNSOptions
 | |
| 			break
 | |
| 		}
 | |
| 		// clusterDNS is not known. Pod with ClusterDNSFirst Policy cannot be created.
 | |
| 		nodeErrorMsg := fmt.Sprintf("kubelet does not have ClusterDNS IP configured and cannot create Pod using %q policy. Falling back to %q policy.", v1.DNSClusterFirst, v1.DNSDefault)
 | |
| 		c.recorder.Eventf(c.nodeRef, v1.EventTypeWarning, "MissingClusterDNS", nodeErrorMsg)
 | |
| 		c.recorder.Eventf(pod, v1.EventTypeWarning, "MissingClusterDNS", "pod: %q. %s", format.Pod(pod), nodeErrorMsg)
 | |
| 		// Fallback to DNSDefault.
 | |
| 		fallthrough
 | |
| 	case podDNSHost:
 | |
| 		// When the kubelet --resolv-conf flag is set to the empty string, use
 | |
| 		// DNS settings that override the docker default (which is to use
 | |
| 		// /etc/resolv.conf) and effectively disable DNS lookups. According to
 | |
| 		// the bind documentation, the behavior of the DNS client library when
 | |
| 		// "nameservers" are not specified is to "use the nameserver on the
 | |
| 		// local machine". A nameserver setting of localhost is equivalent to
 | |
| 		// this documented behavior.
 | |
| 		if c.ResolverConfig == "" {
 | |
| 			switch {
 | |
| 			case c.nodeIP == nil || c.nodeIP.To4() != nil:
 | |
| 				dnsConfig.Servers = []string{"127.0.0.1"}
 | |
| 			case c.nodeIP.To16() != nil:
 | |
| 				dnsConfig.Servers = []string{"::1"}
 | |
| 			}
 | |
| 			dnsConfig.Searches = []string{"."}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if utilfeature.DefaultFeatureGate.Enabled(features.CustomPodDNS) && pod.Spec.DNSConfig != nil {
 | |
| 		dnsConfig = appendDNSConfig(dnsConfig, pod.Spec.DNSConfig)
 | |
| 	}
 | |
| 	return c.formDNSConfigFitsLimits(dnsConfig, pod), nil
 | |
| }
 | |
| 
 | |
| // SetupDNSinContainerizedMounter replaces the nameserver in containerized-mounter's rootfs/etc/resolve.conf with kubelet.ClusterDNS
 | |
| func (c *Configurer) SetupDNSinContainerizedMounter(mounterPath string) {
 | |
| 	resolvePath := filepath.Join(strings.TrimSuffix(mounterPath, "/mounter"), "rootfs", "etc", "resolv.conf")
 | |
| 	dnsString := ""
 | |
| 	for _, dns := range c.clusterDNS {
 | |
| 		dnsString = dnsString + fmt.Sprintf("nameserver %s\n", dns)
 | |
| 	}
 | |
| 	if c.ResolverConfig != "" {
 | |
| 		f, err := os.Open(c.ResolverConfig)
 | |
| 		defer f.Close()
 | |
| 		if err != nil {
 | |
| 			glog.Error("Could not open resolverConf file")
 | |
| 		} else {
 | |
| 			_, hostSearch, _, err := parseResolvConf(f)
 | |
| 			if err != nil {
 | |
| 				glog.Errorf("Error for parsing the reslov.conf file: %v", err)
 | |
| 			} else {
 | |
| 				dnsString = dnsString + "search"
 | |
| 				for _, search := range hostSearch {
 | |
| 					dnsString = dnsString + fmt.Sprintf(" %s", search)
 | |
| 				}
 | |
| 				dnsString = dnsString + "\n"
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if err := ioutil.WriteFile(resolvePath, []byte(dnsString), 0600); err != nil {
 | |
| 		glog.Errorf("Could not write dns nameserver in file %s, with error %v", resolvePath, err)
 | |
| 	}
 | |
| }
 |