NVIDIA-Container-Toolkit—源码阅读—pkg

NVIDIA-Container-Toolkit—CodeReading—pkg

Posted by PYQ on September 21, 2023

config

enginee

docker

option.go

属于docker包。

定义的一些类型结构体:命名为builder应该是设计模式中的builder模式。

1
2
3
4
5
6
type builder struct {
	logger logger.Interface
	path   string
}
// Option defines a function that can be used to configure the config builder
type Option func(*builder)

WithLogger和WithPath函数用于为config builder设置logger和path:比较常用的函数式选项(Functional Options)编程模式

1
2
3
4
5
6
7
8
9
10
func WithLogger(logger logger.Interface) Option {
	return func(b *builder) {
		b.logger = logger
	}
}
func WithPath(path string) Option {
	return func(b *builder) {
		b.path = path
	}
}

build函数用于加载配置文件:

1
2
3
4
5
6
7
func (b *builder) build() (*Config, error) {
	if b.path == "" {
		empty := make(Config)
		return &empty, nil
	}
	return b.loadConfig(b.path)
}

loadConfig用于从磁盘上加载配置文件并返回给Config对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func (b *builder) loadConfig(config string) (*Config, error) {
	info, err := os.Stat(config)
	if os.IsExist(err) && info.IsDir() {
		return nil, fmt.Errorf("config file is a directory")
	}
	cfg := make(Config)
	if os.IsNotExist(err) {
		b.logger.Infof("Config file does not exist; using empty config")
		return &cfg, nil
	}
	b.logger.Infof("Loading config from %v", config)
	readBytes, err := ioutil.ReadFile(config)
	if err != nil {
		return nil, fmt.Errorf("unable to read config: %v", err)
	}
	reader := bytes.NewReader(readBytes)
	if err := json.NewDecoder(reader).Decode(&cfg); err != nil {
		return nil, err
	}
	return &cfg, nil
}
docker.go

docker中修改runtime:

1
2
3
4
5
6
7
8
9
{
  "runtimes": {
    "runtime-name": {
      "path": "",
      "runtimeArgs": ["",
	""]
    }
  }
}

属于docker包。

定义的一些常量和结构体:

1
2
3
4
5
6
const (
	defaultDockerRuntime = "runc"
)
// Config defines a docker config file.
// TODO: This should not be public, but we need to access it from the tests in tools/container/docker
type Config map[string]interface{}

New函数构造了一个docker config,常用的函数选项式编程。

但这里的返回类型我没太看明白

1
2
3
4
5
6
7
8
9
10
func New(opts ...Option) (engine.Interface, error) {
	b := &builder{}
	for _, opt := range opts {
		opt(b)
	}
	if b.logger == nil {
		b.logger = logger.New()
	}
	return b.build()
}

AddRuntime函数添加新的运行时到docker的config中:但是daemon.json中是runtimeArgs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func (c *Config) AddRuntime(name string, path string, setAsDefault bool) error {
	if c == nil {
		return fmt.Errorf("config is nil")
	}
	config := *c
	// Read the existing runtimes
	runtimes := make(map[string]interface{})
	if _, exists := config["runtimes"]; exists {
		runtimes = config["runtimes"].(map[string]interface{})
	}
	// Add / update the runtime definitions
	runtimes[name] = map[string]interface{}{
		"path": path,
		"args": []string{},
	}
	config["runtimes"] = runtimes
	if setAsDefault {
		config["default-runtime"] = name
	}
	*c = config
	return nil
}

DefaultRuntime函数返回docker配置文件中的默认运行时:

1
2
3
4
5
6
7
func (c Config) DefaultRuntime() string {
	r, ok := c["default-runtime"].(string)
	if !ok {
		return ""
	}
	return r
}

