mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Move DNS related kubelet codes into its own package
This commit is contained in:
parent
54e60aca17
commit
0bc2e1f62f
@ -66,6 +66,7 @@ go_library(
|
||||
"//pkg/kubelet/metrics:go_default_library",
|
||||
"//pkg/kubelet/mountpod:go_default_library",
|
||||
"//pkg/kubelet/network:go_default_library",
|
||||
"//pkg/kubelet/network/dns:go_default_library",
|
||||
"//pkg/kubelet/pleg:go_default_library",
|
||||
"//pkg/kubelet/pod:go_default_library",
|
||||
"//pkg/kubelet/preemption:go_default_library",
|
||||
|
@ -77,6 +77,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
|
||||
"k8s.io/kubernetes/pkg/kubelet/metrics"
|
||||
"k8s.io/kubernetes/pkg/kubelet/network"
|
||||
"k8s.io/kubernetes/pkg/kubelet/network/dns"
|
||||
"k8s.io/kubernetes/pkg/kubelet/pleg"
|
||||
kubepod "k8s.io/kubernetes/pkg/kubelet/pod"
|
||||
"k8s.io/kubernetes/pkg/kubelet/preemption"
|
||||
@ -477,6 +478,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
|
||||
}
|
||||
}
|
||||
httpClient := &http.Client{}
|
||||
parsedNodeIP := net.ParseIP(nodeIP)
|
||||
|
||||
klet := &Kubelet{
|
||||
hostname: hostname,
|
||||
@ -488,8 +490,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
|
||||
sourcesReady: config.NewSourcesReady(kubeDeps.PodConfig.SeenAllSources),
|
||||
registerNode: kubeCfg.RegisterNode,
|
||||
registerSchedulable: registerSchedulable,
|
||||
clusterDomain: kubeCfg.ClusterDomain,
|
||||
clusterDNS: clusterDNS,
|
||||
dnsConfigurer: dns.NewConfigurer(kubeDeps.Recorder, nodeRef, parsedNodeIP, clusterDNS, kubeCfg.ClusterDomain, kubeCfg.ResolverConfig),
|
||||
serviceLister: serviceLister,
|
||||
nodeInfo: nodeInfo,
|
||||
masterServiceNamespace: masterServiceNamespace,
|
||||
@ -512,11 +513,10 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
|
||||
maxPods: int(kubeCfg.MaxPods),
|
||||
podsPerCore: int(kubeCfg.PodsPerCore),
|
||||
syncLoopMonitor: atomic.Value{},
|
||||
resolverConfig: kubeCfg.ResolverConfig,
|
||||
daemonEndpoints: daemonEndpoints,
|
||||
containerManager: kubeDeps.ContainerManager,
|
||||
containerRuntimeName: containerRuntime,
|
||||
nodeIP: net.ParseIP(nodeIP),
|
||||
nodeIP: parsedNodeIP,
|
||||
clock: clock.RealClock{},
|
||||
enableControllerAttachDetach: kubeCfg.EnableControllerAttachDetach,
|
||||
iptClient: utilipt.New(utilexec.New(), utildbus.New(), utilipt.ProtocolIpv4),
|
||||
@ -806,7 +806,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
|
||||
experimentalCheckNodeCapabilitiesBeforeMount = false
|
||||
// Replace the nameserver in containerized-mounter's rootfs/etc/resolve.conf with kubelet.ClusterDNS
|
||||
// so that service name could be resolved
|
||||
klet.setupDNSinContainerizedMounter(experimentalMounterPath)
|
||||
klet.dnsConfigurer.SetupDNSinContainerizedMounter(experimentalMounterPath)
|
||||
}
|
||||
|
||||
// setup volumeManager
|
||||
@ -948,11 +948,8 @@ type Kubelet struct {
|
||||
// for internal book keeping; access only from within registerWithApiserver
|
||||
registrationCompleted bool
|
||||
|
||||
// If non-empty, use this for container DNS search.
|
||||
clusterDomain string
|
||||
|
||||
// If non-nil, use this for container DNS server.
|
||||
clusterDNS []net.IP
|
||||
// dnsConfigurer is used for setting up DNS resolver configuration when launching pods.
|
||||
dnsConfigurer *dns.Configurer
|
||||
|
||||
// masterServiceNamespace is the namespace that the master service is exposed in.
|
||||
masterServiceNamespace string
|
||||
@ -1097,11 +1094,6 @@ type Kubelet struct {
|
||||
// Channel for sending pods to kill.
|
||||
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.
|
||||
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)
|
||||
|
||||
// Start gorouting responsible for checking limits in resolv.conf
|
||||
if kl.resolverConfig != "" {
|
||||
go wait.Until(func() { kl.checkLimitsForResolvConf() }, 30*time.Second, wait.NeverStop)
|
||||
if kl.dnsConfigurer.ResolverConfig != "" {
|
||||
go wait.Until(func() { kl.dnsConfigurer.CheckLimitsForResolvConf() }, 30*time.Second, wait.NeverStop)
|
||||
}
|
||||
|
||||
// Start component sync loops.
|
||||
|
@ -18,11 +18,6 @@ package kubelet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
@ -31,7 +26,6 @@ import (
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
"k8s.io/kubernetes/pkg/kubelet/network"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
"k8s.io/kubernetes/pkg/kubelet/util/format"
|
||||
utiliptables "k8s.io/kubernetes/pkg/util/iptables"
|
||||
)
|
||||
|
||||
@ -164,256 +158,6 @@ func (kl *Kubelet) providerRequiresNetworkingConfiguration() bool {
|
||||
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
|
||||
func (kl *Kubelet) syncNetworkStatus() {
|
||||
// For cri integration, network state will be updated in updateRuntimeUp,
|
||||
@ -536,3 +280,11 @@ func getIPTablesMark(bit int) string {
|
||||
value := 1 << uint(bit)
|
||||
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)
|
||||
}
|
||||
|
@ -19,13 +19,9 @@ package kubelet
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"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) {
|
||||
@ -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) {
|
||||
tests := []struct {
|
||||
bit int
|
||||
|
@ -346,7 +346,7 @@ func truncatePodHostnameIfNeeded(podName, hostname string) (string, error) {
|
||||
// given that pod's spec and annotations or returns an error.
|
||||
func (kl *Kubelet) GeneratePodHostNameAndDomain(pod *v1.Pod) (string, string, error) {
|
||||
// TODO(vmarmol): Handle better.
|
||||
clusterDomain := kl.clusterDomain
|
||||
clusterDomain := kl.dnsConfigurer.ClusterDomain
|
||||
|
||||
hostname := pod.Name
|
||||
if len(pod.Spec.Hostname) > 0 {
|
||||
|
@ -41,6 +41,7 @@ filegroup(
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/kubelet/network/cni:all-srcs",
|
||||
"//pkg/kubelet/network/dns:all-srcs",
|
||||
"//pkg/kubelet/network/hairpin:all-srcs",
|
||||
"//pkg/kubelet/network/hostport:all-srcs",
|
||||
"//pkg/kubelet/network/kubenet:all-srcs",
|
||||
|
44
pkg/kubelet/network/dns/BUILD
Normal file
44
pkg/kubelet/network/dns/BUILD
Normal 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"],
|
||||
)
|
4
pkg/kubelet/network/dns/OWNERS
Normal file
4
pkg/kubelet/network/dns/OWNERS
Normal file
@ -0,0 +1,4 @@
|
||||
approvers:
|
||||
- sig-network-approvers
|
||||
reviewers:
|
||||
- sig-network-reviewers
|
314
pkg/kubelet/network/dns/dns.go
Normal file
314
pkg/kubelet/network/dns/dns.go
Normal 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)
|
||||
}
|
||||
}
|
262
pkg/kubelet/network/dns/dns_test.go
Normal file
262
pkg/kubelet/network/dns/dns_test.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user