Skip to content

Commit 7d768be

Browse files
committed
fix: allow containers to start using a large number 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>/network-config.json ``` Signed-off-by: Hayato Kiwata <[email protected]>
1 parent d0d1c2d commit 7d768be

File tree

22 files changed

+350
-168
lines changed

22 files changed

+350
-168
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: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ import (
2020
"fmt"
2121
"testing"
2222

23+
"github.com/containerd/nerdctl/mod/tigron/expect"
24+
"github.com/containerd/nerdctl/mod/tigron/test"
25+
2326
"github.com/containerd/nerdctl/v2/pkg/testutil"
27+
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
2428
)
2529

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

cmd/nerdctl/compose/compose_ps.go

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ import (
2929
"github.com/containerd/containerd/v2/core/runtime/restart"
3030
"github.com/containerd/errdefs"
3131
"github.com/containerd/go-cni"
32-
"github.com/containerd/log"
3332

3433
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
34+
"github.com/containerd/nerdctl/v2/pkg/api/types"
3535
"github.com/containerd/nerdctl/v2/pkg/clientutil"
3636
"github.com/containerd/nerdctl/v2/pkg/cmd/compose"
3737
"github.com/containerd/nerdctl/v2/pkg/containerutil"
@@ -183,9 +183,9 @@ func psAction(cmd *cobra.Command, args []string) error {
183183
var p composeContainerPrintable
184184
var err error
185185
if format == "json" {
186-
p, err = composeContainerPrintableJSON(ctx, container)
186+
p, err = composeContainerPrintableJSON(ctx, container, globalOptions)
187187
} else {
188-
p, err = composeContainerPrintableTab(ctx, container)
188+
p, err = composeContainerPrintableTab(ctx, container, globalOptions)
189189
}
190190
if err != nil {
191191
return err
@@ -234,7 +234,7 @@ func psAction(cmd *cobra.Command, args []string) error {
234234

235235
// composeContainerPrintableTab constructs composeContainerPrintable with fields
236236
// only for console output.
237-
func composeContainerPrintableTab(ctx context.Context, container containerd.Container) (composeContainerPrintable, error) {
237+
func composeContainerPrintableTab(ctx context.Context, container containerd.Container, gOptions types.GlobalCommandOptions) (composeContainerPrintable, error) {
238238
info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)
239239
if err != nil {
240240
return composeContainerPrintable{}, err
@@ -251,20 +251,32 @@ func composeContainerPrintableTab(ctx context.Context, container containerd.Cont
251251
if err != nil {
252252
return composeContainerPrintable{}, err
253253
}
254+
dataStore, err := clientutil.DataStore(gOptions.DataRoot, gOptions.Address)
255+
if err != nil {
256+
return composeContainerPrintable{}, err
257+
}
258+
containerLabels, err := container.Labels(ctx)
259+
if err != nil {
260+
return composeContainerPrintable{}, err
261+
}
262+
ports, err := portutil.LoadPortMappings(dataStore, gOptions.Namespace, info.ID, containerLabels)
263+
if err != nil {
264+
return composeContainerPrintable{}, err
265+
}
254266

255267
return composeContainerPrintable{
256268
Name: info.Labels[labels.Name],
257269
Image: image.Metadata().Name,
258270
Command: formatter.InspectContainerCommandTrunc(spec),
259271
Service: info.Labels[labels.ComposeService],
260272
State: status,
261-
Ports: formatter.FormatPorts(info.Labels),
273+
Ports: formatter.FormatPorts(ports),
262274
}, nil
263275
}
264276

265277
// composeContainerPrintableJSON constructs composeContainerPrintable with fields
266278
// only for json output and compatible docker output.
267-
func composeContainerPrintableJSON(ctx context.Context, container containerd.Container) (composeContainerPrintable, error) {
279+
func composeContainerPrintableJSON(ctx context.Context, container containerd.Container, gOptions types.GlobalCommandOptions) (composeContainerPrintable, error) {
268280
info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)
269281
if err != nil {
270282
return composeContainerPrintable{}, err
@@ -294,6 +306,18 @@ func composeContainerPrintableJSON(ctx context.Context, container containerd.Con
294306
if err != nil {
295307
return composeContainerPrintable{}, err
296308
}
309+
dataStore, err := clientutil.DataStore(gOptions.DataRoot, gOptions.Address)
310+
if err != nil {
311+
return composeContainerPrintable{}, err
312+
}
313+
containerLabels, err := container.Labels(ctx)
314+
if err != nil {
315+
return composeContainerPrintable{}, err
316+
}
317+
portMappings, err := portutil.LoadPortMappings(dataStore, gOptions.Namespace, info.ID, containerLabels)
318+
if err != nil {
319+
return composeContainerPrintable{}, err
320+
}
297321

298322
return composeContainerPrintable{
299323
ID: container.ID(),
@@ -305,7 +329,7 @@ func composeContainerPrintableJSON(ctx context.Context, container containerd.Con
305329
State: state,
306330
Health: "",
307331
ExitCode: exitCode,
308-
Publishers: formatPublishers(info.Labels),
332+
Publishers: formatPublishers(portMappings),
309333
}, nil
310334
}
311335

@@ -321,7 +345,7 @@ type PortPublisher struct {
321345

322346
// formatPublishers parses and returns docker-compatible []PortPublisher from
323347
// label map. If an error happens, an empty slice is returned.
324-
func formatPublishers(labelMap map[string]string) []PortPublisher {
348+
func formatPublishers(portMappings []cni.PortMapping) []PortPublisher {
325349
mapper := func(pm cni.PortMapping) PortPublisher {
326350
return PortPublisher{
327351
URL: pm.HostIP,
@@ -332,12 +356,8 @@ func formatPublishers(labelMap map[string]string) []PortPublisher {
332356
}
333357

334358
var dockerPorts []PortPublisher
335-
if portMappings, err := portutil.ParsePortsLabel(labelMap); err == nil {
336-
for _, p := range portMappings {
337-
dockerPorts = append(dockerPorts, mapper(p))
338-
}
339-
} else {
340-
log.L.Error(err.Error())
359+
for _, p := range portMappings {
360+
dockerPorts = append(dockerPorts, mapper(p))
341361
}
342362
return dockerPorts
343363
}

cmd/nerdctl/container/container_port.go

Lines changed: 15 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,26 @@ 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+
containerLabels, err := found.Container.Labels(ctx)
97+
if err != nil {
98+
return err
99+
}
100+
ports, err := portutil.LoadPortMappings(dataStore, globalOptions.Namespace, found.Container.ID(), containerLabels)
101+
if err != nil {
102+
return err
103+
}
104+
return containerutil.PrintHostPort(ctx, cmd.OutOrStdout(), found.Container, argPort, argProto, ports)
91105
},
92106
}
93107
req := args[0]

cmd/nerdctl/container/container_run_network_linux_test.go

Lines changed: 4 additions & 5 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"
@@ -409,21 +408,21 @@ func TestRunPort(t *testing.T) {
409408
baseTestRunPort(t, testutil.NginxAlpineImage, testutil.NginxAlpineIndexHTMLSnippet, true)
410409
}
411410

412-
func TestRunWithInvalidPortThenCleanUp(t *testing.T) {
411+
func TestRunWithManyPortsThenCleanUp(t *testing.T) {
413412
testCase := nerdtest.Setup()
414413
// docker does not set label restriction to 4096 bytes
415414
testCase.Require = require.Not(nerdtest.Docker)
416415

417416
testCase.SubTests = []*test.Case{
418417
{
419-
Description: "Run a container with invalid ports, and then clean up.",
418+
Description: "Run a container with many ports, and then clean up.",
420419
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
421420
return helpers.Command("run", "--data-root", data.Temp().Path(), "--rm", "-p", "22200-22299:22200-22299", testutil.CommonImage)
422421
},
423422
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
424423
return &test.Expected{
425-
ExitCode: 1,
426-
Errors: []error{errdefs.ErrInvalidArgument},
424+
ExitCode: 0,
425+
Errors: []error{},
427426
Output: func(stdout string, t tig.T) {
428427
getAddrHash := func(addr string) string {
429428
const addrHashLen = 8

pkg/cmd/container/create.go

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

4342
"github.com/containerd/nerdctl/v2/pkg/annotations"
@@ -61,6 +60,7 @@ import (
6160
"github.com/containerd/nerdctl/v2/pkg/mountutil"
6261
"github.com/containerd/nerdctl/v2/pkg/namestore"
6362
"github.com/containerd/nerdctl/v2/pkg/platformutil"
63+
"github.com/containerd/nerdctl/v2/pkg/portutil"
6464
"github.com/containerd/nerdctl/v2/pkg/referenceutil"
6565
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
6666
"github.com/containerd/nerdctl/v2/pkg/store"
@@ -390,6 +390,11 @@ func Create(ctx context.Context, client *containerd.Client, args []string, netMa
390390
}
391391
cOpts = append(cOpts, ilOpt)
392392

393+
err = portutil.GeneratePortMappingsConfig(dataStore, options.GOptions.Namespace, id, netLabelOpts.PortMappings)
394+
if err != nil {
395+
return nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), fmt.Errorf("Error writing to network-config.json: %v", err)
396+
}
397+
393398
opts = append(opts, propagateInternalContainerdLabelsToOCIAnnotations(),
394399
oci.WithAnnotations(strutil.ConvertKVStringsToMap(options.Annotations)))
395400

@@ -689,7 +694,6 @@ type internalLabels struct {
689694
networks []string
690695
ipAddress string
691696
ip6Address string
692-
ports []cni.PortMapping
693697
macAddress string
694698
dnsServers []string
695699
dnsSearchDomains []string
@@ -741,13 +745,6 @@ func withInternalLabels(internalLabels internalLabels) (containerd.NewContainerO
741745
return nil, err
742746
}
743747
m[labels.Networks] = string(networksJSON)
744-
if len(internalLabels.ports) > 0 {
745-
portsJSON, err := json.Marshal(internalLabels.ports)
746-
if err != nil {
747-
return nil, err
748-
}
749-
m[labels.Ports] = string(portsJSON)
750-
}
751748
if internalLabels.logURI != "" {
752749
m[labels.LogURI] = internalLabels.logURI
753750
logConfigJSON, err := json.Marshal(internalLabels.logConfig)
@@ -909,7 +906,6 @@ func withHealthcheck(options types.ContainerCreateOptions, ensuredImage *imgutil
909906
func (il *internalLabels) loadNetOpts(opts types.NetworkOptions) {
910907
il.hostname = opts.Hostname
911908
il.domainname = opts.Domainname
912-
il.ports = opts.PortMappings
913909
il.ipAddress = opts.IPAddress
914910
il.ip6Address = opts.IP6Address
915911
il.networks = opts.NetworkSlice

pkg/cmd/container/inspect.go

Lines changed: 25 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,19 @@ func (x *containerInspector) Handler(ctx context.Context, found containerwalker.
6879
if err != nil {
6980
return err
7081
}
82+
83+
containerLabels, err := found.Container.Labels(ctx)
84+
if err != nil {
85+
return err
86+
}
87+
ports, err := portutil.LoadPortMappings(x.dataStore, x.namespace, n.ID, containerLabels)
88+
if err != nil {
89+
return err
90+
}
91+
if n.Process != nil && n.Process.NetNS != nil && len(ports) > 0 {
92+
n.Process.NetNS.PortMappings = ports
93+
}
94+
7195
switch x.mode {
7296
case "native":
7397
x.entries = append(x.entries, n)

0 commit comments

Comments
 (0)