Skip to content

Commit 02db289

Browse files
committed
fix: allow containers to start using a large numbers of ports
Suppose we have a compose.yaml that allocates a large numbers of ports as follows. ``` > cat compose.yaml services: svc0: image: alpine command: "sleep infinity" ports: - '32000-32060:32000-32060' ``` When we run `nerdctl compose up -d` using this compose.yaml, we will get the following error. ``` FATA[0000] create container failed validation: containers.Labels: label key and value length (4711 bytes) greater than maximum size (4096 bytes), key: nerdctl/ports: invalid argument FATA[0000] error while creating container haytok-svc0-1: error while creating container haytok-svc0-1: exit status 1 ``` This issue is reported in the following issue. - #4027 This issue is considered to be the same as the one with errors when trying to perform many port mappings, such as `nerdctl run -p 80:80 -p 81:81 ~ -p 1000:1000 ...` The current implementation is processing to create a container with the information specified in -p to the label. And as can be seen from the error message, as the number of ports to be port mapped increases, the creation of the container fails because it violates the limit of the maximum number of bytes on the containerd side that can be allocated for a label. Therefore, this PR modifies the container creation process so that containers can be launched without having to assign the information specified in the -p option to the labels. Specifically, port mapping information is stored in the following path, and when port mapping information is required, it is retrieved from this file. ``` <DATAROOT>/<ADDRHASH>/containers/<NAMESPACE>/<CID>/port-mappings.json ``` Signed-off-by: Hayato Kiwata <[email protected]>
1 parent 694c405 commit 02db289

File tree

21 files changed

+254
-114
lines changed

21 files changed

+254
-114
lines changed

cmd/nerdctl/compose/compose_port.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,18 @@ func portAction(cmd *cobra.Command, args []string) error {
8888
return err
8989
}
9090

91+
dataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address)
92+
if err != nil {
93+
return err
94+
}
95+
9196
po := composer.PortOptions{
9297
ServiceName: args[0],
9398
Index: index,
9499
Port: port,
95100
Protocol: protocol,
101+
DataStore: dataStore,
102+
Namespace: globalOptions.Namespace,
96103
}
97104

98105
return c.Port(ctx, cmd.OutOrStdout(), po)

cmd/nerdctl/compose/compose_port_linux_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,16 @@ package compose
1818

1919
import (
2020
"fmt"
21+
"strings"
2122
"testing"
2223

24+
"gotest.tools/v3/assert"
25+
26+
"github.com/containerd/nerdctl/mod/tigron/expect"
27+
"github.com/containerd/nerdctl/mod/tigron/test"
28+
2329
"github.com/containerd/nerdctl/v2/pkg/testutil"
30+
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
2431
)
2532

2633
func TestComposePort(t *testing.T) {
@@ -75,3 +82,44 @@ services:
7582
base.ComposeCmd("-f", comp.YAMLFullPath(), "port", "--protocol", "udp", "svc0", "10000").AssertFail()
7683
base.ComposeCmd("-f", comp.YAMLFullPath(), "port", "--protocol", "tcp", "svc0", "10001").AssertFail()
7784
}
85+
86+
// TestComposeMultiplePorts tests whether it is possible to allocate a large
87+
// number of ports. (https://github.com/containerd/nerdctl/issues/4027)
88+
func TestComposeMultiplePorts(t *testing.T) {
89+
var dockerComposeYAML = fmt.Sprintf(`
90+
services:
91+
svc0:
92+
image: %s
93+
command: "sleep infinity"
94+
ports:
95+
- '32000-32060:32000-32060'
96+
`, testutil.AlpineImage)
97+
98+
testCase := nerdtest.Setup()
99+
100+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
101+
compYamlPath := data.Temp().Save(dockerComposeYAML, "compose.yaml")
102+
data.Labels().Set("composeYaml", compYamlPath)
103+
104+
helpers.Ensure("compose", "-f", compYamlPath, "up", "-d")
105+
}
106+
107+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
108+
helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v")
109+
}
110+
111+
testCase.SubTests = []*test.Case{
112+
{
113+
Description: "Issue #4027 - Allocate a large number of ports.",
114+
NoParallel: true,
115+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
116+
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "port", "svc0", "32000")
117+
},
118+
Expected: test.Expects(expect.ExitCodeSuccess, nil, func(stdout, info string, t *testing.T) {
119+
assert.Assert(t, strings.Contains(stdout, "0.0.0.0:32000"))
120+
}),
121+
},
122+
}
123+
124+
testCase.Run(t)
125+
}

cmd/nerdctl/compose/compose_ps.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/containerd/log"
3333

