Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] Implement namespacing for networks #3096

Merged
merged 1 commit into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/nerdctl/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func shellCompleteNetworkNames(cmd *cobra.Command, exclude []string) ([]string,
excludeMap[ex] = struct{}{}
}

e, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath)
e, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithNamespace(globalOptions.Namespace))
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
Expand Down
25 changes: 25 additions & 0 deletions cmd/nerdctl/network_inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,28 @@ func TestNetworkInspect(t *testing.T) {
}
assert.DeepEqual(base.T, expectedIPAM, got.IPAM)
}

func TestNetworkWithNamespace(t *testing.T) {
testutil.DockerIncompatible(t)

t.Parallel()

tID := testutil.Identifier(t)
base := testutil.NewBase(t)
baseOther := testutil.NewBaseWithNamespace(t, tID)

tearDown := func() {
apostasie marked this conversation as resolved.
Show resolved Hide resolved
base.Cmd("network", "rm", tID).Run()
baseOther.Cmd("namespace", "remove", tID).Run()
}
tearDown()
t.Cleanup(tearDown)

base.Cmd("network", "create", tID).AssertOK()

// Other namespace cannot inspect, prune, see, or remove this network
baseOther.Cmd("network", "inspect", tID).AssertFail()
baseOther.Cmd("network", "prune", "-f").AssertOutNotContains(tID)
baseOther.Cmd("network", "ls").AssertOutNotContains(tID)
baseOther.Cmd("network", "remove", tID).AssertFail()
}
24 changes: 0 additions & 24 deletions cmd/nerdctl/network_remove_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,6 @@ import (
"gotest.tools/v3/assert"
)

func TestNetworkRemoveInOtherNamespace(t *testing.T) {
if rootlessutil.IsRootless() {
t.Skip("test skipped for remove rootless network")
}
if testutil.GetTarget() == testutil.Docker {
t.Skip("test skipped for docker")
}
// --namespace=nerdctl-test
base := testutil.NewBase(t)
// --namespace=nerdctl-other
baseOther := testutil.NewBaseWithNamespace(t, "nerdctl-other")
networkName := testutil.Identifier(t)

base.Cmd("network", "create", networkName).AssertOK()
defer base.Cmd("network", "rm", networkName).AssertOK()

tID := testutil.Identifier(t)
base.Cmd("run", "-d", "--net", networkName, "--name", tID, testutil.AlpineImage, "sleep", "infinity").AssertOK()
defer base.Cmd("rm", "-f", tID).Run()

// delete network in namespace nerdctl-other
baseOther.Cmd("network", "rm", networkName).AssertFail()
}

