mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-16 15:20:17 +00:00
Support Custom Pod DNS in kubelet, gated by feature gate
This commit is contained in:
parent
44b5cf3e12
commit
9f9c721b20
@ -6,11 +6,14 @@ go_library(
|
|||||||
importpath = "k8s.io/kubernetes/pkg/kubelet/network/dns",
|
importpath = "k8s.io/kubernetes/pkg/kubelet/network/dns",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//pkg/apis/core/validation:go_default_library",
|
||||||
|
"//pkg/features:go_default_library",
|
||||||
"//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library",
|
"//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library",
|
||||||
"//pkg/kubelet/container:go_default_library",
|
"//pkg/kubelet/container:go_default_library",
|
||||||
"//pkg/kubelet/util/format:go_default_library",
|
"//pkg/kubelet/util/format:go_default_library",
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||||
"//vendor/k8s.io/client-go/tools/record:go_default_library",
|
"//vendor/k8s.io/client-go/tools/record:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -21,11 +24,14 @@ go_test(
|
|||||||
importpath = "k8s.io/kubernetes/pkg/kubelet/network/dns",
|
importpath = "k8s.io/kubernetes/pkg/kubelet/network/dns",
|
||||||
library = ":go_default_library",
|
library = ":go_default_library",
|
||||||
deps = [
|
deps = [
|
||||||
|
"//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library",
|
||||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||||
"//vendor/github.com/stretchr/testify/require:go_default_library",
|
"//vendor/github.com/stretchr/testify/require:go_default_library",
|
||||||
"//vendor/k8s.io/api/core/v1: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/apis/meta/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||||
"//vendor/k8s.io/client-go/tools/record:go_default_library",
|
"//vendor/k8s.io/client-go/tools/record:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -26,7 +26,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/client-go/tools/record"
|
"k8s.io/client-go/tools/record"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/util/format"
|
"k8s.io/kubernetes/pkg/kubelet/util/format"
|
||||||
@ -39,6 +42,14 @@ var (
|
|||||||
defaultDNSOptions = []string{"ndots:5"}
|
defaultDNSOptions = []string{"ndots:5"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type podDNSType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
podDNSCluster podDNSType = iota
|
||||||
|
podDNSHost
|
||||||
|
podDNSNone
|
||||||
|
)
|
||||||
|
|
||||||
// Configurer is used for setting up DNS resolver configuration when launching pods.
|
// Configurer is used for setting up DNS resolver configuration when launching pods.
|
||||||
type Configurer struct {
|
type Configurer struct {
|
||||||
recorder record.EventRecorder
|
recorder record.EventRecorder
|
||||||
@ -67,37 +78,35 @@ func NewConfigurer(recorder record.EventRecorder, nodeRef *v1.ObjectReference, n
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func omitDuplicates(pod *v1.Pod, combinedSearch []string) []string {
|
func omitDuplicates(strs []string) []string {
|
||||||
uniqueDomains := map[string]bool{}
|
uniqueStrs := make(map[string]bool)
|
||||||
|
|
||||||
for _, dnsDomain := range combinedSearch {
|
var ret []string
|
||||||
if _, exists := uniqueDomains[dnsDomain]; !exists {
|
for _, str := range strs {
|
||||||
combinedSearch[len(uniqueDomains)] = dnsDomain
|
if !uniqueStrs[str] {
|
||||||
uniqueDomains[dnsDomain] = true
|
ret = append(ret, str)
|
||||||
|
uniqueStrs[str] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return combinedSearch[:len(uniqueDomains)]
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Configurer) formDNSSearchFitsLimits(pod *v1.Pod, composedSearch []string) []string {
|
func (c *Configurer) formDNSSearchFitsLimits(composedSearch []string, pod *v1.Pod) []string {
|
||||||
// resolver file Search line current limitations
|
|
||||||
resolvSearchLineDNSDomainsLimit := 6
|
|
||||||
resolvSearchLineLenLimit := 255
|
|
||||||
limitsExceeded := false
|
limitsExceeded := false
|
||||||
|
|
||||||
if len(composedSearch) > resolvSearchLineDNSDomainsLimit {
|
if len(composedSearch) > validation.MaxDNSSearchPaths {
|
||||||
composedSearch = composedSearch[:resolvSearchLineDNSDomainsLimit]
|
composedSearch = composedSearch[:validation.MaxDNSSearchPaths]
|
||||||
limitsExceeded = true
|
limitsExceeded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if resolvSearchhLineStrLen := len(strings.Join(composedSearch, " ")); resolvSearchhLineStrLen > resolvSearchLineLenLimit {
|
if resolvSearchLineStrLen := len(strings.Join(composedSearch, " ")); resolvSearchLineStrLen > validation.MaxDNSSearchListChars {
|
||||||
cutDomainsNum := 0
|
cutDomainsNum := 0
|
||||||
cutDoaminsLen := 0
|
cutDomainsLen := 0
|
||||||
for i := len(composedSearch) - 1; i >= 0; i-- {
|
for i := len(composedSearch) - 1; i >= 0; i-- {
|
||||||
cutDoaminsLen += len(composedSearch[i]) + 1
|
cutDomainsLen += len(composedSearch[i]) + 1
|
||||||
cutDomainsNum++
|
cutDomainsNum++
|
||||||
|
|
||||||
if (resolvSearchhLineStrLen - cutDoaminsLen) <= resolvSearchLineLenLimit {
|
if (resolvSearchLineStrLen - cutDomainsLen) <= validation.MaxDNSSearchListChars {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,39 +116,43 @@ func (c *Configurer) formDNSSearchFitsLimits(pod *v1.Pod, composedSearch []strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
if limitsExceeded {
|
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, " "))
|
log := fmt.Sprintf("Search Line limits were exceeded, some search paths have been omitted, the applied search line is: %s", strings.Join(composedSearch, " "))
|
||||||
c.recorder.Event(pod, v1.EventTypeWarning, "DNSSearchForming", log)
|
c.recorder.Event(pod, v1.EventTypeWarning, "DNSConfigForming", log)
|
||||||
glog.Error(log)
|
glog.Error(log)
|
||||||
}
|
}
|
||||||
return composedSearch
|
return composedSearch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Configurer) formDNSSearchForDNSDefault(hostSearch []string, pod *v1.Pod) []string {
|
func (c *Configurer) formDNSNameserversFitsLimits(nameservers []string, pod *v1.Pod) []string {
|
||||||
return c.formDNSSearchFitsLimits(pod, hostSearch)
|
if len(nameservers) > validation.MaxDNSNameservers {
|
||||||
|
nameservers = nameservers[0:validation.MaxDNSNameservers]
|
||||||
|
log := fmt.Sprintf("Nameserver limits were exceeded, some nameservers have been omitted, the applied nameserver line is: %s", strings.Join(nameservers, " "))
|
||||||
|
c.recorder.Event(pod, v1.EventTypeWarning, "DNSConfigForming", log)
|
||||||
|
glog.Error(log)
|
||||||
|
}
|
||||||
|
return nameservers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Configurer) formDNSSearch(hostSearch []string, pod *v1.Pod) []string {
|
func (c *Configurer) formDNSConfigFitsLimits(dnsConfig *runtimeapi.DNSConfig, pod *v1.Pod) *runtimeapi.DNSConfig {
|
||||||
|
dnsConfig.Servers = c.formDNSNameserversFitsLimits(dnsConfig.Servers, pod)
|
||||||
|
dnsConfig.Searches = c.formDNSSearchFitsLimits(dnsConfig.Searches, pod)
|
||||||
|
return dnsConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Configurer) generateSearchesForDNSClusterFirst(hostSearch []string, pod *v1.Pod) []string {
|
||||||
if c.ClusterDomain == "" {
|
if c.ClusterDomain == "" {
|
||||||
c.formDNSSearchFitsLimits(pod, hostSearch)
|
|
||||||
return hostSearch
|
return hostSearch
|
||||||
}
|
}
|
||||||
|
|
||||||
nsSvcDomain := fmt.Sprintf("%s.svc.%s", pod.Namespace, c.ClusterDomain)
|
nsSvcDomain := fmt.Sprintf("%s.svc.%s", pod.Namespace, c.ClusterDomain)
|
||||||
svcDomain := fmt.Sprintf("svc.%s", c.ClusterDomain)
|
svcDomain := fmt.Sprintf("svc.%s", c.ClusterDomain)
|
||||||
dnsSearch := []string{nsSvcDomain, svcDomain, c.ClusterDomain}
|
clusterSearch := []string{nsSvcDomain, svcDomain, c.ClusterDomain}
|
||||||
|
|
||||||
combinedSearch := append(dnsSearch, hostSearch...)
|
return omitDuplicates(append(clusterSearch, hostSearch...))
|
||||||
|
|
||||||
combinedSearch = omitDuplicates(pod, combinedSearch)
|
|
||||||
return c.formDNSSearchFitsLimits(pod, combinedSearch)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckLimitsForResolvConf checks limits in resolv.conf.
|
// CheckLimitsForResolvConf checks limits in resolv.conf.
|
||||||
func (c *Configurer) CheckLimitsForResolvConf() {
|
func (c *Configurer) CheckLimitsForResolvConf() {
|
||||||
// resolver file Search line current limitations
|
|
||||||
resolvSearchLineDNSDomainsLimit := 6
|
|
||||||
resolvSearchLineLenLimit := 255
|
|
||||||
|
|
||||||
f, err := os.Open(c.ResolverConfig)
|
f, err := os.Open(c.ResolverConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", err.Error())
|
c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", err.Error())
|
||||||
@ -155,21 +168,21 @@ func (c *Configurer) CheckLimitsForResolvConf() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
domainCntLimit := resolvSearchLineDNSDomainsLimit
|
domainCountLimit := validation.MaxDNSSearchPaths
|
||||||
|
|
||||||
if c.ClusterDomain != "" {
|
if c.ClusterDomain != "" {
|
||||||
domainCntLimit -= 3
|
domainCountLimit -= 3
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(hostSearch) > domainCntLimit {
|
if len(hostSearch) > domainCountLimit {
|
||||||
log := fmt.Sprintf("Resolv.conf file '%s' contains search line consisting of more than %d domains!", c.ResolverConfig, domainCntLimit)
|
log := fmt.Sprintf("Resolv.conf file '%s' contains search line consisting of more than %d domains!", c.ResolverConfig, domainCountLimit)
|
||||||
c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", log)
|
c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", log)
|
||||||
glog.Error("CheckLimitsForResolvConf: " + log)
|
glog.Error("CheckLimitsForResolvConf: " + log)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(strings.Join(hostSearch, " ")) > resolvSearchLineLenLimit {
|
if len(strings.Join(hostSearch, " ")) > validation.MaxDNSSearchListChars {
|
||||||
log := fmt.Sprintf("Resolv.conf file '%s' contains search line which length is more than allowed %d chars!", c.ResolverConfig, resolvSearchLineLenLimit)
|
log := fmt.Sprintf("Resolv.conf file '%s' contains search line which length is more than allowed %d chars!", c.ResolverConfig, validation.MaxDNSSearchListChars)
|
||||||
c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", log)
|
c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", log)
|
||||||
glog.Error("CheckLimitsForResolvConf: " + log)
|
glog.Error("CheckLimitsForResolvConf: " + log)
|
||||||
return
|
return
|
||||||
@ -180,7 +193,6 @@ func (c *Configurer) CheckLimitsForResolvConf() {
|
|||||||
|
|
||||||
// parseResolveConf reads a resolv.conf file from the given reader, and parses
|
// parseResolveConf reads a resolv.conf file from the given reader, and parses
|
||||||
// it into nameservers, searches and options, possibly returning an error.
|
// 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) {
|
func parseResolvConf(reader io.Reader) (nameservers []string, searches []string, options []string, err error) {
|
||||||
file, err := ioutil.ReadAll(reader)
|
file, err := ioutil.ReadAll(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -218,15 +230,10 @@ func parseResolvConf(reader io.Reader) (nameservers []string, searches []string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
return nameservers, searches, options, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPodDNS returns DNS setttings for the pod.
|
func (c *Configurer) getHostDNSConfig(pod *v1.Pod) (*runtimeapi.DNSConfig, error) {
|
||||||
func (c *Configurer) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error) {
|
|
||||||
var hostDNS, hostSearch, hostOptions []string
|
var hostDNS, hostSearch, hostOptions []string
|
||||||
// Get host DNS settings
|
// Get host DNS settings
|
||||||
if c.ResolverConfig != "" {
|
if c.ResolverConfig != "" {
|
||||||
@ -241,19 +248,117 @@ func (c *Configurer) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
useClusterFirstPolicy := ((pod.Spec.DNSPolicy == v1.DNSClusterFirst && !kubecontainer.IsHostNetworkPod(pod)) || pod.Spec.DNSPolicy == v1.DNSClusterFirstWithHostNet)
|
return &runtimeapi.DNSConfig{
|
||||||
if useClusterFirstPolicy && len(c.clusterDNS) == 0 {
|
Servers: hostDNS,
|
||||||
// clusterDNS is not known.
|
Searches: hostSearch,
|
||||||
// pod with ClusterDNSFirst Policy cannot be created
|
Options: hostOptions,
|
||||||
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)
|
}, nil
|
||||||
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
|
func getPodDNSType(pod *v1.Pod) (podDNSType, error) {
|
||||||
useClusterFirstPolicy = false
|
dnsPolicy := pod.Spec.DNSPolicy
|
||||||
|
switch dnsPolicy {
|
||||||
|
case v1.DNSNone:
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.CustomPodDNS) {
|
||||||
|
return podDNSNone, nil
|
||||||
|
}
|
||||||
|
// This should not happen as kube-apiserver should have rejected
|
||||||
|
// setting dnsPolicy to DNSNone when feature gate is disabled.
|
||||||
|
return podDNSCluster, fmt.Errorf(fmt.Sprintf("invalid DNSPolicy=%v: custom pod DNS is disabled", dnsPolicy))
|
||||||
|
case v1.DNSClusterFirstWithHostNet:
|
||||||
|
return podDNSCluster, nil
|
||||||
|
case v1.DNSClusterFirst:
|
||||||
|
if !kubecontainer.IsHostNetworkPod(pod) {
|
||||||
|
return podDNSCluster, nil
|
||||||
|
}
|
||||||
|
// Fallback to DNSDefault for pod on hostnetowrk.
|
||||||
|
fallthrough
|
||||||
|
case v1.DNSDefault:
|
||||||
|
return podDNSHost, nil
|
||||||
|
}
|
||||||
|
// This should not happen as kube-apiserver should have rejected
|
||||||
|
// invalid dnsPolicy.
|
||||||
|
return podDNSCluster, fmt.Errorf(fmt.Sprintf("invalid DNSPolicy=%v", dnsPolicy))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge DNS options. If duplicated, entries given by PodDNSConfigOption will
|
||||||
|
// overwrite the existing ones.
|
||||||
|
func mergeDNSOptions(existingDNSConfigOptions []string, dnsConfigOptions []v1.PodDNSConfigOption) []string {
|
||||||
|
optionsMap := make(map[string]string)
|
||||||
|
for _, op := range existingDNSConfigOptions {
|
||||||
|
if index := strings.Index(op, ":"); index != -1 {
|
||||||
|
optionsMap[op[:index]] = op[index+1:]
|
||||||
|
} else {
|
||||||
|
optionsMap[op] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, op := range dnsConfigOptions {
|
||||||
|
if op.Value != nil {
|
||||||
|
optionsMap[op.Name] = *op.Value
|
||||||
|
} else {
|
||||||
|
optionsMap[op.Name] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Reconvert DNS options into a string array.
|
||||||
|
options := []string{}
|
||||||
|
for opName, opValue := range optionsMap {
|
||||||
|
op := opName
|
||||||
|
if opValue != "" {
|
||||||
|
op = op + ":" + opValue
|
||||||
|
}
|
||||||
|
options = append(options, op)
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendDNSConfig appends DNS servers, search paths and options given by
|
||||||
|
// PodDNSConfig to the existing DNS config. Duplicated entries will be merged.
|
||||||
|
// This assumes existingDNSConfig and dnsConfig are not nil.
|
||||||
|
func appendDNSConfig(existingDNSConfig *runtimeapi.DNSConfig, dnsConfig *v1.PodDNSConfig) *runtimeapi.DNSConfig {
|
||||||
|
existingDNSConfig.Servers = omitDuplicates(append(existingDNSConfig.Servers, dnsConfig.Nameservers...))
|
||||||
|
existingDNSConfig.Searches = omitDuplicates(append(existingDNSConfig.Searches, dnsConfig.Searches...))
|
||||||
|
existingDNSConfig.Options = mergeDNSOptions(existingDNSConfig.Options, dnsConfig.Options)
|
||||||
|
return existingDNSConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPodDNS returns DNS setttings for the pod.
|
||||||
|
func (c *Configurer) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error) {
|
||||||
|
dnsConfig, err := c.getHostDNSConfig(pod)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !useClusterFirstPolicy {
|
dnsType, err := getPodDNSType(pod)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to get DNS type for pod %q: %v. Falling back to DNSClusterFirst policy.", format.Pod(pod), err)
|
||||||
|
dnsType = podDNSCluster
|
||||||
|
}
|
||||||
|
switch dnsType {
|
||||||
|
case podDNSNone:
|
||||||
|
// DNSNone should use empty DNS settings as the base.
|
||||||
|
dnsConfig = &runtimeapi.DNSConfig{}
|
||||||
|
case podDNSCluster:
|
||||||
|
if len(c.clusterDNS) != 0 {
|
||||||
|
// For a pod with DNSClusterFirst policy, the cluster DNS server is
|
||||||
|
// the only nameserver configured for the pod. The cluster DNS server
|
||||||
|
// itself will forward queries to other nameservers that is configured
|
||||||
|
// to use, in case the cluster DNS server cannot resolve the DNS query
|
||||||
|
// itself.
|
||||||
|
dnsConfig.Servers = []string{}
|
||||||
|
for _, ip := range c.clusterDNS {
|
||||||
|
dnsConfig.Servers = append(dnsConfig.Servers, ip.String())
|
||||||
|
}
|
||||||
|
dnsConfig.Searches = c.generateSearchesForDNSClusterFirst(dnsConfig.Searches, pod)
|
||||||
|
dnsConfig.Options = defaultDNSOptions
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// clusterDNS is not known. Pod with ClusterDNSFirst Policy cannot be created.
|
||||||
|
nodeErrorMsg := fmt.Sprintf("kubelet does not have ClusterDNS IP configured and cannot create Pod using %q policy. Falling back to %q policy.", v1.DNSClusterFirst, v1.DNSDefault)
|
||||||
|
c.recorder.Eventf(c.nodeRef, v1.EventTypeWarning, "MissingClusterDNS", nodeErrorMsg)
|
||||||
|
c.recorder.Eventf(pod, v1.EventTypeWarning, "MissingClusterDNS", "pod: %q. %s", format.Pod(pod), nodeErrorMsg)
|
||||||
|
// Fallback to DNSDefault.
|
||||||
|
fallthrough
|
||||||
|
case podDNSHost:
|
||||||
// When the kubelet --resolv-conf flag is set to the empty string, use
|
// When the kubelet --resolv-conf flag is set to the empty string, use
|
||||||
// DNS settings that override the docker default (which is to use
|
// DNS settings that override the docker default (which is to use
|
||||||
// /etc/resolv.conf) and effectively disable DNS lookups. According to
|
// /etc/resolv.conf) and effectively disable DNS lookups. According to
|
||||||
@ -262,35 +367,20 @@ func (c *Configurer) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error) {
|
|||||||
// local machine". A nameserver setting of localhost is equivalent to
|
// local machine". A nameserver setting of localhost is equivalent to
|
||||||
// this documented behavior.
|
// this documented behavior.
|
||||||
if c.ResolverConfig == "" {
|
if c.ResolverConfig == "" {
|
||||||
hostSearch = []string{"."}
|
|
||||||
switch {
|
switch {
|
||||||
case c.nodeIP == nil || c.nodeIP.To4() != nil:
|
case c.nodeIP == nil || c.nodeIP.To4() != nil:
|
||||||
hostDNS = []string{"127.0.0.1"}
|
dnsConfig.Servers = []string{"127.0.0.1"}
|
||||||
case c.nodeIP.To16() != nil:
|
case c.nodeIP.To16() != nil:
|
||||||
hostDNS = []string{"::1"}
|
dnsConfig.Servers = []string{"::1"}
|
||||||
}
|
}
|
||||||
} else {
|
dnsConfig.Searches = []string{"."}
|
||||||
hostSearch = c.formDNSSearchForDNSDefault(hostSearch, pod)
|
|
||||||
}
|
}
|
||||||
return &runtimeapi.DNSConfig{
|
|
||||||
Servers: hostDNS,
|
|
||||||
Searches: hostSearch,
|
|
||||||
Options: hostOptions}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// for a pod with DNSClusterFirst policy, the cluster DNS server is the only nameserver configured for
|
if utilfeature.DefaultFeatureGate.Enabled(features.CustomPodDNS) && pod.Spec.DNSConfig != nil {
|
||||||
// the pod. The cluster DNS server itself will forward queries to other nameservers that is configured to use,
|
dnsConfig = appendDNSConfig(dnsConfig, pod.Spec.DNSConfig)
|
||||||
// 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 c.formDNSConfigFitsLimits(dnsConfig, pod), nil
|
||||||
|
|
||||||
return &runtimeapi.DNSConfig{
|
|
||||||
Servers: dns,
|
|
||||||
Searches: dnsSearch,
|
|
||||||
Options: defaultDNSOptions}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupDNSinContainerizedMounter replaces the nameserver in containerized-mounter's rootfs/etc/resolve.conf with kubelet.ClusterDNS
|
// SetupDNSinContainerizedMounter replaces the nameserver in containerized-mounter's rootfs/etc/resolve.conf with kubelet.ClusterDNS
|
||||||
|
@ -25,12 +25,26 @@ import (
|
|||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/client-go/tools/record"
|
"k8s.io/client-go/tools/record"
|
||||||
|
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fetchEvent = func(recorder *record.FakeRecorder) string {
|
||||||
|
select {
|
||||||
|
case event := <-recorder.Events:
|
||||||
|
return event
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func TestParseResolvConf(t *testing.T) {
|
func TestParseResolvConf(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
data string
|
data string
|
||||||
@ -74,7 +88,7 @@ func TestParseResolvConf(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestComposeDNSSearch(t *testing.T) {
|
func TestFormDNSSearchFitsLimits(t *testing.T) {
|
||||||
recorder := record.NewFakeRecorder(20)
|
recorder := record.NewFakeRecorder(20)
|
||||||
nodeRef := &v1.ObjectReference{
|
nodeRef := &v1.ObjectReference{
|
||||||
Kind: "Node",
|
Kind: "Node",
|
||||||
@ -96,62 +110,272 @@ func TestComposeDNSSearch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
dnsNames []string
|
|
||||||
hostNames []string
|
hostNames []string
|
||||||
resultSearch []string
|
resultSearch []string
|
||||||
events []string
|
events []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST"},
|
[]string{"testNS.svc.TEST", "svc.TEST", "TEST"},
|
||||||
[]string{},
|
|
||||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST"},
|
[]string{"testNS.svc.TEST", "svc.TEST", "TEST"},
|
||||||
[]string{},
|
[]string{},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST"},
|
[]string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB"},
|
||||||
[]string{"AAA", "svc.TEST", "BBB", "TEST"},
|
|
||||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB"},
|
[]string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB"},
|
||||||
[]string{},
|
[]string{},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST"},
|
[]string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", strings.Repeat("B", 256), "BBB"},
|
||||||
[]string{"AAA", strings.Repeat("B", 256), "BBB"},
|
|
||||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA"},
|
[]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{"Search Line limits were exceeded, some search paths have been omitted, the applied search line is: testNS.svc.TEST svc.TEST TEST AAA"},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST"},
|
[]string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB", "CCC", "DDD"},
|
||||||
[]string{"AAA", "TEST", "BBB", "TEST", "CCC", "DDD"},
|
|
||||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB", "CCC"},
|
[]string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB", "CCC"},
|
||||||
[]string{
|
[]string{"Search Line limits were exceeded, some search paths have been omitted, the applied search line is: testNS.svc.TEST svc.TEST TEST AAA BBB CCC"},
|
||||||
"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 {
|
for i, tc := range testCases {
|
||||||
dnsSearch := configurer.formDNSSearch(tc.hostNames, pod)
|
dnsSearch := configurer.formDNSSearchFitsLimits(tc.hostNames, pod)
|
||||||
assert.EqualValues(t, tc.resultSearch, dnsSearch, "test [%d]", i)
|
assert.EqualValues(t, tc.resultSearch, dnsSearch, "test [%d]", i)
|
||||||
for _, expectedEvent := range tc.events {
|
for _, expectedEvent := range tc.events {
|
||||||
expected := fmt.Sprintf("%s %s %s", v1.EventTypeWarning, "DNSSearchForming", expectedEvent)
|
expected := fmt.Sprintf("%s %s %s", v1.EventTypeWarning, "DNSConfigForming", expectedEvent)
|
||||||
event := fetchEvent(recorder)
|
event := fetchEvent(recorder)
|
||||||
assert.Equal(t, expected, event, "test [%d]", i)
|
assert.Equal(t, expected, event, "test [%d]", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFormDNSNameserversFitsLimits(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 {
|
||||||
|
desc string
|
||||||
|
nameservers []string
|
||||||
|
expectedNameserver []string
|
||||||
|
expectedEvent bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "valid: 1 nameserver",
|
||||||
|
nameservers: []string{"127.0.0.1"},
|
||||||
|
expectedNameserver: []string{"127.0.0.1"},
|
||||||
|
expectedEvent: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "valid: 3 nameservers",
|
||||||
|
nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"},
|
||||||
|
expectedNameserver: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"},
|
||||||
|
expectedEvent: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid: 4 nameservers, trimmed to 3",
|
||||||
|
nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8", "1.2.3.4"},
|
||||||
|
expectedNameserver: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"},
|
||||||
|
expectedEvent: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
appliedNameservers := configurer.formDNSNameserversFitsLimits(tc.nameservers, pod)
|
||||||
|
assert.EqualValues(t, tc.expectedNameserver, appliedNameservers, tc.desc)
|
||||||
|
event := fetchEvent(recorder)
|
||||||
|
if tc.expectedEvent && len(event) == 0 {
|
||||||
|
t.Errorf("%s: formDNSNameserversFitsLimits(%v) expected event, got no event.", tc.desc, tc.nameservers)
|
||||||
|
} else if !tc.expectedEvent && len(event) > 0 {
|
||||||
|
t.Errorf("%s: formDNSNameserversFitsLimits(%v) expected no event, got event: %v", tc.desc, tc.nameservers, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeDNSOptions(t *testing.T) {
|
||||||
|
testOptionValue := "3"
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
existingDNSConfigOptions []string
|
||||||
|
dnsConfigOptions []v1.PodDNSConfigOption
|
||||||
|
expectedOptions []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Empty dnsConfigOptions",
|
||||||
|
existingDNSConfigOptions: []string{"ndots:5", "debug"},
|
||||||
|
dnsConfigOptions: nil,
|
||||||
|
expectedOptions: []string{"ndots:5", "debug"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "No duplicated entries",
|
||||||
|
existingDNSConfigOptions: []string{"ndots:5", "debug"},
|
||||||
|
dnsConfigOptions: []v1.PodDNSConfigOption{
|
||||||
|
{Name: "single-request"},
|
||||||
|
{Name: "attempts", Value: &testOptionValue},
|
||||||
|
},
|
||||||
|
expectedOptions: []string{"ndots:5", "debug", "single-request", "attempts:3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Overwrite duplicated entries",
|
||||||
|
existingDNSConfigOptions: []string{"ndots:5", "debug"},
|
||||||
|
dnsConfigOptions: []v1.PodDNSConfigOption{
|
||||||
|
{Name: "ndots", Value: &testOptionValue},
|
||||||
|
{Name: "debug"},
|
||||||
|
{Name: "single-request"},
|
||||||
|
{Name: "attempts", Value: &testOptionValue},
|
||||||
|
},
|
||||||
|
expectedOptions: []string{"ndots:3", "debug", "single-request", "attempts:3"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
options := mergeDNSOptions(tc.existingDNSConfigOptions, tc.dnsConfigOptions)
|
||||||
|
// Options order may be changed after conversion.
|
||||||
|
if !sets.NewString(options...).Equal(sets.NewString(tc.expectedOptions...)) {
|
||||||
|
t.Errorf("%s: mergeDNSOptions(%v, %v)=%v, want %v", tc.desc, tc.existingDNSConfigOptions, tc.dnsConfigOptions, options, tc.expectedOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPodDNSType(t *testing.T) {
|
||||||
|
customDNSEnabled := utilfeature.DefaultFeatureGate.Enabled("CustomPodDNS")
|
||||||
|
defer func() {
|
||||||
|
// Restoring the old value.
|
||||||
|
if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("CustomPodDNS=%v", customDNSEnabled)); err != nil {
|
||||||
|
t.Errorf("Failed to set CustomPodDNS feature gate: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
recorder := record.NewFakeRecorder(20)
|
||||||
|
nodeRef := &v1.ObjectReference{
|
||||||
|
Kind: "Node",
|
||||||
|
Name: string("testNode"),
|
||||||
|
UID: types.UID("testNode"),
|
||||||
|
Namespace: "",
|
||||||
|
}
|
||||||
|
testClusterDNSDomain := "TEST"
|
||||||
|
clusterNS := "203.0.113.1"
|
||||||
|
testClusterDNS := []net.IP{net.ParseIP(clusterNS)}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
desc string
|
||||||
|
customPodDNSFeatureGate bool
|
||||||
|
hasClusterDNS bool
|
||||||
|
hostNetwork bool
|
||||||
|
dnsPolicy v1.DNSPolicy
|
||||||
|
expectedDNSType podDNSType
|
||||||
|
expectedError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "valid DNSClusterFirst without hostnetwork",
|
||||||
|
hasClusterDNS: true,
|
||||||
|
dnsPolicy: v1.DNSClusterFirst,
|
||||||
|
expectedDNSType: podDNSCluster,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "valid DNSClusterFirstWithHostNet with hostnetwork",
|
||||||
|
hasClusterDNS: true,
|
||||||
|
hostNetwork: true,
|
||||||
|
dnsPolicy: v1.DNSClusterFirstWithHostNet,
|
||||||
|
expectedDNSType: podDNSCluster,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "valid DNSClusterFirstWithHostNet without hostnetwork",
|
||||||
|
hasClusterDNS: true,
|
||||||
|
dnsPolicy: v1.DNSClusterFirstWithHostNet,
|
||||||
|
expectedDNSType: podDNSCluster,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "valid DNSDefault without hostnetwork",
|
||||||
|
dnsPolicy: v1.DNSDefault,
|
||||||
|
expectedDNSType: podDNSHost,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "valid DNSDefault with hostnetwork",
|
||||||
|
hostNetwork: true,
|
||||||
|
dnsPolicy: v1.DNSDefault,
|
||||||
|
expectedDNSType: podDNSHost,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "DNSClusterFirst with hostnetwork, fallback to DNSDefault",
|
||||||
|
hasClusterDNS: true,
|
||||||
|
hostNetwork: true,
|
||||||
|
dnsPolicy: v1.DNSClusterFirst,
|
||||||
|
expectedDNSType: podDNSHost,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "valid DNSNone with feature gate",
|
||||||
|
customPodDNSFeatureGate: true,
|
||||||
|
dnsPolicy: v1.DNSNone,
|
||||||
|
expectedDNSType: podDNSNone,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "DNSNone without feature gate, should return error",
|
||||||
|
dnsPolicy: v1.DNSNone,
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid DNS policy, should return error",
|
||||||
|
dnsPolicy: "invalidPolicy",
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("CustomPodDNS=%v", tc.customPodDNSFeatureGate)); err != nil {
|
||||||
|
t.Errorf("Failed to set CustomPodDNS feature gate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.hasClusterDNS {
|
||||||
|
configurer.clusterDNS = testClusterDNS
|
||||||
|
} else {
|
||||||
|
configurer.clusterDNS = nil
|
||||||
|
}
|
||||||
|
pod.Spec.DNSPolicy = tc.dnsPolicy
|
||||||
|
pod.Spec.HostNetwork = tc.hostNetwork
|
||||||
|
|
||||||
|
resType, err := getPodDNSType(pod)
|
||||||
|
if tc.expectedError {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("%s: GetPodDNSType(%v) got no error, want error", tc.desc, pod)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if resType != tc.expectedDNSType {
|
||||||
|
t.Errorf("%s: GetPodDNSType(%v)=%v, want %v", tc.desc, pod, resType, tc.expectedDNSType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetPodDNS(t *testing.T) {
|
func TestGetPodDNS(t *testing.T) {
|
||||||
recorder := record.NewFakeRecorder(20)
|
recorder := record.NewFakeRecorder(20)
|
||||||
nodeRef := &v1.ObjectReference{
|
nodeRef := &v1.ObjectReference{
|
||||||
@ -247,6 +471,119 @@ func TestGetPodDNS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetPodDNSCustom(t *testing.T) {
|
||||||
|
customDNSEnabled := utilfeature.DefaultFeatureGate.Enabled("CustomPodDNS")
|
||||||
|
defer func() {
|
||||||
|
// Restoring the old value.
|
||||||
|
if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("CustomPodDNS=%v", customDNSEnabled)); err != nil {
|
||||||
|
t.Errorf("Failed to set CustomPodDNS feature gate: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
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)}
|
||||||
|
testOptionValue := "3"
|
||||||
|
|
||||||
|
configurer := NewConfigurer(recorder, nodeRef, nil, testClusterDNS, testClusterDNSDomain, "")
|
||||||
|
|
||||||
|
pod := &v1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
UID: "",
|
||||||
|
Name: "test_pod",
|
||||||
|
Namespace: "testNS",
|
||||||
|
Annotations: map[string]string{},
|
||||||
|
},
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
DNSPolicy: v1.DNSClusterFirst,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
clusterFirstDNSConfig, err := configurer.GetPodDNS(pod)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Preparing clusterFirstDNSConfig: GetPodDNS(%v), unexpected error: %v", pod, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite DNSPolicy for testing.
|
||||||
|
pod.Spec.DNSPolicy = v1.DNSNone
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
customPodDNSFeatureGate bool
|
||||||
|
dnsConfig *v1.PodDNSConfig
|
||||||
|
expectedDNSConfig *runtimeapi.DNSConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "feature gate is disabled, DNSNone should fallback to DNSClusterFirst",
|
||||||
|
expectedDNSConfig: clusterFirstDNSConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "feature gate is enabled, DNSNone without DNSConfig should have empty DNS settings",
|
||||||
|
customPodDNSFeatureGate: true,
|
||||||
|
expectedDNSConfig: &runtimeapi.DNSConfig{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "feature gate is enabled, DNSNone with DNSConfig should have a merged DNS settings",
|
||||||
|
customPodDNSFeatureGate: true,
|
||||||
|
dnsConfig: &v1.PodDNSConfig{
|
||||||
|
Nameservers: []string{"10.0.0.10"},
|
||||||
|
Searches: []string{"my.domain", "second.domain"},
|
||||||
|
Options: []v1.PodDNSConfigOption{
|
||||||
|
{Name: "ndots", Value: &testOptionValue},
|
||||||
|
{Name: "debug"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedDNSConfig: &runtimeapi.DNSConfig{
|
||||||
|
Servers: []string{"10.0.0.10"},
|
||||||
|
Searches: []string{"my.domain", "second.domain"},
|
||||||
|
Options: []string{"ndots:3", "debug"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("CustomPodDNS=%v", tc.customPodDNSFeatureGate)); err != nil {
|
||||||
|
t.Errorf("Failed to set CustomPodDNS feature gate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pod.Spec.DNSConfig = tc.dnsConfig
|
||||||
|
|
||||||
|
resDNSConfig, err := configurer.GetPodDNS(pod)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: GetPodDNS(%v), unexpected error: %v", tc.desc, pod, err)
|
||||||
|
}
|
||||||
|
if !dnsConfigsAreEqual(resDNSConfig, tc.expectedDNSConfig) {
|
||||||
|
t.Errorf("%s: GetPodDNS(%v)=%v, want %v", tc.desc, pod, resDNSConfig, tc.expectedDNSConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dnsConfigsAreEqual(resConfig, expectedConfig *runtimeapi.DNSConfig) bool {
|
||||||
|
if len(resConfig.Servers) != len(expectedConfig.Servers) ||
|
||||||
|
len(resConfig.Searches) != len(expectedConfig.Searches) ||
|
||||||
|
len(resConfig.Options) != len(expectedConfig.Options) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, server := range resConfig.Servers {
|
||||||
|
if expectedConfig.Servers[i] != server {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, search := range resConfig.Searches {
|
||||||
|
if expectedConfig.Searches[i] != search {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Options order may be changed after conversion.
|
||||||
|
return sets.NewString(resConfig.Options...).Equal(sets.NewString(expectedConfig.Options...))
|
||||||
|
}
|
||||||
|
|
||||||
func newTestPods(count int) []*v1.Pod {
|
func newTestPods(count int) []*v1.Pod {
|
||||||
pods := make([]*v1.Pod, count)
|
pods := make([]*v1.Pod, count)
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
|
Loading…
Reference in New Issue
Block a user