diff --git a/contrib/for-tests/jessie-dnsutils/Dockerfile b/contrib/for-tests/jessie-dnsutils/Dockerfile new file mode 100644 index 0000000000..b65fe62b8b --- /dev/null +++ b/contrib/for-tests/jessie-dnsutils/Dockerfile @@ -0,0 +1,6 @@ +FROM debian:jessie +MAINTAINER Abhishek Shah "abshah@google.com" + +RUN apt-get -q update && \ + apt-get install -y dnsutils && \ + apt-get clean diff --git a/contrib/for-tests/jessie-dnsutils/Makefile b/contrib/for-tests/jessie-dnsutils/Makefile new file mode 100644 index 0000000000..450e9dd86d --- /dev/null +++ b/contrib/for-tests/jessie-dnsutils/Makefile @@ -0,0 +1,8 @@ +all: + @echo "try 'make image' or 'make push'" + +image: + docker build -t gcr.io/google_containers/jessie-dnsutils . + +push: + gcloud docker push gcr.io/google_containers/jessie-dnsutils diff --git a/pkg/kubelet/dockertools/manager.go b/pkg/kubelet/dockertools/manager.go index 02d56d9446..37f40d2c7d 100644 --- a/pkg/kubelet/dockertools/manager.go +++ b/pkg/kubelet/dockertools/manager.go @@ -60,6 +60,10 @@ const ( kubernetesPodLabel = "io.kubernetes.pod.data" kubernetesContainerLabel = "io.kubernetes.container.name" + // ndots specifies the minimum number of dots that a domain name must contain for the resolver to consider it as FQDN (fully-qualified) + // we want to able to consider SRV lookup names like _dns._udp.kube-dns.default.svc to be considered relative. + // hence, setting ndots to be 5. + ndotsDNSOption = "options ndots:5\n" ) // DockerManager implements the Runtime interface. @@ -1221,6 +1225,13 @@ func (dm *DockerManager) runContainerInPod(pod *api.Pod, container *api.Containe } if container.Name == PodInfraContainerName { util.ApplyOomScoreAdj(containerInfo.State.Pid, podOomScoreAdj) + // currently, Docker does not have a flag by which the ndots option can be passed. + // (A seperate issue has been filed with Docker to add a ndots flag) + // The addNDotsOption call appends the ndots option to the resolv.conf file generated by docker. + // This resolv.conf file is shared by all containers of the same pod, and needs to be modified only once per pod. + // we modify it when the pause container is created since it is the first container created in the pod since it holds + // the networking namespace. + err = addNDotsOption(containerInfo.ResolvConfPath) } else { // Children processes of docker daemon will inheritant the OOM score from docker // daemon process. We explicitly apply OOM score 0 by default to the user @@ -1231,6 +1242,36 @@ func (dm *DockerManager) runContainerInPod(pod *api.Pod, container *api.Containe return kubeletTypes.DockerID(id), err } +func addNDotsOption(resolvFilePath string) error { + if len(resolvFilePath) == 0 { + glog.Errorf("DNS ResolvConfPath is empty.") + return nil + } + + if _, err := os.Stat(resolvFilePath); os.IsNotExist(err) { + return fmt.Errorf("DNS ResolvConfPath specified but does not exist. It could not be updated: %s", resolvFilePath) + } + + glog.V(4).Infof("DNS ResolvConfPath exists: %s. Will attempt to add ndots option: %s", resolvFilePath, ndotsDNSOption) + + if err := appendToFile(resolvFilePath, ndotsDNSOption); err != nil { + glog.Errorf("resolv.conf could not be updated. err:%v", err) + return err + } + return nil +} + +func appendToFile(filePath, stringToAppend string) error { + f, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer f.Close() + + _, err = f.WriteString(stringToAppend) + return err +} + // createPodInfraContainer starts the pod infra container for a pod. Returns the docker container ID of the newly created container. func (dm *DockerManager) createPodInfraContainer(pod *api.Pod) (kubeletTypes.DockerID, error) { start := time.Now() diff --git a/test/e2e/dns.go b/test/e2e/dns.go index 3c94199577..6a7faeeeab 100644 --- a/test/e2e/dns.go +++ b/test/e2e/dns.go @@ -38,7 +38,7 @@ var dnsServiceLableSelector = labels.Set{ "kubernetes.io/cluster-service": "true", }.AsSelector() -func createDNSPod(namespace, probeCmd string) *api.Pod { +func createDNSPod(namespace, wheezyProbeCmd, jessieProbeCmd string) *api.Pod { pod := &api.Pod{ TypeMeta: api.TypeMeta{ Kind: "Pod", @@ -78,7 +78,18 @@ func createDNSPod(namespace, probeCmd string) *api.Pod { { Name: "querier", Image: "gcr.io/google_containers/dnsutils", - Command: []string{"sh", "-c", probeCmd}, + Command: []string{"sh", "-c", wheezyProbeCmd}, + VolumeMounts: []api.VolumeMount{ + { + Name: "results", + MountPath: "/results", + }, + }, + }, + { + Name: "jessie-querier", + Image: "gcr.io/google_containers/jessie-dnsutils", + Command: []string{"sh", "-c", jessieProbeCmd}, VolumeMounts: []api.VolumeMount{ { Name: "results", @@ -92,7 +103,7 @@ func createDNSPod(namespace, probeCmd string) *api.Pod { return pod } -func createProbeCommand(namesToResolve []string) (string, []string) { +func createProbeCommand(namesToResolve []string, fileNamePrefix string) (string, []string) { fileNames := make([]string, 0, len(namesToResolve)*2) probeCmd := "for i in `seq 1 600`; do " for _, name := range namesToResolve { @@ -103,10 +114,10 @@ func createProbeCommand(namesToResolve []string) (string, []string) { if strings.HasPrefix(name, "_") { lookup = "SRV" } - fileName := fmt.Sprintf("udp@%s", name) + fileName := fmt.Sprintf("%s_udp@%s", fileNamePrefix, name) fileNames = append(fileNames, fileName) probeCmd += fmt.Sprintf(`test -n "$$(dig +notcp +noall +answer +search %s %s)" && echo OK > /results/%s;`, name, lookup, fileName) - fileName = fmt.Sprintf("tcp@%s", name) + fileName = fmt.Sprintf("%s_tcp@%s", fileNamePrefix, name) fileNames = append(fileNames, fileName) probeCmd += fmt.Sprintf(`test -n "$$(dig +tcp +noall +answer +search %s %s)" && echo OK > /results/%s;`, name, lookup, fileName) } @@ -140,6 +151,36 @@ func assertFilesExist(fileNames []string, fileDir string, pod *api.Pod, client * Expect(len(failed)).To(Equal(0)) } +func validateDNSResults(f *Framework, pod *api.Pod, fileNames []string) { + + By("submitting the pod to kubernetes") + podClient := f.Client.Pods(f.Namespace.Name) + defer func() { + By("deleting the pod") + defer GinkgoRecover() + podClient.Delete(pod.Name, nil) + }() + if _, err := podClient.Create(pod); err != nil { + Failf("Failed to create %s pod: %v", pod.Name, err) + } + + expectNoError(f.WaitForPodRunning(pod.Name)) + + By("retrieving the pod") + pod, err := podClient.Get(pod.Name) + if err != nil { + Failf("Failed to get pod %s: %v", pod.Name, err) + } + + // Try to find results for each expected name. + By("looking for the results for each expected name from probiers") + assertFilesExist(fileNames, "results", pod, f.Client) + + // TODO: probe from the host, too. + + Logf("DNS probes using %s succeeded\n", pod.Name) +} + var _ = Describe("DNS", func() { f := NewFramework("dns") @@ -175,38 +216,13 @@ var _ = Describe("DNS", func() { namesToResolve = append(namesToResolve, "metadata") } - probeCmd, fileNames := createProbeCommand(namesToResolve) + wheezyProbeCmd, wheezyFileNames := createProbeCommand(namesToResolve, "wheezy") + jessieProbeCmd, jessieFileNames := createProbeCommand(namesToResolve, "jessie") // Run a pod which probes DNS and exposes the results by HTTP. By("creating a pod to probe DNS") - pod := createDNSPod(f.Namespace.Name, probeCmd) - - By("submitting the pod to kubernetes") - podClient = f.Client.Pods(f.Namespace.Name) - defer func() { - By("deleting the pod") - defer GinkgoRecover() - podClient.Delete(pod.Name, nil) - }() - if _, err := podClient.Create(pod); err != nil { - Failf("Failed to create %s pod: %v", pod.Name, err) - } - - expectNoError(f.WaitForPodRunning(pod.Name)) - - By("retrieving the pod") - pod, err = podClient.Get(pod.Name) - if err != nil { - Failf("Failed to get pod %s: %v", pod.Name, err) - } - - // Try to find results for each expected name. - By("looking for the results for each expected name") - assertFilesExist(fileNames, "results", pod, f.Client) - - // TODO: probe from the host, too. - - Logf("DNS probes using %s succeeded\n", pod.Name) + pod := createDNSPod(f.Namespace.Name, wheezyProbeCmd, jessieProbeCmd) + validateDNSResults(f, pod, append(wheezyFileNames, jessieFileNames...)) }) It("should provide DNS for services", func() { if providerIs("vagrant") { @@ -283,38 +299,15 @@ var _ = Describe("DNS", func() { fmt.Sprintf("_http._tcp.%s.%s.svc", regularService.Name, f.Namespace.Name), } - probeCmd, fileNames := createProbeCommand(namesToResolve) + wheezyProbeCmd, wheezyFileNames := createProbeCommand(namesToResolve, "wheezy") + jessieProbeCmd, jessieFileNames := createProbeCommand(namesToResolve, "jessie") + // Run a pod which probes DNS and exposes the results by HTTP. By("creating a pod to probe DNS") - pod := createDNSPod(f.Namespace.Name, probeCmd) + pod := createDNSPod(f.Namespace.Name, wheezyProbeCmd, jessieProbeCmd) pod.ObjectMeta.Labels = testServiceSelector - By("submitting the pod to kubernetes") - podClient = f.Client.Pods(f.Namespace.Name) - defer func() { - By("deleting the pod") - defer GinkgoRecover() - podClient.Delete(pod.Name, nil) - }() - if _, err := podClient.Create(pod); err != nil { - Failf("Failed to create %s pod: %v", pod.Name, err) - } - - expectNoError(f.WaitForPodRunning(pod.Name)) - - By("retrieving the pod") - pod, err = podClient.Get(pod.Name) - if err != nil { - Failf("Failed to get pod %s: %v", pod.Name, err) - } - - // Try to find results for each expected name. - By("looking for the results for each expected name") - assertFilesExist(fileNames, "results", pod, f.Client) - - // TODO: probe from the host, too. - - Logf("DNS probes using %s succeeded\n", pod.Name) + validateDNSResults(f, pod, append(wheezyFileNames, jessieFileNames...)) }) })