feat: render workbench redis cache workload

This commit is contained in:
lyon
2026-06-22 12:36:56 +08:00
parent 68a3349003
commit b45104076d
+116 -3
View File
@@ -1010,7 +1010,7 @@ function runtimeStoreEnvForProfile(deploy, profile, serviceId) {
function workbenchRuntimeRedisEnvForProfile(deploy, profile, serviceId) {
if (!isRuntimeLane(profile) || serviceId !== "hwlab-workbench-runtime") return {};
const redis = runtimeLaneConfig(deploy, profile)?.workbenchRuntime?.cache?.redis;
const redis = workbenchRuntimeRedisConfigForProfile(deploy, profile);
if (!redis || typeof redis !== "object" || Array.isArray(redis)) return {};
const env = {
HWLAB_WORKBENCH_CACHE_REDIS_ENABLED: booleanEnv(Boolean(redis.enabled))
@@ -1049,6 +1049,28 @@ function workbenchRuntimeRedisEnvForProfile(deploy, profile, serviceId) {
return env;
}
function workbenchRuntimeRedisConfigForProfile(deploy, profile) {
if (!isRuntimeLane(profile)) return null;
const label = `deploy.lanes.${profile}.workbenchRuntime.cache.redis`;
const redis = runtimeLaneConfig(deploy, profile)?.workbenchRuntime?.cache?.redis;
if (!redis || redis.enabled !== true) return null;
configObject(redis, label);
const namespace = requiredConfigString(redis, "namespace", label);
assert.equal(namespace, namespaceNameForProfile(profile), `${label}.namespace must match ${namespaceNameForProfile(profile)}`);
const serviceName = requiredConfigString(redis, "serviceName", label);
const image = requiredConfigString(redis, "image", label);
const port = requiredConfigPositiveInteger(redis, "port", label);
assert.ok(port <= 65535, `${label}.port must be a valid TCP port`);
const resources = configObject(redis.resources, `${label}.resources`);
configObject(resources.requests, `${label}.resources.requests`);
configObject(resources.limits, `${label}.resources.limits`);
const memoryPolicy = configObject(redis.memoryPolicy, `${label}.memoryPolicy`);
assert.equal(memoryPolicy.persistence, "disabled", `${label}.memoryPolicy.persistence must be disabled`);
requiredConfigString(memoryPolicy, "maxMemory", `${label}.memoryPolicy`);
requiredConfigString(memoryPolicy, "eviction", `${label}.memoryPolicy`);
return redis;
}
function booleanEnv(value) {
return value ? "1" : "0";
}
@@ -5149,12 +5171,101 @@ function v02OpenFgaManifest({ profile = "v02", source }) {
};
}
function runtimeKustomization({ profile = "dev", includeDeviceAgent = profile === "dev", externalPostgres = false } = {}) {
function workbenchRuntimeRedisConf(config) {
return [
"save \"\"",
"appendonly no",
`maxmemory ${config.memoryPolicy.maxMemory}`,
`maxmemory-policy ${config.memoryPolicy.eviction}`,
""
].join("\n");
}
function workbenchRuntimeRedisManifest({ profile = "v03", config, source }) {
const namespace = config.namespace;
const name = config.serviceName;
const port = config.port;
const selector = { "app.kubernetes.io/name": name };
const labels = {
...selector,
"app.kubernetes.io/part-of": "hwlab",
"hwlab.pikastech.local/cache-role": "workbench-derived-read",
"hwlab.pikastech.local/component": "workbench-runtime-cache",
"hwlab.pikastech.local/environment": profile,
"hwlab.pikastech.local/gitops-target": profile,
"hwlab.pikastech.local/profile": profile,
"hwlab.pikastech.local/service-id": name,
"hwlab.pikastech.local/source-commit": source.full
};
const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/gitops-render.mjs" };
const runtimeAnnotations = { ...annotations, "argocd.argoproj.io/sync-wave": "2" };
const templateLabels = { ...labels, ...selector };
return {
apiVersion: "v1",
kind: "List",
items: [
{
apiVersion: "v1",
kind: "ConfigMap",
metadata: { name: `${name}-config`, namespace, labels, annotations: runtimeAnnotations },
data: { "redis.conf": workbenchRuntimeRedisConf(config) }
},
{
apiVersion: "apps/v1",
kind: "Deployment",
metadata: { name, namespace, labels, annotations: runtimeAnnotations },
spec: {
replicas: 1,
selector: { matchLabels: selector },
template: {
metadata: { labels: templateLabels, annotations: runtimeAnnotations },
spec: {
containers: [{
name: "redis",
image: config.image,
imagePullPolicy: "IfNotPresent",
args: ["redis-server", "/usr/local/etc/redis/redis.conf"],
ports: [{ name: "redis", containerPort: port }],
readinessProbe: { exec: { command: ["redis-cli", "-p", String(port), "ping"] }, initialDelaySeconds: 3, periodSeconds: 10, timeoutSeconds: 2, failureThreshold: 3 },
livenessProbe: { exec: { command: ["redis-cli", "-p", String(port), "ping"] }, initialDelaySeconds: 15, periodSeconds: 20, timeoutSeconds: 3, failureThreshold: 3 },
resources: cloneJson(config.resources),
volumeMounts: [{ name: "config", mountPath: "/usr/local/etc/redis", readOnly: true }]
}],
volumes: [{ name: "config", configMap: { name: `${name}-config` } }]
}
}
}
},
{
apiVersion: "v1",
kind: "Service",
metadata: { name, namespace, labels, annotations: runtimeAnnotations },
spec: { type: "ClusterIP", selector, ports: [{ name: "redis", port, targetPort: "redis", protocol: "TCP" }] }
},
{
apiVersion: "networking.k8s.io/v1",
kind: "NetworkPolicy",
metadata: { name: `${name}-ingress`, namespace, labels, annotations: runtimeAnnotations },
spec: {
podSelector: { matchLabels: selector },
policyTypes: ["Ingress"],
ingress: [{
from: [{ podSelector: { matchLabels: { "app.kubernetes.io/name": "hwlab-workbench-runtime" } } }],
ports: [{ protocol: "TCP", port }]
}]
}
}
]
};
}
function runtimeKustomization({ profile = "dev", includeDeviceAgent = profile === "dev", externalPostgres = false, workbenchRedis = false } = {}) {
const resources = ["namespace.yaml", "services.yaml", "health-contract.yaml", "workloads.yaml", "deepseek-proxy.yaml"];
if (!isRuntimeLane(profile)) resources.splice(1, 0, "code-agent-codex-config.yaml");
if (isRuntimeLane(profile)) {
if (externalPostgres) resources.push("external-postgres.yaml");
else resources.push("postgres.yaml");
if (workbenchRedis) resources.push("workbench-redis.yaml");
resources.push("openfga.yaml", "observability.yaml");
}
if (includeDeviceAgent) resources.push("device-agent-71-freq.yaml");
@@ -5289,6 +5400,7 @@ async function plannedFiles(args) {
const namespace = namespaceNameForProfile(profile);
const includeDeviceAgent = profile === "dev";
const externalPostgres = externalPostgresConfigForLane(deploy, profile);
const workbenchRedis = workbenchRuntimeRedisConfigForProfile(deploy, profile);
const profileLabels = { ...baseLabels, "hwlab.pikastech.local/environment": runtimeLabelForProfile(profile), "hwlab.pikastech.local/profile": runtimeLabelForProfile(profile) };
const runtimeNamespace = cloneJson(namespaceTemplate);
runtimeNamespace.metadata.name = namespace;
@@ -5296,7 +5408,7 @@ async function plannedFiles(args) {
annotate(runtimeNamespace.metadata, annotations);
const runtimePath = runtimePathForProfile(profile);
const endpoints = profileEndpoint(args, profile);
putJson(`${runtimePath}/kustomization.yaml`, runtimeKustomization({ profile, includeDeviceAgent, externalPostgres: Boolean(externalPostgres) }));
putJson(`${runtimePath}/kustomization.yaml`, runtimeKustomization({ profile, includeDeviceAgent, externalPostgres: Boolean(externalPostgres), workbenchRedis: Boolean(workbenchRedis) }));
putJson(`${runtimePath}/namespace.yaml`, runtimeNamespace);
if (!isRuntimeLane(profile)) putJson(`${runtimePath}/code-agent-codex-config.yaml`, transformListNamespace(config, namespace, profileLabels, annotations));
putJson(`${runtimePath}/services.yaml`, transformServices({ services, namespace, labels: profileLabels, annotations, profile, deploy }));
@@ -5305,6 +5417,7 @@ async function plannedFiles(args) {
putJson(`${runtimePath}/deepseek-proxy.yaml`, deepSeekProxyManifest({ profile, source, sourceBranch: args.sourceBranch, sourceRepo: args.sourceRepo, gitReadUrl: args.gitReadUrl, deploy, registryPrefix: args.registryPrefix, catalog: artifactCatalog, useDeployImages: args.useDeployImages, metricsSidecarSha256 }));
if (isRuntimeLane(profile) && externalPostgres) putJson(`${runtimePath}/external-postgres.yaml`, externalPostgresManifest({ profile, config: externalPostgres, source }));
if (isRuntimeLane(profile) && !externalPostgres) putJson(`${runtimePath}/postgres.yaml`, v02PostgresManifest({ profile, migrationSql, source }));
if (workbenchRedis) putJson(`${runtimePath}/workbench-redis.yaml`, workbenchRuntimeRedisManifest({ profile, config: workbenchRedis, source }));
if (isRuntimeLane(profile)) putJson(`${runtimePath}/openfga.yaml`, v02OpenFgaManifest({ profile, source }));
if (isRuntimeLane(profile)) putJson(`${runtimePath}/observability.yaml`, observabilityManifest({ deploy, profile, namespace, labels: profileLabels, annotations, metricsSidecarScript }));
if (includeDeviceAgent) putJson(`${runtimePath}/device-agent-71-freq.yaml`, deviceAgent71FreqManifest({ profile, source, registryPrefix: args.registryPrefix, catalog: artifactCatalog, useDeployImages: args.useDeployImages }));