Skip to content

Support stargz snapshotter for dev stages#1402

Merged
sipsma merged 2 commits intomoby:masterfrom
ktock:remote-snapshotter
Aug 31, 2020
Merged

Support stargz snapshotter for dev stages#1402
sipsma merged 2 commits intomoby:masterfrom
ktock:remote-snapshotter

Conversation

@ktock
Copy link
Collaborator

@ktock ktock commented Mar 10, 2020

Related issues:

Throughout building steps, "pulling base image" is one of the time-consuming steps.

Recently, containerd community started "Stargz Snapshotter" (containerd/stargz-snapshotter) non-core subproject for speeding up image pulling. With this snapshotter, we can directly mount stargz-formatted (but still docker-compatible) image to nodes, which takes much shorter than downloading the whole of the image to nodes.

This commit solves the performance problem on pulling base images for "dev" stages using stargz snapshotter.

We introduced new RecordType "snapshot" for unexported base images. These images aren't guaranteed to have their whole contents in the content store but snapshots (possibly "remote" snapshots) are provided. Target (exported) base images don't have this type because its contents need to be fully available on the node for exportation.

We can test this patch with stargz snapshotter on this branch (Please also see this instruction).
We can see the benchmarking result with sample image on containerd/stargz-snapshotter#53 (comment).

This PR also intends to be a discussion thread for the design and implementation strategies of this feature so if there is a better one please tell us.

@AkihiroSuda
Copy link
Member

Can we have integration tests?