RemoveRuntime删除docker配置文件中的运行时:如果删除的运行时是当前的默认运行时,则设置配置中的默认运行时为runc,然后删除掉runtimes字段中的对应运行时,若此时只有这一个运行时,则runtimes字段也删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func (c *Config) RemoveRuntime(name string) error {
	if c == nil {
		return nil
	}
	config := *c
	if _, exists := config["default-runtime"]; exists {
		defaultRuntime := config["default-runtime"].(string)
		if defaultRuntime == name {
			config["default-runtime"] = defaultDockerRuntime
		}
	}
	if _, exists := config["runtimes"]; exists {
		runtimes := config["runtimes"].(map[string]interface{})
		delete(runtimes, name)
		if len(runtimes) == 0 {
			delete(config, "runtimes")
		}
	}
	*c = config
	return nil
}

Save函数将config写入指定路径:先转换为json再写入。

1
2
3
4
5
6
7
8
9
func (c Config) Save(path string) (int64, error) {
	output, err := json.MarshalIndent(c, "", "    ")
	if err != nil {
		return 0, fmt.Errorf("unable to convert to JSON: %v", err)
	}

	n, err := engine.Config(path).Write(output)
	return int64(n), err
}

containerd

option.go

属于containerd包。

containerd更换运行时,containerd的配置文件是toml文件:

1
2
3
4
5
6
7
8
9
10
11
12
[plugins."io.containerd.grpc.v1.cri"]
[plugins."io.containerd.grpc.v1.cri".containerd]
  default_runtime_name = "nvidia"

  [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia]
      privileged_without_host_devices = false
      runtime_engine = ""
      runtime_root = ""
      runtime_type = "io.containerd.runc.v2"
      [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia.options]
        BinaryName = "/usr/bin/nvidia-container-runtime"

定义的一些常量和结构体:

1
2
3
4
5
6
7
8
9
10
11
const (
	defaultRuntimeType = "io.containerd.runc.v2"
)
type builder struct {
	logger               logger.Interface
	path                 string
	runtimeType          string
	useLegacyConfig      bool
	containerAnnotations []string
}
type Option func(*builder)

with系列函数不再赘述。

build函数用于构造config:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func (b *builder) build() (engine.Interface, error) {
	if b.path == "" {
		return nil, fmt.Errorf("config path is empty")
	}
	if b.runtimeType == "" {
		b.runtimeType = defaultRuntimeType
	}
	config, err := b.loadConfig(b.path)
	if err != nil {
		return nil, fmt.Errorf("failed to load config: %v", err)
	}
	config.RuntimeType = b.runtimeType
	config.UseDefaultRuntimeName = !b.useLegacyConfig
	config.ContainerAnnotations = b.containerAnnotations
	version, err := config.parseVersion(b.useLegacyConfig)
	if err != nil {
		return nil, fmt.Errorf("failed to parse config version: %v", err)
	}
	switch version {
	case 1:
		return (*ConfigV1)(config), nil
	case 2:
		return config, nil
	}
	return nil, fmt.Errorf("unsupported config version: %v", version)
}

loadConfig从磁盘上加载containerd config:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func (b *builder) loadConfig(config string) (*Config, error) {
	info, err := os.Stat(config)
	if os.IsExist(err) && info.IsDir() {
		return nil, fmt.Errorf("config file is a directory")
	}
	if os.IsNotExist(err) {
		b.logger.Infof("Config file does not exist; using empty config")
		config = "/dev/null"
	} else {
		b.logger.Infof("Loading config from %v", config)
	}
	tomlConfig, err := toml.LoadFile(config)
	if err != nil {
		return nil, err
	}
	cfg := Config{
		Tree: tomlConfig,
	}
	return &cfg, nil
}

parseVersion函数用于解析config的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (c *Config) parseVersion(useLegacyConfig bool) (int, error) {
	defaultVersion := 2
	if useLegacyConfig {
		defaultVersion = 1
	}
	switch v := c.Get("version").(type) {
	case nil:
		switch len(c.Keys()) {
		case 0: // No config exists, or the config file is empty, use version inferred from containerd
			return defaultVersion, nil
		default: // A config file exists, has content, and no version is set
			return 1, nil
		}
	case int64:
		return int(v), nil
	default:
		return -1, fmt.Errorf("unsupported type for version field: %v", v)
	}
}
containerd.go

