iot_cloud/docs/container_deploy_request.md
2026-05-07 23:21:56 +08:00

16 KiB
Raw Blame History

/container/deploy 容器创建请求参数说明

本文档说明 iot HTTP 接口 POST /container/deploy 当前支持的 JSON 参数、校验规则,以及服务端把 JSON 解码成 Erlang map 后如何转换成下发给 efka 的容器部署命令。

对应代码:

1. 接口

POST /container/deploy
Content-Type: application/json

HTTP body 必须是 JSON object。http_protocol 会使用 json:decode/1 解析请求体:

  • JSON object -> Erlang map
  • JSON array -> Erlang list
  • JSON string -> Erlang binary
  • JSON integer -> Erlang integer
  • JSON float -> Erlang float
  • JSON boolean -> Erlang true | false

/container/deploy handler 只接受顶层 map且必须匹配

#{<<"uuid">> := UUID, <<"task_id">> := TaskId, <<"config">> := Config}

其中:

  • UUID 必须是 binary也就是 JSON string。
  • TaskId 必须是 integer。
  • Config 必须是 map也就是 JSON object。

2. 完整 JSON 格式

下面示例包含当前支持的所有字段。实际请求可以只传必填字段和需要的可选字段。

{
  "uuid": "qbxmjyzrkpntfgswaevodhluicqzxplkm",
  "task_id": 1001,
  "config": {
    "image": "docker.io/library/nginx:latest",
    "container_name": "my_nginx",
    "command": ["nginx", "-g", "daemon off;"],
    "restart": "always",
    "container_dir": "/data/apps/my_nginx",
    "entrypoint": ["/docker-entrypoint.sh"],
    "envs": ["ENV=prod", "TZ=Asia/Shanghai"],
    "expose": ["80", "443/tcp", "53/udp"],
    "volumes": ["/host/data:/data", "/host/log:/var/log:ro"],
    "networks": ["bridge"],
    "network_mode": "bridge",
    "labels": {
      "app": "nginx",
      "env": "prod"
    },
    "user": "www-data",
    "working_dir": "/app",
    "hostname": "myhost",
    "privileged": false,
    "cap_add": ["NET_ADMIN"],
    "cap_drop": ["MKNOD"],
    "devices": ["/dev/ttyUSB0:/dev/ttyUSB0:rwm"],
    "mem_limit": "512m",
    "mem_reservation": "256m",
    "cpu_shares": 512,
    "cpus": 1.5,
    "ulimits": {
      "nofile": "1024:2048"
    },
    "sysctls": {
      "net.ipv4.ip_forward": "1"
    },
    "tmpfs": ["/tmp", "/run:rw,size=64m"],
    "extra_hosts": ["host.docker.internal:host-gateway"],
    "healthcheck": {
      "test": ["CMD-SHELL", "curl -f http://localhost || exit 1"],
      "interval": "30s",
      "timeout": "10s",
      "retries": 3
    }
  }
}

3. 顶层参数

字段 JSON 类型 必填 说明
uuid string 目标 efka 所属主机 UUID。服务端用它查找 iot_host 进程,不会下发到 efka。
task_id integer 部署任务 ID。会进入内部命令的 task_id 字段,用于关联部署结果和部署日志流。
config object 容器创建配置。会被校验并转换成内部部署参数 map。

顶层没有 timeout 字段。当前 HTTP handler 等待 efka command response 的超时时间固定为 10 秒。

4. config 参数

必填字段

字段 JSON 类型 内部类型 说明
image string binary 镜像名。没有 tag 时 efka 部署逻辑会补 :latest
container_name string binary 容器名称。
command string[] binary list 容器启动命令,对应 Docker create config 的 cmd
restart string binary 重启策略,例如 noalwaysunless-stoppedon-failureon-failure:3

可选字段

字段 JSON 类型 默认值 说明
container_dir string "" 容器工作目录在 efka 主机上的应用目录,传给 efka 部署逻辑。
entrypoint string[] [] Docker create config 的 entrypoint
envs string[] [] 环境变量列表,例如 ["A=1"],对应 Docker create config 的 env
expose string[] [] 容器暴露端口,只表示容器端口,不支持宿主机端口绑定。
volumes string[] [] volume bind 列表,格式见后文。
networks string[] [] Docker network 名称列表,用于 networking config。
network_mode string "" Docker host config 的 network_mode
labels object string:string {} 容器 labels。key/value 都必须是 string。
user string "" 容器运行用户。
working_dir string "" 容器工作目录。
hostname string "" 容器 hostname。
privileged boolean false 是否 privileged。
cap_add string[] [] 追加 Linux capability。
cap_drop string[] [] 删除 Linux capability。
devices string[] [] 设备映射列表,格式见后文。
mem_limit string 0 内存上限,解析成字节数。
mem_reservation string 0 内存软限制,解析成字节数。
cpu_shares integer 0 Docker CPU shares。
cpus number 0 CPU 数量,转换成 nano cpus。
ulimits object string:string [] ulimit 配置,格式见后文。
sysctls object string:string {} sysctl 配置。key/value 都必须是 string。
tmpfs string[] {} tmpfs mount 配置,格式见后文。
extra_hosts string[] [] 额外 hosts例如 ["host.docker.internal:host-gateway"]
healthcheck object undefined 健康检查配置,格式见后文。

