Skip to content

Commit

Permalink
Honor custom DNS in play|generate kube
Browse files Browse the repository at this point in the history
when creating kubernetes yaml from containers and pods, we should honor
any custom dns settings the user provided. in the case of generate kube,
these would be provided by --dns, --dns-search, and --dns-opt. if
multiple containers are involved in the generate, the options will be
cumulative and unique with the exception of dns-opt.

when replaying a kube file that has kubernetes dns information, we now
also add that information to the pod creation.

the options for dnspolicy is not enabled as there seemed to be no direct
correlation between kubernetes and podman.

Fixes: containers#9132

Signed-off-by: baude <[email protected]>
  • Loading branch information
baude committed Jan 29, 2021
1 parent 2ee034c commit ca0dd76
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 14 deletions.
112 changes: 98 additions & 14 deletions libpod/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,10 @@ func (p *Pod) podWithContainers(containers []*Container, ports []v1.ContainerPor
deDupPodVolumes := make(map[string]*v1.Volume)
first := true
podContainers := make([]v1.Container, 0, len(containers))
dnsInfo := v1.PodDNSConfig{}
for _, ctr := range containers {
if !ctr.IsInfra() {
ctr, volumes, err := containerToV1Container(ctr)
ctr, volumes, _, err := containerToV1Container(ctr)
if err != nil {
return nil, err
}
Expand All @@ -196,17 +197,33 @@ func (p *Pod) podWithContainers(containers []*Container, ports []v1.ContainerPor
vol := vol
deDupPodVolumes[vol.Name] = &vol
}
} else {
_, _, infraDNS, err := containerToV1Container(ctr)
if err != nil {
return nil, err
}
if infraDNS != nil {
if servers := infraDNS.Nameservers; len(servers) > 0 {
dnsInfo.Nameservers = servers
}
if searches := infraDNS.Searches; len(searches) > 0 {
dnsInfo.Searches = searches
}
if options := infraDNS.Options; len(options) > 0 {
dnsInfo.Options = options
}
}
}
}
podVolumes := make([]v1.Volume, 0, len(deDupPodVolumes))
for _, vol := range deDupPodVolumes {
podVolumes = append(podVolumes, *vol)
}

return addContainersAndVolumesToPodObject(podContainers, podVolumes, p.Name()), nil
return addContainersAndVolumesToPodObject(podContainers, podVolumes, p.Name(), &dnsInfo), nil
}

