Merge pull request #55651 from MrHohn/kubelet-dns-pkg

Automatic merge from submit-queue (batch tested with PRs 55657, 54758, 47584, 55758, 55651). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

Move DNS related kubelet codes into its own package

**What this PR does / why we need it**:
Ref https://github.com/kubernetes/features/issues/504, this PR rearranges DNS related kubelet codes into its own pacakge and adds an OWNERS file.

Again, there is no functional changes, just that codes are moved around and couple fields (`clusterDomain`, `clusterDNS`, `resolverConfig`) are replaced with a `dnsConfigurer` struct.

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes #NONE 

**Special notes for your reviewer**:
/assign @bowei @thockin 

**Release note**:

```release-note
NONE
```
pull/6/head
Kubernetes Submit Queue 2017-11-15 23:57:35 -08:00 committed by GitHub
commit 6e950cc629
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 644 additions and 479 deletions

View File

@ -66,6 +66,7 @@ go_library(
"//pkg/kubelet/metrics:go_default_library", "//pkg/kubelet/metrics:go_default_library",
"//pkg/kubelet/mountpod:go_default_library", "//pkg/kubelet/mountpod:go_default_library",
"//pkg/kubelet/network:go_default_library", "//pkg/kubelet/network:go_default_library",
"//pkg/kubelet/network/dns:go_default_library",
"//pkg/kubelet/pleg:go_default_library", "//pkg/kubelet/pleg:go_default_library",
"//pkg/kubelet/pod:go_default_library", "//pkg/kubelet/pod:go_default_library",
"//pkg/kubelet/preemption:go_default_library", "//pkg/kubelet/preemption:go_default_library",

View File

@ -77,6 +77,7 @@ import (
"k8s.io/kubernetes/pkg/kubelet/lifecycle" "k8s.io/kubernetes/pkg/kubelet/lifecycle"
"k8s.io/kubernetes/pkg/kubelet/metrics" "k8s.io/kubernetes/pkg/kubelet/metrics"
"k8s.io/kubernetes/pkg/kubelet/network" "k8s.io/kubernetes/pkg/kubelet/network"
"k8s.io/kubernetes/pkg/kubelet/network/dns"
"k8s.io/kubernetes/pkg/kubelet/pleg" "k8s.io/kubernetes/pkg/kubelet/pleg"
kubepod "k8s.io/kubernetes/pkg/kubelet/pod" kubepod "k8s.io/kubernetes/pkg/kubelet/pod"
"k8s.io/kubernetes/pkg/kubelet/preemption" "k8s.io/kubernetes/pkg/kubelet/preemption"
@ -477,6 +478,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
} }
} }
httpClient := &http.Client{} httpClient := &http.Client{}
parsedNodeIP := net.ParseIP(nodeIP)
klet := &Kubelet{ klet := &Kubelet{
hostname: hostname, hostname: hostname,
@ -488,8 +490,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
sourcesReady: config.NewSourcesReady(kubeDeps.PodConfig.SeenAllSources), sourcesReady: config.NewSourcesReady(kubeDeps.PodConfig.SeenAllSources),
registerNode: kubeCfg.RegisterNode, registerNode: kubeCfg.RegisterNode,
registerSchedulable: registerSchedulable, registerSchedulable: registerSchedulable,
clusterDomain: kubeCfg.ClusterDomain, dnsConfigurer: dns.NewConfigurer(kubeDeps.Recorder, nodeRef, parsedNodeIP, clusterDNS, kubeCfg.ClusterDomain, kubeCfg.ResolverConfig),
clusterDNS: clusterDNS,
serviceLister: serviceLister, serviceLister: serviceLister,
nodeInfo: nodeInfo, nodeInfo: nodeInfo,
masterServiceNamespace: masterServiceNamespace, masterServiceNamespace: masterServiceNamespace,
@ -512,11 +513,10 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
maxPods: int(kubeCfg.MaxPods), maxPods: int(kubeCfg.MaxPods),
podsPerCore: int(kubeCfg.PodsPerCore), podsPerCore: int(kubeCfg.PodsPerCore),
syncLoopMonitor: atomic.Value{}, syncLoopMonitor: atomic.Value{},
resolverConfig: kubeCfg.ResolverConfig,
daemonEndpoints: daemonEndpoints, daemonEndpoints: daemonEndpoints,
containerManager: kubeDeps.ContainerManager, containerManager: kubeDeps.ContainerManager,
containerRuntimeName: containerRuntime, containerRuntimeName: containerRuntime,
nodeIP: net.ParseIP(nodeIP), nodeIP: parsedNodeIP,
clock: clock.RealClock{}, clock: clock.RealClock{},
enableControllerAttachDetach: kubeCfg.EnableControllerAttachDetach, enableControllerAttachDetach: kubeCfg.EnableControllerAttachDetach,
iptClient: utilipt.New(utilexec.New(), utildbus.New(), utilipt.ProtocolIpv4), iptClient: utilipt.New(utilexec.New(), utildbus.New(), utilipt.ProtocolIpv4),
@ -806,7 +806,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
experimentalCheckNodeCapabilitiesBeforeMount = false experimentalCheckNodeCapabilitiesBeforeMount = false
// Replace the nameserver in containerized-mounter's rootfs/etc/resolve.conf with kubelet.ClusterDNS // Replace the nameserver in containerized-mounter's rootfs/etc/resolve.conf with kubelet.ClusterDNS
// so that service name could be resolved // so that service name could be resolved
klet.setupDNSinContainerizedMounter(experimentalMounterPath) klet.dnsConfigurer.SetupDNSinContainerizedMounter(experimentalMounterPath)
} }
// setup volumeManager // setup volumeManager
@ -948,11 +948,8 @@ type Kubelet struct {
// for internal book keeping; access only from within registerWithApiserver // for internal book keeping; access only from within registerWithApiserver
registrationCompleted bool registrationCompleted bool
// If non-empty, use this for container DNS search. // dnsConfigurer is used for setting up DNS resolver configuration when launching pods.
clusterDomain string dnsConfigurer *dns.Configurer
// If non-nil, use this for container DNS server.
clusterDNS []net.IP
// masterServiceNamespace is the namespace that the master service is exposed in. // masterServiceNamespace is the namespace that the master service is exposed in.
masterServiceNamespace string masterServiceNamespace string
@ -1097,11 +1094,6 @@ type Kubelet struct {
// Channel for sending pods to kill. // Channel for sending pods to kill.
podKillingCh chan *kubecontainer.PodPair podKillingCh chan *kubecontainer.PodPair
// The 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
// Information about the ports which are opened by daemons on Node running this Kubelet server. // Information about the ports which are opened by daemons on Node running this Kubelet server.
daemonEndpoints *v1.NodeDaemonEndpoints daemonEndpoints *v1.NodeDaemonEndpoints
@ -1375,8 +1367,8 @@ func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) {
go wait.Until(kl.podKiller, 1*time.Second, wait.NeverStop) go wait.Until(kl.podKiller, 1*time.Second, wait.NeverStop)
// Start gorouting responsible for checking limits in resolv.conf // Start gorouting responsible for checking limits in resolv.conf
if kl.resolverConfig != "" { if kl.dnsConfigurer.ResolverConfig != "" {
go wait.Until(func() { kl.checkLimitsForResolvConf() }, 30*time.Second, wait.NeverStop) go wait.Until(func() { kl.dnsConfigurer.CheckLimitsForResolvConf() }, 30*time.Second, wait.NeverStop)
} }
// Start component sync loops. // Start component sync loops.

View File

@ -18,11 +18,6 @@ package kubelet
import ( import (
"fmt" "fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
@ -31,7 +26,6 @@ import (
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/kubernetes/pkg/kubelet/network" "k8s.io/kubernetes/pkg/kubelet/network"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types" kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
"k8s.io/kubernetes/pkg/kubelet/util/format"
utiliptables "k8s.io/kubernetes/pkg/util/iptables" utiliptables "k8s.io/kubernetes/pkg/util/iptables"
) )
@ -164,256 +158,6 @@ func (kl *Kubelet) providerRequiresNetworkingConfiguration() bool {
return supported return supported
} }
func omitDuplicates(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
}
}
return combinedSearch[:len(uniqueDomains)]
}
func (kl *Kubelet) formDNSSearchFitsLimits(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 kl.formDNSSearchFitsLimits(pod, hostSearch)
}
func (kl *Kubelet) formDNSSearch(hostSearch []string, pod *v1.Pod) []string {
if kl.clusterDomain == "" {
kl.formDNSSearchFitsLimits(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(pod, combinedSearch)
return kl.formDNSSearchFitsLimits(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, searches and options, possibly returning an error.
// TODO: move to utility package
func (kl *Kubelet) 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:]
}
}
// There used to be code here to scrub DNS for each cloud, but doesn't
// make sense anymore since cloudproviders are being factored out.
// contact @thockin or @wlan0 for more information
return nameservers, searches, options, nil
}
// GetClusterDNS returns a list of the DNS servers, a list of the DNS search
// domains of the cluster, and a list of resolv.conf options.
func (kl *Kubelet) GetClusterDNS(pod *v1.Pod) ([]string, []string, []string, bool, error) {
var hostDNS, hostSearch, hostOptions []string
// Get host DNS settings
if kl.resolverConfig != "" {
f, err := os.Open(kl.resolverConfig)
if err != nil {
return nil, nil, nil, false, err
}
defer f.Close()
hostDNS, hostSearch, hostOptions, err = kl.parseResolvConf(f)
if err != nil {
return nil, nil, nil, false, err
}
}
useClusterFirstPolicy := ((pod.Spec.DNSPolicy == v1.DNSClusterFirst && !kubecontainer.IsHostNetworkPod(pod)) || pod.Spec.DNSPolicy == v1.DNSClusterFirstWithHostNet)
if useClusterFirstPolicy && len(kl.clusterDNS) == 0 {
// clusterDNS is not known.
// pod with ClusterDNSFirst Policy cannot be created
kl.recorder.Eventf(pod, v1.EventTypeWarning, "MissingClusterDNS", "kubelet does not have ClusterDNS IP configured and cannot create Pod using %q policy. Falling back to DNSDefault policy.", pod.Spec.DNSPolicy)
log := fmt.Sprintf("kubelet does not have ClusterDNS IP configured and cannot create Pod using %q policy. pod: %q. Falling back to DNSDefault policy.", pod.Spec.DNSPolicy, format.Pod(pod))
kl.recorder.Eventf(kl.nodeRef, v1.EventTypeWarning, "MissingClusterDNS", log)
// fallback to DNSDefault
useClusterFirstPolicy = false
}
if !useClusterFirstPolicy {
// 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 kl.resolverConfig == "" {
hostSearch = []string{"."}
switch {
case kl.nodeIP == nil || kl.nodeIP.To4() != nil:
hostDNS = []string{"127.0.0.1"}
case kl.nodeIP.To16() != nil:
hostDNS = []string{"::1"}
}
} else {
hostSearch = kl.formDNSSearchForDNSDefault(hostSearch, pod)
}
return hostDNS, hostSearch, hostOptions, useClusterFirstPolicy, nil
}
// 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
dns := make([]string, len(kl.clusterDNS))
for i, ip := range kl.clusterDNS {
dns[i] = ip.String()
}
dnsSearch := kl.formDNSSearch(hostSearch, pod)
return dns, dnsSearch, hostOptions, useClusterFirstPolicy, nil
}
// Replace the nameserver in containerized-mounter's rootfs/etc/resolve.conf with kubelet.ClusterDNS
func (kl *Kubelet) setupDNSinContainerizedMounter(mounterPath string) {
resolvePath := filepath.Join(strings.TrimSuffix(mounterPath, "/mounter"), "rootfs", "etc", "resolv.conf")
dnsString := ""
for _, dns := range kl.clusterDNS {
dnsString = dnsString + fmt.Sprintf("nameserver %s\n", dns)
}
if kl.resolverConfig != "" {
f, err := os.Open(kl.resolverConfig)
defer f.Close()
if err != nil {
glog.Error("Could not open resolverConf file")
} else {
_, hostSearch, _, err := kl.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)
}
}
// syncNetworkStatus updates the network state // syncNetworkStatus updates the network state
func (kl *Kubelet) syncNetworkStatus() { func (kl *Kubelet) syncNetworkStatus() {
// For cri integration, network state will be updated in updateRuntimeUp, // For cri integration, network state will be updated in updateRuntimeUp,
@ -536,3 +280,11 @@ func getIPTablesMark(bit int) string {
value := 1 << uint(bit) value := 1 << uint(bit)
return fmt.Sprintf("%#08x/%#08x", value, value) return fmt.Sprintf("%#08x/%#08x", value, value)
} }
// GetClusterDNS returns a list of the DNS servers, a list of the DNS search
// domains of the cluster, and a list of resolv.conf options.
// This function is defined in kubecontainer.RuntimeHelper interface so we
// have to implement it.
func (kl *Kubelet) GetClusterDNS(pod *v1.Pod) ([]string, []string, []string, bool, error) {
return kl.dnsConfigurer.GetClusterDNS(pod)
}

View File

@ -19,13 +19,9 @@ package kubelet
import ( import (
"fmt" "fmt"
"net" "net"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/api/core/v1"
"k8s.io/client-go/tools/record"
) )
func TestNetworkHostGetsPodNotFound(t *testing.T) { func TestNetworkHostGetsPodNotFound(t *testing.T) {
@ -189,207 +185,6 @@ func TestNodeIPParam(t *testing.T) {
} }
} }
func TestParseResolvConf(t *testing.T) {
testCases := []struct {
data string
nameservers []string
searches []string
options []string
}{
{"", []string{}, []string{}, []string{}},
{" ", []string{}, []string{}, []string{}},
{"\n", []string{}, []string{}, []string{}},
{"\t\n\t", []string{}, []string{}, []string{}},
{"#comment\n", []string{}, []string{}, []string{}},
{" #comment\n", []string{}, []string{}, []string{}},
{"#comment\n#comment", []string{}, []string{}, []string{}},
{"#comment\nnameserver", []string{}, []string{}, []string{}},
{"#comment\nnameserver\nsearch", []string{}, []string{}, []string{}},
{"nameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}},
{" nameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}},
{"\tnameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}},
{"nameserver\t1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}},
{"nameserver \t 1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}},
{"nameserver 1.2.3.4\nnameserver 5.6.7.8", []string{"1.2.3.4", "5.6.7.8"}, []string{}, []string{}},
{"nameserver 1.2.3.4 #comment", []string{"1.2.3.4"}, []string{}, []string{}},
{"search foo", []string{}, []string{"foo"}, []string{}},
{"search foo bar", []string{}, []string{"foo", "bar"}, []string{}},
{"search foo bar bat\n", []string{}, []string{"foo", "bar", "bat"}, []string{}},
{"search foo\nsearch bar", []string{}, []string{"bar"}, []string{}},
{"nameserver 1.2.3.4\nsearch foo bar", []string{"1.2.3.4"}, []string{"foo", "bar"}, []string{}},
{"nameserver 1.2.3.4\nsearch foo\nnameserver 5.6.7.8\nsearch bar", []string{"1.2.3.4", "5.6.7.8"}, []string{"bar"}, []string{}},
{"#comment\nnameserver 1.2.3.4\n#comment\nsearch foo\ncomment", []string{"1.2.3.4"}, []string{"foo"}, []string{}},
{"options ndots:5 attempts:2", []string{}, []string{}, []string{"ndots:5", "attempts:2"}},
{"options ndots:1\noptions ndots:5 attempts:3", []string{}, []string{}, []string{"ndots:5", "attempts:3"}},
{"nameserver 1.2.3.4\nsearch foo\nnameserver 5.6.7.8\nsearch bar\noptions ndots:5 attempts:4", []string{"1.2.3.4", "5.6.7.8"}, []string{"bar"}, []string{"ndots:5", "attempts:4"}},
}
testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
defer testKubelet.Cleanup()
kubelet := testKubelet.kubelet
for i, tc := range testCases {
ns, srch, opts, err := kubelet.parseResolvConf(strings.NewReader(tc.data))
require.NoError(t, err)
assert.EqualValues(t, tc.nameservers, ns, "test case [%d]: name servers", i)
assert.EqualValues(t, tc.searches, srch, "test case [%d] searches", i)
assert.EqualValues(t, tc.options, opts, "test case [%d] options", i)
}
}
func TestComposeDNSSearch(t *testing.T) {
testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
defer testKubelet.Cleanup()
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{},
},
{
[]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{
"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)
assert.EqualValues(t, tc.resultSearch, dnsSearch, "test [%d]", i)
for _, expectedEvent := range tc.events {
expected := fmt.Sprintf("%s %s %s", v1.EventTypeWarning, "DNSSearchForming", expectedEvent)
event := fetchEvent(recorder)
assert.Equal(t, expected, event, "test [%d]", i)
}
}
}
func TestGetClusterDNS(t *testing.T) {
testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
defer testKubelet.Cleanup()
kubelet := testKubelet.kubelet
clusterNS := "203.0.113.1"
kubelet.clusterDomain = "kubernetes.io"
kubelet.clusterDNS = []net.IP{net.ParseIP(clusterNS)}
pods := newTestPods(4)
pods[0].Spec.DNSPolicy = v1.DNSClusterFirstWithHostNet
pods[1].Spec.DNSPolicy = v1.DNSClusterFirst
pods[2].Spec.DNSPolicy = v1.DNSClusterFirst
pods[2].Spec.HostNetwork = false
pods[3].Spec.DNSPolicy = v1.DNSDefault
options := make([]struct {
DNS []string
DNSSearch []string
}, 4)
for i, pod := range pods {
var err error
options[i].DNS, options[i].DNSSearch, _, _, err = kubelet.GetClusterDNS(pod)
if err != nil {
t.Fatalf("failed to generate container options: %v", err)
}
}
if len(options[0].DNS) != 1 || options[0].DNS[0] != clusterNS {
t.Errorf("expected nameserver %s, got %+v", clusterNS, options[0].DNS)
}
if len(options[0].DNSSearch) == 0 || options[0].DNSSearch[0] != ".svc."+kubelet.clusterDomain {
t.Errorf("expected search %s, got %+v", ".svc."+kubelet.clusterDomain, options[0].DNSSearch)
}
if len(options[1].DNS) != 1 || options[1].DNS[0] != "127.0.0.1" {
t.Errorf("expected nameserver 127.0.0.1, got %+v", options[1].DNS)
}
if len(options[1].DNSSearch) != 1 || options[1].DNSSearch[0] != "." {
t.Errorf("expected search \".\", got %+v", options[1].DNSSearch)
}
if len(options[2].DNS) != 1 || options[2].DNS[0] != clusterNS {
t.Errorf("expected nameserver %s, got %+v", clusterNS, options[2].DNS)
}
if len(options[2].DNSSearch) == 0 || options[2].DNSSearch[0] != ".svc."+kubelet.clusterDomain {
t.Errorf("expected search %s, got %+v", ".svc."+kubelet.clusterDomain, options[2].DNSSearch)
}
if len(options[3].DNS) != 1 || options[3].DNS[0] != "127.0.0.1" {
t.Errorf("expected nameserver 127.0.0.1, got %+v", options[3].DNS)
}
if len(options[3].DNSSearch) != 1 || options[3].DNSSearch[0] != "." {
t.Errorf("expected search \".\", got %+v", options[3].DNSSearch)
}
kubelet.resolverConfig = "/etc/resolv.conf"
for i, pod := range pods {
var err error
options[i].DNS, options[i].DNSSearch, _, _, err = kubelet.GetClusterDNS(pod)
if err != nil {
t.Fatalf("failed to generate container options: %v", err)
}
}
t.Logf("nameservers %+v", options[1].DNS)
if len(options[0].DNS) != 1 {
t.Errorf("expected cluster nameserver only, got %+v", options[0].DNS)
} else if options[0].DNS[0] != clusterNS {
t.Errorf("expected nameserver %s, got %v", clusterNS, options[0].DNS[0])
}
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)
}
if len(options[2].DNS) != 1 {
t.Errorf("expected cluster nameserver only, got %+v", options[2].DNS)
} else if options[2].DNS[0] != clusterNS {
t.Errorf("expected nameserver %s, got %v", clusterNS, options[2].DNS[0])
}
if len(options[2].DNSSearch) != expLength {
t.Errorf("expected prepend of cluster domain, got %+v", options[2].DNSSearch)
} else if options[2].DNSSearch[0] != ".svc."+kubelet.clusterDomain {
t.Errorf("expected domain %s, got %s", ".svc."+kubelet.clusterDomain, options[0].DNSSearch)
}
}
func TestGetIPTablesMark(t *testing.T) { func TestGetIPTablesMark(t *testing.T) {
tests := []struct { tests := []struct {
bit int bit int

View File

@ -346,7 +346,7 @@ func truncatePodHostnameIfNeeded(podName, hostname string) (string, error) {
// given that pod's spec and annotations or returns an error. // given that pod's spec and annotations or returns an error.
func (kl *Kubelet) GeneratePodHostNameAndDomain(pod *v1.Pod) (string, string, error) { func (kl *Kubelet) GeneratePodHostNameAndDomain(pod *v1.Pod) (string, string, error) {
// TODO(vmarmol): Handle better. // TODO(vmarmol): Handle better.
clusterDomain := kl.clusterDomain clusterDomain := kl.dnsConfigurer.ClusterDomain
hostname := pod.Name hostname := pod.Name
if len(pod.Spec.Hostname) > 0 { if len(pod.Spec.Hostname) > 0 {

View File

@ -41,6 +41,7 @@ filegroup(
srcs = [ srcs = [
":package-srcs", ":package-srcs",
"//pkg/kubelet/network/cni:all-srcs", "//pkg/kubelet/network/cni:all-srcs",
"//pkg/kubelet/network/dns:all-srcs",
"//pkg/kubelet/network/hairpin:all-srcs", "//pkg/kubelet/network/hairpin:all-srcs",
"//pkg/kubelet/network/hostport:all-srcs", "//pkg/kubelet/network/hostport:all-srcs",
"//pkg/kubelet/network/kubenet:all-srcs", "//pkg/kubelet/network/kubenet:all-srcs",

View File

@ -0,0 +1,44 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["dns.go"],
importpath = "k8s.io/kubernetes/pkg/kubelet/network/dns",
visibility = ["//visibility:public"],
deps = [
"//pkg/kubelet/container:go_default_library",
"//pkg/kubelet/util/format:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/client-go/tools/record:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["dns_test.go"],
importpath = "k8s.io/kubernetes/pkg/kubelet/network/dns",
library = ":go_default_library",
deps = [
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/github.com/stretchr/testify/require:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/client-go/tools/record:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,4 @@
approvers:
- sig-network-approvers
reviewers:
- sig-network-reviewers

View File

@ -0,0 +1,314 @@
/*
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"
"k8s.io/client-go/tools/record"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/kubernetes/pkg/kubelet/util/format"
"github.com/golang/glog"
)
// 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(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
}
}
return combinedSearch[:len(uniqueDomains)]
}
func (c *Configurer) formDNSSearchFitsLimits(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, " "))
c.recorder.Event(pod, v1.EventTypeWarning, "DNSSearchForming", log)
glog.Error(log)
}
return composedSearch
}
func (c *Configurer) formDNSSearchForDNSDefault(hostSearch []string, pod *v1.Pod) []string {
return c.formDNSSearchFitsLimits(pod, hostSearch)
}
func (c *Configurer) formDNSSearch(hostSearch []string, pod *v1.Pod) []string {
if c.ClusterDomain == "" {
c.formDNSSearchFitsLimits(pod, hostSearch)
return hostSearch
}
nsSvcDomain := fmt.Sprintf("%s.svc.%s", pod.Namespace, c.ClusterDomain)
svcDomain := fmt.Sprintf("svc.%s", c.ClusterDomain)
dnsSearch := []string{nsSvcDomain, svcDomain, c.ClusterDomain}
combinedSearch := append(dnsSearch, hostSearch...)
combinedSearch = omitDuplicates(pod, combinedSearch)
return c.formDNSSearchFitsLimits(pod, combinedSearch)
}
// CheckLimitsForResolvConf checks limits in resolv.conf.
func (c *Configurer) CheckLimitsForResolvConf() {
// resolver file Search line current limitations
resolvSearchLineDNSDomainsLimit := 6
resolvSearchLineLenLimit := 255
f, err := os.Open(c.ResolverConfig)
if err != nil {
c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", err.Error())
glog.Error("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.Error("CheckLimitsForResolvConf: " + err.Error())
return
}
domainCntLimit := resolvSearchLineDNSDomainsLimit
if c.ClusterDomain != "" {
domainCntLimit -= 3
}
if len(hostSearch) > domainCntLimit {
log := fmt.Sprintf("Resolv.conf file '%s' contains search line consisting of more than %d domains!", c.ResolverConfig, domainCntLimit)
c.recorder.Event(c.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!", c.ResolverConfig, resolvSearchLineLenLimit)
c.recorder.Event(c.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, searches and options, possibly returning an error.
// TODO: move to utility package
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:]
}
}
// There used to be code here to scrub DNS for each cloud, but doesn't
// make sense anymore since cloudproviders are being factored out.
// contact @thockin or @wlan0 for more information
return nameservers, searches, options, nil
}
// GetClusterDNS returns a list of the DNS servers, a list of the DNS search
// domains of the cluster, and a list of resolv.conf options.
// TODO: This should return a struct.
func (c *Configurer) GetClusterDNS(pod *v1.Pod) ([]string, []string, []string, bool, error) {
var hostDNS, hostSearch, hostOptions []string
// Get host DNS settings
if c.ResolverConfig != "" {
f, err := os.Open(c.ResolverConfig)
if err != nil {
return nil, nil, nil, false, err
}
defer f.Close()
hostDNS, hostSearch, hostOptions, err = parseResolvConf(f)
if err != nil {
return nil, nil, nil, false, err
}
}
useClusterFirstPolicy := ((pod.Spec.DNSPolicy == v1.DNSClusterFirst && !kubecontainer.IsHostNetworkPod(pod)) || pod.Spec.DNSPolicy == v1.DNSClusterFirstWithHostNet)
if useClusterFirstPolicy && len(c.clusterDNS) == 0 {
// clusterDNS is not known.
// pod with ClusterDNSFirst Policy cannot be created
c.recorder.Eventf(pod, v1.EventTypeWarning, "MissingClusterDNS", "kubelet does not have ClusterDNS IP configured and cannot create Pod using %q policy. Falling back to DNSDefault policy.", pod.Spec.DNSPolicy)
log := fmt.Sprintf("kubelet does not have ClusterDNS IP configured and cannot create Pod using %q policy. pod: %q. Falling back to DNSDefault policy.", pod.Spec.DNSPolicy, format.Pod(pod))
c.recorder.Eventf(c.nodeRef, v1.EventTypeWarning, "MissingClusterDNS", log)
// fallback to DNSDefault
useClusterFirstPolicy = false
}
if !useClusterFirstPolicy {
// 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 == "" {
hostSearch = []string{"."}
switch {
case c.nodeIP == nil || c.nodeIP.To4() != nil:
hostDNS = []string{"127.0.0.1"}
case c.nodeIP.To16() != nil:
hostDNS = []string{"::1"}
}
} else {
hostSearch = c.formDNSSearchForDNSDefault(hostSearch, pod)
}
return hostDNS, hostSearch, hostOptions, useClusterFirstPolicy, nil
}
// 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
dns := make([]string, len(c.clusterDNS))
for i, ip := range c.clusterDNS {
dns[i] = ip.String()
}
dnsSearch := c.formDNSSearch(hostSearch, pod)
return dns, dnsSearch, hostOptions, useClusterFirstPolicy, 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)
}
}

View File

@ -0,0 +1,262 @@
/*
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"
"net"
"strings"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParseResolvConf(t *testing.T) {
testCases := []struct {
data string
nameservers []string
searches []string
options []string
}{
{"", []string{}, []string{}, []string{}},
{" ", []string{}, []string{}, []string{}},
{"\n", []string{}, []string{}, []string{}},
{"\t\n\t", []string{}, []string{}, []string{}},
{"#comment\n", []string{}, []string{}, []string{}},
{" #comment\n", []string{}, []string{}, []string{}},
{"#comment\n#comment", []string{}, []string{}, []string{}},
{"#comment\nnameserver", []string{}, []string{}, []string{}},
{"#comment\nnameserver\nsearch", []string{}, []string{}, []string{}},
{"nameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}},
{" nameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}},
{"\tnameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}},
{"nameserver\t1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}},
{"nameserver \t 1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}},
{"nameserver 1.2.3.4\nnameserver 5.6.7.8", []string{"1.2.3.4", "5.6.7.8"}, []string{}, []string{}},
{"nameserver 1.2.3.4 #comment", []string{"1.2.3.4"}, []string{}, []string{}},
{"search foo", []string{}, []string{"foo"}, []string{}},
{"search foo bar", []string{}, []string{"foo", "bar"}, []string{}},
{"search foo bar bat\n", []string{}, []string{"foo", "bar", "bat"}, []string{}},
{"search foo\nsearch bar", []string{}, []string{"bar"}, []string{}},
{"nameserver 1.2.3.4\nsearch foo bar", []string{"1.2.3.4"}, []string{"foo", "bar"}, []string{}},
{"nameserver 1.2.3.4\nsearch foo\nnameserver 5.6.7.8\nsearch bar", []string{"1.2.3.4", "5.6.7.8"}, []string{"bar"}, []string{}},
{"#comment\nnameserver 1.2.3.4\n#comment\nsearch foo\ncomment", []string{"1.2.3.4"}, []string{"foo"}, []string{}},
{"options ndots:5 attempts:2", []string{}, []string{}, []string{"ndots:5", "attempts:2"}},
{"options ndots:1\noptions ndots:5 attempts:3", []string{}, []string{}, []string{"ndots:5", "attempts:3"}},
{"nameserver 1.2.3.4\nsearch foo\nnameserver 5.6.7.8\nsearch bar\noptions ndots:5 attempts:4", []string{"1.2.3.4", "5.6.7.8"}, []string{"bar"}, []string{"ndots:5", "attempts:4"}},
}
for i, tc := range testCases {
ns, srch, opts, err := parseResolvConf(strings.NewReader(tc.data))
require.NoError(t, err)
assert.EqualValues(t, tc.nameservers, ns, "test case [%d]: name servers", i)
assert.EqualValues(t, tc.searches, srch, "test case [%d] searches", i)
assert.EqualValues(t, tc.options, opts, "test case [%d] options", i)
}
}
func TestComposeDNSSearch(t *testing.T) {
recorder := record.NewFakeRecorder(20)
nodeRef := &v1.ObjectReference{
Kind: "Node",
Name: string("testNode"),
UID: types.UID("testNode"),
Namespace: "",
}
testClusterDNSDomain := "TEST"
configurer := NewConfigurer(recorder, nodeRef, nil, nil, testClusterDNSDomain, "")
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "",
Name: "test_pod",
Namespace: "testNS",
Annotations: map[string]string{},
},
}
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{},
},
{
[]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{
"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 := configurer.formDNSSearch(tc.hostNames, pod)
assert.EqualValues(t, tc.resultSearch, dnsSearch, "test [%d]", i)
for _, expectedEvent := range tc.events {
expected := fmt.Sprintf("%s %s %s", v1.EventTypeWarning, "DNSSearchForming", expectedEvent)
event := fetchEvent(recorder)
assert.Equal(t, expected, event, "test [%d]", i)
}
}
}
func TestGetClusterDNS(t *testing.T) {
recorder := record.NewFakeRecorder(20)
nodeRef := &v1.ObjectReference{
Kind: "Node",
Name: string("testNode"),
UID: types.UID("testNode"),
Namespace: "",
}
clusterNS := "203.0.113.1"
testClusterDNSDomain := "kubernetes.io"
testClusterDNS := []net.IP{net.ParseIP(clusterNS)}
configurer := NewConfigurer(recorder, nodeRef, nil, testClusterDNS, testClusterDNSDomain, "")
pods := newTestPods(4)
pods[0].Spec.DNSPolicy = v1.DNSClusterFirstWithHostNet
pods[1].Spec.DNSPolicy = v1.DNSClusterFirst
pods[2].Spec.DNSPolicy = v1.DNSClusterFirst
pods[2].Spec.HostNetwork = false
pods[3].Spec.DNSPolicy = v1.DNSDefault
options := make([]struct {
DNS []string
DNSSearch []string
}, 4)
for i, pod := range pods {
var err error
options[i].DNS, options[i].DNSSearch, _, _, err = configurer.GetClusterDNS(pod)
if err != nil {
t.Fatalf("failed to generate container options: %v", err)
}
}
if len(options[0].DNS) != 1 || options[0].DNS[0] != clusterNS {
t.Errorf("expected nameserver %s, got %+v", clusterNS, options[0].DNS)
}
if len(options[0].DNSSearch) == 0 || options[0].DNSSearch[0] != ".svc."+configurer.ClusterDomain {
t.Errorf("expected search %s, got %+v", ".svc."+configurer.ClusterDomain, options[0].DNSSearch)
}
if len(options[1].DNS) != 1 || options[1].DNS[0] != "127.0.0.1" {
t.Errorf("expected nameserver 127.0.0.1, got %+v", options[1].DNS)
}
if len(options[1].DNSSearch) != 1 || options[1].DNSSearch[0] != "." {
t.Errorf("expected search \".\", got %+v", options[1].DNSSearch)
}
if len(options[2].DNS) != 1 || options[2].DNS[0] != clusterNS {
t.Errorf("expected nameserver %s, got %+v", clusterNS, options[2].DNS)
}
if len(options[2].DNSSearch) == 0 || options[2].DNSSearch[0] != ".svc."+configurer.ClusterDomain {
t.Errorf("expected search %s, got %+v", ".svc."+configurer.ClusterDomain, options[2].DNSSearch)
}
if len(options[3].DNS) != 1 || options[3].DNS[0] != "127.0.0.1" {
t.Errorf("expected nameserver 127.0.0.1, got %+v", options[3].DNS)
}
if len(options[3].DNSSearch) != 1 || options[3].DNSSearch[0] != "." {
t.Errorf("expected search \".\", got %+v", options[3].DNSSearch)
}
testResolverConfig := "/etc/resolv.conf"
configurer = NewConfigurer(recorder, nodeRef, nil, testClusterDNS, testClusterDNSDomain, testResolverConfig)
for i, pod := range pods {
var err error
options[i].DNS, options[i].DNSSearch, _, _, err = configurer.GetClusterDNS(pod)
if err != nil {
t.Fatalf("failed to generate container options: %v", err)
}
}
t.Logf("nameservers %+v", options[1].DNS)
if len(options[0].DNS) != 1 {
t.Errorf("expected cluster nameserver only, got %+v", options[0].DNS)
} else if options[0].DNS[0] != clusterNS {
t.Errorf("expected nameserver %s, got %v", clusterNS, options[0].DNS[0])
}
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."+configurer.ClusterDomain {
t.Errorf("expected domain %s, got %s", ".svc."+configurer.ClusterDomain, options[0].DNSSearch)
}
if len(options[2].DNS) != 1 {
t.Errorf("expected cluster nameserver only, got %+v", options[2].DNS)
} else if options[2].DNS[0] != clusterNS {
t.Errorf("expected nameserver %s, got %v", clusterNS, options[2].DNS[0])
}
if len(options[2].DNSSearch) != expLength {
t.Errorf("expected prepend of cluster domain, got %+v", options[2].DNSSearch)
} else if options[2].DNSSearch[0] != ".svc."+configurer.ClusterDomain {
t.Errorf("expected domain %s, got %s", ".svc."+configurer.ClusterDomain, options[0].DNSSearch)
}
}
func newTestPods(count int) []*v1.Pod {
pods := make([]*v1.Pod, count)
for i := 0; i < count; i++ {
pods[i] = &v1.Pod{
Spec: v1.PodSpec{
HostNetwork: true,
},
ObjectMeta: metav1.ObjectMeta{
UID: types.UID(10000 + i),
Name: fmt.Sprintf("pod%d", i),
},
}
}
return pods
}