Merge pull request #33550 from rtreffer/kubelet-allow-multiple-dns-server

Automatic merge from submit-queue

Allow multipe DNS servers as comma-seperated argument for kubelet --dns

This PR explores how kubectls "--dns" could be extended to specify multiple DNS servers for in-cluster PODs. Testing on the local libvirt-coreos cluster shows that multiple DNS server are injected without issues.

Specifying multiple DNS servers increases resilience against
- Packet drops
- Single server failure

I am debugging services that do 50+ DNS requests for a single incoming interactive request, thus highly increase the chance of a slowdown (+5s) due to a single packet drop. Switching to two DNS servers will reduce the impact of the issues (roughly +1s on glibc, 0s on musl, error-rate goes down to error-rate^2).

Note that there is no need to change any runtime related code as far as I know. In the case of "default" dns the /etc/resolv.conf is parsed and multiple DNS server are send to the backend anyway. This only adds the same capability for the clusterFirst case.

I've heard from @thockin that multiple DNS entries are somehow considered. I've no idea what was considered, though. This is what I would like to see for our production use, though.

```release-note
NONE
```
This commit is contained in:
Kubernetes Submit Queue 2017-02-15 12:45:32 -08:00 committed by GitHub
commit 3bc575c91f
10 changed files with 51 additions and 20 deletions

View File