func TestNetworkRemove(t *testing.T) {
if rootlessutil.IsRootless() {
t.Skip("test skipped for remove rootless network")
Expand Down
13 changes: 11 additions & 2 deletions docs/cni.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,16 @@ For example:
## Custom networks

You can also customize your CNI network by providing configuration files.
For example you have one configuration file(`/etc/cni/net.d/10-mynet.conf`)

When rootful, the expected root location is `/etc/cni/net.d`.
For rootless, the expected root location is `~/.config/cni/net.d/`
apostasie marked this conversation as resolved.
Show resolved Hide resolved

Configuration files (like `10-mynet.conf`) can be placed either in the root location,
or under a subfolder.
If in the root location, this network will be available to all nerdctl namespaces.
If placed in a subfolder, it will be available only to the identically named namespace.

For example, you have one configuration file(`/etc/cni/net.d/10-mynet.conf`)
for `bridge` network:

```json
Expand All @@ -138,7 +147,7 @@ for `bridge` network:
```

This will configure a new CNI network with the name `mynet`, and you can use
this network to create a container:
this network to create a container in any namespace:

```console
# nerdctl run -it --net mynet --rm alpine ip addr show
Expand Down
4 changes: 4 additions & 0 deletions docs/dir.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,9 @@ Data volume

Can be overridden with `nerdctl --cni-netconfpath=<NETCONFPATH>` flag and environment variable `$NETCONFPATH`.

At the top-level of <NETCONFPATH>, network (files) are shared accross all namespaces.
Sub-folders inside <NETCONFPATH> are only available to the namespace bearing the same name,
and its networks definitions are private.

Files:
- `nerdctl-<NWNAME>.conflist`: CNI conf list created by nerdctl
2 changes: 1 addition & 1 deletion pkg/cmd/compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import (

// New returns a new *composer.Composer.
func New(client *containerd.Client, globalOptions types.GlobalCommandOptions, options composer.Options, stdout, stderr io.Writer) (*composer.Composer, error) {
cniEnv, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithDefaultNetwork())
cniEnv, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithNamespace(globalOptions.Namespace), netutil.WithDefaultNetwork())
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/container/kill.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func cleanupNetwork(ctx context.Context, container containerd.Container, globalO
case nettype.Host, nettype.None, nettype.Container:
// NOP
case nettype.CNI:
e, err := netutil.NewCNIEnv(globalOpts.CNIPath, globalOpts.CNINetConfPath, netutil.WithDefaultNetwork())
e, err := netutil.NewCNIEnv(globalOpts.CNIPath, globalOpts.CNINetConfPath, netutil.WithNamespace(globalOpts.Namespace), netutil.WithDefaultNetwork())
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/network/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func Create(options types.NetworkCreateOptions, stdout io.Writer) error {
options.Subnets = []string{""}
}

e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath)
e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))
apostasie marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/network/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (

func Inspect(ctx context.Context, options types.NetworkInspectOptions) error {
globalOptions := options.GOptions
e, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath)
e, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))

if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/network/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func List(ctx context.Context, options types.NetworkListOptions) error {
}
}

e, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath)
e, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/network/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
)

func Prune(ctx context.Context, client *containerd.Client, options types.NetworkPruneOptions) error {
e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath)
e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/network/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (
)

func Remove(ctx context.Context, client *containerd.Client, options types.NetworkRemoveOptions) error {
e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath)
e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/containerutil/container_network_manager_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type cniNetworkManagerPlatform struct {

// Verifies that the internal network settings are correct.
func (m *cniNetworkManager) VerifyNetworkOptions(_ context.Context) error {
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithDefaultNetwork())
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithNamespace(m.globalOptions.Namespace), netutil.WithDefaultNetwork())
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/containerutil/container_network_manager_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type cniNetworkManagerPlatform struct {

// Verifies that the internal network settings are correct.
func (m *cniNetworkManager) VerifyNetworkOptions(_ context.Context) error {
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithDefaultNetwork())
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithNamespace(m.globalOptions.Namespace), netutil.WithDefaultNetwork())
if err != nil {
return err
}
Expand Down Expand Up @@ -67,7 +67,7 @@ func (m *cniNetworkManager) VerifyNetworkOptions(_ context.Context) error {
}

func (m *cniNetworkManager) getCNI() (gocni.CNI, error) {
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithDefaultNetwork())
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithNamespace(m.globalOptions.Namespace), netutil.WithDefaultNetwork())
if err != nil {
return nil, fmt.Errorf("failed to instantiate CNI env: %s", err)
}
Expand Down
26 changes: 24 additions & 2 deletions pkg/netutil/netutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import (
type CNIEnv struct {
Path string
NetconfPath string
Namespace string
}

type CNIEnvOpt func(e *CNIEnv) error
Expand Down Expand Up @@ -132,6 +133,16 @@ func WithDefaultNetwork() CNIEnvOpt {
}
}

func WithNamespace(namespace string) CNIEnvOpt {
return func(e *CNIEnv) error {
if err := os.MkdirAll(filepath.Join(e.NetconfPath, namespace), 0755); err != nil {
return err
}
e.Namespace = namespace
return nil
}
}

func NewCNIEnv(cniPath, cniConfPath string, opts ...CNIEnvOpt) (*CNIEnv, error) {
e := CNIEnv{
Path: cniPath,
Expand Down Expand Up @@ -193,7 +204,10 @@ func (e *CNIEnv) FilterNetworks(filterf func(*NetworkConfig) bool) ([]*NetworkCo
}

func (e *CNIEnv) getConfigPathForNetworkName(netName string) string {
return filepath.Join(e.NetconfPath, "nerdctl-"+netName+".conflist")
if netName == DefaultNetworkName || e.Namespace == "" {
return filepath.Join(e.NetconfPath, "nerdctl-"+netName+".conflist")
}
return filepath.Join(e.NetconfPath, e.Namespace, "nerdctl-"+netName+".conflist")
}

func (e *CNIEnv) usedSubnets() ([]*net.IPNet, error) {
Expand Down Expand Up @@ -404,10 +418,18 @@ func (e *CNIEnv) writeNetworkConfig(net *NetworkConfig) error {
// networkConfigList loads config from dir if dir exists.
func (e *CNIEnv) networkConfigList() ([]*NetworkConfig, error) {
l := []*NetworkConfig{}
fileNames, err := libcni.ConfFiles(e.NetconfPath, []string{".conf", ".conflist", ".json"})
common, err := libcni.ConfFiles(e.NetconfPath, []string{".conf", ".conflist", ".json"})
if err != nil {
return nil, err
}
namespaced := []string{}
if e.Namespace != "" {
namespaced, err = libcni.ConfFiles(filepath.Join(e.NetconfPath, e.Namespace), []string{".conf", ".conflist", ".json"})
if err != nil {
return nil, err
}
}
fileNames := append(common, namespaced...)
sort.Strings(fileNames)
for _, fileName := range fileNames {
var lcl *libcni.NetworkConfigList
Expand Down
2 changes: 1 addition & 1 deletion pkg/ocihook/ocihook.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func newHandlerOpts(state *specs.State, dataStore, cniPath, cniNetconfPath strin
case nettype.Host, nettype.None, nettype.Container:
// NOP
case nettype.CNI:
e, err := netutil.NewCNIEnv(cniPath, cniNetconfPath, netutil.WithDefaultNetwork())
e, err := netutil.NewCNIEnv(cniPath, cniNetconfPath, netutil.WithNamespace(namespace), netutil.WithDefaultNetwork())
if err != nil {
return nil, err
}
Expand Down
Loading