istio
170 строк · 6.0 Кб
1// Copyright Istio Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package main16
17import (18"context"19"fmt"20"path"21"path/filepath"22"time"23
24"golang.org/x/sync/errgroup"25
26"istio.io/istio/pkg/log"27testenv "istio.io/istio/pkg/test/env"28"istio.io/istio/pkg/tracing"29"istio.io/istio/pkg/util/sets"30"istio.io/istio/tools/docker-builder/builder"31"istio.io/istio/tools/docker-builder/dockerfile"32)
33
34// RunCrane builds docker images using go-containerregistry, rather than relying on Docker. This
35// works by parsing each Dockerfile and determining the resulting image config (labels, entrypoint,
36// env vars, etc) as well as all files that should be copied in. Notably, RUN is not supported. This
37// is not a problem for Istio, as all of our images do any building outside the docker context
38// anyway.
39//
40// Once we have determined the config, we use the go-containerregistry to apply this config on top of
41// the configured base image, and add a new layer for all the copies. This layer is constructed in a
42// highly optimized manner - rather than copying things around from original source, to docker
43// staging folder, to docker context, to a tar file, etc, we directly read the original source files
44// into memory and stream them into an in memory tar buffer.
45//
46// Building in this way ends up being roughly 10x faster than docker. Future work to enable
47// sha256-simd (https://github.com/google/go-containerregistry/issues/1330) makes this even faster -
48// pushing all images drops to sub-second times, with the registry being the bottleneck (which could
49// also use sha256-simd possibly).
50func RunCrane(ctx context.Context, a Args) error {51ctx, span := tracing.Start(ctx, "RunCrane")52defer span.End()53g := errgroup.Group{}54
55variants := sets.New(a.Variants...)56// hasDoubleDefault checks if we defined both DefaultVariant and PrimaryVariant. If we did, these57// are the same exact docker build, just requesting different tags. As an optimization, and to ensure58// byte-for-byte identical images, we will collapse these into a single build with multiple tags.59hasDoubleDefault := variants.Contains(DefaultVariant) && variants.Contains(PrimaryVariant)60
61// First, construct our build plan. Doing this first allows us to figure out which base images we will need,62// so we can pull them in the background63builds := []builder.BuildSpec{}64bases := sets.New[string]()65for _, v := range a.Variants {66for _, t := range a.Targets {67b := builder.BuildSpec{68Name: t,69Dests: extractTags(a, t, v, hasDoubleDefault),70}71for _, arch := range a.Architectures {72p := a.PlanFor(arch).Find(t)73if p == nil {74continue75}76df := p.Dockerfile77dargs := createArgs(a, t, v, arch)78args, err := dockerfile.Parse(df, dockerfile.WithArgs(dargs), dockerfile.IgnoreRuns())79if err != nil {80return fmt.Errorf("parse: %v", err)81}82args.Arch = arch83args.Name = t84// args.Files provides a mapping from final destination -> docker context source85// docker context is virtual, so we need to rewrite the "docker context source" to the real path of disk86plan := a.PlanFor(arch).Find(args.Name)87if plan == nil {88continue89}90// Plan is a list of real file paths, but we don't have a strong mapping from "docker context source"91// to "real path on disk". We do have a weak mapping though, by reproducing docker-copy.sh92for dest, src := range args.Files {93translated, err := translate(plan.Dependencies(), src)94if err != nil {95return err96}97args.Files[dest] = translated98}99bases.Insert(args.Base)100b.Args = append(b.Args, args)101}102builds = append(builds, b)103}104}105
106// Warm up our base images while we are building everything. This isn't pulling them -- we actually107// never pull them -- but it is pulling the config file from the remote registry.108builder.WarmBase(ctx, a.Architectures, sets.SortedList(bases)...)109
110// Build all dependencies111makeStart := time.Now()112for _, arch := range a.Architectures {113if err := RunMake(ctx, a, arch, a.PlanFor(arch).Targets()...); err != nil {114return err115}116}117log.WithLabels("runtime", time.Since(makeStart)).Infof("make complete")118
119// Finally, construct images and push them120dockerStart := time.Now()121for _, b := range builds {122b := b123g.Go(func() error {124if err := builder.Build(ctx, b); err != nil {125return fmt.Errorf("build %v: %v", b.Name, err)126}127return nil128})129}130if err := g.Wait(); err != nil {131return err132}133log.WithLabels("runtime", time.Since(dockerStart)).Infof("images complete")134return nil135}
136
137// translate takes a "docker context path" and a list of real paths and finds the real path for the associated
138// docker context path. Example: translate([out/linux_amd64/binary, foo/other], amd64/binary) -> out/linux_amd64/binary
139func translate(plan []string, src string) (string, error) {140src = filepath.Clean(src)141base := filepath.Base(src)142
143// TODO: this currently doesn't handle multi-arch144// Likely docker.yaml should explicitly declare multi-arch targets145for _, p := range plan {146pb := filepath.Base(p)147if pb == base {148return absPath(p), nil149}150}151
152// Next check for folder. This should probably be arbitrary depth153// Example: plan=[certs], src=certs/foo.bar154dir := filepath.Dir(src)155for _, p := range plan {156pb := filepath.Base(p)157if pb == dir {158return absPath(filepath.Join(p, base)), nil159}160}161
162return "", fmt.Errorf("failed to find real source for %v. plan: %+v", src, plan)163}
164
165func absPath(p string) string {166if path.IsAbs(p) {167return p168}169return path.Join(testenv.IstioSrc, p)170}
171