talm

Форк
0
/
install.go 
300 строк · 8.4 Кб
1
// This Source Code Form is subject to the terms of the Mozilla Public
2
// License, v. 2.0. If a copy of the MPL was not distributed with this
3
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4

5
package install
6

7
import (
8
	"bytes"
9
	"context"
10
	"fmt"
11
	"io"
12
	"log"
13
	"os"
14
	"strconv"
15

16
	"github.com/containerd/containerd"
17
	"github.com/containerd/containerd/cio"
18
	"github.com/containerd/containerd/errdefs"
19
	"github.com/containerd/containerd/namespaces"
20
	"github.com/containerd/containerd/oci"
21
	"github.com/opencontainers/runtime-spec/specs-go"
22
	"github.com/siderolabs/go-kmsg"
23
	"github.com/siderolabs/go-procfs/procfs"
24
	"golang.org/x/sys/unix"
25

26
	"github.com/aenix-io/talm/internal/app/machined/pkg/runtime"
27
	containerdrunner "github.com/aenix-io/talm/internal/app/machined/pkg/system/runner/containerd"
28
	"github.com/aenix-io/talm/internal/pkg/capability"
29
	"github.com/aenix-io/talm/internal/pkg/containers/image"
30
	"github.com/aenix-io/talm/internal/pkg/environment"
31
	"github.com/aenix-io/talm/internal/pkg/extensions"
32
	machineapi "github.com/siderolabs/talos/pkg/machinery/api/machine"
33
	configcore "github.com/siderolabs/talos/pkg/machinery/config"
34
	"github.com/siderolabs/talos/pkg/machinery/config/config"
35
	"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
36
	"github.com/siderolabs/talos/pkg/machinery/constants"
37
)
38

