1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
|
# 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 <br/>dockerfile.v0 or gateway.v0.
loop
Frontend ->> FrontendLLBBridge : Solve
FrontendLLBBridge ->> Job : Build
activate Job
note over FrontendLLBBridge, Frontend : Implementations may also<br/>call FrontendLLBBridge to<br/>solve graphs before<br/>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<br/>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<br/>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<br/>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<br/>by build option "source" is solved<br/>and the rootfs of that image<br/>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 <br/>that listens on stdio of frontend<br/>container. Requests are then<br/>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<br/>definition/frontend-based solves<br/>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
```
|