Back to Buildkit

Solve Request Lifecycle

docs/dev/request-lifecycle.md

0.29.09.7 KB
Original Source

Solve Request Lifecycle

Buildkit solves build graphs to find the final result. By default, nothing will be exported to the client, but requests can be made after solving the graph to export results to external destinations (like the client’s filesystem).

A solve request goes through the following:

  1. Client makes a solve request and sends it to buildkitd over gRPC. The request may either include a LLB definition, or the name of a frontend (must be dockerfile.v0 or gateway.v0), but it must not be both.
  2. Buildkitd receives the solve request with the Controller. The controller is registered as the ControlServer gRPC service.
  3. The controller passes it down to the LLB solver, which will create a job for this request. It will also create a FrontendLLBBridge, that provides a solving interface over the job object.
  4. The request is processed:
    • If the request is definition-based, it will simply build the definition.
    • If the request is frontend-based, it will run the frontend over the gateway while passing it a reference to the FrontendLLBBridge. Frontends must return a result for the solve request, but they may also issue solve requests themselves to the bridge.
  5. The results are plumbed back to the client, and the temporary job and bridge are discarded.
<!-- Diagram from https://gist.github.com/hinshun/9618a3c4b64b3bb16864603ef332f3a5 --->
mermaid
sequenceDiagram
    ControlClient ->> ControlServer : Solve
    ControlServer ->> Solver : Solve

    Solver ->> Job : Create job
    activate Job

    Solver ->> FrontendLLBBridge : Create bridge over Job
    activate FrontendLLBBridge

    Solver ->> FrontendLLBBridge : Solve

    alt definition-based solve
        FrontendLLBBridge ->> Job : Build
        activate Job
        Job -->> FrontendLLBBridge : Result
        deactivate Job
    else frontend-based solve
        FrontendLLBBridge ->> Frontend : Solve
        activate Frontend
        note over FrontendLLBBridge, Frontend : Frontend must be either 
dockerfile.v0 or gateway.v0.

        loop
            Frontend ->> FrontendLLBBridge : Solve
            FrontendLLBBridge ->> Job : Build
            activate Job
            note over FrontendLLBBridge, Frontend : Implementations may also
call FrontendLLBBridge to
solve graphs before
returning the result.
            Job -->> FrontendLLBBridge : Result
            deactivate Job
            FrontendLLBBridge -->> Frontend : Result
        end

        Frontend -->> FrontendLLBBridge : Result
        deactivate Frontend
    end

    FrontendLLBBridge -->> Solver : Result
    Solver ->> FrontendLLBBridge : Discard
    deactivate FrontendLLBBridge

    Solver ->> Job : Discard
    deactivate Job

    Solver -->> ControlServer : Result
    ControlServer -->> ControlClient : Result

An important detail is that frontends may also issue solve requests, which are often definition-based solves, but can also be frontend-based solves, allowing for composability of frontends. Note that if a frontend makes a frontend-based solve request, they will share the same FrontendLLBBridge and underlying job.

Dockerfile frontend (dockerfile.v0)

Buildkit comes with a Dockerfile frontend which essentially is a parser that translates Dockerfile instructions into a LLB definition. In order to introduce new features into the Dockerfile DSL without breaking backwards compatibility, Dockerfiles can include a syntax directive at the top of the file to indicate a frontend image to use.

For example, users can include a syntax directive to use docker/dockerfile:1-labs to opt-in for an extended Dockerfile DSL that takes advantage of Buildkit features. However, the frontend image doesn’t have to be Dockerfile-specific. One can write a frontend that reads a YAML file, and using the syntax directive, issue the build request using docker build -f my-config.yaml.

The lifecycle of a dockerfile.v0 frontend-based solve request goes through the following:

  1. Starting from the "frontend-based solve" path, the bridge looks up the Dockerfile frontend if the frontend key is dockerfile.v0, and requests a solve to the frontend. The gateway forwarder implements the frontend interface and wraps over a BuildFunc that builds Dockerfiles.
  2. The BuildFunc issues a solve request to read the Dockerfile from a source (local context, git, or HTTP), and parses it to find a syntax directive.
    • If a syntax directive is found, it delegates the solve to the gateway.v0 frontend.
    • If a syntax directive is not found, then it parses the Dockerfile instructions and builds an LLB. The LLB is marshaled into a definition and sent in a solve request.
<!-- Diagram from https://gist.github.com/hinshun/2c18e16b07b38049bac72a4b602985b5 -->
mermaid
sequenceDiagram
    participant Job
    participant FrontendLLBBridge

    # FIXME: use boxes with https://github.com/mermaid-js/mermaid/issues/1505
    # box "Dockerfile frontend"
    participant Frontend as Gateway Forwarder
    participant BuildFunc
    # end box

    # FIXME: use incoming messages with https://github.com/mermaid-js/mermaid/issues/1357
    Job ->> FrontendLLBBridge : Solve
    FrontendLLBBridge ->> Frontend : Solve

    Frontend ->> BuildFunc : Call
    activate BuildFunc

    BuildFunc ->> FrontendLLBBridge : Solve
    FrontendLLBBridge ->> Job : Build
    activate Job
    note over Frontend : Solve to read