不支持字段

字段 当前行为 说明
ports 明确拒绝 如果传入会返回 unsupported container config keys: ports。当前只支持 expose,不支持宿主机端口绑定。
env_file 忽略 当前校验不会识别该字段,后续构造 Docker create options 时也不会使用。
其他未知字段 忽略 ports 外,未知字段不会报错,也不会进入内部部署参数。

5. 校验规则

校验分为两层。

第一层在 container_handler

  • uuid 必须是 binary。
  • task_id 必须是 integer。
  • config 必须是 map。

第二层在 docker_container_builder:deploy_request/2

  • 先拒绝不支持的 ports 字段。
  • 检查必填字段是否存在。
  • 检查已知字段类型。
  • 构造内部部署 map。
  • 对需要解析的字段做格式解析例如端口、volume、size、duration、ulimit。

类型错误会返回类似:

required parameter: <<"command">>, type must be: list of string
optional parameter: <<"labels">>, type must be: map of string:string

缺少必填字段会返回类似:

miss requied parameter: <<"image">>

注意:错误文本中的 requied 是当前代码里的原始拼写。

6. 内部转换结果

config 校验通过后,docker_container_builder:deploy_request(TaskId, Config) 生成:

#{
    action => deploy,
    task_id => TaskId,
    params => #{
        container_name => ContainerName,
        container_dir => ContainerDir,
        create => #{
            config => ContainerConfig,
            host_config => HostConfig,
            networking_config => NetworkingConfig
        }
    }
}

该 map 会通过 efka/iot 长连接协议下发:

{command, Ref, {container, #{
    action => deploy,
    task_id => TaskId,
    params => Params
}}}

efka 返回:

{command_response, Ref, {container, Reply}}

HTTP handler 最多等待 10 秒。超时返回 HTTP 504其他参数或执行错误通常返回 HTTP 400找不到 host 返回业务错误 code 404。

7. Docker create config 映射

create.configbuild_docker_container_config/1 生成:

内部字段 来源 JSON 字段 转换规则
image image 原值。
cmd command 原值。
entrypoint entrypoint 默认 []
env envs 默认 []
labels labels 默认 {}
volumes volumes 只保留 container path 列表。
user user 默认 ""
working_dir working_dir 默认 ""
hostname hostname 默认 ""
exposed_ports expose 转成 #{container_port, protocol} map 列表。
healthcheck healthcheck 未传时为 undefined

8. Docker host config 映射

create.host_configbuild_docker_host_config/1 生成:

内部字段 来源 JSON 字段 转换规则
binds volumes 转成 Docker bind 字符串列表。
network_mode network_mode 默认 ""
restart_policy restart 转成 #{name, maximum_retry_count}
privileged privileged 默认 false
cap_add cap_add 默认 []
cap_drop cap_drop 默认 []
devices devices 转成设备映射 map 列表。
memory mem_limit 解析成字节数,未传为 0
memory_reservation mem_reservation 解析成字节数,未传为 0
nano_cpus cpus cpus * 1000000000,未传为 0
cpu_shares cpu_shares 未传为 0
ulimits ulimits 转成 ulimit map 列表。
tmpfs tmpfs 转成 map。
sysctls sysctls 默认 {}
extra_hosts extra_hosts 默认 []

9. Docker networking config 映射

create.networking_configbuild_docker_networking_config/1 生成:

#{endpoints => [#{name => Network} || Network <- Networks]}

来源字段:

"networks": ["bridge", "mynet"]

转换结果:

#{endpoints => [
    #{name => <<"bridge">>},
    #{name => <<"mynet">>}
]}

10. 复杂字段转换规则

restart

输入:

"restart": "always"

转换:

#{name => <<"always">>, maximum_retry_count => 0}

输入:

"restart": "on-failure:3"

转换:

#{name => <<"on-failure">>, maximum_retry_count => 3}

expose

输入:

["80", "443/tcp", "53/udp"]

转换:

[
    #{container_port => 80, protocol => <<"tcp">>},
    #{container_port => 443, protocol => <<"tcp">>},
    #{container_port => 53, protocol => <<"udp">>}
]

端口必须是无符号整数,且不能超过 4294967295

volumes

输入:

["/host/data:/data", "/host/log:/var/log:ro", "/host/cache:/cache:rw"]

转换为 create.config.volumes

[<<"/data">>, <<"/var/log">>, <<"/cache">>]

转换为 create.host_config.binds

[
    <<"/host/data:/data">>,
    <<"/host/log:/var/log:ro">>,
    <<"/host/cache:/cache">>
]

规则:

  • host_path:container_path -> 读写挂载。
  • host_path:container_path:ro -> 只读挂载。
  • host_path:container_path:rw -> 当前实现视为读写挂载,输出时不会保留 :rw
  • host path 和 container path 都不能为空。

devices

输入:

["/dev/ttyUSB0:/dev/ttyUSB0", "/dev/snd:/dev/snd:rwm"]

