add --mac-address to podman play kube

Add a new --mac-address flag to podman play kube. This is used to specify
a static MAC address which should be used for the pod. This option can be
specified several times because play kube can create more than one pod.

Fixes #9731

Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
This commit is contained in:
Paul Holzinger
2021-05-04 17:06:57 +02:00
parent dea6189982
commit fb7d16c7a8
10 changed files with 92 additions and 16 deletions

View File

@ -2,6 +2,7 @@ package pods
import ( import (
"fmt" "fmt"
"net"
"os" "os"
"github.com/containers/common/pkg/auth" "github.com/containers/common/pkg/auth"
@ -27,6 +28,7 @@ type playKubeOptionsWrapper struct {
} }
var ( var (
macs []string
// https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/ // https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/
defaultSeccompRoot = "/var/lib/kubelet/seccomp" defaultSeccompRoot = "/var/lib/kubelet/seccomp"
kubeOptions = playKubeOptionsWrapper{} kubeOptions = playKubeOptionsWrapper{}
@ -61,6 +63,10 @@ func init() {
flags.StringVar(&kubeOptions.CredentialsCLI, credsFlagName, "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") flags.StringVar(&kubeOptions.CredentialsCLI, credsFlagName, "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry")
_ = kubeCmd.RegisterFlagCompletionFunc(credsFlagName, completion.AutocompleteNone) _ = kubeCmd.RegisterFlagCompletionFunc(credsFlagName, completion.AutocompleteNone)
staticMACFlagName := "mac-address"
flags.StringSliceVar(&macs, staticMACFlagName, nil, "Static MAC addresses to assign to the pods")
_ = kubeCmd.RegisterFlagCompletionFunc(staticMACFlagName, completion.AutocompleteNone)
networkFlagName := "network" networkFlagName := "network"
flags.StringVar(&kubeOptions.Network, networkFlagName, "", "Connect pod to CNI network(s)") flags.StringVar(&kubeOptions.Network, networkFlagName, "", "Connect pod to CNI network(s)")
_ = kubeCmd.RegisterFlagCompletionFunc(networkFlagName, common.AutocompleteNetworkFlag) _ = kubeCmd.RegisterFlagCompletionFunc(networkFlagName, common.AutocompleteNetworkFlag)
@ -128,6 +134,15 @@ func kube(cmd *cobra.Command, args []string) error {
if yamlfile == "-" { if yamlfile == "-" {
yamlfile = "/dev/stdin" yamlfile = "/dev/stdin"
} }
for _, mac := range macs {
m, err := net.ParseMAC(mac)
if err != nil {
return err
}
kubeOptions.StaticMACs = append(kubeOptions.StaticMACs, m)
}
report, err := registry.ContainerEngine().PlayKube(registry.GetContext(), yamlfile, kubeOptions.PlayKubeOptions) report, err := registry.ContainerEngine().PlayKube(registry.GetContext(), yamlfile, kubeOptions.PlayKubeOptions)
if err != nil { if err != nil {
return err return err

View File

@ -70,6 +70,10 @@ Assign a static ip address to the pod. This option can be specified several time
Set logging driver for all created containers. Set logging driver for all created containers.
#### **\-\-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.
#### **\-\-network**=*networks*, **\-\-net** #### **\-\-network**=*networks*, **\-\-net**
A comma-separated list of the names of CNI networks the pod should join. A comma-separated list of the names of CNI networks the pod should join.

View File

@ -21,11 +21,12 @@ func PlayKube(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime) runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder) decoder := r.Context().Value("decoder").(*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"`
Start bool `schema:"start"` Start bool `schema:"start"`
StaticIPs []string `schema:"staticIPs"` StaticIPs []string `schema:"staticIPs"`
StaticMACs []string `schema:"staticMACs"`
}{ }{
TLSVerify: true, TLSVerify: true,
Start: true, Start: true,
@ -48,6 +49,17 @@ func PlayKube(w http.ResponseWriter, r *http.Request) {
staticIPs = append(staticIPs, ip) staticIPs = append(staticIPs, ip)
} }
staticMACs := make([]net.HardwareAddr, 0, len(query.StaticMACs))
for _, macString := range query.StaticMACs {
mac, err := net.ParseMAC(macString)
if err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
err)
return
}
staticMACs = append(staticMACs, mac)
}
// Fetch the K8s YAML file from the body, and copy it to a temp file. // Fetch the K8s YAML file from the body, and copy it to a temp file.
tmpfile, err := ioutil.TempFile("", "libpod-play-kube.yml") tmpfile, err := ioutil.TempFile("", "libpod-play-kube.yml")
if err != nil { if err != nil {
@ -78,13 +90,14 @@ func PlayKube(w http.ResponseWriter, r *http.Request) {
containerEngine := abi.ContainerEngine{Libpod: runtime} containerEngine := abi.ContainerEngine{Libpod: runtime}
options := entities.PlayKubeOptions{ options := entities.PlayKubeOptions{
Authfile: authfile, Authfile: authfile,
Username: username, Username: username,
Password: password, Password: password,
Network: query.Network, Network: query.Network,
Quiet: true, Quiet: true,
LogDriver: query.LogDriver, LogDriver: query.LogDriver,
StaticIPs: staticIPs, StaticIPs: staticIPs,
StaticMACs: staticMACs,
} }
if _, found := r.URL.Query()["tlsVerify"]; found { if _, found := r.URL.Query()["tlsVerify"]; found {
options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)

View File

@ -40,6 +40,12 @@ func (s *APIServer) registerPlayHandlers(r *mux.Router) error {
// description: Static IPs used for the pods. // description: Static IPs used for the pods.
// items: // items:
// type: string // type: string
// - in: query
// name: staticMACs
// type: array
// description: Static MACs used for the pods.
// items:
// type: string
// - in: body // - in: body
// name: request // name: request
// description: Kubernetes YAML file. // description: Kubernetes YAML file.

View File

@ -27,6 +27,8 @@ type KubeOptions struct {
SeccompProfileRoot *string SeccompProfileRoot *string
// StaticIPs - Static IP address used by the pod(s). // StaticIPs - Static IP address used by the pod(s).
StaticIPs *[]net.IP StaticIPs *[]net.IP
// StaticMACs - Static MAC address used by the pod(s).
StaticMACs *[]net.HardwareAddr
// ConfigMaps - slice of pathnames to kubernetes configmap YAMLs. // ConfigMaps - slice of pathnames to kubernetes configmap YAMLs.
ConfigMaps *[]string ConfigMaps *[]string
// LogDriver for the container. For example: journald // LogDriver for the container. For example: journald

View File

@ -181,6 +181,22 @@ func (o *KubeOptions) GetStaticIPs() []net.IP {
return *o.StaticIPs return *o.StaticIPs
} }
// WithStaticMACs
func (o *KubeOptions) WithStaticMACs(value []net.HardwareAddr) *KubeOptions {
v := &value
o.StaticMACs = v
return o
}
// GetStaticMACs
func (o *KubeOptions) GetStaticMACs() []net.HardwareAddr {
var staticMACs []net.HardwareAddr
if o.StaticMACs == nil {
return staticMACs
}
return *o.StaticMACs
}
// WithConfigMaps // WithConfigMaps
func (o *KubeOptions) WithConfigMaps(value []string) *KubeOptions { func (o *KubeOptions) WithConfigMaps(value []string) *KubeOptions {
v := &value v := &value

View File

@ -30,6 +30,8 @@ type PlayKubeOptions struct {
SeccompProfileRoot string SeccompProfileRoot string
// StaticIPs - Static IP address used by the pod(s). // StaticIPs - Static IP address used by the pod(s).
StaticIPs []net.IP StaticIPs []net.IP
// StaticMACs - Static MAC address used by the pod(s).
StaticMACs []net.HardwareAddr
// ConfigMaps - slice of pathnames to kubernetes configmap YAMLs. // ConfigMaps - slice of pathnames to kubernetes configmap YAMLs.
ConfigMaps []string ConfigMaps []string
// LogDriver for the container. For example: journald // LogDriver for the container. For example: journald

View File

@ -199,11 +199,17 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
} }
if len(options.StaticIPs) > *ipIndex { if len(options.StaticIPs) > *ipIndex {
p.StaticIP = &options.StaticIPs[*ipIndex] p.StaticIP = &options.StaticIPs[*ipIndex]
*ipIndex++
} else if len(options.StaticIPs) > 0 { } else if len(options.StaticIPs) > 0 {
// only warn if the user has set at least one ip ip // only warn if the user has set at least one ip
logrus.Warn("No more static ips left using a random one") logrus.Warn("No more static ips left using a random one")
} }
if len(options.StaticMACs) > *ipIndex {
p.StaticMAC = &options.StaticMACs[*ipIndex]
} 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++
// Create the Pod // Create the Pod
pod, err := generate.MakePod(p, ic.Libpod) pod, err := generate.MakePod(p, ic.Libpod)

View File

@ -11,7 +11,8 @@ 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).WithStaticIPs(opts.StaticIPs) options.WithLogDriver(opts.LogDriver).WithNetwork(opts.Network).WithSeccompProfileRoot(opts.SeccompProfileRoot)
options.WithStaticIPs(opts.StaticIPs).WithStaticMACs(opts.StaticMACs)
if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined { if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined {
options.WithSkipTLSVerify(s == types.OptionalBoolTrue) options.WithSkipTLSVerify(s == types.OptionalBoolTrue)

View File

@ -1717,7 +1717,7 @@ spec:
} }
}) })
It("podman play kube --ip", func() { It("podman play kube --ip and --mac-address", func() {
var i, numReplicas int32 var i, numReplicas int32
numReplicas = 3 numReplicas = 3
deployment := getDeployment(withReplicas(numReplicas)) deployment := getDeployment(withReplicas(numReplicas))
@ -1735,6 +1735,10 @@ spec:
for _, ip := range ips { for _, ip := range ips {
playArgs = append(playArgs, "--ip", ip) playArgs = append(playArgs, "--ip", ip)
} }
macs := []string{"e8:d8:82:c9:80:40", "e8:d8:82:c9:80:50", "e8:d8:82:c9:80:60"}
for _, mac := range macs {
playArgs = append(playArgs, "--mac-address", mac)
}
kube := podmanTest.Podman(append(playArgs, kubeYaml)) kube := podmanTest.Podman(append(playArgs, kubeYaml))
kube.WaitWithDefaultTimeout() kube.WaitWithDefaultTimeout()
@ -1747,6 +1751,13 @@ spec:
Expect(inspect.ExitCode()).To(Equal(0)) Expect(inspect.ExitCode()).To(Equal(0))
Expect(inspect.OutputToString()).To(Equal(ips[i])) Expect(inspect.OutputToString()).To(Equal(ips[i]))
} }
for i = 0; i < numReplicas; i++ {
inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(&podNames[i]), "--format", "{{ .NetworkSettings.Networks." + net + ".MacAddress }}"})
inspect.WaitWithDefaultTimeout()
Expect(inspect.ExitCode()).To(Equal(0))
Expect(inspect.OutputToString()).To(Equal(macs[i]))
}
}) })
It("podman play kube test with network portbindings", func() { It("podman play kube test with network portbindings", func() {