瀏覽代碼

Add support for `init` on services

It's already supported by `swarmkit`, and act the same as
`HostConfig.Init` on container creation.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
Vincent Demeester 7 年之前
父節點
當前提交
e401b88e59

+ 4 - 0
api/swagger.yaml

@@ -2721,6 +2721,10 @@ definitions:
               - "default"
               - "default"
               - "process"
               - "process"
               - "hyperv"
               - "hyperv"
+          Init:
+            description: "Run an init inside the container that forwards signals and reaps processes. This field is omitted if empty, and the default (as configured on the daemon) is used."
+            type: "boolean"
+            x-nullable: true
       NetworkAttachmentSpec:
       NetworkAttachmentSpec:
         description: |
         description: |
           Read-only spec type for non-swarm containers attached to swarm overlay
           Read-only spec type for non-swarm containers attached to swarm overlay

+ 1 - 0
api/types/swarm/container.go

@@ -55,6 +55,7 @@ type ContainerSpec struct {
 	User            string                  `json:",omitempty"`
 	User            string                  `json:",omitempty"`
 	Groups          []string                `json:",omitempty"`
 	Groups          []string                `json:",omitempty"`
 	Privileges      *Privileges             `json:",omitempty"`
 	Privileges      *Privileges             `json:",omitempty"`
+	Init            *bool                   `json:",omitempty"`
 	StopSignal      string                  `json:",omitempty"`
 	StopSignal      string                  `json:",omitempty"`
 	TTY             bool                    `json:",omitempty"`
 	TTY             bool                    `json:",omitempty"`
 	OpenStdin       bool                    `json:",omitempty"`
 	OpenStdin       bool                    `json:",omitempty"`

+ 17 - 0
daemon/cluster/convert/container.go

@@ -35,6 +35,7 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
 		Secrets:    secretReferencesFromGRPC(c.Secrets),
 		Secrets:    secretReferencesFromGRPC(c.Secrets),
 		Configs:    configReferencesFromGRPC(c.Configs),
 		Configs:    configReferencesFromGRPC(c.Configs),
 		Isolation:  IsolationFromGRPC(c.Isolation),
 		Isolation:  IsolationFromGRPC(c.Isolation),
+		Init:       initFromGRPC(c.Init),
 	}
 	}
 
 
 	if c.DNSConfig != nil {
 	if c.DNSConfig != nil {
@@ -119,6 +120,21 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
 	return containerSpec
 	return containerSpec
 }
 }
 
 