转换:

[
    #{
        path_on_host => <<"/dev/ttyUSB0">>,
        path_in_container => <<"/dev/ttyUSB0">>,
        cgroup_permissions => <<"rwm">>
    },
    #{
        path_on_host => <<"/dev/snd">>,
        path_in_container => <<"/dev/snd">>,
        cgroup_permissions => <<"rwm">>
    }
]

规则:

  • host_path:container_path -> 权限默认为 rwm
  • host_path:container_path:permissions -> 使用第三段作为权限。
  • 任意路径或 permissions 为空时返回 invalid device mapping

ulimits

输入:

{
  "nofile": "1024:2048",
  "nproc": "4096"
}

转换:

[
    #{name => <<"nofile">>, soft => 1024, hard => 2048},
    #{name => <<"nproc">>, soft => 4096, hard => 4096}
]

规则:

  • "soft:hard" -> 分别设置 soft 和 hard。
  • "limit" -> soft 和 hard 都等于 limit。
  • 数值必须是无符号整数。

tmpfs

输入:

["/tmp", "/run:rw,size=64m"]

转换:

#{
    <<"/tmp">> => <<>>,
    <<"/run">> => <<"rw,size=64m">>
}

规则:

  • "path" -> options 为 ""
  • "path:options" -> options 为第二段。
  • path 不能为空。

healthcheck

输入:

{
  "test": ["CMD-SHELL", "curl -f http://localhost || exit 1"],
  "interval": "30s",
  "timeout": "10s",
  "retries": 3
}

转换:

#{
    test => [<<"CMD-SHELL">>, <<"curl -f http://localhost || exit 1">>],
    interval_ns => 30000000000,
    timeout_ns => 10000000000,
    retries => 3
}

字段规则:

字段 JSON 类型 默认值 说明
test any [] 当前只要求 healthcheck 的 key 是 string不单独校验 test 类型;建议传 string array。
interval string 或 integer "0s" string 会解析时间单位integer 直接视为纳秒。
timeout string 或 integer "0s" string 会解析时间单位integer 直接视为纳秒。
retries integer 0 当前构造阶段直接取值;建议传非负整数。

时间单位:

单位 含义
ns 纳秒
us 微秒
ms 毫秒
s
m 分钟
h 小时
无单位

示例:

  • "30s" -> 30000000000
  • "10ms" -> 10000000
  • "2m" -> 120000000000
  • "5" -> 5000000000

mem_limit 和 mem_reservation

输入:

"mem_limit": "512m",
"mem_reservation": "1g"

转换:

memory => 536870912,
memory_reservation => 1073741824

支持单位:

单位 倍数
b 或无单位 1
k, kb, ki, kib 1024
m, mb, mi, mib 1048576
g, gb, gi, gib 1073741824
t, tb, ti, tib 1099511627776

数值支持整数或小数,例如 "1.5g"

cpus

输入:

"cpus": 1.5

转换:

nano_cpus => 1500000000

规则:

  • integer 或 float 都可以。
  • 必须大于等于 0。
  • 转换公式:trunc(Cpus * 1000000000)

11. 最小请求示例

{
  "uuid": "qbxmjyzrkpntfgswaevodhluicqzxplkm",
  "task_id": 1001,
  "config": {
    "image": "docker.io/library/nginx:latest",
    "container_name": "my_nginx",
    "command": ["nginx", "-g", "daemon off;"],
    "restart": "always"
  }
}

对应内部命令示意:

{command, Ref, {container, #{
    action => deploy,
    task_id => 1001,
    params => #{
        container_name => <<"my_nginx">>,
        container_dir => <<>>,
        create => #{
            config => #{
                image => <<"docker.io/library/nginx:latest">>,
                cmd => [<<"nginx">>, <<"-g">>, <<"daemon off;">>],
                entrypoint => [],
                env => [],
                labels => #{},
                volumes => [],
                user => <<>>,
                working_dir => <<>>,
                hostname => <<>>,
                exposed_ports => [],
                healthcheck => undefined
            },
            host_config => #{
                binds => [],
                network_mode => <<>>,
                restart_policy => #{name => <<"always">>, maximum_retry_count => 0},
                privileged => false,
                cap_add => [],
                cap_drop => [],
                devices => [],
                memory => 0,
                memory_reservation => 0,
                nano_cpus => 0,
                cpu_shares => 0,
                ulimits => [],
                tmpfs => #{},
                sysctls => #{},
                extra_hosts => []
            },
            networking_config => #{endpoints => []}
        }
    }
}}}

12. 响应

成功时 HTTP body 由 iot_util:json_data/1 包装:

{
  "result": "ok"
}

如果 efka 返回的是 JSON binaryhandler 会尝试解码后再放入 result

错误示例:

{
  "error": {
    "code": 400,
    "message": "unsupported container config keys: ports"
  }
}

常见状态:

HTTP 状态 场景
200 host not found 或构造阶段返回的业务错误也可能通过 200 包装业务错误。
400 参数校验失败、efka 返回普通错误。
504 等待 efka command response 超时。