From fb4bff01c597b2fb8e77c4558eb8f06aedde9a03 Mon Sep 17 00:00:00 2001 From: Ricky Pai Date: Mon, 31 Jul 2017 11:44:47 -0700 Subject: [PATCH] Kubelet manage hosts file for HostNetwork Pods instead of Docker --- pkg/kubelet/kubelet_pods.go | 50 ++++++++++++++++++-------- pkg/kubelet/kubelet_pods_test.go | 61 ++++++++++++++++++++++++++++++-- 2 files changed, 94 insertions(+), 17 deletions(-) diff --git a/pkg/kubelet/kubelet_pods.go b/pkg/kubelet/kubelet_pods.go index b3e752f49e2..c7588e30f91 100644 --- a/pkg/kubelet/kubelet_pods.go +++ b/pkg/kubelet/kubelet_pods.go @@ -109,16 +109,17 @@ func (kl *Kubelet) makeDevices(pod *v1.Pod, container *v1.Container) ([]kubecont // makeMounts determines the mount points for the given container. func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, hostDomain, podIP string, podVolumes kubecontainer.VolumeMap) ([]kubecontainer.Mount, error) { - // Kubernetes only mounts on /etc/hosts if : - // - container does not use hostNetwork and - // - container is not an infrastructure(pause) container + // Kubernetes only mounts on /etc/hosts if: + // - container is not an infrastructure (pause) container // - container is not already mounting on /etc/hosts - // When the pause container is being created, its IP is still unknown. Hence, PodIP will not have been set. - // OS is not Windows - mountEtcHostsFile := !pod.Spec.HostNetwork && len(podIP) > 0 && runtime.GOOS != "windows" + // - OS is not Windows + // Kubernetes will not mount /etc/hosts if: + // - when the Pod sandbox is being created, its IP is still unknown. Hence, PodIP will not have been set. + mountEtcHostsFile := len(podIP) > 0 && runtime.GOOS != "windows" glog.V(3).Infof("container: %v/%v/%v podIP: %q creating hosts mount: %v", pod.Namespace, pod.Name, container.Name, podIP, mountEtcHostsFile) mounts := []kubecontainer.Mount{} for _, mount := range container.VolumeMounts { + // do not mount /etc/hosts if container is already mounting on the path mountEtcHostsFile = mountEtcHostsFile && (mount.MountPath != etcHostsPath) vol, ok := podVolumes[mount.Name] if !ok || vol.Mounter == nil { @@ -197,7 +198,7 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h } if mountEtcHostsFile { hostAliases := pod.Spec.HostAliases - hostsMount, err := makeHostsMount(podDir, podIP, hostName, hostDomain, hostAliases) + hostsMount, err := makeHostsMount(podDir, podIP, hostName, hostDomain, hostAliases, pod.Spec.HostNetwork) if err != nil { return nil, err } @@ -208,9 +209,9 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h // makeHostsMount makes the mountpoint for the hosts file that the containers // in a pod are injected with. -func makeHostsMount(podDir, podIP, hostName, hostDomainName string, hostAliases []v1.HostAlias) (*kubecontainer.Mount, error) { +func makeHostsMount(podDir, podIP, hostName, hostDomainName string, hostAliases []v1.HostAlias, useHostNetwork bool) (*kubecontainer.Mount, error) { hostsFilePath := path.Join(podDir, "etc-hosts") - if err := ensureHostsFile(hostsFilePath, podIP, hostName, hostDomainName, hostAliases); err != nil { + if err := ensureHostsFile(hostsFilePath, podIP, hostName, hostDomainName, hostAliases, useHostNetwork); err != nil { return nil, err } return &kubecontainer.Mount{ @@ -224,13 +225,34 @@ func makeHostsMount(podDir, podIP, hostName, hostDomainName string, hostAliases // ensureHostsFile ensures that the given host file has an up-to-date ip, host // name, and domain name. -func ensureHostsFile(fileName, hostIP, hostName, hostDomainName string, hostAliases []v1.HostAlias) error { - content := hostsFileContent(hostIP, hostName, hostDomainName, hostAliases) - return ioutil.WriteFile(fileName, content, 0644) +func ensureHostsFile(fileName, hostIP, hostName, hostDomainName string, hostAliases []v1.HostAlias, useHostNetwork bool) error { + var hostsFileContent []byte + var err error + + if useHostNetwork { + // if Pod is using host network, read hosts file from the node's filesystem. + // `etcHostsPath` references the location of the hosts file on the node. + // `/etc/hosts` for *nix systems. + hostsFileContent, err = nodeHostsFileContent(etcHostsPath) + if err != nil { + return err + } + } else { + // if Pod is not using host network, create a managed hosts file with Pod IP and other information. + hostsFileContent = managedHostsFileContent(hostIP, hostName, hostDomainName, hostAliases) + } + + return ioutil.WriteFile(fileName, hostsFileContent, 0644) } -// hostsFileContent is the content of the managed etc hosts -func hostsFileContent(hostIP, hostName, hostDomainName string, hostAliases []v1.HostAlias) []byte { +// nodeHostsFileContent reads the content of node's hosts file. +func nodeHostsFileContent(hostsFilePath string) ([]byte, error) { + return ioutil.ReadFile(hostsFilePath) +} + +// managedHostsFileContent generates the content of the managed etc hosts based on Pod IP and other +// information. +func managedHostsFileContent(hostIP, hostName, hostDomainName string, hostAliases []v1.HostAlias) []byte { var buffer bytes.Buffer buffer.WriteString("# Kubernetes-managed hosts file.\n") buffer.WriteString("127.0.0.1\tlocalhost\n") // ipv4 localhost diff --git a/pkg/kubelet/kubelet_pods_test.go b/pkg/kubelet/kubelet_pods_test.go index 986de4fac8c..eb9558706ae 100644 --- a/pkg/kubelet/kubelet_pods_test.go +++ b/pkg/kubelet/kubelet_pods_test.go @@ -20,7 +20,10 @@ import ( "bytes" "errors" "fmt" + "io/ioutil" "net" + "os" + "path/filepath" "sort" "testing" @@ -179,7 +182,59 @@ func TestMakeMounts(t *testing.T) { } } -func TestHostsFileContent(t *testing.T) { +func TestNodeHostsFileContent(t *testing.T) { + testCases := []struct { + hostsFileName string + expectedContent string + }{ + { + "hosts_test_file1", + `# hosts file for testing. +127.0.0.1 localhost +::1 localhost ip6-localhost ip6-loopback +fe00::0 ip6-localnet +fe00::0 ip6-mcastprefix +fe00::1 ip6-allnodes +fe00::2 ip6-allrouters +123.45.67.89 some.domain +`, + }, + { + "hosts_test_file2", + `# another hosts file for testing. +127.0.0.1 localhost +::1 localhost ip6-localhost ip6-loopback +fe00::0 ip6-localnet +fe00::0 ip6-mcastprefix +fe00::1 ip6-allnodes +fe00::2 ip6-allrouters +12.34.56.78 another.domain +`, + }, + } + + for _, testCase := range testCases { + tmpdir, err := writeHostsFile(testCase.hostsFileName, testCase.expectedContent) + require.NoError(t, err, "could not create a temp hosts file") + defer os.RemoveAll(tmpdir) + + actualContent, fileReadErr := nodeHostsFileContent(filepath.Join(tmpdir, testCase.hostsFileName)) + require.NoError(t, fileReadErr, "could not create read hosts file") + assert.Equal(t, testCase.expectedContent, string(actualContent), "hosts file content not expected") + } +} + +// writeHostsFile will write a hosts file into a temporary dir, and return that dir. +// Caller is responsible for deleting the dir and its contents. +func writeHostsFile(filename string, cfg string) (string, error) { + tmpdir, err := ioutil.TempDir("", "kubelet=kubelet_pods_test.go=") + if err != nil { + return "", err + } + return tmpdir, ioutil.WriteFile(filepath.Join(tmpdir, filename), []byte(cfg), 0644) +} + +func TestManagedHostsFileContent(t *testing.T) { testCases := []struct { hostIP string hostName string @@ -264,8 +319,8 @@ fe00::2 ip6-allrouters } for _, testCase := range testCases { - actualContent := string(hostsFileContent(testCase.hostIP, testCase.hostName, testCase.hostDomainName, testCase.hostAliases)) - assert.Equal(t, testCase.expectedContent, actualContent, "hosts file content not expected") + actualContent := managedHostsFileContent(testCase.hostIP, testCase.hostName, testCase.hostDomainName, testCase.hostAliases) + assert.Equal(t, testCase.expectedContent, string(actualContent), "hosts file content not expected") } }