【NVIDIA】Ascend Docker Runtime v26.0.1 之五 install/ — 超深度逐行分析
install/ — 超深度逐行分析
四个文件:main.go (128行) + common.go (33行) + constant.go (87行) + docker_process.go (215行) + containerd_process.go (272行) = 735行
核心职责:安装/卸载ascend-docker-runtime,自动修改Docker或Containerd配置文件
一、模块定位
1.1 业务职责
install/ 模块是 ascend-docker-runtime 的安装辅助工具。它负责在安装或卸载时自动修改容器引擎的配置文件:
- Docker场景 — 修改
/etc/docker/daemon.json,添加/移除ascend运行时 - Containerd场景 — 修改
/etc/containerd/config.toml,修改runc配置 - iSula场景 — 与Docker相同的处理逻辑
- cgroup v1/v2兼容 — 根据cgroup版本选择不同的配置策略
1.2 在系统中的位置
二、install/main.go 逐行解析(128行)
2.1 模板定义
const commonTemplate = `{
"runtimes": {
"ascend": {
"path": "%s",
"runtimeArgs": []
}
},
"default-runtime": "ascend"
}`
const noDefaultTemplate = `{
"runtimes": {
"ascend": {
"path": "%s",
"runtimeArgs": []
}
}
}`
| 模板 | 用途 |
|---|---|
commonTemplate |
创建新daemon.json时使用,设置ascend为默认运行时 |
noDefaultTemplate |
创建新daemon.json但不修改默认运行时(保留docker默认runc) |
设计意图:当daemon.json不存在时,用模板直接生成。%s是ascend-docker-runtime二进制路径。
2.2 常量
const (
maxCommandLength = 65535
logPath = api.InstallHelperRunLogPath
minCommandLength = 2
installSceneIndexFromEnd = 4
)
var reserveDefaultRuntime = false
| 常量 | 用途 |
|---|---|
maxCommandLength |
参数最大长度 |
logPath |
安装日志路径 |
installSceneIndexFromEnd |
场景参数从末尾算的索引位置(第4个) |
reserveDefaultRuntime |
是否保留原有默认runtime(不强制改为ascend) |
2.3 main函数
func main() {
ctx, _ := context.WithCancel(context.Background())
if err := initLogModule(ctx); err != nil {
log.Fatal(err)
}
logPrefixWords, err := mindxcheckutils.GetLogPrefix()
if err != nil {
log.Fatal(err)
}
hwlog.RunLog.Infof("%v start running script", logPrefixWords)
与其他入口文件相同的模式:创建context → 初始化日志 → 获取日志前缀。
if !mindxcheckutils.StringChecker(strings.Join(os.Args, " "), 0,
maxCommandLength, mindxcheckutils.DefaultWhiteList+" ") {
hwlog.RunLog.Errorf("%v check command failed, maybe command contains illegal char", logPrefixWords)
log.Fatalf("command error, please check %s for detail", logPath)
}
参数安全校验:与runtime/main.go和hook/main.go一致,StringChecker白名单过滤。
const helpMessage = "\tadd <config file path> <new config file path> " +
"<docker-runtime path> <whether reserve default> <docker or containerd> <cgroup info> <os name> <os version>\n" +
"\t rm <config file path> <new config file path> <docker or containerd> <whether reserve default>" +
" <docker or containerd> <cgroup info> <os name> <os version>\n" + "\t -h help command"
helpFlag := flag.Bool("h", false, helpMessage)
flag.Parse()
if *helpFlag {
_, err := fmt.Println(helpMessage)
log.Fatalf("need help, error: %v", err)
}
命令行格式:
install add <src> <dst> <runtime_path> <reserve_default> <scene> <cgroup> <os_name> <os_version>
install rm <src> <dst> <runtime_path> <reserve_default> <scene> <cgroup> <os_name> <os_version>
| 参数位置 | add命令 | rm命令 | 说明 |
|---|---|---|---|
| 0 | add | rm | 操作类型 |
| 1 | 源配置文件路径 | 源配置文件路径 | 原始daemon.json/config.toml |
| 2 | 目标配置文件路径 | 目标配置文件路径 | 修改后的配置文件输出路径 |
| 3 | runtime二进制路径 | (不用) | ascend-docker-runtime路径 |
| 4 | 是否保留默认runtime | 是否保留默认runtime | “yes"或"no” |
| 5 | 场景 | 场景 | “docker”/“containerd”/“isula” |
| 6 | cgroup信息 | cgroup信息 | "cgroup2fs"或其他 |
| 7 | OS名称 | OS名称 | 如"openEuler" |
| 8 | OS版本 | OS版本 | 如"24.03" |
add命令9个参数(addCommandLength=9),rm命令8个参数(rmCommandLength=8)。
command := flag.Args()
if len(command) == 0 {
log.Fatalf("error param")
}
var behavior string
if !process.CheckParamLength(command) {
log.Fatalf("error param")
}
installScene := command[len(command)-installSceneIndexFromEnd]
if installScene == process.InstallSceneDocker || installScene == process.InstallSceneIsula {
behavior, err = process.DockerProcess(command)
} else if installScene == process.InstallSceneContainerd {
behavior, err = process.ContainerdProcess(command)
} else {
log.Fatalf("error param: %v", command[len(command)-1])
}
if err != nil {
hwlog.RunLog.Errorf("%v run script failed: %v", logPrefixWords, err)
log.Fatal(fmt.Errorf("error in installation, err is %v", err))
}
hwlog.RunLog.Infof("%v run %v success", logPrefixWords, behavior)
}
逐行解析:
flag.Args()— 获取非flag参数(即add/rm及其参数)CheckParamLength— 校验参数数量(add=9, rm=8)installScene— 从末尾第4个参数提取场景(docker/containerd/isula)- 根据场景分发:
- docker/isula →
DockerProcess - containerd →
ContainerdProcess
- docker/isula →
- 记录成功日志
三、install/process/constant.go 逐行解析(87行)
3.1 模板常量(与main.go中重复定义)
const commonTemplate = `...`
const noDefaultTemplate = `...`
注意:这些模板在main.go和constant.go中重复定义。这是代码冗余,可能是因为main.go和process包需要分别使用。
3.2 位置索引常量
const (
reserveIndexFromEnd = 5
actionPosition = 0
srcFilePosition = 1
destFilePosition = 2
runtimeFilePosition = 3
rmCommandLength = 8
addCommandLength = 9
maxFileSize = 1024 * 1024 * 10 // 10MB
cgroupInfoIndexFromEnd = 3
osNameIndexFromEnd = 2
osVersionIndexFromEnd = 1
perm os.FileMode = 0600
)
参数位置映射:
command[0] = action (add/rm)
command[1] = srcFilePath (源配置文件)
command[2] = destFilePath (目标配置文件)
command[3] = runtimeFilePath (runtime二进制路径,仅add)
从末尾索引的参数:
command[len-1] = osVersion
command[len-2] = osName
command[len-3] = cgroupInfo
command[len-4] = installScene (docker/containerd/isula)
command[len-5] = reserveDefault (yes/no)
3.3 配置键常量
const (
addCommand = "add"
rmCommand = "rm"
defaultRuntimeKey = "default-runtime"
InstallSceneDocker = "docker"
InstallSceneContainerd = "containerd"
InstallSceneIsula = "isula"
v1NeedChangeKeyRuntime = "runtime"
v1NeedChangeKeyRuntimeType = "runtime_type"
v1RuntimeType = "io.containerd.runtime.v1.linux"
v2RuncRuntimeType = "io.containerd.runc.v2"
defaultRuntimeValue = "runc"
v1RuntimeTypeFirstLevelPlugin = "io.containerd.grpc.v1.cri"
containerdKey = "containerd"
runtimesKey = "runtimes"
runcKey = "runc"
runcOptionsKey = "options"
binaryNameKey = "BinaryName"
cgroupV2InfoStr = "cgroup2fs"
openEulerStr = "openEuler"
openEulerVersionForV2RuntimeType = "24.03"
)
| 常量 | 用途 |
|---|---|
v1RuntimeTypeFirstLevelPlugin |
containerd的CRI插件key |
v1RuntimeType |
containerd V1运行时类型(cgroup v1用) |
v2RuncRuntimeType |
containerd V2 runc运行时类型 |
binaryNameKey |
containerd配置中BinaryName字段名 |
cgroupV2InfoStr |
cgroup v2的文件系统标识 |
openEulerVersionForV2RuntimeType |
openEuler 24.03使用V2运行时类型 |
四、install/process/common.go 逐行解析(33行)
func checkParamAndGetBehavior(action string, command []string) (bool, string) {
correctParam, behavior := false, ""
if action == addCommand && len(command) == addCommandLength {
correctParam = true
behavior = "install"
}
if action == rmCommand && len(command) == rmCommandLength {
correctParam = true
behavior = "uninstall"
}
return correctParam, behavior
}
func CheckParamLength(command []string) bool {
return len(command) == addCommandLength || len(command) == rmCommandLength
}
| 函数 | 逻辑 |
|---|---|
checkParamAndGetBehavior |
add+9参数→install, rm+8参数→uninstall |
CheckParamLength |
参数数量必须正好是9(add)或8(rm) |
五、install/process/docker_process.go 逐行解析(215行)
5.1 DockerProcess — 主入口
func DockerProcess(command []string) (string, error) {
if len(command) == 0 {
return "", fmt.Errorf("error param, length of command is 0")
}
action := command[actionPosition]
correctParam, behavior := checkParamAndGetBehavior(action, command)
if !correctParam {
return "", fmt.Errorf("error param")
}
srcFilePath := command[srcFilePosition]
if _, err := os.Stat(srcFilePath); os.IsNotExist(err) {
if _, err := mindxcheckutils.RealDirChecker(filepath.Dir(srcFilePath), true, false); err != nil {
return behavior, err
}
} else {
if _, err := mindxcheckutils.RealFileChecker(srcFilePath, true, false, mindxcheckutils.DefaultSize); err != nil {
return behavior, err
}
}
destFilePath := command[destFilePosition]
if _, err := mindxcheckutils.RealDirChecker(filepath.Dir(destFilePath), true, false); err != nil {
return behavior, err
}
runtimeFilePath := ""
if len(command) == addCommandLength {
runtimeFilePath = command[runtimeFilePosition]
if _, err := mindxcheckutils.RealFileChecker(runtimeFilePath, true, false, mindxcheckutils.DefaultSize); err != nil {
return behavior, err
}
}
setReserveDefaultRuntime(command)
writeContent, err := createJsonString(srcFilePath, runtimeFilePath, action)
if err != nil {
return behavior, err
}
return behavior, writeJson(destFilePath, writeContent)
}
逐行解析:
- 提取action(add/rm)
- 参数校验
- 源文件校验:
- 如果源文件不存在 → 校验其父目录存在(新建文件场景)
- 如果源文件存在 → 安全校验文件
- 目标文件校验:校验目标文件父目录存在
- runtime路径校验(仅add命令):安全校验runtime二进制
setReserveDefaultRuntime— 解析是否保留默认runtimecreateJsonString— 生成修改后的JSON内容writeJson— 写入目标文件
5.2 createJsonString — 生成配置JSON
func createJsonString(srcFilePath, runtimeFilePath, action string) ([]byte, error) {
var writeContent []byte
if _, err := os.Stat(srcFilePath); err == nil {
// 源文件已存在 → 修改现有配置
daemon, err := modifyDaemon(srcFilePath, runtimeFilePath, action)
if err != nil {
return nil, err
}
writeContent, err = json.MarshalIndent(daemon, "", " ")
if err != nil {
return nil, err
}
} else if os.IsNotExist(err) {
// 源文件不存在 → 用模板创建新配置
if !reserveDefaultRuntime {
writeContent = []byte(fmt.Sprintf(commonTemplate, runtimeFilePath))
} else {
writeContent = []byte(fmt.Sprintf(noDefaultTemplate, runtimeFilePath))
}
} else {
return nil, err
}
return writeContent, nil
}
三分支逻辑:
| 情况 | 处理 |
|---|---|
| 源文件存在 | modifyDaemon修改现有JSON |
| 源文件不存在 + 不保留默认 | 用commonTemplate(设ascend为默认) |
| 源文件不存在 + 保留默认 | 用noDefaultTemplate(不设默认) |
5.3 modifyDaemon — 修改现有daemon.json
func modifyDaemon(srcFilePath, runtimeFilePath, action string) (map[string]interface{}, error) {
daemon, err := loadOriginJson(srcFilePath)
if err != nil {
return nil, err
}
if _, ok := daemon["runtimes"]; !ok && action == addCommand {
daemon["runtimes"] = map[string]interface{}{}
}
runtimeValue := daemon["runtimes"]
runtimeConfig, runtimeConfigOk := runtimeValue.(map[string]interface{})
if !runtimeConfigOk && action == addCommand {
return nil, fmt.Errorf("extract runtime failed")
}
if action == addCommand {
runtimeConfig, daemon, err = addDockerDaemon(runtimeConfig, daemon, runtimeFilePath)
} else if action == rmCommand {
runtimeConfig, daemon, err = rmDockerDaemon(runtimeConfig, daemon, runtimeConfigOk)
}
return daemon, err
}
逐行解析:
loadOriginJson— 读取并解析原始daemon.json- 如果"runtimes"键不存在且是add操作 → 创建空的runtimes map
- 类型断言提取runtimeConfig
- add →
addDockerDaemon;rm →rmDockerDaemon
5.4 addDockerDaemon / rmDockerDaemon
func addDockerDaemon(runtimeConfig, daemon map[string]interface{}, runtimeFilePath string,
) (map[string]interface{}, map[string]interface{}, error) {
if runtimeConfig == nil {
return nil, daemon, fmt.Errorf("runtime config is nil")
}
if _, ok := runtimeConfig["ascend"]; !ok {
runtimeConfig["ascend"] = map[string]interface{}{}
}
ascendConfig, ok := runtimeConfig["ascend"].(map[string]interface{})
if !ok {
return nil, nil, fmt.Errorf("extract ascend failed")
}
ascendConfig["path"] = runtimeFilePath
if _, ok := ascendConfig["runtimeArgs"]; !ok {
ascendConfig["runtimeArgs"] = []string{}
}
if !reserveDefaultRuntime && daemon != nil {
daemon[defaultRuntimeKey] = "ascend"
}
return runtimeConfig, daemon, nil
}
add操作生成的JSON结构:
{
"runtimes": {
"ascend": {
"path": "/usr/local/bin/ascend-docker-runtime",
"runtimeArgs": []
}
},
"default-runtime": "ascend"
}
func rmDockerDaemon(runtimeConfig, daemon map[string]interface{}, runtimeConfigOk bool,
) (map[string]interface{}, map[string]interface{}, error) {
if runtimeConfigOk {
delete(runtimeConfig, "ascend")
}
if value, ok := daemon[defaultRuntimeKey]; ok && value == "ascend" {
delete(daemon, defaultRuntimeKey)
}
return runtimeConfig, daemon, nil
}
rm操作:删除"ascend"运行时配置 + 如果默认runtime是ascend则删除默认runtime设置。
5.5 setReserveDefaultRuntime
func setReserveDefaultRuntime(command []string) {
reserveCmdPostion := len(command) - reserveIndexFromEnd
if reserveCmdPostion >= len(command) || reserveCmdPostion < 0 {
return
}
if command[reserveCmdPostion] == "yes" {
reserveDefaultRuntime = true
}
}
- 从命令行参数倒数第5个提取reserve标志
- “yes” → 保留原有默认runtime(不强制改为ascend)
六、install/process/containerd_process.go 逐行解析(272行)
6.1 commandArgs 结构体
type commandArgs struct {
action string
srcFilePath string
runtimeFilePath string
destFilePath string
cgroupInfo string
osName string
osVersion string
}
6.2 ContainerdProcess — 主入口
func ContainerdProcess(command []string) (string, error) {
// ...参数校验同DockerProcess...
arg := &commandArgs{
action: action,
srcFilePath: srcFilePath,
runtimeFilePath: runtimeFilePath,
destFilePath: destFilePath,
cgroupInfo: command[len(command)-cgroupInfoIndexFromEnd],
osName: command[len(command)-osNameIndexFromEnd],
osVersion: command[len(command)-osVersionIndexFromEnd],
}
err := editContainerdConfig(arg)
// ...
return behavior, nil
}
与DockerProcess的区别:
- Docker修改daemon.json(JSON格式)
- Containerd修改config.toml(TOML格式)
- Containerd需要额外参数:cgroupInfo、osName、osVersion
6.3 editContainerdConfig — 核心配置编辑
func editContainerdConfig(arg *commandArgs) error {
if arg == nil {
return errors.New("arg is nil")
}
cfg := config.Config{}
if err := config.LoadConfig(arg.srcFilePath, &cfg); err != nil {
return err
}
if strings.Contains(arg.cgroupInfo, cgroupV2InfoStr) {
hwlog.RunLog.Info("it is cgroup v2")
binaryName := ""
if arg.action == addCommand {
binaryName = arg.runtimeFilePath
}
err := changeCgroupV2BinaryNameConfig(&cfg, binaryName)
if err != nil {
return err
}
} else {
hwlog.RunLog.Info("it is cgroup v1")
runtimeValue := defaultRuntimeValue
runtimeType := v2RuncRuntimeType
if arg.action == addCommand {
runtimeValue = arg.runtimeFilePath
runtimeType = v1RuntimeType
if arg.osName == openEulerStr && arg.osVersion == openEulerVersionForV2RuntimeType {
runtimeType = v2RuncRuntimeType
}
}
err := changeCgroupV1Config(&cfg, runtimeValue, runtimeType)
if err != nil {
return err
}
}
err := writeContainerdConfigToFile(cfg, arg.destFilePath)
return err
}
cgroup v1 vs v2 配置策略:
| 维度 | cgroup v1 | cgroup v2 |
|---|---|---|
| 修改位置 | plugins.io.containerd.runtime.v1.linux |
plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options |
| 修改字段 | runtime(路径)+ runtime_type |
BinaryName(路径) |
| add操作 | runtime=ascend路径, runtime_type=v1.linux | BinaryName=ascend路径 |
| rm操作 | runtime=runc, runtime_type=v2.runc | BinaryName=“” (空) |
| openEuler 24.03特殊处理 | 使用v2.runc类型 | 无 |
6.4 changeCgroupV2BinaryNameConfig
func changeCgroupV2BinaryNameConfig(cfg *config.Config, binaryName string) error {
value, ok := cfg.Plugins[v1RuntimeTypeFirstLevelPlugin]
if !ok {
return fmt.Errorf(notFindPluginErrorStr, v1RuntimeTypeFirstLevelPlugin)
}
valueMap := value.ToMap()
containerdConfig := valueMap[containerdKey]
runtimesConfig, err := getMap(containerdConfig, runtimesKey)
if err != nil {
return err
}
runcConfig, err := getMap(runtimesConfig, runcKey)
if err != nil {
return err
}
runcOptionsConfig, err := getMap(runcConfig, runcOptionsKey)
if err != nil {
return err
}
runcOptionsConfigMap, ok := runcOptionsConfig.(map[string]interface{})
if !ok {
return fmt.Errorf(convertConfigFailErrorStr, runcOptionsKey, runcOptionsConfig)
}
runcOptionsConfigMap[binaryNameKey] = binaryName
newTree, err := toml.TreeFromMap(valueMap)
if err != nil {
return err
}
cfg.Plugins[v1RuntimeTypeFirstLevelPlugin] = *newTree
return nil
}
逐行解析:
- 获取
io.containerd.grpc.v1.cri插件配置 - 层层深入:containerd → runtimes → runc → options
- 设置
BinaryName为ascend-docker-runtime路径(add)或空字符串(rm) - 将修改后的map转回TOML Tree
- 更新cfg.Plugins
配置路径:plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options.BinaryName
6.5 changeCgroupV1Config
func changeCgroupV1Config(cfg *config.Config, runtimeValue, runtimeType string) error {
err := changeCgroupV1RuntimeConfig(cfg, runtimeValue)
if err != nil {
return err
}
return changeCgroupV1RuntimeTypeConfig(cfg, runtimeType)
}
两步操作:
changeCgroupV1RuntimeConfig— 修改plugins."io.containerd.runtime.v1.linux".runtime为runtime路径changeCgroupV1RuntimeTypeConfig— 修改plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.runtime_type为运行时类型
6.6 getMap — 安全map取值
func getMap(input interface{}, key string) (interface{}, error) {
inputMap, ok := input.(map[string]interface{})
if !ok {
return nil, fmt.Errorf(convertConfigFailErrorStr, key, input)
}
output, ok := inputMap[key]
if !ok {
return nil, fmt.Errorf("can not find config: %v", key)
}
return output, nil
}
- 类型断言 + 键存在性检查
- 每一步都有错误处理,防止空指针panic
6.7 writeContainerdConfigToFile
func writeContainerdConfigToFile(cfg config.Config, destFilePath string) error {
tomlString, err := toml.Marshal(cfg)
if err != nil {
return err
}
file, err := os.OpenFile(destFilePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, perm)
if err != nil {
return err
}
defer func() {
err := file.Close()
if err != nil {
hwlog.RunLog.Errorf("failed to close file, error: %v", err)
}
}()
_, err = file.Write(tomlString)
return err
}
toml.Marshal— 将Config序列化为TOML格式O_CREATE|O_RDWR|O_TRUNC— 创建或截断写入- 文件权限0600(仅所有者读写)
- defer关闭文件
七、完整安装/卸载流程
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐

所有评论(0)