Honor custom DNS in play|generate kube

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: #9132

Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
baude 2021-01-28 15:20:15 -06:00
parent 2ee034c1e6
commit ca0dd76bf3
3 changed files with 187 additions and 14 deletions

View File

@ -171,9 +171,10 @@ func (p *Pod) podWithContainers(containers []*Container, ports []v1.ContainerPor
deDupPodVolumes := make(map[string]*v1.Volume) deDupPodVolumes := make(map[string]*v1.Volume)
first := true first := true
podContainers := make([]v1.Container, 0, len(containers)) podContainers := make([]v1.Container, 0, len(containers))
dnsInfo := v1.PodDNSConfig{}
for _, ctr := range containers { for _, ctr := range containers {
if !ctr.IsInfra() { if !ctr.IsInfra() {
ctr, volumes, err := containerToV1Container(ctr) ctr, volumes, _, err := containerToV1Container(ctr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -196,6 +197,22 @@ func (p *Pod) podWithContainers(containers []*Container, ports []v1.ContainerPor
vol := vol vol := vol
deDupPodVolumes[vol.Name] = &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)) podVolumes := make([]v1.Volume, 0, len(deDupPodVolumes))
@ -203,10 +220,10 @@ func (p *Pod) podWithContainers(containers []*Container, ports []v1.ContainerPor
podVolumes = append(podVolumes, *vol) 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{ tm := v12.TypeMeta{
Kind: "Pod", Kind: "Pod",
APIVersion: "v1", APIVersion: "v1",
@ -228,6 +245,9 @@ func addContainersAndVolumesToPodObject(containers []v1.Container, volumes []v1.
Containers: containers, Containers: containers,
Volumes: volumes, Volumes: volumes,
} }
if dnsOptions != nil {
ps.DNSConfig = dnsOptions
}
p := v1.Pod{ p := v1.Pod{
TypeMeta: tm, TypeMeta: tm,
ObjectMeta: om, ObjectMeta: om,
@ -241,32 +261,65 @@ func addContainersAndVolumesToPodObject(containers []v1.Container, volumes []v1.
func simplePodWithV1Containers(ctrs []*Container) (*v1.Pod, error) { func simplePodWithV1Containers(ctrs []*Container) (*v1.Pod, error) {
kubeCtrs := make([]v1.Container, 0, len(ctrs)) kubeCtrs := make([]v1.Container, 0, len(ctrs))
kubeVolumes := make([]v1.Volume, 0) kubeVolumes := make([]v1.Volume, 0)
podDNS := v1.PodDNSConfig{}
for _, ctr := range ctrs { for _, ctr := range ctrs {
kubeCtr, kubeVols, err := containerToV1Container(ctr) kubeCtr, kubeVols, ctrDNS, err := containerToV1Container(ctr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
kubeCtrs = append(kubeCtrs, kubeCtr) kubeCtrs = append(kubeCtrs, kubeCtr)
kubeVolumes = append(kubeVolumes, kubeVols...) 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 // containerToV1Container converts information we know about a libpod container
// to a V1.Container specification. // 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{} kubeContainer := v1.Container{}
kubeVolumes := []v1.Volume{} kubeVolumes := []v1.Volume{}
kubeSec, err := generateKubeSecurityContext(c) kubeSec, err := generateKubeSecurityContext(c)
if err != nil { if err != nil {
return kubeContainer, kubeVolumes, err return kubeContainer, kubeVolumes, nil, err
} }
if len(c.config.Spec.Linux.Devices) > 0 { if len(c.config.Spec.Linux.Devices) > 0 {
// TODO Enable when we can support devices and their names // TODO Enable when we can support devices and their names
kubeContainer.VolumeDevices = generateKubeVolumeDeviceFromLinuxDevice(c.Spec().Linux.Devices) 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 { if len(c.config.UserVolumes) > 0 {
@ -274,7 +327,7 @@ func containerToV1Container(c *Container) (v1.Container, []v1.Volume, error) {
// Volume names need to be coordinated "globally" in the kube files. // Volume names need to be coordinated "globally" in the kube files.
volumeMounts, volumes, err := libpodMountsToKubeVolumeMounts(c) volumeMounts, volumes, err := libpodMountsToKubeVolumeMounts(c)
if err != nil { if err != nil {
return kubeContainer, kubeVolumes, err return kubeContainer, kubeVolumes, nil, err
} }
kubeContainer.VolumeMounts = volumeMounts kubeContainer.VolumeMounts = volumeMounts
kubeVolumes = append(kubeVolumes, volumes...) kubeVolumes = append(kubeVolumes, volumes...)
@ -282,16 +335,16 @@ func containerToV1Container(c *Container) (v1.Container, []v1.Volume, error) {
envVariables, err := libpodEnvVarsToKubeEnvVars(c.config.Spec.Process.Env) envVariables, err := libpodEnvVarsToKubeEnvVars(c.config.Spec.Process.Env)
if err != nil { if err != nil {
return kubeContainer, kubeVolumes, err return kubeContainer, kubeVolumes, nil, err
} }
portmappings, err := c.PortMappings() portmappings, err := c.PortMappings()
if err != nil { if err != nil {
return kubeContainer, kubeVolumes, err return kubeContainer, kubeVolumes, nil, err
} }
ports, err := ocicniPortMappingToContainerPort(portmappings) ports, err := ocicniPortMappingToContainerPort(portmappings)
if err != nil { if err != nil {
return kubeContainer, kubeVolumes, err return kubeContainer, kubeVolumes, nil, err
} }
containerCommands := c.Command() containerCommands := c.Command()
@ -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 // ocicniPortMappingToContainerPort takes an ocicni portmapping and converts

View File

@ -3,6 +3,7 @@ package kube
import ( import (
"context" "context"
"fmt" "fmt"
"net"
"strings" "strings"
"github.com/containers/common/pkg/parse" "github.com/containers/common/pkg/parse"
@ -44,6 +45,31 @@ func ToPodGen(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec)
podPorts := getPodPorts(podYAML.Spec.Containers) podPorts := getPodPorts(podYAML.Spec.Containers)
p.PortMappings = podPorts 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 return p, nil
} }

View File

@ -540,4 +540,67 @@ var _ = Describe("Podman generate kube", func() {
kube.WaitWithDefaultTimeout() kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).ToNot(Equal(0)) 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"))
})
}) })