diff --git a/act/container/docker_network.go b/act/container/docker_network.go index aac7599b..e872a713 100644 --- a/act/container/docker_network.go +++ b/act/container/docker_network.go @@ -87,45 +87,54 @@ func NewDockerNetworkCreateExecutor(name string, opts NewDockerNetworkCreateExec return err } defer cli.Close() + return createDockerNetwork(ctx, cli, name, opts) + } +} - // Only create the network if it doesn't exist - networks, err := cli.NetworkList(ctx, client.NetworkListOptions{}) - if err != nil { +func createDockerNetwork(ctx context.Context, cli client.APIClient, name string, opts NewDockerNetworkCreateExecutorInput) error { + // Only create the network if it doesn't exist + networks, err := cli.NetworkList(ctx, client.NetworkListOptions{}) + if err != nil { + return err + } + var options map[string]string + // For Gitea, reduce log noise + // common.Logger(ctx).Debugf("%v", networks) + for _, n := range networks.Items { + if n.Name == name { + common.Logger(ctx).Debugf("Network %v exists", name) + return nil + } + // the daemon's mtu only reaches its default bridge, Docker's default is left out to keep default-network-opts + if mtu := n.Options["com.docker.network.driver.mtu"]; n.Name == network.NetworkBridge && mtu != "" && mtu != "1500" { + options = map[string]string{"com.docker.network.driver.mtu": mtu} + } + } + + for i := range networkCreateAttempts { + if i > 0 { + common.Logger(ctx).Infof("Waiting for a free docker address pool to create network %s", name) + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(time.Duration(i) * networkCreateRetryDelay): + } + } + if _, err = cli.NetworkCreate(ctx, name, client.NetworkCreateOptions{ + Driver: "bridge", + Scope: "local", + EnableIPv4: opts.EnableIPv4, + EnableIPv6: opts.EnableIPv6, + Options: options, + Labels: runnerLabels(opts.RunnerUUID), + }); err == nil { + return nil + } + if !isAddressPoolExhausted(err) { return err } - // For Gitea, reduce log noise - // common.Logger(ctx).Debugf("%v", networks) - for _, n := range networks.Items { - if n.Name == name { - common.Logger(ctx).Debugf("Network %v exists", name) - return nil - } - } - - for i := range networkCreateAttempts { - if i > 0 { - common.Logger(ctx).Infof("Waiting for a free docker address pool to create network %s", name) - select { - case <-ctx.Done(): - return ctx.Err() - case <-time.After(time.Duration(i) * networkCreateRetryDelay): - } - } - if _, err = cli.NetworkCreate(ctx, name, client.NetworkCreateOptions{ - Driver: "bridge", - Scope: "local", - EnableIPv4: opts.EnableIPv4, - EnableIPv6: opts.EnableIPv6, - Labels: runnerLabels(opts.RunnerUUID), - }); err == nil { - return nil - } - if !isAddressPoolExhausted(err) { - return err - } - } - return fmt.Errorf("docker has no address pool left for this job's network, lower runner.capacity or widen default-address-pools in the docker daemon config: %w", err) } + return fmt.Errorf("docker has no address pool left for this job's network, lower runner.capacity or widen default-address-pools in the docker daemon config: %w", err) } func runnerLabels(runnerUUID string) map[string]string { diff --git a/act/container/docker_network_test.go b/act/container/docker_network_test.go index 382f26f9..9fc1f2ae 100644 --- a/act/container/docker_network_test.go +++ b/act/container/docker_network_test.go @@ -57,6 +57,27 @@ func TestIsAddressPoolExhausted(t *testing.T) { assert.False(t, isAddressPoolExhausted(cerrdefs.ErrInvalidArgument.WithMessage("invalid subnet 10.0.0.0/8: it overlaps with an existing network"))) } +func TestCreateDockerNetworkInheritsDaemonMTU(t *testing.T) { + ctx := context.Background() + for _, tc := range []struct { + bridgeOptions, want map[string]string + }{ + { + map[string]string{"com.docker.network.bridge.host_binding_ipv4": "127.0.0.1", "com.docker.network.driver.mtu": "1450"}, + map[string]string{"com.docker.network.driver.mtu": "1450"}, + }, + {map[string]string{"com.docker.network.driver.mtu": "1500"}, nil}, + } { + client := &mockDockerClient{} + client.On("NetworkList", ctx, mobyclient.NetworkListOptions{}). + Return(mobyclient.NetworkListResult{Items: []network.Summary{{Name: network.NetworkBridge, Options: tc.bridgeOptions}}}, nil) + client.On("NetworkCreate", ctx, "job-network", mobyclient.NetworkCreateOptions{Driver: "bridge", Scope: "local", Options: tc.want}). + Return(mobyclient.NetworkCreateResult{}, nil) + require.NoError(t, createDockerNetwork(ctx, client, "job-network", NewDockerNetworkCreateExecutorInput{})) + client.AssertExpectations(t) + } +} + func (m *mockDockerClient) NetworkConnect(ctx context.Context, id string, opts mobyclient.NetworkConnectOptions) (mobyclient.NetworkConnectResult, error) { return mobyclient.NetworkConnectResult{}, m.Called(ctx, id, opts).Error(0) } diff --git a/act/container/docker_run_test.go b/act/container/docker_run_test.go index a2c59c51..be684ad7 100644 --- a/act/container/docker_run_test.go +++ b/act/container/docker_run_test.go @@ -137,6 +137,11 @@ func (m *mockDockerClient) NetworkList(ctx context.Context, opts mobyclient.Netw return args.Get(0).(mobyclient.NetworkListResult), args.Error(1) } +func (m *mockDockerClient) NetworkCreate(ctx context.Context, name string, opts mobyclient.NetworkCreateOptions) (mobyclient.NetworkCreateResult, error) { + args := m.Called(ctx, name, opts) + return args.Get(0).(mobyclient.NetworkCreateResult), args.Error(1) +} + func (m *mockDockerClient) NetworkInspect(ctx context.Context, id string, opts mobyclient.NetworkInspectOptions) (mobyclient.NetworkInspectResult, error) { args := m.Called(ctx, id, opts) return args.Get(0).(mobyclient.NetworkInspectResult), args.Error(1) diff --git a/internal/pkg/config/config.example.yaml b/internal/pkg/config/config.example.yaml index 999ec1f1..dba49655 100644 --- a/internal/pkg/config/config.example.yaml +++ b/internal/pkg/config/config.example.yaml @@ -223,6 +223,7 @@ container: # created by the runner. Omit the entire block to use Docker's defaults. An auto-created # network is labelled com.gitea.runner.uuid=, which is how the idle # cleanup tells its own leftovers apart from those of other runners on the same daemon. + # It also takes the `mtu` set in the Docker daemon config. #network_create_options: # enable_ipv4: true # Omit to use Docker's default (IPv4 enabled). Set false to disable IPv4. # enable_ipv6: false # Omit to use Docker's default (IPv6 disabled). Enabling it requires dockerd started with --ipv6.