Merge pull request #54773 from phsiao/dns_options_handling

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

Support copying "options" in resolv.conf into pod sandbox when dnsPolicy is Default

**What this PR does / why we need it**:

This PR adds support for copying "options" from host's /etc/resolv.conf (or --resolv-conf) into pod's resolv.conf when dnsPolicy is Default.  Being able to customize options is important because it is common to leverage options to fine-tune the behavior of DNS client.

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

**Special notes for your reviewer**:

I originally wanted to also tackle the issue of copying options for when dnsPolicy is ClusterFirst, but with ability to "merge" with default options (ndots:5 more specifically) when it makes sense.  I decided to leave it off for now because the "merging" may need more discussions.   Happy to add that to this PR or create another PR for that if it makes sense and is clear what should be done.   I think even when dnsPolicy is ClusterFirst it is important to allow customization.

**Release note**:

```kubelet: add support for copying "options" from /etc/resolv.conf (or --resolv-conf if it is used) into pod's /etc/resolv.conf when dnsPolicy is Default.```
pull/6/head
Kubernetes Submit Queue 2017-11-08 22:08:24 -08:00 committed by GitHub
commit 3b359fb675
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 60 additions and 46 deletions

View File

@ -47,7 +47,7 @@ type HandlerRunner interface {
// able to get necessary informations like the RunContainerOptions, DNS settings, Host IP. // able to get necessary informations like the RunContainerOptions, DNS settings, Host IP.
type RuntimeHelper interface { type RuntimeHelper interface {
GenerateRunContainerOptions(pod *v1.Pod, container *v1.Container, podIP string) (contOpts *RunContainerOptions, err error) GenerateRunContainerOptions(pod *v1.Pod, container *v1.Container, podIP string) (contOpts *RunContainerOptions, err error)
GetClusterDNS(pod *v1.Pod) (dnsServers []string, dnsSearches []string, useClusterFirstPolicy bool, err error) GetClusterDNS(pod *v1.Pod) (dnsServers []string, dnsSearches []string, dnsOptions []string, useClusterFirstPolicy bool, err error)
// GetPodCgroupParent returns the CgroupName identifer, and its literal cgroupfs form on the host // GetPodCgroupParent returns the CgroupName identifer, and its literal cgroupfs form on the host
// of a pod. // of a pod.
GetPodCgroupParent(pod *v1.Pod) string GetPodCgroupParent(pod *v1.Pod) string

View File

@ -44,8 +44,8 @@ func (f *FakeRuntimeHelper) GetPodCgroupParent(pod *v1.Pod) string {
return "" return ""
} }
func (f *FakeRuntimeHelper) GetClusterDNS(pod *v1.Pod) ([]string, []string, bool, error) { func (f *FakeRuntimeHelper) GetClusterDNS(pod *v1.Pod) ([]string, []string, []string, bool, error) {
return f.DNSServers, f.DNSSearches, false, f.Err return f.DNSServers, f.DNSSearches, nil, false, f.Err
} }
// This is not used by docker runtime. // This is not used by docker runtime.

View File

@ -1401,21 +1401,21 @@ func (kl *Kubelet) GetKubeClient() clientset.Interface {
return kl.kubeClient return kl.kubeClient
} }
// GetClusterDNS returns a list of the DNS servers and a list of the DNS search // GetClusterDNS returns a list of the DNS servers, a list of the DNS search
// domains of the cluster. // domains of the cluster, and a list of resolv.conf options.
func (kl *Kubelet) GetClusterDNS(pod *v1.Pod) ([]string, []string, bool, error) { func (kl *Kubelet) GetClusterDNS(pod *v1.Pod) ([]string, []string, []string, bool, error) {
var hostDNS, hostSearch []string var hostDNS, hostSearch, hostOptions []string
// Get host DNS settings // Get host DNS settings
if kl.resolverConfig != "" { if kl.resolverConfig != "" {
f, err := os.Open(kl.resolverConfig) f, err := os.Open(kl.resolverConfig)
if err != nil { if err != nil {
return nil, nil, false, err return nil, nil, nil, false, err
} }
defer f.Close() defer f.Close()
hostDNS, hostSearch, err = kl.parseResolvConf(f) hostDNS, hostSearch, hostOptions, err = kl.parseResolvConf(f)
if err != nil { if err != nil {
return nil, nil, false, err return nil, nil, nil, false, err
} }
} }
useClusterFirstPolicy := ((pod.Spec.DNSPolicy == v1.DNSClusterFirst && !kubecontainer.IsHostNetworkPod(pod)) || pod.Spec.DNSPolicy == v1.DNSClusterFirstWithHostNet) useClusterFirstPolicy := ((pod.Spec.DNSPolicy == v1.DNSClusterFirst && !kubecontainer.IsHostNetworkPod(pod)) || pod.Spec.DNSPolicy == v1.DNSClusterFirstWithHostNet)
@ -1444,7 +1444,7 @@ func (kl *Kubelet) GetClusterDNS(pod *v1.Pod) ([]string, []string, bool, error)
} else { } else {
hostSearch = kl.formDNSSearchForDNSDefault(hostSearch, pod) hostSearch = kl.formDNSSearchForDNSDefault(hostSearch, pod)
} }
return hostDNS, hostSearch, useClusterFirstPolicy, nil return hostDNS, hostSearch, hostOptions, useClusterFirstPolicy, nil
} }
// 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
@ -1456,7 +1456,7 @@ func (kl *Kubelet) GetClusterDNS(pod *v1.Pod) ([]string, []string, bool, error)
} }
dnsSearch := kl.formDNSSearch(hostSearch, pod) dnsSearch := kl.formDNSSearch(hostSearch, pod)
return dns, dnsSearch, useClusterFirstPolicy, nil return dns, dnsSearch, hostOptions, useClusterFirstPolicy, nil
} }
// syncPod is the transaction script for the sync of a single pod. // syncPod is the transaction script for the sync of a single pod.
@ -2237,7 +2237,7 @@ func (kl *Kubelet) setupDNSinContainerizedMounter(mounterPath string) {
if err != nil { if err != nil {
glog.Error("Could not open resolverConf file") glog.Error("Could not open resolverConf file")
} else { } else {
_, hostSearch, err := kl.parseResolvConf(f) _, hostSearch, _, err := kl.parseResolvConf(f)
if err != nil { if err != nil {
glog.Errorf("Error for parsing the reslov.conf file: %v", err) glog.Errorf("Error for parsing the reslov.conf file: %v", err)
} else { } else {

View File

@ -169,7 +169,7 @@ func (kl *Kubelet) checkLimitsForResolvConf() {
} }
defer f.Close() defer f.Close()
_, hostSearch, err := kl.parseResolvConf(f) _, hostSearch, _, err := kl.parseResolvConf(f)
if err != nil { if err != nil {
kl.recorder.Event(kl.nodeRef, v1.EventTypeWarning, "checkLimitsForResolvConf", err.Error()) kl.recorder.Event(kl.nodeRef, v1.EventTypeWarning, "checkLimitsForResolvConf", err.Error())
glog.Error("checkLimitsForResolvConf: " + err.Error()) glog.Error("checkLimitsForResolvConf: " + err.Error())
@ -200,12 +200,12 @@ func (kl *Kubelet) 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 and searches, possibly returning an error. // it into nameservers, searches and options, possibly returning an error.
// TODO: move to utility package // TODO: move to utility package
func (kl *Kubelet) parseResolvConf(reader io.Reader) (nameservers []string, searches []string, err error) { func (kl *Kubelet) 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 {
return nil, nil, err return nil, nil, nil, err
} }
// Lines of the form "nameserver 1.2.3.4" accumulate. // Lines of the form "nameserver 1.2.3.4" accumulate.
@ -214,6 +214,10 @@ func (kl *Kubelet) parseResolvConf(reader io.Reader) (nameservers []string, sear
// Lines of the form "search example.com" overrule - last one wins. // Lines of the form "search example.com" overrule - last one wins.
searches = []string{} 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") lines := strings.Split(string(file), "\n")
for l := range lines { for l := range lines {
trimmed := strings.TrimSpace(lines[l]) trimmed := strings.TrimSpace(lines[l])
@ -230,13 +234,16 @@ func (kl *Kubelet) parseResolvConf(reader io.Reader) (nameservers []string, sear
if fields[0] == "search" { if fields[0] == "search" {
searches = fields[1:] 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 // There used to be code here to scrub DNS for each cloud, but doesn't
// make sense anymore since cloudproviders are being factored out. // make sense anymore since cloudproviders are being factored out.
// contact @thockin or @wlan0 for more information // contact @thockin or @wlan0 for more information
return nameservers, searches, nil return nameservers, searches, options, nil
} }
// syncNetworkStatus updates the network state // syncNetworkStatus updates the network state

View File

@ -74,39 +74,44 @@ func TestParseResolvConf(t *testing.T) {
data string data string
nameservers []string nameservers []string
searches []string searches []string
options []string
}{ }{
{"", []string{}, []string{}}, {"", []string{}, []string{}, []string{}},
{" ", []string{}, []string{}}, {" ", []string{}, []string{}, []string{}},
{"\n", []string{}, []string{}}, {"\n", []string{}, []string{}, []string{}},
{"\t\n\t", []string{}, []string{}}, {"\t\n\t", []string{}, []string{}, []string{}},
{"#comment\n", []string{}, []string{}}, {"#comment\n", []string{}, []string{}, []string{}},
{" #comment\n", []string{}, []string{}}, {" #comment\n", []string{}, []string{}, []string{}},
{"#comment\n#comment", []string{}, []string{}}, {"#comment\n#comment", []string{}, []string{}, []string{}},
{"#comment\nnameserver", []string{}, []string{}}, {"#comment\nnameserver", []string{}, []string{}, []string{}},
{"#comment\nnameserver\nsearch", []string{}, []string{}}, {"#comment\nnameserver\nsearch", []string{}, []string{}, []string{}},
{"nameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}}, {"nameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}},
{" nameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}}, {" nameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}},
{"\tnameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}}, {"\tnameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}},
{"nameserver\t1.2.3.4", []string{"1.2.3.4"}, []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{}}, {"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{}}, {"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{}}, {"nameserver 1.2.3.4 #comment", []string{"1.2.3.4"}, []string{}, []string{}},
{"search foo", []string{}, []string{"foo"}}, {"search foo", []string{}, []string{"foo"}, []string{}},
{"search foo bar", []string{}, []string{"foo", "bar"}}, {"search foo bar", []string{}, []string{"foo", "bar"}, []string{}},
{"search foo bar bat\n", []string{}, []string{"foo", "bar", "bat"}}, {"search foo bar bat\n", []string{}, []string{"foo", "bar", "bat"}, []string{}},
{"search foo\nsearch bar", []string{}, []string{"bar"}}, {"search foo\nsearch bar", []string{}, []string{"bar"}, []string{}},
{"nameserver 1.2.3.4\nsearch foo bar", []string{"1.2.3.4"}, []string{"foo", "bar"}}, {"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"}}, {"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"}}, {"#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 */) testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
defer testKubelet.Cleanup() defer testKubelet.Cleanup()
kubelet := testKubelet.kubelet kubelet := testKubelet.kubelet
for i, tc := range testCases { for i, tc := range testCases {
ns, srch, err := kubelet.parseResolvConf(strings.NewReader(tc.data)) ns, srch, opts, err := kubelet.parseResolvConf(strings.NewReader(tc.data))
require.NoError(t, err) require.NoError(t, err)
assert.EqualValues(t, tc.nameservers, ns, "test case [%d]: name servers", i) 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.searches, srch, "test case [%d] searches", i)
assert.EqualValues(t, tc.options, opts, "test case [%d] options", i)
} }
} }

View File

@ -2194,7 +2194,7 @@ func TestGetClusterDNS(t *testing.T) {
}, 4) }, 4)
for i, pod := range pods { for i, pod := range pods {
var err error var err error
options[i].DNS, options[i].DNSSearch, _, err = kubelet.GetClusterDNS(pod) options[i].DNS, options[i].DNSSearch, _, _, err = kubelet.GetClusterDNS(pod)
if err != nil { if err != nil {
t.Fatalf("failed to generate container options: %v", err) t.Fatalf("failed to generate container options: %v", err)
} }
@ -2227,7 +2227,7 @@ func TestGetClusterDNS(t *testing.T) {
kubelet.resolverConfig = "/etc/resolv.conf" kubelet.resolverConfig = "/etc/resolv.conf"
for i, pod := range pods { for i, pod := range pods {
var err error var err error
options[i].DNS, options[i].DNSSearch, _, err = kubelet.GetClusterDNS(pod) options[i].DNS, options[i].DNSSearch, _, _, err = kubelet.GetClusterDNS(pod)
if err != nil { if err != nil {
t.Fatalf("failed to generate container options: %v", err) t.Fatalf("failed to generate container options: %v", err)
} }

View File

@ -74,14 +74,16 @@ func (m *kubeGenericRuntimeManager) generatePodSandboxConfig(pod *v1.Pod, attemp
Annotations: newPodAnnotations(pod), Annotations: newPodAnnotations(pod),
} }
dnsServers, dnsSearches, useClusterFirstPolicy, err := m.runtimeHelper.GetClusterDNS(pod) dnsServers, dnsSearches, dnsOptions, useClusterFirstPolicy, err := m.runtimeHelper.GetClusterDNS(pod)
if err != nil { if err != nil {
return nil, err return nil, err
} }
podSandboxConfig.DnsConfig = &runtimeapi.DNSConfig{ podSandboxConfig.DnsConfig = &runtimeapi.DNSConfig{
Servers: dnsServers, Servers: dnsServers,
Searches: dnsSearches, Searches: dnsSearches,
Options: dnsOptions,
} }
if useClusterFirstPolicy { if useClusterFirstPolicy {
podSandboxConfig.DnsConfig.Options = defaultDNSOptions podSandboxConfig.DnsConfig.Options = defaultDNSOptions
} }

View File

@ -1041,7 +1041,7 @@ func (r *Runtime) generateRunCommand(pod *v1.Pod, uuid, networkNamespaceID strin
} }
} else { } else {
// Setup DNS. // Setup DNS.
dnsServers, dnsSearches, _, err := r.runtimeHelper.GetClusterDNS(pod) dnsServers, dnsSearches, _, _, err := r.runtimeHelper.GetClusterDNS(pod)
if err != nil { if err != nil {
return "", err return "", err
} }