Change:
Currently, kubelet mounts hosts file for pods. For hostNetwork pods, the behavior is different and instead, kubelet relies on docker (or whatever container runtime) to mount the host's hosts file. We should have kubelet handle hosts file for hostNetwork pods. This would have several benefits: control over hosts file in case the container runtime does something consistent behavior between hostNetwork and non-hostNetwork pods consistent behavior between different container runtimes -managed hosts file enables features like to be used for hostNetwork pods Implementation would require: kubelet's or its caller output different content based on hostNetwork. i. If it's not hostNetwork, use existing behavior. ii. If it's using hostNetwork, read the content from the host's hosts file. hostNetwork constraint in deciding whether to mount the file Open Questions: do we treat changes to the host's hosts file? should the file's content be static between container restarts of the same Pod? or should every container restart pick up the latest content of the hosts file? the former seems better for consistency, but letter might be more in line with other features that use host resources like host volumes. condition to manage hosts file also excludes windows even though windows also the use of hosts file and the same format. seems like this should be supported also. windows-support ships, should it have validation against windows platform? the tricky bit is the client, API, and minions' kubelet may be running on different platforms, and currently, there's no precedence to platform validation. /kind feature /sig node

Discussion:
// 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) { <del> // Kubernetes only mounts on /etc/hosts if : // - container does not use hostNetwork and // - container is not an infrastructure(pause) container </del> <ins> // Kubernetes only mounts on /etc/hosts if: // - container is not an infrastructure (pause) container </ins> // - container is not already mounting on /etc/hosts <del> // 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" </del> <ins> // - 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" </ins> 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 { <ins> // do not mount /etc/hosts if container is already mounting on the path </ins> mountEtcHostsFile = mountEtcHostsFile && (mount.MountPath != etcHostsPath) vol, ok := podVolumes[mount.Name] if !ok || vol.Mounter == nil {

} if mountEtcHostsFile { hostAliases := pod.Spec.HostAliases <del> hostsMount, err := makeHostsMount(podDir, podIP, hostName, hostDomain, hostAliases) </del> <ins> hostsMount, err := makeHostsMount(podDir, podIP, hostName, hostDomain, hostAliases, pod.Spec.HostNetwork) </ins> if err != nil { return nil, err }

// makeHostsMount makes the mountpoint for the hosts file that the containers // in a pod are injected with. <del> func makeHostsMount(podDir, podIP, hostName, hostDomainName string, hostAliases []v1.HostAlias) (*kubecontainer.Mount, error) { </del> <ins> func makeHostsMount(podDir, podIP, hostName, hostDomainName string, hostAliases []v1.HostAlias, useHostNetwork bool) (*kubecontainer.Mount, error) { </ins> hostsFilePath := path.Join(podDir, "etc-hosts") <del> if err := ensureHostsFile(hostsFilePath, podIP, hostName, hostDomainName, hostAliases); err != nil { </del> <ins> if err := ensureHostsFile(hostsFilePath, podIP, hostName, hostDomainName, hostAliases, useHostNetwork); err != nil { </ins> return nil, err } return &kubecontainer.Mount{

// ensureHostsFile ensures that the given host file has an up-to-date ip, host // name, and domain name. <del> func ensureHostsFile(fileName, hostIP, hostName, hostDomainName string, hostAliases []v1.HostAlias) error { content := hostsFileContent(hostIP, hostName, hostDomainName, hostAliases) return ioutil.WriteFile(fileName, content, 0644) </del> <ins> 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) } // nodeHostsFileContent reads the content of node's hosts file. func nodeHostsFileContent(hostsFilePath string) ([]byte, error) { return ioutil.ReadFile(hostsFilePath) </ins> } <del> // hostsFileContent is the content of the managed etc hosts func hostsFileContent(hostIP, hostName, hostDomainName string, hostAliases []v1.HostAlias) []byte { </del> <ins> // 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 { </ins> var buffer bytes.Buffer buffer.WriteString("# Kubernetes-managed hosts file.n") buffer.WriteString("127.0.0.1tlocalhostn")                      // ipv4 localhost

"bytes" "errors" "fmt" <ins> "io/ioutil" </ins> "net" <ins> "os" "path/filepath" </ins> "sort" "testing"

} } <del> func TestHostsFileContent(t *testing.T) { </del> <ins> 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) { </ins> testCases := []struct { hostIP          string hostName        string

} for _, testCase := range testCases { <del> actualContent := string(hostsFileContent(testCase.hostIP, testCase.hostName, testCase.hostDomainName, testCase.hostAliases)) assert.Equal(t, testCase.expectedContent, actualContent, "hosts file content not expected") </del> <ins> actualContent := managedHostsFileContent(testCase.hostIP, testCase.hostName, testCase.hostDomainName, testCase.hostAliases) assert.Equal(t, testCase.expectedContent, string(actualContent), "hosts file content not expected") </ins> } }