Change:
Kubernetes version v1.3.0 Kubelet concatenates host and cluster domain entries for my pod, resulting in an invalid config (too many entries with duplicates). The max is only 6 search domains. Any more and DNS resolution breaks when trying to leverage search domains. The example below has only cluster DNS entries, but illustrates the issue: Host search nameserver 10.233.0.2 nameserver 10.90.0.1 options timeout:2 options attempts:2 Container kubectl run -i --tty busybox --image=busybox -- sh (skip waiting text) / # cat search nameserver 10.233.0.2 options attempts:2 options ndots:5 I should be enable cluster DNS config in my host DNS settings and still have functional pod DNS. Kubelet should be intelligent enough to filter out duplicate search domain entries (or drop search domains beyond the 6th entry)
I think some sane checks needs to be too. libc limits search field to 6 records and 255(?) characters. This limitation should be checked before creation.
Hey guys, I'm working on it.
Dup of

Discussion:
// handled by pod workers). go wait.Until(kl.podKiller, 1*time.Second, wait.NeverStop) <ins> // Start gorouting responsible for checking limits in resolv.conf if kl.resolverConfig != "" { go wait.Until(func() { kl.checkLimitsForResolvConf() }, 30*time.Second, wait.NeverStop) } </ins> // Start component sync loops. kl.statusManager.Start() kl.probeManager.Start()

if kl.resolverConfig == "" { hostDNS = []string{"127.0.0.1"} hostSearch = []string{"."} <ins> } else { hostSearch = kl.formDNSSearchForDNSDefault(hostSearch, pod) </ins> } return hostDNS, hostSearch, nil }

// 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 dns := []string{kl.clusterDNS.String()} <del> var dnsSearch []string if kl.clusterDomain != "" { nsSvcDomain := fmt.Sprintf("%s.svc.%s", pod.Namespace, kl.clusterDomain) svcDomain := fmt.Sprintf("svc.%s", kl.clusterDomain) dnsSearch = append([]string{nsSvcDomain, svcDomain, kl.clusterDomain}, hostSearch...) } else { dnsSearch = hostSearch } </del> <ins> dnsSearch := kl.formDNSSearch(hostSearch, pod) </ins> return dns, dnsSearch, nil }

"fmt" "io" "io/ioutil" <ins> "os" </ins> "strings" "github.com/golang/glog"

