mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
builder: use buildkit's GC for build cache
This allows users to configure the buildkit GC. The following enables the default GC: ``` { "builder": { "gc": { "enabled": true } } } ``` The default GC policy has a simple config: ``` { "builder": { "gc": { "enabled": true, "defaultKeepStorage": "30GB" } } } ``` A custom GC policy can be used instead by specifying a list of cache prune rules: ``` { "builder": { "gc": { "enabled": true, "policy": [ {"keepStorage": "512MB", "filter": ["unused-for=1400h"]]}, {"keepStorage": "30GB", "all": true} ] } } } ``` Signed-off-by: Tibor Vass <tibor@docker.com>
This commit is contained in:
parent
b1116479b2
commit
4a776d0ca7
8 changed files with 201 additions and 32 deletions
|
@ -13,11 +13,13 @@ import (
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/backend"
|
"github.com/docker/docker/api/types/backend"
|
||||||
"github.com/docker/docker/builder"
|
"github.com/docker/docker/builder"
|
||||||
|
"github.com/docker/docker/daemon/config"
|
||||||
"github.com/docker/docker/daemon/images"
|
"github.com/docker/docker/daemon/images"
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
"github.com/docker/docker/pkg/system"
|
"github.com/docker/docker/pkg/system"
|
||||||
"github.com/docker/libnetwork"
|
"github.com/docker/libnetwork"
|
||||||
controlapi "github.com/moby/buildkit/api/services/control"
|
controlapi "github.com/moby/buildkit/api/services/control"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
"github.com/moby/buildkit/control"
|
"github.com/moby/buildkit/control"
|
||||||
"github.com/moby/buildkit/identity"
|
"github.com/moby/buildkit/identity"
|
||||||
"github.com/moby/buildkit/session"
|
"github.com/moby/buildkit/session"
|
||||||
|
@ -57,6 +59,7 @@ type Opt struct {
|
||||||
NetworkController libnetwork.NetworkController
|
NetworkController libnetwork.NetworkController
|
||||||
DefaultCgroupParent string
|
DefaultCgroupParent string
|
||||||
ResolverOpt resolver.ResolveOptionsFunc
|
ResolverOpt resolver.ResolveOptionsFunc
|
||||||
|
BuilderConfig config.BuilderConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builder can build using BuildKit backend
|
// Builder can build using BuildKit backend
|
||||||
|
@ -134,43 +137,18 @@ func (b *Builder) Prune(ctx context.Context, opts types.BuildCachePruneOptions)
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var unusedFor time.Duration
|
pi, err := toBuildkitPruneInfo(opts)
|
||||||
unusedForValues := opts.Filters.Get("unused-for")
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
switch len(unusedForValues) {
|
|
||||||
case 0:
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
var err error
|
|
||||||
unusedFor, err = time.ParseDuration(unusedForValues[0])
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, errors.Wrap(err, "unused-for filter expects a duration (e.g., '24h')")
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 0, nil, errMultipleFilterValues
|
|
||||||
}
|
|
||||||
|
|
||||||
bkFilter := make([]string, 0, opts.Filters.Len())
|
|
||||||
for cacheField := range cacheFields {
|
|
||||||
values := opts.Filters.Get(cacheField)
|
|
||||||
switch len(values) {
|
|
||||||
case 0:
|
|
||||||
bkFilter = append(bkFilter, cacheField)
|
|
||||||
case 1:
|
|
||||||
bkFilter = append(bkFilter, cacheField+"=="+values[0])
|
|
||||||
default:
|
|
||||||
return 0, nil, errMultipleFilterValues
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
defer close(ch)
|
defer close(ch)
|
||||||
return b.controller.Prune(&controlapi.PruneRequest{
|
return b.controller.Prune(&controlapi.PruneRequest{
|
||||||
All: opts.All,
|
All: pi.All,
|
||||||
KeepDuration: int64(unusedFor),
|
KeepDuration: int64(pi.KeepDuration),
|
||||||
KeepBytes: opts.KeepStorage,
|
KeepBytes: pi.KeepBytes,
|
||||||
Filter: bkFilter,
|
Filter: pi.Filter,
|
||||||
}, &pruneProxy{
|
}, &pruneProxy{
|
||||||
streamProxy: streamProxy{ctx: ctx},
|
streamProxy: streamProxy{ctx: ctx},
|
||||||
ch: ch,
|
ch: ch,
|
||||||
|
@ -531,3 +509,41 @@ func toBuildkitExtraHosts(inp []string) (string, error) {
|
||||||
}
|
}
|
||||||
return strings.Join(hosts, ","), nil
|
return strings.Join(hosts, ","), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toBuildkitPruneInfo(opts types.BuildCachePruneOptions) (client.PruneInfo, error) {
|
||||||
|
var unusedFor time.Duration
|
||||||
|
unusedForValues := opts.Filters.Get("unused-for")
|
||||||
|
|
||||||
|
switch len(unusedForValues) {
|
||||||
|
case 0:
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
var err error
|
||||||
|
unusedFor, err = time.ParseDuration(unusedForValues[0])
|
||||||
|
if err != nil {
|
||||||
|
return client.PruneInfo{}, errors.Wrap(err, "unused-for filter expects a duration (e.g., '24h')")
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return client.PruneInfo{}, errMultipleFilterValues
|
||||||
|
}
|
||||||
|
|
||||||
|
bkFilter := make([]string, 0, opts.Filters.Len())
|
||||||
|
for cacheField := range cacheFields {
|
||||||
|
values := opts.Filters.Get(cacheField)
|
||||||
|
switch len(values) {
|
||||||
|
case 0:
|
||||||
|
bkFilter = append(bkFilter, cacheField)
|
||||||
|
case 1:
|
||||||
|
bkFilter = append(bkFilter, cacheField+"=="+values[0])
|
||||||
|
default:
|
||||||
|
return client.PruneInfo{}, errMultipleFilterValues
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return client.PruneInfo{
|
||||||
|
All: opts.All,
|
||||||
|
KeepDuration: unusedFor,
|
||||||
|
KeepBytes: opts.KeepStorage,
|
||||||
|
Filter: bkFilter,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
|
@ -6,15 +6,19 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/containerd/containerd/content/local"
|
"github.com/containerd/containerd/content/local"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/builder/builder-next/adapters/containerimage"
|
"github.com/docker/docker/builder/builder-next/adapters/containerimage"
|
||||||
"github.com/docker/docker/builder/builder-next/adapters/snapshot"
|
"github.com/docker/docker/builder/builder-next/adapters/snapshot"
|
||||||
containerimageexp "github.com/docker/docker/builder/builder-next/exporter"
|
containerimageexp "github.com/docker/docker/builder/builder-next/exporter"
|
||||||
"github.com/docker/docker/builder/builder-next/imagerefchecker"
|
"github.com/docker/docker/builder/builder-next/imagerefchecker"
|
||||||
mobyworker "github.com/docker/docker/builder/builder-next/worker"
|
mobyworker "github.com/docker/docker/builder/builder-next/worker"
|
||||||
|
"github.com/docker/docker/daemon/config"
|
||||||
"github.com/docker/docker/daemon/graphdriver"
|
"github.com/docker/docker/daemon/graphdriver"
|
||||||
|
units "github.com/docker/go-units"
|
||||||
"github.com/moby/buildkit/cache"
|
"github.com/moby/buildkit/cache"
|
||||||
"github.com/moby/buildkit/cache/metadata"
|
"github.com/moby/buildkit/cache/metadata"
|
||||||
registryremotecache "github.com/moby/buildkit/cache/remotecache/registry"
|
registryremotecache "github.com/moby/buildkit/cache/remotecache/registry"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
"github.com/moby/buildkit/control"
|
"github.com/moby/buildkit/control"
|
||||||
"github.com/moby/buildkit/exporter"
|
"github.com/moby/buildkit/exporter"
|
||||||
"github.com/moby/buildkit/frontend"
|
"github.com/moby/buildkit/frontend"
|
||||||
|
@ -127,12 +131,18 @@ func newController(rt http.RoundTripper, opt Opt) (*control.Controller, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gcPolicy, err := getGCPolicy(opt.BuilderConfig, root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not get builder GC policy")
|
||||||
|
}
|
||||||
|
|
||||||
wopt := mobyworker.Opt{
|
wopt := mobyworker.Opt{
|
||||||
ID: "moby",
|
ID: "moby",
|
||||||
SessionManager: opt.SessionManager,
|
SessionManager: opt.SessionManager,
|
||||||
MetadataStore: md,
|
MetadataStore: md,
|
||||||
ContentStore: store,
|
ContentStore: store,
|
||||||
CacheManager: cm,
|
CacheManager: cm,
|
||||||
|
GCPolicy: gcPolicy,
|
||||||
Snapshotter: snapshotter,
|
Snapshotter: snapshotter,
|
||||||
Executor: exec,
|
Executor: exec,
|
||||||
ImageSource: src,
|
ImageSource: src,
|
||||||
|
@ -165,3 +175,44 @@ func newController(rt http.RoundTripper, opt Opt) (*control.Controller, error) {
|
||||||
// TODO: set ResolveCacheExporterFunc for exporting cache
|
// TODO: set ResolveCacheExporterFunc for exporting cache
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getGCPolicy(conf config.BuilderConfig, root string) ([]client.PruneInfo, error) {
|
||||||
|
var gcPolicy []client.PruneInfo
|
||||||
|
if conf.GC.Enabled {
|
||||||
|
var (
|
||||||
|
defaultKeepStorage int64
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if conf.GC.DefaultKeepStorage != "" {
|
||||||
|
defaultKeepStorage, err = units.RAMInBytes(conf.GC.DefaultKeepStorage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "could not parse '%s' as Builder.GC.DefaultKeepStorage config", conf.GC.DefaultKeepStorage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.GC.Policy == nil {
|
||||||
|
gcPolicy = mobyworker.DefaultGCPolicy(root, defaultKeepStorage)
|
||||||
|
} else {
|
||||||
|
gcPolicy = make([]client.PruneInfo, len(conf.GC.Policy))
|
||||||
|
for i, p := range conf.GC.Policy {
|
||||||
|
b, err := units.RAMInBytes(p.KeepStorage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if b == 0 {
|
||||||
|
b = defaultKeepStorage
|
||||||
|
}
|
||||||
|
gcPolicy[i], err = toBuildkitPruneInfo(types.BuildCachePruneOptions{
|
||||||
|
All: p.All,
|
||||||
|
KeepStorage: b,
|
||||||
|
Filters: p.Filter,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gcPolicy, nil
|
||||||
|
}
|
||||||
|
|
51
builder/builder-next/worker/gc.go
Normal file
51
builder/builder-next/worker/gc.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package worker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultCap int64 = 2e9 // 2GB
|
||||||
|
|
||||||
|
// tempCachePercent represents the percentage ratio of the cache size in bytes to temporarily keep for a short period of time (couple of days)
|
||||||
|
// over the total cache size in bytes. Because there is no perfect value, a mathematically pleasing one was chosen.
|
||||||
|
// The value is approximately 13.8
|
||||||
|
const tempCachePercent = math.E * math.Pi * math.Phi
|
||||||
|
|
||||||
|
// DefaultGCPolicy returns a default builder GC policy
|
||||||
|
func DefaultGCPolicy(p string, defaultKeepBytes int64) []client.PruneInfo {
|
||||||
|
keep := defaultKeepBytes
|
||||||
|
if defaultKeepBytes == 0 {
|
||||||
|
keep = detectDefaultGCCap(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
tempCacheKeepBytes := int64(math.Round(float64(keep) / 100. * float64(tempCachePercent)))
|
||||||
|
const minTempCacheKeepBytes = 512 * 1e6 // 512MB
|
||||||
|
if tempCacheKeepBytes < minTempCacheKeepBytes {
|
||||||
|
tempCacheKeepBytes = minTempCacheKeepBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
return []client.PruneInfo{
|
||||||
|
// if build cache uses more than 512MB delete the most easily reproducible data after it has not been used for 2 days
|
||||||
|
{
|
||||||
|
Filter: []string{"type==source.local,type==exec.cachemount,type==source.git.checkout"},
|
||||||
|
KeepDuration: 48 * 3600, // 48h
|
||||||
|
KeepBytes: tempCacheKeepBytes,
|
||||||
|
},
|
||||||
|
// remove any data not used for 60 days
|
||||||
|
{
|
||||||
|
KeepDuration: 60 * 24 * 3600, // 60d
|
||||||
|
KeepBytes: keep,
|
||||||
|
},
|
||||||
|
// keep the unshared build cache under cap
|
||||||
|
{
|
||||||
|
KeepBytes: keep,
|
||||||
|
},
|
||||||
|
// if previous policies were insufficient start deleting internal data to keep build cache under cap
|
||||||
|
{
|
||||||
|
All: true,
|
||||||
|
KeepBytes: keep,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
17
builder/builder-next/worker/gc_unix.go
Normal file
17
builder/builder-next/worker/gc_unix.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package worker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func detectDefaultGCCap(root string) int64 {
|
||||||
|
var st syscall.Statfs_t
|
||||||
|
if err := syscall.Statfs(root, &st); err != nil {
|
||||||
|
return defaultCap
|
||||||
|
}
|
||||||
|
diskSize := int64(st.Bsize) * int64(st.Blocks) // nolint unconvert
|
||||||
|
avail := diskSize / 10
|
||||||
|
return (avail/(1<<30) + 1) * 1e9 // round up
|
||||||
|
}
|
7
builder/builder-next/worker/gc_windows.go
Normal file
7
builder/builder-next/worker/gc_windows.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package worker
|
||||||
|
|
||||||
|
func detectDefaultGCCap(root string) int64 {
|
||||||
|
return defaultCap
|
||||||
|
}
|
|
@ -292,6 +292,7 @@ func newRouterOptions(config *config.Config, d *daemon.Daemon) (routerOptions, e
|
||||||
NetworkController: d.NetworkController(),
|
NetworkController: d.NetworkController(),
|
||||||
DefaultCgroupParent: cgroupParent,
|
DefaultCgroupParent: cgroupParent,
|
||||||
ResolverOpt: d.NewResolveOptionsFunc(),
|
ResolverOpt: d.NewResolveOptionsFunc(),
|
||||||
|
BuilderConfig: config.Builder,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return opts, err
|
return opts, err
|
||||||
|
|
22
daemon/config/builder.go
Normal file
22
daemon/config/builder.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import "github.com/docker/docker/api/types/filters"
|
||||||
|
|
||||||
|
// BuilderGCRule represents a GC rule for buildkit cache
|
||||||
|
type BuilderGCRule struct {
|
||||||
|
All bool `json:",omitempty"`
|
||||||
|
Filter filters.Args `json:",omitempty"`
|
||||||
|
KeepStorage string `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuilderGCConfig contains GC config for a buildkit builder
|
||||||
|
type BuilderGCConfig struct {
|
||||||
|
Enabled bool `json:",omitempty"`
|
||||||
|
Policy []BuilderGCRule `json:",omitempty"`
|
||||||
|
DefaultKeepStorage string `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuilderConfig contains config for the builder
|
||||||
|
type BuilderConfig struct {
|
||||||
|
GC BuilderGCConfig `json:",omitempty"`
|
||||||
|
}
|
|
@ -55,6 +55,7 @@ var flatOptions = map[string]bool{
|
||||||
"runtimes": true,
|
"runtimes": true,
|
||||||
"default-ulimits": true,
|
"default-ulimits": true,
|
||||||
"features": true,
|
"features": true,
|
||||||
|
"builder": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// skipValidateOptions contains configuration keys
|
// skipValidateOptions contains configuration keys
|
||||||
|
@ -62,6 +63,7 @@ var flatOptions = map[string]bool{
|
||||||
// for unknown flag validation.
|
// for unknown flag validation.
|
||||||
var skipValidateOptions = map[string]bool{
|
var skipValidateOptions = map[string]bool{
|
||||||
"features": true,
|
"features": true,
|
||||||
|
"builder": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// skipDuplicates contains configuration keys that
|
// skipDuplicates contains configuration keys that
|
||||||
|
@ -225,6 +227,8 @@ type CommonConfig struct {
|
||||||
// Features contains a list of feature key value pairs indicating what features are enabled or disabled.
|
// Features contains a list of feature key value pairs indicating what features are enabled or disabled.
|
||||||
// If a certain feature doesn't appear in this list then it's unset (i.e. neither true nor false).
|
// If a certain feature doesn't appear in this list then it's unset (i.e. neither true nor false).
|
||||||
Features map[string]bool `json:"features,omitempty"`
|
Features map[string]bool `json:"features,omitempty"`
|
||||||
|
|
||||||
|
Builder BuilderConfig `json:"builder,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsValueSet returns true if a configuration value
|
// IsValueSet returns true if a configuration value
|
||||||
|
|
Loading…
Reference in a new issue