Skip to content

Commit 18b9892

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 18b9892

3 files changed

Lines changed: 108 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: 84 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,90 @@ 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+
if err := rootfsViewAccessor.Prepare(ctx, r.Bundle); err != nil {
81+
log.G(ctx).WithError(err).Warn("urunc(shim): failed to prepare rootfs view; falling back to legacy boot artifact extraction")
82+
} else {
83+
log.G(ctx).Debug("urunc(shim): rootfs view prepared")
84+
}
85+
} else {
86+
log.G(ctx).WithField("rootfs_type", rootfsChoice.Type).Debug("urunc(shim): rootfs view prepare skipped")
87+
}
88+
}
89+
6790
return resp, nil
6891
}
6992

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

74143
func (s *taskService) RegisterTTRPC(server *ttrpc.Server) error {
75144
taskAPI.RegisterTaskService(server, s)
76145
return nil
77146
}
147+
148+
func (s *taskService) bundlePathFor(ctx context.Context, containerID string) (string, error) {
149+
if s.stateRoot == "" {
150+
return "", fmt.Errorf("task service state root is empty (shim cwd layout assumption violated)")
151+
}
152+
ns, err := namespaces.NamespaceRequired(ctx)
153+
if err != nil {
154+
return "", fmt.Errorf("namespace required: %w", err)
155+
}
156+
return filepath.Join(s.stateRoot, ns, containerID), nil
157+
}

0 commit comments

Comments
 (0)