return supported } <ins> func omitDuplicates(kl *Kubelet, pod *v1.Pod, combinedSearch []string) []string { uniqueDomains := map[string]bool{} for _, dnsDomain := range combinedSearch { if _, exists := uniqueDomains[dnsDomain]; !exists { combinedSearch[len(uniqueDomains)] = dnsDomain uniqueDomains[dnsDomain] = true } else { log := fmt.Sprintf("Found and omitted duplicated dns domain in host search line: '%s' during merging with cluster dns domains", dnsDomain) kl.recorder.Event(pod, v1.EventTypeWarning, "DNSSearchForming", log) glog.Error(log) } } return combinedSearch[:len(uniqueDomains)] } func formDNSSearchFitsLimits(kl *Kubelet, pod *v1.Pod, composedSearch []string) []string { // resolver file Search line current limitations resolvSearchLineDNSDomainsLimit := 6 resolvSearchLineLenLimit := 255 limitsExceeded := false if len(composedSearch) > resolvSearchLineDNSDomainsLimit { composedSearch = composedSearch[:resolvSearchLineDNSDomainsLimit] limitsExceeded = true } if resolvSearchhLineStrLen := len(strings.Join(composedSearch, " ")); resolvSearchhLineStrLen > resolvSearchLineLenLimit { cutDomainsNum := 0 cutDoaminsLen := 0 for i := len(composedSearch) - 1; i >= 0; i-- { cutDoaminsLen += len(composedSearch[i]) + 1 cutDomainsNum++ if (resolvSearchhLineStrLen - cutDoaminsLen) <= resolvSearchLineLenLimit { break } } composedSearch = composedSearch[:(len(composedSearch) - cutDomainsNum)] limitsExceeded = true } if limitsExceeded { log := fmt.Sprintf("Search Line limits were exceeded, some dns names have been omitted, the applied search line is: %s", strings.Join(composedSearch, " ")) kl.recorder.Event(pod, v1.EventTypeWarning, "DNSSearchForming", log) glog.Error(log) } return composedSearch } func (kl *Kubelet) formDNSSearchForDNSDefault(hostSearch []string, pod *v1.Pod) []string { return formDNSSearchFitsLimits(kl, pod, hostSearch) } func (kl *Kubelet) formDNSSearch(hostSearch []string, pod *v1.Pod) []string { if kl.clusterDomain == "" { formDNSSearchFitsLimits(kl, pod, hostSearch) return hostSearch } nsSvcDomain := fmt.Sprintf("%s.svc.%s", pod.Namespace, kl.clusterDomain) svcDomain := fmt.Sprintf("svc.%s", kl.clusterDomain) dnsSearch := []string{nsSvcDomain, svcDomain, kl.clusterDomain} combinedSearch := append(dnsSearch, hostSearch...) combinedSearch = omitDuplicates(kl, pod, combinedSearch) return formDNSSearchFitsLimits(kl, pod, combinedSearch) } func (kl *Kubelet) checkLimitsForResolvConf() { // resolver file Search line current limitations resolvSearchLineDNSDomainsLimit := 6 resolvSearchLineLenLimit := 255 f, err := os.Open(kl.resolverConfig) if err != nil { kl.recorder.Event(kl.nodeRef, v1.EventTypeWarning, "checkLimitsForResolvConf", err.Error()) glog.Error("checkLimitsForResolvConf: " + err.Error()) return } defer f.Close() _, hostSearch, err := kl.parseResolvConf(f) if err != nil { kl.recorder.Event(kl.nodeRef, v1.EventTypeWarning, "checkLimitsForResolvConf", err.Error()) glog.Error("checkLimitsForResolvConf: " + err.Error()) return } domainCntLimit := resolvSearchLineDNSDomainsLimit if kl.clusterDomain != "" { domainCntLimit -= 3 } if len(hostSearch) > domainCntLimit { log := fmt.Sprintf("Resolv.conf file '%s' contains search line consisting of more than %d domains!", kl.resolverConfig, domainCntLimit) kl.recorder.Event(kl.nodeRef, v1.EventTypeWarning, "checkLimitsForResolvConf", log) glog.Error("checkLimitsForResolvConf: " + log) return } if len(strings.Join(hostSearch, " ")) > resolvSearchLineLenLimit { log := fmt.Sprintf("Resolv.conf file '%s' contains search line which length is more than allowed %d chars!", kl.resolverConfig, resolvSearchLineLenLimit) kl.recorder.Event(kl.nodeRef, v1.EventTypeWarning, "checkLimitsForResolvConf", log) glog.Error("checkLimitsForResolvConf: " + log) return } return } </ins> // parseResolveConf reads a resolv.conf file from the given reader, and parses // it into nameservers and searches, possibly returning an error. // TODO: move to utility package

package kubelet import ( <ins> "fmt" </ins> "net" "reflect" "strings" "testing" "k8s.io/kubernetes/pkg/api/v1" <ins> "k8s.io/kubernetes/pkg/client/record" </ins> "k8s.io/kubernetes/pkg/util/bandwidth" )

} } <ins> func TestComposeDNSSearch(t *testing.T) { testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) kubelet := testKubelet.kubelet recorder := record.NewFakeRecorder(20) kubelet.recorder = recorder pod := podWithUidNameNs("", "test_pod", "testNS") kubelet.clusterDomain = "TEST" testCases := []struct { dnsNames     []string hostNames    []string resultSearch []string events       []string }{ { []string{"testNS.svc.TEST", "svc.TEST", "TEST"}, []string{}, []string{"testNS.svc.TEST", "svc.TEST", "TEST"}, []string{}, }, { []string{"testNS.svc.TEST", "svc.TEST", "TEST"}, []string{"AAA", "svc.TEST", "BBB", "TEST"}, []string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB"}, []string{ "Found and omitted duplicated dns domain in host search line: 'svc.TEST' during merging with cluster dns domains", "Found and omitted duplicated dns domain in host search line: 'TEST' during merging with cluster dns domains", }, }, { []string{"testNS.svc.TEST", "svc.TEST", "TEST"}, []string{"AAA", strings.Repeat("B", 256), "BBB"}, []string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA"}, []string{"Search Line limits were exceeded, some dns names have been omitted, the applied search line is: testNS.svc.TEST svc.TEST TEST AAA"}, }, { []string{"testNS.svc.TEST", "svc.TEST", "TEST"}, []string{"AAA", "TEST", "BBB", "TEST", "CCC", "DDD"}, []string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB", "CCC"}, []string{ "Found and omitted duplicated dns domain in host search line: 'TEST' during merging with cluster dns domains", "Found and omitted duplicated dns domain in host search line: 'TEST' during merging with cluster dns domains", "Search Line limits were exceeded, some dns names have been omitted, the applied search line is: testNS.svc.TEST svc.TEST TEST AAA BBB CCC", }, }, } fetchEvent := func(recorder *record.FakeRecorder) string { select { case event := <-recorder.Events: return event default: return "No more events!" } } for i, tc := range testCases { dnsSearch := kubelet.formDNSSearch(tc.hostNames, pod) if !reflect.DeepEqual(dnsSearch, tc.resultSearch) { t.Errorf("[%d] expected search line %#v, got %#v", i, tc.resultSearch, dnsSearch) } for _, expectedEvent := range tc.events { expected := fmt.Sprintf("%s %s %s", v1.EventTypeWarning, "DNSSearchForming", expectedEvent) event := fetchEvent(recorder) if event != expected { t.Errorf("[%d] expected event '%s', got '%s", i, expected, event) } } } } </ins> func TestCleanupBandwidthLimits(t *testing.T) { testPod := func(name, ingress string) *v1.Pod { pod := podWithUidNameNs("", name, "")

} else if options[0].DNS[0] != clusterNS { t.Errorf("expected nameserver %s, got %v", clusterNS, options[0].DNS[0]) } <del> if len(options[0].DNSSearch) != len(options[1].DNSSearch)+3 { </del> <ins> expLength := len(options[1].DNSSearch) + 3 if expLength > 6 { expLength = 6 } if len(options[0].DNSSearch) != expLength { </ins> t.Errorf("expected prepend of cluster domain, got %+v", options[0].DNSSearch) } else if options[0].DNSSearch[0] != ".svc."+kubelet.clusterDomain { t.Errorf("expected domain %s, got %s", ".svc."+kubelet.clusterDomain, options[0].DNSSearch)