39
// RunInstallerContainer performs an installation via the installer container.
40
//
41
//nolint:gocyclo,cyclop
42
func RunInstallerContainer(disk, platform, ref string, cfg configcore.Config, cfgContainer configcore.Container, opts ...Option) error {
43
	const containerID = "upgrade"
44

45
	options := DefaultInstallOptions()
46

47
	for _, opt := range opts {
48
		if err := opt(&options); err != nil {
49
			return err
50
		}
51
	}
52

53
	var (
54
		registriesConfig config.Registries
55
		extensionsConfig []config.Extension
56
	)
57

58
	if cfg != nil && cfg.Machine() != nil {
59
		registriesConfig = cfg.Machine().Registries()
60
		extensionsConfig = cfg.Machine().Install().Extensions()
61
	} else {
62
		registriesConfig = &v1alpha1.RegistriesConfig{}
63
	}
64

65
	ctx, cancel := context.WithCancel(context.Background())
66
	defer cancel()
67

68
	ctx = namespaces.WithNamespace(ctx, constants.SystemContainerdNamespace)
69

70
	client, err := containerd.New(constants.SystemContainerdAddress)
71
	if err != nil {
72
		return err
73
	}
74

75
	defer client.Close() //nolint:errcheck
76

77
	var done func(context.Context) error
78

79
	ctx, done, err = client.WithLease(ctx)
80
	defer done(ctx) //nolint:errcheck
81

82
	var img containerd.Image
83

84
	if !options.Pull {
85
		img, err = client.GetImage(ctx, ref)
86
	}
87

88
	if img == nil || err != nil && errdefs.IsNotFound(err) {
89
		log.Printf("pulling %q", ref)
90

91
		img, err = image.Pull(ctx, registriesConfig, client, ref)
92
	}
93

94
	if err != nil {
95
		return err
96
	}
97

98
	puller, err := extensions.NewPuller(client)
99
	if err != nil {
100
		return err
101
	}
102

103
	if extensionsConfig != nil {
104
		if err = puller.PullAndMount(ctx, registriesConfig, extensionsConfig); err != nil {
105
			return err
106
		}
107
	}
108

109
	defer func() {
110
		if err = puller.Cleanup(ctx); err != nil {
111
			log.Printf("error cleaning up pulled system extensions: %s", err)
112
		}
113
	}()
114

115
	// See if there's previous container/snapshot to clean up
116
	var oldcontainer containerd.Container
117

118
	if oldcontainer, err = client.LoadContainer(ctx, containerID); err == nil {
119
		if err = oldcontainer.Delete(ctx, containerd.WithSnapshotCleanup); err != nil {
120
			return fmt.Errorf("error deleting old container instance: %w", err)
121
		}
122
	}
123

124
	if err = client.SnapshotService("").Remove(ctx, containerID); err != nil && !errdefs.IsNotFound(err) {
125
		return fmt.Errorf("error cleaning up stale snapshot: %w", err)
126
	}
127

128
	mounts := []specs.Mount{
129
		{Type: "bind", Destination: "/dev", Source: "/dev", Options: []string{"rbind", "rshared", "rw"}},
130
		{Type: "bind", Destination: constants.SystemExtensionsPath, Source: constants.SystemExtensionsPath, Options: []string{"rbind", "rshared", "ro"}},
131
	}
132

133
	// mount the machined socket into the container for upgrade pre-checks if the socket exists
134
	if _, err = os.Stat(constants.MachineSocketPath); err == nil {
135
		mounts = append(mounts,
136
			specs.Mount{Type: "bind", Destination: constants.MachineSocketPath, Source: constants.MachineSocketPath, Options: []string{"rbind", "rshared", "ro"}},
137
		)
138
	}
139

140
	// mount the efivars into the container if the efivars directory exists
141
	if _, err = os.Stat(constants.EFIVarsMountPoint); err == nil {
142
		mounts = append(mounts,
143
			specs.Mount{Type: "efivarfs", Source: "efivarfs", Destination: constants.EFIVarsMountPoint, Options: []string{"rw", "nosuid", "nodev", "noexec", "relatime"}},
144
		)
145
	}
146

147
	// mount the /.extra directory into the container if the directory exists
148
	if _, err = os.Stat(constants.SDStubDynamicInitrdPath); err == nil {
149
		mounts = append(mounts,
150
			specs.Mount{Type: "bind", Destination: constants.SDStubDynamicInitrdPath, Source: constants.SDStubDynamicInitrdPath, Options: []string{"rbind", "rshared", "ro"}},
151
		)
152
	}
153

154
	// TODO(andrewrynhard): To handle cases when the newer version changes the
155
	// platform name, this should be determined in the installer container.
156
	config := constants.ConfigNone
157
	if c := procfs.ProcCmdline().Get(constants.KernelParamConfig).First(); c != nil {
158
		config = *c
159
	}
160

161
	upgrade := strconv.FormatBool(options.Upgrade)
162
	force := strconv.FormatBool(options.Force)
163
	zero := strconv.FormatBool(options.Zero)
164

165
	args := []string{
166
		"/bin/installer",
167
		"install",
168
		"--disk=" + disk,
169
		"--platform=" + platform,
170
		"--config=" + config,
171
		"--upgrade=" + upgrade,
172
		"--force=" + force,
173
		"--zero=" + zero,
174
	}
175

176
	for _, arg := range options.ExtraKernelArgs {
177
		args = append(args, "--extra-kernel-arg", arg)
178
	}
179

180
	for _, preservedArg := range []string{
181
		constants.KernelParamSideroLink,
182
		constants.KernelParamEventsSink,
183
		constants.KernelParamLoggingKernel,
184
		constants.KernelParamEquinixMetalEvents,
185
		constants.KernelParamDashboardDisabled,
186
		constants.KernelParamNetIfnames,
187
	} {
188
		if c := procfs.ProcCmdline().Get(preservedArg).First(); c != nil {
189
			args = append(args, "--extra-kernel-arg", fmt.Sprintf("%s=%s", preservedArg, *c))
190
		}
191
	}
192

193
	specOpts := []oci.SpecOpts{
194
		oci.WithImageConfig(img),
195
		oci.WithProcessArgs(args...),
196
		oci.WithHostNamespace(specs.NetworkNamespace),
197
		oci.WithHostNamespace(specs.PIDNamespace),
198
		oci.WithMounts(mounts),
199
		oci.WithHostHostsFile,
200
		oci.WithHostResolvconf,
201
		oci.WithParentCgroupDevices,
202
		oci.WithCapabilities(capability.AllGrantableCapabilities()),
203
		oci.WithMaskedPaths(nil),
204
		oci.WithReadonlyPaths(nil),
205
		oci.WithWriteableSysfs,
206
		oci.WithWriteableCgroupfs,
207
		oci.WithSelinuxLabel(""),
208
		oci.WithApparmorProfile(""),
209
		oci.WithSeccompUnconfined,
210
		oci.WithAllDevicesAllowed,
211
		oci.WithEnv(environment.Get(cfg)),
212
	}
213

214
	containerOpts := []containerd.NewContainerOpts{
215
		containerd.WithImage(img),
216
		containerd.WithNewSnapshot(containerID, img),
217
		containerd.WithNewSpec(specOpts...),
218
	}
219

220
	container, err := client.NewContainer(ctx, containerID, containerOpts...)
221
	if err != nil {
222
		return err
223
	}
224

225
	defer container.Delete(ctx, containerd.WithSnapshotCleanup) //nolint:errcheck
226

227
	f, err := os.OpenFile("/dev/kmsg", os.O_RDWR|unix.O_CLOEXEC|unix.O_NONBLOCK|unix.O_NOCTTY, 0o666)
228
	if err != nil {
229
		return fmt.Errorf("failed to open /dev/kmsg: %w", err)
230
	}
231
	//nolint:errcheck
232
	defer f.Close()
233

234
	w := &kmsg.Writer{KmsgWriter: f}
235

236
	var r interface {
237
		io.Reader
238
		WaitAndClose(context.Context, containerd.Task)
239
	}
240

241
	if cfgContainer != nil {
242
		var configBytes []byte
243

244
		configBytes, err = cfgContainer.Bytes()
245
		if err != nil {
246
			return err
247
		}
248

249
		r = &containerdrunner.StdinCloser{
250
			Stdin:  bytes.NewReader(configBytes),
251
			Closer: make(chan struct{}),
252
		}
253
	}
254

255
	creator := cio.NewCreator(cio.WithStreams(r, w, w))
256

257
	t, err := container.NewTask(ctx, creator)
258
	if err != nil {
259
		return err
260
	}
261

262
	if r != nil {
263
		go r.WaitAndClose(ctx, t)
264
	}
265

266
	defer t.Delete(ctx) //nolint:errcheck
267

268
	if err = t.Start(ctx); err != nil {
269
		return fmt.Errorf("failed to start %q task: %w", "upgrade", err)
270
	}
271

272
	statusC, err := t.Wait(ctx)
273
	if err != nil {
274
		return fmt.Errorf("failed waiting for %q task: %w", "upgrade", err)
275
	}
276

277
	status := <-statusC
278

279
	code := status.ExitCode()
280
	if code != 0 {
281
		return fmt.Errorf("task %q failed: exit code %d", "upgrade", code)
282
	}
283

284
	return nil
285
}
286

287
// OptionsFromUpgradeRequest builds installer options from upgrade request.
288
func OptionsFromUpgradeRequest(r runtime.Runtime, in *machineapi.UpgradeRequest) []Option {
289
	opts := []Option{
290
		WithPull(false),
291
		WithUpgrade(true),
292
		WithForce(!in.GetPreserve()),
293
	}
294

295
	if r.Config() != nil && r.Config().Machine() != nil {
296
		opts = append(opts, WithExtraKernelArgs(r.Config().Machine().Install().ExtraKernelArgs()))
297
	}
298

299
	return opts
300
}
301

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

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

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

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