Reject invalid deployment configuration early

This commit is contained in:
ms2k 2026-07-19 02:01:37 +09:00
commit 0e351c3640
4 changed files with 543 additions and 55 deletions

View file

@ -2,6 +2,36 @@
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- define "remora.validateValues" -}}
{{- if ge (int .Values.runtime.workerRenewSeconds) (int .Values.coordination.leaseTtlSeconds) -}}
{{- fail "runtime.workerRenewSeconds must be less than coordination.leaseTtlSeconds" -}}
{{- end -}}
{{- range $entry := splitList "," .Values.outbound.allowedSchemePorts -}}
{{- $parts := splitList ":" $entry -}}
{{- $port := int (index $parts 1) -}}
{{- if or (lt $port 1) (gt $port 65535) -}}
{{- fail "outbound.allowedSchemePorts contains a port outside 1..65535" -}}
{{- end -}}
{{- end -}}
{{- if and .Values.networkPolicy.enabled .Values.outbound.allowedCidrs -}}
{{- range $cidr := splitList "," .Values.outbound.allowedCidrs -}}
{{- if not (has $cidr $.Values.networkPolicy.externalEgressCidrs) -}}
{{- fail "every outbound.allowedCidrs entry must appear in networkPolicy.externalEgressCidrs" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if .Values.podDisruptionBudget.enabled -}}
{{- $minimum := toString .Values.podDisruptionBudget.minAvailable -}}
{{- if not (hasSuffix "%" $minimum) -}}
{{- range $component := list "api" "worker" "web" -}}
{{- if gt (int $minimum) (int (get $.Values $component).replicas) -}}
{{- fail "podDisruptionBudget.minAvailable cannot exceed a workload replica count" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- define "remora.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}

View file

@ -1,3 +1,4 @@
{{- include "remora.validateValues" . }}
apiVersion: v1
kind: ConfigMap
metadata:
@ -24,6 +25,7 @@ data:
REMORA_RESERVED_TARGET_SEGMENTS: {{ required "runtime.reservedTargetSegments is required" .Values.runtime.reservedTargetSegments | quote }}
REMORA_ADMISSION_POLICY_VERSION: {{ required "runtime.admissionPolicyVersion is required" .Values.runtime.admissionPolicyVersion | quote }}
REMORA_MAX_ATTEMPTS: {{ .Values.runtime.maxAttempts | quote }}
REMORA_WORKER_RENEW_SECONDS: {{ .Values.runtime.workerRenewSeconds | quote }}
REMORA_RECOVERY_INTERVAL_SECONDS: {{ .Values.runtime.recoveryIntervalSeconds | quote }}
REMORA_EVENT_ALLOWED_ORIGINS: {{ required "runtime.eventAllowedOrigins is required" .Values.runtime.eventAllowedOrigins | quote }}
REMORA_EVENT_RETENTION_SECONDS: {{ .Values.runtime.eventRetentionSeconds | quote }}

View file

@ -1,98 +1,423 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["api", "worker", "web", "database", "coordination", "oidc", "target", "vault"],
"additionalProperties": false,
"required": [
"nameOverride",
"fullnameOverride",
"imagePullSecrets",
"podDnsPolicy",
"podDnsConfig",
"api",
"worker",
"web",
"database",
"coordination",
"oidc",
"target",
"vault",
"runtime",
"outbound",
"ingress",
"networkPolicy",
"observability",
"podDisruptionBudget"
],
"properties": {
"api": {"$ref": "#/$defs/workload"},
"worker": {"$ref": "#/$defs/workload"},
"podDnsPolicy": {"enum": ["ClusterFirst", "ClusterFirstWithHostNet", "Default", "None"]},
"podDnsConfig": {"type": "object"},
"web": {
"allOf": [{"$ref": "#/$defs/workload"}],
"type": "object",
"required": ["publicOrigin", "oidcClientId"],
"properties": {
"publicOrigin": {"type": "string", "format": "uri"},
"oidcClientId": {"type": "string", "minLength": 1}
"nameOverride": {"$ref": "#/$defs/dnsLabelOrEmpty"},
"fullnameOverride": {"$ref": "#/$defs/dnsLabelOrEmpty"},
"imagePullSecrets": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["name"],
"properties": {"name": {"$ref": "#/$defs/nonEmpty"}}
}
},
"podDnsPolicy": {"enum": ["ClusterFirst", "ClusterFirstWithHostNet", "Default", "None"]},
"podDnsConfig": {"$ref": "#/$defs/podDnsConfig"},
"api": {"$ref": "#/$defs/workload"},
"worker": {"$ref": "#/$defs/workload"},
"web": {"$ref": "#/$defs/webWorkload"},
"database": {
"type": "object",
"required": ["urlSecret"],
"properties": {"urlSecret": {"$ref": "#/$defs/secretRef"}}
"additionalProperties": false,
"required": ["urlSecret", "maxApiConnections", "maxWorkerConnections"],
"properties": {
"urlSecret": {"$ref": "#/$defs/secretRef"},
"maxApiConnections": {"$ref": "#/$defs/positiveInteger"},
"maxWorkerConnections": {"$ref": "#/$defs/positiveInteger"}
}
},
"coordination": {
"type": "object",
"required": ["urlSecret", "namespace", "globalAttemptLimit"],
"additionalProperties": false,
"required": [
"urlSecret",
"namespace",
"globalAttemptLimit",
"leaseTtlSeconds",
"reserveTtlSeconds",
"progressTtlSeconds"
],
"properties": {
"urlSecret": {"$ref": "#/$defs/secretRef"},
"namespace": {"type": "string", "minLength": 1},
"globalAttemptLimit": {"type": "integer", "minimum": 1}
"namespace": {"$ref": "#/$defs/nonEmpty"},
"globalAttemptLimit": {"$ref": "#/$defs/positiveInteger"},
"leaseTtlSeconds": {"$ref": "#/$defs/positiveInteger"},
"reserveTtlSeconds": {"$ref": "#/$defs/positiveInteger"},
"progressTtlSeconds": {"$ref": "#/$defs/positiveInteger"}
}
},
"oidc": {
"type": "object",
"required": ["issuer", "audience", "resourceClient"],
"additionalProperties": false,
"required": ["issuer", "audience", "resourceClient", "projectRoleClaims"],
"properties": {
"issuer": {"type": "string", "format": "uri"},
"audience": {"type": "string", "minLength": 1},
"resourceClient": {"type": "string", "minLength": 1}
"issuer": {"$ref": "#/$defs/httpUrl"},
"audience": {"$ref": "#/$defs/nonEmpty"},
"resourceClient": {"$ref": "#/$defs/nonEmpty"},
"projectRoleClaims": {"$ref": "#/$defs/commaList"}
}
},
"target": {
"type": "object",
"required": ["storeId", "endpoint", "bucket", "exclusiveWriter", "credentialsSecret"],
"additionalProperties": false,
"required": [
"storeId",
"endpoint",
"bucket",
"region",
"root",
"defaultPrefix",
"exclusiveWriter",
"credentialsSecret"
],
"properties": {
"storeId": {"type": "string", "minLength": 1},
"endpoint": {"type": "string", "format": "uri"},
"bucket": {"type": "string", "minLength": 1},
"storeId": {"$ref": "#/$defs/nonEmpty"},
"endpoint": {"$ref": "#/$defs/httpUrl"},
"bucket": {
"type": "string",
"allOf": [
{"pattern": "^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$"},
{"not": {"pattern": "\\.\\."}}
]
},
"region": {"$ref": "#/$defs/nonEmpty"},
"root": {"type": "string", "maxLength": 1024},
"defaultPrefix": {"type": "string", "maxLength": 1024},
"exclusiveWriter": {"const": true},
"credentialsSecret": {
"type": "object",
"additionalProperties": false,
"required": ["name", "accessKey", "secretKey"],
"properties": {
"name": {"type": "string", "minLength": 1},
"accessKey": {"type": "string", "minLength": 1},
"secretKey": {"type": "string", "minLength": 1}
"name": {"$ref": "#/$defs/nonEmpty"},
"accessKey": {"$ref": "#/$defs/nonEmpty"},
"secretKey": {"$ref": "#/$defs/nonEmpty"}
}
}
}
},
"vault": {
"type": "object",
"required": ["address", "path", "apiRole", "workerRole"],
"additionalProperties": false,
"required": [
"image",
"address",
"path",
"kubernetesAuthMount",
"audience",
"apiRole",
"workerRole",
"caSecret"
],
"properties": {
"address": {"type": "string", "format": "uri"},
"path": {"type": "string", "minLength": 1},
"apiRole": {"type": "string", "minLength": 1},
"workerRole": {"type": "string", "minLength": 1}
}
}
},
"$defs": {
"workload": {
"type": "object",
"required": ["replicas", "image"],
"properties": {
"replicas": {"type": "integer", "minimum": 1},
"image": {
"image": {"$ref": "#/$defs/nonEmpty"},
"address": {"$ref": "#/$defs/httpUrl"},
"path": {"$ref": "#/$defs/nonEmpty"},
"kubernetesAuthMount": {"$ref": "#/$defs/nonEmpty"},
"audience": {"$ref": "#/$defs/nonEmpty"},
"apiRole": {"$ref": "#/$defs/nonEmpty"},
"workerRole": {"$ref": "#/$defs/nonEmpty"},
"caSecret": {
"type": "object",
"required": ["repository", "tag", "pullPolicy"],
"additionalProperties": false,
"required": ["name", "key"],
"properties": {
"repository": {"type": "string", "minLength": 1},
"tag": {"type": "string", "minLength": 1},
"digest": {"type": "string", "pattern": "^(|sha256:[0-9a-f]{64})$"},
"pullPolicy": {"enum": ["Always", "IfNotPresent", "Never"]}
"name": {"type": "string"},
"key": {"$ref": "#/$defs/nonEmpty"}
}
}
}
},
"runtime": {
"type": "object",
"additionalProperties": false,
"required": [
"reservedTargetSegments",
"admissionPolicyVersion",
"maxAttempts",
"workerLocalAttemptLimit",
"workerRenewSeconds",
"recoveryIntervalSeconds",
"eventAllowedOrigins",
"eventRetentionSeconds",
"logFilter"
],
"properties": {
"reservedTargetSegments": {"$ref": "#/$defs/commaList"},
"admissionPolicyVersion": {"$ref": "#/$defs/nonEmpty"},
"maxAttempts": {"$ref": "#/$defs/positiveInteger"},
"workerLocalAttemptLimit": {"$ref": "#/$defs/positiveInteger"},
"workerRenewSeconds": {"$ref": "#/$defs/positiveInteger"},
"recoveryIntervalSeconds": {"$ref": "#/$defs/positiveInteger"},
"eventAllowedOrigins": {"$ref": "#/$defs/httpUrlList"},
"eventRetentionSeconds": {"$ref": "#/$defs/positiveInteger"},
"logFilter": {"$ref": "#/$defs/nonEmpty"}
}
},
"outbound": {
"type": "object",
"additionalProperties": false,
"required": ["allowedSchemePorts", "allowedCidrs", "maxRedirects"],
"properties": {
"allowedSchemePorts": {
"type": "string",
"pattern": "^[a-z][a-z0-9+.-]*:[1-9][0-9]{0,4}(,[a-z][a-z0-9+.-]*:[1-9][0-9]{0,4})*$"
},
"allowedCidrs": {"$ref": "#/$defs/cidrListOrEmpty"},
"maxRedirects": {"type": "integer", "minimum": 0, "maximum": 100}
}
},
"ingress": {
"type": "object",
"additionalProperties": false,
"required": ["enabled", "className", "annotations", "host", "tlsSecretName"],
"properties": {
"enabled": {"type": "boolean"},
"className": {"type": "string"},
"annotations": {"$ref": "#/$defs/stringMap"},
"host": {
"type": "string",
"pattern": "^[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$"
},
"tlsSecretName": {"type": "string"}
}
},
"networkPolicy": {
"type": "object",
"additionalProperties": false,
"required": [
"enabled",
"ingressNamespaceSelector",
"ingressPodSelector",
"monitoringNamespaceSelector",
"monitoringPodSelector",
"dnsPodLabels",
"dependencyPodLabels",
"externalEgressCidrs",
"allowPublicSourceEgress"
],
"properties": {
"enabled": {"type": "boolean"},
"ingressNamespaceSelector": {"$ref": "#/$defs/labelSelector"},
"ingressPodSelector": {"$ref": "#/$defs/labelSelector"},
"monitoringNamespaceSelector": {"$ref": "#/$defs/labelSelector"},
"monitoringPodSelector": {"$ref": "#/$defs/labelSelector"},
"dnsPodLabels": {"$ref": "#/$defs/labelMapList"},
"dependencyPodLabels": {"$ref": "#/$defs/labelMapList"},
"externalEgressCidrs": {
"type": "array",
"uniqueItems": true,
"items": {"$ref": "#/$defs/cidr"}
},
"allowPublicSourceEgress": {"type": "boolean"}
}
},
"observability": {
"type": "object",
"additionalProperties": false,
"required": ["serviceMonitor", "prometheusRule"],
"properties": {
"serviceMonitor": {
"type": "object",
"additionalProperties": false,
"required": ["enabled", "interval"],
"properties": {
"enabled": {"type": "boolean"},
"interval": {"type": "string", "pattern": "^[1-9][0-9]*(ms|s|m|h)$"}
}
},
"prometheusRule": {
"type": "object",
"additionalProperties": false,
"required": ["enabled"],
"properties": {"enabled": {"type": "boolean"}}
}
}
},
"podDisruptionBudget": {
"type": "object",
"additionalProperties": false,
"required": ["enabled", "minAvailable"],
"properties": {
"enabled": {"type": "boolean"},
"minAvailable": {
"oneOf": [
{"type": "integer", "minimum": 0},
{"type": "string", "pattern": "^(100|[0-9]{1,2})%$"}
]
}
}
}
},
"$defs": {
"nonEmpty": {"type": "string", "minLength": 1},
"positiveInteger": {"type": "integer", "minimum": 1, "maximum": 2147483647},
"dnsLabelOrEmpty": {
"type": "string",
"pattern": "^(|[a-z0-9](?:[-a-z0-9]*[a-z0-9])?)$",
"maxLength": 63
},
"httpUrl": {"type": "string", "pattern": "^https?://[^[:space:]]+$"},
"httpUrlList": {
"type": "string",
"pattern": "^https?://[^,[:space:]]+(,https?://[^,[:space:]]+)*$"
},
"commaList": {"type": "string", "pattern": "^[^,[:space:]]+(,[^,[:space:]]+)*$"},
"cidr": {"type": "string", "pattern": "^[0-9A-Fa-f:.]+/[0-9]{1,3}$"},
"cidrListOrEmpty": {
"type": "string",
"pattern": "^(|[0-9A-Fa-f:.]+/[0-9]{1,3}(,[0-9A-Fa-f:.]+/[0-9]{1,3})*)$"
},
"stringMap": {
"type": "object",
"additionalProperties": {"type": "string"}
},
"labelMapList": {
"type": "array",
"items": {"$ref": "#/$defs/stringMap"}
},
"labelSelector": {
"type": "object",
"additionalProperties": false,
"properties": {
"matchLabels": {"$ref": "#/$defs/stringMap"},
"matchExpressions": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["key", "operator"],
"properties": {
"key": {"$ref": "#/$defs/nonEmpty"},
"operator": {"enum": ["In", "NotIn", "Exists", "DoesNotExist"]},
"values": {
"type": "array",
"items": {"type": "string"}
}
}
}
}
}
},
"podDnsConfig": {
"type": "object",
"additionalProperties": false,
"properties": {
"nameservers": {
"type": "array",
"maxItems": 3,
"items": {"type": "string", "minLength": 1}
},
"searches": {
"type": "array",
"maxItems": 32,
"items": {"type": "string", "minLength": 1}
},
"options": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["name"],
"properties": {
"name": {"$ref": "#/$defs/nonEmpty"},
"value": {"type": "string"}
}
}
}
}
},
"image": {
"type": "object",
"additionalProperties": false,
"required": ["repository", "tag", "digest", "pullPolicy"],
"properties": {
"repository": {"$ref": "#/$defs/nonEmpty"},
"tag": {"$ref": "#/$defs/nonEmpty"},
"digest": {"type": "string", "pattern": "^(|sha256:[0-9a-f]{64})$"},
"pullPolicy": {"enum": ["Always", "IfNotPresent", "Never"]}
}
},
"resources": {
"type": "object",
"additionalProperties": false,
"required": ["requests", "limits"],
"properties": {
"requests": {"$ref": "#/$defs/resourceMap"},
"limits": {"$ref": "#/$defs/resourceMap"}
}
},
"resourceMap": {
"type": "object",
"additionalProperties": {"type": ["string", "number"]}
},
"workload": {
"type": "object",
"additionalProperties": false,
"required": ["replicas", "image", "resources"],
"properties": {
"replicas": {"$ref": "#/$defs/positiveInteger"},
"image": {"$ref": "#/$defs/image"},
"resources": {"$ref": "#/$defs/resources"}
}
},
"webWorkload": {
"type": "object",
"additionalProperties": false,
"required": [
"replicas",
"image",
"publicOrigin",
"apiUrl",
"eventsUrl",
"oidcClientId",
"oidcScope",
"cspConnectSrc",
"resources"
],
"properties": {
"replicas": {"$ref": "#/$defs/positiveInteger"},
"image": {"$ref": "#/$defs/image"},
"publicOrigin": {"$ref": "#/$defs/httpUrl"},
"apiUrl": {"type": "string", "pattern": "^(https?://|/).+"},
"eventsUrl": {"type": "string", "pattern": "^(|https?://.+|wss?://.+|/.+)$"},
"oidcClientId": {"$ref": "#/$defs/nonEmpty"},
"oidcScope": {"$ref": "#/$defs/nonEmpty"},
"cspConnectSrc": {"$ref": "#/$defs/nonEmpty"},
"resources": {"$ref": "#/$defs/resources"}
}
},
"secretRef": {
"type": "object",
"additionalProperties": false,
"required": ["name", "key"],
"properties": {
"name": {"type": "string", "minLength": 1},
"key": {"type": "string", "minLength": 1}
"name": {"$ref": "#/$defs/nonEmpty"},
"key": {"$ref": "#/$defs/nonEmpty"}
}
}
}

View file

@ -109,6 +109,7 @@ runtime:
admissionPolicyVersion: deployment-v1
maxAttempts: 3
workerLocalAttemptLimit: 4
workerRenewSeconds: 10
recoveryIntervalSeconds: 2
eventAllowedOrigins: https://remora.example.com
eventRetentionSeconds: 3600