fix(zoo): pass luxd flags to zoo-chain so validators stay up

The Rust zoo-operator configures each validator by passing the ZooNetwork CR
config map as ZOO_* env vars. luxd (ghcr.io/luxfi/node, CMD ["./luxd"]) does
NOT read http-host / bootstrap / track-chains from ZOO_* env — only from real
CLI flags. So luxd bound its HTTP/health API to the default 127.0.0.1: the
kubelet liveness probe targets the pod IP, never reaches /ext/health, and
SIGTERMs the node ~90s after boot — an endless restart loop that kept testnet
and devnet validators from ever stabilizing (and would have hit mainnet too).

Inject the flags directly onto the operator-rendered StatefulSet via a
per-overlay statefulset-patch.yaml, applied by deploy-all.sh right after the
kustomize apply (the operator owns the StatefulSet, so it is not in the
kustomize set and cannot be patched through the overlay; the operator does not
re-render an existing pod template, so the patch persists).

- --http-host=0.0.0.0 + --http-allowed-hosts=* so the probe can reach health
- --bootstrap-ips/--bootstrap-ids/--network-id/--sybil-protection-enabled as
  real flags (the ZOO_* env equivalents were silently ignored: nodes booted
  with 'Adding bootstrap nodes ... count=0')
- devnet: drop the bogus --track-chains=zoo (and TRACK_CHAINS env) — luxd
  rejects the bare 'zoo' alias; the devnet Zoo EVM chain isn't created yet
