Operator topology fix:
- base/operator.yaml: pin ghcr.io/zooai/operator:v0.1.1 (was :main floating).
Add OPERATOR_NAMESPACE env via fieldRef: metadata.namespace so the
leader-election Lease lock is namespace-scoped, not racing in a single
hardcoded 'zoo-system' literal. imagePullPolicy: Always → IfNotPresent
(semver tag is immutable).
- zoo-system/patches.yaml: zoo-operator replicas → 2 (HA, leader-election
picks one). Drop the stale zoo-explorer raw-Deployment patch (operator
emits it now via ZooExplorer CR).
- zoo-{mainnet,testnet,devnet}/network-patch.yaml: scale per-env
zoo-operator → 0. Single zoo-system operator watches every namespace
(--namespace '' default); per-env operators were thrashing the same
Lease and never becoming leader.
CR migration (away from raw manifests):
- base/network.yaml: full rewrite as ZooNetwork + ZooChain + ZooExplorer
+ ZooGateway base CRs (CRDs already installed: zoonetworks /
zoochains / zooexplorers / zoogateways).
- base/explorer.yaml: emptied — ZooExplorer CR replaces the raw
Deployment; operator renders zoo-explorer-explorer Deployment + Svc.
- base/gateway.yaml: Service-only — ZooGateway moved to network.yaml.
- base/ingress.yaml: kind: Ingress → 4 kind: IngressRoute
(hanzo.ai/v1alpha1). Traefik proxy Deployment kept (it IS the
hanzoai/ingress L7 proxy on this cluster).
- zoo-{mainnet,testnet}/network-patch.yaml: env-specific patches —
validators (mainnet=5, testnet=3), bootstrap IPs+IDs from live Lux
mainnet+testnet svcs, TRACK_CHAINS = Zoo blockchainID per env.
- zoo-{testnet,devnet}/ingressroute.yaml: new — api.test.zoo.network,
explore.test.zoo.network, api-explore.test.zoo.network (and dev.*
variants).
- zoo-{system,testnet,devnet}/kustomization.yaml: include new
ingressroute.yaml resource + strip mainnet-only IngressRoutes via
$patch: delete pattern.
- zoo-mainnet/zoo-industries.yaml: kind: Ingress → IngressRoute.
Blockers surfaced (separate followups):
1. Operator does NOT translate `spec.config` env keys to lqd CLI
flags. Rendered StatefulSet sets ZOO_BOOTSTRAP_IPS etc., but
luxd reads LUXD_*-prefixed env. Operator controller.rs needs to
render as CLI args. BLOCKS validators joining Lux primary.
2. hanzo.ai/v1alpha1 IngressRoute CRDs not registered on
do-sfo3-zoo-k8s — current ingress is traefik:v3.3 with
traefik.io/v1alpha1 native. Either install hanzoai/ingress CRDs
OR migrate IngressRoutes to traefik.io group.
3. ZooNetwork operator doesn't emit per-pod LoadBalancer Services
needed for validators to be reachable as external bootstrap peers.
4. Validator staking keys: operator only takes secret_prefix; doesn't
derive from a KMS mnemonic. Need 5 Secrets per env OR operator
extension.
283 lines
7.5 KiB
YAML
283 lines
7.5 KiB
YAML
# Ingress — hanzoai/ingress (Traefik) for Zoo TLS termination
|
|
# cert-manager handles ACME via letsencrypt-prod ClusterIssuer
|
|
---
|
|
apiVersion: cert-manager.io/v1
|
|
kind: ClusterIssuer
|
|
metadata:
|
|
name: letsencrypt-prod
|
|
spec:
|
|
acme:
|
|
server: https://acme-v02.api.letsencrypt.org/directory
|
|
email: [email protected]
|
|
privateKeySecretRef:
|
|
name: letsencrypt-prod-key
|
|
solvers:
|
|
- http01:
|
|
ingress:
|
|
class: ingress
|
|
---
|
|
apiVersion: v1
|
|
kind: ServiceAccount
|
|
metadata:
|
|
name: zoo-ingress
|
|
labels:
|
|
app: zoo-ingress
|
|
app.kubernetes.io/name: zoo-ingress
|
|
app.kubernetes.io/part-of: zoo
|
|
---
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: ClusterRole
|
|
metadata:
|
|
name: zoo-ingress
|
|
rules:
|
|
- apiGroups: [""]
|
|
resources: [services, endpoints, secrets, namespaces, configmaps]
|
|
verbs: [get, list, watch]
|
|
- apiGroups: [discovery.k8s.io]
|
|
resources: [endpointslices]
|
|
verbs: [get, list, watch]
|
|
- apiGroups: [networking.k8s.io]
|
|
resources: [ingresses, ingressclasses]
|
|
verbs: [get, list, watch]
|
|
- apiGroups: [networking.k8s.io]
|
|
resources: [ingresses/status]
|
|
verbs: [update]
|
|
- apiGroups: [traefik.io]
|
|
resources: ["*"]
|
|
verbs: [get, list, watch]
|
|
---
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: ClusterRoleBinding
|
|
metadata:
|
|
name: zoo-ingress
|
|
roleRef:
|
|
apiGroup: rbac.authorization.k8s.io
|
|
kind: ClusterRole
|
|
name: zoo-ingress
|
|
subjects:
|
|
- kind: ServiceAccount
|
|
name: zoo-ingress
|
|
namespace: zoo
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: zoo-ingress
|
|
labels:
|
|
app: zoo-ingress
|
|
app.kubernetes.io/name: zoo-ingress
|
|
app.kubernetes.io/part-of: zoo
|
|
spec:
|
|
replicas: 2
|
|
selector:
|
|
matchLabels:
|
|
app: zoo-ingress
|
|
strategy:
|
|
type: RollingUpdate
|
|
rollingUpdate:
|
|
maxSurge: 1
|
|
maxUnavailable: 0
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: zoo-ingress
|
|
spec:
|
|
serviceAccountName: zoo-ingress
|
|
imagePullSecrets:
|
|
- name: ghcr-secret
|
|
- name: hanzo
|
|
hostNetwork: true
|
|
dnsPolicy: ClusterFirstWithHostNet
|
|
terminationGracePeriodSeconds: 30
|
|
affinity:
|
|
podAntiAffinity:
|
|
requiredDuringSchedulingIgnoredDuringExecution:
|
|
- labelSelector:
|
|
matchExpressions:
|
|
- key: app
|
|
operator: In
|
|
values:
|
|
- zoo-ingress
|
|
topologyKey: kubernetes.io/hostname
|
|
containers:
|
|
- name: ingress
|
|
image: traefik:v3.3
|
|
imagePullPolicy: Always
|
|
args:
|
|
- --providers.kubernetesingress=true
|
|
- --providers.kubernetesingress.ingressclass=ingress
|
|
- --providers.kubernetesingress.allowemptyservices=true
|
|
- --providers.kubernetescrd=true
|
|
- --providers.kubernetescrd.allowcrossnamespace=true
|
|
- --entrypoints.web.address=:80
|
|
- --entrypoints.web.http.redirections.entrypoint.to=:443
|
|
- --entrypoints.web.http.redirections.entrypoint.scheme=https
|
|
- --entrypoints.web.http.redirections.entrypoint.permanent=true
|
|
- --entrypoints.websecure.address=:443
|
|
- --entrypoints.websecure.http.tls=true
|
|
- --entrypoints.websecure.http.tls.certresolver=letsencrypt
|
|
- --[email protected]
|
|
- --certificatesresolvers.letsencrypt.acme.storage=/data/acme.json
|
|
- --certificatesresolvers.letsencrypt.acme.dnschallenge=true
|
|
- --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare
|
|
- --certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53
|
|
- --ping=true
|
|
- --ping.entryPoint=web
|
|
- --log.level=INFO
|
|
- --accesslog=true
|
|
env:
|
|
- name: CF_API_EMAIL
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: cloudflare-api-credentials
|
|
key: EMAIL
|
|
- name: CF_API_KEY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: cloudflare-api-credentials
|
|
key: APIKEY
|
|
ports:
|
|
- name: web
|
|
containerPort: 80
|
|
hostPort: 80
|
|
protocol: TCP
|
|
- name: websecure
|
|
containerPort: 443
|
|
hostPort: 443
|
|
protocol: TCP
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 128Mi
|
|
limits:
|
|
cpu: "1"
|
|
memory: 512Mi
|
|
securityContext:
|
|
runAsUser: 0
|
|
capabilities:
|
|
add:
|
|
- NET_BIND_SERVICE
|
|
drop:
|
|
- ALL
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /ping
|
|
port: 80
|
|
scheme: HTTP
|
|
initialDelaySeconds: 10
|
|
periodSeconds: 10
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /ping
|
|
port: 80
|
|
scheme: HTTP
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 5
|
|
volumeMounts:
|
|
- name: data
|
|
mountPath: /data
|
|
volumes:
|
|
- name: data
|
|
hostPath:
|
|
path: /var/lib/zoo-ingress
|
|
type: DirectoryOrCreate
|
|
---
|
|
# IngressRoutes for Zoo services — hanzoai/ingress native CRD shape
|
|
# (kind: IngressRoute, apiVersion: hanzo.ai/v1alpha1). NO `kind: Ingress`
|
|
# with annotation-driven middleware — that was the Traefik compat path.
|
|
#
|
|
# Mainnet/testnet host names come from each overlay's ZooGateway CR;
|
|
# the routes themselves are static here. Per-env overlays patch the
|
|
# hosts in their network-patch.yaml ZooGateway and add explore-specific
|
|
# IngressRoutes if needed.
|
|
apiVersion: hanzo.ai/v1alpha1
|
|
kind: IngressRoute
|
|
metadata:
|
|
name: zoo-rpc
|
|
labels:
|
|
app.kubernetes.io/part-of: zoo
|
|
spec:
|
|
entryPoints:
|
|
- websecure
|
|
routes:
|
|
- match: Host(`api.zoo.network`)
|
|
kind: Rule
|
|
services:
|
|
- name: zoo-chain-rpc
|
|
port: 9630
|
|
passHostHeader: false
|
|
tls:
|
|
certResolver: letsencrypt
|
|
domains:
|
|
- main: api.zoo.network
|
|
---
|
|
apiVersion: hanzo.ai/v1alpha1
|
|
kind: IngressRoute
|
|
metadata:
|
|
name: zoo-gateway
|
|
labels:
|
|
app.kubernetes.io/part-of: zoo
|
|
spec:
|
|
entryPoints:
|
|
- websecure
|
|
routes:
|
|
- match: Host(`api.zoo.ngo`)
|
|
kind: Rule
|
|
services:
|
|
- name: zoo-gateway
|
|
port: 9630
|
|
passHostHeader: false
|
|
tls:
|
|
certResolver: letsencrypt
|
|
domains:
|
|
- main: api.zoo.ngo
|
|
---
|
|
apiVersion: hanzo.ai/v1alpha1
|
|
kind: IngressRoute
|
|
metadata:
|
|
name: zoo-explorer
|
|
labels:
|
|
app.kubernetes.io/part-of: zoo
|
|
spec:
|
|
entryPoints:
|
|
- websecure
|
|
routes:
|
|
- match: Host(`explore.zoo.network`)
|
|
kind: Rule
|
|
services:
|
|
- name: zoo-explorer-explorer
|
|
port: 8090
|
|
passHostHeader: false
|
|
- match: Host(`api-explore.zoo.network`)
|
|
kind: Rule
|
|
services:
|
|
- name: zoo-explorer-explorer
|
|
port: 8090
|
|
passHostHeader: false
|
|
tls:
|
|
certResolver: letsencrypt
|
|
domains:
|
|
- main: explore.zoo.network
|
|
- main: api-explore.zoo.network
|
|
---
|
|
apiVersion: hanzo.ai/v1alpha1
|
|
kind: IngressRoute
|
|
metadata:
|
|
name: zoo-bridge
|
|
labels:
|
|
app.kubernetes.io/part-of: zoo
|
|
spec:
|
|
entryPoints:
|
|
- websecure
|
|
routes:
|
|
- match: Host(`bridge.zoo.network`)
|
|
kind: Rule
|
|
services:
|
|
- name: zoo-bridge
|
|
port: 3000
|
|
passHostHeader: false
|
|
tls:
|
|
certResolver: letsencrypt
|
|
domains:
|
|
- main: bridge.zoo.network
|