iot_cloud/docs/container_deploy_request.md
2026-04-19 15:01:41 +08:00

11 KiB
Raw Blame History

Container Deploy 请求格式说明

本文档说明 HTTP 接口 /container/deploy 接收的 JSON 格式,以及它在服务端如何被转换为 message.proto 中的 ContainerRequest.Deploy / ContainerDeployParams / ContainerSpec

当前实现对应代码:

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. configContainerDeployParams 的映射

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. configContainerSpec 的详细映射

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_path
    • read_only = false
  • host_path:container_path:ro
    • read_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_path
    • cgroup_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.test
  • interval / timeout 会被解析成纳秒
  • retries 直接写入

支持的时间单位:

  • ns
  • us
  • ms
  • s
  • m
  • h

示例:

  • "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_bytes
  • ResourceLimits.memory_reservation_bytes

支持单位:

  • b
  • k, kb, ki, kib
  • m, mb, mi, mib
  • g, gb, gi, gib
  • t, tb, ti, tib

示例:

  • "512m" -> 536870912
  • "1g" -> 1073741824

cpus

目标字段:

  • ResourceLimits.nano_cpus

转换规则:

  • 直接乘以 1_000_000_000

示例:

  • 1 -> 1000000000
  • 1.5 -> 1500000000

cpu_shares

目标字段:

  • ResourceLimits.cpu_shares

直接按整数写入。

resources 对象生成规则

只有在以下字段至少存在一个时,才会生成 ContainerSpec.resources

  • mem_limit
  • mem_reservation
  • cpus
  • cpu_shares

如果这些字段都不存在,则 resources = undefined

5. 参数校验规则

在 HTTP 层,config 会先经过基础类型校验。

必填字段:

  • image: string
  • container_name: string
  • command: string[]
  • restart: string

可选字段:

  • privileged: boolean
  • entrypoint: string[]
  • envs: string[]
  • ports: string[]
  • expose: string[]
  • volumes: string[]
  • networks: string[]
  • labels: map<string,string>
  • user: string
  • working_dir: string
  • hostname: string
  • container_dir: string
  • network_mode: string
  • cap_add: string[]
  • cap_drop: string[]
  • devices: string[]
  • mem_limit: string
  • mem_reservation: string
  • cpu_shares: integer
  • cpus: number
  • ulimits: 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" 这类字符串解析进去。这样语义才完整。