This commit is contained in:
hanzo-dev
2026-06-23 19:21:58 +00:00
parent 45ea50e53d
commit 11afcc3033
7 changed files with 105 additions and 1 deletions
+2 -1
View File
@@ -22,10 +22,11 @@ spec:
storage:
storageClass: do-block-storage
size: 20Gi
# luxd ignores ZOO_* env for http-host/bootstrap flags; the real flags are
# injected via statefulset-patch.yaml (see kustomization.yaml).
config:
# Devnet has no sybil protection (Lux primary networkID 3 convention).
SYBIL_PROTECTION_ENABLED: "false"
TRACK_CHAINS: "zoo"
---
apiVersion: zoo.network/v1alpha1
kind: ZooChain
+26
View File
@@ -0,0 +1,26 @@
# luxd flag injection for the operator-rendered zoo-chain StatefulSet.
# See k8s/zoo-mainnet/statefulset-patch.yaml for the full rationale: luxd ignores
# ZOO_* env for these settings, so --http-host=0.0.0.0 (etc.) must be passed as
# CLI flags or the kubelet liveness probe can't reach /ext/health and the node
# restart-loops. devnet = Lux primary networkId 3, 1 validator, sybil protection
# off (networkId 3 convention), tracks the "zoo" chain alias.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: zoo-chain
spec:
template:
spec:
containers:
- name: zoo-node
command: ["./luxd"]
args:
- --http-host=0.0.0.0
- --http-allowed-hosts=*
- --network-id=3
- --sybil-protection-enabled=false
# No --track-chains: the devnet Zoo EVM chain (200202) is not yet
# created on the Lux devnet P-chain, and luxd rejects the bare "zoo"
# alias ("input string is smaller than the checksum size"). This single
# validator runs the primary network; add --track-chains=<blockchainID>
# here once the devnet chain exists.
+5
View File
@@ -28,6 +28,11 @@ spec:
storage:
storageClass: do-block-storage
size: 200Gi
# NOTE: the Rust zoo-operator renders these config keys as ZOO_* env vars,
# but luxd does NOT read its http-host / bootstrap flags from ZOO_* env — it
# only honors real CLI flags. The flags that actually configure the node
# (--http-host=0.0.0.0, --bootstrap-ips, ...) are injected via the
# statefulset-patch.yaml strategic-merge patch below (see kustomization.yaml).
config:
BOOTSTRAP_IPS: "129.212.164.46:9631,137.184.246.133:9631,143.244.210.43:9631,143.244.210.71:9631,24.144.70.85:9631"
BOOTSTRAP_IDS: "NodeID-Mf3JfSY91oDwfBqf7rCLmhg4NDtDghw1f,NodeID-2TwSZ2oyeBK2mv7JiseEQ8m74rotDj4QR,NodeID-Ld9VFBQ9zGbd79z2vzaAqkQ3jHuqbtRpo,NodeID-8mY2fhUehN27v3LCU84BnnKEoeRfd2weC,NodeID-DwsrqSkPoE3pXWrUt9nkJ5yBycwRQ246X"
+32
View File
@@ -0,0 +1,32 @@
# luxd flag injection for the operator-rendered zoo-chain StatefulSet.
#
# The Rust zoo-operator builds the StatefulSet from the ZooNetwork CR and passes
# config as ZOO_* env vars. luxd (the ghcr.io/luxfi/node image, CMD ["./luxd"])
# does NOT read its http-host / bootstrap / track-chains settings from ZOO_* env
# — only from real CLI flags. Without --http-host=0.0.0.0 luxd binds the HTTP /
# health API to 127.0.0.1, so the kubelet liveness probe (which hits the pod IP)
# can never reach /ext/health: the node boots, passes its internal check, then
# gets SIGTERM'd ~90s in when the probe fails 3x — an endless restart loop.
#
# This strategic-merge patch sets command+args directly so luxd gets the flags.
# The operator does not re-render an existing StatefulSet's pod template (it only
# checks progress), so these flags persist across reconciles. mainnet = Lux
# primary networkId 1, 5 validators, Zoo EVM chain 4HBM17czc7zFQMPrd9CVZ6YbABm1jecf99iwvFw8Gbo6gFaU3.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: zoo-chain
spec:
template:
spec:
containers:
- name: zoo-node
command: ["./luxd"]
args:
- --http-host=0.0.0.0
- --http-allowed-hosts=*
- --network-id=1
- --sybil-protection-enabled=true
- --bootstrap-ips=129.212.164.46:9631,137.184.246.133:9631,143.244.210.43:9631,143.244.210.71:9631,24.144.70.85:9631
- --bootstrap-ids=NodeID-Mf3JfSY91oDwfBqf7rCLmhg4NDtDghw1f,NodeID-2TwSZ2oyeBK2mv7JiseEQ8m74rotDj4QR,NodeID-Ld9VFBQ9zGbd79z2vzaAqkQ3jHuqbtRpo,NodeID-8mY2fhUehN27v3LCU84BnnKEoeRfd2weC,NodeID-DwsrqSkPoE3pXWrUt9nkJ5yBycwRQ246X
- --track-chains=4HBM17czc7zFQMPrd9CVZ6YbABm1jecf99iwvFw8Gbo6gFaU3
+2
View File
@@ -24,6 +24,8 @@ spec:
storage:
storageClass: do-block-storage
size: 100Gi
# luxd ignores ZOO_* env for http-host/bootstrap flags; the real flags are
# injected via statefulset-patch.yaml (see kustomization.yaml).
config:
BOOTSTRAP_IPS: "134.199.187.16:9641,209.38.174.84:9641,209.38.173.80:9641,134.199.187.62:9641,164.90.247.204:9641"
BOOTSTRAP_IDS: "NodeID-Mf3JfSY91oDwfBqf7rCLmhg4NDtDghw1f,NodeID-2TwSZ2oyeBK2mv7JiseEQ8m74rotDj4QR,NodeID-Ld9VFBQ9zGbd79z2vzaAqkQ3jHuqbtRpo,NodeID-8mY2fhUehN27v3LCU84BnnKEoeRfd2weC,NodeID-4smuFz5Z9cm8BEyQU1oPQ9nwgXyMgaNqu"
+24
View File
@@ -0,0 +1,24 @@
# luxd flag injection for the operator-rendered zoo-chain StatefulSet.
# See k8s/zoo-mainnet/statefulset-patch.yaml for the full rationale: luxd ignores
# ZOO_* env for these settings, so --http-host=0.0.0.0 (etc.) must be passed as
# CLI flags or the kubelet liveness probe can't reach /ext/health and the node
# restart-loops. testnet = Lux primary networkId 2, 3 validators, Zoo testnet EVM
# chain c3PrgGAAoJXz8RgNAw3j7dSUjUiHVk3mtTneYfr7GvZRxmTPH.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: zoo-chain
spec:
template:
spec:
containers:
- name: zoo-node
command: ["./luxd"]
args:
- --http-host=0.0.0.0
- --http-allowed-hosts=*
- --network-id=2
- --sybil-protection-enabled=true
- --bootstrap-ips=134.199.187.16:9641,209.38.174.84:9641,209.38.173.80:9641,134.199.187.62:9641,164.90.247.204:9641
- --bootstrap-ids=NodeID-Mf3JfSY91oDwfBqf7rCLmhg4NDtDghw1f,NodeID-2TwSZ2oyeBK2mv7JiseEQ8m74rotDj4QR,NodeID-Ld9VFBQ9zGbd79z2vzaAqkQ3jHuqbtRpo,NodeID-8mY2fhUehN27v3LCU84BnnKEoeRfd2weC,NodeID-4smuFz5Z9cm8BEyQU1oPQ9nwgXyMgaNqu
- --track-chains=c3PrgGAAoJXz8RgNAw3j7dSUjUiHVk3mtTneYfr7GvZRxmTPH
+14
View File
@@ -48,6 +48,20 @@ fi
echo "Applying overlay: ${NS}..."
kubectl apply -k "${OVERLAY_DIR}"
# Inject luxd CLI flags onto the operator-rendered zoo-chain StatefulSet.
# The Rust zoo-operator owns this StatefulSet (it is not in the kustomize set,
# so it can't be patched via the overlay) and passes config as ZOO_* env vars,
# which luxd ignores for http-host / bootstrap / track-chains. Without
# --http-host=0.0.0.0 luxd binds its HTTP/health API to 127.0.0.1 and the
# kubelet liveness probe (pod IP) can never reach it → ~90s SIGTERM restart loop.
# The operator does not re-render an existing StatefulSet's pod template, so this
# patch persists across reconciles; re-running this deploy re-asserts it.
STS_PATCH="${OVERLAY_DIR}/statefulset-patch.yaml"
if [[ -f "${STS_PATCH}" ]]; then
echo "Patching luxd flags onto statefulset/zoo-chain..."
kubectl -n "${NS}" patch statefulset zoo-chain --type=strategic --patch-file "${STS_PATCH}"
fi
# Patch each manifest image onto its deployment so service-built dispatches
# take effect without committing image pins into the base.
python3 - "${MANIFEST}" "${NS}" <<'PYEOF'