mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-21 10:09:07 +00:00
Merge pull request #3823 from giggsoff/clean_builder_cache
Builder disk usage and clean
This commit is contained in:
commit
5f3856c94c
@ -12,6 +12,7 @@ func pkgUsage() {
|
|||||||
|
|
||||||
fmt.Printf("'subcommand' is one of:\n")
|
fmt.Printf("'subcommand' is one of:\n")
|
||||||
fmt.Printf(" build\n")
|
fmt.Printf(" build\n")
|
||||||
|
fmt.Printf(" builder\n")
|
||||||
fmt.Printf(" push\n")
|
fmt.Printf(" push\n")
|
||||||
fmt.Printf(" show-tag\n")
|
fmt.Printf(" show-tag\n")
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
@ -28,6 +29,8 @@ func pkg(args []string) {
|
|||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "build":
|
case "build":
|
||||||
pkgBuild(args[1:])
|
pkgBuild(args[1:])
|
||||||
|
case "builder":
|
||||||
|
pkgBuilder(args[1:])
|
||||||
case "push":
|
case "push":
|
||||||
pkgPush(args[1:])
|
pkgPush(args[1:])
|
||||||
case "show-tag":
|
case "show-tag":
|
||||||
|
84
src/cmd/linuxkit/pkg_builder.go
Normal file
84
src/cmd/linuxkit/pkg_builder.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/pkglib"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func pkgBuilderUsage() {
|
||||||
|
invoked := filepath.Base(os.Args[0])
|
||||||
|
fmt.Printf("USAGE: %s builder command [options]\n\n", invoked)
|
||||||
|
fmt.Printf("Supported commands are\n")
|
||||||
|
// Please keep these in alphabetical order
|
||||||
|
fmt.Printf(" du\n")
|
||||||
|
fmt.Printf(" prune\n")
|
||||||
|
fmt.Printf("\n")
|
||||||
|
fmt.Printf("'options' are the backend specific options.\n")
|
||||||
|
fmt.Printf("See '%s builder [command] --help' for details.\n\n", invoked)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the builder
|
||||||
|
func pkgBuilder(args []string) {
|
||||||
|
if len(args) < 1 {
|
||||||
|
pkgBuilderUsage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
switch args[0] {
|
||||||
|
// Please keep cases in alphabetical order
|
||||||
|
case "du":
|
||||||
|
pkgBuilderCommands(args[0], args[1:])
|
||||||
|
case "prune":
|
||||||
|
pkgBuilderCommands(args[0], args[1:])
|
||||||
|
case "help", "-h", "-help", "--help":
|
||||||
|
pkgBuilderUsage()
|
||||||
|
os.Exit(0)
|
||||||
|
default:
|
||||||
|
log.Errorf("No 'builder' command specified.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pkgBuilderCommands(command string, args []string) {
|
||||||
|
flags := flag.NewFlagSet(command, flag.ExitOnError)
|
||||||
|
builders := flags.String("builders", "", "Which builders to use for which platforms, e.g. linux/arm64=docker-context-arm64, overrides defaults and environment variables, see https://github.com/linuxkit/linuxkit/blob/master/docs/packages.md#Providing-native-builder-nodes")
|
||||||
|
platforms := flags.String("platforms", fmt.Sprintf("linux/%s", runtime.GOARCH), "Which platforms we built images for")
|
||||||
|
builderImage := flags.String("builder-image", defaultBuilderImage, "buildkit builder container image to use")
|
||||||
|
verbose := flags.Bool("v", false, "Verbose output")
|
||||||
|
if err := flags.Parse(args); err != nil {
|
||||||
|
log.Fatal("Unable to parse args")
|
||||||
|
}
|
||||||
|
// build the builders map
|
||||||
|
buildersMap := make(map[string]string)
|
||||||
|
// look for builders env var
|
||||||
|
buildersMap, err := buildPlatformBuildersMap(os.Getenv(buildersEnvVar), buildersMap)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%s in environment variable %s\n", err.Error(), buildersEnvVar)
|
||||||
|
}
|
||||||
|
// any CLI options override env var
|
||||||
|
buildersMap, err = buildPlatformBuildersMap(*builders, buildersMap)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%s in --builders flag\n", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
platformsToClean := strings.Split(*platforms, ",")
|
||||||
|
switch command {
|
||||||
|
case "du":
|
||||||
|
if err := pkglib.DiskUsage(buildersMap, *builderImage, platformsToClean, *verbose); err != nil {
|
||||||
|
log.Fatalf("Unable to print disk usage of builder: %v", err)
|
||||||
|
}
|
||||||
|
case "prune":
|
||||||
|
if err := pkglib.PruneBuilder(buildersMap, *builderImage, platformsToClean, *verbose); err != nil {
|
||||||
|
log.Fatalf("Unable to prune builder: %v", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Errorf("unexpected command %s", command)
|
||||||
|
pkgBuilderUsage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/google/go-containerregistry/pkg/v1/types"
|
"github.com/google/go-containerregistry/pkg/v1/types"
|
||||||
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
|
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
|
||||||
lktspec "github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
|
lktspec "github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
|
||||||
|
buildkitClient "github.com/moby/buildkit/client"
|
||||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -52,7 +53,9 @@ func (d *dockerMocker) contextSupportCheck() error {
|
|||||||
}
|
}
|
||||||
return errors.New("contexts not supported")
|
return errors.New("contexts not supported")
|
||||||
}
|
}
|
||||||
|
func (d *dockerMocker) builder(_ context.Context, _, _, _ string, _ bool) (*buildkitClient.Client, error) {
|
||||||
|
return nil, fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
func (d *dockerMocker) build(ctx context.Context, tag, pkg, dockerContext, builderImage, platform string, builderRestart bool, c spec.CacheProvider, r io.Reader, stdout io.Writer, imageBuildOpts dockertypes.ImageBuildOptions) error {
|
func (d *dockerMocker) build(ctx context.Context, tag, pkg, dockerContext, builderImage, platform string, builderRestart bool, c spec.CacheProvider, r io.Reader, stdout io.Writer, imageBuildOpts dockertypes.ImageBuildOptions) error {
|
||||||
if !d.enableBuild {
|
if !d.enableBuild {
|
||||||
return errors.New("build disabled")
|
return errors.New("build disabled")
|
||||||
|
@ -57,6 +57,7 @@ type dockerRunner interface {
|
|||||||
load(src io.Reader) error
|
load(src io.Reader) error
|
||||||
pull(img string) (bool, error)
|
pull(img string) (bool, error)
|
||||||
contextSupportCheck() error
|
contextSupportCheck() error
|
||||||
|
builder(ctx context.Context, dockerContext, builderImage, platform string, restart bool) (*buildkitClient.Client, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type dockerRunnerImpl struct {
|
type dockerRunnerImpl struct {
|
||||||
|
183
src/cmd/linuxkit/pkglib/utils.go
Normal file
183
src/cmd/linuxkit/pkglib/utils.go
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
package pkglib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
|
"github.com/docker/go-units"
|
||||||
|
buildkitClient "github.com/moby/buildkit/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func printTableHeader(tw *tabwriter.Writer) {
|
||||||
|
fmt.Fprintln(tw, "ID\tRECLAIMABLE\tSIZE\tLAST ACCESSED")
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTableRow(tw *tabwriter.Writer, di *buildkitClient.UsageInfo) {
|
||||||
|
id := di.ID
|
||||||
|
if di.Mutable {
|
||||||
|
id += "*"
|
||||||
|
}
|
||||||
|
size := units.HumanSize(float64(di.Size))
|
||||||
|
if di.Shared {
|
||||||
|
size += "*"
|
||||||
|
}
|
||||||
|
lastAccessed := ""
|
||||||
|
if di.LastUsedAt != nil {
|
||||||
|
lastAccessed = units.HumanDuration(time.Since(*di.LastUsedAt)) + " ago"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(tw, "%-40s\t%-5v\t%-10s\t%s\n", id, !di.InUse, size, lastAccessed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printSummary(tw *tabwriter.Writer, du []*buildkitClient.UsageInfo) {
|
||||||
|
total := int64(0)
|
||||||
|
reclaimable := int64(0)
|
||||||
|
shared := int64(0)
|
||||||
|
|
||||||
|
for _, di := range du {
|
||||||
|
if di.Size > 0 {
|
||||||
|
total += di.Size
|
||||||
|
if !di.InUse {
|
||||||
|
reclaimable += di.Size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if di.Shared {
|
||||||
|
shared += di.Size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if shared > 0 {
|
||||||
|
fmt.Fprintf(tw, "Shared:\t%s\n", units.HumanSize(float64(shared)))
|
||||||
|
fmt.Fprintf(tw, "Private:\t%s\n", units.HumanSize(float64(total-shared)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(tw, "Reclaimable:\t%s\n", units.HumanSize(float64(reclaimable)))
|
||||||
|
fmt.Fprintf(tw, "Total:\t%s\n", units.HumanSize(float64(total)))
|
||||||
|
tw.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func printKV(w io.Writer, k string, v interface{}) {
|
||||||
|
fmt.Fprintf(w, "%s:\t%v\n", k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printVerbose(tw *tabwriter.Writer, du []*buildkitClient.UsageInfo) {
|
||||||
|
for _, di := range du {
|
||||||
|
printKV(tw, "ID", di.ID)
|
||||||
|
if len(di.Parents) != 0 {
|
||||||
|
printKV(tw, "Parent", strings.Join(di.Parents, ","))
|
||||||
|
}
|
||||||
|
printKV(tw, "Created at", di.CreatedAt)
|
||||||
|
printKV(tw, "Mutable", di.Mutable)
|
||||||
|
printKV(tw, "Reclaimable", !di.InUse)
|
||||||
|
printKV(tw, "Shared", di.Shared)
|
||||||
|
printKV(tw, "Size", units.HumanSize(float64(di.Size)))
|
||||||
|
if di.Description != "" {
|
||||||
|
printKV(tw, "Description", di.Description)
|
||||||
|
}
|
||||||
|
printKV(tw, "Usage count", di.UsageCount)
|
||||||
|
if di.LastUsedAt != nil {
|
||||||
|
printKV(tw, "Last used", units.HumanDuration(time.Since(*di.LastUsedAt))+" ago")
|
||||||
|
}
|
||||||
|
if di.RecordType != "" {
|
||||||
|
printKV(tw, "Type", di.RecordType)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(tw, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
tw.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClientForPlatform(ctx context.Context, buildersMap map[string]string, builderImage, platform string) (*buildkitClient.Client, error) {
|
||||||
|
p, err := platforms.Parse(platform)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse platform: %s", err)
|
||||||
|
}
|
||||||
|
dr := newDockerRunner(false)
|
||||||
|
builderName := getBuilderForPlatform(p.Architecture, buildersMap)
|
||||||
|
client, err := dr.builder(ctx, builderName, builderImage, platform, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to ensure builder container: %v", err)
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiskUsage of builder
|
||||||
|
func DiskUsage(buildersMap map[string]string, builderImage string, platformsToClean []string, verbose bool) error {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
for _, platform := range platformsToClean {
|
||||||
|
client, err := getClientForPlatform(ctx, buildersMap, builderImage, platform)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot get client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
du, err := client.DiskUsage(ctx)
|
||||||
|
if err != nil {
|
||||||
|
_ = client.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = client.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot close client: %s", err)
|
||||||
|
}
|
||||||
|
tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0)
|
||||||
|
if len(du) > 0 {
|
||||||
|
if verbose {
|
||||||
|
printVerbose(tw, du)
|
||||||
|
} else {
|
||||||
|
printTableHeader(tw)
|
||||||
|
for _, di := range du {
|
||||||
|
printTableRow(tw, di)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printSummary(tw, du)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PruneBuilder clean build cache of builder
|
||||||
|
func PruneBuilder(buildersMap map[string]string, builderImage string, platformsToClean []string, verbose bool) error {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
total := int64(0)
|
||||||
|
for _, platform := range platformsToClean {
|
||||||
|
client, err := getClientForPlatform(ctx, buildersMap, builderImage, platform)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot get client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan buildkitClient.UsageInfo)
|
||||||
|
processed := make(chan struct{})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(processed)
|
||||||
|
for du := range ch {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("%s\t%s\tremoved\n", du.ID, units.HumanSize(float64(du.Size)))
|
||||||
|
}
|
||||||
|
total += du.Size
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
err = client.Prune(ctx, ch)
|
||||||
|
if err != nil {
|
||||||
|
_ = client.Close()
|
||||||
|
close(ch)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = client.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot close client: %s", err)
|
||||||
|
}
|
||||||
|
close(ch)
|
||||||
|
<-processed
|
||||||
|
}
|
||||||
|
fmt.Printf("Reclaimed:\t%s\n", units.BytesSize(float64(total)))
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user