Skip to content

Commit 1989568

Browse files
committed
feat(shim): wire rootfs view into task lifecycle
Prepare a read-only rootfs view after guest rootfs choice on Create and tear down containerd view resources on task Delete when the shim process is still alive. Signed-off-by: sidneychang <2190206983@qq.com>
1 parent 4d6b746 commit 1989568

3 files changed

Lines changed: 112 additions & 16 deletions

File tree

pkg/containerd-shim/guest_rootfs.go

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
taskAPI "github.com/containerd/containerd/api/runtime/task/v2"
2525
specs "github.com/opencontainers/runtime-spec/specs-go"
2626
"github.com/urunc-dev/urunc/pkg/unikontainers"
27+
"github.com/urunc-dev/urunc/pkg/unikontainers/types"
2728
)
2829

2930
const annotRootfsParams = "com.urunc.internal.rootfs.params"
@@ -33,35 +34,35 @@ var errGuestRootfsChoiceSkipped = errors.New("guest rootfs choice skipped")
3334
// chooseGuestRootfs runs the same ChooseRootfs logic as runtime Exec after inner
3435
// task Create (#684) and records the result in annotRootfsParams so Exec knows
3536
// selection already happened.
36-
func chooseGuestRootfs(r *taskAPI.CreateTaskRequest) error {
37+
func chooseGuestRootfs(r *taskAPI.CreateTaskRequest) (types.RootfsParams, error) {
3738
configPath := filepath.Join(r.Bundle, "config.json")
3839
info, err := os.Stat(configPath)
3940
if err != nil {
40-
return fmt.Errorf("stat config.json: %w", err)
41+
return types.RootfsParams{}, fmt.Errorf("stat config.json: %w", err)
4142
}
4243

4344
data, err := os.ReadFile(configPath)
4445
if err != nil {
45-
return fmt.Errorf("read config.json: %w", err)
46+
return types.RootfsParams{}, fmt.Errorf("read config.json: %w", err)
4647
}
4748

4849
var spec specs.Spec
4950
if err := json.Unmarshal(data, &spec); err != nil {
50-
return fmt.Errorf("unmarshal config.json: %w", err)
51+
return types.RootfsParams{}, fmt.Errorf("unmarshal config.json: %w", err)
5152
}
5253
if spec.Root == nil {
53-
return fmt.Errorf("invalid OCI spec: root section is required")
54+
return types.RootfsParams{}, fmt.Errorf("invalid OCI spec: root section is required")
5455
}
5556

5657
config, err := unikontainers.GetUnikernelConfig(filepath.Clean(r.Bundle), &spec)
5758
if err != nil {
58-
return fmt.Errorf("%w: %w", errGuestRootfsChoiceSkipped, err)
59+
return types.RootfsParams{}, fmt.Errorf("%w: %w", errGuestRootfsChoiceSkipped, err)
5960
}
6061

6162
annotations := config.Map()
6263
uruncCfg, err := unikontainers.LoadUruncConfig(unikontainers.UruncConfigPath)
6364
if err != nil && uruncCfg == nil {
64-
return err
65+
return types.RootfsParams{}, err
6566
}
6667

6768
rootfsParams, err := unikontainers.ChooseRootfs(
@@ -71,12 +72,12 @@ func chooseGuestRootfs(r *taskAPI.CreateTaskRequest) error {
7172
uruncCfg,
7273
)
7374
if err != nil {
74-
return err
75+
return types.RootfsParams{}, err
7576
}
7677

7778
encoded, err := json.Marshal(rootfsParams)
7879
if err != nil {
79-
return err
80+
return types.RootfsParams{}, err
8081
}
8182
if spec.Annotations == nil {
8283
spec.Annotations = make(map[string]string)
@@ -85,8 +86,10 @@ func chooseGuestRootfs(r *taskAPI.CreateTaskRequest) error {
8586

8687
patched, err := json.MarshalIndent(spec, "", " ")
8788
if err != nil {
88-
return fmt.Errorf("marshal config.json: %w", err)
89+
return types.RootfsParams{}, fmt.Errorf("marshal config.json: %w", err)
8990
}
90-
91-
return os.WriteFile(configPath, patched, info.Mode())
91+
if err := os.WriteFile(configPath, patched, info.Mode()); err != nil {
92+
return types.RootfsParams{}, err
93+
}
94+
return rootfsParams, nil
9295
}

pkg/containerd-shim/task_plugin.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
package containerdshim
1616

1717
import (
18+
"os"
19+
"path/filepath"
20+
1821
"github.com/containerd/containerd/pkg/shutdown"
1922
"github.com/containerd/containerd/plugin"
2023
runcTask "github.com/containerd/containerd/runtime/v2/runc/task"
@@ -45,9 +48,15 @@ func init() {
4548
return nil, err
4649
}
4750

51+
cwd, err := os.Getwd()
52+
if err != nil {
53+
return nil, err
54+
}
55+
4856
return &taskService{
4957
TaskService: inner,
5058
containerdAddress: ic.Address,
59+
stateRoot: filepath.Dir(filepath.Dir(cwd)),
5160
}, nil
5261
},
5362
})

pkg/containerd-shim/task_service.go

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ package containerdshim
1717
import (
1818
"context"
1919
"errors"
20+
"fmt"
21+
"path/filepath"
2022

2123
taskAPI "github.com/containerd/containerd/api/runtime/task/v2"
24+
"github.com/containerd/containerd/namespaces"
2225
"github.com/containerd/log"
2326
"github.com/containerd/ttrpc"
2427
containerdShim "github.com/urunc-dev/urunc/pkg/containerd-shim/containerd"
@@ -31,6 +34,8 @@ type taskService struct {
3134
taskAPI.TaskService
3235

3336
containerdAddress string
37+
// Used on Delete, where cwd may no longer be the bundle.
38+
stateRoot string
3439
}
3540

3641
func (s *taskService) Create(ctx context.Context, r *taskAPI.CreateTaskRequest) (*taskAPI.CreateTaskResponse, error) {
@@ -53,9 +58,8 @@ func (s *taskService) Create(ctx context.Context, r *taskAPI.CreateTaskRequest)
5358
return resp, err
5459
}
5560

56-
// ChooseRootfs after inner task Create so bundle rootfs is mounted;
57-
// params are persisted in bundle config.json for runtime Exec.
58-
if err := chooseGuestRootfs(r); err != nil {
61+
rootfsChoice, err := chooseGuestRootfs(r)
62+
if err != nil {
5963
if errors.Is(err, errGuestRootfsChoiceSkipped) {
6064
log.G(ctx).WithError(err).Debug("urunc(shim): guest rootfs choice skipped")
6165
return resp, nil
@@ -64,14 +68,94 @@ func (s *taskService) Create(ctx context.Context, r *taskAPI.CreateTaskRequest)
6468
return nil, err
6569
}
6670

71+
log.G(ctx).WithFields(map[string]any{
72+
"rootfs_type": rootfsChoice.Type,
73+
"rootfs_path": rootfsChoice.Path,
74+
"mon_rootfs": rootfsChoice.MonRootfs,
75+
}).Debug("urunc(shim): guest rootfs chosen")
76+
77+
if session != nil {
78+
rootfsViewAccessor := containerdShim.NewRootfsViewAccessor(session)
79+
if rootfsViewAccessor.ShouldPrepare(rootfsChoice) {
80+
log.G(ctx).WithFields(map[string]any{
81+
"rootfs_type": rootfsChoice.Type,
82+
"rootfs_path": rootfsChoice.Path,
83+
}).Debug("urunc(shim): preparing rootfs view")
84+
if err := rootfsViewAccessor.Prepare(ctx, r.Bundle); err != nil {
85+
log.G(ctx).WithError(err).Warn("urunc(shim): failed to prepare rootfs view; falling back to legacy boot artifact extraction")
86+
} else {
87+
log.G(ctx).Debug("urunc(shim): rootfs view prepared")
88+
}
89+
} else {
90+
log.G(ctx).WithField("rootfs_type", rootfsChoice.Type).Debug("urunc(shim): rootfs view prepare skipped")
91+
}
92+
}
93+
6794
return resp, nil
6895
}
6996

7097
func (s *taskService) Delete(ctx context.Context, r *taskAPI.DeleteRequest) (*taskAPI.DeleteResponse, error) {
71-
return s.TaskService.Delete(ctx, r)
98+
shouldCleanup := false
99+
snapshotter := ""
100+
var loadErr error
101+
102+
if r.ExecID == "" {
103+
bundle, err := s.bundlePathFor(ctx, r.ID)
104+
if err != nil {
105+
log.G(ctx).WithError(err).Warn("urunc(shim): resolve bundle path during Delete failed")
106+
loadErr = err
107+
} else {
108+
shouldCleanup, snapshotter, loadErr = containerdShim.ShouldCleanupRootfsView(bundle)
109+
}
110+
}
111+
112+
// Delete tears down the monitor namespace before removing the view it may pin.
113+
resp, err := s.TaskService.Delete(ctx, r)
114+
115+
if loadErr != nil {
116+
if err != nil {
117+
return resp, err
118+
}
119+
return resp, loadErr
120+
}
121+
122+
if shouldCleanup {
123+
session, sessionErr := containerdShim.OpenSession(ctx, s.containerdAddress, r.ID)
124+
if sessionErr != nil {
125+
log.G(ctx).WithError(sessionErr).Warn("urunc(shim): open containerd session for rootfs view cleanup failed")
126+
if err == nil {
127+
err = sessionErr
128+
}
129+
} else {
130+
defer func() {
131+
if err := session.Close(); err != nil {
132+
log.G(ctx).WithError(err).Warn("urunc(shim): failed to close containerd session after rootfs view cleanup")
133+
}
134+
}()
135+
if cleanupErr := containerdShim.NewRootfsViewAccessor(session).Cleanup(ctx, snapshotter); cleanupErr != nil {
136+
log.G(ctx).WithError(cleanupErr).Warn("urunc(shim): delete rootfs view during Delete failed")
137+
if err == nil {
138+
err = cleanupErr
139+
}
140+
}
141+
}
142+
}
143+
144+
return resp, err
72145
}
73146

74147
func (s *taskService) RegisterTTRPC(server *ttrpc.Server) error {
75148
taskAPI.RegisterTaskService(server, s)
76149
return nil
77150
}
151+
152+
func (s *taskService) bundlePathFor(ctx context.Context, containerID string) (string, error) {
153+
if s.stateRoot == "" {
154+
return "", fmt.Errorf("task service state root is empty (shim cwd layout assumption violated)")
155+
}
156+
ns, err := namespaces.NamespaceRequired(ctx)
157+
if err != nil {
158+
return "", fmt.Errorf("namespace required: %w", err)
159+
}
160+
return filepath.Join(s.stateRoot, ns, containerID), nil
161+
}

0 commit comments

Comments
 (0)