buildkit/cmd/buildkitd/main_containerd_worker.go

275 lines
8.4 KiB
Go

//go:build (linux && !no_containerd_worker) || (windows && !no_containerd_worker)
// +build linux,!no_containerd_worker windows,!no_containerd_worker
package main
import (
"context"
"os"
"strconv"
"strings"
"time"
ctd "github.com/containerd/containerd"
"github.com/moby/buildkit/cmd/buildkitd/config"
"github.com/moby/buildkit/util/network/cniprovider"
"github.com/moby/buildkit/util/network/netproviders"
"github.com/moby/buildkit/worker"
"github.com/moby/buildkit/worker/base"
"github.com/moby/buildkit/worker/containerd"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"golang.org/x/sync/semaphore"
)
const (
defaultContainerdAddress = "/run/containerd/containerd.sock"
defaultContainerdNamespace = "buildkit"
)
func init() {
defaultConf, _ := defaultConf()
enabledValue := func(b *bool) string {
if b == nil {
return "auto"
}
return strconv.FormatBool(*b)
}
if defaultConf.Workers.Containerd.Address == "" {
defaultConf.Workers.Containerd.Address = defaultContainerdAddress
}
if defaultConf.Workers.Containerd.Namespace == "" {
defaultConf.Workers.Containerd.Namespace = defaultContainerdNamespace
}
flags := []cli.Flag{
cli.StringFlag{
Name: "containerd-worker",
Usage: "enable containerd workers (true/false/auto)",
Value: enabledValue(defaultConf.Workers.Containerd.Enabled),
},
cli.StringFlag{
Name: "containerd-worker-addr",
Usage: "containerd socket",
Value: defaultConf.Workers.Containerd.Address,
},
cli.StringSliceFlag{
Name: "containerd-worker-labels",
Usage: "user-specific annotation labels (com.example.foo=bar)",
},
// TODO: containerd-worker-platform should be replaced by ability
// to set these from containerd configuration
cli.StringSliceFlag{
Name: "containerd-worker-platform",
Usage: "override supported platforms for worker",
Hidden: true,
},
cli.StringFlag{
Name: "containerd-worker-namespace",
Usage: "override containerd namespace",
Value: defaultConf.Workers.Containerd.Namespace,
Hidden: true,
},
cli.StringFlag{
Name: "containerd-worker-net",
Usage: "worker network type (auto, cni or host)",
Value: defaultConf.Workers.Containerd.NetworkConfig.Mode,
},
cli.StringFlag{
Name: "containerd-cni-config-path",
Usage: "path of cni config file",
Value: defaultConf.Workers.Containerd.NetworkConfig.CNIConfigPath,
},
cli.StringFlag{
Name: "containerd-cni-binary-dir",
Usage: "path of cni binary files",
Value: defaultConf.Workers.Containerd.NetworkConfig.CNIBinaryPath,
},
cli.StringFlag{
Name: "containerd-worker-snapshotter",
Usage: "snapshotter name to use",
Value: ctd.DefaultSnapshotter,
},
cli.StringFlag{
Name: "containerd-worker-apparmor-profile",
Usage: "set the name of the apparmor profile applied to containers",
},
}
if defaultConf.Workers.Containerd.GC == nil || *defaultConf.Workers.Containerd.GC {
flags = append(flags, cli.BoolTFlag{
Name: "containerd-worker-gc",
Usage: "Enable automatic garbage collection on worker",
})
} else {
flags = append(flags, cli.BoolFlag{
Name: "containerd-worker-gc",
Usage: "Enable automatic garbage collection on worker",
})
}
flags = append(flags, cli.Int64Flag{
Name: "containerd-worker-gc-keepstorage",
Usage: "Amount of storage GC keep locally (MB)",
Value: func() int64 {
if defaultConf.Workers.Containerd.GCKeepStorage != 0 {
return defaultConf.Workers.Containerd.GCKeepStorage / 1e6
}
return config.DetectDefaultGCCap(defaultConf.Root) / 1e6
}(),
Hidden: len(defaultConf.Workers.Containerd.GCPolicy) != 0,
})
registerWorkerInitializer(
workerInitializer{
fn: containerdWorkerInitializer,
// 1 is less preferred than 0 (runcCtor)
priority: 1,
},
flags...,
)
// TODO(AkihiroSuda): allow using multiple snapshotters. should be useful for some applications that does not work with the default overlay snapshotter. e.g. mysql (docker/for-linux#72)",
}
func applyContainerdFlags(c *cli.Context, cfg *config.Config) error {
if cfg.Workers.Containerd.Address == "" {
cfg.Workers.Containerd.Address = defaultContainerdAddress
}
if c.GlobalIsSet("containerd-worker") {
boolOrAuto, err := parseBoolOrAuto(c.GlobalString("containerd-worker"))
if err != nil {
return err
}
cfg.Workers.Containerd.Enabled = boolOrAuto
}
// GlobalBool works for BoolT as well
rootless := c.GlobalBool("rootless")
if rootless {
logrus.Warn("rootless mode is not supported for containerd workers. disabling containerd worker.")
b := false
cfg.Workers.Containerd.Enabled = &b
return nil
}
labels, err := attrMap(c.GlobalStringSlice("containerd-worker-labels"))
if err != nil {
return err
}
if cfg.Workers.Containerd.Labels == nil {
cfg.Workers.Containerd.Labels = make(map[string]string)
}
for k, v := range labels {
cfg.Workers.Containerd.Labels[k] = v
}
if c.GlobalIsSet("containerd-worker-addr") {
cfg.Workers.Containerd.Address = c.GlobalString("containerd-worker-addr")
}
if platforms := c.GlobalStringSlice("containerd-worker-platform"); len(platforms) != 0 {
cfg.Workers.Containerd.Platforms = platforms
}
if c.GlobalIsSet("containerd-worker-namespace") || cfg.Workers.Containerd.Namespace == "" {
cfg.Workers.Containerd.Namespace = c.GlobalString("containerd-worker-namespace")
}
if c.GlobalIsSet("containerd-worker-gc") {
v := c.GlobalBool("containerd-worker-gc")
cfg.Workers.Containerd.GC = &v
}
if c.GlobalIsSet("containerd-worker-gc-keepstorage") {
cfg.Workers.Containerd.GCKeepStorage = c.GlobalInt64("containerd-worker-gc-keepstorage") * 1e6
}
if c.GlobalIsSet("containerd-worker-net") {
cfg.Workers.Containerd.NetworkConfig.Mode = c.GlobalString("containerd-worker-net")
}
if c.GlobalIsSet("containerd-cni-config-path") {
cfg.Workers.Containerd.NetworkConfig.CNIConfigPath = c.GlobalString("containerd-cni-config-path")
}
if c.GlobalIsSet("containerd-cni-binary-dir") {
cfg.Workers.Containerd.NetworkConfig.CNIBinaryPath = c.GlobalString("containerd-cni-binary-dir")
}
if c.GlobalIsSet("containerd-worker-snapshotter") {
cfg.Workers.Containerd.Snapshotter = c.GlobalString("containerd-worker-snapshotter")
}
if c.GlobalIsSet("containerd-worker-apparmor-profile") {
cfg.Workers.Containerd.ApparmorProfile = c.GlobalString("containerd-worker-apparmor-profile")
}
return nil
}
func containerdWorkerInitializer(c *cli.Context, common workerInitializerOpt) ([]worker.Worker, error) {
if err := applyContainerdFlags(c, common.config); err != nil {
return nil, err
}
cfg := common.config.Workers.Containerd
if (cfg.Enabled == nil && !validContainerdSocket(cfg.Address)) || (cfg.Enabled != nil && !*cfg.Enabled) {
return nil, nil
}
dns := getDNSConfig(common.config.DNS)
nc := netproviders.Opt{
Mode: common.config.Workers.Containerd.NetworkConfig.Mode,
CNI: cniprovider.Opt{
Root: common.config.Root,
ConfigPath: common.config.Workers.Containerd.CNIConfigPath,
BinaryDir: common.config.Workers.Containerd.CNIBinaryPath,
},
}
var parallelismSem *semaphore.Weighted
if cfg.MaxParallelism > 0 {
parallelismSem = semaphore.NewWeighted(int64(cfg.MaxParallelism))
}
snapshotter := ctd.DefaultSnapshotter
if cfg.Snapshotter != "" {
snapshotter = cfg.Snapshotter
}
opt, err := containerd.NewWorkerOpt(common.config.Root, cfg.Address, snapshotter, cfg.Namespace, cfg.Labels, dns, nc, common.config.Workers.Containerd.ApparmorProfile, parallelismSem, common.traceSocket, ctd.WithTimeout(60*time.Second))
if err != nil {
return nil, err
}
opt.GCPolicy = getGCPolicy(cfg.GCConfig, common.config.Root)
opt.RegistryHosts = resolverFunc(common.config)
if platformsStr := cfg.Platforms; len(platformsStr) != 0 {
platforms, err := parsePlatforms(platformsStr)
if err != nil {
return nil, errors.Wrap(err, "invalid platforms")
}
opt.Platforms = platforms
}
w, err := base.NewWorker(context.TODO(), opt)
if err != nil {
return nil, err
}
return []worker.Worker{w}, nil
}
func validContainerdSocket(socket string) bool {
if strings.HasPrefix(socket, "tcp://") {
// FIXME(AkihiroSuda): prohibit tcp?
return true
}
socketPath := strings.TrimPrefix(socket, "unix://")
if _, err := os.Stat(socketPath); errors.Is(err, os.ErrNotExist) {
// FIXME(AkihiroSuda): add more conditions
logrus.Warnf("skipping containerd worker, as %q does not exist", socketPath)
return false
}
// TODO: actually dial and call introspection API
return true
}