Merge pull request #29666 from vefimova/fix_29270

Automatic merge from submit-queue

Fixed merging of host's and dns' search lines

Fixed forming of pod's Search line in resolv.conf:
 - exclude duplicates while merging of host's and dns' search lines to form pod's one
 - truncate pod's search line if it exceeds resolver limits: is > 255 chars and containes > 6 searches
 - monitoring the resolv.conf file which is used by kubelet (set thru --resolv-conf="") and logging and eventing if search line in it consists of more than 3 entries (or 6 if Cluster Domain is set) or its lenght is > 255 chars
 - logging and eventing when a pod's search line is > 255 chars or containes > 6 searches during forming

Fixes #29270

**Release note**:

```release-note
Fixed forming resolver search line for pods: exclude duplicates, obey libc limitations, logging and eventing appropriately.
```
pull/6/head
Kubernetes Submit Queue 2017-01-19 01:37:50 -08:00 committed by GitHub
commit 0144fae64f
4 changed files with 208 additions and 10 deletions

View File

@ -1243,6 +1243,11 @@ func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) {
// handled by pod workers).
go wait.Until(kl.podKiller, 1*time.Second, wait.NeverStop)
// Start gorouting responsible for checking limits in resolv.conf
if kl.resolverConfig != "" {
go wait.Until(func() { kl.checkLimitsForResolvConf() }, 30*time.Second, wait.NeverStop)
}
// Start component sync loops.
kl.statusManager.Start()
kl.probeManager.Start()
@ -1299,6 +1304,8 @@ func (kl *Kubelet) GetClusterDNS(pod *v1.Pod) ([]string, []string, error) {
if kl.resolverConfig == "" {
hostDNS = []string{"127.0.0.1"}
hostSearch = []string{"."}
} else {
hostSearch = kl.formDNSSearchForDNSDefault(hostSearch, pod)
}
return hostDNS, hostSearch, nil
}
@ -1307,15 +1314,7 @@ func (kl *Kubelet) GetClusterDNS(pod *v1.Pod) ([]string, []string, error) {
// 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()}
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
}
dnsSearch := kl.formDNSSearch(hostSearch, pod)
return dns, dnsSearch, nil
}

View File

@ -20,6 +20,7 @@ import (
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"github.com/golang/glog"
@ -89,6 +90,120 @@ func (kl *Kubelet) providerRequiresNetworkingConfiguration() bool {
return supported
}
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
}
// 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

View File

@ -17,12 +17,14 @@ limitations under the License.
package kubelet
import (
"fmt"
"net"
"reflect"
"strings"
"testing"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/record"
"k8s.io/kubernetes/pkg/util/bandwidth"
)
@ -112,6 +114,84 @@ func TestParseResolvConf(t *testing.T) {
}
}
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)
}
}
}
}
func TestCleanupBandwidthLimits(t *testing.T) {
testPod := func(name, ingress string) *v1.Pod {
pod := podWithUidNameNs("", name, "")

View File

@ -211,7 +211,11 @@ func TestGenerateRunContainerOptions_DNSConfigurationParams(t *testing.T) {
} else if options[0].DNS[0] != clusterNS {
t.Errorf("expected nameserver %s, got %v", clusterNS, options[0].DNS[0])
}
if len(options[0].DNSSearch) != len(options[1].DNSSearch)+3 {
expLength := len(options[1].DNSSearch) + 3
if expLength > 6 {
expLength = 6
}
if len(options[0].DNSSearch) != expLength {
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)