Add benchmarking code (#448)
* adding benchmarking code * enable writing to file * fix build * time more stuff * adding benchmarking to integration tests * compare docker and kaniko times in integration tests * Switch to setting benchmark file with an env var * close file at the right time * fix integration test with environment variables * fix integration tests * Adding benchmarking documentation to DEVELOPEMENT.md * human readable benchmarking steps
This commit is contained in:
parent
a49fd79e0e
commit
7cde036f44
|
|
@ -97,6 +97,27 @@ Requirements:
|
||||||
|
|
||||||
These tests will be kicked off by [reviewers](#reviews) for submitted PRs.
|
These tests will be kicked off by [reviewers](#reviews) for submitted PRs.
|
||||||
|
|
||||||
|
### Benchmarking
|
||||||
|
|
||||||
|
The goal is for Kaniko to be at least as fast at building Dockerfiles as Docker is, and to that end, we've built
|
||||||
|
in benchmarking to check the speed of not only each full run, but also how long each step of each run takes. To turn
|
||||||
|
on benchmarking, just set the `BENCHMARK_FILE` environment variable, and kaniko will output all the benchmark info
|
||||||
|
of each run to that file location.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run -v $(pwd):/workspace -v ~/.config:/root/.config \
|
||||||
|
-e BENCHMARK_FILE=/workspace/benchmark_file \
|
||||||
|
gcr.io/kaniko-project/executor:latest \
|
||||||
|
--dockerfile=<path to Dockerfile> --context=/workspace \
|
||||||
|
--destination=gcr.io/my-repo/my-image
|
||||||
|
```
|
||||||
|
Additionally, the integration tests can output benchmarking information to a `benchmarks` directory under the
|
||||||
|
`integration` directory if the `BENCHMARK` environment variable is set to `true.`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
BENCHMARK=true go test -v --bucket $GCS_BUCKET --repo $IMAGE_REPO
|
||||||
|
```
|
||||||
|
|
||||||
## Creating a PR
|
## Creating a PR
|
||||||
|
|
||||||
When you have changes you would like to propose to kaniko, you will need to:
|
When you have changes you would like to propose to kaniko, you will need to:
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ func init() {
|
||||||
addHiddenFlags(RootCmd)
|
addHiddenFlags(RootCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RootCmd is the kaniko command that is run
|
||||||
var RootCmd = &cobra.Command{
|
var RootCmd = &cobra.Command{
|
||||||
Use: "executor",
|
Use: "executor",
|
||||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,15 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/GoogleContainerTools/kaniko/pkg/timing"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ExecutorImage is the name of the kaniko executor image
|
// ExecutorImage is the name of the kaniko executor image
|
||||||
ExecutorImage = "executor-image"
|
ExecutorImage = "executor-image"
|
||||||
WarmerImage = "warmer-image"
|
//WarmerImage is the name of the kaniko cache warmer image
|
||||||
|
WarmerImage = "warmer-image"
|
||||||
|
|
||||||
dockerPrefix = "docker-"
|
dockerPrefix = "docker-"
|
||||||
kanikoPrefix = "kaniko-"
|
kanikoPrefix = "kaniko-"
|
||||||
|
|
@ -159,7 +162,9 @@ func (d *DockerFileBuilder) BuildImage(imageRepo, gcsBucket, dockerfilesPath, do
|
||||||
additionalFlags...)...,
|
additionalFlags...)...,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
timer := timing.Start(dockerfile + "_docker")
|
||||||
_, err := RunCommandWithoutTest(dockerCmd)
|
_, err := RunCommandWithoutTest(dockerCmd)
|
||||||
|
timing.DefaultRun.Stop(timer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to build image %s with docker command \"%s\": %s", dockerImage, dockerCmd.Args, err)
|
return fmt.Errorf("Failed to build image %s with docker command \"%s\": %s", dockerImage, dockerCmd.Args, err)
|
||||||
}
|
}
|
||||||
|
|
@ -182,6 +187,12 @@ func (d *DockerFileBuilder) BuildImage(imageRepo, gcsBucket, dockerfilesPath, do
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
benchmarkEnv := "BENCHMARK_FILE=false"
|
||||||
|
if os.Getenv("BENCHMARK") == "true" {
|
||||||
|
os.Mkdir("benchmarks", 0755)
|
||||||
|
benchmarkEnv = "BENCHMARK_FILE=/workspace/benchmarks/" + dockerfile
|
||||||
|
}
|
||||||
|
|
||||||
// build kaniko image
|
// build kaniko image
|
||||||
additionalFlags = append(buildArgs, additionalKanikoFlagsMap[dockerfile]...)
|
additionalFlags = append(buildArgs, additionalKanikoFlagsMap[dockerfile]...)
|
||||||
kanikoImage := GetKanikoImage(imageRepo, dockerfile)
|
kanikoImage := GetKanikoImage(imageRepo, dockerfile)
|
||||||
|
|
@ -189,6 +200,7 @@ func (d *DockerFileBuilder) BuildImage(imageRepo, gcsBucket, dockerfilesPath, do
|
||||||
append([]string{"run",
|
append([]string{"run",
|
||||||
"-v", os.Getenv("HOME") + "/.config/gcloud:/root/.config/gcloud",
|
"-v", os.Getenv("HOME") + "/.config/gcloud:/root/.config/gcloud",
|
||||||
"-v", cwd + ":/workspace",
|
"-v", cwd + ":/workspace",
|
||||||
|
"-e", benchmarkEnv,
|
||||||
ExecutorImage,
|
ExecutorImage,
|
||||||
"-f", path.Join(buildContextPath, dockerfilesPath, dockerfile),
|
"-f", path.Join(buildContextPath, dockerfilesPath, dockerfile),
|
||||||
"-d", kanikoImage, reproducibleFlag,
|
"-d", kanikoImage, reproducibleFlag,
|
||||||
|
|
@ -196,7 +208,9 @@ func (d *DockerFileBuilder) BuildImage(imageRepo, gcsBucket, dockerfilesPath, do
|
||||||
additionalFlags...)...,
|
additionalFlags...)...,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
timer = timing.Start(dockerfile + "_kaniko")
|
||||||
_, err = RunCommandWithoutTest(kanikoCmd)
|
_, err = RunCommandWithoutTest(kanikoCmd)
|
||||||
|
timing.DefaultRun.Stop(timer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to build image %s with kaniko command \"%s\": %s", dockerImage, kanikoCmd.Args, err)
|
return fmt.Errorf("Failed to build image %s with kaniko command \"%s\": %s", dockerImage, kanikoCmd.Args, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import (
|
||||||
"github.com/google/go-containerregistry/pkg/name"
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/daemon"
|
"github.com/google/go-containerregistry/pkg/v1/daemon"
|
||||||
|
|
||||||
|
"github.com/GoogleContainerTools/kaniko/pkg/timing"
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||||
)
|
)
|
||||||
|
|
@ -215,6 +216,16 @@ func TestRun(t *testing.T) {
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if os.Getenv("BENCHMARK") == "true" {
|
||||||
|
f, err := os.Create("benchmark")
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("Failed to create benchmark file")
|
||||||
|
} else {
|
||||||
|
f.WriteString(timing.Summary())
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLayers(t *testing.T) {
|
func TestLayers(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import (
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/snapshot"
|
"github.com/GoogleContainerTools/kaniko/pkg/snapshot"
|
||||||
|
"github.com/GoogleContainerTools/kaniko/pkg/timing"
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -56,14 +57,18 @@ type stageBuilder struct {
|
||||||
|
|
||||||
// newStageBuilder returns a new type stageBuilder which contains all the information required to build the stage
|
// newStageBuilder returns a new type stageBuilder which contains all the information required to build the stage
|
||||||
func newStageBuilder(opts *config.KanikoOptions, stage config.KanikoStage) (*stageBuilder, error) {
|
func newStageBuilder(opts *config.KanikoOptions, stage config.KanikoStage) (*stageBuilder, error) {
|
||||||
|
t := timing.Start("Retrieving Source Image")
|
||||||
sourceImage, err := util.RetrieveSourceImage(stage, opts)
|
sourceImage, err := util.RetrieveSourceImage(stage, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
timing.DefaultRun.Stop(t)
|
||||||
|
t = timing.Start("Retrieving Config File")
|
||||||
imageConfig, err := util.RetrieveConfigFile(sourceImage)
|
imageConfig, err := util.RetrieveConfigFile(sourceImage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
timing.DefaultRun.Stop(t)
|
||||||
if err := resolveOnBuild(&stage, &imageConfig.Config); err != nil {
|
if err := resolveOnBuild(&stage, &imageConfig.Config); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -179,17 +184,21 @@ func (s *stageBuilder) build() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if shouldUnpack {
|
if shouldUnpack {
|
||||||
|
t := timing.Start("FS Unpacking")
|
||||||
if _, err := util.GetFSFromImage(constants.RootDir, s.image); err != nil {
|
if _, err := util.GetFSFromImage(constants.RootDir, s.image); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
timing.DefaultRun.Stop(t)
|
||||||
}
|
}
|
||||||
if err := util.DetectFilesystemWhitelist(constants.WhitelistPath); err != nil {
|
if err := util.DetectFilesystemWhitelist(constants.WhitelistPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Take initial snapshot
|
// Take initial snapshot
|
||||||
|
t := timing.Start("Initial FS snapshot")
|
||||||
if err := s.snapshotter.Init(); err != nil {
|
if err := s.snapshotter.Init(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
timing.DefaultRun.Stop(t)
|
||||||
|
|
||||||
cacheGroup := errgroup.Group{}
|
cacheGroup := errgroup.Group{}
|
||||||
for index, command := range cmds {
|
for index, command := range cmds {
|
||||||
|
|
@ -199,7 +208,7 @@ func (s *stageBuilder) build() error {
|
||||||
|
|
||||||
// Add the next command to the cache key.
|
// Add the next command to the cache key.
|
||||||
compositeKey.AddKey(command.String())
|
compositeKey.AddKey(command.String())
|
||||||
|
t := timing.Start("Command: " + command.String())
|
||||||
// If the command uses files from the context, add them.
|
// If the command uses files from the context, add them.
|
||||||
files, err := command.FilesUsedFromContext(&s.cf.Config, args)
|
files, err := command.FilesUsedFromContext(&s.cf.Config, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -239,6 +248,7 @@ func (s *stageBuilder) build() error {
|
||||||
if err := s.saveSnapshotToImage(command.String(), tarPath); err != nil {
|
if err := s.saveSnapshotToImage(command.String(), tarPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
timing.DefaultRun.Stop(t)
|
||||||
}
|
}
|
||||||
if err := cacheGroup.Wait(); err != nil {
|
if err := cacheGroup.Wait(); err != nil {
|
||||||
logrus.Warnf("error uploading layer to cache: %s", err)
|
logrus.Warnf("error uploading layer to cache: %s", err)
|
||||||
|
|
@ -247,15 +257,21 @@ func (s *stageBuilder) build() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stageBuilder) takeSnapshot(files []string) (string, error) {
|
func (s *stageBuilder) takeSnapshot(files []string) (string, error) {
|
||||||
|
var snapshot string
|
||||||
|
var err error
|
||||||
|
t := timing.Start("Snapshotting FS")
|
||||||
if files == nil || s.opts.SingleSnapshot {
|
if files == nil || s.opts.SingleSnapshot {
|
||||||
return s.snapshotter.TakeSnapshotFS()
|
snapshot, err = s.snapshotter.TakeSnapshotFS()
|
||||||
|
} else {
|
||||||
|
// Volumes are very weird. They get created in their command, but snapshotted in the next one.
|
||||||
|
// Add them to the list of files to snapshot.
|
||||||
|
for v := range s.cf.Config.Volumes {
|
||||||
|
files = append(files, v)
|
||||||
|
}
|
||||||
|
snapshot, err = s.snapshotter.TakeSnapshot(files)
|
||||||
}
|
}
|
||||||
// Volumes are very weird. They get created in their command, but snapshotted in the next one.
|
timing.DefaultRun.Stop(t)
|
||||||
// Add them to the list of files to snapshot.
|
return snapshot, err
|
||||||
for v := range s.cf.Config.Volumes {
|
|
||||||
files = append(files, v)
|
|
||||||
}
|
|
||||||
return s.snapshotter.TakeSnapshot(files)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stageBuilder) shouldTakeSnapshot(index int, files []string) bool {
|
func (s *stageBuilder) shouldTakeSnapshot(index int, files []string) bool {
|
||||||
|
|
@ -315,6 +331,7 @@ func (s *stageBuilder) saveSnapshotToImage(createdBy string, tarPath string) err
|
||||||
|
|
||||||
// DoBuild executes building the Dockerfile
|
// DoBuild executes building the Dockerfile
|
||||||
func DoBuild(opts *config.KanikoOptions) (v1.Image, error) {
|
func DoBuild(opts *config.KanikoOptions) (v1.Image, error) {
|
||||||
|
t := timing.Start("Total Build Time")
|
||||||
// Parse dockerfile and unpack base image to root
|
// Parse dockerfile and unpack base image to root
|
||||||
stages, err := dockerfile.Stages(opts)
|
stages, err := dockerfile.Stages(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -349,6 +366,17 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
timing.DefaultRun.Stop(t)
|
||||||
|
benchmarkFile := os.Getenv("BENCHMARK_FILE")
|
||||||
|
// false is a keyword for integration tests to turn off benchmarking
|
||||||
|
if benchmarkFile != "" && benchmarkFile != "false" {
|
||||||
|
f, err := os.Create(benchmarkFile)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warnf("Unable to create benchmarking file %s: %s", benchmarkFile, err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
f.WriteString(timing.Summary())
|
||||||
|
}
|
||||||
return sourceImage, nil
|
return sourceImage, nil
|
||||||
}
|
}
|
||||||
if stage.SaveStage {
|
if stage.SaveStage {
|
||||||
|
|
@ -364,6 +392,7 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package timing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"sync"
|
"sync"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -42,7 +41,6 @@ func (tr *TimedRun) Stop(t *Timer) {
|
||||||
if _, ok := tr.categories[t.category]; !ok {
|
if _, ok := tr.categories[t.category]; !ok {
|
||||||
tr.categories[t.category] = 0
|
tr.categories[t.category] = 0
|
||||||
}
|
}
|
||||||
fmt.Println(stop)
|
|
||||||
tr.cl.Lock()
|
tr.cl.Lock()
|
||||||
defer tr.cl.Unlock()
|
defer tr.cl.Unlock()
|
||||||
tr.categories[t.category] += stop.Sub(t.startTime)
|
tr.categories[t.category] += stop.Sub(t.startTime)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue