Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ expect - see [Known Issues](#known-issues).
- [Flag `FF_KANIKO_OCI_WARMER`](#flag-ff_kaniko_oci_warmer)
- [Flag `FF_KANIKO_RUN_VIA_TINI`](#flag-ff_kaniko_run_via_tini)
- [Flag `FF_KANIKO_COPY_CHMOD_ON_IMPLICIT_DIRS`](#flag-ff_kaniko_copy_chmod_on_implicit_dirs)
- [Flag `FF_KANIKO_SKIP_INTERSTAGE_CLEANUP`](#flag-ff_kaniko_skip_interstage_cleanup)
- [Debug Image](#debug-image)
- [Security](#security)
- [Verifying Signed Kaniko Images](#verifying-signed-kaniko-images)
Expand Down Expand Up @@ -1429,6 +1430,12 @@ When files are copied into a non-existing directory, both kaniko and buildkit wi
Set this flag to `true` to implement COPY chmod like buildkit. Defaults to `false`.
Currently no plans to activate.

#### Flag `FF_KANIKO_SKIP_INTERSTAGE_CLEANUP`

In multi-stage builds, kaniko always tears down the filesystem and re-unpacks the base image between stages. When a stage's `FROM` resolves to the immediately preceding preceding stage, this round-trip is redundant.
Set this flag to `true` to skip cleanup when consecutive stages chain directly Defaults to `false`.


### Debug Image

The kaniko executor image is based on scratch and doesn't contain a shell. We
Expand Down
2 changes: 2 additions & 0 deletions golden/golden_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
testissuemz195 "github.com/osscontainertools/kaniko/golden/testdata/test_issue_mz195"
testissuemz333 "github.com/osscontainertools/kaniko/golden/testdata/test_issue_mz333"
testissuemz338 "github.com/osscontainertools/kaniko/golden/testdata/test_issue_mz338"
testissuemz523 "github.com/osscontainertools/kaniko/golden/testdata/test_issue_mz523"
testunittests "github.com/osscontainertools/kaniko/golden/testdata/test_unittests"
"github.com/osscontainertools/kaniko/golden/types"
"github.com/osscontainertools/kaniko/pkg/config"
Expand Down Expand Up @@ -62,6 +63,7 @@ var allTests = map[string][]types.GoldenTests{
"test_issue_mz195": {testissuemz195.Tests},
"test_issue_mz333": {testissuemz333.Tests},
"test_issue_mz338": {testissuemz338.Tests},
"test_issue_mz523": {testissuemz523.Tests},
"test_unittests": testunittests.Tests,
}
var update bool
Expand Down
15 changes: 15 additions & 0 deletions golden/testdata/test_issue_mz523/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM debian:12.10 AS first-stage

# mz523: We can't squash second+first stages,
# but when we switch the context we don't need
# to clean the filesystem and unpack the image again.

FROM first-stage AS second-stage
RUN touch test

FROM second-stage AS third-stage
RUN touch test2

FROM first-stage AS fourth-stage
COPY --from=second-stage test test
COPY --from=third-stage test2 test2
18 changes: 18 additions & 0 deletions golden/testdata/test_issue_mz523/plans/nocleanup
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM debian:12.10 AS first-stage
UNPACK debian:12.10
SAVE STAGE /kaniko/stages/0

FROM first-stage AS second-stage
RUN touch test
SAVE STAGE /kaniko/stages/1
SAVE FILES [test] /kaniko/deps/1

FROM second-stage AS third-stage
RUN touch test2
SAVE FILES [test2] /kaniko/deps/2
CLEAN

FROM first-stage AS fourth-stage
UNPACK /kaniko/stages/0
COPY --from=second-stage test test
COPY --from=third-stage test2 test2
22 changes: 22 additions & 0 deletions golden/testdata/test_issue_mz523/plans/normal
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM debian:12.10 AS first-stage
UNPACK debian:12.10
SAVE STAGE /kaniko/stages/0
CLEAN

FROM first-stage AS second-stage
UNPACK /kaniko/stages/0
RUN touch test
SAVE STAGE /kaniko/stages/1
SAVE FILES [test] /kaniko/deps/1
CLEAN

FROM second-stage AS third-stage
UNPACK /kaniko/stages/1
RUN touch test2
SAVE FILES [test2] /kaniko/deps/2
CLEAN

FROM first-stage AS fourth-stage
UNPACK /kaniko/stages/0
COPY --from=second-stage test test
COPY --from=third-stage test2 test2
21 changes: 21 additions & 0 deletions golden/testdata/test_issue_mz523/test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package testissuemz523

import "github.com/osscontainertools/kaniko/golden/types"

var Tests = types.GoldenTests{
Name: "test_issue_mz523",
Dockerfile: "Dockerfile",
Tests: []types.GoldenTest{
{
Args: []string{"--no-push"},
Plan: "normal",
},
{
Args: []string{"--no-push"},
Env: map[string]string{
"FF_KANIKO_SKIP_INTERSTAGE_CLEANUP": "1",
},
Plan: "nocleanup",
},
},
}
1 change: 1 addition & 0 deletions integration/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ var KanikoEnv = []string{
"FF_KANIKO_OCI_WARMER=1",
"FF_KANIKO_RUN_VIA_TINI=1",
"FF_KANIKO_COPY_CHMOD_ON_IMPLICIT_DIRS=1",
"FF_KANIKO_SKIP_INTERSTAGE_CLEANUP=1",
}

var WarmerEnv = []string{
Expand Down
2 changes: 2 additions & 0 deletions pkg/config/stage.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ type KanikoStage struct {
SaveStage bool
MetaArgs []instructions.ArgCommand
Index int
Unpack bool
Clean bool
}
12 changes: 12 additions & 0 deletions pkg/dockerfile/dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,8 @@ func MakeKanikoStages(opts *config.KanikoOptions, stages []instructions.Stage, m
Final: i == targetStage,
MetaArgs: metaArgs,
Index: i,
Unpack: true,
Clean: true,
}
}
if opts.SkipUnusedStages && config.EnvBoolDefault("FF_KANIKO_SQUASH_STAGES", true) {
Expand Down Expand Up @@ -383,6 +385,14 @@ func MakeKanikoStages(opts *config.KanikoOptions, stages []instructions.Stage, m
}
kanikoStages = onlyUsedStages
}
if config.EnvBool("FF_KANIKO_SKIP_INTERSTAGE_CLEANUP") {
for i := range kanikoStages {
if i > 0 && kanikoStages[i].BaseImageIndex == kanikoStages[i-1].Index {
kanikoStages[i-1].Clean = false
kanikoStages[i].Unpack = false
}
}
}
return kanikoStages, nil
}

Expand Down Expand Up @@ -459,5 +469,7 @@ func squash(a, b config.KanikoStage) config.KanikoStage {
SaveStage: b.SaveStage,
MetaArgs: append(a.MetaArgs, b.MetaArgs...),
Index: b.Index,
Unpack: a.Unpack,
Clean: b.Clean,
}
}
48 changes: 30 additions & 18 deletions pkg/executor/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ type snapShotter interface {
type stageBuilder struct {
index int
final bool
unpack bool
clean bool
image v1.Image
cf *v1.ConfigFile
baseImageDigest string
Expand Down Expand Up @@ -142,6 +144,8 @@ func newStageBuilder(args *dockerfile.BuildArgs, opts *config.KanikoOptions, sta
s := &stageBuilder{
index: stage.Index,
final: stage.Final,
unpack: stage.Unpack,
clean: stage.Clean,
image: sourceImage,
cf: imageConfig,
snapshotter: snapshotter,
Expand Down Expand Up @@ -339,7 +343,7 @@ func (s *stageBuilder) build(digestToCacheKey map[string]string) error {
if s.final && s.opts.Materialize {
shouldUnpack = true
}
if s.index == 0 && s.opts.InitialFSUnpacked {
if s.index == 0 && s.opts.InitialFSUnpacked || !s.unpack {
shouldUnpack = false
}

Expand Down Expand Up @@ -751,10 +755,12 @@ func RenderStages(stages []config.KanikoStage, opts *config.KanikoOptions, fileC
} else {
printf("FROM %s\n", s.BaseName)
}
if s.BaseImageStoredLocally {
printf("UNPACK %s%d\n", config.KanikoIntermediateStagesDir, s.BaseImageIndex)
} else {
printf("UNPACK %s\n", s.BaseName)
if s.Unpack {
if s.BaseImageStoredLocally {
printf("UNPACK %s%d\n", config.KanikoIntermediateStagesDir, s.BaseImageIndex)
} else {
printf("UNPACK %s\n", s.BaseName)
}
}
for _, c := range s.Commands {
command, err := commands.GetCommand(c, fileContext, opts.Secrets, opts.RunV2, opts.CacheCopyLayers, opts.CacheRunLayers)
Expand Down Expand Up @@ -782,9 +788,13 @@ func RenderStages(stages []config.KanikoStage, opts *config.KanikoOptions, fileC
if len(filesToSave) > 0 {
printf("SAVE FILES %v %s%d\n", filesToSave, config.KanikoInterStageDepsDir, s.Index)
}
printf("CLEAN\n\n")
if opts.PreserveContext && !opts.PreCleanup {
printf("RESTORE CONTEXT\n\n")
if s.Clean {
printf("CLEAN\n\n")
if opts.PreserveContext && !opts.PreCleanup {
printf("RESTORE CONTEXT\n\n")
}
} else {
printf("\n")
}
}
logrus.Panic("unreachable - we should always have a final stage")
Expand Down Expand Up @@ -978,18 +988,20 @@ func DoBuild(opts *config.KanikoOptions) (image v1.Image, retErr error) {
}

// Delete the filesystem
if err := util.DeleteFilesystem(); err != nil {
return nil, fmt.Errorf("deleting file system after stage %d: %w", stage.Index, err)
}
if opts.PreserveContext && !opts.PreCleanup {
if tarball == "" {
return nil, fmt.Errorf("context snapshot is missing")
if sb.clean {
if err := util.DeleteFilesystem(); err != nil {
return nil, fmt.Errorf("deleting file system after stage %d: %w", stage.Index, err)
}
_, err := util.UnpackLocalTarArchive(tarball, config.RootDir)
if err != nil {
return nil, fmt.Errorf("failed to unpack context snapshot: %w", err)
if opts.PreserveContext && !opts.PreCleanup {
if tarball == "" {
return nil, fmt.Errorf("context snapshot is missing")
}
_, err := util.UnpackLocalTarArchive(tarball, config.RootDir)
if err != nil {
return nil, fmt.Errorf("failed to unpack context snapshot: %w", err)
}
logrus.Info("Context restored")
}
logrus.Info("Context restored")
}
}

Expand Down
Loading