@leverege/build-tools 2.114.0-DEVOP-578.2 → 2.114.0
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/README.md +148 -5
- package/package.json +2 -2
- package/src/helm-charts/llm-prompt-server/helmup.bootstrap +52 -0
- package/src/helmup.sh +37 -6
- package/src/k8s/commands/cryo.mjs +741 -48
- package/src/k8s/commands/exe.mjs +1 -5
- package/src/k8s/k8s.mjs +2 -3
- package/src/k8s/lib/kubectl.mjs +41 -10
- package/src/service-man/config/service-accounts/llm-prompt-server.json +2 -1
package/README.md
CHANGED
|
@@ -44,6 +44,55 @@ Provides: `color()`, `errorExit()`, `warnOnError()`, `exitOnError()`, `generateP
|
|
|
44
44
|
|
|
45
45
|
### Helm & Kubernetes Deployment
|
|
46
46
|
|
|
47
|
+
#### The "field is immutable" error and `helmup --mutate`
|
|
48
|
+
|
|
49
|
+
A routine `helmup` will occasionally fail with an error like:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
Error: UPGRADE FAILED: cannot patch "my-service" with kind Deployment:
|
|
53
|
+
Deployment.apps "my-service" is invalid: spec.selector: Invalid value: ...:
|
|
54
|
+
field is immutable
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Why it happens.** Kubernetes enforces immutability on certain fields after a resource is first
|
|
58
|
+
created. The most common is `spec.selector` on a Deployment — the label selector that determines
|
|
59
|
+
which pods belong to the workload. Once set at creation time, it can never be patched in place;
|
|
60
|
+
any attempt to change it is rejected by the API server outright, regardless of how the change
|
|
61
|
+
arrives (kubectl apply, helm upgrade, etc.). Helm has no built-in way to recover from this on its
|
|
62
|
+
own.
|
|
63
|
+
([Kubernetes docs — label selector updates](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#label-selector-updates))
|
|
64
|
+
|
|
65
|
+
At Leverege, this most commonly surfaces when a service chart is upgraded from the older
|
|
66
|
+
self-contained flat chart structure to the newer Leverege Base Chart library approach
|
|
67
|
+
(`leverege-base-charts`). As the platform's chart conventions have evolved and improved over the
|
|
68
|
+
years, migrating a chart with `init-my-chart` can alter the rendered selector labels, leaving the
|
|
69
|
+
live Deployment's immutable `spec.selector` incompatible with what helm now wants to apply.
|
|
70
|
+
|
|
71
|
+
**What `helmup --mutate` does.** Before invoking helm, helmup deletes the `Deployment` object
|
|
72
|
+
for the service by name (`kubectl delete deployments.apps <service>`) and waits for the deletion
|
|
73
|
+
to complete. Helm then runs its normal upgrade, which recreates the Deployment from scratch with
|
|
74
|
+
the new selector. Because the object is gone before helm runs, there is no immutable-field
|
|
75
|
+
conflict to hit.
|
|
76
|
+
|
|
77
|
+
helmup will ask for confirmation before deleting and prints a warning that brief downtime is
|
|
78
|
+
coming. The upgrade proceeds automatically once you confirm.
|
|
79
|
+
|
|
80
|
+
**Side effects to be aware of:**
|
|
81
|
+
|
|
82
|
+
- *Brief downtime* — pods are terminated when the Deployment is deleted and do not come back
|
|
83
|
+
until helm recreates the object. The duration is typically a few seconds.
|
|
84
|
+
- *LoadBalancers and static IPs are unaffected* — only the Deployment object is deleted; Services
|
|
85
|
+
(and their external IPs) remain untouched throughout.
|
|
86
|
+
- *StatefulSets are not handled* — `--mutate` targets `deployments.apps` only. If you hit an
|
|
87
|
+
immutable field on a StatefulSet (e.g. `spec.volumeClaimTemplates`), you must delete the
|
|
88
|
+
StatefulSet manually using `--cascade=orphan` to preserve PVCs, then re-run helmup normally.
|
|
89
|
+
([Kubernetes docs — StatefulSet limitations](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#limitations))
|
|
90
|
+
|
|
91
|
+
In short: reach for `--mutate` whenever you see the immutable field error on a Deployment. It is
|
|
92
|
+
targeted, explicit, and safe — the only cost is the brief downtime while the object is recreated.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
47
96
|
**`helmup`** — Context-safe Helm deployment tool. Installs or upgrades one or more services.
|
|
48
97
|
```bash
|
|
49
98
|
helmup <service> [service...] # deploy one or more services
|
|
@@ -170,8 +219,98 @@ gcp-snapshots --project <id> [--age <days>] [--list|--delete]
|
|
|
170
219
|
|
|
171
220
|
### Kubernetes Utilities
|
|
172
221
|
|
|
173
|
-
**`
|
|
174
|
-
|
|
222
|
+
**`k8s`** — Unified Kubernetes toolkit with cluster-context safety via `overwhelm`. All
|
|
223
|
+
subcommands verify the active cluster context before acting to prevent cross-cluster
|
|
224
|
+
contamination.
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
k8s fwd <target> # port-forward to a named cluster endpoint
|
|
228
|
+
k8s exe [target] # exec into a pod — shortcut or fuzzy match
|
|
229
|
+
k8s scale <up|dn> [deployments...] # scale deployments up or down
|
|
230
|
+
k8s roll [deployments...] # rolling restart of one or more deployments
|
|
231
|
+
k8s log [service] # stream pod logs with pino-pretty formatting
|
|
232
|
+
k8s cryo init # generate k8s-cryo.yaml from live cluster state
|
|
233
|
+
k8s cryo hibernate # scale all workloads to zero and drain node pools
|
|
234
|
+
k8s cryo wake # restore node pools and workload replica counts
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**`k8s fwd`** — Port-forwards a named cluster service to localhost. Handles `Ctrl+C` cleanly.
|
|
238
|
+
```bash
|
|
239
|
+
k8s fwd elastic # localhost:9200 → elasticsearch8:9200
|
|
240
|
+
k8s fwd redis # localhost:6379 → redis-master:6379
|
|
241
|
+
k8s fwd valkey # localhost:6379 → valkey-master:6379
|
|
242
|
+
k8s fwd prom # localhost:9090 → prometheus-operated:9090
|
|
243
|
+
k8s fwd alert # localhost:9093 → alertmanager-operated:9093
|
|
244
|
+
k8s fwd grafana # localhost:3000 → prometheus-stack-grafana:3000
|
|
245
|
+
k8s fwd <target> --local <port> # override the local port
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**`k8s exe`** — Execs into a running pod. Named shortcuts resolve to the right namespace
|
|
249
|
+
and CLI automatically; any other string is used as a fuzzy filter against pods in the
|
|
250
|
+
default namespace.
|
|
251
|
+
```bash
|
|
252
|
+
k8s exe # interactive pod picker (all namespaces)
|
|
253
|
+
k8s exe cnpg # bash on a cnpg-operands pod
|
|
254
|
+
k8s exe es8 # bash on an elasticsearch8 node
|
|
255
|
+
k8s exe grafana # bash on grafana (prometheus namespace)
|
|
256
|
+
k8s exe grafold # bash on legacy grafana (monitoring namespace)
|
|
257
|
+
k8s exe prometheus # bash on prometheus-operator pod
|
|
258
|
+
k8s exe psql # psql -U postgres on a CNPG node
|
|
259
|
+
k8s exe redis # redis-cli on redis master
|
|
260
|
+
k8s exe valkey # valkey-cli on valkey master
|
|
261
|
+
k8s exe <pattern> # fuzzy match against pod names in default namespace
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**`k8s scale`** — Scales one or more deployments in a namespace. Notifies Slack on each
|
|
265
|
+
result. Interactive picker if no deployment names are given.
|
|
266
|
+
```bash
|
|
267
|
+
k8s scale up api-server message-processor # scale to 1 replica
|
|
268
|
+
k8s scale dn api-server # scale to 0
|
|
269
|
+
k8s scale up # interactive: pick from listed deployments
|
|
270
|
+
k8s scale dn -n monitoring grafana # scale in a specific namespace
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**`k8s roll`** — Rolling restart of one or more deployments (`kubectl rollout restart`).
|
|
274
|
+
Notifies Slack on each result. Interactive picker if no names given.
|
|
275
|
+
```bash
|
|
276
|
+
k8s roll api-server authz-server
|
|
277
|
+
k8s roll # interactive picker
|
|
278
|
+
k8s roll -n monitoring grafana
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**`k8s log`** — Streams pod logs with pino-pretty formatting. Follows the stream until
|
|
282
|
+
`Ctrl+C`. Can tee raw output to a timestamped file.
|
|
283
|
+
```bash
|
|
284
|
+
k8s log # interactive pod picker (all namespaces)
|
|
285
|
+
k8s log api-server # stream by app= label selector, default namespace
|
|
286
|
+
k8s log api-server -n staging # specify namespace
|
|
287
|
+
k8s log api-server --tee # also save raw logs to api-server-<timestamp>.log
|
|
288
|
+
k8s log api-server --tee my.log # save to a named file
|
|
289
|
+
k8s log api-server --no-pretty # raw output, skip pino-pretty
|
|
290
|
+
k8s log api-server --tail 200 # start with last 200 lines (default 50)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**`k8s cryo`** — Cluster hibernation and wake tool. Uses `k8s-cryo.yaml` in the current
|
|
294
|
+
directory to define shutdown order, dependency phases, node pool sizing, and CNPG operator
|
|
295
|
+
awareness. Intended to be run from a cluster directory (e.g. `plat-k8s/tst-builder/`).
|
|
296
|
+
```bash
|
|
297
|
+
k8s cryo init # scan live cluster and generate k8s-cryo.yaml
|
|
298
|
+
k8s cryo init --force # overwrite existing k8s-cryo.yaml
|
|
299
|
+
k8s cryo hibernate # stop all workloads and drain node pools
|
|
300
|
+
k8s cryo wake # restore nodes and scale workloads back up
|
|
301
|
+
k8s cryo hibernate ./path/to/k8s-cryo.yaml # explicit config path
|
|
302
|
+
```
|
|
303
|
+
`k8s cryo init` scans the live cluster for deployments, StatefulSets, CNPG clusters and
|
|
304
|
+
poolers, node pools, and system namespaces (traefik, cert-manager, external-secrets, velero,
|
|
305
|
+
estafette, prometheus, monitoring). It queries live replica counts for variable-size
|
|
306
|
+
StatefulSets (timescale slave, valkey replicas, elasticsearch data) and emits a complete
|
|
307
|
+
`k8s-cryo.yaml`. Review the generated file — especially the Project phase member list and
|
|
308
|
+
any cluster-specific replica counts — before committing.
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
**`k8x`** *(deprecated — use `k8s exe`)* — Opens an interactive shell (or specific CLI)
|
|
313
|
+
on a running pod, with fuzzy finder support for pod selection. Requires `fzf`.
|
|
175
314
|
```bash
|
|
176
315
|
k8x # open bash on any pod (fuzzy select)
|
|
177
316
|
k8x cnpg # bash on a cnpg cluster node
|
|
@@ -182,15 +321,16 @@ k8x redis # redis-cli on redis master
|
|
|
182
321
|
k8x valkey # valkey-cli on valkey master
|
|
183
322
|
```
|
|
184
323
|
|
|
185
|
-
**`k8scale`** —
|
|
324
|
+
**`k8scale`** *(deprecated — use `k8s scale` and `k8s roll`)* — Scales Kubernetes
|
|
325
|
+
deployments or performs rolling restarts.
|
|
186
326
|
```bash
|
|
187
327
|
k8scale up [services] # scale up to minReplicas
|
|
188
328
|
k8scale dn [services] # scale down to zero
|
|
189
329
|
k8scale roll [services] # rolling restart
|
|
190
330
|
```
|
|
191
331
|
|
|
192
|
-
**`klog`** — Streams logs from a pod with pino-pretty
|
|
193
|
-
log file. Requires `fzf` for interactive selection.
|
|
332
|
+
**`klog`** *(deprecated — use `k8s log`)* — Streams logs from a pod with pino-pretty
|
|
333
|
+
formatting. Saves to a timestamped log file. Requires `fzf` for interactive selection.
|
|
194
334
|
```bash
|
|
195
335
|
klog # fuzzy-select pod and stream logs
|
|
196
336
|
klog <app-name> # stream logs for named app
|
|
@@ -357,6 +497,9 @@ BUILD_TOOLS_DEBUG=1 docker-to-registry
|
|
|
357
497
|
|
|
358
498
|
| Tool | Replacement |
|
|
359
499
|
|------|-------------|
|
|
500
|
+
| `k8x` | `k8s exe` |
|
|
501
|
+
| `k8scale` | `k8s scale` / `k8s roll` |
|
|
502
|
+
| `klog` | `k8s log` |
|
|
360
503
|
| `pkgck` | `repo-panopticon` |
|
|
361
504
|
| `docker-to-registry-py` | `docker-to-registry` |
|
|
362
505
|
| `chart-to-museum` | `push-my-chart` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leverege/build-tools",
|
|
3
|
-
"version": "2.114.0
|
|
3
|
+
"version": "2.114.0",
|
|
4
4
|
"description": "A collection of build / support tools for Leverege developers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"execa": "^10.0.1",
|
|
101
101
|
"find-yarn-workspace-root": "^2.0.0",
|
|
102
102
|
"glob": "^13.0.6",
|
|
103
|
-
"googleapis": "^
|
|
103
|
+
"googleapis": "^174.0.1",
|
|
104
104
|
"handlebars": "^4.7.9",
|
|
105
105
|
"ignore": "^7.0.6",
|
|
106
106
|
"inquirer": "^14.0.2",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
function createLlmPromptServerJwtSecret() {
|
|
4
|
+
# Create a placeholder k8s secret so llm-prompt-server starts cleanly on first install.
|
|
5
|
+
# ESO will then be wired up to manage the actual JWT credentials from GCP Secret Manager.
|
|
6
|
+
kubectl get secret llm-prompt-server-jwt --namespace default &> $DEVNULL
|
|
7
|
+
if [ $? -ne 0 ];
|
|
8
|
+
then
|
|
9
|
+
printf "\nCreating placeholder `color g llm-prompt-server-jwt` secret in default namespace\n"
|
|
10
|
+
kubectl create secret generic llm-prompt-server-jwt \
|
|
11
|
+
--namespace default \
|
|
12
|
+
--from-literal=config='{"jwtSub":"llm-prompt-server","jwtSecret":"bootstrap","jwtIssuer":"imagineService"}'
|
|
13
|
+
warnOnError $? "Failed to create llm-prompt-server-jwt placeholder secret"
|
|
14
|
+
else
|
|
15
|
+
printf "\n`color g llm-prompt-server-jwt` secret already exists - skipping\n"
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Wire up the ExternalSecret only if ESO is enabled for this cluster.
|
|
19
|
+
# Set ESO_ENABLED=true in overwhelm.yaml to activate (default: false).
|
|
20
|
+
if [ "${ESO_ENABLED}" = "true" ];
|
|
21
|
+
then
|
|
22
|
+
showInstalling "llm-prompt-server JWT ExternalSecret"
|
|
23
|
+
cat<<LLM_JWT_SECRET | kubectl apply -f -
|
|
24
|
+
apiVersion: external-secrets.io/v1
|
|
25
|
+
kind: ExternalSecret
|
|
26
|
+
metadata:
|
|
27
|
+
name: llm-prompt-server-jwt
|
|
28
|
+
namespace: default
|
|
29
|
+
spec:
|
|
30
|
+
refreshInterval: 1h
|
|
31
|
+
secretStoreRef:
|
|
32
|
+
name: gcp-secret-manager
|
|
33
|
+
kind: ClusterSecretStore
|
|
34
|
+
target:
|
|
35
|
+
name: llm-prompt-server-jwt
|
|
36
|
+
creationPolicy: Merge
|
|
37
|
+
template:
|
|
38
|
+
engineVersion: v2
|
|
39
|
+
data:
|
|
40
|
+
config: '{"jwtSub":"llm-prompt-server","jwtSecret":"{{ .JWT_SERVICE_SECRET }}","jwtIssuer":"imagineService"}'
|
|
41
|
+
data:
|
|
42
|
+
- secretKey: JWT_SERVICE_SECRET
|
|
43
|
+
remoteRef:
|
|
44
|
+
key: JWT_SERVICE_SECRET
|
|
45
|
+
LLM_JWT_SECRET
|
|
46
|
+
else
|
|
47
|
+
printf "\n`color y '***WARNING:'` ESO_ENABLED is not set - skipping llm-prompt-server JWT ExternalSecret\n"
|
|
48
|
+
printf " Set ESO_ENABLED=true in overwhelm.yaml and re-run helmup llm-prompt-server to wire up the JWT secret.\n\n"
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
createLlmPromptServerJwtSecret
|
package/src/helmup.sh
CHANGED
|
@@ -20,7 +20,7 @@ function giveHelp() {
|
|
|
20
20
|
`color g "--k8s-match"` Only proceed if k8s context is correctly set
|
|
21
21
|
`color g "--dry-run"` Show what helm would do without applying changes
|
|
22
22
|
`color g "--debug"` Enable verbose debug output
|
|
23
|
-
`color g "--mutate"`
|
|
23
|
+
`color g "--mutate"` Delete the k8s deployment object before upgrading (causes downtime)
|
|
24
24
|
`color g "--help"` Show this help message
|
|
25
25
|
|
|
26
26
|
Invocation Modes:
|
|
@@ -106,7 +106,6 @@ PLATFORM=(
|
|
|
106
106
|
db-curator
|
|
107
107
|
emailer
|
|
108
108
|
imagine
|
|
109
|
-
llm-prompt-server
|
|
110
109
|
message-processor
|
|
111
110
|
messenger
|
|
112
111
|
resource-server
|
|
@@ -121,6 +120,7 @@ AUXILIARY=(
|
|
|
121
120
|
argocd
|
|
122
121
|
fota-server
|
|
123
122
|
geotile-server
|
|
123
|
+
llm-prompt-server
|
|
124
124
|
overdose
|
|
125
125
|
pgbouncer
|
|
126
126
|
pubsub-pulse
|
|
@@ -128,6 +128,7 @@ AUXILIARY=(
|
|
|
128
128
|
pusher
|
|
129
129
|
reason
|
|
130
130
|
transponder-dh
|
|
131
|
+
triton-inference-server
|
|
131
132
|
vin-decoder-server
|
|
132
133
|
vms-server
|
|
133
134
|
)
|
|
@@ -655,23 +656,53 @@ DELETE_WAIT
|
|
|
655
656
|
|
|
656
657
|
# plugins pass HELMUP as a single quoted string - split it into args without using eval
|
|
657
658
|
[[ $# -eq 1 ]] && set -- $1
|
|
658
|
-
|
|
659
|
-
|
|
659
|
+
|
|
660
|
+
# Tee helm output to a temp file so we can inspect it on failure while still
|
|
661
|
+
# streaming it to the terminal in real-time. 2>&1 captures stderr (where helm
|
|
662
|
+
# writes UPGRADE FAILED messages) alongside stdout.
|
|
663
|
+
local HELM_LOG
|
|
664
|
+
HELM_LOG=$(mktemp)
|
|
665
|
+
"$@" --set "helmup.culprit=$culprit" --set "helmup.deployT=$deployT" 2>&1 | tee "$HELM_LOG"
|
|
666
|
+
local HELM_EXIT=${PIPESTATUS[0]}
|
|
667
|
+
|
|
660
668
|
# usually the helm command will run without error but a common failure is caused
|
|
661
669
|
# by the lack of use of our protonVPN profile which causes the i/o timeout from helm
|
|
662
|
-
if [[
|
|
670
|
+
if [[ $HELM_EXIT -ne 0 ]];
|
|
663
671
|
then
|
|
664
|
-
|
|
672
|
+
if grep -q "field is immutable" "$HELM_LOG";
|
|
673
|
+
then
|
|
674
|
+
cat<<IMMUTABLE_FIELD
|
|
675
|
+
|
|
676
|
+
$RED_ERROR Helm upgrade failed — an immutable field has changed.
|
|
677
|
+
|
|
678
|
+
`color y "This typically happens when a service chart has been updated to use the new Leverege
|
|
679
|
+
Base Chart structure and the Deployment's label selector has changed. Kubernetes does not
|
|
680
|
+
allow selector changes on an existing Deployment — the object must be deleted and recreated."`
|
|
681
|
+
|
|
682
|
+
`color g "Re-run with --mutate to let helmup delete and recreate the Deployment for you:"`
|
|
683
|
+
|
|
684
|
+
`color c "helmup $SERVICE --mutate"`
|
|
685
|
+
|
|
686
|
+
`color y "Note: --mutate causes brief downtime while the Deployment is recreated.
|
|
687
|
+
The service's LoadBalancer, static IP, and any PVCs are unaffected."`
|
|
688
|
+
|
|
689
|
+
IMMUTABLE_FIELD
|
|
690
|
+
else
|
|
691
|
+
cat<<HELMUP_FAIL
|
|
665
692
|
|
|
666
693
|
$RED_ERROR helmup failed for => `color y "$SERVICE"`
|
|
667
694
|
|
|
668
695
|
`color y "Please investigate the issue, and if it isn't obvious then check your VPN connectivity!"`
|
|
669
696
|
|
|
670
697
|
HELMUP_FAIL
|
|
698
|
+
fi
|
|
671
699
|
|
|
700
|
+
rm -f "$HELM_LOG"
|
|
672
701
|
exit 1
|
|
673
702
|
fi
|
|
674
703
|
|
|
704
|
+
rm -f "$HELM_LOG"
|
|
705
|
+
|
|
675
706
|
case "$INVOKED_AS" in
|
|
676
707
|
"helmup"|"helmdn")
|
|
677
708
|
sendToSlack "$*"
|