Builder disk usage and clean

We use dedicated docker container as builder and we are able to clean
data inside only by re-creating of it. Let's add disk usage and clean
commands for builder.

Signed-off-by: Petr Fedchenkov <giggsoff@gmail.com>
This commit is contained in:
Petr Fedchenkov 2022-07-27 19:50:17 +03:00
parent 86cc42bf79
commit 45a5c97931
No known key found for this signature in database
GPG Key ID: 01AB26025D699586
5 changed files with 275 additions and 1 deletions

View File

@ -12,6 +12,7 @@ func pkgUsage() {
fmt.Printf("'subcommand' is one of:\n")
fmt.Printf(" build\n")
fmt.Printf(" builder\n")
fmt.Printf(" push\n")
fmt.Printf(" show-tag\n")
fmt.Printf("\n")
@ -28,6 +29,8 @@ func pkg(args []string) {
switch args[0] {
case "build":
pkgBuild(args[1:])
case "builder":
pkgBuilder(args[1:])
case "push":
pkgPush(args[1:])
case "show-tag":

View 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)
}
}

View File

@ -19,6 +19,7 @@ import (
"github.com/google/go-containerregistry/pkg/v1/types"
"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"
)
@ -52,7 +53,9 @@ func (d *dockerMocker) contextSupportCheck() error {
}
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 {
if !d.enableBuild {
return errors.New("build disabled")

View File

@ -57,6 +57,7 @@ type dockerRunner interface {
load(src io.Reader) error
pull(img string) (bool, error)
contextSupportCheck() error
builder(ctx context.Context, dockerContext, builderImage, platform string, restart bool) (*buildkitClient.Client, error)
}
type dockerRunnerImpl struct {

View 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
}