+func initFromGRPC(v *gogotypes.BoolValue) *bool {
+	if v == nil {
+		return nil
+	}
+	value := v.GetValue()
+	return &value
+}
+
+func initToGRPC(v *bool) *gogotypes.BoolValue {
+	if v == nil {
+		return nil
+	}
+	return &gogotypes.BoolValue{Value: *v}
+}
+
 func secretReferencesToGRPC(sr []*types.SecretReference) []*swarmapi.SecretReference {
 func secretReferencesToGRPC(sr []*types.SecretReference) []*swarmapi.SecretReference {
 	refs := make([]*swarmapi.SecretReference, 0, len(sr))
 	refs := make([]*swarmapi.SecretReference, 0, len(sr))
 	for _, s := range sr {
 	for _, s := range sr {
@@ -234,6 +250,7 @@ func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
 		Secrets:    secretReferencesToGRPC(c.Secrets),
 		Secrets:    secretReferencesToGRPC(c.Secrets),
 		Configs:    configReferencesToGRPC(c.Configs),
 		Configs:    configReferencesToGRPC(c.Configs),
 		Isolation:  isolationToGRPC(c.Isolation),
 		Isolation:  isolationToGRPC(c.Isolation),
+		Init:       initToGRPC(c.Init),
 	}
 	}
 
 
 	if c.DNSConfig != nil {
 	if c.DNSConfig != nil {

+ 9 - 0
daemon/cluster/executor/container/container.go

@@ -172,6 +172,14 @@ func (c *containerConfig) isolation() enginecontainer.Isolation {
 	return convert.IsolationFromGRPC(c.spec().Isolation)
 	return convert.IsolationFromGRPC(c.spec().Isolation)
 }
 }
 
 
+func (c *containerConfig) init() *bool {
+	if c.spec().Init == nil {
+		return nil
+	}
+	init := c.spec().Init.GetValue()
+	return &init
+}
+
 func (c *containerConfig) exposedPorts() map[nat.Port]struct{} {
 func (c *containerConfig) exposedPorts() map[nat.Port]struct{} {
 	exposedPorts := make(map[nat.Port]struct{})
 	exposedPorts := make(map[nat.Port]struct{})
 	if c.task.Endpoint == nil {
 	if c.task.Endpoint == nil {
@@ -355,6 +363,7 @@ func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
 		Mounts:         c.mounts(),
 		Mounts:         c.mounts(),
 		ReadonlyRootfs: c.spec().ReadOnly,
 		ReadonlyRootfs: c.spec().ReadOnly,
 		Isolation:      c.isolation(),
 		Isolation:      c.isolation(),
+		Init:           c.init(),
 	}
 	}
 
 
 	if c.spec().DNSConfig != nil {
 	if c.spec().DNSConfig != nil {

+ 8 - 0
integration/internal/swarm/service.go

@@ -86,6 +86,14 @@ func defaultServiceSpec() swarmtypes.ServiceSpec {
 	return spec
 	return spec
 }
 }
 
 
+// ServiceWithInit sets whether the service should use init or not
+func ServiceWithInit(b *bool) func(*swarmtypes.ServiceSpec) {
+	return func(spec *swarmtypes.ServiceSpec) {
+		ensureContainerSpec(spec)
+		spec.TaskTemplate.ContainerSpec.Init = b
+	}
+}
+
 // ServiceWithImage sets the image to use for the service
 // ServiceWithImage sets the image to use for the service
 func ServiceWithImage(image string) func(*swarmtypes.ServiceSpec) {
 func ServiceWithImage(image string) func(*swarmtypes.ServiceSpec) {
 	return func(spec *swarmtypes.ServiceSpec) {
 	return func(spec *swarmtypes.ServiceSpec) {

+ 54 - 0
integration/service/create_test.go

@@ -2,6 +2,7 @@ package service // import "github.com/docker/docker/integration/service"
 
 
 import (
 import (
 	"context"
 	"context"
+	"fmt"
 	"io/ioutil"
 	"io/ioutil"
 	"testing"
 	"testing"
 	"time"
 	"time"
@@ -11,11 +12,64 @@ import (
 	swarmtypes "github.com/docker/docker/api/types/swarm"
 	swarmtypes "github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/client"
 	"github.com/docker/docker/client"
 	"github.com/docker/docker/integration/internal/swarm"
 	"github.com/docker/docker/integration/internal/swarm"
+	"github.com/docker/docker/internal/test/daemon"
 	"github.com/gotestyourself/gotestyourself/assert"
 	"github.com/gotestyourself/gotestyourself/assert"
 	is "github.com/gotestyourself/gotestyourself/assert/cmp"
 	is "github.com/gotestyourself/gotestyourself/assert/cmp"
 	"github.com/gotestyourself/gotestyourself/poll"
 	"github.com/gotestyourself/gotestyourself/poll"
 )
 )
 
 
+func TestServiceCreateInit(t *testing.T) {
+	defer setupTest(t)()
+	t.Run("daemonInitDisabled", testServiceCreateInit(false))
+	t.Run("daemonInitEnabled", testServiceCreateInit(true))
+}
+
+func testServiceCreateInit(daemonEnabled bool) func(t *testing.T) {
+	return func(t *testing.T) {
+		var ops = []func(*daemon.Daemon){}
+
+		if daemonEnabled {
+			ops = append(ops, daemon.WithInit)
+		}
+		d := swarm.NewSwarm(t, testEnv, ops...)
+		defer d.Stop(t)
+		client := d.NewClientT(t)
+		defer client.Close()
+
+		booleanTrue := true
+		booleanFalse := false
+
+		serviceID := swarm.CreateService(t, d)
+		poll.WaitOn(t, serviceRunningTasksCount(client, serviceID, 1), swarm.ServicePoll)
+		i := inspectServiceContainer(t, client, serviceID)
+		// HostConfig.Init == nil means that it delegates to daemon configuration
+		assert.Check(t, i.HostConfig.Init == nil)
+
+		serviceID = swarm.CreateService(t, d, swarm.ServiceWithInit(&booleanTrue))
+		poll.WaitOn(t, serviceRunningTasksCount(client, serviceID, 1), swarm.ServicePoll)
+		i = inspectServiceContainer(t, client, serviceID)
+		assert.Check(t, is.Equal(true, *i.HostConfig.Init))
+
+		serviceID = swarm.CreateService(t, d, swarm.ServiceWithInit(&booleanFalse))
+		poll.WaitOn(t, serviceRunningTasksCount(client, serviceID, 1), swarm.ServicePoll)
+		i = inspectServiceContainer(t, client, serviceID)
+		assert.Check(t, is.Equal(false, *i.HostConfig.Init))
+	}
+}
+
+func inspectServiceContainer(t *testing.T, client client.APIClient, serviceID string) types.ContainerJSON {
+	t.Helper()
+	filter := filters.NewArgs()
+	filter.Add("label", fmt.Sprintf("com.docker.swarm.service.id=%s", serviceID))
+	containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{Filters: filter})
+	assert.NilError(t, err)
+	assert.Check(t, is.Len(containers, 1))
+
+	i, err := client.ContainerInspect(context.Background(), containers[0].ID)
+	assert.NilError(t, err)
+	return i
+}
+
 func TestCreateServiceMultipleTimes(t *testing.T) {
 func TestCreateServiceMultipleTimes(t *testing.T) {
 	defer setupTest(t)()
 	defer setupTest(t)()
 	d := swarm.NewSwarm(t, testEnv)
 	d := swarm.NewSwarm(t, testEnv)

+ 5 - 1
internal/test/daemon/daemon.go

@@ -66,6 +66,7 @@ type Daemon struct {
 	userlandProxy bool
 	userlandProxy bool
 	execRoot      string
 	execRoot      string
 	experimental  bool
 	experimental  bool
+	init          bool
 	dockerdBinary string
 	dockerdBinary string
 	log           logT
 	log           logT
 
 
@@ -229,7 +230,10 @@ func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error {
 		fmt.Sprintf("--userland-proxy=%t", d.userlandProxy),
 		fmt.Sprintf("--userland-proxy=%t", d.userlandProxy),
 	)
 	)
 	if d.experimental {
 	if d.experimental {
-		args = append(args, "--experimental", "--init")
+		args = append(args, "--experimental")
+	}
+	if d.init {
+		args = append(args, "--init")
 	}
 	}
 	if !(d.UseDefaultHost || d.UseDefaultTLSHost) {
 	if !(d.UseDefaultHost || d.UseDefaultTLSHost) {
 		args = append(args, []string{"--host", d.Sock()}...)
 		args = append(args, []string{"--host", d.Sock()}...)

+ 6 - 0
internal/test/daemon/ops.go

@@ -5,6 +5,12 @@ import "github.com/docker/docker/internal/test/environment"
 // WithExperimental sets the daemon in experimental mode
 // WithExperimental sets the daemon in experimental mode
 func WithExperimental(d *Daemon) {
 func WithExperimental(d *Daemon) {
 	d.experimental = true
 	d.experimental = true
+	d.init = true
+}
+
+// WithInit sets the daemon init
+func WithInit(d *Daemon) {
+	d.init = true
 }
 }
 
 
 // WithDockerdBinary sets the dockerd binary to the specified one
 // WithDockerdBinary sets the dockerd binary to the specified one