feat: add workbench redis cache runtime config

This commit is contained in:
lyon
2026-06-22 12:27:32 +08:00
parent d70b66317f
commit 46014fe782
3 changed files with 203 additions and 2 deletions
+118
View File
@@ -337,6 +337,7 @@
"workbench": { "$ref": "#/$defs/workbenchUiPolicy" },
"observability": { "$ref": "#/$defs/runtimeObservabilityPolicy" },
"runtimeStore": { "$ref": "#/$defs/runtimeStore" },
"workbenchRuntime": { "$ref": "#/$defs/workbenchRuntime" },
"sourceRepo": { "type": "string" },
"envReuseServices": {
"type": "array",
@@ -387,6 +388,123 @@
},
"additionalProperties": false
},
"workbenchRuntime": {
"type": "object",
"properties": {
"cache": { "$ref": "#/$defs/workbenchRuntimeCache" }
},
"additionalProperties": false
},
"workbenchRuntimeCache": {
"type": "object",
"properties": {
"redis": { "$ref": "#/$defs/workbenchRuntimeRedisCache" }
},
"additionalProperties": false
},
"workbenchRuntimeRedisCache": {
"type": "object",
"required": ["enabled", "namespace", "serviceName", "port", "image", "resources", "memoryPolicy", "ttl", "limits", "timeouts", "metrics", "disableSwitchEnv", "unavailableBehavior"],
"properties": {
"enabled": { "type": "boolean" },
"namespace": { "type": "string", "pattern": "^hwlab-v[0-9]{2,}$" },
"serviceName": {
"type": "string",
"minLength": 1,
"maxLength": 63,
"pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$"
},
"port": { "type": "integer", "minimum": 1, "maximum": 65535 },
"image": { "type": "string", "minLength": 1 },
"resources": { "$ref": "#/$defs/workbenchRuntimeRedisResources" },
"memoryPolicy": { "$ref": "#/$defs/workbenchRuntimeRedisMemoryPolicy" },
"ttl": { "$ref": "#/$defs/workbenchRuntimeRedisTtl" },
"limits": { "$ref": "#/$defs/workbenchRuntimeRedisLimits" },
"timeouts": { "$ref": "#/$defs/workbenchRuntimeRedisTimeouts" },
"metrics": { "$ref": "#/$defs/workbenchRuntimeRedisMetrics" },
"disableSwitchEnv": { "type": "string", "pattern": "^[A-Z0-9_]+$" },
"unavailableBehavior": { "$ref": "#/$defs/workbenchRuntimeRedisUnavailableBehavior" }
},
"additionalProperties": false
},
"workbenchRuntimeRedisResources": {
"type": "object",
"required": ["requests", "limits"],
"properties": {
"requests": { "$ref": "#/$defs/workbenchRuntimeResourcePair" },
"limits": { "$ref": "#/$defs/workbenchRuntimeResourcePair" }
},
"additionalProperties": false
},
"workbenchRuntimeResourcePair": {
"type": "object",
"required": ["cpu", "memory"],
"properties": {
"cpu": { "type": "string", "minLength": 1 },
"memory": { "type": "string", "minLength": 1 }
},
"additionalProperties": false
},
"workbenchRuntimeRedisMemoryPolicy": {
"type": "object",
"required": ["persistence", "maxMemory", "eviction"],
"properties": {
"persistence": { "type": "string", "enum": ["disabled"] },
"maxMemory": { "type": "string", "minLength": 1 },
"eviction": { "type": "string", "minLength": 1 }
},
"additionalProperties": false
},
"workbenchRuntimeRedisTtl": {
"type": "object",
"required": ["sessionsSummaryMs", "terminalTurnMs", "terminalTracePageMs", "runningPageMs"],
"properties": {
"sessionsSummaryMs": { "type": "integer", "minimum": 0 },
"terminalTurnMs": { "type": "integer", "minimum": 0 },
"terminalTracePageMs": { "type": "integer", "minimum": 0 },
"runningPageMs": { "type": "integer", "minimum": 0 }
},
"additionalProperties": false
},
"workbenchRuntimeRedisLimits": {
"type": "object",
"required": ["maxKeyBytes", "maxPayloadBytes"],
"properties": {
"maxKeyBytes": { "type": "integer", "minimum": 1 },
"maxPayloadBytes": { "type": "integer", "minimum": 1 }
},
"additionalProperties": false
},
"workbenchRuntimeRedisTimeouts": {
"type": "object",
"required": ["connectMs", "operationMs"],
"properties": {
"connectMs": { "type": "integer", "minimum": 1 },
"operationMs": { "type": "integer", "minimum": 1 }
},
"additionalProperties": false
},
"workbenchRuntimeRedisMetrics": {
"type": "object",
"required": ["labels"],
"properties": {
"labels": {
"type": "object",
"additionalProperties": { "type": "string", "minLength": 1 }
}
},
"additionalProperties": false
},
"workbenchRuntimeRedisUnavailableBehavior": {
"type": "object",
"required": ["livenessFailure", "readMode", "fallbackStateSource"],
"properties": {
"livenessFailure": { "type": "boolean" },
"readMode": { "type": "string", "enum": ["db-or-degraded"] },
"fallbackStateSource": { "type": "boolean" }
},
"additionalProperties": false
},
"workbenchUiPolicy": {
"type": "object",
"properties": {
+40
View File
@@ -306,6 +306,46 @@ lanes:
queryRetryMaxAttempts: 5
queryRetryInitialDelayMs: 250
queryRetryMaxDelayMs: 5000
workbenchRuntime:
cache:
redis:
enabled: true
namespace: hwlab-v03
serviceName: hwlab-workbench-redis
port: 6379
image: redis:7.2-alpine
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 250m
memory: 256Mi
memoryPolicy:
persistence: disabled
maxMemory: 192mb
eviction: allkeys-lru
ttl:
sessionsSummaryMs: 30000
terminalTurnMs: 300000
terminalTracePageMs: 300000
runningPageMs: 5000
limits:
maxKeyBytes: 512
maxPayloadBytes: 262144
timeouts:
connectMs: 200
operationMs: 100
metrics:
labels:
lane: v03
service: hwlab-workbench-runtime
cacheKeyClass: workbench-derived-read
disableSwitchEnv: HWLAB_WORKBENCH_CACHE_REDIS_ENABLED
unavailableBehavior:
livenessFailure: false
readMode: db-or-degraded
fallbackStateSource: false
envReuseServices:
- hwlab-cloud-api
- hwlab-workbench-runtime
+45 -2
View File
@@ -938,7 +938,8 @@ function deployServicesForProfile(deploy, profile) {
copy.env = mergeEnvMaps(
copy.env,
serviceDeclarationEnvForProfile(deploy, profile, service.serviceId),
runtimeStoreEnvForProfile(deploy, profile, service.serviceId)
runtimeStoreEnvForProfile(deploy, profile, service.serviceId),
workbenchRuntimeRedisEnvForProfile(deploy, profile, service.serviceId)
);
return [service.serviceId, copy];
}));
@@ -950,7 +951,8 @@ function deployServicesForProfile(deploy, profile) {
serviceId: override.serviceId,
env: mergeEnvMaps(
serviceDeclarationEnvForProfile(deploy, profile, override.serviceId),
runtimeStoreEnvForProfile(deploy, profile, override.serviceId)
runtimeStoreEnvForProfile(deploy, profile, override.serviceId),
workbenchRuntimeRedisEnvForProfile(deploy, profile, override.serviceId)
)
};
services.set(override.serviceId, {
@@ -1006,6 +1008,47 @@ function runtimeStoreEnvForProfile(deploy, profile, serviceId) {
return env;
}
function workbenchRuntimeRedisEnvForProfile(deploy, profile, serviceId) {
if (!isRuntimeLane(profile) || serviceId !== "hwlab-workbench-runtime") return {};
const redis = runtimeLaneConfig(deploy, profile)?.workbenchRuntime?.cache?.redis;
if (!redis || typeof redis !== "object" || Array.isArray(redis)) return {};
const env = {
HWLAB_WORKBENCH_CACHE_REDIS_ENABLED: booleanEnv(Boolean(redis.enabled))
};
const namespace = typeof redis.namespace === "string" ? redis.namespace.trim() : "";
const serviceName = typeof redis.serviceName === "string" ? redis.serviceName.trim() : "";
const port = Number.isInteger(redis.port) ? redis.port : null;
if (namespace) env.HWLAB_WORKBENCH_CACHE_REDIS_NAMESPACE = namespace;
if (serviceName) env.HWLAB_WORKBENCH_CACHE_REDIS_SERVICE_NAME = serviceName;
if (port !== null) env.HWLAB_WORKBENCH_CACHE_REDIS_PORT = String(port);
if (namespace && serviceName && port !== null) {
env.HWLAB_WORKBENCH_CACHE_REDIS_URL = `redis://${serviceName}.${namespace}.svc.cluster.local:${port}/0`;
}
const ttl = redis.ttl ?? {};
if (Number.isInteger(ttl.sessionsSummaryMs)) env.HWLAB_WORKBENCH_CACHE_REDIS_TTL_SESSIONS_SUMMARY_MS = String(ttl.sessionsSummaryMs);
if (Number.isInteger(ttl.terminalTurnMs)) env.HWLAB_WORKBENCH_CACHE_REDIS_TTL_TERMINAL_TURN_MS = String(ttl.terminalTurnMs);
if (Number.isInteger(ttl.terminalTracePageMs)) env.HWLAB_WORKBENCH_CACHE_REDIS_TTL_TERMINAL_TRACE_PAGE_MS = String(ttl.terminalTracePageMs);
if (Number.isInteger(ttl.runningPageMs)) env.HWLAB_WORKBENCH_CACHE_REDIS_TTL_RUNNING_PAGE_MS = String(ttl.runningPageMs);
const limits = redis.limits ?? {};
if (Number.isInteger(limits.maxKeyBytes)) env.HWLAB_WORKBENCH_CACHE_REDIS_MAX_KEY_BYTES = String(limits.maxKeyBytes);
if (Number.isInteger(limits.maxPayloadBytes)) env.HWLAB_WORKBENCH_CACHE_REDIS_MAX_PAYLOAD_BYTES = String(limits.maxPayloadBytes);
const timeouts = redis.timeouts ?? {};
if (Number.isInteger(timeouts.connectMs)) env.HWLAB_WORKBENCH_CACHE_REDIS_CONNECT_TIMEOUT_MS = String(timeouts.connectMs);
if (Number.isInteger(timeouts.operationMs)) env.HWLAB_WORKBENCH_CACHE_REDIS_OPERATION_TIMEOUT_MS = String(timeouts.operationMs);
const labels = redis.metrics?.labels;
if (labels && typeof labels === "object" && !Array.isArray(labels)) {
env.HWLAB_WORKBENCH_CACHE_REDIS_METRICS_LABELS = JSON.stringify(labels);
}
const behavior = redis.unavailableBehavior ?? {};
if (typeof behavior.livenessFailure === "boolean") env.HWLAB_WORKBENCH_CACHE_REDIS_UNAVAILABLE_LIVENESS_FAILURE = booleanEnv(behavior.livenessFailure);
if (typeof behavior.readMode === "string") env.HWLAB_WORKBENCH_CACHE_REDIS_UNAVAILABLE_READ_MODE = behavior.readMode;
if (typeof behavior.fallbackStateSource === "boolean") env.HWLAB_WORKBENCH_CACHE_REDIS_UNAVAILABLE_FALLBACK_STATE_SOURCE = booleanEnv(behavior.fallbackStateSource);
if (typeof redis.disableSwitchEnv === "string" && redis.disableSwitchEnv.trim()) {
env[redis.disableSwitchEnv.trim()] = booleanEnv(Boolean(redis.enabled));
}
return env;
}
function booleanEnv(value) {
return value ? "1" : "0";
}