func addContainersAndVolumesToPodObject(containers []v1.Container, volumes []v1.Volume, podName string) *v1.Pod {
func addContainersAndVolumesToPodObject(containers []v1.Container, volumes []v1.Volume, podName string, dnsOptions *v1.PodDNSConfig) *v1.Pod {
tm := v12.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
Expand All @@ -228,6 +245,9 @@ func addContainersAndVolumesToPodObject(containers []v1.Container, volumes []v1.
Containers: containers,
Volumes: volumes,
}
if dnsOptions != nil {
ps.DNSConfig = dnsOptions
}
p := v1.Pod{
TypeMeta: tm,
ObjectMeta: om,
Expand All @@ -241,57 +261,90 @@ func addContainersAndVolumesToPodObject(containers []v1.Container, volumes []v1.
func simplePodWithV1Containers(ctrs []*Container) (*v1.Pod, error) {
kubeCtrs := make([]v1.Container, 0, len(ctrs))
kubeVolumes := make([]v1.Volume, 0)
podDNS := v1.PodDNSConfig{}
for _, ctr := range ctrs {
kubeCtr, kubeVols, err := containerToV1Container(ctr)
kubeCtr, kubeVols, ctrDNS, err := containerToV1Container(ctr)
if err != nil {
return nil, err
}
kubeCtrs = append(kubeCtrs, kubeCtr)
kubeVolumes = append(kubeVolumes, kubeVols...)
}
return addContainersAndVolumesToPodObject(kubeCtrs, kubeVolumes, strings.ReplaceAll(ctrs[0].Name(), "_", "")), nil

// Combine DNS information in sum'd structure
if ctrDNS != nil {
// nameservers
if servers := ctrDNS.Nameservers; servers != nil {
if podDNS.Nameservers == nil {
podDNS.Nameservers = make([]string, 0)
}
for _, s := range servers {
if !util.StringInSlice(s, podDNS.Nameservers) { // only append if it does not exist
podDNS.Nameservers = append(podDNS.Nameservers, s)
}
}
}
// search domains
if domains := ctrDNS.Searches; domains != nil {
if podDNS.Searches == nil {
podDNS.Searches = make([]string, 0)
}
for _, d := range domains {
if !util.StringInSlice(d, podDNS.Searches) { // only append if it does not exist
podDNS.Searches = append(podDNS.Searches, d)
}
}
}
// dns options
if options := ctrDNS.Options; options != nil {
if podDNS.Options == nil {
podDNS.Options = make([]v1.PodDNSConfigOption, 0)
}
podDNS.Options = append(podDNS.Options, options...)
}
} // end if ctrDNS
}
return addContainersAndVolumesToPodObject(kubeCtrs, kubeVolumes, strings.ReplaceAll(ctrs[0].Name(), "_", ""), &podDNS), nil
}

// containerToV1Container converts information we know about a libpod container
// to a V1.Container specification.
func containerToV1Container(c *Container) (v1.Container, []v1.Volume, error) {
func containerToV1Container(c *Container) (v1.Container, []v1.Volume, *v1.PodDNSConfig, error) {
kubeContainer := v1.Container{}
kubeVolumes := []v1.Volume{}
kubeSec, err := generateKubeSecurityContext(c)
if err != nil {
return kubeContainer, kubeVolumes, err
return kubeContainer, kubeVolumes, nil, err
}

if len(c.config.Spec.Linux.Devices) > 0 {
// TODO Enable when we can support devices and their names
kubeContainer.VolumeDevices = generateKubeVolumeDeviceFromLinuxDevice(c.Spec().Linux.Devices)
return kubeContainer, kubeVolumes, errors.Wrapf(define.ErrNotImplemented, "linux devices")
return kubeContainer, kubeVolumes, nil, errors.Wrapf(define.ErrNotImplemented, "linux devices")
}

if len(c.config.UserVolumes) > 0 {
// TODO When we until we can resolve what the volume name should be, this is disabled
// Volume names need to be coordinated "globally" in the kube files.
volumeMounts, volumes, err := libpodMountsToKubeVolumeMounts(c)
if err != nil {
return kubeContainer, kubeVolumes, err
return kubeContainer, kubeVolumes, nil, err
}
kubeContainer.VolumeMounts = volumeMounts
kubeVolumes = append(kubeVolumes, volumes...)
}

envVariables, err := libpodEnvVarsToKubeEnvVars(c.config.Spec.Process.Env)
if err != nil {
return kubeContainer, kubeVolumes, err
return kubeContainer, kubeVolumes, nil, err
}

portmappings, err := c.PortMappings()
if err != nil {
return kubeContainer, kubeVolumes, err
return kubeContainer, kubeVolumes, nil, err
}
ports, err := ocicniPortMappingToContainerPort(portmappings)
if err != nil {
return kubeContainer, kubeVolumes, err
return kubeContainer, kubeVolumes, nil, err
}

containerCommands := c.Command()
Expand Down Expand Up @@ -355,7 +408,38 @@ func containerToV1Container(c *Container) (v1.Container, []v1.Volume, error) {
}
}

return kubeContainer, kubeVolumes, nil
// Obtain the DNS entries from the container
dns := v1.PodDNSConfig{}

// DNS servers
if servers := c.config.DNSServer; len(servers) > 0 {
dnsServers := make([]string, 0)
for _, server := range servers {
dnsServers = append(dnsServers, server.String())
}
dns.Nameservers = dnsServers
}

// DNS search domains
if searches := c.config.DNSSearch; len(searches) > 0 {
dns.Searches = searches
}

// DNS options
if options := c.config.DNSOption; len(options) > 0 {
dnsOptions := make([]v1.PodDNSConfigOption, 0)
for _, option := range options {
// the option can be "k:v" or just "k", no delimiter is required
opts := strings.SplitN(option, ":", 2)
dnsOpt := v1.PodDNSConfigOption{
Name: opts[0],
Value: &opts[1],
}
dnsOptions = append(dnsOptions, dnsOpt)
}
dns.Options = dnsOptions
}
return kubeContainer, kubeVolumes, &dns, nil
}

// ocicniPortMappingToContainerPort takes an ocicni portmapping and converts
Expand Down
26 changes: 26 additions & 0 deletions pkg/specgen/generate/kube/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package kube
import (
"context"
"fmt"
"net"
"strings"

"github.com/containers/common/pkg/parse"
Expand Down Expand Up @@ -44,6 +45,31 @@ func ToPodGen(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec)
podPorts := getPodPorts(podYAML.Spec.Containers)
p.PortMappings = podPorts

if dnsConfig := podYAML.Spec.DNSConfig; dnsConfig != nil {
// name servers
if dnsServers := dnsConfig.Nameservers; len(dnsServers) > 0 {
servers := make([]net.IP, 0)
for _, server := range dnsServers {
servers = append(servers, net.ParseIP(server))
}
p.DNSServer = servers
}
// search domans
if domains := dnsConfig.Searches; len(domains) > 0 {
p.DNSSearch = domains
}
// dns options
if options := dnsConfig.Options; len(options) > 0 {
dnsOptions := make([]string, 0)
for _, opts := range options {
d := opts.Name
if opts.Value != nil {
d += ":" + *opts.Value
}
dnsOptions = append(dnsOptions, d)
}
}
}
return p, nil
}

Expand Down
63 changes: 63 additions & 0 deletions test/e2e/generate_kube_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -540,4 +540,67 @@ var _ = Describe("Podman generate kube", func() {
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).ToNot(Equal(0))
})

It("podman generate kube on a container with dns options", func() {
top := podmanTest.Podman([]string{"run", "-dt", "--name", "top", "--dns", "8.8.8.8", "--dns-search", "foobar.com", "--dns-opt", "color:blue", ALPINE, "top"})
top.WaitWithDefaultTimeout()
Expect(top.ExitCode()).To(BeZero())

kube := podmanTest.Podman([]string{"generate", "kube", "top"})
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).To(Equal(0))

pod := new(v1.Pod)
err := yaml.Unmarshal(kube.Out.Contents(), pod)
Expect(err).To(BeNil())

Expect(StringInSlice("8.8.8.8", pod.Spec.DNSConfig.Nameservers)).To(BeTrue())
Expect(StringInSlice("foobar.com", pod.Spec.DNSConfig.Searches)).To(BeTrue())
Expect(len(pod.Spec.DNSConfig.Options)).To(BeNumerically(">", 0))
Expect(pod.Spec.DNSConfig.Options[0].Name).To(Equal("color"))
Expect(*pod.Spec.DNSConfig.Options[0].Value).To(Equal("blue"))
})

It("podman generate kube multiple contianer dns servers and options are cumulative", func() {
top1 := podmanTest.Podman([]string{"run", "-dt", "--name", "top1", "--dns", "8.8.8.8", "--dns-search", "foobar.com", ALPINE, "top"})
top1.WaitWithDefaultTimeout()
Expect(top1.ExitCode()).To(BeZero())

top2 := podmanTest.Podman([]string{"run", "-dt", "--name", "top2", "--dns", "8.7.7.7", "--dns-search", "homer.com", ALPINE, "top"})
top2.WaitWithDefaultTimeout()
Expect(top2.ExitCode()).To(BeZero())

kube := podmanTest.Podman([]string{"generate", "kube", "top1", "top2"})
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).To(Equal(0))

pod := new(v1.Pod)
err := yaml.Unmarshal(kube.Out.Contents(), pod)
Expect(err).To(BeNil())

Expect(StringInSlice("8.8.8.8", pod.Spec.DNSConfig.Nameservers)).To(BeTrue())
Expect(StringInSlice("8.7.7.7", pod.Spec.DNSConfig.Nameservers)).To(BeTrue())
Expect(StringInSlice("foobar.com", pod.Spec.DNSConfig.Searches)).To(BeTrue())
Expect(StringInSlice("homer.com", pod.Spec.DNSConfig.Searches)).To(BeTrue())
})

It("podman generate kube on a pod with dns options", func() {
top := podmanTest.Podman([]string{"run", "--pod", "new:pod1", "-dt", "--name", "top", "--dns", "8.8.8.8", "--dns-search", "foobar.com", "--dns-opt", "color:blue", ALPINE, "top"})
top.WaitWithDefaultTimeout()
Expect(top.ExitCode()).To(BeZero())

kube := podmanTest.Podman([]string{"generate", "kube", "pod1"})
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).To(Equal(0))

pod := new(v1.Pod)
err := yaml.Unmarshal(kube.Out.Contents(), pod)
Expect(err).To(BeNil())

Expect(StringInSlice("8.8.8.8", pod.Spec.DNSConfig.Nameservers)).To(BeTrue())
Expect(StringInSlice("foobar.com", pod.Spec.DNSConfig.Searches)).To(BeTrue())
Expect(len(pod.Spec.DNSConfig.Options)).To(BeNumerically(">", 0))
Expect(pod.Spec.DNSConfig.Options[0].Name).To(Equal("color"))
Expect(*pod.Spec.DNSConfig.Options[0].Value).To(Equal("blue"))
})
})

0 comments on commit ca0dd76

Please sign in to comment.