Browse code

builder: fix bridge networking when using buildkit

Signed-off-by: Tibor Vass <tibor@docker.com>

Tibor Vass authored on 2018/08/22 11:37:32
Showing 11 changed files
... ...
@@ -37,6 +37,7 @@ func init() {
37 37
 type Opt struct {
38 38
 	SessionManager    *session.Manager
39 39
 	Root              string
40
+	NetnsRoot         string
40 41
 	Dist              images.DistributionServices
41 42
 	NetworkController libnetwork.NetworkController
42 43
 }
... ...
@@ -90,7 +90,7 @@ func newController(rt http.RoundTripper, opt Opt) (*control.Controller, error) {
90 90
 		return nil, err
91 91
 	}
92 92
 
93
-	exec, err := newExecutor(root, opt.NetworkController)
93
+	exec, err := newExecutor(root, opt.NetnsRoot, opt.NetworkController)
94 94
 	if err != nil {
95 95
 		return nil, err
96 96
 	}
... ...
@@ -3,41 +3,46 @@
3 3
 package buildkit
4 4
 
5 5
 import (
6
-	"fmt"
6
+	"os"
7 7
 	"path/filepath"
8
+	"strconv"
8 9
 	"sync"
9 10
 
10 11
 	"github.com/docker/libnetwork"
11 12
 	"github.com/moby/buildkit/executor"
12 13
 	"github.com/moby/buildkit/executor/runcexecutor"
13 14
 	"github.com/moby/buildkit/identity"
15
+	"github.com/moby/buildkit/solver/pb"
14 16
 	"github.com/moby/buildkit/util/network"
15
-	"github.com/pkg/errors"
16
-	"github.com/sirupsen/logrus"
17
+	specs "github.com/opencontainers/runtime-spec/specs-go"
17 18
 )
18 19
 
19 20
 const networkName = "bridge"
20 21
 
21
-func newExecutor(root string, net libnetwork.NetworkController) (executor.Executor, error) {
22
-	// FIXME: fix bridge networking
23
-	_ = bridgeProvider{}
22
+func newExecutor(root, netnsRoot string, net libnetwork.NetworkController) (executor.Executor, error) {
23
+	networkProviders := map[pb.NetMode]network.Provider{
24
+		pb.NetMode_UNSET: &bridgeProvider{NetworkController: net, netnsRoot: netnsRoot},
25
+		pb.NetMode_HOST:  network.NewHostProvider(),
26
+		pb.NetMode_NONE:  network.NewNoneProvider(),
27
+	}
24 28
 	return runcexecutor.New(runcexecutor.Opt{
25 29
 		Root:              filepath.Join(root, "executor"),
26 30
 		CommandCandidates: []string{"docker-runc", "runc"},
27
-	}, nil)
31
+	}, networkProviders)
28 32
 }
29 33
 
30 34
 type bridgeProvider struct {
31 35
 	libnetwork.NetworkController
36
+	netnsRoot string
32 37
 }
33 38
 