@ -163,7 +163,7 @@ func (s *KubeletServer) AddFlags(fs *pflag.FlagSet) {
fs.BoolVar(&s.RegisterNode, "register-node", s.RegisterNode, "Register the node with the apiserver (defaults to true if --api-servers is set)") fs.BoolVar(&s.RegisterNode, "register-node", s.RegisterNode, "Register the node with the apiserver (defaults to true if --api-servers is set)")
fs.StringVar(&s.ClusterDomain, "cluster-domain", s.ClusterDomain, "Domain for this cluster. If set, kubelet will configure all containers to search this domain in addition to the host's search domains") fs.StringVar(&s.ClusterDomain, "cluster-domain", s.ClusterDomain, "Domain for this cluster. If set, kubelet will configure all containers to search this domain in addition to the host's search domains")
fs.StringVar(&s.MasterServiceNamespace, "master-service-namespace", s.MasterServiceNamespace, "The namespace from which the kubernetes master services should be injected into pods") fs.StringVar(&s.MasterServiceNamespace, "master-service-namespace", s.MasterServiceNamespace, "The namespace from which the kubernetes master services should be injected into pods")
fs.StringVar(&s.ClusterDNS, "cluster-dns", s.ClusterDNS, "IP address for a cluster DNS server. This value is used for containers' DNS server in case of Pods with \"dnsPolicy=ClusterFirst\"") fs.StringSliceVar(&s.ClusterDNS, "cluster-dns", s.ClusterDNS, "Comma-separated list of DNS server IP address. This value is used for containers DNS server in case of Pods with \"dnsPolicy=ClusterFirst\". Note: all DNS servers appearing in the list MUST serve the same set of records otherwise name resolution within the cluster may not work correctly. There is no guarantee as to which DNS server may be contacted for name resolution.")
fs.DurationVar(&s.StreamingConnectionIdleTimeout.Duration, "streaming-connection-idle-timeout", s.StreamingConnectionIdleTimeout.Duration, "Maximum time a streaming connection can be idle before the connection is automatically closed. 0 indicates no timeout. Example: '5m'") fs.DurationVar(&s.StreamingConnectionIdleTimeout.Duration, "streaming-connection-idle-timeout", s.StreamingConnectionIdleTimeout.Duration, "Maximum time a streaming connection can be idle before the connection is automatically closed. 0 indicates no timeout. Example: '5m'")
fs.DurationVar(&s.NodeStatusUpdateFrequency.Duration, "node-status-update-frequency", s.NodeStatusUpdateFrequency.Duration, "Specifies how often kubelet posts node status to master. Note: be cautious when changing the constant, it must work with nodeMonitorGracePeriod in nodecontroller. Default: 10s") fs.DurationVar(&s.NodeStatusUpdateFrequency.Duration, "node-status-update-frequency", s.NodeStatusUpdateFrequency.Duration, "Specifies how often kubelet posts node status to master. Note: be cautious when changing the constant, it must work with nodeMonitorGracePeriod in nodecontroller. Default: 10s")
s.NodeLabels = make(map[string]string) s.NodeLabels = make(map[string]string)

View File

@ -238,10 +238,10 @@ type KubeletConfiguration struct {
// masterServiceNamespace is The namespace from which the kubernetes // masterServiceNamespace is The namespace from which the kubernetes
// master services should be injected into pods. // master services should be injected into pods.
MasterServiceNamespace string MasterServiceNamespace string
// clusterDNS is the IP address for a cluster DNS server. If set, kubelet // clusterDNS is a list of IP address for a cluster DNS server. If set,
// will configure all containers to use this for DNS resolution in // kubelet will configure all containers to use this for DNS resolution
// addition to the host's DNS servers // instead of the host's DNS servers
ClusterDNS string ClusterDNS []string
// streamingConnectionIdleTimeout is the maximum time a streaming connection // streamingConnectionIdleTimeout is the maximum time a streaming connection
// can be idle before the connection is automatically closed. // can be idle before the connection is automatically closed.
StreamingConnectionIdleTimeout metav1.Duration StreamingConnectionIdleTimeout metav1.Duration

View File

@ -292,10 +292,10 @@ type KubeletConfiguration struct {
// masterServiceNamespace is The namespace from which the kubernetes // masterServiceNamespace is The namespace from which the kubernetes
// master services should be injected into pods. // master services should be injected into pods.
MasterServiceNamespace string `json:"masterServiceNamespace"` MasterServiceNamespace string `json:"masterServiceNamespace"`
// clusterDNS is the IP address for a cluster DNS server. If set, kubelet // clusterDNS is a list of IP address for the cluster DNS server. If set,
// will configure all containers to use this for DNS resolution in // kubelet will configure all containers to use this for DNS resolution
// addition to the host's DNS servers // instead of the host's DNS servers
ClusterDNS string `json:"clusterDNS"` ClusterDNS []string `json:"clusterDNS"`
// streamingConnectionIdleTimeout is the maximum time a streaming connection // streamingConnectionIdleTimeout is the maximum time a streaming connection
// can be idle before the connection is automatically closed. // can be idle before the connection is automatically closed.
StreamingConnectionIdleTimeout metav1.Duration `json:"streamingConnectionIdleTimeout"` StreamingConnectionIdleTimeout metav1.Duration `json:"streamingConnectionIdleTimeout"`

View File

@ -309,7 +309,7 @@ func autoConvert_v1alpha1_KubeletConfiguration_To_componentconfig_KubeletConfigu
} }
out.ClusterDomain = in.ClusterDomain out.ClusterDomain = in.ClusterDomain
out.MasterServiceNamespace = in.MasterServiceNamespace out.MasterServiceNamespace = in.MasterServiceNamespace
out.ClusterDNS = in.ClusterDNS out.ClusterDNS = *(*[]string)(unsafe.Pointer(&in.ClusterDNS))
out.StreamingConnectionIdleTimeout = in.StreamingConnectionIdleTimeout out.StreamingConnectionIdleTimeout = in.StreamingConnectionIdleTimeout
out.NodeStatusUpdateFrequency = in.NodeStatusUpdateFrequency out.NodeStatusUpdateFrequency = in.NodeStatusUpdateFrequency
out.ImageMinimumGCAge = in.ImageMinimumGCAge out.ImageMinimumGCAge = in.ImageMinimumGCAge
@ -483,7 +483,7 @@ func autoConvert_componentconfig_KubeletConfiguration_To_v1alpha1_KubeletConfigu
} }
out.ClusterDomain = in.ClusterDomain out.ClusterDomain = in.ClusterDomain
out.MasterServiceNamespace = in.MasterServiceNamespace out.MasterServiceNamespace = in.MasterServiceNamespace
out.ClusterDNS = in.ClusterDNS out.ClusterDNS = *(*[]string)(unsafe.Pointer(&in.ClusterDNS))
out.StreamingConnectionIdleTimeout = in.StreamingConnectionIdleTimeout out.StreamingConnectionIdleTimeout = in.StreamingConnectionIdleTimeout
out.NodeStatusUpdateFrequency = in.NodeStatusUpdateFrequency out.NodeStatusUpdateFrequency = in.NodeStatusUpdateFrequency
out.ImageMinimumGCAge = in.ImageMinimumGCAge out.ImageMinimumGCAge = in.ImageMinimumGCAge

