blob: bd3987568b4bda5888d53cac03695efb4f97af86 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Lorenz Brunc88c82d2020-05-08 14:35:04 +02002// SPDX-License-Identifier: Apache-2.0
Lorenz Brunc88c82d2020-05-08 14:35:04 +02003
4package containerd
5
6import (
7 "context"
Lorenz Brun878f5f92020-05-12 16:15:39 +02008 "fmt"
Lorenz Brun6acfc322020-05-13 17:01:26 +02009 "io"
Lorenz Brunc88c82d2020-05-08 14:35:04 +020010 "os"
11 "os/exec"
Lorenz Brun8b0431a2020-07-13 16:56:36 +020012 "path/filepath"
Serge Bazanskic7359672020-10-30 16:38:57 +010013 "strings"
Lorenz Brun6acfc322020-05-13 17:01:26 +020014 "time"
15
Lorenz Brun0ec0c532024-08-29 12:39:47 +000016 ctr "github.com/containerd/containerd/v2/client"
17 "github.com/containerd/containerd/v2/pkg/namespaces"
Lorenz Brun8b0431a2020-07-13 16:56:36 +020018
Serge Bazanski31370b02021-01-07 16:31:14 +010019 "source.monogon.dev/metropolis/node/core/localstorage"
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020020 "source.monogon.dev/osbase/supervisor"
Lorenz Brunc88c82d2020-05-08 14:35:04 +020021)
22
Lorenz Brun8b0431a2020-07-13 16:56:36 +020023const (
24 preseedNamespacesDir = "/containerd/preseed/"
25)
26
Lorenz Brun878f5f92020-05-12 16:15:39 +020027type Service struct {
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020028 EphemeralVolume *localstorage.EphemeralContainerdDirectory
Lorenz Brun878f5f92020-05-12 16:15:39 +020029}
Lorenz Brunc88c82d2020-05-08 14:35:04 +020030
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020031func (s *Service) Run(ctx context.Context) error {
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020032 cmd := exec.CommandContext(ctx, "/containerd/bin/containerd", "--config", "/containerd/conf/config.toml")
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020033 cmd.Env = []string{"PATH=/containerd/bin", "TMPDIR=" + s.EphemeralVolume.Tmp.FullPath()}
Lorenz Brun878f5f92020-05-12 16:15:39 +020034
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020035 runscFifo, err := os.OpenFile(s.EphemeralVolume.RunSCLogsFIFO.FullPath(), os.O_CREATE|os.O_RDONLY, os.ModeNamedPipe|0777)
36 if err != nil {
Lorenz Brun878f5f92020-05-12 16:15:39 +020037 return err
Lorenz Brunc88c82d2020-05-08 14:35:04 +020038 }
Serge Bazanski967be212020-11-02 11:26:59 +010039
40 if err := supervisor.Run(ctx, "runsc", s.logPump(runscFifo)); err != nil {
41 return fmt.Errorf("failed to start runsc log pump: %w", err)
42 }
43
44 if err := supervisor.Run(ctx, "preseed", s.runPreseed); err != nil {
45 return fmt.Errorf("failed to start preseed runnable: %w", err)
46 }
47 return supervisor.RunCommand(ctx, cmd)
48}
49
50// logPump returns a runnable that pipes data from a file/FIFO into its raw logger.
51// TODO(q3k): refactor this out to a generic function in supervisor or logtree.
52func (s *Service) logPump(fifo *os.File) supervisor.Runnable {
53 return func(ctx context.Context) error {
54 supervisor.Signal(ctx, supervisor.SignalHealthy)
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020055 for {
Serge Bazanski967be212020-11-02 11:26:59 +010056 // Quit if requested.
57 select {
58 case <-ctx.Done():
59 return ctx.Err()
60 default:
61 }
62
63 n, err := io.Copy(supervisor.RawLogger(ctx), fifo)
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020064 if n == 0 && err == nil {
Serge Bazanski216fe7b2021-05-21 18:36:16 +020065 // Hack because pipes/FIFOs can return zero reads when nobody
66 // is writing. To avoid busy-looping, sleep a bit before
67 // retrying. This does not loose data since the FIFO internal
68 // buffer will stall writes when it becomes full. 10ms maximum
69 // stall in a non-latency critical process (reading debug logs)
70 // is not an issue for us.
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020071 time.Sleep(10 * time.Millisecond)
72 } else if err != nil {
Tim Windelschmidt5f1a7de2024-09-19 02:00:14 +020073 return fmt.Errorf("log pump failed: %w", err)
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020074 }
75 }
Lorenz Brun8b0431a2020-07-13 16:56:36 +020076 }
Lorenz Brunc88c82d2020-05-08 14:35:04 +020077}
Lorenz Brun8b0431a2020-07-13 16:56:36 +020078
Serge Bazanski216fe7b2021-05-21 18:36:16 +020079// runPreseed loads OCI bundles in tar form from preseedNamespacesDir into
80// containerd at startup.
81// This can be run multiple times, containerd will automatically dedup the
82// layers. containerd uses namespaces to keep images (and everything else)
83// separate so to define where the images will be loaded to they need to be in
84// a folder named after the namespace they should be loaded into. containerd's
85// CRI plugin (which is built as part of containerd) uses a hardcoded namespace
86// ("k8s.io") for everything accessed through CRI, so if an image should be
87// available on K8s it needs to be in that namespace.
88// As an example if image helloworld should be loaded for use with Kubernetes,
89// the OCI bundle needs to be at <preseedNamespacesDir>/k8s.io/helloworld.tar.
90// No tagging beyond what's in the bundle is performed.
Lorenz Brun8b0431a2020-07-13 16:56:36 +020091func (s *Service) runPreseed(ctx context.Context) error {
92 client, err := ctr.New(s.EphemeralVolume.ClientSocket.FullPath())
93 if err != nil {
94 return fmt.Errorf("failed to connect to containerd: %w", err)
95 }
96 logger := supervisor.Logger(ctx)
Lorenz Brun764a2de2021-11-22 16:26:36 +010097 preseedNamespaceDirs, err := os.ReadDir(preseedNamespacesDir)
Lorenz Brun8b0431a2020-07-13 16:56:36 +020098 if err != nil {
99 return fmt.Errorf("failed to open preseed dir: %w", err)
100 }
101 for _, dir := range preseedNamespaceDirs {
102 if !dir.IsDir() {
Serge Bazanskic7359672020-10-30 16:38:57 +0100103 logger.Warningf("Non-Directory %q found in preseed folder, ignoring", dir.Name())
Lorenz Brun8b0431a2020-07-13 16:56:36 +0200104 continue
105 }
106 namespace := dir.Name()
Lorenz Brun764a2de2021-11-22 16:26:36 +0100107 images, err := os.ReadDir(filepath.Join(preseedNamespacesDir, namespace))
Lorenz Brun8b0431a2020-07-13 16:56:36 +0200108 if err != nil {
109 return fmt.Errorf("failed to list namespace preseed directory for ns \"%v\": %w", namespace, err)
110 }
111 ctxWithNS := namespaces.WithNamespace(ctx, namespace)
112 for _, image := range images {
113 if image.IsDir() {
Serge Bazanskic7359672020-10-30 16:38:57 +0100114 logger.Warningf("Directory %q found in preseed namespaced folder, ignoring", image.Name())
Lorenz Brun8b0431a2020-07-13 16:56:36 +0200115 continue
116 }
117 imageFile, err := os.Open(filepath.Join(preseedNamespacesDir, namespace, image.Name()))
118 if err != nil {
119 return fmt.Errorf("failed to open preseed image \"%v\": %w", image.Name(), err)
120 }
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200121 // defer in this loop is fine since we're never going to preseed
122 // more than ~1M images which is where our file descriptor limit
123 // is.
Lorenz Brun8b0431a2020-07-13 16:56:36 +0200124 defer imageFile.Close()
125 importedImages, err := client.Import(ctxWithNS, imageFile)
126 if err != nil {
127 return fmt.Errorf("failed to import preseed image: %w", err)
128 }
129 var importedImageNames []string
130 for _, img := range importedImages {
131 importedImageNames = append(importedImageNames, img.Name)
132 }
Tim Windelschmidt9c4bece2024-02-13 18:32:44 +0100133 logger.Infof("Successfully imported preseeded bundle %q into containerd namespace %s", strings.Join(importedImageNames, ","), namespace)
Lorenz Brun8b0431a2020-07-13 16:56:36 +0200134 }
135 }
136 supervisor.Signal(ctx, supervisor.SignalHealthy)
137 supervisor.Signal(ctx, supervisor.SignalDone)
138 return nil
139}