34
-func (p *bridgeProvider) NewInterface() (network.Interface, error) {
39
+func (p *bridgeProvider) New() (network.Namespace, error) {
35 40
 	n, err := p.NetworkByName(networkName)
36 41
 	if err != nil {
37 42
 		return nil, err
38 43
 	}
39 44
 
40
-	iface := &lnInterface{ready: make(chan struct{})}
45
+	iface := &lnInterface{ready: make(chan struct{}), provider: p}
41 46
 	iface.Once.Do(func() {
42 47
 		go iface.init(p.NetworkController, n)
43 48
 	})
... ...
@@ -45,33 +50,13 @@ func (p *bridgeProvider) NewInterface() (network.Interface, error) {
45 45
 	return iface, nil
46 46
 }
47 47
 
48
-func (p *bridgeProvider) Release(iface network.Interface) error {
49
-	go func() {
50
-		if err := p.release(iface); err != nil {
51
-			logrus.Errorf("%s", err)
52
-		}
53
-	}()
54
-	return nil
55
-}
56
-
57
-func (p *bridgeProvider) release(iface network.Interface) error {
58
-	li, ok := iface.(*lnInterface)
59
-	if !ok {
60
-		return errors.Errorf("invalid interface %T", iface)
61
-	}
62
-	err := li.sbx.Delete()
63
-	if err1 := li.ep.Delete(true); err1 != nil && err == nil {
64
-		err = err1
65
-	}
66
-	return err
67
-}
68
-
69 48
 type lnInterface struct {
70 49
 	ep  libnetwork.Endpoint
71 50
 	sbx libnetwork.Sandbox
72 51
 	sync.Once
73
-	err   error
74
-	ready chan struct{}
52
+	err      error
53
+	ready    chan struct{}
54
+	provider *bridgeProvider
75 55
 }
76 56
 
77 57
 func (iface *lnInterface) init(c libnetwork.NetworkController, n libnetwork.Network) {
... ...
@@ -99,14 +84,26 @@ func (iface *lnInterface) init(c libnetwork.NetworkController, n libnetwork.Netw
99 99
 	iface.ep = ep
100 100
 }
101 101
 
102
-func (iface *lnInterface) Set(pid int) error {
102
+func (iface *lnInterface) Set(s *specs.Spec) {
103 103
 	<-iface.ready
104 104
 	if iface.err != nil {
105
-		return iface.err
105
+		return
106
+	}
107
+	// attach netns to bridge within the container namespace, using reexec in a prestart hook
108
+	s.Hooks = &specs.Hooks{
109
+		Prestart: []specs.Hook{{
110
+			Path: filepath.Join("/proc", strconv.Itoa(os.Getpid()), "exe"),
111
+			Args: []string{"libnetwork-setkey", iface.sbx.ContainerID(), iface.provider.NetworkController.ID()},
112
+		}},
106 113
 	}
107
-	return iface.sbx.SetKey(fmt.Sprintf("/proc/%d/ns/net", pid))
108 114
 }
109 115
 
110
-func (iface *lnInterface) Remove(pid int) error {
111
-	return nil
116
+func (iface *lnInterface) Close() error {
117
+	<-iface.ready
118
+	err := iface.sbx.Delete()
119
+	if iface.err != nil {
120
+		// iface.err takes precedence over cleanup errors
121
+		return iface.err
122
+	}
123
+	return err
112 124
 }
... ...
@@ -10,7 +10,7 @@ import (
10 10
 	"github.com/moby/buildkit/executor"
11 11
 )
12 12
 
13
-func newExecutor(_ string, _ libnetwork.NetworkController) (executor.Executor, error) {
13
+func newExecutor(_, _ string, _ libnetwork.NetworkController) (executor.Executor, error) {
14 14
 	return &winExecutor{}, nil
15 15
 }
16 16
 
... ...
@@ -288,6 +288,7 @@ func newRouterOptions(config *config.Config, daemon *daemon.Daemon) (routerOptio
288 288
 	bk, err := buildkit.New(buildkit.Opt{
289 289
 		SessionManager:    sm,
290 290
 		Root:              filepath.Join(config.Root, "buildkit"),
291
+		NetnsRoot:         filepath.Join(config.ExecRoot, "netns"),
291 292
 		Dist:              daemon.DistributionServices(),
292 293
 		NetworkController: daemon.NetworkController(),
293 294
 	})
... ...
@@ -26,7 +26,7 @@ github.com/imdario/mergo v0.3.6
26 26
 golang.org/x/sync 1d60e4601c6fd243af51cc01ddf169918a5407ca
27 27
 
28 28
 # buildkit
29
-github.com/moby/buildkit 49906c62925ed429ec9174a0b6869982967f1a39
29
+github.com/moby/buildkit e1cd06ad6b74e4b747306c4408c451b3b6d87a89
30 30
 github.com/tonistiigi/fsutil b19464cd1b6a00773b4f2eb7acf9c30426f9df42
31 31
 github.com/grpc-ecosystem/grpc-opentracing 8e809c8a86450a29b90dcc9efbf062d0fe6d9746
32 32
 github.com/opentracing/opentracing-go 1361b9cd60be79c4c3a7fa9841b3c132e40066a7
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	"github.com/mitchellh/hashstructure"
15 15
 	"github.com/moby/buildkit/executor"
16 16
 	"github.com/moby/buildkit/snapshot"
17
+	"github.com/moby/buildkit/util/network"
17 18
 	specs "github.com/opencontainers/runtime-spec/specs-go"
18 19
 	"github.com/pkg/errors"
19 20
 )
... ...
@@ -21,7 +22,7 @@ import (
21 21
 // Ideally we don't have to import whole containerd just for the default spec
22 22
 
23 23
 // GenerateSpec generates spec using containerd functionality.
24
-func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mount, id, resolvConf, hostsFile string, hostNetwork bool, opts ...oci.SpecOpts) (*specs.Spec, func(), error) {
24
+func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mount, id, resolvConf, hostsFile string, namespace network.Namespace, opts ...oci.SpecOpts) (*specs.Spec, func(), error) {
25 25
 	c := &containers.Container{
26 26
 		ID: id,
27 27
 	}
... ...
@@ -30,16 +31,15 @@ func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mou
30 30
 		ctx = namespaces.WithNamespace(ctx, "buildkit")
31 31
 	}
32 32
 
33
-	if hostNetwork {
34
-		opts = append(opts, oci.WithHostNamespace(specs.NetworkNamespace))
35
-	}
36
-
37 33
 	// Note that containerd.GenerateSpec is namespaced so as to make
38 34
 	// specs.Linux.CgroupsPath namespaced
39 35
 	s, err := oci.GenerateSpec(ctx, nil, c, opts...)
40 36
 	if err != nil {
41 37
 		return nil, nil, err
42 38
 	}
39
+	// set the networking information on the spec
40
+	namespace.Set(s)
41
+
43 42
 	s.Process.Args = meta.Args
44 43
 	s.Process.Env = meta.Env
45 44
 	s.Process.Cwd = meta.Cwd
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"path/filepath"
11 11
 	"strconv"
12 12
 	"strings"
13
-	"sync"
14 13
 	"syscall"
15 14
 
16 15
 	"github.com/containerd/containerd/contrib/seccomp"
... ...
@@ -26,7 +25,6 @@ import (
26 26
 	"github.com/moby/buildkit/util/network"
27 27
 	rootlessspecconv "github.com/moby/buildkit/util/rootless/specconv"
28 28
 	"github.com/moby/buildkit/util/system"
29
-	runcsystem "github.com/opencontainers/runc/libcontainer/system"
30 29
 	specs "github.com/opencontainers/runtime-spec/specs-go"
31 30
 	"github.com/pkg/errors"
32 31
 	"github.com/sirupsen/logrus"
... ...
@@ -43,14 +41,14 @@ type Opt struct {
43 43
 var defaultCommandCandidates = []string{"buildkit-runc", "runc"}
44 44
 
45 45
 type runcExecutor struct {
46
-	runc            *runc.Runc
47
-	root            string
48
-	cmd             string
49
-	rootless        bool
50
-	networkProvider network.Provider
46
+	runc             *runc.Runc
47
+	root             string
48
+	cmd              string
49
+	rootless         bool
50
+	networkProviders map[pb.NetMode]network.Provider
51 51
 }
52 52
 
53
-func New(opt Opt, networkProvider network.Provider) (executor.Executor, error) {
53
+func New(opt Opt, networkProviders map[pb.NetMode]network.Provider) (executor.Executor, error) {
54 54
 	cmds := opt.CommandCandidates
55 55
 	if cmds == nil {
56 56
 		cmds = defaultCommandCandidates
... ...
@@ -70,10 +68,6 @@ func New(opt Opt, networkProvider network.Provider) (executor.Executor, error) {
70 70
 
71 71
 	root := opt.Root
72 72
 
73
-	if err := setSubReaper(); err != nil {
74
-		return nil, err
75
-	}
76
-
77 73
 	if err := os.MkdirAll(root, 0700); err != nil {
78 74
 		return nil, errors.Wrapf(err, "failed to create %s", root)
79 75
 	}
... ...
@@ -98,36 +92,28 @@ func New(opt Opt, networkProvider network.Provider) (executor.Executor, error) {
98 98
 	}
99 99
 
100 100
 	w := &runcExecutor{
101
-		runc:            runtime,
102
-		root:            root,
103
-		rootless:        opt.Rootless,
104
-		networkProvider: networkProvider,
101
+		runc:             runtime,
102
+		root:             root,
103
+		rootless:         opt.Rootless,
104
+		networkProviders: networkProviders,
105 105
 	}
106 106
 	return w, nil
107 107
 }
108 108
 
109 109
 func (w *runcExecutor) Exec(ctx context.Context, meta executor.Meta, root cache.Mountable, mounts []executor.Mount, stdin io.ReadCloser, stdout, stderr io.WriteCloser) error {
110
-	var iface network.Interface
111
-	// FIXME: still uses host if no provider configured
112
-	if meta.NetMode == pb.NetMode_UNSET {
113
-		if w.networkProvider != nil {
114
-			var err error
115
-			iface, err = w.networkProvider.NewInterface()
116
-			if err != nil || iface == nil {
117
-				meta.NetMode = pb.NetMode_HOST
118
-			}
119
-		} else {
120
-			meta.NetMode = pb.NetMode_HOST
121
-		}
110
+	provider, ok := w.networkProviders[meta.NetMode]
111
+	if !ok {
112
+		return errors.Errorf("unknown network mode %s", meta.NetMode)
113
+	}
114
+	namespace, err := provider.New()
115
+	if err != nil {
116
+		return err
122 117
 	}
118
+	defer namespace.Close()
119
+
123 120
 	if meta.NetMode == pb.NetMode_HOST {
124 121
 		logrus.Info("enabling HostNetworking")
125 122
 	}
126
-	defer func() {
127
-		if iface != nil {
128
-			w.networkProvider.Release(iface)
129
-		}
130
-	}()
131 123
 
132 124
 	resolvConf, err := oci.GetResolvConf(ctx, w.root)
133 125
 	if err != nil {
... ...
@@ -187,7 +173,7 @@ func (w *runcExecutor) Exec(ctx context.Context, meta executor.Meta, root cache.
187 187
 	if meta.ReadonlyRootFS {
188 188
 		opts = append(opts, containerdoci.WithRootFSReadonly())
189 189
 	}
190
-	spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, meta.NetMode == pb.NetMode_HOST, opts...)
190
+	spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, opts...)
191 191
 	if err != nil {
192 192
 		return err
193 193
 	}
... ...
@@ -225,75 +211,14 @@ func (w *runcExecutor) Exec(ctx context.Context, meta executor.Meta, root cache.
225 225
 	}
226 226
 	defer forwardIO.Close()
227 227
 
228
-	pidFilePath := filepath.Join(w.root, "runc_pid_"+identity.NewID())
229
-	defer os.RemoveAll(pidFilePath)
230
-
231 228
 	logrus.Debugf("> creating %s %v", id, meta.Args)
232
-	err = w.runc.Create(ctx, id, bundle, &runc.CreateOpts{
233
-		PidFile: pidFilePath,
234
-		IO:      forwardIO,
229
+	status, err := w.runc.Run(ctx, id, bundle, &runc.CreateOpts{
230
+		IO: forwardIO,
235 231
 	})
236 232
 	if err != nil {
237 233
 		return err
238 234
 	}
239
-	forwardIO.release()
240 235
 
241
-	defer func() {
242
-		go func() {
243
-			if err := w.runc.Delete(context.TODO(), id, &runc.DeleteOpts{}); err != nil {
244
-				logrus.Errorf("failed to delete %s: %+v", id, err)
245
-			}
246
-		}()
247
-	}()
248
-
249
-	dt, err := ioutil.ReadFile(pidFilePath)
250
-	if err != nil {
251
-		return err
252
-	}
253
-	pid, err := strconv.Atoi(string(dt))
254
-	if err != nil {
255
-		return err
256
-	}
257
-
258
-	done := make(chan struct{})
259
-	defer close(done)
260
-
261
-	go func() {
262
-		select {
263
-		case <-done:
264
-		case <-ctx.Done():
265
-			syscall.Kill(-pid, syscall.SIGKILL)
266
-		}
267
-	}()
268
-
269
-	if iface != nil {
270
-		if err := iface.Set(pid); err != nil {
271
-			return errors.Wrap(err, "could not set the network")
272
-		}
273
-		defer func() {
274
-			iface.Remove(pid)
275
-		}()
276
-	}
277
-
278
-	err = w.runc.Start(ctx, id)
279
-	if err != nil {
280
-		return err
281
-	}
282
-
283
-	p, err := os.FindProcess(pid)
284
-	if err != nil {
285
-		return err
286
-	}
287
-
288
-	status := 0
289
-	ps, err := p.Wait()
290
-	if err != nil {
291
-		status = 255
292
-	}
293
-
294
-	if ws, ok := ps.Sys().(syscall.WaitStatus); ok {
295
-		status = ws.ExitStatus()
296
-	}
297 236
 	if status != 0 {
298 237
 		return errors.Errorf("exit code: %d", status)
299 238
 	}
... ...
@@ -336,7 +261,7 @@ func newForwardIO(stdin io.ReadCloser, stdout, stderr io.WriteCloser) (f *forwar
336 336
 }
337 337
 
338 338
 func (s *forwardIO) Close() error {
339
-	s.release()
339
+	s.CloseAfterStart()
340 340
 	var err error
341 341
 	for _, cl := range s.toClose {
342 342
 		if err1 := cl.Close(); err == nil {
... ...
@@ -348,11 +273,12 @@ func (s *forwardIO) Close() error {
348 348
 }
349 349
 
350 350
 // release releases active FDs if the process doesn't need them any more
351
-func (s *forwardIO) release() {
351
+func (s *forwardIO) CloseAfterStart() error {
352 352
 	for _, cl := range s.toRelease {
353 353
 		cl.Close()
354 354
 	}
355 355
 	s.toRelease = nil
356
+	return nil
356 357
 }
357 358
 
358 359
 func (s *forwardIO) Set(cmd *exec.Cmd) {
... ...
@@ -401,16 +327,6 @@ func (s *forwardIO) writeCloserToFile(wc io.WriteCloser) (*os.File, error) {
401 401
 	return pw, nil
402 402
 }
403 403
 
404
-var subReaperOnce sync.Once
405
-var subReaperError error
406
-
407
-func setSubReaper() error {
408
-	subReaperOnce.Do(func() {
409
-		subReaperError = runcsystem.SetSubreaper(1)
410
-	})
411
-	return subReaperError
412
-}
413
-
414 404
 func (s *forwardIO) Stdin() io.WriteCloser {
415 405
 	return nil
416 406
 }
417 407
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+package network
1
+
2
+import (
3
+	"github.com/containerd/containerd/oci"
4
+	specs "github.com/opencontainers/runtime-spec/specs-go"
5
+)
6
+
7
+func NewHostProvider() Provider {
8
+	return &host{}
9
+}
10
+
11
+type host struct {
12
+}
13
+
14
+func (h *host) New() (Namespace, error) {
15
+	return &hostNS{}, nil
16
+}
17
+
18
+type hostNS struct {
19
+}
20
+
21
+func (h *hostNS) Set(s *specs.Spec) {
22
+	oci.WithHostNamespace(specs.NetworkNamespace)(nil, nil, nil, s)
23
+}
24
+
25
+func (h *hostNS) Close() error {
26
+	return nil
27
+}
... ...
@@ -1,17 +1,32 @@
1 1
 package network
2 2
 
3
+import (
4
+	"io"
5
+
6
+	"github.com/moby/buildkit/solver/pb"
7
+	specs "github.com/opencontainers/runtime-spec/specs-go"
8
+)
9
+
10
+// Default returns the default network provider set
11
+func Default() map[pb.NetMode]Provider {
12
+	return map[pb.NetMode]Provider{
13
+		// FIXME: still uses host if no provider configured
14
+		pb.NetMode_UNSET: NewHostProvider(),
15
+		pb.NetMode_HOST:  NewHostProvider(),
16
+		pb.NetMode_NONE:  NewNoneProvider(),
17
+	}
18
+}
19
+
3 20
 // Provider interface for Network
4 21
 type Provider interface {
5
-	NewInterface() (Interface, error)
6
-	Release(Interface) error
22
+	New() (Namespace, error)
7 23
 }
8 24
 
9
-// Interface of network for workers
10
-type Interface interface {
11
-	// Set the pid with network interace namespace
12
-	Set(int) error
13
-	// Removes the network interface
14
-	Remove(int) error
25
+// Namespace of network for workers
26
+type Namespace interface {
27
+	io.Closer
28
+	// Set the namespace on the spec
29
+	Set(*specs.Spec)
15 30
 }
16 31
 
17 32
 // NetworkOpts hold network options
18 33
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+package network
1
+
2
+import (
3
+	specs "github.com/opencontainers/runtime-spec/specs-go"
4
+)
5
+
6
+func NewNoneProvider() Provider {
7
+	return &none{}
8
+}
9
+
10
+type none struct {
11
+}
12
+
13
+func (h *none) New() (Namespace, error) {
14
+	return &noneNS{}, nil
15
+}
16
+
17
+type noneNS struct {
18
+}
19
+
20
+func (h *noneNS) Set(s *specs.Spec) {
21
+}
22
+
23
+func (h *noneNS) Close() error {
24
+	return nil
25
+}