Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP - Use buildkit for building images #264

Conversation

cmac4603
Copy link
Contributor

@cmac4603 cmac4603 commented Oct 16, 2022

WIP - Doesn't work

Description:

  • Build images using buildkit
  • Adds git submodules for buildkit gRPC codegen
  • Doesn't work yet, but we get a HTTP 101 when upgrading via /session endpoint on the docker API

Run the buildkit test

cargo run --example build_buildkit --features buildkit

Description:
- Build images using buildkit
- Adds git submodules for buildkit gRPC codegen
- Doesn't work yet, but we get a HTTP 101 when upgrading
  via /session endpoint on the docker API
@fussybeaver
Copy link
Owner

This looks really fabulous.. I'm going to make some room this weekend to play around with it. Are you using a reference implementation from somewhere ?

@cmac4603
Copy link
Contributor Author

cmac4603 commented Oct 20, 2022

This looks really fabulous.. I'm going to make some room this weekend to play around with it. Are you using a reference implementation from somewhere ?

No, this is just from going through what docker CLI does. I think it might almost work, I'm seeing a JSON data error, perhaps a different response format is being used.

&res = Err(
    JsonDataError {
        message: "invalid type: string \"Cm8KR3NoYTI1Njo2MjU0ZGJmYTc2ZjE1Njk2MDNlZjhjZmY2MGI0ZmViNzcwMjlmOWMxYjkzNmUzZmVkOWRlMzdjZDg0M2I2ZjcxGiRbaW50ZXJuYWxdIGxvYWQgcmVtb3RlIGJ1aWxkIGNvbnRleHQ=\", expected struct ImageId at line 1 column 188",
        column: 188,
    },
)
Message: Err(JsonDataError { message: "invalid type: string \"Cm8KR3NoYTI1Njo2MjU0ZGJmYTc2ZjE1Njk2MDNlZjhjZmY2MGI0ZmViNzcwMjlmOWMxYjkzNmUzZmVkOWRlMzdjZDg0M2I2ZjcxGiRbaW50ZXJuYWxdIGxvYWQgcmVtb3RlIGJ1aWxkIGNvbnRleHQ=\", expected struct ImageId at line 1 column 188", column: 188 })

If you have any questions @fussybeaver feel free to reach out

@cmac4603
Copy link
Contributor Author

Ok(
    BuildInfo {
        id: None,
        stream: None,
        error: Some(
            "failed to solve with frontend dockerfile.v0: failed to create LLB definition: no active sessions",
        ),
        error_detail: Some(
            ErrorDetail {
                code: None,
                message: Some(
                    "failed to solve with frontend dockerfile.v0: failed to create LLB definition: no active sessions",
                ),
            },
        ),
        status: None,
        progress: None,
        progress_detail: None,
        aux: None,
    },
)

@fussybeaver
Copy link
Owner

fussybeaver commented Oct 23, 2022

If I try with a valid build config I also get no active sessions. It looks like you can glean more from base64 decoding the aux field in each line... for example, with the following example one error line I have is: resolve image config base64: invalid input - I'm wondering if some of the input needs to be base64 encoded.

    let docker = Docker::connect_with_unix_defaults().unwrap();

    let mut build_image_args = HashMap::new();
    build_image_args.insert("dummy", "value");

    let mut build_image_labels = HashMap::new();
    build_image_labels.insert("maintainer", "somemaintainer");

    let build_image_options = BuildImageOptions {
        t: "bollard-build-buildkit-example",
        dockerfile: "Dockerfile",
        version: BuilderVersion::BuilderBuildKit,
        ..Default::default()
    };

    let filename = &args().nth(1).expect("needs first argument");
    let archive = File::open(filename).await.expect("could not open file");
    let stream = FramedRead::new(archive, BytesCodec::new());
    let body = Body::wrap_stream(stream);

    let mut image_build_stream = docker.build_image(build_image_options, None, Some(body));

    while let Some(msg) = image_build_stream.next().await {
        println!("Message: {:?}", msg);
    }

@JakeCooper
Copy link

Would love to help on this in any way possible!

Trying to integrate Bollard into Nixpacks (Railway's Opensource Build Engine) instead of calling the local client so that we can do remote builds

Our caching system requires --mount, which requires buildkit. I might try and play with this tomorrow to see if I can get something going myself

@fussybeaver
Copy link
Owner

fussybeaver commented Nov 5, 2022

There's definitely some moving parts that need to be tamed.

The relevant moby code that spins up buildkit is here. The aux field that is returned from Docker seems to be serialized into a protobuf StatusResponse

Unfortunately, the protobuf in the buildkit repository doesn't seem to compile cleanly with tonic/prost at the moment, so it could be useful to figure out how that's supposed to work and how to integrate it into the Bollard codebase.

Other observations I want to take a look at:

@fussybeaver
Copy link
Owner

fussybeaver commented Nov 6, 2022

Edit: managed to get a simple From scratch to compile with Buildkit. My work is tracked here

...
Response: Ok(StatusResponse { vertexes: [Vertex { digest: "sha256:e8c613e07b0b7ff33893b694f7759a10d42e180f2b4dc349fb57dc6b71dcab00", inputs: [], name: "exporting to image", cached: false, started: Some(Timestamp { seconds: 1667747763, nanos: 683161727 }), completed: None, error: "", progress_group: None }], statuses: [VertexStatus { id: "writing image sha256:71de1148337f4d1845be01eb4caf15d78e4eb15a1ab96030809826698a5b7e30", vertex: "sha256:e8c613e07b0b7ff33893b694f7759a10d42e180f2b4dc349fb57dc6b71dcab00", name: "", current: 0, total: 0, timestamp: Some(Timestamp { seconds: 1667747763, nanos: 683215364 }), started: Some(Timestamp { seconds: 1667747763, nanos: 683215130 }), completed: None }], logs: [], warnings: [] })
Nov 06 15:16:03.685 DEBUG hyper::proto::h1::decode: incoming chunked header: 0x6F (111 bytes)
Nov 06 15:16:03.685 DEBUG bollard::read: Decoding JSON line from stream: {"id":"moby.image.id","aux":{"ID":"sha256:71de1148337f    45be01eb4caf15d78e4eb15a1ab96030809826698a5b7e30"}}
```dockerd[556]: time="2022-11-06T15:10:58.270717053Z" level=debug msg="fetch response received" host=registry-1.docker.io response.header.content-length=160 response.header.content-type=application/json response.header.date="Sun, 06 Nov 2022 15:10:58 GMT" response.header.docker-distribution-api-version=registry/2.0 response.header.docker-ratelimit-source=152.37.101.53 response.header.strict-transport-security="max-age=31536000" response.header.www-authenticate="Bearer realm=\"https://auth.docker.io/token\",service=\"registry.docker.io\",scope=\"repository:docker/dockerfile:pull\"" response.status="401 Unauthorized" url="https://registry-1.docker.io/v2/docker/dockerfile/manifests/1"

Caveats:

  • If I'm following the docker daemon logs correctly, the docker daemon wants to auth the docker hub and is not picking up credentials properly from Bollard. So, at the moment it won't build anything more complex.
  • Compiling with tonic/prost was not very clean (probably stemming from my inexperience with these libraries). Any advice and/or help in this department would be very welcome. I ended up copying some of the generated files out, re-arranging the file structure and even editing some references to point to the prost common types. Most importantly I wasn't able to answer:
    • Does tonic handle imports to external repositories like in go?
    • Can tonic/prost generate a nested file structure, or why does it generate a flat directory structure with incompatible rust filenames?
    • How to handle the common types gracefully (Timestamp)?

@cmac4603
Copy link
Contributor Author

cmac4603 commented Nov 6, 2022

@fussybeaver That is amazing! Is there anything you need a hand with? I hope no one feels I just drop this unfinished 😅

For the tonic lib fetching dependencies, pretty sure that is out of scope for the project. Two things I can think of:

  1. Fetch the files first in the builds.rs
  2. Use git submodules

Second might be easier to track with versioning?

@fussybeaver
Copy link
Owner

@cmac4603 very grateful about the initial work, clearly it's not straightforward and needs some iterations.

Including git submodules might work, but these have .proto files with external references: https://github.com/moby/buildkit/blob/master/api/types/worker.proto#L5-L6

import "github.com/gogo/protobuf/gogoproto/gogo.proto";
import "github.com/moby/buildkit/solver/pb/ops.proto";

So out-of-the-box tonic/prost won't handle it, though I guess there's possibly some options/flags that might work. Possible also to kick this can down the road and maybe deal with it later.

@JakeCooper
Copy link

JakeCooper commented Nov 6, 2022

Totally spitballing here, but there's a Session RPC call, and the SolveRequest takes a Session string

Wouldn't be surprised if the GRPC sessions for buildkit were materially different from the http sessions, and thus you'd need to pre-negotiate the session using the GRPC API and use that

@fussybeaver
Copy link
Owner

So, an update here, I have managed to get buildkit working for public repositories in #276 , and will merge this into master soon. The solution was actually to invert the grpc relationship (bollard is the grpc server, buildkit acts as a client), hook up tonic's existing type system that accepts an AsyncRead + AsyncWrite + Connected, and use the healthcheck as a response to keep the grpc server up.

Some remaining work, if anyone is interested (not sure I'll have the time this year), is implementing the grpc AuthProvider for private registries - I didn't see any code in Docker to suggest this is implemented there, I saw an example implementation in the Dagger CI app. I also noticed a FileSync provider that Docker uses to build Dockerfiles from the local disk. But for the moment we have some sort of parity with the existing Docker image build API.

@cmac4603
Copy link
Contributor Author

cmac4603 commented Dec 7, 2022

@fussybeaver I wouldn't mind taking a look after the first part is merged

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.

3 participants