拒绝和更改
控制平面返回的每个拒绝都携带一个机器可读的代码、失败的字段路径、接收到的值以及可接受的格式。根据代码进行分支;切勿匹配消息文本,该文本是为人编写的,措辞改进时会更改。
{ "code": "healthcheck_timeout_below_floor", "field": "healthCheck.timeout", "received": "4s", "expected": ">= 10s, or omit the field", "why": "a timed-out exec is SIGKILLed and reparented to a PID 1 that never reaps it", "doc": "https://odysseus.delta-telematics.ca/docs/reference/manifest/deployment#healthcheck"}- · pkg/api/validation_response.go拒绝意味着未存储任何内容。修复指定的字段并重新发送。
Generated from Rule. Hand edits to this table are overwritten on the next build — change the Go doc comment, or the generator.
| 代码 | 字段 | 接受的形式 | 原因 |
|---|---|---|---|
anchored_secret_dropped |
secrets |
保留条目 {name: %q, vault_path: %q, anchor: %q},或在同一更新中移除卷 %q 以释放锚点 | 更新丢弃了一个仍被挂载的卷所锚定的密钥;容器将在没有其自身数据目录初始化时所用凭据的情况下被重新创建 |
anchored_secret_repointed |
<computed> |
%q — 卷 %q 上的材料初始化时所用的路径。要轮换,请使用 POST /api/v1/deployments/{name}/secrets/rotate;要释放锚点,请在同一更新中移除卷 %q | 更改路径会导致规格哈希漂移并重新创建容器,然后容器将使用它从未见过的材料对未更改的数据目录进行身份验证 |
deployment_name_invalid |
name |
仅限小写字母、数字和连字符,以字母开头,以字母或数字结尾——例如 %q | 该名称被原样用作容器名称、Traefik 路由器 ID、租户网络上的 DNS 别名以及 Consul KV 路径段,而所有这些都拒绝大写字母、下划线、点和空格 |
deployment_name_too_long |
name |
最多 %d 个字符,例如 %q | 该名称成为容器的 DNS 标签,而超过 63 个字符的标签无法被同级容器解析 |
deployment_not_found |
name, deployment_id |
一个已存在的部署名称——使用 GET /api/v1/deployments 列出它们 | PUT 更新现有记录且不能创建新记录;POST /api/v1/deployments 用于创建 |
dvm_recommendation_apply_invalid_spec |
spec |
一条使卷保持有效的更改——请参阅底层消息 | 平台不会写入其自身验证器拒绝的卷规范 |
dvm_recommendation_bad_parameter |
parameters.<computed> |
<computed> | <computed> |
dvm_recommendation_insufficient_nodes |
parameters.target_replicas |
%d 个已注册节点——一个用于主节点,每个副本一个。请再注册 %d 个,或应用更小的副本计数。 | 每个副本必须位于不同的节点上,因此 N 个副本需要 N+1 个节点,平台不会将两个副本放在同一台机器上 |
dvm_recommendation_not_applicable |
id |
“applicable”字段为 true 的建议的 ID——读取 GET /api/v1/dvm/recommendations 并对这些建议采取行动 | <computed> |
dvm_recommendation_not_found |
id |
此租户的 GET /api/v1/dvm/recommendations 返回的建议 ID——重新读取该列表以获取当前 ID | 建议在每次读取时根据实时卷状态重新计算,因此一旦它描述的条件不再成立,该建议就会消失 |
dvm_recommendation_stale_measurement |
parameters.target_size_bytes |
至少 %d 字节的大小,这是卷当前的容量 | 卷自计算此建议以来已增长,将其调整到低于其自身内容的大小将导致实时数据声明不足 |
dvm_recommendation_tenant_required |
X-Tenant-ID |
租户 ID,例如“X-Tenant-ID: acme”——平台管理员在仪表板标题中的租户选择器中选择一个 | 建议是关于一个租户的卷的,如果不指定租户,将返回一个看起来像真实答案的空列表 |
dvm_recommendation_unknown_action |
parameters.action |
以下之一:%s、%s | 应用端点未实现的动作动词,否则将返回成功但不执行任何操作 |
dvm_recommendation_unknown_priority |
priority |
以下之一:%s——例如 ?priority=%s | 对不存在的优先级进行过滤将返回一个看起来像真实答案的空列表 |
dvm_recommendation_unknown_type |
type |
以下之一:%s — 例如 ?type=%s | 对不存在的类型进行过滤将返回一个空列表,该列表会被视为真实答案 |
dvm_recommendation_volume_missing |
volume_id |
此租户中仍存在的卷 — 请阅读 GET /api/v1/dvm/volumes | 该建议是基于一个已被删除或移动的卷计算得出的,因此应用它将为不存在的对象编写规范 |
healthcheck_command_missing |
healthCheck.command |
镜像可运行的命令,例如 [“wget”,“-qO-”,“http://127.0.0.1:8080/health”] | 无命令的 exec 声明不会产生任何容器健康检查 |
healthcheck_retries_below_floor |
healthCheck.retries |
设置 >= %d,或省略 retries 以采用 Docker 的默认值 | 一个不健康的容器会降低整个部署的健康度,而 retries 是唯一的抑制手段 |
healthcheck_timeout_below_floor |
healthCheck.timeout |
设置 >= %s,或省略 timeout 以采用 Docker 的 30 秒默认值 | 超时的 exec 进程会被 SIGKILL 终止,并被重新挂载到一个永远不会回收它的 PID 1 进程下 |
healthcheck_type_inert |
healthCheck.type |
设置 type: “exec” 并指定镜像可运行的命令——shell 形式 [“wget -qO- http://127.0.0.1:%d%s”] 或 argv 形式 [“wget”,“-qO-”,“http://127.0.0.1:%d%s”] | 一个惰性声明会静默地满足它本应守护的 dependsOn:健康状态和滚动更新就绪门控 |
healthcheck_type_unsupported |
healthCheck.type |
唯一接受的值是 “exec” | 没有其他类型能产生容器健康检查 |
image_not_found |
image |
一个清单已存在于注册表中的镜像引用——请先使用您要部署的确切标签推送它,或修正标签中的拼写错误 | 注册表确认此确切引用不存在清单;允许写入将销毁正在运行的容器,然后才发现镜像无法拉取——在 odysseus-marketing 2026-08-21 上测量到的故障中,一个健康的容器因一个从未被推送过的标签被拆除了两次 |
image_required |
image |
一个固定的引用,例如 “nginx:1.27”——绝不使用 :latest | 没有它就没有任何东西可运行 |
job_node_candidates_unresolvable |
nodeId |
一个具有可达代理程序的节点——注册或认领一个,然后重试 | 没有可列出的具有可达代理程序的节点,因此无法满足任何目标——要么没有节点注册,要么网关无法读取,从这里无法区分这两种情况 |
job_node_not_found |
nodeId |
以下之一:%s | 一个作业没有调度器,因此未知节点不是降级的放置——它是一个作业,会不断向不存在的机器派发和重试 |
manifest_wrong_kind |
kind |
类型:部署——或将此文档发送到其自身的端点 | 作业和部署是不同的原语,具有不同的字段,将一个解码为另一个会丢弃其不共享的字段 |
name_required |
name |
一个 DNS 风格的小写名称,例如 “web-api” | 名称是部署的标识及其容器名称前缀 |
network_node_candidates_unresolvable |
nodeId |
为此租户注册一个节点,然后创建网络 | 没有为此租户注册节点,因此无处创建网络——要么没有节点注册,要么节点存储不可达,从这里无法区分这两种情况 |
network_node_not_a_candidate |
nodeId |
以下之一:%s | Docker 网络仅存在于创建它的主机上,而此租户无法在您指定的节点上放置工作负载——该网络将无法从您可能运行的任何容器访问 |
network_node_required |
nodeId |
以下之一:%s | Docker 网络创建在一台主机上,因此当有多个节点可用时,平台无法知道您指的是哪个——为您选择正是导致网络出现在无人预期的节点上的原因 |
network_option_unsupported_on_agent |
<computed> |
省略它——或者直接在节点上创建网络,直到代理程序支持就绪 | 节点代理程序的网络线路格式仅包含名称、驱动程序、标签和内部,因此此值无法到达主机,网络将在没有它的情况下创建 |
node_candidates_unresolvable |
nodeId |
为此租户注册一个节点,或省略 nodeId 以让调度器放置此部署 | 没有为此租户注册节点,因此无法满足任何目标——要么没有节点注册,要么节点存储无法读取,从这里无法区分这两种情况 |
node_claim_release_no_tenant |
X-Tenant-ID |
放弃其声明的租户的 UUID | 一个节点在两个租户下有两个声明,因此一个未指定租户的请求不指定任何声明,可能移除其中一个 |
node_not_a_placement_candidate |
nodeId |
以下之一:%s(或省略 nodeId 以让调度器决定) | 放置仅考虑此部署所属租户的已注册节点,因此该集合之外的节点在此处会被接受,但在调度时会被静默覆盖 |
node_owned_by_another_tenant |
nodeName |
一个尚未注册到其他租户的节点名称 | 一个节点仅属于一个租户且从不共享。节点代理程序会将机器上的每个容器返回给出示该机器租户令牌的任何人,因为它信任此令牌;而第二次注册将允许一个租户的工作负载被放置在另一个租户的硬件上 |
plaintext_credential_in_environment |
<computed>, environment.%s |
移除 environment.%s 并改为引用 Vault:“secrets”: [{“name”: %q, “vault_path”: “deployments/%s”}] | Consul 以明文形式存储环境变量值:它们会出现在每个规范导出和备份中,任何能读取 <computed> 的人都能读取该密钥 |
query_parameter_unknown |
<computed> |
<computed> | <computed> |
registry_credential_store_unavailable |
image |
无——您的请求格式正确。请在控制平面报告健康状态后重试;操作员应检查 ODYSSEUS_CONSUL_ADDRESS 是否已设置以及 /healthz 是否报告 Consul 已连接。 | 控制平面无法访问其自身的凭据存储,因此无法判断此镜像是否可拉取,并且当原因在于其自身而非注册表时,它将不会接受无法验证的写入——仅不可达的注册表会被允许通过,但会附带警告。 |
replicas_negative |
replicas |
0 或更大 — 使用 0 可停止部署而不删除它 | 负的副本数没有意义;缩放到 0 是停止部署的方式 |
replicas_out_of_range |
replicas |
一个介于 0 和 %d 之间的整数(max_replicas_per_deployment;如果您的集群确实运行更多,请在控制平面的配置中提高它) | 失控的副本数会在任何配额生效之前耗尽集群中的每个节点;允许缩放至零 (0),负数无意义 |
reserved_infrastructure_name |
name |
除了 %q 以外的任何名称——该名称在整个平台范围内被保留,因为控制平面会以 %s 的身份连接到它(在 %s 中配置)。如果您想更改平台自身的数据库,请编辑主机上的 infrastructure/docker-compose.yml;它被刻意设计为不是 Odysseus 部署。如果您想拥有自己的数据库,%q 或 %q 是可用的。 | %s 是控制平面需要的一项服务,用于记录其操作,因此它不能是控制平面启动的工作负载——在冷启动时,控制平面将等待一个它尚未启动的容器,并且该启动的审计跟踪将会丢失 |
rotation_transition_invalid |
phase |
来自阶段 %q 的 %v 之一 | 重叠窗口保证在任何时刻都不会出现零个有效凭据,且该保证仅在阶段按顺序推进时成立 |
secret_anchor_invalid |
<computed> |
“process”、“external” 或 “volume(<volume-id>)” 之一 — 例如 “volume(codecheck-db-data)” | 锚点告知平台此凭据是否可被重新铸造;无法识别的值将被静默忽略,这正是此字段旨在防止的故障 |
secret_anchor_volume_ephemeral |
<computed> |
类型为 “volume” 或 “bind” 的卷;%q 的类型为 %q。如果材料确实随容器重新创建,请使用 “process” | tmpfs 本质上是临时的 — 它随容器一起丢弃,因此不锚定任何内容,在其上设置锚点会提供虚假保证 |
secret_anchor_volume_not_declared |
<computed> |
引用此规范声明的卷的 volume(<id>);在此声明:%v | 指向工作负载未挂载的卷的锚点无法保护任何内容 — 材料将在下次重新创建时重新铸造,容器将使用其数据存储没有的凭据进行身份验证 |
secret_compose_no_reference |
<computed> |
包含至少一个 {{secret.<resource>.<key>}} 的模板 | 没有密钥引用的模板是常量,应放在 environment: 中,在那里它是可见且可比较的 |
secret_compose_resource_undeclared |
<computed> |
此部署声明的资源:%v | 模板只能读取工作负载已引用的资源,因此拼写错误会在此处被捕获,而不是在调度时因无容器而失败关闭 |
secret_compose_template_empty |
<computed> |
postgresql://user:{{secret.<resource>.<key>}}@host:5432/db | 空模板渲染为空环境变量,消费者将其视为未设置 |
secret_compose_template_malformed |
<computed> |
{{secret.<resource>.<key>}} — 小写 DNS 资源,环境变量形式的键 | 无法识别的占位符会以字面花括号形式传递给容器,工作负载将使用错误的凭据进行连接 |
secret_generate_axis_conflict |
generate |
字节/编码(随机字节渲染为文本)或长度/字符集(从字母表中抽取字符)——不能同时指定 | 两者描述的是不同的内容,平台必须忽略其中一个,而它不会静默地这样做 |
secret_generate_below_entropy_floor |
generate.length |
长度 >= %d,字符集为 %q(或省略长度以使用默认值 %d) | 长度 %d,字母表有 %d 个字符,熵为 %.0f 位;平台下限为 %d 位,低于此值时,离线暴力破解是一个预算问题而非物理限制 |
secret_generate_bytes_out_of_range |
generate.bytes |
%d..%d(省略则使用默认值 %d) | %d 字节即 %d 位;平台下限为 %d 位,超过 %d 字节时,编码后的值会超出 Vault 为其配置的 %d 字符上限 |
secret_generate_charset_unknown |
generate.charset |
%q、%q、%q 之一 | 未知的字母表无法进行熵检查,因此平台无法保证该值满足其下限 |
secret_generate_encoding_unknown |
generate.encoding |
%q、%q、%q 之一 | 平台无法用其未实现的编码来渲染字节,而猜测编码会将应用程序无法解码的材料交给它 |
secret_generate_key_duplicate |
<computed> |
每个键仅列出一次 | 重复的键会被铸造两次,第二次写入会静默胜出 |
secret_generate_keys_required |
generate.keys |
至少一个键,例如 [“password”] | 没有键的 generate 块不会铸造任何内容,并静默地生成一个指向空文档的引用,该引用在调度时会以失败关闭 |
secret_generate_length_out_of_range |
generate.length |
1..%d | 负长度没有意义,过大的长度会在部分写入后被 Vault 拒绝 |
secret_generate_would_persist |
secrets[%d].generate |
nil — 在持久化之前调用 StripSecretAuthoring | 存储的 generate 块会在每次回滚时重新评估,并可能铸造出数据存储不知道的材料 |
secret_key_invalid |
<computed> |
一个匹配 [A-Za-z_][A-Za-z0-9_]* 的环境变量名,例如 “S3_ACCESS_KEY” | 文档键会原样成为容器环境变量,且在 sh 中不能以数字开头 |
secret_key_provided_and_generated |
<computed> |
在 values 或 generate.keys 中声明键,不要两者都声明 | 平台将不得不在您的值和生成的值之间做出选择,而任何一种选择都是静默的修改 |
secret_material_would_persist |
secrets[%d].values |
一个空的 values 映射——在持久化之前调用 StripSecretAuthoring | Consul KV 仅持有引用;存储的值会进入规范哈希,影响每个修订版本和每次回滚 |
secret_mount_path_unsupported |
secrets[%d].mount_path |
移除 mount_path;在容器启动时通过入口点从环境变量值渲染文件 | 交付仅限环境变量(WP2 裁决 D1)——文件挂载会将值放在控制平面不控制的磁盘上 |
secret_name_invalid |
secrets[%d].name |
一个环境变量前缀:[A-Za-z0-9_]+,例如 “DB_CREDS” | 名称会作为前缀添加到从此文档解析的每个键(NAME_key)上,因此它本身必须是合法的环境变量材料 |
secret_path_name |
<computed> |
命名空间后的 DNS 标签:小写字母、数字和内部连字符,1-63 个字符,例如 “resources/codecheck-db” | 名称会与工作负载名称进行比较,以检测在两个命名空间下同时引用的凭据;一个不命名任何文档的路径无法参与该比较,并在调度时解析为空 |
secret_path_namespace |
<computed>, namespace |
<computed> | 只有这些命名空间被控制平面的 Vault 策略和租户应用程序策略授予;其他任何内容在调度时都会解析为 403,工作负载会进入降级状态且没有容器 |
secret_path_namespace_split |
secrets[%d].vault_path |
每个凭据一个命名空间——对每个对 %q 的引用都使用 %q | 同一凭据在两个命名空间下是两个 Vault 文档,必须手动保持一致;这种重复正是 resources/ 命名空间旨在消除的 |
secret_path_shape |
<computed>, resource |
<computed> | 第一段是每个 Vault 策略授予——控制平面的和每个租户应用程序的——所针对的命名空间;授予命名空间之外的路径会解析为 403,而 403 对于不对其进行分类的调用者来说与空密钥无法区分 |
secret_rotation_unsupported |
secrets[%d].rotation |
“restart”,或省略该字段——它默认为 restart | 容器仅通过重新创建才能获取新材质;signal/webhook/file_watch 从未实现,且会静默地不执行任何操作 |
secret_value_empty |
<computed> |
非空字符串 | 空值会解析为空环境变量,大多数镜像将其视为未设置,然后在运行时出现难以理解的失败 |
secret_value_too_long |
<computed> |
最多 %d 字节 | Vault KV v2 限制文档大小,过大的值会在写入时被拒绝,此时其他键可能已被存储 |
secret_vault_path_not_relative |
secrets[%d].vault_path |
不带前导“/”且不含“..”元素的相对路径,例如“deployments/codecheck-db” | 该路径会附加到租户自身的 Vault 命名空间;前导斜杠或“..”会使其逃逸并访问其他租户的材质 |
secret_vault_path_required |
secrets[%d].vault_path |
租户挂载点内的相对 Vault KV 路径,例如“deployments/codecheck-db” | 没有它,控制平面在调度时无物可解析,部署会以无容器的方式失败关闭。 |
secret_write_empty |
values |
要么是 {“values”:{“KEY”:“…”}},要么是 {“generate”:{“keys”:[“KEY”]}} | 一次不提供任何内容的写入会创建一个空文档,该文档在调度时会失败关闭。 |
secrets_version_negative |
secretsVersion |
新规范上为 0;此后只有轮换 API 会递增它。 | secretsVersion 是规范哈希所覆盖的轮换计数器——负值不可能由轮换产生,因此该规范并非来自此平台。 |
volume_access_mode_conflict |
<computed> |
每个复制/临时(ReadWriteOnce)卷只能有一个读写消费者 — 请以只读方式挂载,或先分离其他部署 | 在单写入器卷上同时进行两个写入操作会相互损坏;单写入器是该类别的保证 |
volume_binding_unavailable |
volumes |
移除分布式挂载,或在此控制平面上启用卷管理 | 在没有控制器的情况下允许挂载将存储一个无法解析的绑定 |
volume_class_not_wired |
<computed> |
类别为“临时”或“复制”的卷 | 存储类 %q 尚不可调度 — 其后端布线是里程碑 28 的后续部分,现在绑定将静默挂载节点本地存储,而非该类别所承诺的内容 |
volume_mount_shape |
volumes[%d].%s |
<computed> | <computed> |
volume_not_found |
<computed> |
此租户拥有的卷的类型化 ID — 使用 GET /api/v1/dvm/volumes 列出它们 | 该引用未解析为此租户拥有的卷 |
volume_not_ready |
<computed> |
处于“就绪”阶段的卷 | 挂载仍在物化(或已降级)的卷会将容器调度到可能尚未存在于目标节点上的存储 |
volume_primary_mismatch |
nodeId |
%q(卷的当前主副本),或省略 nodeId 并让放置策略跟随卷 | 在主节点之外进行读写挂载会写入副本,而下一次复制操作随后会覆盖该副本 — 这是数据丢失,而非偏好设置 |
volume_snapshot_not_found |
snapshots/{snap} |
此卷实际持有的快照记录的 ID —— 使用 GET /api/v1/tenants/{tenant}/volumes/{id}/snapshots 列出它们。该列表通常为空:快照创建被 volume_snapshot_not_implemented 拒绝,因此唯一可能存在的记录是在该拒绝发布之前写入的 | 由旧端点分发的 ID,代表从未启动的工作,因此此处的 404 通常不是丢失的记录,而是 volume_snapshot_not_implemented 在一次调用后出现 |
volume_snapshot_not_implemented |
<computed> |
此请求没有被接受的形式:该操作背后没有实现,因此任何请求体都不会成功。Odysseus 确实提供了以下功能,以及它们各自满足的需求 — (1) 为了在持有卷的节点丢失时保持卷的 DATA 可用,请为卷设置副本数:在“replicated”类的卷上执行 POST /api/v1/tenants/{tenant}/volumes/{id}/replicas {“node_id”:“<node>”},这会在另一个节点上保持一个实时副本,并在主节点失效时将其提升;(2) 为了保持部署 CONFIGURATION 的时间点副本 — 其规格、扩缩策略、金丝雀状态和修订历史 — 请使用 Backups 在风险变更后快速回滚 (POST /api/v1/backups {“deploymentName”:“<name>”}),或使用 Archives 获取可回溯更远的版本化历史 (GET /api/v1/archives/deployments/{name});两者都不会读取或写入卷上的任何字节;(3) 为了保持卷 BYTES 的时间点副本,请从拥有这些字节的工作负载内部获取 — 例如,一个挂载该卷并将其转储写入节点之外(例如写入“object”类的卷)的定时 cronjob 部署 — 因为只有该工作负载知道其数据何时是一致的 | 平台中没有任何组件执行分布式卷快照 — 没有节点代理程序暴露快照操作,也没有控制器消费快照请求 — 因此接受此请求将返回一个标识符,对应的工作永远不会开始、永远不会完成,并且永远无法获取 |
变更 — 服务器进行的您未发送的更改
Section titled “变更 — 服务器进行的您未发送的更改”静默变更是一种伪装成 200 响应的静默拒绝。下面的每一项都会在响应体和日志中公布,因此您无需自行发现对工作负载的更改。
Generated from Rule. Hand edits to this table are overwritten on the next build — change the Go doc comment, or the generator.