Dockerfile
    Job -->> FrontendLLBBridge : Result
    deactivate Job
    FrontendLLBBridge -->> BuildFunc : Result

    alt Dockerfile has syntax directive
        BuildFunc ->> FrontendLLBBridge : Solve
        activate FrontendLLBBridge #FFBBBB
        note over Frontend : Dockerfile delegates
to gateway.v0
        FrontendLLBBridge -->> BuildFunc : Result
        deactivate FrontendLLBBridge
    else Dockerfile has no syntax directive
        BuildFunc ->> FrontendLLBBridge : Solve
        FrontendLLBBridge ->> Job : Build
        activate Job
        note over Frontend : Solved by
Dockerfile2LLB
        Job -->> FrontendLLBBridge : Result
        deactivate Job
        FrontendLLBBridge -->> BuildFunc : Result
    end

    BuildFunc -->> Frontend : Return
    deactivate BuildFunc

    Frontend -->> FrontendLLBBridge : Result
    FrontendLLBBridge -->> Job : Result

Gateway frontend (gateway.v0)

The gateway frontend allows external frontends to be implemented as container images, allowing for a pluggable architecture. The container images have access to the gRPC service through stdin/stdout. The easiest way to implement a frontend image is to create a golang binary that vendors buildkit because they have a convenient LLB builders and utilities.

The lifecycle of a gateway.v0 frontend-based solve request goes through the following:

  1. Starting from the "frontend-based solve" path, the bridge looks up the Gateway frontend if the frontend key is gateway.v0, and requests a solve to the frontend.
  2. The gateway frontend resolves a frontend image from the source key and solves the request to retrieve the rootfs for the image.
  3. A temporary gRPC server is created that forwards requests to the LLB bridge.
  4. A container using the frontend image rootfs is created, and a gRPC connection is established from a process inside the container to the temporary bridge forwarder.
  5. The frontend image is then able to build LLBs and send solve requests through the forwarder.
  6. The container exits, and then the results are plumbed back to the LLB bridge, which plumbs them back to the client.
<!-- Diagram from https://gist.github.com/hinshun/ecf554c32522fc94a33488b353230b27 -->
mermaid
sequenceDiagram
    participant Job
    participant FrontendLLBBridge
    participant Frontend as Gateway frontend
    participant Worker
    participant LLBBridgeForwarder
    participant Executor
    participant Container as Frontend Container
    
    Job ->> FrontendLLBBridge : Solve
    FrontendLLBBridge ->> Frontend : Solve
    Frontend ->> Worker : ResolveImageConfig
    activate Worker
    Worker -->> Frontend : Digest
    deactivate Worker
    Frontend ->> FrontendLLBBridge : Solve
    
    FrontendLLBBridge ->> Job : Build
    activate Job
    note over FrontendLLBBridge, Frontend : The frontend image specified
by build option "source" is solved
and the rootfs of that image
is then used to run the container.
    Job -->> FrontendLLBBridge : Result
    deactivate Job
    
    FrontendLLBBridge -->> Frontend : Result
    
    note over LLBBridgeForwarder, Executor : A temporary gRPC server is created 
that listens on stdio of frontend
container. Requests are then
forwarded to LLB bridge.
    Frontend ->> LLBBridgeForwarder : Create forwarder
    activate LLBBridgeForwarder
    
    Frontend ->> FrontendLLBBridge : Exec
    FrontendLLBBridge ->> Worker : Exec
    Worker ->> Executor : Exec
    
    Executor ->> Container : Create container task
    activate Container #MediumSlateBlue
    
    rect rgba(100, 100, 100, .1)
      note over Executor, Container : Frontend images may request
definition/frontend-based solves
like any other client.
      loop
        Container ->> LLBBridgeForwarder : Solve
        LLBBridgeForwarder ->> FrontendLLBBridge : Solve
        activate FrontendLLBBridge #FFBBBB
        FrontendLLBBridge -->> LLBBridgeForwarder : Result
        deactivate FrontendLLBBridge
        LLBBridgeForwarder -->> Container : Result
      end
    end
    
    Container -->> Executor : Exit
    deactivate Container
    
    Executor -->> Worker : Exit
    Worker -->> FrontendLLBBridge : Exit
    FrontendLLBBridge -->> Frontend : Exit
    Frontend ->> LLBBridgeForwarder : Discard
    deactivate LLBBridgeForwarder
    
    Frontend -->> FrontendLLBBridge : Result
    FrontendLLBBridge -->> Job : Result