newFunc := func(root string) (ctdsnapshot.Snapshotter, error) {
gopts := []grpc.DialOption{
grpc.WithInsecure(),
grpc.WithBackoffMaxDelay(3 * time.Second),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WithBackoffMaxDelay is deprecated

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your feedback. I fixed it.

},
cli.StringFlag{
Name: "oci-worker-proxy-snapshotter-address",
Usage: "use proxy snapshotter at specified address",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With unix:// or without?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your feedback. Explicitly described not to include unix:// prefix and added small validation against the specified path.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imho *-address should have protocol, *-path or *-socket doesn't need to.

@ktock ktock force-pushed the remote-snapshotter branch 3 times, most recently from a58eebe to 425c1c7 Compare March 11, 2020 11:29
@ktock
Copy link
Collaborator Author

ktock commented Mar 13, 2020

I'm currently working on the benchmarking and let me share the latest benchmarking results for building sample images(benchmarking scripts are here). I'll work on integration tests soon.

  • legacy: unpatched buildkit(f1ecc78) + tarball base image
  • stargz: this PR's buildkit + stargz-formatted base image
  • estargz: this PR's buildkit + extended stargz-formatted base image

Please note each build's initial state; there is no content on the cache(so pulling base images occur for each build).

With OCI worker

result_oci

With containerd worker

result_containerd

@ktock ktock force-pushed the remote-snapshotter branch 5 times, most recently from 2398937 to d8135be Compare March 20, 2020 02:32
@ktock
Copy link
Collaborator Author

ktock commented Mar 20, 2020

Added unit and integration testes and fixed some bugs to make them happy. Containerd hasn't released remote-snapshots-related patches yet(but already merged) so current integration testes are for oci worker. After these patches are released, we can apply tests for containerd worker with another PR. PTAL:pray:

Copy link
Member

@AkihiroSuda AkihiroSuda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well done

@AkihiroSuda
Copy link
Member

@tonistiigi WDYT?

Copy link
Member

@tonistiigi tonistiigi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that this is controlled from the Dockerfile frontend. Frontends/LLB shouldn't optimize for a specific backend implementation. Not sure how it even works, if you rebuild with different targets and get previous cache. And layer being in a target stage does not mean it is exported (same as it not being it target stage doesn't mean it never will be exported). Every record should be exportable with every exporter (the timing can be different for different implementations, eg. #870 )

This is based on a very minimal review, so lmk if I missed anything.

@ktock
Copy link
Collaborator Author

ktock commented Mar 26, 2020

@tonistiigi Thanks for you feedbacks and pointer! #870 seems to have same motivation with this patch in terms of defering pulling layers until when it's actually needed.

From #870,

All the ops using inputs should expect that a input could be a remote and not a cache.ImmutableRef. If they receive a remote but need to access the data directly (for example exec op) they can call a method on it that would extract it and change the type to immutableref. This method would be where the pull now actually happens...

Now containerd doesn't require layers being locally available even to exec something on them if these layers are remote snapshots. So in this case, we can hopefully defer the pulling (= changing the type from remote to immutableref) even until export occurs and we don't need to pull nor have immutablerefs for all unexported layers, without inconsistency.

This patch initially aimed to enable bulidkit to use the containerd's remote snapshots from scratch but now I think this feature can be applied more simply on #870 as an advanced optimization as mentioned above. Do you have plans to move #870 forward?

@tonistiigi
Copy link
Member

Now containerd doesn't require layers being locally available even to exec something on them if these layers are remote snapshots. So in this case, we can hopefully defer the pulling (= changing the type from remote to immutableref) even until export occurs and we don't need to pull nor have immutablerefs for all unexported layers, without inconsistency.

Yes, it is fine to run a container without all the snapshots/blobs locally if the implementation supports it. But at any point the record used by that container or any other part of the build should remain exportable. This also complicates caching a lot. It is possible that the cache needs to store the registry ref where the rest of the data can be pulled and loading from a cache needs to check if the new build still has access to the remote resource.

we don't need to pull nor have immutablerefs for all unexported layers,

More likely I think the design is that immutableref can have this separate implementation. If you would make some parts of build work on something else than immutableref this would be a lot of exceptions.

Do you have plans to move #870 forward?

I probably don't have time to work on it on near future. But I can give you design direction if you are interested. @hinshun and I have also been discussing something called MergeOp that is very much related and needs a proper proposal/design. The hardest part is that I don't want to break Moby implementation with this and that model that doesn't have content blobs makes the whole thing even more complicated.

@tonistiigi tonistiigi mentioned this pull request Apr 8, 2020
@tonistiigi
Copy link
Member

@ktock #1475 is getting into a ready state. Feel free to take a look if it has everything you need and port this over to the logic where refs can request a data source if they only have partial data when they are loaded from cache.

@ktock
Copy link
Collaborator Author

ktock commented Aug 3, 2020

Thanks! I'll take a look at.

@ktock ktock force-pushed the remote-snapshotter branch 3 times, most recently from e21ee71 to 46c4626 Compare August 17, 2020 14:55
@ktock
Copy link
Collaborator Author

ktock commented Aug 17, 2020

I've ported this PR over the lazy ref logic (#1475). Thanks for the great work! Based on that logic, the new patch doesn't require changes on the frontend and remote snapshots seem to be able to be used with immutableRefs with keeping them safely exportable. The following is a summary of the design in the new patch.

  • immutableRef.Extract() and Mount() don't unlazy the ref if it's snapshot can be provided as a remote snapshot from the underlying snapshotter.
  • mutableRefs possibly have some lazy parents (immutableRefs) because Extract() method don't guarantee that the contents are unlazied.
  • cache.DescHandler has a new field SnapshotLabels which contains key-value pairs used by the underlying snapshotter for searching for remote snapshots corresponding to the blobs. By default, *containerimage.puller adds labels needed to query stargz blobs to the registry.

@tonistiigi @AkihiroSuda What do you think about the above design? Please tell me if I'm missing something.
I'll work on tests once we reach an agreement on the design.

@tonistiigi
Copy link
Member

cc @sipsma

@tonistiigi
Copy link
Member

@ktock panic in CI

sandbox.go:218: panic: runtime error: invalid memory address or nil pointer dereference
        sandbox.go:218: [signal SIGSEGV: segmentation violation code=0x1 addr=0x20 pc=0xbc2820]
        sandbox.go:218: 
        sandbox.go:218: goroutine 595 [running]:
        sandbox.go:218: github.com/moby/buildkit/cache.(*immutableRef).prepareRemoteSnapshots(0xc0004e1580, 0x1351d60, 0xc0004c6d50, 0x0, 0xc0003cf320, 0x1, 0x1)
        sandbox.go:218: 	/src/cache/refs.go:420 +0x160
        sandbox.go:218: github.com/moby/buildkit/cache.(*immutableRef).Extract(0xc0004e1580, 0x1351d60, 0xc0004c6d50, 0x0, 0x0)
        sandbox.go:218: 	/src/cache/refs.go:394 +0x19c```

@ktock ktock force-pushed the remote-snapshotter branch from 46c4626 to 6902261 Compare August 18, 2020 04:25
@ktock
Copy link
Collaborator Author

ktock commented Aug 18, 2020

@tonistiigi Thanks for the comment. Fixed the panic.

for _, desc := range p.manifest.Remote.Descriptors {
layers += fmt.Sprintf("%s,", desc.Digest.String())
}
if len(layers) >= 1 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use strings.TrimSuffix to trim the extra ,.

Or consider using strings.Join

@ktock ktock force-pushed the remote-snapshotter branch from 6902261 to b81e1a3 Compare August 20, 2020 10:12
Copy link
Collaborator

@sipsma sipsma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, one minor nit. Also, would it be possible to add an integ test for this (probably to client/client_test.go) either in this PR or in a follow-up? EDIT: saw you already mentioned adding tests in a previous comment, nevermind :-)

return sr.extract(ctx, sr.descHandlers)
}

func (sr *immutableRef) prepareRemoteSnapshots(ctx context.Context, dhs DescHandlers) (bool, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think this method may as well be wrapped in a flightcontrol.Group, similar to sr.extract, just to de-dupe some work when possible.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback! Fixed to use fileghtcontrol.Group.

@ktock ktock force-pushed the remote-snapshotter branch from b81e1a3 to a2415fc Compare August 22, 2020 14:18
Signed-off-by: ktock <ktokunaga.mail@gmail.com>
@ktock ktock force-pushed the remote-snapshotter branch 3 times, most recently from 6388c6c to 7e25d84 Compare August 24, 2020 07:41
@ktock
Copy link
Collaborator Author

ktock commented Aug 24, 2020

Thanks for the feedback. Added test for this PR.


// Check if image layers are lazy
var layers []ocispec.Descriptor
for _, layer := range manifest.Layers[:len(manifest.Layers)-1] {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why check all but the last layer? Something to do with stargz? Would be good to have a comment for.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comment. This is because the topmost layer (created by running a command on that stargz image) isn't lazy. I added comments about this and also added a check to make sure that the topmost layer isn't lazy.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh duh, forgot that the topmost layer was created by run, makes sense, thanks

@ktock ktock force-pushed the remote-snapshotter branch from 7e25d84 to 95add53 Compare August 27, 2020 06:49
Signed-off-by: ktock <ktokunaga.mail@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants