Skip to content

Commit eb6b492

Browse files
authored
Merge pull request #4290 from haytok/issue_4027
fix: allow containers to start using a large numbers of ports
2 parents c1dcfa4 + c13417d commit eb6b492

File tree

23 files changed

+351
-168
lines changed

23 files changed

+351
-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

docs/dir.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Files:
3535
- `<CID>-json.log`: used by `nerdctl logs`
3636
- `oci-hook.*.log`: logs of the OCI hook
3737
- `lifecycle.json`: used to store stateful information about the container that can only be retrieved through OCI hooks
38+
- `network-config.json`: used to store port mapping information for containers run with the `-p` option.
3839

3940
### `<DATAROOT>/<ADDRHASH>/names/<NAMESPACE>`
4041
e.g. `/var/lib/nerdctl/1935db59/names/default`

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)