From 131828a1ad157222e8f33ad24d2efddc466555da Mon Sep 17 00:00:00 2001 From: Vincent Behar Date: Wed, 9 Dec 2020 01:16:25 +0100 Subject: [PATCH] feat: support multiple registry mirrors with fallback (#1498) Fixes #1473 The initial implementation of the registry mirror only allowed a single mirror, and if pulling from the mirror failed, the build would fail. This change introduces: - multiple registry mirrors instead of a single one - fallback if an image can't be pulled from a registry This is the same behavior as the docker daemon and will allow using a registry mirror such as `mirror.gcr.io` which is incomplete and doesn't have all the content that the default registry on docker.io has. Note that there are no changes in the CLI flags, the `--registry-mirror` flag is still valid. But now it can be used multiple times to set up more than one registry mirror. Co-authored-by: Tejal Desai --- README.md | 6 ++- cmd/executor/cmd/root.go | 2 +- integration/integration_test.go | 3 +- pkg/config/options.go | 2 +- pkg/image/image_util.go | 83 ++++++++++++++++++--------------- pkg/image/image_util_test.go | 2 +- 6 files changed, 55 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 225f5aaa7..09302889a 100644 --- a/README.md +++ b/README.md @@ -660,7 +660,10 @@ Expected format is `my.registry.url=/path/to/the/certificate.cert` #### --registry-mirror -Set this flag if you want to use a registry mirror instead of default `index.docker.io`. +Set this flag if you want to use a registry mirror instead of the default `index.docker.io`. You can use this flag more than once, if you want to set multiple mirrors. If an image is not found on the first mirror, Kaniko will try the next mirror(s), and at the end fallback on the default registry. + +Expected format is `mirror.gcr.io` for example. + Note that you can't specify a URL with scheme for this flag. Some valid options are: @@ -668,6 +671,7 @@ Note that you can't specify a URL with scheme for this flag. Some valid options * `127.0.0.1` * `192.168.0.1:5000` + #### --reproducible Set this flag to strip timestamps out of the built image and make it reproducible. diff --git a/cmd/executor/cmd/root.go b/cmd/executor/cmd/root.go index 68a0540b2..88d0f01ed 100644 --- a/cmd/executor/cmd/root.go +++ b/cmd/executor/cmd/root.go @@ -173,7 +173,7 @@ func addKanikoOptionsFlags() { RootCmd.PersistentFlags().VarP(&opts.SkipTLSVerifyRegistries, "skip-tls-verify-registry", "", "Insecure registry ignoring TLS verify to push and pull. Set it repeatedly for multiple registries.") opts.RegistriesCertificates = make(map[string]string) RootCmd.PersistentFlags().VarP(&opts.RegistriesCertificates, "registry-certificate", "", "Use the provided certificate for TLS communication with the given registry. Expected format is 'my.registry.url=/path/to/the/server/certificate'.") - RootCmd.PersistentFlags().StringVarP(&opts.RegistryMirror, "registry-mirror", "", "", "Registry mirror to use has pull-through cache instead of docker.io.") + RootCmd.PersistentFlags().VarP(&opts.RegistryMirrors, "registry-mirror", "", "Registry mirror to use as pull-through cache instead of docker.io. Set it repeatedly for multiple mirrors.") RootCmd.PersistentFlags().BoolVarP(&opts.IgnoreVarRun, "whitelist-var-run", "", true, "Ignore /var/run directory when taking image snapshot. Set it to false to preserve /var/run/ in destination image. (Default true).") RootCmd.PersistentFlags().VarP(&opts.Labels, "label", "", "Set metadata for an image. Set it repeatedly for multiple labels.") RootCmd.PersistentFlags().BoolVarP(&opts.SkipUnusedStages, "skip-unused-stages", "", false, "Build only used stages if defined to true. Otherwise it builds by default all stages, even the unnecessaries ones until it reaches the target stage / end of Dockerfile") diff --git a/integration/integration_test.go b/integration/integration_test.go index 6f0b9b8a6..1ccce6a4e 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -304,7 +304,7 @@ func TestGitBuildcontextSubPath(t *testing.T) { checkContainerDiffOutput(t, diff, expected) } -func TestBuildViaRegistryMirror(t *testing.T) { +func TestBuildViaRegistryMirrors(t *testing.T) { repo := getGitRepo() dockerfile := fmt.Sprintf("%s/%s/Dockerfile_registry_mirror", integrationPath, dockerfilesPath) @@ -327,6 +327,7 @@ func TestBuildViaRegistryMirror(t *testing.T) { dockerRunFlags = append(dockerRunFlags, ExecutorImage, "-f", dockerfile, "-d", kanikoImage, + "--registry-mirror", "doesnotexist.example.com", "--registry-mirror", "us-mirror.gcr.io", "-c", fmt.Sprintf("git://%s", repo)) diff --git a/pkg/config/options.go b/pkg/config/options.go index a9e799b51..7651c7522 100644 --- a/pkg/config/options.go +++ b/pkg/config/options.go @@ -44,7 +44,7 @@ type KanikoOptions struct { DigestFile string ImageNameDigestFile string OCILayoutPath string - RegistryMirror string + RegistryMirrors multiArg Destinations multiArg BuildArgs multiArg InsecureRegistries multiArg diff --git a/pkg/image/image_util.go b/pkg/image/image_util.go index 4faeba01c..6e8b25abd 100644 --- a/pkg/image/image_util.go +++ b/pkg/image/image_util.go @@ -106,51 +106,45 @@ func remoteImage(image string, opts *config.KanikoOptions) (v1.Image, error) { return nil, err } + if ref.Context().RegistryStr() == name.DefaultRegistry { + ref, err := normalizeReference(ref, image) + if err != nil { + return nil, err + } + + for _, registryMirror := range opts.RegistryMirrors { + var newReg name.Registry + if opts.InsecurePull || opts.InsecureRegistries.Contains(registryMirror) { + newReg, err = name.NewRegistry(registryMirror, name.WeakValidation, name.Insecure) + } else { + newReg, err = name.NewRegistry(registryMirror, name.StrictValidation) + } + if err != nil { + return nil, err + } + ref := setNewRegistry(ref, newReg) + + logrus.Infof("Retrieving image %s from registry mirror %s", ref, registryMirror) + remoteImage, err := remote.Image(ref, remoteOptions(registryMirror, opts)...) + if err != nil { + logrus.Warnf("Failed to retrieve image %s from registry mirror %s: %s. Will try with the next mirror, or fallback to the default registry.", ref, registryMirror, err) + continue + } + return remoteImage, nil + } + } + registryName := ref.Context().RegistryStr() - var newReg name.Registry - toSet := false - - if opts.RegistryMirror != "" && registryName == name.DefaultRegistry { - registryName = opts.RegistryMirror - - newReg, err = name.NewRegistry(opts.RegistryMirror, name.StrictValidation) - if err != nil { - return nil, err - } - - ref, err = normalizeReference(ref, image) - if err != nil { - return nil, err - } - - toSet = true - } - if opts.InsecurePull || opts.InsecureRegistries.Contains(registryName) { - newReg, err = name.NewRegistry(registryName, name.WeakValidation, name.Insecure) + newReg, err := name.NewRegistry(registryName, name.WeakValidation, name.Insecure) if err != nil { return nil, err } - - toSet = true + ref = setNewRegistry(ref, newReg) } - if toSet { - if tag, ok := ref.(name.Tag); ok { - tag.Repository.Registry = newReg - ref = tag - } - - if digest, ok := ref.(name.Digest); ok { - digest.Repository.Registry = newReg - ref = digest - } - } - - logrus.Infof("Retrieving image %s", ref) - - rOpts := remoteOptions(registryName, opts) - return remote.Image(ref, rOpts...) + logrus.Infof("Retrieving image %s from registry %s", ref, registryName) + return remote.Image(ref, remoteOptions(registryName, opts)...) } // normalizeReference adds the library/ prefix to images without it. @@ -165,6 +159,19 @@ func normalizeReference(ref name.Reference, image string) (name.Reference, error return ref, nil } +func setNewRegistry(ref name.Reference, newReg name.Registry) name.Reference { + switch r := ref.(type) { + case name.Tag: + r.Repository.Registry = newReg + return r + case name.Digest: + r.Repository.Registry = newReg + return r + default: + return ref + } +} + func remoteOptions(registryName string, opts *config.KanikoOptions) []remote.Option { tr := util.MakeTransport(opts, registryName) diff --git a/pkg/image/image_util_test.go b/pkg/image/image_util_test.go index 4a99d4ef1..2c1937e1b 100644 --- a/pkg/image/image_util_test.go +++ b/pkg/image/image_util_test.go @@ -104,7 +104,7 @@ func Test_ScratchImageFromMirror(t *testing.T) { actual, err := RetrieveSourceImage(config.KanikoStage{ Stage: stages[1], }, &config.KanikoOptions{ - RegistryMirror: "mirror.gcr.io", + RegistryMirrors: []string{"mirror.gcr.io"}, }) expected := empty.Image testutil.CheckErrorAndDeepEqual(t, false, err, expected, actual)