3434
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
35+
"github.com/containerd/nerdctl/v2/pkg/api/types"
3536
"github.com/containerd/nerdctl/v2/pkg/clientutil"
3637
"github.com/containerd/nerdctl/v2/pkg/cmd/compose"
3738
"github.com/containerd/nerdctl/v2/pkg/containerutil"
@@ -183,9 +184,9 @@ func psAction(cmd *cobra.Command, args []string) error {
183184
var p composeContainerPrintable
184185
var err error
185186
if format == "json" {
186-
p, err = composeContainerPrintableJSON(ctx, container)
187+
p, err = composeContainerPrintableJSON(ctx, container, globalOptions)
187188
} else {
188-
p, err = composeContainerPrintableTab(ctx, container)
189+
p, err = composeContainerPrintableTab(ctx, container, globalOptions)
189190
}
190191
if err != nil {
191192
return err
@@ -234,7 +235,7 @@ func psAction(cmd *cobra.Command, args []string) error {
234235

235236
// composeContainerPrintableTab constructs composeContainerPrintable with fields
236237
// only for console output.
237-
func composeContainerPrintableTab(ctx context.Context, container containerd.Container) (composeContainerPrintable, error) {
238+
func composeContainerPrintableTab(ctx context.Context, container containerd.Container, gOptions types.GlobalCommandOptions) (composeContainerPrintable, error) {
238239
info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)
239240
if err != nil {
240241
return composeContainerPrintable{}, err
@@ -251,20 +252,28 @@ func composeContainerPrintableTab(ctx context.Context, container containerd.Cont
251252
if err != nil {
252253
return composeContainerPrintable{}, err
253254
}
255+
dataStore, err := clientutil.DataStore(gOptions.DataRoot, gOptions.Address)
256+
if err != nil {
257+
return composeContainerPrintable{}, err
258+
}
259+
ports, err := portutil.LoadPortMappings(dataStore, gOptions.Namespace, info.ID)
260+
if err != nil {
261+
return composeContainerPrintable{}, err
262+
}
254263

255264
return composeContainerPrintable{
256265
Name: info.Labels[labels.Name],
257266
Image: image.Metadata().Name,
258267
Command: formatter.InspectContainerCommandTrunc(spec),
259268
Service: info.Labels[labels.ComposeService],
260269
State: status,
261-
Ports: formatter.FormatPorts(info.Labels),
270+
Ports: formatter.FormatPorts(ports),
262271
}, nil
263272
}
264273

265274
// composeContainerPrintableJSON constructs composeContainerPrintable with fields
266275
// only for json output and compatible docker output.
267-
func composeContainerPrintableJSON(ctx context.Context, container containerd.Container) (composeContainerPrintable, error) {
276+
func composeContainerPrintableJSON(ctx context.Context, container containerd.Container, gOptions types.GlobalCommandOptions) (composeContainerPrintable, error) {
268277
info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)
269278
if err != nil {
270279
return composeContainerPrintable{}, err
@@ -294,6 +303,14 @@ func composeContainerPrintableJSON(ctx context.Context, container containerd.Con
294303
if err != nil {
295304
return composeContainerPrintable{}, err
296305
}
306+
dataStore, err := clientutil.DataStore(gOptions.DataRoot, gOptions.Address)
307+
if err != nil {
308+
return composeContainerPrintable{}, err
309+
}
310+
portsJSON, err := portutil.LoadPortMappingsData(dataStore, gOptions.Namespace, info.ID)
311+
if err != nil {
312+
return composeContainerPrintable{}, err
313+
}
297314

298315
return composeContainerPrintable{
299316
ID: container.ID(),
@@ -305,7 +322,7 @@ func composeContainerPrintableJSON(ctx context.Context, container containerd.Con
305322
State: state,
306323
Health: "",
307324
ExitCode: exitCode,
308-
Publishers: formatPublishers(info.Labels),
325+
Publishers: formatPublishers(portsJSON),
309326
}, nil
310327
}
311328

@@ -321,7 +338,7 @@ type PortPublisher struct {
321338

322339
// formatPublishers parses and returns docker-compatible []PortPublisher from
323340
// label map. If an error happens, an empty slice is returned.
324-
func formatPublishers(labelMap map[string]string) []PortPublisher {
341+
func formatPublishers(portsJSON string) []PortPublisher {
325342
mapper := func(pm cni.PortMapping) PortPublisher {
326343
return PortPublisher{
327344
URL: pm.HostIP,
@@ -332,7 +349,7 @@ func formatPublishers(labelMap map[string]string) []PortPublisher {
332349
}
333350

334351
var dockerPorts []PortPublisher
335-
if portMappings, err := portutil.ParsePortsLabel(labelMap); err == nil {
352+
if portMappings, err := portutil.ParsePortsLabel(portsJSON); err == nil {
336353
for _, p := range portMappings {
337354
dockerPorts = append(dockerPorts, mapper(p))
338355
}

cmd/nerdctl/container/container_port.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/containerd/nerdctl/v2/pkg/clientutil"
3030
"github.com/containerd/nerdctl/v2/pkg/containerutil"
3131
"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker"
32+
"github.com/containerd/nerdctl/v2/pkg/portutil"
3233
)
3334

3435
func PortCommand() *cobra.Command {
@@ -81,13 +82,22 @@ func portAction(cmd *cobra.Command, args []string) error {
8182
}
8283
defer cancel()
8384

85+
dataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address)
86+
if err != nil {
87+
return err
88+
}
89+
8490
walker := &containerwalker.ContainerWalker{
8591
Client: client,
8692
OnFound: func(ctx context.Context, found containerwalker.Found) error {
8793
if found.MatchCount > 1 {
8894
return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
8995
}
90-
return containerutil.PrintHostPort(ctx, cmd.OutOrStdout(), found.Container, argPort, argProto)
96+
ports, err := portutil.LoadPortMappings(dataStore, globalOptions.Namespace, found.Container.ID())
97+
if err != nil {
98+
return err
99+
}
100+
return containerutil.PrintHostPort(ctx, cmd.OutOrStdout(), found.Container, argPort, argProto, ports)
91101
},
92102
}
93103
req := args[0]

cmd/nerdctl/container/container_run_network_linux_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import (
3636

3737
"github.com/containerd/containerd/v2/defaults"
3838
"github.com/containerd/containerd/v2/pkg/netns"
39-
"github.com/containerd/errdefs"
4039
"github.com/containerd/nerdctl/mod/tigron/expect"
4140
"github.com/containerd/nerdctl/mod/tigron/require"
4241
"github.com/containerd/nerdctl/mod/tigron/test"
@@ -366,8 +365,8 @@ func TestRunWithInvalidPortThenCleanUp(t *testing.T) {
366365
},
367366
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
368367
return &test.Expected{
369-
ExitCode: 1,
370-
Errors: []error{errdefs.ErrInvalidArgument},
368+
ExitCode: 0,
369+
Errors: []error{},
371370
Output: func(stdout string, info string, t *testing.T) {
372371
getAddrHash := func(addr string) string {
373372
const addrHashLen = 8

pkg/cmd/container/create.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import (
3636
"github.com/containerd/containerd/v2/core/containers"
3737
"github.com/containerd/containerd/v2/pkg/cio"
3838
"github.com/containerd/containerd/v2/pkg/oci"
39-
"github.com/containerd/go-cni"
4039
"github.com/containerd/log"
4140

4241
"github.com/containerd/nerdctl/v2/pkg/annotations"
@@ -58,6 +57,7 @@ import (
5857
"github.com/containerd/nerdctl/v2/pkg/mountutil"
5958
"github.com/containerd/nerdctl/v2/pkg/namestore"
6059
"github.com/containerd/nerdctl/v2/pkg/platformutil"
60+
"github.com/containerd/nerdctl/v2/pkg/portutil"
6161
"github.com/containerd/nerdctl/v2/pkg/referenceutil"
6262
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
6363
"github.com/containerd/nerdctl/v2/pkg/store"
@@ -379,6 +379,11 @@ func Create(ctx context.Context, client *containerd.Client, args []string, netMa
379379
}
380380
cOpts = append(cOpts, ilOpt)
381381

382+
err = portutil.GeneratePortMappingsConfig(dataStore, options.GOptions.Namespace, id, netLabelOpts.PortMappings)
383+
if err != nil {
384+
return nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), fmt.Errorf("Error writing to port-mappings.json: %v", err)
385+
}
386+
382387
opts = append(opts, propagateInternalContainerdLabelsToOCIAnnotations(),
383388
oci.WithAnnotations(strutil.ConvertKVStringsToMap(options.Annotations)))
384389

@@ -678,7 +683,6 @@ type internalLabels struct {
678683
networks []string
679684
ipAddress string
680685
ip6Address string
681-
ports []cni.PortMapping
682686
macAddress string
683687
dnsServers []string
684688
dnsSearchDomains []string
@@ -730,13 +734,6 @@ func withInternalLabels(internalLabels internalLabels) (containerd.NewContainerO
730734
return nil, err
731735
}
732736
m[labels.Networks] = string(networksJSON)
733-
if len(internalLabels.ports) > 0 {
734-
portsJSON, err := json.Marshal(internalLabels.ports)
735-
if err != nil {
736-
return nil, err
737-
}
738-
m[labels.Ports] = string(portsJSON)
739-
}
740737
if internalLabels.logURI != "" {
741738
m[labels.LogURI] = internalLabels.logURI
742739
logConfigJSON, err := json.Marshal(internalLabels.logConfig)
@@ -838,7 +835,6 @@ func withInternalLabels(internalLabels internalLabels) (containerd.NewContainerO
838835
func (il *internalLabels) loadNetOpts(opts types.NetworkOptions) {
839836
il.hostname = opts.Hostname
840837
il.domainname = opts.Domainname
841-
il.ports = opts.PortMappings
842838
il.ipAddress = opts.IPAddress
843839
il.ip6Address = opts.IP6Address
844840
il.networks = opts.NetworkSlice

pkg/cmd/container/inspect.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,36 @@ import (
2525
"github.com/containerd/containerd/v2/core/snapshots"
2626

2727
"github.com/containerd/nerdctl/v2/pkg/api/types"
28+
"github.com/containerd/nerdctl/v2/pkg/clientutil"
2829
"github.com/containerd/nerdctl/v2/pkg/containerdutil"
2930
"github.com/containerd/nerdctl/v2/pkg/containerinspector"
3031
"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker"
3132
"github.com/containerd/nerdctl/v2/pkg/imgutil"
3233
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
34+
"github.com/containerd/nerdctl/v2/pkg/portutil"
3335
)
3436

3537
// Inspect prints detailed information for each container in `containers`.
3638
func Inspect(ctx context.Context, client *containerd.Client, containers []string, options types.ContainerInspectOptions) ([]any, error) {
39+
dataStore, err := clientutil.DataStore(options.GOptions.DataRoot, options.GOptions.Address)
40+
if err != nil {
41+
return []any{}, err
42+
}
43+
3744
f := &containerInspector{
3845
mode: options.Mode,
3946
size: options.Size,
4047
snapshotter: containerdutil.SnapshotService(client, options.GOptions.Snapshotter),
48+
dataStore: dataStore,
49+
namespace: options.GOptions.Namespace,
4150
}
4251

4352
walker := &containerwalker.ContainerWalker{
4453
Client: client,
4554
OnFound: f.Handler,
4655
}
4756

48-
err := walker.WalkAll(ctx, containers, true)
57+
err = walker.WalkAll(ctx, containers, true)
4958
if err != nil {
5059
return []any{}, err
5160
}
@@ -58,6 +67,8 @@ type containerInspector struct {
5867
size bool
5968
snapshotter snapshots.Snapshotter
6069
entries []interface{}
70+
dataStore string
71+
namespace string
6172
}
6273

6374
func (x *containerInspector) Handler(ctx context.Context, found containerwalker.Found) error {
@@ -68,6 +79,15 @@ func (x *containerInspector) Handler(ctx context.Context, found containerwalker.
6879
if err != nil {
6980
return err
7081
}
82+
83+
ports, err := portutil.LoadPortMappings(x.dataStore, x.namespace, n.ID)
84+
if err != nil {
85+
return err
86+
}
87+
if n.Process.NetNS != nil && len(ports) > 0 {
88+
n.Process.NetNS.PortMappings = ports
89+
}
90+
7191
switch x.mode {
7292
case "native":
7393
x.entries = append(x.entries, n)

pkg/cmd/container/kill.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/containerd/log"
3434

3535
"github.com/containerd/nerdctl/v2/pkg/api/types"
36+
"github.com/containerd/nerdctl/v2/pkg/clientutil"
3637
"github.com/containerd/nerdctl/v2/pkg/containerutil"
3738
"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker"
3839
"github.com/containerd/nerdctl/v2/pkg/labels"
@@ -122,14 +123,14 @@ func killContainer(ctx context.Context, container containerd.Container, signal s
122123
// cleanupNetwork removes cni network setup, specifically the forwards
123124
func cleanupNetwork(ctx context.Context, container containerd.Container, globalOpts types.GlobalCommandOptions) error {
124125
return rootlessutil.WithDetachedNetNSIfAny(func() error {
125-
// retrieve info to get current active port mappings
126-
info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)
126+
// retrieve current active port mappings
127+
dataStore, err := clientutil.DataStore(globalOpts.DataRoot, globalOpts.Address)
127128
if err != nil {
128129
return err
129130
}
130-
ports, portErr := portutil.ParsePortsLabel(info.Labels)
131-
if portErr != nil {
132-
return fmt.Errorf("no oci spec: %q", portErr)
131+
ports, err := portutil.LoadPortMappings(dataStore, globalOpts.Namespace, container.ID())
132+
if err != nil {
133+
return fmt.Errorf("no oci spec: %q", err)
133134
}
134135
portMappings := []cni.NamespaceOpts{
135136
cni.WithCapabilityPortMap(ports),

0 commit comments

Comments
 (0)