# Lifecycle + BuildKit ## Problem Currently the lifecycle supports two methods for interacting with produced images to `analyze` and `export`: 1. `daemon` - An image can be exported to a Docker daemon. This uses the standard docker [client][docker-client] interface which supports _socket_ or _http_ communication. 2. `registry` - An image can be exported directly to an OCI registry. This for the most part follows the [OCI distribution specification][oci-dist-spec]. BuildKit's frontend LLB interface doesn't fall into either of the supported methods. [docker-client]: https://github.com/buildpacks/imgutil/blob/main/local/local.go#L77 [oci-dist-spec]: https://github.com/opencontainers/distribution-spec/blob/main/spec.md ## Solution ### Option 1 - Reimplement Analyze and Export in BuildKit Part of the [current implementation][exporter-implementation] in this project. #### Pros * Bypasses the need for any other component changes. #### Cons * Goes against [the spec](https://github.com/buildpacks/spec/blob/main/platform.md#operations): > A platform MUST execute these phases either by invoking the following phase-specific lifecycle binaries in order: > > 1. `/cnb/lifecycle/detector` > 2. `/cnb/lifecycle/analyzer` > 3. `/cnb/lifecycle/restorer` > 4. `/cnb/lifecycle/builder` > 5. `/cnb/lifecycle/exporter` > > or by executing `/cnb/lifecycle/creator`. * Ignores the `lifecycle` embedded in the builder which would a) be unexpected and b) prevents changes to `lifecycle` functionality. * There is a large implementation and maintenance overhead. [exporter-implementation]: https://github.com/EricHripko/cnbp/blob/bfe5c2ada25bb8f6392210f2714d4885a9e73513/pkg/cnbp2llb/export.go#L24 ### Option 2 - Interface via OCI layout format This option proposes that a new more versatile method is supported, [OCI image layout][oci-layout]. The general idea is to add a `--layout` flag to `analyzer` and `exporter` which would indicate that the contents of the analyzed and exported image will be placed in OCI image layout format. Additional configuration can take the form of the following environment variables: * `CNB_LAYOUT_DIR` - The directory where the analyzed/exported image is located. (default: `<layers>/image`) An example of how the OCI layout format would look like: ``` $ find <directory> -type f ./index.json ./oci-layout ./blobs/sha256/3588d02542238316759cbf24502f4344ffcc8a60c803870022f335d1390c13b4 ./blobs/sha256/4b0bc1c4050b03c95ef2a8e36e25feac42fd31283e8c30b3ee5df6b043155d3c ./blobs/sha256/7968321274dc6b6171697c33df7815310468e694ac5be0ec03ff053bb135e768 ``` #### Details * When calling the `analyzer`, the frontend will provide existing image information by providing a dump of the previously built image in OCI layout. * When calling the `exporter`, the exporter will write new layers to the same directory and overwrite the `./index.json` file. * The layers themselves would not be compressed ([`mediaType`][mediatypes] = `application/vnd.oci.image.layer.v1.tar`) to prevent an additional (de)compression cycle since the daemon will be compressing them upon `pushing` the image to a registry. #### Pros * The format is a known standard in the ecosystem with tools (ie. [skopeo][skopeo], [snyk][snyk]) supporting it. * Creates a clear seperation of concerns where the use of BuildKit is irrelevent to the lifecycle. #### Cons * #### Potential Future Work * Be able to use _partial_ OCI Layout * We may be able to optimize the amount of data that needs to be transfered if we are able to only work with the minimal amount of layers as oppose to all the layers in the image. For example, analyzer may only need the `config` and `exporter` may only produce _new_ layers. ([`MergeOps`][mergeops] may be necessary here). * Allow compression to be configurable via `CNB_COMPRESSION` or something similar. * This would enable use cases where the platform want to control the compression algorithm when exporting to OCI layout format. For example, if using Skopio you may want layers to already be compressed when relocating image elsewhere. #### Questions * How feasible is it to use _partial_ OCI layouts? * Is this dependant on [`MergeOps`][mergeops]? * On rebuild, should the lifecycle delete layers that are no longer necessary? [oci-layout]: https://github.com/opencontainers/image-spec/blob/master/image-layout.md [skopeo]: https://github.com/containers/skopeo [snyk]: https://snyk.io/blog/container-image-formats/ [attempt-unpack]: https://github.com/moby/buildkit/blob/4e69662758446c7dc0e6de2bc1f7973d03bacbed/client/llb/fileop.go#L430 [mergeops]: https://github.com/moby/buildkit/issues/1431 [mediatypes]: https://github.com/opencontainers/image-spec/blob/master/media-types.md#oci-image-media-types [compression-levels]: https://golang.org/pkg/compress/flate/#NoCompression ### Option 3 - Interface directly with BuildKit This option propose that a direct gRPC connection from the host is provided to the `lifecycle`. The lifecycle would take gRPC host information and connect to it using the [frontend client library][grcp-connection]. It can then interact with it just like the frontend. _**NOTE: Requires additional research to determine feasibility.**_ #### Pros * The lifecycle can more directly operate with BuildKit taking advantage of any possible optimizations. Similar to the Docker daemon [optimization "hack"][docker-hack]. #### Cons * Adds another Docker (non-standard) specific interface to be supported to the `lifecycle`. * The BuildKit integration is split into two individually maintained components. [docker-hack]: https://github.com/buildpacks/imgutil/blob/7bcc5817ab03f943bd81fb2d9e86b64233ceeca3/local/local.go#L432 [grcp-connection]: https://sourcegraph.com/github.com/moby/buildkit@d31a0dc5a29a6304a9a05a970c87083d34c58190/-/blob/frontend/gateway/grpcclient/client.go#L43