@intentius/chant-lexicon-k8s 0.124.31 → 0.124.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/apps.js +18 -0
- package/dist/actions/batch.js +6 -0
- package/dist/actions/core.js +48 -0
- package/dist/actions/index.js +13 -0
- package/dist/actions/networking.js +11 -0
- package/dist/actions/rbac.js +8 -0
- package/dist/api/classify.js +123 -0
- package/dist/api/connect.js +62 -0
- package/dist/api/fake-cluster.js +175 -0
- package/dist/api/operation-surface.d.ts.map +1 -1
- package/dist/api/operation-surface.js +109 -0
- package/dist/api/owner-chain.js +100 -0
- package/dist/api/sweep-types.js +35 -0
- package/dist/behaviour-kinds.js +82 -0
- package/dist/codegen/docs-cli.js +2 -0
- package/dist/codegen/docs.js +146 -0
- package/dist/codegen/generate-cli.js +30 -0
- package/dist/codegen/generate-lexicon.js +74 -0
- package/dist/codegen/generate-list-map-keys.js +88 -0
- package/dist/codegen/generate-operations.js +53 -0
- package/dist/codegen/generate-typescript.js +124 -0
- package/dist/codegen/generate.js +173 -0
- package/dist/codegen/naming.js +174 -0
- package/dist/codegen/package.js +36 -0
- package/dist/codegen/patches.js +88 -0
- package/dist/codegen/typecheck.js +4 -0
- package/dist/codegen/versions.js +41 -0
- package/dist/components/argo-app.js +51 -0
- package/dist/components/builders.js +22 -0
- package/dist/components/capability-plugin.js +31 -0
- package/dist/components/flux-reconcile.js +61 -0
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +18 -0
- package/dist/components/kubectl-apply.d.ts +83 -1
- package/dist/components/kubectl-apply.d.ts.map +1 -1
- package/dist/components/kubectl-apply.js +274 -0
- package/dist/components/kustomize-apply.js +25 -0
- package/dist/composites/adot-collector.js +184 -0
- package/dist/composites/agic-ingress.js +85 -0
- package/dist/composites/aks-external-dns-agent.js +146 -0
- package/dist/composites/alb-ingress.js +86 -0
- package/dist/composites/argo-app.js +199 -0
- package/dist/composites/autoscaled-service.js +174 -0
- package/dist/composites/azure-disk-storage-class.js +47 -0
- package/dist/composites/azure-file-storage-class.js +48 -0
- package/dist/composites/azure-monitor-collector.js +176 -0
- package/dist/composites/batch-job.js +139 -0
- package/dist/composites/catalog.js +4110 -0
- package/dist/composites/cockroachdb-cluster.js +371 -0
- package/dist/composites/cockroachdb-region-stack.js +369 -0
- package/dist/composites/config-connector-context.js +40 -0
- package/dist/composites/configured-app.js +135 -0
- package/dist/composites/cron-schedule.js +38 -0
- package/dist/composites/cron-workload.js +109 -0
- package/dist/composites/ebs-storage-class.js +54 -0
- package/dist/composites/efs-storage-class.js +46 -0
- package/dist/composites/external-dns-agent.js +122 -0
- package/dist/composites/filestore-storage-class.js +49 -0
- package/dist/composites/fluent-bit-agent.js +157 -0
- package/dist/composites/flux-app.js +156 -0
- package/dist/composites/gce-ingress.js +83 -0
- package/dist/composites/gce-pd-storage-class.js +49 -0
- package/dist/composites/gke-external-dns-agent.js +129 -0
- package/dist/composites/gke-fluent-bit-agent.js +165 -0
- package/dist/composites/gke-gateway.js +91 -0
- package/dist/composites/gke-otel-collector.js +94 -0
- package/dist/composites/index.js +51 -0
- package/dist/composites/inference-service.js +76 -0
- package/dist/composites/irsa-service-account.js +77 -0
- package/dist/composites/member-defaults.js +14 -0
- package/dist/composites/metrics-server.js +181 -0
- package/dist/composites/model.js +92 -0
- package/dist/composites/monitored-service.js +152 -0
- package/dist/composites/namespace-env.js +114 -0
- package/dist/composites/network-isolated-app.js +124 -0
- package/dist/composites/node-agent.js +149 -0
- package/dist/composites/operator-stack.js +268 -0
- package/dist/composites/otel-collector-agent.js +142 -0
- package/dist/composites/otel-collector-gateway.js +216 -0
- package/dist/composites/otel-collector-node.js +176 -0
- package/dist/composites/otel-collector-rbac.js +115 -0
- package/dist/composites/otel-collector-shape.js +128 -0
- package/dist/composites/otel-collector.js +115 -0
- package/dist/composites/otel-operator-collector.js +123 -0
- package/dist/composites/ray-cluster.js +378 -0
- package/dist/composites/ray-job.js +136 -0
- package/dist/composites/ray-service.js +178 -0
- package/dist/composites/secure-ingress.js +100 -0
- package/dist/composites/security-context.js +1 -0
- package/dist/composites/sidecar-app.js +105 -0
- package/dist/composites/stateful-app.js +111 -0
- package/dist/composites/vllm-serving-runtime.js +114 -0
- package/dist/composites/web-app.js +157 -0
- package/dist/composites/worker-pool.js +167 -0
- package/dist/composites/workload-identity-sa.js +81 -0
- package/dist/composites/workload-identity-service-account.js +77 -0
- package/dist/config-schema.js +72 -0
- package/dist/config.js +59 -0
- package/dist/coverage.js +27 -0
- package/dist/crd/crd-sources.js +615 -0
- package/dist/crd/loader.js +181 -0
- package/dist/crd/oci-chart.js +144 -0
- package/dist/crd/parser.js +382 -0
- package/dist/crd/project-codegen.js +273 -0
- package/dist/crd/types.js +7 -0
- package/dist/deep-observe-hooks.js +306 -0
- package/dist/deep-observe.js +234 -0
- package/dist/default-labels.js +87 -0
- package/dist/describe-resources.js +699 -0
- package/dist/describe-stack-status.js +99 -0
- package/dist/detect.js +11 -0
- package/dist/effect-receipt-row.js +205 -0
- package/dist/export-resources.js +39 -0
- package/dist/generated/index.js +1092 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/group-namespace.js +96 -0
- package/dist/import/embedded.js +197 -0
- package/dist/import/generator.js +288 -0
- package/dist/import/live-export.js +85 -0
- package/dist/import/parser.js +204 -0
- package/dist/import/testdata/embedded/fixtures.js +60 -0
- package/dist/include/entity.js +78 -0
- package/dist/include/resolve.js +170 -0
- package/dist/index.js +59 -0
- package/dist/integrity.json +3 -3
- package/dist/kube/apply.js +90 -0
- package/dist/kube/connect.js +46 -0
- package/dist/kube/delete.js +138 -0
- package/dist/kube/describe.js +169 -0
- package/dist/kube/events.js +132 -0
- package/dist/kube/flags.js +91 -0
- package/dist/kube/get.js +159 -0
- package/dist/kube/group.js +123 -0
- package/dist/kube/logs.js +82 -0
- package/dist/kube/project.js +94 -0
- package/dist/kube/render.js +227 -0
- package/dist/kube/source.js +122 -0
- package/dist/kube/target.js +63 -0
- package/dist/kube/testing.js +28 -0
- package/dist/kube/top.js +99 -0
- package/dist/kube/verdict.js +72 -0
- package/dist/kube/wait.js +129 -0
- package/dist/kustomize/render.js +73 -0
- package/dist/kustomize/rendered-entity.js +67 -0
- package/dist/kustomize/root.js +68 -0
- package/dist/lint/audit-catalog.js +61 -0
- package/dist/lint/audit-lineage.js +118 -0
- package/dist/lint/post-synth/argo-helpers.js +36 -0
- package/dist/lint/post-synth/argo002.js +31 -0
- package/dist/lint/post-synth/argo003.js +53 -0
- package/dist/lint/post-synth/argo005.js +54 -0
- package/dist/lint/post-synth/crd-schema-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/crd-schema-helpers.js +214 -0
- package/dist/lint/post-synth/flux002.js +56 -0
- package/dist/lint/post-synth/flux003.js +50 -0
- package/dist/lint/post-synth/index.js +104 -0
- package/dist/lint/post-synth/k8s-helpers.js +186 -0
- package/dist/lint/post-synth/monitoring-selection-helpers.js +163 -0
- package/dist/lint/post-synth/otel-placement-helpers.js +293 -0
- package/dist/lint/post-synth/sops-helpers.js +43 -0
- package/dist/lint/post-synth/wk8005.js +44 -0
- package/dist/lint/post-synth/wk8006.js +56 -0
- package/dist/lint/post-synth/wk8041.js +60 -0
- package/dist/lint/post-synth/wk8042.js +36 -0
- package/dist/lint/post-synth/wk8101.js +52 -0
- package/dist/lint/post-synth/wk8102.js +30 -0
- package/dist/lint/post-synth/wk8103.js +33 -0
- package/dist/lint/post-synth/wk8104.js +58 -0
- package/dist/lint/post-synth/wk8105.js +33 -0
- package/dist/lint/post-synth/wk8201.js +44 -0
- package/dist/lint/post-synth/wk8202.js +34 -0
- package/dist/lint/post-synth/wk8203.js +34 -0
- package/dist/lint/post-synth/wk8204.js +71 -0
- package/dist/lint/post-synth/wk8205.js +38 -0
- package/dist/lint/post-synth/wk8207.js +33 -0
- package/dist/lint/post-synth/wk8208.js +33 -0
- package/dist/lint/post-synth/wk8209.js +33 -0
- package/dist/lint/post-synth/wk8301.js +49 -0
- package/dist/lint/post-synth/wk8302.js +34 -0
- package/dist/lint/post-synth/wk8303.js +68 -0
- package/dist/lint/post-synth/wk8304.js +58 -0
- package/dist/lint/post-synth/wk8305.js +96 -0
- package/dist/lint/post-synth/wk8306.js +38 -0
- package/dist/lint/post-synth/wk8401.js +86 -0
- package/dist/lint/post-synth/wk8402.js +31 -0
- package/dist/lint/post-synth/wk8403.js +49 -0
- package/dist/lint/post-synth/wk8404.js +36 -0
- package/dist/lint/post-synth/wk8405.js +65 -0
- package/dist/lint/post-synth/wk8406.js +50 -0
- package/dist/lint/post-synth/wk8407.js +58 -0
- package/dist/lint/post-synth/wk8501.js +36 -0
- package/dist/lint/post-synth/wk8502.js +35 -0
- package/dist/lint/post-synth/wk8503.js +164 -0
- package/dist/lint/post-synth/wk8504.js +20 -0
- package/dist/lint/post-synth/wk8505.js +31 -0
- package/dist/lint/post-synth/wk8601.js +40 -0
- package/dist/lint/post-synth/wk8602.js +120 -0
- package/dist/lint/post-synth/wk8603.js +63 -0
- package/dist/lint/post-synth/wk8604.js +31 -0
- package/dist/lint/post-synth/wk8605.js +124 -0
- package/dist/lint/post-synth/wk8606.js +22 -0
- package/dist/lint/post-synth/wk8701.js +31 -0
- package/dist/lint/post-synth/wk8702.js +37 -0
- package/dist/lint/post-synth/wk8703.js +31 -0
- package/dist/lint/post-synth/wk8704.js +135 -0
- package/dist/lint/post-synth/wk8705.js +38 -0
- package/dist/lint/rules/argo-appset-single-project.js +56 -0
- package/dist/lint/rules/argo-ast.js +94 -0
- package/dist/lint/rules/argo-automated-prune.js +59 -0
- package/dist/lint/rules/flux-source-ref-pin.js +44 -0
- package/dist/lint/rules/hardcoded-namespace.js +57 -0
- package/dist/lint/rules/latest-image-tag.js +103 -0
- package/dist/lint/rules/missing-resource-limits.js +85 -0
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +18 -0
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +57 -0
- package/dist/manifest-entity.js +71 -0
- package/dist/manifest.json +1 -1
- package/dist/micro-time.js +49 -0
- package/dist/op/activities/apply-diff.d.ts +31 -0
- package/dist/op/activities/apply-diff.d.ts.map +1 -0
- package/dist/op/activities/apply-diff.js +79 -0
- package/dist/op/activities/argo.js +98 -0
- package/dist/op/activities/ensure-secret.js +38 -0
- package/dist/op/activities/index.js +51 -0
- package/dist/op/activities/kubectl.d.ts +64 -0
- package/dist/op/activities/kubectl.d.ts.map +1 -1
- package/dist/op/activities/kubectl.js +517 -0
- package/dist/op/activities/wait-for-ready.js +229 -0
- package/dist/op/builders.js +68 -0
- package/dist/package-cli.js +21 -0
- package/dist/patch/json-patch.js +187 -0
- package/dist/patch/rendered-patch.js +94 -0
- package/dist/plugin.js +741 -0
- package/dist/project-kinds.js +56 -0
- package/dist/receipt-store.js +216 -0
- package/dist/rules/crd-schema-helpers.ts +2 -1
- package/dist/secret-labels.js +33 -0
- package/dist/secret-store.js +116 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +578 -0
- package/dist/sops/encrypted-secret-file.js +194 -0
- package/dist/sops/entity.js +32 -0
- package/dist/spec/fetch.js +58 -0
- package/dist/spec/parse.js +622 -0
- package/dist/subscribe-changes.js +168 -0
- package/dist/teardown.js +197 -0
- package/dist/telemetry-release.js +86 -0
- package/dist/validate-cli.js +17 -0
- package/dist/validate.js +47 -0
- package/dist/variables.js +34 -0
- package/dist/whoami.js +109 -0
- package/package.json +12 -12
- package/src/api/operation-surface.ts +2 -1
- package/src/codegen/package.ts +2 -2
- package/src/components/index.ts +3 -0
- package/src/components/kubectl-apply-plan.k3d.e2e.test.ts +127 -0
- package/src/components/kubectl-apply.test.ts +262 -0
- package/src/components/kubectl-apply.ts +341 -12
- package/src/deep-observe-hooks.ts +2 -2
- package/src/import/roundtrip-build.test.ts +46 -0
- package/src/lint/post-synth/crd-schema-helpers.ts +2 -1
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
- package/src/op/activities/apply-diff.ts +87 -0
- package/src/op/activities/kubectl-plan.test.ts +224 -0
- package/src/op/activities/kubectl.ts +204 -16
- package/src/serializer.ts +2 -1
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RayCluster composite — KubeRay RayCluster CR + surrounding K8s infra.
|
|
3
|
+
*
|
|
4
|
+
* Encodes production defaults for running Ray on Kubernetes:
|
|
5
|
+
* - NetworkPolicy with podSelector rules (avoids GKE pod CIDR mismatch)
|
|
6
|
+
* - PodDisruptionBudget on the head (minAvailable: 1)
|
|
7
|
+
* - preStop: ray stop hooks + terminationGracePeriodSeconds: 120 on all pods
|
|
8
|
+
* - ServiceAccount for the head + optional autoscaler ClusterRole/CRB
|
|
9
|
+
* - Optional shared ReadWriteMany PVC for training data (Filestore/EFS)
|
|
10
|
+
* - Optional GCS spillover env var for object store spill-to-GCS
|
|
11
|
+
*/
|
|
12
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
13
|
+
import { ServiceAccount, ClusterRole, ClusterRoleBinding, NetworkPolicy, PodDisruptionBudget, PersistentVolumeClaim, Service, RayCluster as RayClusterResource, } from "../generated/index.js";
|
|
14
|
+
// ── Internal helpers ─────────────────────────────────────────────────────────
|
|
15
|
+
/** Parse a CPU string to a numeric count for --num-cpus. "2" → "2", "500m" → "0.5". */
|
|
16
|
+
function cpuToNumCpus(cpu) {
|
|
17
|
+
if (cpu.endsWith("m")) {
|
|
18
|
+
const millis = parseInt(cpu.slice(0, -1), 10);
|
|
19
|
+
return String(millis / 1000);
|
|
20
|
+
}
|
|
21
|
+
return cpu;
|
|
22
|
+
}
|
|
23
|
+
/** Build resource requests/limits. GPU count maps to nvidia.com/gpu. */
|
|
24
|
+
function buildResources(spec) {
|
|
25
|
+
const base = { cpu: spec.cpu, memory: spec.memory };
|
|
26
|
+
if (spec.gpu)
|
|
27
|
+
base["nvidia.com/gpu"] = String(spec.gpu);
|
|
28
|
+
return { requests: { ...base }, limits: { ...base } };
|
|
29
|
+
}
|
|
30
|
+
/** Shared volume list for a pod (dshm + optional PVC). */
|
|
31
|
+
function buildPodVolumes(shmSize, pvcName) {
|
|
32
|
+
const vols = [
|
|
33
|
+
{ name: "dshm", emptyDir: { medium: "Memory", sizeLimit: shmSize } },
|
|
34
|
+
];
|
|
35
|
+
if (pvcName) {
|
|
36
|
+
vols.push({ name: "shared-data", persistentVolumeClaim: { claimName: pvcName } });
|
|
37
|
+
}
|
|
38
|
+
return vols;
|
|
39
|
+
}
|
|
40
|
+
/** Shared volume mounts for a container (dshm + optional shared-data). */
|
|
41
|
+
function buildVolumeMounts(mountPath) {
|
|
42
|
+
const mounts = [
|
|
43
|
+
{ name: "dshm", mountPath: "/dev/shm" },
|
|
44
|
+
];
|
|
45
|
+
if (mountPath) {
|
|
46
|
+
mounts.push({ name: "shared-data", mountPath });
|
|
47
|
+
}
|
|
48
|
+
return mounts;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Extract the Ray version from a container image reference.
|
|
52
|
+
* Used to populate spec.rayVersion on KubeRay CRs so the autoscaler
|
|
53
|
+
* pulls an image that matches the running Ray version.
|
|
54
|
+
*
|
|
55
|
+
* "rayproject/ray:2.40.0-py310-cpu" → "2.40.0"
|
|
56
|
+
* "rayproject/ray:latest" → undefined
|
|
57
|
+
*/
|
|
58
|
+
function extractRayVersion(image) {
|
|
59
|
+
const tag = image.includes(":") ? image.split(":").pop() : image;
|
|
60
|
+
const m = /^([0-9]+\.[0-9]+\.[0-9]+)/.exec(tag);
|
|
61
|
+
return m ? m[1] : undefined;
|
|
62
|
+
}
|
|
63
|
+
// ── Shared cluster spec builder (used by RayJob and RayService too) ──────────
|
|
64
|
+
/**
|
|
65
|
+
* Build headGroupSpec and workerGroupSpecs for a KubeRay CR.
|
|
66
|
+
* Exported for use in RayJob and RayService composites.
|
|
67
|
+
*/
|
|
68
|
+
export function buildRayClusterParts(cluster, saName, spilloverBucket, pvcName, mountPath) {
|
|
69
|
+
const { image, head, workerGroups } = cluster;
|
|
70
|
+
const rayVersion = extractRayVersion(image);
|
|
71
|
+
const shmSize = head.shmSize ?? "2Gi";
|
|
72
|
+
const resolvedMountPath = pvcName ? (mountPath ?? "/mnt/ray-data") : undefined;
|
|
73
|
+
// ── Head container ─────────────────────────────────────────────────────
|
|
74
|
+
const headEnv = [...(head.env ?? [])];
|
|
75
|
+
if (spilloverBucket) {
|
|
76
|
+
headEnv.push({
|
|
77
|
+
name: "RAY_object_spilling_config",
|
|
78
|
+
value: JSON.stringify({
|
|
79
|
+
type: "smart_open",
|
|
80
|
+
params: { uri: `gs://${spilloverBucket}/spill` },
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const headContainer = {
|
|
85
|
+
name: "ray-head",
|
|
86
|
+
image,
|
|
87
|
+
resources: buildResources(head.resources),
|
|
88
|
+
ports: [
|
|
89
|
+
{ containerPort: 6379, name: "gcs-server" },
|
|
90
|
+
{ containerPort: 8265, name: "dashboard" },
|
|
91
|
+
{ containerPort: 10001, name: "client" },
|
|
92
|
+
{ containerPort: 8080, name: "metrics" },
|
|
93
|
+
],
|
|
94
|
+
...(headEnv.length > 0 && { env: headEnv }),
|
|
95
|
+
volumeMounts: buildVolumeMounts(resolvedMountPath),
|
|
96
|
+
lifecycle: { preStop: { exec: { command: ["ray", "stop"] } } },
|
|
97
|
+
};
|
|
98
|
+
const headGroupSpec = {
|
|
99
|
+
rayStartParams: {
|
|
100
|
+
...(head.reserveHead && { "num-cpus": "0" }),
|
|
101
|
+
...(head.rayStartParams ?? {}),
|
|
102
|
+
},
|
|
103
|
+
template: {
|
|
104
|
+
spec: {
|
|
105
|
+
serviceAccountName: saName,
|
|
106
|
+
terminationGracePeriodSeconds: 120,
|
|
107
|
+
containers: [headContainer],
|
|
108
|
+
volumes: buildPodVolumes(shmSize, pvcName),
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
// ── Worker groups ───────────────────────────────────────────────────────
|
|
113
|
+
const workerGroupSpecs = workerGroups.map((group) => {
|
|
114
|
+
// Always inject the GCS reconnect timeout — default 60s terminates workers
|
|
115
|
+
// when the head is briefly unavailable (eviction, rolling restart). 600s gives
|
|
116
|
+
// workers 10 minutes to wait for head recovery before self-terminating.
|
|
117
|
+
const workerEnv = [
|
|
118
|
+
{ name: "RAY_gcs_rpc_server_reconnect_timeout_s", value: "600" },
|
|
119
|
+
...(group.env ?? []),
|
|
120
|
+
];
|
|
121
|
+
const workerContainer = {
|
|
122
|
+
name: "ray-worker",
|
|
123
|
+
image,
|
|
124
|
+
resources: buildResources(group.resources),
|
|
125
|
+
env: workerEnv,
|
|
126
|
+
volumeMounts: buildVolumeMounts(resolvedMountPath),
|
|
127
|
+
lifecycle: { preStop: { exec: { command: ["ray", "stop"] } } },
|
|
128
|
+
};
|
|
129
|
+
const rayStartParams = {
|
|
130
|
+
"num-cpus": cpuToNumCpus(group.resources.cpu),
|
|
131
|
+
...(group.resources.gpu ? { "num-gpus": String(group.resources.gpu) } : {}),
|
|
132
|
+
...(group.rayStartParams ?? {}),
|
|
133
|
+
};
|
|
134
|
+
const podSpec = {
|
|
135
|
+
terminationGracePeriodSeconds: 120,
|
|
136
|
+
containers: [workerContainer],
|
|
137
|
+
volumes: buildPodVolumes("2Gi", pvcName),
|
|
138
|
+
};
|
|
139
|
+
if (group.gpuTolerations) {
|
|
140
|
+
podSpec.tolerations = [
|
|
141
|
+
{ key: "nvidia.com/gpu", operator: "Exists", effect: "NoSchedule" },
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
groupName: group.groupName,
|
|
146
|
+
replicas: group.replicas,
|
|
147
|
+
minReplicas: group.minReplicas ?? group.replicas,
|
|
148
|
+
maxReplicas: group.maxReplicas ?? group.replicas,
|
|
149
|
+
idleTimeoutSeconds: group.idleTimeoutSeconds ?? 60,
|
|
150
|
+
rayStartParams,
|
|
151
|
+
template: { spec: podSpec },
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
return { headGroupSpec, workerGroupSpecs, rayVersion };
|
|
155
|
+
}
|
|
156
|
+
// ── NetworkPolicy builder ─────────────────────────────────────────────────────
|
|
157
|
+
/**
|
|
158
|
+
* Build a NetworkPolicy for a Ray cluster.
|
|
159
|
+
* Uses podSelector-only rules for intra-cluster traffic to avoid GKE pod CIDR
|
|
160
|
+
* mismatch (GKE secondary pod CIDRs differ from declared subnet CIDRs).
|
|
161
|
+
* GCS egress uses ipBlock excluding RFC1918 to allow Google APIs while
|
|
162
|
+
* blocking internal lateral movement.
|
|
163
|
+
*/
|
|
164
|
+
export function buildRayNetworkPolicy(name, namespace, commonLabels, exposeDashboard, defOverride) {
|
|
165
|
+
const clusterSelector = { matchLabels: { "ray.io/cluster-name": name } };
|
|
166
|
+
const rayPorts = [
|
|
167
|
+
{ port: 6379, protocol: "TCP" },
|
|
168
|
+
{ port: 10001, protocol: "TCP" },
|
|
169
|
+
{ port: 10002, protocol: "TCP" },
|
|
170
|
+
{ port: 8080, protocol: "TCP" },
|
|
171
|
+
// Ephemeral gRPC ports for worker-to-worker communication (requires K8s 1.25+)
|
|
172
|
+
{ port: 32768, endPort: 60999, protocol: "TCP" },
|
|
173
|
+
];
|
|
174
|
+
const ingressRules = [
|
|
175
|
+
{
|
|
176
|
+
from: [{ podSelector: clusterSelector }],
|
|
177
|
+
ports: [...rayPorts, { port: 8265, protocol: "TCP" }],
|
|
178
|
+
},
|
|
179
|
+
];
|
|
180
|
+
// When the dashboard is externally exposed, allow ingress from outside the cluster
|
|
181
|
+
if (exposeDashboard) {
|
|
182
|
+
ingressRules.push({ ports: [{ port: 8265, protocol: "TCP" }] });
|
|
183
|
+
}
|
|
184
|
+
const egressRules = [
|
|
185
|
+
// Intra-cluster Ray traffic
|
|
186
|
+
{
|
|
187
|
+
to: [{ podSelector: clusterSelector }],
|
|
188
|
+
ports: [...rayPorts, { port: 8265, protocol: "TCP" }],
|
|
189
|
+
},
|
|
190
|
+
// DNS — required for head service name resolution
|
|
191
|
+
{
|
|
192
|
+
ports: [
|
|
193
|
+
{ port: 53, protocol: "UDP" },
|
|
194
|
+
{ port: 53, protocol: "TCP" },
|
|
195
|
+
],
|
|
196
|
+
},
|
|
197
|
+
// GCS / Artifact Registry HTTPS — ipBlock excludes RFC1918 to prevent
|
|
198
|
+
// lateral movement while allowing Google API endpoints
|
|
199
|
+
{
|
|
200
|
+
to: [
|
|
201
|
+
{
|
|
202
|
+
ipBlock: {
|
|
203
|
+
cidr: "0.0.0.0/0",
|
|
204
|
+
except: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
ports: [{ port: 443, protocol: "TCP" }],
|
|
209
|
+
},
|
|
210
|
+
];
|
|
211
|
+
return new NetworkPolicy(mergeDefaults({
|
|
212
|
+
metadata: {
|
|
213
|
+
name,
|
|
214
|
+
namespace,
|
|
215
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "network-policy" },
|
|
216
|
+
},
|
|
217
|
+
spec: {
|
|
218
|
+
podSelector: clusterSelector,
|
|
219
|
+
policyTypes: ["Ingress", "Egress"],
|
|
220
|
+
ingress: ingressRules,
|
|
221
|
+
egress: egressRules,
|
|
222
|
+
},
|
|
223
|
+
}, defOverride));
|
|
224
|
+
}
|
|
225
|
+
// ── Composite ────────────────────────────────────────────────────────────────
|
|
226
|
+
/**
|
|
227
|
+
* Create a RayCluster composite — returns a KubeRay RayCluster CR and the
|
|
228
|
+
* surrounding K8s resources needed for a production Ray cluster.
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```ts
|
|
232
|
+
* import { RayCluster } from "@intentius/chant-lexicon-k8s";
|
|
233
|
+
*
|
|
234
|
+
* const ray = RayCluster({
|
|
235
|
+
* name: "ray",
|
|
236
|
+
* namespace: "ray-system",
|
|
237
|
+
* cluster: {
|
|
238
|
+
* image: "us-docker.pkg.dev/my-project/ray-images/ray:2.40.0",
|
|
239
|
+
* head: { resources: { cpu: "2", memory: "8Gi" } },
|
|
240
|
+
* workerGroups: [
|
|
241
|
+
* { groupName: "cpu", replicas: 2, minReplicas: 1, maxReplicas: 8,
|
|
242
|
+
* resources: { cpu: "2", memory: "4Gi" } },
|
|
243
|
+
* ],
|
|
244
|
+
* },
|
|
245
|
+
* enableAutoscaler: true,
|
|
246
|
+
* spilloverBucket: "my-ray-spill",
|
|
247
|
+
* });
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
export const RayCluster = Composite((props) => {
|
|
251
|
+
const { name, namespace, cluster, sharedStorage, enableAutoscaler = false, spilloverBucket, exposeDashboard = false, labels: extraLabels = {}, defaults: defs, } = props;
|
|
252
|
+
const saName = `${name}-head`;
|
|
253
|
+
const pvcName = sharedStorage ? `${name}-shared` : undefined;
|
|
254
|
+
const mountPath = sharedStorage?.mountPath;
|
|
255
|
+
const commonLabels = {
|
|
256
|
+
"app.kubernetes.io/name": name,
|
|
257
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
258
|
+
"app.kubernetes.io/component": "ray",
|
|
259
|
+
...extraLabels,
|
|
260
|
+
};
|
|
261
|
+
// -- ServiceAccount --
|
|
262
|
+
const serviceAccount = new ServiceAccount(mergeDefaults({
|
|
263
|
+
metadata: {
|
|
264
|
+
name: saName,
|
|
265
|
+
namespace,
|
|
266
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "service-account" },
|
|
267
|
+
},
|
|
268
|
+
}, defs?.serviceAccount));
|
|
269
|
+
// -- Autoscaler RBAC (optional) --
|
|
270
|
+
const clusterRole = enableAutoscaler ? new ClusterRole(mergeDefaults({
|
|
271
|
+
metadata: {
|
|
272
|
+
name: `${name}-autoscaler`,
|
|
273
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
|
|
274
|
+
},
|
|
275
|
+
rules: [
|
|
276
|
+
{
|
|
277
|
+
apiGroups: [""],
|
|
278
|
+
resources: ["pods"],
|
|
279
|
+
verbs: ["get", "list", "watch", "create", "delete", "patch"],
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
apiGroups: [""],
|
|
283
|
+
resources: ["pods/status"],
|
|
284
|
+
verbs: ["get"],
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
apiGroups: ["ray.io"],
|
|
288
|
+
resources: ["rayclusters"],
|
|
289
|
+
verbs: ["get", "list", "patch"],
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
}, defs?.clusterRole)) : undefined;
|
|
293
|
+
const clusterRoleBinding = enableAutoscaler ? new ClusterRoleBinding(mergeDefaults({
|
|
294
|
+
metadata: {
|
|
295
|
+
name: `${name}-autoscaler`,
|
|
296
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
|
|
297
|
+
},
|
|
298
|
+
roleRef: {
|
|
299
|
+
apiGroup: "rbac.authorization.k8s.io",
|
|
300
|
+
kind: "ClusterRole",
|
|
301
|
+
name: `${name}-autoscaler`,
|
|
302
|
+
},
|
|
303
|
+
subjects: [{ kind: "ServiceAccount", name: saName, namespace }],
|
|
304
|
+
}, defs?.clusterRoleBinding)) : undefined;
|
|
305
|
+
// -- NetworkPolicy --
|
|
306
|
+
const networkPolicy = buildRayNetworkPolicy(name, namespace, commonLabels, exposeDashboard, defs?.networkPolicy);
|
|
307
|
+
// -- PodDisruptionBudget (head) --
|
|
308
|
+
const pdb = new PodDisruptionBudget(mergeDefaults({
|
|
309
|
+
metadata: {
|
|
310
|
+
name: `${name}-head`,
|
|
311
|
+
namespace,
|
|
312
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "disruption-budget" },
|
|
313
|
+
},
|
|
314
|
+
spec: {
|
|
315
|
+
minAvailable: 1,
|
|
316
|
+
selector: {
|
|
317
|
+
matchLabels: {
|
|
318
|
+
"ray.io/cluster-name": name,
|
|
319
|
+
"ray.io/node-type": "head",
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
}, defs?.pdb));
|
|
324
|
+
// -- Shared PVC (optional) --
|
|
325
|
+
const pvc = sharedStorage ? new PersistentVolumeClaim(mergeDefaults({
|
|
326
|
+
metadata: {
|
|
327
|
+
name: pvcName,
|
|
328
|
+
namespace,
|
|
329
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "storage" },
|
|
330
|
+
},
|
|
331
|
+
spec: {
|
|
332
|
+
accessModes: ["ReadWriteMany"],
|
|
333
|
+
storageClassName: sharedStorage.storageClass,
|
|
334
|
+
resources: { requests: { storage: sharedStorage.size } },
|
|
335
|
+
},
|
|
336
|
+
}, defs?.pvc)) : undefined;
|
|
337
|
+
// -- Dashboard LoadBalancer Service (optional) --
|
|
338
|
+
const dashboardService = exposeDashboard ? new Service(mergeDefaults({
|
|
339
|
+
metadata: {
|
|
340
|
+
name: `${name}-dashboard`,
|
|
341
|
+
namespace,
|
|
342
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "dashboard" },
|
|
343
|
+
},
|
|
344
|
+
spec: {
|
|
345
|
+
type: "LoadBalancer",
|
|
346
|
+
selector: {
|
|
347
|
+
"ray.io/cluster-name": name,
|
|
348
|
+
"ray.io/node-type": "head",
|
|
349
|
+
},
|
|
350
|
+
ports: [{ port: 8265, targetPort: 8265, protocol: "TCP", name: "dashboard" }],
|
|
351
|
+
},
|
|
352
|
+
}, defs?.dashboardService)) : undefined;
|
|
353
|
+
// -- RayCluster CR --
|
|
354
|
+
const { headGroupSpec, workerGroupSpecs, rayVersion } = buildRayClusterParts(cluster, saName, spilloverBucket, pvcName, mountPath);
|
|
355
|
+
const rayCluster = new RayClusterResource(mergeDefaults({
|
|
356
|
+
metadata: {
|
|
357
|
+
name,
|
|
358
|
+
namespace,
|
|
359
|
+
labels: commonLabels,
|
|
360
|
+
},
|
|
361
|
+
spec: {
|
|
362
|
+
...(rayVersion && { rayVersion }),
|
|
363
|
+
...(enableAutoscaler && { enableInTreeAutoscaling: true }),
|
|
364
|
+
headGroupSpec,
|
|
365
|
+
workerGroupSpecs,
|
|
366
|
+
},
|
|
367
|
+
}, defs?.rayCluster));
|
|
368
|
+
return {
|
|
369
|
+
serviceAccount,
|
|
370
|
+
...(clusterRole && { clusterRole }),
|
|
371
|
+
...(clusterRoleBinding && { clusterRoleBinding }),
|
|
372
|
+
networkPolicy,
|
|
373
|
+
pdb,
|
|
374
|
+
...(pvc && { pvc }),
|
|
375
|
+
...(dashboardService && { dashboardService }),
|
|
376
|
+
rayCluster,
|
|
377
|
+
};
|
|
378
|
+
}, "RayCluster");
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RayJob composite — KubeRay RayJob CR + surrounding K8s infra.
|
|
3
|
+
*
|
|
4
|
+
* RayJob spins up an ephemeral Ray cluster, runs a single entrypoint command,
|
|
5
|
+
* then tears the cluster down. Ideal for batch training pipelines.
|
|
6
|
+
*
|
|
7
|
+
* Encodes the same production defaults as RayCluster (NetworkPolicy,
|
|
8
|
+
* preStop hooks, optional shared PVC, optional autoscaler RBAC).
|
|
9
|
+
*/
|
|
10
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
11
|
+
import { ServiceAccount, ClusterRole, ClusterRoleBinding, PersistentVolumeClaim, RayJob as RayJobResource, } from "../generated/index.js";
|
|
12
|
+
import { buildRayClusterParts, buildRayNetworkPolicy, } from "./ray-cluster.js";
|
|
13
|
+
/**
|
|
14
|
+
* Create a RayJob composite — ephemeral Ray cluster for a single batch job.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { RayJob } from "@intentius/chant-lexicon-k8s";
|
|
19
|
+
*
|
|
20
|
+
* const job = RayJob({
|
|
21
|
+
* name: "train-resnet",
|
|
22
|
+
* namespace: "ray-system",
|
|
23
|
+
* entrypoint: "python train.py --epochs 50",
|
|
24
|
+
* cluster: {
|
|
25
|
+
* image: "us-docker.pkg.dev/my-project/ray-images/ray:2.40.0",
|
|
26
|
+
* head: { resources: { cpu: "2", memory: "8Gi" } },
|
|
27
|
+
* workerGroups: [
|
|
28
|
+
* { groupName: "gpu", replicas: 4,
|
|
29
|
+
* resources: { cpu: "8", memory: "32Gi", gpu: 1 },
|
|
30
|
+
* gpuTolerations: true },
|
|
31
|
+
* ],
|
|
32
|
+
* },
|
|
33
|
+
* spilloverBucket: "my-ray-spill",
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export const RayJob = Composite((props) => {
|
|
38
|
+
const { name, namespace, entrypoint, cluster, runtimeEnvYAML, shutdownAfterJobFinishes = true, ttlSecondsAfterFinished = 300, sharedStorage, enableAutoscaler = false, spilloverBucket, labels: extraLabels = {}, defaults: defs, } = props;
|
|
39
|
+
const saName = `${name}-head`;
|
|
40
|
+
const pvcName = sharedStorage ? `${name}-shared` : undefined;
|
|
41
|
+
const mountPath = sharedStorage?.mountPath;
|
|
42
|
+
const commonLabels = {
|
|
43
|
+
"app.kubernetes.io/name": name,
|
|
44
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
45
|
+
"app.kubernetes.io/component": "ray",
|
|
46
|
+
...extraLabels,
|
|
47
|
+
};
|
|
48
|
+
// -- ServiceAccount --
|
|
49
|
+
const serviceAccount = new ServiceAccount(mergeDefaults({
|
|
50
|
+
metadata: {
|
|
51
|
+
name: saName,
|
|
52
|
+
namespace,
|
|
53
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "service-account" },
|
|
54
|
+
},
|
|
55
|
+
}, defs?.serviceAccount));
|
|
56
|
+
// -- Autoscaler RBAC (optional) --
|
|
57
|
+
const clusterRole = enableAutoscaler ? new ClusterRole(mergeDefaults({
|
|
58
|
+
metadata: {
|
|
59
|
+
name: `${name}-autoscaler`,
|
|
60
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
|
|
61
|
+
},
|
|
62
|
+
rules: [
|
|
63
|
+
{
|
|
64
|
+
apiGroups: [""],
|
|
65
|
+
resources: ["pods"],
|
|
66
|
+
verbs: ["get", "list", "watch", "create", "delete", "patch"],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
apiGroups: [""],
|
|
70
|
+
resources: ["pods/status"],
|
|
71
|
+
verbs: ["get"],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
apiGroups: ["ray.io"],
|
|
75
|
+
resources: ["rayjobs"],
|
|
76
|
+
verbs: ["get", "list", "patch"],
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
}, defs?.clusterRole)) : undefined;
|
|
80
|
+
const clusterRoleBinding = enableAutoscaler ? new ClusterRoleBinding(mergeDefaults({
|
|
81
|
+
metadata: {
|
|
82
|
+
name: `${name}-autoscaler`,
|
|
83
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
|
|
84
|
+
},
|
|
85
|
+
roleRef: {
|
|
86
|
+
apiGroup: "rbac.authorization.k8s.io",
|
|
87
|
+
kind: "ClusterRole",
|
|
88
|
+
name: `${name}-autoscaler`,
|
|
89
|
+
},
|
|
90
|
+
subjects: [{ kind: "ServiceAccount", name: saName, namespace }],
|
|
91
|
+
}, defs?.clusterRoleBinding)) : undefined;
|
|
92
|
+
// -- NetworkPolicy --
|
|
93
|
+
const networkPolicy = buildRayNetworkPolicy(name, namespace, commonLabels, false, defs?.networkPolicy);
|
|
94
|
+
// -- Shared PVC (optional) --
|
|
95
|
+
const pvc = sharedStorage ? new PersistentVolumeClaim(mergeDefaults({
|
|
96
|
+
metadata: {
|
|
97
|
+
name: pvcName,
|
|
98
|
+
namespace,
|
|
99
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "storage" },
|
|
100
|
+
},
|
|
101
|
+
spec: {
|
|
102
|
+
accessModes: ["ReadWriteMany"],
|
|
103
|
+
storageClassName: sharedStorage.storageClass,
|
|
104
|
+
resources: { requests: { storage: sharedStorage.size } },
|
|
105
|
+
},
|
|
106
|
+
}, defs?.pvc)) : undefined;
|
|
107
|
+
// -- RayJob CR --
|
|
108
|
+
const { headGroupSpec, workerGroupSpecs, rayVersion } = buildRayClusterParts(cluster, saName, spilloverBucket, pvcName, mountPath);
|
|
109
|
+
const rayJob = new RayJobResource(mergeDefaults({
|
|
110
|
+
metadata: {
|
|
111
|
+
name,
|
|
112
|
+
namespace,
|
|
113
|
+
labels: commonLabels,
|
|
114
|
+
},
|
|
115
|
+
spec: {
|
|
116
|
+
entrypoint,
|
|
117
|
+
...(runtimeEnvYAML && { runtimeEnvYAML }),
|
|
118
|
+
shutdownAfterJobFinishes,
|
|
119
|
+
ttlSecondsAfterFinished,
|
|
120
|
+
rayClusterSpec: {
|
|
121
|
+
...(rayVersion && { rayVersion }),
|
|
122
|
+
...(enableAutoscaler && { enableInTreeAutoscaling: true }),
|
|
123
|
+
headGroupSpec,
|
|
124
|
+
workerGroupSpecs,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
}, defs?.rayJob));
|
|
128
|
+
return {
|
|
129
|
+
serviceAccount,
|
|
130
|
+
...(clusterRole && { clusterRole }),
|
|
131
|
+
...(clusterRoleBinding && { clusterRoleBinding }),
|
|
132
|
+
networkPolicy,
|
|
133
|
+
...(pvc && { pvc }),
|
|
134
|
+
rayJob,
|
|
135
|
+
};
|
|
136
|
+
}, "RayJob");
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RayService composite — KubeRay RayService CR + surrounding K8s infra.
|
|
3
|
+
*
|
|
4
|
+
* RayService manages a persistent Ray Serve HTTP endpoint with zero-downtime
|
|
5
|
+
* blue-green upgrades. Ideal for online inference and serving workloads.
|
|
6
|
+
*
|
|
7
|
+
* Encodes the same production defaults as RayCluster (NetworkPolicy, preStop
|
|
8
|
+
* hooks, PDB, optional shared PVC, optional autoscaler RBAC) plus a
|
|
9
|
+
* LoadBalancer Service exposing the Serve HTTP endpoint on port 8000.
|
|
10
|
+
*/
|
|
11
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
12
|
+
import { ServiceAccount, ClusterRole, ClusterRoleBinding, PodDisruptionBudget, PersistentVolumeClaim, Service, RayService as RayServiceResource, } from "../generated/index.js";
|
|
13
|
+
import { buildRayClusterParts, buildRayNetworkPolicy, } from "./ray-cluster.js";
|
|
14
|
+
/**
|
|
15
|
+
* Create a RayService composite — persistent Ray Serve endpoint with
|
|
16
|
+
* zero-downtime blue-green upgrades.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { RayService } from "@intentius/chant-lexicon-k8s";
|
|
21
|
+
*
|
|
22
|
+
* const svc = RayService({
|
|
23
|
+
* name: "inference",
|
|
24
|
+
* namespace: "ray-system",
|
|
25
|
+
* serveConfigV2: `
|
|
26
|
+
* applications:
|
|
27
|
+
* - name: classifier
|
|
28
|
+
* import_path: app:deployment
|
|
29
|
+
* deployments:
|
|
30
|
+
* - name: Classifier
|
|
31
|
+
* num_replicas: 2
|
|
32
|
+
* `,
|
|
33
|
+
* cluster: {
|
|
34
|
+
* image: "us-docker.pkg.dev/my-project/ray-images/ray:2.40.0",
|
|
35
|
+
* head: { resources: { cpu: "2", memory: "8Gi" } },
|
|
36
|
+
* workerGroups: [
|
|
37
|
+
* { groupName: "gpu", replicas: 2,
|
|
38
|
+
* resources: { cpu: "4", memory: "16Gi", gpu: 1 },
|
|
39
|
+
* gpuTolerations: true },
|
|
40
|
+
* ],
|
|
41
|
+
* },
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export const RayService = Composite((props) => {
|
|
46
|
+
const { name, namespace, serveConfigV2, cluster, sharedStorage, enableAutoscaler = false, spilloverBucket, labels: extraLabels = {}, defaults: defs, } = props;
|
|
47
|
+
const saName = `${name}-head`;
|
|
48
|
+
const pvcName = sharedStorage ? `${name}-shared` : undefined;
|
|
49
|
+
const mountPath = sharedStorage?.mountPath;
|
|
50
|
+
const commonLabels = {
|
|
51
|
+
"app.kubernetes.io/name": name,
|
|
52
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
53
|
+
"app.kubernetes.io/component": "ray",
|
|
54
|
+
...extraLabels,
|
|
55
|
+
};
|
|
56
|
+
// -- ServiceAccount --
|
|
57
|
+
const serviceAccount = new ServiceAccount(mergeDefaults({
|
|
58
|
+
metadata: {
|
|
59
|
+
name: saName,
|
|
60
|
+
namespace,
|
|
61
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "service-account" },
|
|
62
|
+
},
|
|
63
|
+
}, defs?.serviceAccount));
|
|
64
|
+
// -- Autoscaler RBAC (optional) --
|
|
65
|
+
const clusterRole = enableAutoscaler ? new ClusterRole(mergeDefaults({
|
|
66
|
+
metadata: {
|
|
67
|
+
name: `${name}-autoscaler`,
|
|
68
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
|
|
69
|
+
},
|
|
70
|
+
rules: [
|
|
71
|
+
{
|
|
72
|
+
apiGroups: [""],
|
|
73
|
+
resources: ["pods"],
|
|
74
|
+
verbs: ["get", "list", "watch", "create", "delete", "patch"],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
apiGroups: [""],
|
|
78
|
+
resources: ["pods/status"],
|
|
79
|
+
verbs: ["get"],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
apiGroups: ["ray.io"],
|
|
83
|
+
resources: ["rayservices"],
|
|
84
|
+
verbs: ["get", "list", "patch"],
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
}, defs?.clusterRole)) : undefined;
|
|
88
|
+
const clusterRoleBinding = enableAutoscaler ? new ClusterRoleBinding(mergeDefaults({
|
|
89
|
+
metadata: {
|
|
90
|
+
name: `${name}-autoscaler`,
|
|
91
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
|
|
92
|
+
},
|
|
93
|
+
roleRef: {
|
|
94
|
+
apiGroup: "rbac.authorization.k8s.io",
|
|
95
|
+
kind: "ClusterRole",
|
|
96
|
+
name: `${name}-autoscaler`,
|
|
97
|
+
},
|
|
98
|
+
subjects: [{ kind: "ServiceAccount", name: saName, namespace }],
|
|
99
|
+
}, defs?.clusterRoleBinding)) : undefined;
|
|
100
|
+
// -- NetworkPolicy --
|
|
101
|
+
// RayService exposes port 8000 (Serve HTTP) externally via LoadBalancer,
|
|
102
|
+
// so we allow ingress from outside the cluster on that port.
|
|
103
|
+
const networkPolicy = buildRayNetworkPolicy(name, namespace, commonLabels, false, defs?.networkPolicy);
|
|
104
|
+
// -- PodDisruptionBudget (head) --
|
|
105
|
+
const pdb = new PodDisruptionBudget(mergeDefaults({
|
|
106
|
+
metadata: {
|
|
107
|
+
name: `${name}-head`,
|
|
108
|
+
namespace,
|
|
109
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "disruption-budget" },
|
|
110
|
+
},
|
|
111
|
+
spec: {
|
|
112
|
+
minAvailable: 1,
|
|
113
|
+
selector: {
|
|
114
|
+
matchLabels: {
|
|
115
|
+
"ray.io/cluster-name": name,
|
|
116
|
+
"ray.io/node-type": "head",
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
}, defs?.pdb));
|
|
121
|
+
// -- Shared PVC (optional) --
|
|
122
|
+
const pvc = sharedStorage ? new PersistentVolumeClaim(mergeDefaults({
|
|
123
|
+
metadata: {
|
|
124
|
+
name: pvcName,
|
|
125
|
+
namespace,
|
|
126
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "storage" },
|
|
127
|
+
},
|
|
128
|
+
spec: {
|
|
129
|
+
accessModes: ["ReadWriteMany"],
|
|
130
|
+
storageClassName: sharedStorage.storageClass,
|
|
131
|
+
resources: { requests: { storage: sharedStorage.size } },
|
|
132
|
+
},
|
|
133
|
+
}, defs?.pvc)) : undefined;
|
|
134
|
+
// -- Ray Serve LoadBalancer Service --
|
|
135
|
+
const serveService = new Service(mergeDefaults({
|
|
136
|
+
metadata: {
|
|
137
|
+
name: `${name}-serve`,
|
|
138
|
+
namespace,
|
|
139
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "serve" },
|
|
140
|
+
},
|
|
141
|
+
spec: {
|
|
142
|
+
type: "LoadBalancer",
|
|
143
|
+
selector: {
|
|
144
|
+
"ray.io/cluster-name": name,
|
|
145
|
+
"ray.io/node-type": "head",
|
|
146
|
+
},
|
|
147
|
+
ports: [{ port: 8000, targetPort: 8000, protocol: "TCP", name: "serve" }],
|
|
148
|
+
},
|
|
149
|
+
}, defs?.serveService));
|
|
150
|
+
// -- RayService CR --
|
|
151
|
+
const { headGroupSpec, workerGroupSpecs, rayVersion } = buildRayClusterParts(cluster, saName, spilloverBucket, pvcName, mountPath);
|
|
152
|
+
const rayService = new RayServiceResource(mergeDefaults({
|
|
153
|
+
metadata: {
|
|
154
|
+
name,
|
|
155
|
+
namespace,
|
|
156
|
+
labels: commonLabels,
|
|
157
|
+
},
|
|
158
|
+
spec: {
|
|
159
|
+
serveConfigV2,
|
|
160
|
+
rayClusterConfig: {
|
|
161
|
+
...(rayVersion && { rayVersion }),
|
|
162
|
+
...(enableAutoscaler && { enableInTreeAutoscaling: true }),
|
|
163
|
+
headGroupSpec,
|
|
164
|
+
workerGroupSpecs,
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
}, defs?.rayService));
|
|
168
|
+
return {
|
|
169
|
+
serviceAccount,
|
|
170
|
+
...(clusterRole && { clusterRole }),
|
|
171
|
+
...(clusterRoleBinding && { clusterRoleBinding }),
|
|
172
|
+
networkPolicy,
|
|
173
|
+
pdb,
|
|
174
|
+
...(pvc && { pvc }),
|
|
175
|
+
serveService,
|
|
176
|
+
rayService,
|
|
177
|
+
};
|
|
178
|
+
}, "RayService");
|