istio

Форк
0
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

15
package main
16

17
import (
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"
27
	testenv "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).
50
func RunCrane(ctx context.Context, a Args) error {
51
	ctx, span := tracing.Start(ctx, "RunCrane")
52
	defer span.End()
53
	g := errgroup.Group{}
54

55
	variants := sets.New(a.Variants...)
56
	// hasDoubleDefault checks if we defined both DefaultVariant and PrimaryVariant. If we did, these
57
	// are the same exact docker build, just requesting different tags. As an optimization, and to ensure
58
	// byte-for-byte identical images, we will collapse these into a single build with multiple tags.
59
	hasDoubleDefault := 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 background
63
	builds := []builder.BuildSpec{}
64
	bases := sets.New[string]()
65
	for _, v := range a.Variants {
66
		for _, t := range a.Targets {
67
			b := builder.BuildSpec{
68
				Name:  t,
69
				Dests: extractTags(a, t, v, hasDoubleDefault),
70
			}
71
			for _, arch := range a.Architectures {
72
				p := a.PlanFor(arch).Find(t)
73
				if p == nil {
74
					continue
75
				}
76
				df := p.Dockerfile
77
				dargs := createArgs(a, t, v, arch)
78
				args, err := dockerfile.Parse(df, dockerfile.WithArgs(dargs), dockerfile.IgnoreRuns())
79
				if err != nil {
80
					return fmt.Errorf("parse: %v", err)
81
				}
82
				args.Arch = arch
83
				args.Name = t
84
				// args.Files provides a mapping from final destination -> docker context source
85
				// docker context is virtual, so we need to rewrite the "docker context source" to the real path of disk
86
				plan := a.PlanFor(arch).Find(args.Name)
87
				if plan == nil {
88
					continue
89
				}
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.sh
92
				for dest, src := range args.Files {
93
					translated, err := translate(plan.Dependencies(), src)
94
					if err != nil {
95
						return err
96
					}
97
					args.Files[dest] = translated
98
				}
99
				bases.Insert(args.Base)
100
				b.Args = append(b.Args, args)
101
			}
102
			builds = append(builds, b)
103
		}
104
	}
105

106
	// Warm up our base images while we are building everything. This isn't pulling them -- we actually
107
	// never pull them -- but it is pulling the config file from the remote registry.
108
	builder.WarmBase(ctx, a.Architectures, sets.SortedList(bases)...)
109

110
	// Build all dependencies
111
	makeStart := time.Now()
112
	for _, arch := range a.Architectures {
113
		if err := RunMake(ctx, a, arch, a.PlanFor(arch).Targets()...); err != nil {
114
			return err
115
		}
116
	}
117
	log.WithLabels("runtime", time.Since(makeStart)).Infof("make complete")
118

119
	// Finally, construct images and push them
120
	dockerStart := time.Now()
121
	for _, b := range builds {
122
		b := b
123
		g.Go(func() error {
124
			if err := builder.Build(ctx, b); err != nil {
125
				return fmt.Errorf("build %v: %v", b.Name, err)
126
			}
127
			return nil
128
		})
129
	}
130
	if err := g.Wait(); err != nil {
131
		return err
132
	}
133
	log.WithLabels("runtime", time.Since(dockerStart)).Infof("images complete")
134
	return nil
135
}
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
139
func translate(plan []string, src string) (string, error) {
140
	src = filepath.Clean(src)
141
	base := filepath.Base(src)
142

143
	// TODO: this currently doesn't handle multi-arch
144
	// Likely docker.yaml should explicitly declare multi-arch targets
145
	for _, p := range plan {
146
		pb := filepath.Base(p)
147
		if pb == base {
148
			return absPath(p), nil
149
		}
150
	}
151

152
	// Next check for folder. This should probably be arbitrary depth
153
	// Example: plan=[certs], src=certs/foo.bar
154
	dir := filepath.Dir(src)
155
	for _, p := range plan {
156
		pb := filepath.Base(p)
157
		if pb == dir {
158
			return absPath(filepath.Join(p, base)), nil
159
		}
160
	}
161

162
	return "", fmt.Errorf("failed to find real source for %v. plan: %+v", src, plan)
163
}
164

165
func absPath(p string) string {
166
	if path.IsAbs(p) {
167
		return p
168
	}
169
	return path.Join(testenv.IstioSrc, p)
170
}
171

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.