refactor(shim): precompute guest rootfs after task create#702
Conversation
✅ Deploy Preview for urunc canceled.
|
cmainas
left a comment
There was a problem hiding this comment.
Hello @sidneychang ,
I have added several comments in some changes. Moreover:
- I do not think we need to encode for the new rootfs annotations that the shim creates. Please let me know if I am wrong.
- Why do not we perform the
chooseRootfafunctionality afterTaskService.Create? THis will simplify a lot the changes. - The logic of
ChooseRootfshas changed a lot and it has made the code less cleaner. For example, it creates new instances for a unikernel interface multiple times, while just a single instance is enough as the original code. - The code for annotations is all around the place. Just place it in one file and let it be ugly. We will just throw it away.
| if err := conf.validate(); err != nil { | ||
| return nil, err | ||
| } | ||
| if err := conf.decode(); err != nil { |
There was a problem hiding this comment.
We do not need to decode the annotations passed form the shim. They should be plain test.
| return jsonConf, nil | ||
| } | ||
|
|
||
| // GetUnikernelConfigFromSpecAnnotations retrieves urunc configuration only |
There was a problem hiding this comment.
Since this function is called from the shim, then the annotations should already be in the config and hence we can reuse the previous function which will not fallback to urunc.json if the annotations exist in the config.json
| // Decode from State.Annotations only: the shim writes the internal key to | ||
| // Spec (config.json), and saveContainerState copies missing Spec keys into | ||
| // state.json during create/InitialSetup before reexec reaches Exec. |
There was a problem hiding this comment.
While this is correct and we should follow it across urunc, we do not need an explicit comment just for that..
|
|
||
| // DecodeRootfsParams reads rootfs parameters from OCI annotations. The bool | ||
| // return value reports whether the internal annotation was present. | ||
| func DecodeRootfsParams(annotations map[string]string) (types.RootfsParams, bool, error) { |
There was a problem hiding this comment.
We can derive if the shim has done the rootfs selection by checking the MonRootfs value when it gets returned.
| return nil | ||
| } | ||
|
|
||
| unikernel, err := unikernels.New(unikernelType) |
There was a problem hiding this comment.
This check should not be performed here, but as before we should try each one of the options in one place.
| // bundle layout and annotations. specRoot may be relative to bundle. rootfsMounts | ||
| // carries CreateTask mounts from the shim; when nil, block backing may be probed | ||
| // from the live container rootfs mount. | ||
| func ChooseRootfs(bundle, specRoot string, annot map[string]string, cfg *UruncConfig, rootfsMounts []RootfsMount) (types.RootfsParams, error) { |
There was a problem hiding this comment.
This function changes the whole logic of choosing rootfs a lot. I do not really see why this is necessary and the previous logic should remain the same.
| if containerRootfsBlock == nil && shouldMountContainerRootfs(annot) { | ||
| unikernelPreview, err := unikernels.New(annot[annotType]) | ||
| if err != nil { | ||
| return types.RootfsParams{}, err | ||
| } | ||
| if unikernelPreview.SupportsBlock() { | ||
| rootFsDevice, err := getMountInfo(rootfsDir) | ||
| if err == nil { | ||
| containerRootfsBlock = &rootFsDevice | ||
| } else if !errors.Is(err, ErrMountpoint) { | ||
| uniklog.Errorf("failed to get container's rootfs mount info: %v", err) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
I do not really see the benefit if this checks here. We can directly call or use the logic from selectRootfs
| annotValue := rs.annot[annotMountRootfs] | ||
| return shouldMountContainerRootfs(rs.annot) | ||
| } | ||
|
|
||
| func shouldMountContainerRootfs(annot map[string]string) bool { | ||
| annotValue := annot[annotMountRootfs] |
There was a problem hiding this comment.
This would not be necessary if the logic of chooseRootfs remained the same.
| func RootfsParamsAnnotation() string { | ||
| return annotInternalRootfsParams | ||
| } | ||
|
|
88a6e4c to
e813c54
Compare
|
Thank you @cmainas for the detailed review and helpful suggestions. I have updated the code according to your comments. |
1552ffa to
e813c54
Compare
cmainas
left a comment
There was a problem hiding this comment.
Thank you @sidneychang for addressing the comments. The code seems way better now. I have added some not that important comments and uurnc ensures that even if the configuration does not exist, the defaults values will be used. It would be very helpful if we could also do that in the shim, reusing the configuration part from urunc.
| if u.Spec.Root != nil { | ||
| specRoot = u.Spec.Root.Path | ||
| } | ||
| rootfsParams, err = ChooseRootfs(u.State.Bundle, specRoot, u.State.Annotations, u.UruncCfg) |
There was a problem hiding this comment.
At this point we should assume that the u.Spec.Root exists. Not that important though.
| } | ||
| res.MonRootfs = monRootfs | ||
|
|
||
| return res, nil |
There was a problem hiding this comment.
I am not sure if we need to change this function. It is called from chooseRootfs and even in the levle of shim or in the level of urunc, there is no issue of creating it. We just need to make sure to delete it later.
There was a problem hiding this comment.
Tracking who created the directory might be a nightmare though.
| } | ||
| } | ||
|
|
||
| if _, err = os.Stat(rootfsParams.MonRootfs); err != nil { |
There was a problem hiding this comment.
It seems we can directly call MkdiAll. According to the documentation "If path is already a directory, MkdirAll does nothing and returns nil. " from https://pkg.go.dev/os#MkdirAll
e813c54 to
d7beeca
Compare
d7beeca to
da580a7
Compare
|
Hi @cmainas — When Fix: initialize cfg := &UruncConfig{ExtraBins: defaultExtraBinConfig()}CI should be green now. |
|
Hello @sidneychang , this is a nice catch. However, should not we use the default config for all the fields of the configuration? I mean we should extend your change and initialize |
da580a7 to
0601f25
Compare
Run ChooseRootfs in the shim after inner task Create, once the bundle rootfs is mounted (urunc-dev#684). Persist JSON RootfsParams in config.json as com.urunc.internal.rootfs.params so Exec reuses the choice; read state then spec annotations because reexec may not yet mirror config.json. Export ChooseRootfs from unikontainers. When the annotation is absent, Exec runs the same selection as the former u.chooseRootfs() (podman and urunc CLI). Ensure MonRootfs exists before rootfs setup in Exec. Fixes: urunc-dev#684 Signed-off-by: sidneychang <2190206983@qq.com>
0601f25 to
9e58d34
Compare
cmainas
left a comment
There was a problem hiding this comment.
Thank you @sidneychang for this change.
Run ChooseRootfs in the shim after inner task Create, once the bundle rootfs is mounted (#684). Persist JSON RootfsParams in config.json as com.urunc.internal.rootfs.params so Exec reuses the choice; read state then spec annotations because reexec may not yet mirror config.json. Export ChooseRootfs from unikontainers. When the annotation is absent, Exec runs the same selection as the former u.chooseRootfs() (podman and urunc CLI). Ensure MonRootfs exists before rootfs setup in Exec. Fixes: #684 PR: #702 Signed-off-by: sidneychang <2190206983@qq.com> Reviewed-by: Charalampos Mainas <cmainas@nubificus.co.uk> Approved-by: Charalampos Mainas <cmainas@nubificus.co.uk>
Run ChooseRootfs in the shim after inner task Create, once the bundle rootfs is mounted (#684). Persist JSON RootfsParams in config.json as com.urunc.internal.rootfs.params so Exec reuses the choice; read state then spec annotations because reexec may not yet mirror config.json. Export ChooseRootfs from unikontainers. When the annotation is absent, Exec runs the same selection as the former u.chooseRootfs() (podman and urunc CLI). Ensure MonRootfs exists before rootfs setup in Exec. Fixes: #684 PR: #702 Signed-off-by: sidneychang <2190206983@qq.com> Reviewed-by: Charalampos Mainas <cmainas@nubificus.co.uk> Approved-by: Charalampos Mainas <cmainas@nubificus.co.uk>
Description
This PR precomputes guest rootfs in the urunc containerd task shim after inner
TaskService.Createmountsbundle/rootfs, so block-backed snapshots (e.g. devmapper) can be selected without relying ongetMountInfoon an unmounted path.Problem
Previously, guest rootfs selection was performed only in the runtime
Exec()path. In the containerd shim path, this made block-backed container rootfs detection fragile, because the runtime had to rediscover the guest rootfs from the bundle rootfs path later throughgetMountInfo.For block-backed snapshots such as devmapper, the rootfs decision should be made only after the wrapped containerd task service has completed
Create, when the bundle rootfs has already been prepared by the normal containerd/runc create flow. This allows urunc to reuse the existing rootfs selection logic on the actual bundle rootfs path, instead of introducing a separate shim-only rootfs discovery path.Solution
This PR moves guest rootfs selection into the urunc containerd task shim after the wrapped
TaskService.Createsucceeds.The shim now:
Createfirst;config.json;ChooseRootfsimplementation with the bundle path and OCIspec.Root.Path;RootfsParamsinconfig.jsonunder the internal annotationcom.urunc.internal.rootfs.params.Then, runtime
Exec()consumes this precomputed rootfs result when the annotation is present. If the annotation is absent, such as in direct urunc / podman-style flows,Exec()falls back to runningChooseRootfsitself. This keeps the original runtime behavior while making the containerd shim path more reliable for block-backed rootfs selection.Related issues
How was this tested?
LLM usage
Cursor
Checklist
make lint).make test_ctr,make test_nerdctl,make test_docker,make test_crictl).