11 KiB
Container Deploy 请求格式说明
本文档说明 HTTP 接口 /container/deploy 接收的 JSON 格式,以及它在服务端如何被转换为 message.proto 中的 ContainerRequest.Deploy / ContainerDeployParams / ContainerSpec。
当前实现对应代码:
- HTTP 入口校验:src/transport/http/container_handler.erl
- 请求构造器:src/host/iot_container_request_builder.erl
- protobuf 定义:proto/message.proto
1. HTTP 请求格式
接口:
POST /container/deploy
请求体顶层结构:
{
"uuid": "host-uuid",
"task_id": 1001,
"config": {
"image": "docker.io/library/nginx:latest",
"container_name": "my_nginx",
"container_dir": "/data/apps/my_nginx",
"command": ["nginx", "-g", "daemon off;"],
"entrypoint": ["/docker-entrypoint.sh"],
"envs": ["ENV1=val1", "ENV2=val2"],
"expose": ["80", "443/tcp"],
"volumes": ["/host/data:/data", "/host/log:/var/log:ro"],
"networks": ["bridge"],
"network_mode": "bridge",
"labels": {
"role": "web",
"env": "prod"
},
"restart": "always",
"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
}
}
}
2. 顶层字段与 protobuf 的关系
HTTP 顶层字段和 protobuf 请求体的关系如下:
| HTTP 字段 | 类型 | 目标 protobuf 字段 | 说明 |
|---|---|---|---|
uuid |
string |
不进入 protobuf body | 仅用于在服务端定位 iot_host 进程 |
task_id |
integer |
ContainerRequest.Deploy.task_id |
必填,用于异步消息集合标识 |
config |
object |
ContainerRequest.Deploy.params |
必填,转换成 ContainerDeployParams |
最终构造出来的请求体结构是:
ContainerRequest {
action = {
deploy,
ContainerRequest.Deploy {
task_id = TaskId,
params = ContainerDeployParams {...}
}
}
}
3. config 到 ContainerDeployParams 的映射
config 会被转换成:
ContainerDeployParams {
container_name,
container_dir,
spec = ContainerSpec {...}
}
字段映射如下:
config 字段 |
类型 | protobuf 字段 | 说明 |
|---|---|---|---|
container_name |
string |
ContainerDeployParams.container_name |
必填 |
container_dir |
string |
ContainerDeployParams.container_dir |
可选,默认 "" |
| 其余部署字段 | 多种 | ContainerDeployParams.spec |
转成 ContainerSpec |
4. config 到 ContainerSpec 的详细映射
4.1 直接映射字段
以下字段基本按原值写入:
config 字段 |
类型 | protobuf 字段 | 默认值 |
|---|---|---|---|
image |
string |
ContainerSpec.image |
必填 |
command |
string[] |
ContainerSpec.command |
必填 |
entrypoint |
string[] |
ContainerSpec.entrypoint |
[] |
envs |
string[] |
ContainerSpec.env |
[] |
networks |
string[] |
ContainerSpec.networks |
[] |
network_mode |
string |
ContainerSpec.network_mode |
"" |
user |
string |
ContainerSpec.user |
"" |
working_dir |
string |
ContainerSpec.working_dir |
"" |
hostname |
string |
ContainerSpec.hostname |
"" |
privileged |
boolean |
ContainerSpec.privileged |
false |
cap_add |
string[] |
ContainerSpec.cap_add |
[] |
cap_drop |
string[] |
ContainerSpec.cap_drop |
[] |
extra_hosts |
string[] |
ContainerSpec.extra_hosts |
[] |
4.2 map 转换字段
config 字段 |
类型 | protobuf 字段 | 转换方式 |
|---|---|---|---|
labels |
map<string,string> |
ContainerSpec.labels |
转成 key/value 列表 |
sysctls |
map<string,string> |
ContainerSpec.sysctls |
转成 key/value 列表 |
4.3 结构化转换字段
volumes
输入类型:
["/host/data:/data", "/host/log:/var/log:ro"]
目标:
ContainerSpec.volumes = [VolumeBind...]
转换规则:
host_path:container_pathread_only = false
host_path:container_path:roread_only = true
host_path:container_path:rw- 当前实现也会被接受,但
read_only = false
- 当前实现也会被接受,但
生成结构:
VolumeBind {
host_path,
container_path,
read_only
}
expose
输入类型:
["80", "443/tcp", "53/udp"]
目标:
ContainerSpec.expose = [PortExpose...]
转换规则:
"80"->container_port = 80,protocol = "tcp""443/tcp"->container_port = 443,protocol = "tcp""53/udp"->container_port = 53,protocol = "udp"
生成结构:
PortExpose {
container_port,
protocol
}
devices
输入类型:
["/dev/ttyUSB0:/dev/ttyUSB0", "/dev/snd:/dev/snd:rwm"]
目标:
ContainerSpec.devices = [DeviceMapping...]
转换规则:
host_path:container_pathcgroup_permissions = "rwm"
host_path:container_path:permissions- 使用第三段作为
cgroup_permissions
- 使用第三段作为
ulimits
输入类型:
{
"nofile": "1024:2048",
"nproc": "4096"
}
目标:
ContainerSpec.ulimits = [Ulimit...]
转换规则:
"1024:2048"->soft = 1024,hard = 2048"4096"->soft = 4096,hard = 4096
tmpfs
输入类型:
["/tmp", "/run:rw,size=64m"]
目标:
ContainerSpec.tmpfs = [TmpfsMount...]
转换规则:
"path"->options = """path:options"->options为第二段
healthcheck
输入类型:
{
"test": ["CMD-SHELL", "curl -f http://localhost || exit 1"],
"interval": "30s",
"timeout": "10s",
"retries": 3
}
目标:
ContainerSpec.healthcheck = Healthcheck {
test,
interval_ns,
timeout_ns,
retries
}
转换规则:
test直接写入Healthcheck.testinterval/timeout会被解析成纳秒retries直接写入
支持的时间单位:
nsusmssmh
示例:
"30s"->30000000000"10ms"->10000000"2m"->120000000000
4.4 资源限制字段
restart
输入类型:
"always"
或:
"on-failure:3"
目标:
RestartPolicy {
name,
maximum_retry_count
}
转换规则:
"always"->name = "always",maximum_retry_count = 0"on-failure:3"->name = "on-failure",maximum_retry_count = 3
mem_limit / mem_reservation
目标字段:
ResourceLimits.memory_bytesResourceLimits.memory_reservation_bytes
支持单位:
bk,kb,ki,kibm,mb,mi,mibg,gb,gi,gibt,tb,ti,tib
示例:
"512m"->536870912"1g"->1073741824
cpus
目标字段:
ResourceLimits.nano_cpus
转换规则:
- 直接乘以
1_000_000_000
示例:
1->10000000001.5->1500000000
cpu_shares
目标字段:
ResourceLimits.cpu_shares
直接按整数写入。
resources 对象生成规则
只有在以下字段至少存在一个时,才会生成 ContainerSpec.resources:
mem_limitmem_reservationcpuscpu_shares
如果这些字段都不存在,则 resources = undefined。
5. 参数校验规则
在 HTTP 层,config 会先经过基础类型校验。
必填字段:
image: stringcontainer_name: stringcommand: string[]restart: string
可选字段:
privileged: booleanentrypoint: string[]envs: string[]ports: string[]expose: string[]volumes: string[]networks: string[]labels: map<string,string>user: stringworking_dir: stringhostname: stringcontainer_dir: stringnetwork_mode: stringcap_add: string[]cap_drop: string[]devices: string[]mem_limit: stringmem_reservation: stringcpu_shares: integercpus: numberulimits: map<string,string>sysctls: map<string,string>tmpfs: string[]extra_hosts: string[]healthcheck: map<string,any>
6. 当前限制
ports
虽然 HTTP 校验允许 ports 字段出现,但当前 builder 仍然会拒绝它:
unsupported container config keys: ports
原因是当前 message.proto 里只有:
ContainerSpec.expose
它表达的是容器端口暴露,不包含主机端口绑定信息;而 ports 一般是类似 8080:80 的 host/container 绑定语义,两者并不等价。
env_file
当前实现不再接收 env_file。如果传入,HTTP 层就不会通过类型校验,因为它不在 validate_config/1 的允许字段列表中。
7. 推荐请求示例
{
"uuid": "qbxmjyzrkpntfgswaevodhluicqzxplkm",
"task_id": 1001,
"config": {
"image": "docker.io/library/nginx:latest",
"container_name": "my_nginx",
"container_dir": "/data/apps/my_nginx",
"command": ["nginx", "-g", "daemon off;"],
"entrypoint": ["/docker-entrypoint.sh"],
"envs": ["ENV=prod", "TZ=Asia/Shanghai"],
"expose": ["80", "443/tcp"],
"volumes": ["/host/data:/data", "/host/log:/var/log:ro"],
"networks": ["bridge"],
"network_mode": "bridge",
"labels": {
"app": "nginx",
"env": "prod"
},
"restart": "always",
"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
}
}
}
8. 后续如果要支持 ports
建议先扩展 message.proto,增加明确表达 host/container 端口绑定的结构,例如:
message PortBinding {
uint32 host_port = 1;
uint32 container_port = 2;
string protocol = 3;
string host_ip = 4;
}
然后再在 ContainerSpec 中增加类似 repeated PortBinding ports = ...; 的字段,再由 builder 把 "8080:80/tcp" 这类字符串解析进去。这样语义才完整。