play kube add support for multiple networks

Allow the same --network options for play kube as for podman run/create.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
Paul Holzinger
2021-12-13 15:56:20 +01:00
parent 535818414c
commit 3e9af2029f
11 changed files with 108 additions and 50 deletions

View File

@ -69,7 +69,7 @@ func init() {
_ = kubeCmd.RegisterFlagCompletionFunc(staticMACFlagName, completion.AutocompleteNone) _ = kubeCmd.RegisterFlagCompletionFunc(staticMACFlagName, completion.AutocompleteNone)
networkFlagName := "network" networkFlagName := "network"
flags.StringVar(&kubeOptions.Network, networkFlagName, "", "Connect pod to CNI network(s)") flags.StringArrayVar(&kubeOptions.Networks, networkFlagName, nil, "Connect pod to network(s) or network mode")
_ = kubeCmd.RegisterFlagCompletionFunc(networkFlagName, common.AutocompleteNetworkFlag) _ = kubeCmd.RegisterFlagCompletionFunc(networkFlagName, common.AutocompleteNetworkFlag)
staticIPFlagName := "ip" staticIPFlagName := "ip"

View File

@ -142,6 +142,7 @@ removed. Any volumes created are left intact.
#### **--ip**=*IP address* #### **--ip**=*IP address*
Assign a static ip address to the pod. This option can be specified several times when play kube creates more than one pod. Assign a static ip address to the pod. This option can be specified several times when play kube creates more than one pod.
Note: When joining multiple networks you should use the **--network name:ip=\<ip\>** syntax.
#### **--log-driver**=driver #### **--log-driver**=driver
@ -167,15 +168,24 @@ This option is currently supported only by the **journald** log driver.
#### **--mac-address**=*MAC address* #### **--mac-address**=*MAC address*
Assign a static mac address to the pod. This option can be specified several times when play kube creates more than one pod. Assign a static mac address to the pod. This option can be specified several times when play kube creates more than one pod.
Note: When joining multiple networks you should use the **--network name:mac=\<mac\>** syntax.
#### **--network**=*mode*, **--net** #### **--network**=*mode*, **--net**
Change the network mode of the pod. The host and bridge network mode should be configured in the yaml file. Change the network mode of the pod. The host network mode should be configured in the YAML file.
Valid _mode_ values are: Valid _mode_ values are:
- **bridge[:OPTIONS,...]**: Create a network stack on the default bridge. This is the default for rootfull containers. It is possible to specify these additional options:
- **alias=name**: Add network-scoped alias for the container.
- **ip=IPv4**: Specify a static ipv4 address for this container.
- **ip=IPv6**: Specify a static ipv6 address for this container.
- **mac=MAC**: Specify a static mac address address for this container.
- **interface_name**: Specify a name for the created network interface inside the container.
For example to set a static ipv4 address and a static mac address, use `--network bridge:ip=10.88.0.10,mac=44:33:22:11:00:99`.
- \<network name or ID\>[:OPTIONS,...]: Connect to a user-defined network; this is the network name or ID from a network created by **[podman network create](podman-network-create.1.md)**. Using the network name implies the bridge network mode. It is possible to specify the same options described under the bridge mode above. You can use the **--network** option multiple times to specify additional networks.
- **none**: Create a network namespace for the container but do not configure network interfaces for it, thus the container has no network connectivity. - **none**: Create a network namespace for the container but do not configure network interfaces for it, thus the container has no network connectivity.
- **container:**_id_: Reuse another container's network stack. - **container:**_id_: Reuse another container's network stack.
- **network**: Connect to a user-defined network, multiple networks should be comma-separated.
- **ns:**_path_: Path to a network namespace to join. - **ns:**_path_: Path to a network namespace to join.
- **private**: Create a new namespace for the container. This will use the **bridge** mode for rootfull containers and **slirp4netns** for rootless ones. - **private**: Create a new namespace for the container. This will use the **bridge** mode for rootfull containers and **slirp4netns** for rootless ones.
- **slirp4netns[:OPTIONS,...]**: use **slirp4netns**(1) to create a user network stack. This is the default for rootless containers. It is possible to specify these additional options: - **slirp4netns[:OPTIONS,...]**: use **slirp4netns**(1) to create a user network stack. This is the default for rootless containers. It is possible to specify these additional options:
@ -253,9 +263,9 @@ $ podman play kube demo.yml --configmap configmap-foo.yml --configmap configmap-
52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6 52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
``` ```
CNI network(s) can be specified as comma-separated list using ``--network`` Create a pod connected to two networks (called net1 and net2) with a static ip
``` ```
$ podman play kube demo.yml --network cni1,cni2 $ podman play kube demo.yml --network net1:ip=10.89.1.5 --network net2:ip=10.89.10.10
52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6 52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
``` ```

View File

@ -23,7 +23,7 @@ func PlayKube(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
query := struct { query := struct {
Network string `schema:"network"` Network []string `schema:"network"`
TLSVerify bool `schema:"tlsVerify"` TLSVerify bool `schema:"tlsVerify"`
LogDriver string `schema:"logDriver"` LogDriver string `schema:"logDriver"`
LogOptions []string `schema:"logOptions"` LogOptions []string `schema:"logOptions"`
@ -103,7 +103,7 @@ func PlayKube(w http.ResponseWriter, r *http.Request) {
Authfile: authfile, Authfile: authfile,
Username: username, Username: username,
Password: password, Password: password,
Network: query.Network, Networks: query.Network,
NoHosts: query.NoHosts, NoHosts: query.NoHosts,
Quiet: true, Quiet: true,
LogDriver: query.LogDriver, LogDriver: query.LogDriver,

View File

@ -18,8 +18,10 @@ func (s *APIServer) registerPlayHandlers(r *mux.Router) error {
// parameters: // parameters:
// - in: query // - in: query
// name: network // name: network
// type: array
// description: USe the network mode or specify an array of networks.
// items:
// type: string // type: string
// description: Connect the pod to this network.
// - in: query // - in: query
// name: tlsVerify // name: tlsVerify
// type: boolean // type: boolean

View File

@ -15,8 +15,8 @@ type KubeOptions struct {
Username *string Username *string
// Password for authenticating against the registry. // Password for authenticating against the registry.
Password *string Password *string
// Network - name of the CNI network to connect to. // Network - name of the networks to connect to.
Network *string Network *[]string
// NoHosts - do not generate /etc/hosts file in pod's containers // NoHosts - do not generate /etc/hosts file in pod's containers
NoHosts *bool NoHosts *bool
// Quiet - suppress output when pulling images. // Quiet - suppress output when pulling images.

View File

@ -79,15 +79,15 @@ func (o *KubeOptions) GetPassword() string {
} }
// WithNetwork set field Network to given value // WithNetwork set field Network to given value
func (o *KubeOptions) WithNetwork(value string) *KubeOptions { func (o *KubeOptions) WithNetwork(value []string) *KubeOptions {
o.Network = &value o.Network = &value
return o return o
} }
// GetNetwork returns value of field Network // GetNetwork returns value of field Network
func (o *KubeOptions) GetNetwork() string { func (o *KubeOptions) GetNetwork() []string {
if o.Network == nil { if o.Network == nil {
var z string var z []string
return z return z
} }
return *o.Network return *o.Network

View File

@ -26,8 +26,8 @@ type PlayKubeOptions struct {
Username string Username string
// Password for authenticating against the registry. // Password for authenticating against the registry.
Password string Password string
// Network - name of the CNI network to connect to. // Networks - name of the network to connect to.
Network string Networks []string
// Quiet - suppress output when pulling images. // Quiet - suppress output when pulling images.
Quiet bool Quiet bool
// SignaturePolicy - path to a signature-policy file. // SignaturePolicy - path to a signature-policy file.

View File

@ -17,6 +17,7 @@ import (
"github.com/containers/image/v5/types" "github.com/containers/image/v5/types"
"github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/define"
nettypes "github.com/containers/podman/v3/libpod/network/types"
"github.com/containers/podman/v3/pkg/autoupdate" "github.com/containers/podman/v3/pkg/autoupdate"
"github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/specgen"
@ -195,8 +196,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
return nil, err return nil, err
} }
if options.Network != "" { ns, networks, netOpts, err := specgen.ParseNetworkFlag(options.Networks)
ns, networks, netOpts, err := specgen.ParseNetworkFlag([]string{options.Network})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -206,28 +206,41 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
} }
podOpt.Net.Network = ns podOpt.Net.Network = ns
if len(networks) > 0 {
podOpt.Net.Networks = networks podOpt.Net.Networks = networks
}
if len(netOpts) > 0 {
podOpt.Net.NetworkOptions = netOpts podOpt.Net.NetworkOptions = netOpts
}
}
// FIXME This is very hard to support properly // FIXME This is very hard to support properly with a good ux
// if len(options.StaticIPs) > *ipIndex { if len(options.StaticIPs) > *ipIndex {
// podOpt.Net.StaticIP = &options.StaticIPs[*ipIndex] if !podOpt.Net.Network.IsBridge() {
// } else if len(options.StaticIPs) > 0 { errors.Wrap(define.ErrInvalidArg, "static ip addresses can only be set when the network mode is bridge")
// // only warn if the user has set at least one ip }
// logrus.Warn("No more static ips left using a random one") if len(podOpt.Net.Networks) != 1 {
// } return nil, errors.Wrap(define.ErrInvalidArg, "cannot set static ip addresses for more than network, use netname:ip=<ip> syntax to specify ips for more than network")
// if len(options.StaticMACs) > *ipIndex { }
// podOpt.Net.StaticMAC = &options.StaticMACs[*ipIndex] for name, netOpts := range podOpt.Net.Networks {
// } else if len(options.StaticIPs) > 0 { netOpts.StaticIPs = append(netOpts.StaticIPs, options.StaticIPs[*ipIndex])
// // only warn if the user has set at least one mac podOpt.Net.Networks[name] = netOpts
// logrus.Warn("No more static macs left using a random one") }
// } } else if len(options.StaticIPs) > 0 {
// *ipIndex++ // only warn if the user has set at least one ip
logrus.Warn("No more static ips left using a random one")
}
if len(options.StaticMACs) > *ipIndex {
if !podOpt.Net.Network.IsBridge() {
errors.Wrap(define.ErrInvalidArg, "static mac address can only be set when the network mode is bridge")
}
if len(podOpt.Net.Networks) != 1 {
return nil, errors.Wrap(define.ErrInvalidArg, "cannot set static mac address for more than network, use netname:mac=<mac> syntax to specify mac for more than network")
}
for name, netOpts := range podOpt.Net.Networks {
netOpts.StaticMAC = nettypes.HardwareAddr(options.StaticMACs[*ipIndex])
podOpt.Net.Networks[name] = netOpts
}
} else if len(options.StaticIPs) > 0 {
// only warn if the user has set at least one mac
logrus.Warn("No more static macs left using a random one")
}
*ipIndex++
p := specgen.NewPodSpecGenerator() p := specgen.NewPodSpecGenerator()
if err != nil { if err != nil {

View File

@ -11,7 +11,7 @@ import (
func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, opts entities.PlayKubeOptions) (*entities.PlayKubeReport, error) { func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, opts entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
options := new(play.KubeOptions).WithAuthfile(opts.Authfile).WithUsername(opts.Username).WithPassword(opts.Password) options := new(play.KubeOptions).WithAuthfile(opts.Authfile).WithUsername(opts.Username).WithPassword(opts.Password)
options.WithCertDir(opts.CertDir).WithQuiet(opts.Quiet).WithSignaturePolicy(opts.SignaturePolicy).WithConfigMaps(opts.ConfigMaps) options.WithCertDir(opts.CertDir).WithQuiet(opts.Quiet).WithSignaturePolicy(opts.SignaturePolicy).WithConfigMaps(opts.ConfigMaps)
options.WithLogDriver(opts.LogDriver).WithNetwork(opts.Network).WithSeccompProfileRoot(opts.SeccompProfileRoot) options.WithLogDriver(opts.LogDriver).WithNetwork(opts.Networks).WithSeccompProfileRoot(opts.SeccompProfileRoot)
options.WithStaticIPs(opts.StaticIPs).WithStaticMACs(opts.StaticMACs) options.WithStaticIPs(opts.StaticIPs).WithStaticMACs(opts.StaticMACs)
if len(opts.LogOptions) > 0 { if len(opts.LogOptions) > 0 {
options.WithLogOptions(opts.LogOptions) options.WithLogOptions(opts.LogOptions)

View File

@ -2136,6 +2136,41 @@ spec:
} }
}) })
It("podman play kube with multiple networks", func() {
ctr := getCtr(withImage(ALPINE))
pod := getPod(withCtr(ctr))
err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
net1 := "net1" + stringid.GenerateNonCryptoID()
net2 := "net2" + stringid.GenerateNonCryptoID()
net := podmanTest.Podman([]string{"network", "create", "--subnet", "10.0.11.0/24", net1})
net.WaitWithDefaultTimeout()
defer podmanTest.removeCNINetwork(net1)
Expect(net).Should(Exit(0))
net = podmanTest.Podman([]string{"network", "create", "--subnet", "10.0.12.0/24", net2})
net.WaitWithDefaultTimeout()
defer podmanTest.removeCNINetwork(net2)
Expect(net).Should(Exit(0))
ip1 := "10.0.11.5"
ip2 := "10.0.12.10"
kube := podmanTest.Podman([]string{"play", "kube", "--network", net1 + ":ip=" + ip1, "--network", net2 + ":ip=" + ip2, kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))
inspect := podmanTest.Podman([]string{"exec", getCtrNameInPod(pod), "ip", "addr"})
inspect.WaitWithDefaultTimeout()
Expect(inspect).Should(Exit(0))
Expect(inspect.OutputToString()).To(ContainSubstring(ip1))
Expect(inspect.OutputToString()).To(ContainSubstring(ip2))
Expect(inspect.OutputToString()).To(ContainSubstring("eth0"))
Expect(inspect.OutputToString()).To(ContainSubstring("eth1"))
})
It("podman play kube test with network portbindings", func() { It("podman play kube test with network portbindings", func() {
ip := "127.0.0.100" ip := "127.0.0.100"
port := "5000" port := "5000"

View File

@ -104,8 +104,6 @@ RELABEL="system_u:object_r:container_file_t:s0"
TESTDIR=$PODMAN_TMPDIR/testdir TESTDIR=$PODMAN_TMPDIR/testdir
mkdir -p $TESTDIR mkdir -p $TESTDIR
echo "$testYaml" | sed "s|TESTDIR|${TESTDIR}|g" > $PODMAN_TMPDIR/test.yaml echo "$testYaml" | sed "s|TESTDIR|${TESTDIR}|g" > $PODMAN_TMPDIR/test.yaml
run_podman 125 play kube --network bridge $PODMAN_TMPDIR/test.yaml
is "$output" ".*invalid value passed to --network: bridge or host networking must be configured in YAML" "podman plan-network should fail with --network host"
run_podman 125 play kube --network host $PODMAN_TMPDIR/test.yaml run_podman 125 play kube --network host $PODMAN_TMPDIR/test.yaml
is "$output" ".*invalid value passed to --network: bridge or host networking must be configured in YAML" "podman plan-network should fail with --network host" is "$output" ".*invalid value passed to --network: bridge or host networking must be configured in YAML" "podman plan-network should fail with --network host"
run_podman play kube --network slirp4netns:port_handler=slirp4netns $PODMAN_TMPDIR/test.yaml run_podman play kube --network slirp4netns:port_handler=slirp4netns $PODMAN_TMPDIR/test.yaml