@@ -10,11 +10,14 @@ import (
10
10
"fmt"
11
11
"os"
12
12
"os/exec"
13
+ "runtime"
14
+ "strconv"
13
15
"strings"
14
16
"time"
15
17
16
18
"github.com/containerd/log"
17
19
"github.com/pkg/errors"
20
+ "golang.org/x/sys/unix"
18
21
19
22
"github.com/containerd/nydus-snapshotter/config"
20
23
"github.com/containerd/nydus-snapshotter/pkg/daemon"
@@ -36,29 +39,79 @@ const endpointGetBackend string = "/api/v1/daemons/%s/backend"
36
39
// ensure the daemon has reached specified state.
37
40
// - `d` may have not been inserted into daemonStates and store yet.
38
41
func (m * Manager ) StartDaemon (d * daemon.Daemon ) error {
39
- cmd , err := m .BuildDaemonCommand (d , "" , false )
40
- if err != nil {
41
- return errors .Wrapf (err , "create command for daemon %s" , d .ID ())
42
- }
42
+ var nydusdPid int
43
+ if m .delegateNydusd {
44
+ if err := executeInMntNamespace ("/proc/1/ns/mnt" , func () error {
45
+ var err error
46
+ cmd , err := m .BuildDaemonCommand (d , "" , false )
47
+ if err != nil {
48
+ return errors .Wrapf (err , "create command for daemon %s" , d .ID ())
49
+ }
43
50
44
- if err := cmd .Start (); err != nil {
45
- return err
51
+ o , err := cmd .CombinedOutput ()
52
+ if err != nil {
53
+ return errors .Wrapf (err , "start delegatee %s" , d .ID ())
54
+ }
55
+
56
+ output := strings .TrimSpace (string (o ))
57
+ serviceUnit := strings .TrimPrefix (output , "Running as unit:" )
58
+
59
+ // systemctl show --property=MainPID run-rc28cf5fdc45c497cbe6736aea1e2701e.service
60
+ // MainPID=1037947
61
+ cmd = exec .Command ("systemctl" , "show" , "--property=MainPID" , strings .TrimSpace (serviceUnit ))
62
+ o , err = cmd .CombinedOutput ()
63
+ if err != nil {
64
+ return errors .Wrapf (err , "get nydusd MainPID for delegatee %s" , d .ID ())
65
+ }
66
+ output = strings .TrimSpace (string (o ))
67
+ tokens := strings .Split (output , "=" )
68
+ if len (tokens ) != 2 {
69
+ return errors .Errorf ("unexpected output %q from systemctl show" , output )
70
+ }
71
+
72
+ if tokens [0 ] != "MainPID" {
73
+ return errors .Errorf ("unexpected property %q from systemctl show" , tokens [0 ])
74
+ }
75
+
76
+ nydusdPid , err = strconv .Atoi (tokens [1 ])
77
+ if err != nil {
78
+ return errors .Wrapf (err , "parse nydusd pid %q from systemctl show" , tokens [1 ])
79
+ }
80
+
81
+ log .L .Infof ("Delegatee nydusd %s started with pid %d" , d .ID (), nydusdPid )
82
+
83
+ return nil
84
+ }); err != nil {
85
+ return errors .Wrapf (err , "start daemon %s" , d .ID ())
86
+ }
87
+ } else {
88
+ var err error
89
+ cmd , err := m .BuildDaemonCommand (d , "" , false )
90
+ if err != nil {
91
+ return errors .Wrapf (err , "create command for daemon %s" , d .ID ())
92
+ }
93
+
94
+ if err := cmd .Start (); err != nil {
95
+ return err
96
+ }
97
+
98
+ nydusdPid = cmd .Process .Pid
46
99
}
47
100
48
101
d .Lock ()
49
102
defer d .Unlock ()
50
103
51
- d .States .ProcessID = cmd . Process . Pid
104
+ d .States .ProcessID = nydusdPid
52
105
53
106
// Profile nydusd daemon CPU usage during its startup.
54
107
if config .GetDaemonProfileCPUDuration () > 0 {
55
- processState , err := metrics .GetProcessStat (cmd . Process . Pid )
108
+ processState , err := metrics .GetProcessStat (nydusdPid )
56
109
if err == nil {
57
110
timer := time .NewTimer (time .Duration (config .GetDaemonProfileCPUDuration ()) * time .Second )
58
111
59
112
go func () {
60
113
<- timer .C
61
- currentProcessState , err := metrics .GetProcessStat (cmd . Process . Pid )
114
+ currentProcessState , err := metrics .GetProcessStat (nydusdPid )
62
115
if err != nil {
63
116
log .L .WithError (err ).Warnf ("Failed to get daemon %s process state." , d .ID ())
64
117
return
@@ -75,7 +128,7 @@ func (m *Manager) StartDaemon(d *daemon.Daemon) error {
75
128
// TODO: Is it right to commit daemon before nydusd successfully started?
76
129
// And it brings extra latency of accessing DB. Only write daemon record to
77
130
// DB when nydusd is started?
78
- err = m .UpdateDaemon (d )
131
+ err : = m .UpdateDaemon (d )
79
132
if err != nil {
80
133
// Nothing we can do, just ignore it for now
81
134
log .L .Errorf ("Fail to update daemon info (%+v) to DB: %v" , d , err )
@@ -212,14 +265,49 @@ func (m *Manager) BuildDaemonCommand(d *daemon.Daemon, bin string, upgrade bool)
212
265
nydusdPath = m .NydusdBinaryPath
213
266
}
214
267
268
+ var cmd * exec.Cmd
215
269
log .L .Infof ("nydusd command: %s %s" , nydusdPath , strings .Join (args , " " ))
270
+ if m .delegateNydusd {
271
+ delegateeCmdFlags := []string {"--description" , "nydusd" }
272
+ args = append ([]string {nydusdPath }, args ... )
273
+ args = append (delegateeCmdFlags , args ... )
274
+ cmd = exec .Command ("systemd-run" , args ... )
275
+ } else {
276
+ cmd = exec .Command (nydusdPath , args ... )
277
+ // nydusd standard output and standard error rather than its logs are
278
+ // always redirected to snapshotter's respectively
279
+ cmd .Stdout = os .Stdout
280
+ cmd .Stderr = os .Stderr
281
+ }
216
282
217
- cmd := exec .Command (nydusdPath , args ... )
283
+ return cmd , nil
284
+ }
218
285
219
- // nydusd standard output and standard error rather than its logs are
220
- // always redirected to snapshotter's respectively
221
- cmd . Stdout = os . Stdout
222
- cmd . Stderr = os . Stderr
286
+ // executeInMntNamespace runs a function in the specified namespace and ensures thread isolation
287
+ func executeInMntNamespace ( mntNamespacePath string , fn func () error ) error {
288
+ runtime . LockOSThread ()
289
+ defer runtime . UnlockOSThread ()
223
290
224
- return cmd , nil
291
+ // Detach from the shared fs of the rest of the Go process in order to
292
+ // be able to CLONE_NEWNS.
293
+ if err := unix .Unshare (unix .CLONE_FS ); err != nil {
294
+ return errors .Wrap (err , "failed to unshare filesystem namespace" )
295
+ }
296
+
297
+ targetNS , err := os .Open (mntNamespacePath )
298
+ if err != nil {
299
+ return errors .Wrapf (err , "failed to open target mnt namespace %q" , mntNamespacePath )
300
+ }
301
+ defer targetNS .Close ()
302
+
303
+ if err := unix .Setns (int (targetNS .Fd ()), unix .CLONE_NEWNS ); err != nil {
304
+ return errors .Wrapf (err , "failed to enter the namespace %q" , mntNamespacePath )
305
+ }
306
+
307
+ if err := fn (); err != nil {
308
+ log .L .WithError (err ).Errorf ("failed to execute in the mnt namespace %s" , mntNamespacePath )
309
+ return err
310
+ }
311
+
312
+ return err
225
313
}
0 commit comments