mirror of
https://github.com/containers/podman.git
synced 2025-06-01 17:17:47 +08:00
Merge pull request #1623 from mheon/static_ip
Add ability to specify static IPs with --ip flag
This commit is contained in:
@ -248,6 +248,10 @@ var createFlags = []cli.Flag{
|
||||
Name: "interactive, i",
|
||||
Usage: "Keep STDIN open even if not attached",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "ip",
|
||||
Usage: "Specify a static IPv4 address for the container",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "ipc",
|
||||
Usage: "IPC namespace to use",
|
||||
|
@ -1512,6 +1512,7 @@ _podman_container_run() {
|
||||
--hostname -h
|
||||
--image-volume
|
||||
--init-path
|
||||
--ip
|
||||
--ipc
|
||||
--kernel-memory
|
||||
--label-file
|
||||
|
@ -286,7 +286,9 @@ Not implemented
|
||||
|
||||
**--ip**=""
|
||||
|
||||
Not implemented
|
||||
Specify a static IP address for the container, for example '10.88.64.128'.
|
||||
Can only be used if no additional CNI networks to join were specified via '--network=<network-name>', and if the container is not joining another container's network namespace via '--network=container:<name|id>'.
|
||||
The address must be within the default CNI network's pool (default 10.88.0.0/16).
|
||||
|
||||
**--ipc**=""
|
||||
|
||||
|
@ -297,7 +297,9 @@ Not implemented
|
||||
|
||||
**--ip**=""
|
||||
|
||||
Not implemented
|
||||
Specify a static IP address for the container, for example '10.88.64.128'.
|
||||
Can only be used if no additional CNI networks to join were specified via '--network=<network-name>', and if the container is not joining another container's network namespace via '--network=container:<name|id>'.
|
||||
The address must be within the default CNI network's pool (default 10.88.0.0/16).
|
||||
|
||||
**--ipc**=""
|
||||
|
||||
|
@ -269,9 +269,13 @@ type ContainerConfig struct {
|
||||
// Network Config
|
||||
|
||||
// CreateNetNS indicates that libpod should create and configure a new
|
||||
// network namespace for the container
|
||||
// This cannot be set if NetNsCtr is also set
|
||||
// network namespace for the container.
|
||||
// This cannot be set if NetNsCtr is also set.
|
||||
CreateNetNS bool `json:"createNetNS"`
|
||||
// StaticIP is a static IP to request for the container.
|
||||
// This cannot be set unless CreateNetNS is set.
|
||||
// If not set, the container will be dynamically assigned an IP by CNI.
|
||||
StaticIP net.IP `json:"staticIP"`
|
||||
// PortMappings are the ports forwarded to the container's network
|
||||
// namespace
|
||||
// These are not used unless CreateNetNS is true
|
||||
|
@ -1383,6 +1383,10 @@ func easyjson1dbef17bDecodeGithubComContainersLibpodLibpod2(in *jlexer.Lexer, ou
|
||||
}
|
||||
case "createNetNS":
|
||||
out.CreateNetNS = bool(in.Bool())
|
||||
case "staticIP":
|
||||
if data := in.UnsafeBytes(); in.Ok() {
|
||||
in.AddError((out.StaticIP).UnmarshalText(data))
|
||||
}
|
||||
case "portMappings":
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
@ -2005,6 +2009,16 @@ func easyjson1dbef17bEncodeGithubComContainersLibpodLibpod2(out *jwriter.Writer,
|
||||
}
|
||||
out.Bool(bool(in.CreateNetNS))
|
||||
}
|
||||
{
|
||||
const prefix string = ",\"staticIP\":"
|
||||
if first {
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
} else {
|
||||
out.RawString(prefix)
|
||||
}
|
||||
out.RawText((in.StaticIP).MarshalText())
|
||||
}
|
||||
if len(in.PortMappings) != 0 {
|
||||
const prefix string = ",\"portMappings\":"
|
||||
if first {
|
||||
|
@ -5,6 +5,7 @@ package libpod
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@ -25,8 +26,8 @@ import (
|
||||
)
|
||||
|
||||
// Get an OCICNI network config
|
||||
func getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.PortMapping) ocicni.PodNetwork {
|
||||
return ocicni.PodNetwork{
|
||||
func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.PortMapping, staticIP net.IP) ocicni.PodNetwork {
|
||||
network := ocicni.PodNetwork{
|
||||
Name: name,
|
||||
Namespace: name, // TODO is there something else we should put here? We don't know about Kube namespaces
|
||||
ID: id,
|
||||
@ -34,11 +35,21 @@ func getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.Po
|
||||
PortMappings: ports,
|
||||
Networks: networks,
|
||||
}
|
||||
|
||||
if staticIP != nil {
|
||||
defaultNetwork := r.netPlugin.GetDefaultNetworkName()
|
||||
|
||||
network.Networks = []string{defaultNetwork}
|
||||
network.NetworkConfig = make(map[string]ocicni.NetworkConfig)
|
||||
network.NetworkConfig[defaultNetwork] = ocicni.NetworkConfig{IP: staticIP.String()}
|
||||
}
|
||||
|
||||
return network
|
||||
}
|
||||
|
||||
// Create and configure a new network namespace for a container
|
||||
func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (err error) {
|
||||
podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings)
|
||||
podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings, ctr.config.StaticIP)
|
||||
|
||||
results, err := r.netPlugin.SetUpPod(podNetwork)
|
||||
if err != nil {
|
||||
@ -216,7 +227,7 @@ func (r *Runtime) teardownNetNS(ctr *Container) error {
|
||||
|
||||
logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID())
|
||||
|
||||
podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings)
|
||||
podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings, ctr.config.StaticIP)
|
||||
|
||||
// The network may have already been torn down, so don't fail here, just log
|
||||
if err := r.netPlugin.TearDownPod(podNetwork); err != nil {
|
||||
|
@ -828,6 +828,31 @@ func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, netwo
|
||||
}
|
||||
}
|
||||
|
||||
// WithStaticIP indicates that the container should request a static IP from
|
||||
// the CNI plugins.
|
||||
// It cannot be set unless WithNetNS has already been passed.
|
||||
// Further, it cannot be set if additional CNI networks to join have been
|
||||
// specified.
|
||||
func WithStaticIP(ip net.IP) CtrCreateOption {
|
||||
return func(ctr *Container) error {
|
||||
if ctr.valid {
|
||||
return ErrCtrFinalized
|
||||
}
|
||||
|
||||
if !ctr.config.CreateNetNS {
|
||||
return errors.Wrapf(ErrInvalidArg, "cannot set a static IP if the container is not creating a network namespace")
|
||||
}
|
||||
|
||||
if len(ctr.config.Networks) != 0 {
|
||||
return errors.Wrapf(ErrInvalidArg, "cannot set a static IP if joining additional CNI networks")
|
||||
}
|
||||
|
||||
ctr.config.StaticIP = ip
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogPath sets the path to the log file.
|
||||
func WithLogPath(path string) CtrCreateOption {
|
||||
return func(ctr *Container) error {
|
||||
|
@ -1,3 +1,5 @@
|
||||
// +build seccomp ostree selinux varlink exclude_graphdriver_devicemapper
|
||||
|
||||
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
|
||||
|
||||
package libpod
|
||||
|
@ -2,6 +2,7 @@ package createconfig
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -311,9 +312,6 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib
|
||||
var pod *libpod.Pod
|
||||
var err error
|
||||
|
||||
// Uncomment after talking to mheon about unimplemented funcs
|
||||
// options = append(options, libpod.WithLabels(c.labels))
|
||||
|
||||
if c.Interactive {
|
||||
options = append(options, libpod.WithStdin())
|
||||
}
|
||||
@ -442,6 +440,15 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib
|
||||
if logPath != "" {
|
||||
options = append(options, libpod.WithLogPath(logPath))
|
||||
}
|
||||
if c.IPAddress != "" {
|
||||
ip := net.ParseIP(c.IPAddress)
|
||||
if ip == nil {
|
||||
return nil, errors.Wrapf(libpod.ErrInvalidArg, "cannot parse %s as IP address", c.IPAddress)
|
||||
} else if ip.To4() == nil {
|
||||
return nil, errors.Wrapf(libpod.ErrInvalidArg, "%s is not an IPv4 address", c.IPAddress)
|
||||
}
|
||||
options = append(options, libpod.WithStaticIP(ip))
|
||||
}
|
||||
|
||||
options = append(options, libpod.WithPrivileged(c.Privileged))
|
||||
|
||||
|
58
test/e2e/run_staticip_test.go
Normal file
58
test/e2e/run_staticip_test.go
Normal file
@ -0,0 +1,58 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Podman run with --ip flag", func() {
|
||||
var (
|
||||
tempdir string
|
||||
err error
|
||||
podmanTest PodmanTest
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
tempdir, err = CreateTempDirInTempDir()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
podmanTest = PodmanCreate(tempdir)
|
||||
podmanTest.RestoreAllArtifacts()
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
podmanTest.Cleanup()
|
||||
f := CurrentGinkgoTestDescription()
|
||||
timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds())
|
||||
GinkgoWriter.Write([]byte(timedResult))
|
||||
})
|
||||
|
||||
It("Podman run --ip with garbage address", func() {
|
||||
result := podmanTest.Podman([]string{"run", "-ti", "--ip", "114232346", ALPINE, "ls"})
|
||||
result.WaitWithDefaultTimeout()
|
||||
Expect(result.ExitCode()).ToNot(Equal(0))
|
||||
})
|
||||
|
||||
It("Podman run --ip with v6 address", func() {
|
||||
result := podmanTest.Podman([]string{"run", "-ti", "--ip", "2001:db8:bad:beef::1", ALPINE, "ls"})
|
||||
result.WaitWithDefaultTimeout()
|
||||
Expect(result.ExitCode()).ToNot(Equal(0))
|
||||
})
|
||||
|
||||
It("Podman run --ip with non-allocatable IP", func() {
|
||||
result := podmanTest.Podman([]string{"run", "-ti", "--ip", "203.0.113.124", ALPINE, "ls"})
|
||||
result.WaitWithDefaultTimeout()
|
||||
Expect(result.ExitCode()).ToNot(Equal(0))
|
||||
})
|
||||
|
||||
It("Podman run with specified static IP has correct IP", func() {
|
||||
result := podmanTest.Podman([]string{"run", "-ti", "--ip", "10.88.64.128", ALPINE, "ip", "addr"})
|
||||
result.WaitWithDefaultTimeout()
|
||||
Expect(result.ExitCode()).To(Equal(0))
|
||||
Expect(result.OutputToString()).To(ContainSubstring("10.88.64.128/16"))
|
||||
})
|
||||
})
|
@ -14,7 +14,7 @@ github.com/containers/image 918dbb93e6e099b196b498c38d079f6bb924d0c8
|
||||
github.com/containers/storage 41294c85d97bef688e18f710402895dbecde3308
|
||||
github.com/containers/psgo 5dde6da0bc8831b35243a847625bcf18183bd1ee
|
||||
github.com/coreos/go-systemd v14
|
||||
github.com/cri-o/ocicni master
|
||||
github.com/cri-o/ocicni 2d2983e40c242322a56c22a903785e7f83eb378c
|
||||
github.com/cyphar/filepath-securejoin v0.2.1
|
||||
github.com/davecgh/go-spew v1.1.0
|
||||
github.com/docker/distribution 7a8efe719e55bbfaff7bc5718cdf0ed51ca821df
|
||||
|
41
vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go
generated
vendored
41
vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go
generated
vendored
@ -3,6 +3,7 @@ package ocicni
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
@ -351,14 +352,14 @@ func (plugin *cniNetworkPlugin) getNetwork(name string) (*cniNetwork, error) {
|
||||
return net, nil
|
||||
}
|
||||
|
||||
func (plugin *cniNetworkPlugin) getDefaultNetworkName() string {
|
||||
func (plugin *cniNetworkPlugin) GetDefaultNetworkName() string {
|
||||
plugin.RLock()
|
||||
defer plugin.RUnlock()
|
||||
return plugin.defaultNetName
|
||||
}
|
||||
|
||||
func (plugin *cniNetworkPlugin) getDefaultNetwork() *cniNetwork {
|
||||
defaultNetName := plugin.getDefaultNetworkName()
|
||||
defaultNetName := plugin.GetDefaultNetworkName()
|
||||
if defaultNetName == "" {
|
||||
return nil
|
||||
}
|
||||
@ -383,7 +384,7 @@ func (plugin *cniNetworkPlugin) Name() string {
|
||||
func (plugin *cniNetworkPlugin) forEachNetwork(podNetwork *PodNetwork, forEachFunc func(*cniNetwork, string, *PodNetwork) error) error {
|
||||
networks := podNetwork.Networks
|
||||
if len(networks) == 0 {
|
||||
networks = append(networks, plugin.getDefaultNetworkName())
|
||||
networks = append(networks, plugin.GetDefaultNetworkName())
|
||||
}
|
||||
for i, netName := range networks {
|
||||
// Interface names start at "eth0" and count up for each network
|
||||
@ -408,7 +409,7 @@ func (plugin *cniNetworkPlugin) SetUpPod(podNetwork PodNetwork) ([]cnitypes.Resu
|
||||
plugin.podLock(podNetwork).Lock()
|
||||
defer plugin.podUnlock(podNetwork)
|
||||
|
||||
_, err := plugin.loNetwork.addToNetwork(plugin.cacheDir, &podNetwork, "lo")
|
||||
_, err := plugin.loNetwork.addToNetwork(plugin.cacheDir, &podNetwork, "lo", "")
|
||||
if err != nil {
|
||||
logrus.Errorf("Error while adding to cni lo network: %s", err)
|
||||
return nil, err
|
||||
@ -416,7 +417,12 @@ func (plugin *cniNetworkPlugin) SetUpPod(podNetwork PodNetwork) ([]cnitypes.Resu
|
||||
|
||||
results := make([]cnitypes.Result, 0)
|
||||
if err := plugin.forEachNetwork(&podNetwork, func(network *cniNetwork, ifName string, podNetwork *PodNetwork) error {
|
||||
result, err := network.addToNetwork(plugin.cacheDir, podNetwork, ifName)
|
||||
ip := ""
|
||||
if conf, ok := podNetwork.NetworkConfig[network.name]; ok {
|
||||
ip = conf.IP
|
||||
}
|
||||
|
||||
result, err := network.addToNetwork(plugin.cacheDir, podNetwork, ifName, ip)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error while adding pod to CNI network %q: %s", network.name, err)
|
||||
return err
|
||||
@ -439,7 +445,12 @@ func (plugin *cniNetworkPlugin) TearDownPod(podNetwork PodNetwork) error {
|
||||
defer plugin.podUnlock(podNetwork)
|
||||
|
||||
return plugin.forEachNetwork(&podNetwork, func(network *cniNetwork, ifName string, podNetwork *PodNetwork) error {
|
||||
if err := network.deleteFromNetwork(plugin.cacheDir, podNetwork, ifName); err != nil {
|
||||
ip := ""
|
||||
if conf, ok := podNetwork.NetworkConfig[network.name]; ok {
|
||||
ip = conf.IP
|
||||
}
|
||||
|
||||
if err := network.deleteFromNetwork(plugin.cacheDir, podNetwork, ifName, ip); err != nil {
|
||||
logrus.Errorf("Error while removing pod from CNI network %q: %s", network.name, err)
|
||||
return err
|
||||
}
|
||||
@ -491,8 +502,8 @@ func (plugin *cniNetworkPlugin) GetPodNetworkStatus(podNetwork PodNetwork) ([]cn
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (network *cniNetwork) addToNetwork(cacheDir string, podNetwork *PodNetwork, ifName string) (cnitypes.Result, error) {
|
||||
rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName)
|
||||
func (network *cniNetwork) addToNetwork(cacheDir string, podNetwork *PodNetwork, ifName, ip string) (cnitypes.Result, error) {
|
||||
rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName, ip)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error adding network: %v", err)
|
||||
return nil, err
|
||||
@ -509,8 +520,8 @@ func (network *cniNetwork) addToNetwork(cacheDir string, podNetwork *PodNetwork,
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (network *cniNetwork) deleteFromNetwork(cacheDir string, podNetwork *PodNetwork, ifName string) error {
|
||||
rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName)
|
||||
func (network *cniNetwork) deleteFromNetwork(cacheDir string, podNetwork *PodNetwork, ifName, ip string) error {
|
||||
rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName, ip)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error deleting network: %v", err)
|
||||
return err
|
||||
@ -526,7 +537,7 @@ func (network *cniNetwork) deleteFromNetwork(cacheDir string, podNetwork *PodNet
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildCNIRuntimeConf(cacheDir string, podNetwork *PodNetwork, ifName string) (*libcni.RuntimeConf, error) {
|
||||
func buildCNIRuntimeConf(cacheDir string, podNetwork *PodNetwork, ifName, ip string) (*libcni.RuntimeConf, error) {
|
||||
logrus.Infof("Got pod network %+v", podNetwork)
|
||||
|
||||
rt := &libcni.RuntimeConf{
|
||||
@ -542,6 +553,14 @@ func buildCNIRuntimeConf(cacheDir string, podNetwork *PodNetwork, ifName string)
|
||||
},
|
||||
}
|
||||
|
||||
// Add requested static IP to CNI_ARGS
|
||||
if ip != "" {
|
||||
if tstIP := net.ParseIP(ip); tstIP == nil {
|
||||
return nil, fmt.Errorf("unable to parse IP address %q", ip)
|
||||
}
|
||||
rt.Args = append(rt.Args, [2]string{"IP", ip})
|
||||
}
|
||||
|
||||
if len(podNetwork.PortMappings) == 0 {
|
||||
return rt, nil
|
||||
}
|
||||
|
17
vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go
generated
vendored
17
vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go
generated
vendored
@ -24,6 +24,14 @@ type PortMapping struct {
|
||||
HostIP string `json:"hostIP"`
|
||||
}
|
||||
|
||||
// NetworkConfig is additional configuration for a single CNI network.
|
||||
type NetworkConfig struct {
|
||||
// IP is a static IP to be specified in the network. Can only be used
|
||||
// with the hostlocal IP allocator. If left unset, an IP will be
|
||||
// dynamically allocated.
|
||||
IP string
|
||||
}
|
||||
|
||||
// PodNetwork configures the network of a pod sandbox.
|
||||
type PodNetwork struct {
|
||||
// Name is the name of the sandbox.
|
||||
@ -40,6 +48,11 @@ type PodNetwork struct {
|
||||
// Networks is a list of CNI network names to attach to the sandbox
|
||||
// Leave this list empty to attach the default network to the sandbox
|
||||
Networks []string
|
||||
|
||||
// NetworkConfig is configuration specific to a single CNI network.
|
||||
// It is optional, and can be omitted for some or all specified networks
|
||||
// without issue.
|
||||
NetworkConfig map[string]NetworkConfig
|
||||
}
|
||||
|
||||
// CNIPlugin is the interface that needs to be implemented by a plugin
|
||||
@ -48,6 +61,10 @@ type CNIPlugin interface {
|
||||
// for a plugin by name, e.g.
|
||||
Name() string
|
||||
|
||||
// GetDefaultNetworkName returns the name of the plugin's default
|
||||
// network.
|
||||
GetDefaultNetworkName() string
|
||||
|
||||
// SetUpPod is the method called after the sandbox container of
|
||||
// the pod has been created but before the other containers of the
|
||||
// pod are launched.
|
||||
|
Reference in New Issue
Block a user