Skip to content

Commit e2c007a

Browse files
committed
feat: optimize build time by removing unnecessary I/O
This PR contains two optimizations: * reorder steps to make variant + dependencies layers cacheable * promote first dependency to base image for 'scratch' builds First optimization is pretty simple, given that many packages contain same top dependencies: ```yaml dependencies: - stage: base ``` It makes sure LLB is ordered so that base part of the LLB up to the dependencies is identical across packages builds and can be eliminated to a single instance. Second optimization is more tricky, for `scratch` packages: ```yaml variant: scratch dependencies: - stage: tools - stage: libressl ``` It promotes `tools` to be base image (instead of scratch). To compare, in `Dockerfile` syntax it means replacing: ``` FROM scratch COPY --from=tools / / COPY --from=libressl / / ``` with: ``` FROM tools COPY --from=libressl / / ``` If the `tools` layer is huge (which is often the case, in order of 0.5 GiB), it removes a lot of file copy operations and improves build time. Signed-off-by: Andrey Smirnov <[email protected]>
1 parent 384f28d commit e2c007a

File tree

2 files changed

+56
-28
lines changed

2 files changed

+56
-28
lines changed

internal/pkg/convert/graph.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ type GraphLLB struct {
2727
Checksummer llb.State
2828
LocalContext llb.State
2929

30-
cache map[*solver.PackageNode]llb.State
30+
baseImageProcessor llbProcessor
31+
cache map[*solver.PackageNode]llb.State
3132
}
3233

34+
type llbProcessor func(llb.State) llb.State
35+
3336
// NewGraphLLB creates new GraphLLB and initializes shared images.
3437
func NewGraphLLB(graph *solver.PackageGraph, options *environment.Options) *GraphLLB {
3538
result := &GraphLLB{
@@ -72,7 +75,11 @@ func (graph *GraphLLB) buildBaseImages() {
7275
return root
7376
}
7477

75-
graph.BaseImages[v1alpha2.Alpine] = addEnv(addPkg(llb.Image(
78+
graph.baseImageProcessor = func(root llb.State) llb.State {
79+
return addEnv(addPkg(root))
80+
}
81+
82+
graph.BaseImages[v1alpha2.Alpine] = graph.baseImageProcessor(llb.Image(
7683
constants.DefaultBaseImage,
7784
llb.WithCustomName(graph.Options.CommonPrefix+"base"),
7885
).Run(
@@ -81,9 +88,9 @@ func (graph *GraphLLB) buildBaseImages() {
8188
).Run(
8289
llb.Args([]string{"ln", "-svf", "/bin/bash", "/bin/sh"}),
8390
llb.WithCustomName(graph.Options.CommonPrefix+"base-symlink"),
84-
).Root()))
91+
).Root())
8592

86-
graph.BaseImages[v1alpha2.Scratch] = addEnv(addPkg(llb.Scratch()))
93+
graph.BaseImages[v1alpha2.Scratch] = graph.baseImageProcessor(llb.Scratch())
8794
}
8895

8996
func (graph *GraphLLB) buildChecksummer() {

internal/pkg/convert/node.go

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ type NodeLLB struct {
3333

3434
Graph *GraphLLB
3535
Prefix string
36+
37+
promotedDependency string
3638
}
3739

3840
// NewNodeLLB wraps PackageNode for LLB conversion.
@@ -45,8 +47,18 @@ func NewNodeLLB(node *solver.PackageNode, graph *GraphLLB) *NodeLLB {
4547
}
4648
}
4749

48-
func (node *NodeLLB) base() llb.State {
49-
return node.Graph.BaseImages[node.Pkg.Variant]
50+
func (node *NodeLLB) base() (llb.State, error) {
51+
if node.Pkg.Variant == v1alpha2.Scratch && len(node.Dependencies) > 0 {
52+
// pull the first dependency as base image if the package build is from scratch
53+
promotedDep := node.Dependencies[0]
54+
node.promotedDependency = promotedDep.ID()
55+
56+
depState, _, err := node.convertDependency(promotedDep)
57+
58+
return node.Graph.baseImageProcessor(depState), err
59+
}
60+
61+
return node.Graph.BaseImages[node.Pkg.Variant], nil
5062
}
5163

5264
func (node *NodeLLB) install(root llb.State) llb.State {
@@ -70,6 +82,22 @@ func (node *NodeLLB) context(root llb.State) llb.State {
7082
)
7183
}
7284

85+
func (node *NodeLLB) convertDependency(dep solver.PackageDependency) (depState llb.State, srcName string, err error) {
86+
if dep.IsInternal() {
87+
depState, err = NewNodeLLB(dep.Node, node.Graph).Build()
88+
if err != nil {
89+
return llb.Scratch(), "", err
90+
}
91+
92+
srcName = dep.Node.Name
93+
} else {
94+
depState = llb.Image(dep.Image)
95+
srcName = dep.Image
96+
}
97+
98+
return
99+
}
100+
73101
func (node *NodeLLB) dependencies(root llb.State) (llb.State, error) {
74102
deps := make([]solver.PackageDependency, 0, len(node.Dependencies))
75103

@@ -96,28 +124,19 @@ func (node *NodeLLB) dependencies(root llb.State) (llb.State, error) {
96124

97125
seen[dep.ID()] = struct{}{}
98126

99-
var (
100-
depState llb.State
101-
srcName string
102-
)
103-
104-
if dep.IsInternal() {
105-
var err error
106-
107-
depState, err = NewNodeLLB(dep.Node, node.Graph).Build()
108-
if err != nil {
109-
return llb.Scratch(), err
110-
}
127+
if node.promotedDependency == dep.ID() {
128+
// dependency promoted as base image, skip it
129+
continue
130+
}
111131

112-
srcName = dep.Node.Name
113-
} else {
114-
depState = llb.Image(dep.Image)
115-
srcName = dep.Image
132+
depState, srcName, err := node.convertDependency(dep)
133+
if err != nil {
134+
return llb.Scratch(), err
116135
}
117136

118137
root = root.File(
119138
llb.Copy(depState, dep.Src(), dep.Dest(), defaultCopyOptions),
120-
llb.WithCustomNamef(node.Prefix+"copy --from %s %s -> %s", srcName, dep.Src(), dep.Dest()))
139+
llb.WithCustomNamef("copy --from %s %s -> %s", srcName, dep.Src(), dep.Dest()))
121140
}
122141

123142
return root, nil
@@ -229,21 +248,23 @@ func (node *NodeLLB) finalize(root llb.State) llb.State {
229248

230249
// Build converts PackageNode to buildkit LLB.
231250
func (node *NodeLLB) Build() (llb.State, error) {
232-
var err error
233-
234251
if state, ok := node.Graph.cache[node.PackageNode]; ok {
235252
return state, nil
236253
}
237254

238-
root := node.base()
239-
root = node.install(root)
240-
root = node.context(root)
255+
root, err := node.base()
256+
if err != nil {
257+
return llb.Scratch(), err
258+
}
241259

242260
root, err = node.dependencies(root)
243261
if err != nil {
244262
return llb.Scratch(), err
245263
}
246264

265+
root = node.install(root)
266+
root = node.context(root)
267+
247268
for i, step := range node.Pkg.Steps {
248269
root = node.step(root, i, step)
249270
}

0 commit comments

Comments
 (0)