View File

@ -185,6 +185,11 @@ func DeepCopy_v1alpha1_KubeletConfiguration(in interface{}, out interface{}, c *
*out = new(bool) *out = new(bool)
**out = **in **out = **in
} }
if in.ClusterDNS != nil {
in, out := &in.ClusterDNS, &out.ClusterDNS
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ImageGCHighThresholdPercent != nil { if in.ImageGCHighThresholdPercent != nil {
in, out := &in.ImageGCHighThresholdPercent, &out.ImageGCHighThresholdPercent in, out := &in.ImageGCHighThresholdPercent, &out.ImageGCHighThresholdPercent
*out = new(int32) *out = new(int32)

View File

@ -156,6 +156,11 @@ func DeepCopy_componentconfig_KubeletConfiguration(in interface{}, out interface
*out = make([]string, len(*in)) *out = make([]string, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.ClusterDNS != nil {
in, out := &in.ClusterDNS, &out.ClusterDNS
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.RegisterWithTaints != nil { if in.RegisterWithTaints != nil {
in, out := &in.RegisterWithTaints, &out.RegisterWithTaints in, out := &in.RegisterWithTaints, &out.RegisterWithTaints
*out = make([]api.Taint, len(*in)) *out = make([]api.Taint, len(*in))

View File

@ -10710,9 +10710,16 @@ func GetOpenAPIDefinitions(ref openapi.ReferenceCallback) map[string]openapi.Ope
}, },
"clusterDNS": { "clusterDNS": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: "clusterDNS is the IP address for a cluster DNS server. If set, kubelet will configure all containers to use this for DNS resolution in addition to the host's DNS servers", Description: "clusterDNS is a list of IP address for the cluster DNS server. If set, kubelet will configure all containers to use this for DNS resolution instead of the host's DNS servers",
Type: []string{"string"}, Type: []string{"array"},
Format: "", Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
}, },
}, },
"streamingConnectionIdleTimeout": { "streamingConnectionIdleTimeout": {

View File

@ -408,6 +408,16 @@ func NewMainKubelet(kubeCfg *componentconfig.KubeletConfiguration, kubeDeps *Kub
oomWatcher := NewOOMWatcher(kubeDeps.CAdvisorInterface, kubeDeps.Recorder) oomWatcher := NewOOMWatcher(kubeDeps.CAdvisorInterface, kubeDeps.Recorder)
clusterDNS := make([]net.IP, 0, len(kubeCfg.ClusterDNS))
for _, ipEntry := range kubeCfg.ClusterDNS {
ip := net.ParseIP(ipEntry)
if ip == nil {
glog.Warningf("Invalid clusterDNS ip '%q'", ipEntry)
} else {
clusterDNS = append(clusterDNS, ip)
}
}
klet := &Kubelet{ klet := &Kubelet{
hostname: hostname, hostname: hostname,
nodeName: nodeName, nodeName: nodeName,
@ -422,7 +432,7 @@ func NewMainKubelet(kubeCfg *componentconfig.KubeletConfiguration, kubeDeps *Kub
registerSchedulable: kubeCfg.RegisterSchedulable, registerSchedulable: kubeCfg.RegisterSchedulable,
standaloneMode: standaloneMode, standaloneMode: standaloneMode,
clusterDomain: kubeCfg.ClusterDomain, clusterDomain: kubeCfg.ClusterDomain,
clusterDNS: net.ParseIP(kubeCfg.ClusterDNS), clusterDNS: clusterDNS,
serviceLister: serviceLister, serviceLister: serviceLister,
nodeLister: nodeLister, nodeLister: nodeLister,
nodeInfo: nodeInfo, nodeInfo: nodeInfo,
@ -851,7 +861,7 @@ type Kubelet struct {
clusterDomain string clusterDomain string
// If non-nil, use this for container DNS server. // If non-nil, use this for container DNS server.
clusterDNS net.IP clusterDNS []net.IP
// masterServiceNamespace is the namespace that the master service is exposed in. // masterServiceNamespace is the namespace that the master service is exposed in.
masterServiceNamespace string masterServiceNamespace string
@ -1268,7 +1278,7 @@ func (kl *Kubelet) GetClusterDNS(pod *v1.Pod) ([]string, []string, error) {
} }
} }
useClusterFirstPolicy := pod.Spec.DNSPolicy == v1.DNSClusterFirst useClusterFirstPolicy := pod.Spec.DNSPolicy == v1.DNSClusterFirst
if useClusterFirstPolicy && kl.clusterDNS == nil { if useClusterFirstPolicy && len(kl.clusterDNS) == 0 {
// clusterDNS is not known. // clusterDNS is not known.
// pod with ClusterDNSFirst Policy cannot be created // 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) 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)
@ -1299,8 +1309,12 @@ func (kl *Kubelet) GetClusterDNS(pod *v1.Pod) ([]string, []string, error) {
// for a pod with DNSClusterFirst policy, the cluster DNS server is the only nameserver configured for // 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, // 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 // in case the cluster DNS server cannot resolve the DNS query itself
dns := []string{kl.clusterDNS.String()} dns := make([]string, len(kl.clusterDNS))
for i, ip := range kl.clusterDNS {
dns[i] = ip.String()
}
dnsSearch := kl.formDNSSearch(hostSearch, pod) dnsSearch := kl.formDNSSearch(hostSearch, pod)
return dns, dnsSearch, nil return dns, dnsSearch, nil
} }

View File

@ -175,7 +175,7 @@ func TestGenerateRunContainerOptions_DNSConfigurationParams(t *testing.T) {
clusterNS := "203.0.113.1" clusterNS := "203.0.113.1"
kubelet.clusterDomain = "kubernetes.io" kubelet.clusterDomain = "kubernetes.io"
kubelet.clusterDNS = net.ParseIP(clusterNS) kubelet.clusterDNS = []net.IP{net.ParseIP(clusterNS)}
pods := newTestPods(2) pods := newTestPods(2)
pods[0].Spec.DNSPolicy = v1.DNSClusterFirst pods[0].Spec.DNSPolicy = v1.DNSClusterFirst

View File

@ -128,7 +128,7 @@ func GetHollowKubeletConfig(
c.EvictionPressureTransitionPeriod.Duration = 5 * time.Minute c.EvictionPressureTransitionPeriod.Duration = 5 * time.Minute
c.MaxPods = int32(maxPods) c.MaxPods = int32(maxPods)
c.PodsPerCore = int32(podsPerCore) c.PodsPerCore = int32(podsPerCore)
c.ClusterDNS = "" c.ClusterDNS = []string{}
c.DockerExecHandlerName = "native" c.DockerExecHandlerName = "native"
c.ImageGCHighThresholdPercent = 90 c.ImageGCHighThresholdPercent = 90
c.ImageGCLowThresholdPercent = 80 c.ImageGCLowThresholdPercent = 80