属于containerd包。

包括一个containerd的配置文件的结构体以及一个构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Config struct {
	*toml.Tree
	RuntimeType           string
	UseDefaultRuntimeName bool
	ContainerAnnotations  []string
}
func New(opts ...Option) (engine.Interface, error) {
	b := &builder{}
	for _, opt := range opts {
		opt(b)
	}
	if b.logger == nil {
		b.logger = logger.New()
	}
	return b.build()
}
config_v2.go

属于containerd包。

AddRuntime函数将运行时添加到containerd的配置文件中,也就是在开头的那些配置内容。

getRuntimeAnnotations函数获得配置文件中容器注解。

DefaultRuntime函数用于返回配置文件中的默认运行时,RemoveRuntime函数用于移除配置文件中的运行时,这里两个注释也写错了

Save函数将配置写入路径。

config_v1.go

和v2有细微区别。

crio

option.go

属于crio包。

builder、option以及with系列函数和docker部分一样,不再赘述。

build和loadConfig函数也类似,但是换成了toml。

crio.go

属于crio包。

config.go

属于engine包。

Write函数用于将内容写入配置文件中:如果路径为空,将内容写入标准输出。如果内容为空,尝试删除文件。在写入文件之前,会检查路径所在的目录是否存在,如果不存在,则尝试创建目录。函数返回写入的字节数和可能出现的错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Config represents a runtime config
type Config string
// Write writes the specified contents to a config file.
func (c Config) Write(output []byte) (int, error) {
	path := string(c)
	if path == "" {
		n, err := os.Stdout.Write(output)
		if err == nil {
			os.Stdout.WriteString("\n")
		}
		return n, err
	}
	if len(output) == 0 {
		err := os.Remove(path)
		if err != nil {
			return 0, fmt.Errorf("unable to remove empty file: %v", err)
		}
		return 0, nil
	}
	if dir := filepath.Dir(path); dir != "" {
		err := os.MkdirAll(dir, 0755)
		if err != nil {
			return 0, fmt.Errorf("unable to create directory %v: %v", dir, err)
		}
	}
	f, err := os.Create(path)
	if err != nil {
		return 0, fmt.Errorf("unable to open %v for writing: %v", path, err)
	}
	defer f.Close()
	return f.Write(output)
}

api.go

属于engine包,定义了一些接口。

1
2
3
4
5
6
7
// Interface defines the API for a runtime config updater.
type Interface interface {
	DefaultRuntime() string
	AddRuntime(string, string, bool) error
	RemoveRuntime(string) error
	Save(string) (int64, error)
}

ocihook

oci-hook.go

属于ocihook包。

CreateHook函数根据传入的路径创建一个钩子文件。它接受两个参数:hookFilePath 表示钩子文件的路径,nvidiaContainerRuntimeHookExecutablePath 表示 NVIDIA 容器运行时钩子可执行文件的路径。

generateOciHook是创建一个podman类型的钩子,先检查给定的路径是否在PATH里面,不再就添加,然后生成一个podman类型的hook,并返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func generateOciHook(executablePath string) podmanHook {
	pathParts := []string{"/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin"}
	dir := filepath.Dir(executablePath)
	var found bool
	for _, pathPart := range pathParts {
		if pathPart == dir {
			found = true
			break
		}
	}
	if !found {
		pathParts = append(pathParts, dir)
	}
	envPath := "PATH=" + strings.Join(pathParts, ":")
	always := true
	hook := podmanHook{
		Version: "1.0.0",
		Stages:  []string{"prestart"},
		Hook: specHook{
			Path: executablePath,
			Args: []string{filepath.Base(executablePath), "prestart"},
			Env:  []string{envPath},
		},
		When: When{
			Always:   &always,
			Commands: []string{".*"},
		},
	}
	return hook
}

hooks.go

属于ocihook包,定义了一些不同规范的hook。