provisioning-code/schemas/lib/service_class.ncl

126 lines
5.2 KiB
Text

# Service classification — the prior that derives a component's compute envelope,
# QoS intent, stateful-ness, and preferred node placement. A component declares
# `class` and optionally overrides `resources`/`placement`; consumers (render
# pipeline, placement simulator) resolve effective values via `effective`.
{
# Coarse service taxonomy. Drives the default resource envelope and node affinity.
# 'gateway is the non-compute case: a declarative resource (e.g. a Cilium Gateway
# API object) with no pods — no resource envelope, no placement.
ServiceClass = [| 'controller, 'web, 'app_server, 'cache, 'datastore, 'storage_engine, 'edge, 'gateway |],
# Node role taxonomy. A component's class maps to the node classes it may run on.
NodeClass = [| 'control_plane, 'general, 'storage, 'edge |],
# Effective compute envelope — the complete output shape of `effective`.
ResourceSpec = {
cpu | { request | String, limit | String },
memory | { request | String, limit | String },
},
# Partial override a component may declare under `resources`. Any omitted field
# falls back to the class default during resolution.
ResourceOverride = {
cpu | { request | String, limit | String } | optional,
memory | { request | String, limit | String } | optional,
},
# Runtime health probe. `type` selects the k8s probe handler; `port` defaults to
# the component's first declared port when omitted by the renderer.
ProbeSpec = {
type | [| 'http, 'tcp, 'exec |] | doc "k8s probe handler" | default = 'http,
path | String | doc "httpGet path (http type)" | default = "/",
port | Number | optional,
command | Array String | doc "exec command (exec type)" | default = [],
initial_delay | Number | doc "initialDelaySeconds" | default = 30,
period | Number | doc "periodSeconds" | default = 15,
timeout | Number | doc "timeoutSeconds" | default = 5,
failure_threshold | Number | doc "failureThreshold" | default = 3,
},
# Placement policy. `mode` 'prefer = soft affinity (respects cost co-location);
# 'require = hard nodeSelector. `spread` = anti-affinity across nodes.
Placement = {
node_class | Array NodeClass | optional,
mode | [| 'prefer, 'require |] | doc "soft (prefer) vs hard (require) placement" | default = 'prefer,
spread | Bool | doc "anti-affinity across nodes" | default = true,
},
# Per-class default envelope. cpu/memory are k8s quantity strings; `qos` is the
# intended QoS class (k8s derives actual QoS from request==limit); `stateful`
# marks PVC-backed services; `node_class` is the preferred placement.
class_default = fun class =>
class
|> match {
'controller => {
cpu = { request = "50m", limit = "200m" },
memory = { request = "64Mi", limit = "128Mi" },
qos = 'burstable,
stateful = false,
node_class = ['control_plane, 'general],
},
'web => {
cpu = { request = "50m", limit = "500m" },
memory = { request = "128Mi", limit = "256Mi" },
qos = 'burstable,
stateful = false,
node_class = ['general],
},
'app_server => {
cpu = { request = "100m", limit = "1000m" },
memory = { request = "384Mi", limit = "768Mi" },
qos = 'burstable,
stateful = false,
node_class = ['general],
},
'cache => {
cpu = { request = "50m", limit = "500m" },
memory = { request = "64Mi", limit = "256Mi" },
qos = 'burstable,
stateful = false,
node_class = ['general, 'storage],
},
'datastore => {
cpu = { request = "200m", limit = "1000m" },
memory = { request = "512Mi", limit = "1Gi" },
qos = 'burstable,
stateful = true,
node_class = ['storage],
},
'storage_engine => {
cpu = { request = "100m", limit = "500m" },
memory = { request = "256Mi", limit = "512Mi" },
qos = 'guaranteed,
stateful = true,
node_class = ['storage],
},
'edge => {
cpu = { request = "50m", limit = "500m" },
memory = { request = "64Mi", limit = "256Mi" },
qos = 'burstable,
stateful = false,
node_class = ['edge, 'general],
},
'gateway => {
# Non-compute declarative resource (Gateway CR) — no pods, no footprint.
cpu = { request = "0", limit = "0" },
memory = { request = "0", limit = "0" },
qos = 'besteffort,
stateful = false,
node_class = [],
},
},
# Resolve effective resources + placement for a class with optional partial
# overrides ({cpu?, memory?, node_class?}). Override fields win; the rest fall
# back to the class default. Output conforms to ResourceSpec plus placement hints.
effective = fun class override =>
let d = class_default class in
{
cpu = if std.record.has_field "cpu" override then override.cpu else d.cpu,
memory = if std.record.has_field "memory" override then override.memory else d.memory,
qos = d.qos,
stateful = d.stateful,
node_class = if std.record.has_field "node_class" override then override.node_class else d.node_class,
},
}