Own the Zoo services, and only those

deploy/zoo carries the four services that had no declaration here at all --
exchange-api, status, and both exchanges -- so Zoo's own repo is where they are
declared. zoo.exchange among them serves its published bundle with no pod.

deploy/hanzo declared Hanzo's iam, billing, cloud, cloud-api and login. Hanzo
declares those too, so two Applications from two repos claimed the same objects
and CD said so: Deployment/iam is part of applications hanzo-iam and
zoo-hanzo-iam, and the same for billing. Whichever synced last decided what ran.

cert-manager was declared here, in Lux and in Hanzo. It is one cluster-scoped
install; Hanzo declares it.

otel-agent's ClusterRole and ClusterRoleBinding are cluster-scoped and carry one
name, so two brands declaring them is a collision by construction. One
declaration remains.
This commit is contained in:
2026-09-04 21:04:48 -07:00
parent ee93e29a7e
commit 9f2acfc210
13 changed files with 332 additions and 922 deletions
@@ -1,58 +0,0 @@
# Generated from the live cert-manager/cert-manager-cainjector Deployment on do-sfo3-zoo-k8s
# by hack/live2values.py. Live is the source: a repo of hand-applied
# manifests has unknown currency, the running fleet does not.
replicas: 1
selectorLabels:
app.kubernetes.io/component: cainjector
app.kubernetes.io/instance: cert-manager
app.kubernetes.io/name: cainjector
image:
repository: quay.io/jetstack/cert-manager-cainjector
tag: v1.20.1
digest: sha256:a3ff773a577d8ff6ad1fe9ab4ea0b74873e0341e0aac5d9411f3fc235f51bdf8
pullPolicy: IfNotPresent
component: cainjector
args:
- --v=2
- --leader-election-namespace=kube-system
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
resources: {}
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
ports:
- containerPort: 9402
name: http-metrics
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: cert-manager-cainjector
enableServiceLinks: false
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
topologySpread:
enabled: false
lifecycle: null
terminationGracePeriodSeconds: 30
defaultReadinessProbe: false
imagePullSecrets: []
# Live runs this pod with these settings and the chart renders only fsGroup
# unless podSecurityContext is set, so omitting them is a silent drop to uid 0
# (or to seccomp Unconfined) on the first server-side sync.
podSecurityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
# Live carries these; the chart drops any pod annotation it is not told about.
podAnnotations:
prometheus.io/path: "/metrics"
prometheus.io/port: "9402"
prometheus.io/scrape: "true"
@@ -1,84 +0,0 @@
# Generated from the live cert-manager/cert-manager-webhook Deployment on do-sfo3-zoo-k8s
# by hack/live2values.py. Live is the source: a repo of hand-applied
# manifests has unknown currency, the running fleet does not.
replicas: 1
selectorLabels:
app.kubernetes.io/component: webhook
app.kubernetes.io/instance: cert-manager
app.kubernetes.io/name: webhook
image:
repository: quay.io/jetstack/cert-manager-webhook
tag: v1.20.1
digest: sha256:b876bcaf46ace2a5e860e8371539a6e1c8ccca54b1988a97c8a7483b3815d985
pullPolicy: IfNotPresent
component: webhook
args:
- --v=2
- --secure-port=10250
- --dynamic-serving-ca-secret-namespace=$(POD_NAMESPACE)
- --dynamic-serving-ca-secret-name=cert-manager-webhook-ca
- --dynamic-serving-dns-names=cert-manager-webhook
- --dynamic-serving-dns-names=cert-manager-webhook.$(POD_NAMESPACE)
- --dynamic-serving-dns-names=cert-manager-webhook.$(POD_NAMESPACE).svc
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
resources: {}
livenessProbe:
failureThreshold: 3
httpGet:
path: /livez
port: healthcheck
scheme: HTTP
initialDelaySeconds: 60
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: healthcheck
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 1
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
ports:
- containerPort: 10250
name: https
servicePort: 443
targetPort: https
- containerPort: 6080
name: healthcheck
- containerPort: 9402
name: metrics
targetPort: http-metrics
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: cert-manager-webhook
enableServiceLinks: false
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
topologySpread:
enabled: false
lifecycle: null
terminationGracePeriodSeconds: 30
imagePullSecrets: []
# Live runs this pod with these settings and the chart renders only fsGroup
# unless podSecurityContext is set, so omitting them is a silent drop to uid 0
# (or to seccomp Unconfined) on the first server-side sync.
podSecurityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
-79
View File
@@ -1,79 +0,0 @@
# Generated from the live cert-manager/cert-manager Deployment on do-sfo3-zoo-k8s
# by hack/live2values.py. Live is the source: a repo of hand-applied
# manifests has unknown currency, the running fleet does not.
replicas: 1
selectorLabels:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: cert-manager
app.kubernetes.io/name: cert-manager
containerName: cert-manager-controller
image:
repository: quay.io/jetstack/cert-manager-controller
tag: v1.20.1
digest: sha256:9f9556b4b131554694c67c8229d231b1f7d69b882b5f061a56bafa465f3b22fc
pullPolicy: IfNotPresent
component: controller
args:
- --v=2
- --cluster-resource-namespace=$(POD_NAMESPACE)
- --leader-election-namespace=kube-system
- --acme-http01-solver-image=quay.io/jetstack/cert-manager-acmesolver:v1.20.1
- --max-concurrent-challenges=60
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
resources: {}
livenessProbe:
failureThreshold: 8
httpGet:
path: /livez
port: http-healthz
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 15
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
# The port name was `tcp-prometheus-servicemonitor`, 29 characters, and this file
# could not render at all: the chart's values.schema.json caps a port name at 15,
# which is correct — a CONTAINER port name is validated as an IANA_SVC_NAME (<=15),
# while a SERVICE port name is a DNS-1123 label (<=63). One values entry feeds
# both, so the longest legal name is 15. Live proved the asymmetry: the Service
# port carried the 29-char name while the container port next to it was
# `http-metrics`. Using the container's own name is therefore the only expressible
# answer, and it is free: there is no ServiceMonitor CRD on either cluster and
# nothing anywhere references the long name.
ports:
- containerPort: 9402
name: http-metrics
targetPort: http-metrics
- containerPort: 9403
name: http-healthz
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: cert-manager
enableServiceLinks: false
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
topologySpread:
enabled: false
lifecycle: null
terminationGracePeriodSeconds: 30
defaultReadinessProbe: false
imagePullSecrets: []
# Live runs this pod with these settings and the chart renders only fsGroup
# unless podSecurityContext is set, so omitting them is a silent drop to uid 0
# (or to seccomp Unconfined) on the first server-side sync.
podSecurityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
-60
View File
@@ -1,60 +0,0 @@
# Generated from the live hanzo/billing Deployment on do-sfo3-zoo-k8s
# by hack/live2values.py. Live is the source: a repo of hand-applied
# manifests has unknown currency, the running fleet does not.
replicas: 0
selectorLabels:
app: billing
image:
repository: ghcr.io/zooai/billing
tag: latest
digest: sha256:1f6e903262ee71cc5780d98fb0c7189f18da6a5e07150e1ab4d954d338869046
pullPolicy: IfNotPresent
partOf: hanzo-universe
env:
- name: COMMERCE_API_URL
value: http://commerce.hanzo.svc:8001
- name: IAM_URL
value: https://hanzo.id
- name: NEXT_PUBLIC_AUTH_ISSUER
value: https://hanzo.id
envFrom:
- secretRef:
name: billing-secrets
resources:
limits:
cpu: 200m
memory: 128Mi
requests:
cpu: 50m
memory: 32Mi
livenessProbe:
failureThreshold: 3
httpGet:
path: /
port: 80
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /
port: 80
scheme: HTTP
initialDelaySeconds: 3
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
ports:
- containerPort: 80
imagePullSecrets:
- name: ghcr-secret
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
topologySpread:
enabled: false
lifecycle: null
terminationGracePeriodSeconds: 30
-103
View File
@@ -1,103 +0,0 @@
# Generated from the live hanzo/cloud-api Deployment on do-sfo3-zoo-k8s
# by hack/live2values.py. Live is the source: a repo of hand-applied
# manifests has unknown currency, the running fleet does not.
replicas: 1
selectorLabels:
app: cloud-api
containerName: cloud
image:
repository: ghcr.io/hanzoai/cloud
tag: v1.786.123
digest: sha256:b8bdfa604943a018c16d59ed2fc0a7b52b4c0fe80d3ef37474c7f76780974fe8
pullPolicy: IfNotPresent
partOf: zoo-cloud
env:
- name: CLOUD_LISTEN
value: :8000
- name: CLOUD_HEALTH_LISTEN
value: :9090
- name: CLOUD_DATA_DIR
value: /var/lib/cloud
- name: RUNNING_IN_DOCKER
value: 'true'
- name: IAM_URL
value: http://iam.hanzo.svc.cluster.local
- name: IAM_ISSUER
value: https://zoolabs.id
- name: AUTH_ISSUER
value: https://zoolabs.id
- name: JWT_ISSUER
value: https://zoolabs.id
- name: JWKS_URL
value: http://iam.hanzo.svc.cluster.local/v1/iam/.well-known/jwks
- name: IAM_KEYS_URL
value: http://iam.hanzo.svc.cluster.local/v1/iam/.well-known/jwks
- name: IAM_AUDIENCE
value: zoo-cloud
- name: GATEWAY_ALLOWED_AUDIENCES
value: zoo-cloud,zoo-console,zoo-id,https://api.zoo.cloud
- name: AUTH_PUBLIC_PATHS
value: /healthz,/readyz,/__stats,/.well-known/,/favicon
- name: driverName
value: sqlite
- name: dbName
value: hanzo_cloud
- name: modelConfigPath
value: /conf/models.yaml
- name: TIKTOKEN_CACHE_DIR
value: /var/lib/cloud/tiktoken
resources:
limits:
cpu: '2'
memory: 2Gi
requests:
cpu: 500m
memory: 1Gi
volumeMounts:
- mountPath: /var/lib/cloud
name: cloud-data
- mountPath: /data/cloud
name: cloud-data
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 9090
scheme: HTTP
initialDelaySeconds: 20
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /readyz
port: 9090
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
ports:
- containerPort: 8000
name: http
- containerPort: 9090
name: ops
volumes:
- name: cloud-data
persistentVolumeClaim:
claimName: cloud-data
imagePullSecrets:
- name: ghcr-secret
fsGroup: 1000
strategy: Recreate
topologySpread:
enabled: false
lifecycle: null
terminationGracePeriodSeconds: 30
# Live runs this pod with these settings and the chart renders only fsGroup
# unless podSecurityContext is set, so omitting them is a silent drop to uid 0
# (or to seccomp Unconfined) on the first server-side sync.
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
-36
View File
@@ -1,36 +0,0 @@
# Generated from the live hanzo/cloud Deployment on do-sfo3-zoo-k8s
# by hack/live2values.py. Live is the source: a repo of hand-applied
# manifests has unknown currency, the running fleet does not.
replicas: 1
selectorLabels:
app: cloud
image:
repository: ghcr.io/zooai/cloud
tag: latest
digest: sha256:dae5d0561c4504103b62a68538a2f0a700a3f7559494cc20af8501c24835a18d
pullPolicy: IfNotPresent
partOf: hanzo-cloud
env:
- name: NEXT_PUBLIC_ANALYTICS_URL
value: https://analytics.hanzo.ai/script.js
- name: NEXT_PUBLIC_ANALYTICS_SITE_ID
value: d54f7730-8743-4ef1-9e02-1b10f456b798
resources:
limits:
cpu: 500m
memory: 1Gi
requests:
cpu: 100m
memory: 256Mi
ports:
- containerPort: 80
imagePullSecrets:
- name: ghcr-secret
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
topologySpread:
enabled: false
lifecycle: null
terminationGracePeriodSeconds: 30
defaultReadinessProbe: false
-70
View File
@@ -1,70 +0,0 @@
# Generated from the live hanzo/hanzo-login Deployment on do-sfo3-zoo-k8s
# by hack/live2values.py. Live is the source: a repo of hand-applied
# manifests has unknown currency, the running fleet does not.
replicas: 2
selectorLabels:
app: hanzo-login
image:
repository: ghcr.io/zooai/id
tag: latest
digest: sha256:937b61ba9c082b578e464f136e2e1342b6c641289c945f05028be476dba0db5b
pullPolicy: IfNotPresent
partOf: hanzo-universe
env:
- name: IAM_ORIGIN
value: https://iam.hanzo.ai
- name: NEXT_PUBLIC_IAM_URL
value: https://iam.hanzo.ai
- name: NEXT_PUBLIC_ORG
value: hanzo
- name: NEXT_PUBLIC_CLIENT_ID
value: app-hanzo
- name: NEXT_PUBLIC_ANALYTICS_URL
value: https://analytics.hanzo.ai/script.js
- name: NEXT_PUBLIC_ANALYTICS_SITE_ID
value: 9c0befac-aba7-4dcb-b350-c348f7a89b01
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 50m
memory: 128Mi
livenessProbe:
failureThreshold: 3
httpGet:
path: /login
port: 3000
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 5
readinessProbe:
failureThreshold: 3
httpGet:
path: /login
port: 3000
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
ports:
- containerPort: 3000
servicePort: 80
imagePullSecrets:
- name: ghcr-secret
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
topologySpread:
enabled: false
lifecycle: null
terminationGracePeriodSeconds: 30
# Enforcement switch, read by the zoo-fleet ApplicationSet's templatePatch --
# not by the chart. selfHeal returns a hand-edited object to what is written
# here; prune stays off, so nothing is ever deleted.
cd:
automated: true
-126
View File
@@ -1,126 +0,0 @@
# Generated from the live hanzo/iam Deployment on do-sfo3-zoo-k8s
# by hack/live2values.py. Live is the source: a repo of hand-applied
# manifests has unknown currency, the running fleet does not.
replicas: 0
selectorLabels:
app: iam
image:
repository: ghcr.io/zooai/iam
tag: latest
digest: sha256:174d7168b3bfdc625fa9bc1eb329ad902b711db44db0f7875c35f5b481c68816
pullPolicy: IfNotPresent
partOf: hanzo-universe
component: iam
env:
- name: GOMEMLIMIT
value: 1750MiB
- name: GOMAXPROCS
value: '2'
- name: REGISTRY_SIGNING_KEY_FILE
value: /etc/registry-signing/signing.key
- name: redisEndpoint
value: hanzo-kv:6379
- name: INSIGHTS_HOST
value: http://insights-capture.hanzo.svc:3000
- name: INSIGHTS_API_KEY
valueFrom:
secretKeyRef:
key: API_KEY
name: insights-client-secrets
optional: true
- name: IAM_DATABASE_URL
value: user=hanzo password=hanzo-pg-secret-6fcc8aeb host=sql-0.sql.hanzo.svc.cluster.local port=5432 dbname=iam sslmode=disable
resources:
limits:
cpu: '2'
memory: 2Gi
requests:
cpu: 200m
memory: 384Mi
volumeMounts:
- mountPath: /etc/registry-signing
name: registry-signing-key
readOnly: true
- mountPath: /etc/kms/auth
name: kms-auth
readOnly: true
- mountPath: /etc/iam-init-data
name: init-data
readOnly: true
- mountPath: /conf
name: iam-conf
readOnly: true
livenessProbe:
failureThreshold: 5
httpGet:
path: /api/health
port: 8000
scheme: HTTP
initialDelaySeconds: 60
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 10
readinessProbe:
failureThreshold: 3
httpGet:
path: /api/health
port: 8000
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
ports:
- containerPort: 8000
name: http
servicePort: 80
volumes:
- name: registry-signing-key
secret:
items:
- key: SIGNING_KEY
path: signing.key
secretName: registry-signing-key
- name: kms-auth
secret:
items:
- key: CLIENTID
path: clientId
optional: true
secretName: iam-kms-auth
- configMap:
name: iam-init-data
name: init-data
- configMap:
items:
- key: app.prod.conf
path: app.conf
name: iam-conf
name: iam-conf
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- iam
topologyKey: kubernetes.io/hostname
weight: 100
imagePullSecrets:
- name: ghcr-secret
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
topologySpread:
enabled: false
lifecycle:
preStop:
exec:
command:
- sh
- -c
- sleep 5
terminationGracePeriodSeconds: 45
-306
View File
@@ -1,306 +0,0 @@
# Zoo's node-local o11y collector — the same one hanzo-k8s and lux-k8s run.
#
# Zoo had NO observability at all before this — not a competing stack, an
# absent one. Four nodes producing nothing: no host metrics, no logs, no
# traces. This is the same agent hanzo and lux run, pointed at the same door.
#
# The ONLY difference from hanzo's agent is the endpoint: cloud's ZAP sockets
# are a ClusterIP over there, so from here they are reached through the `otlz`
# internal LoadBalancer on the shared DigitalOcean VPC. Everything else —
# image, receivers, processors, pipelines — is deliberately identical. If this
# file ever needs a Lux-specific receiver or a different wire, that is the
# signal that the plane has grown a second way of doing something.
replicas: 1
workload:
kind: DaemonSet
selectorLabels:
app: otel-agent
containerName: otel-agent
image:
# Pinned to the same build hanzo runs, and it must stay in step: this SHA is
# the first with per-pod ZAP node identity (aeff4d2) and lowercase span kind
# (9aab666). ghcr.io/hanzoai/otel-collector publishes no resolvable semver,
# so a tag would not pin anything.
repository: ghcr.io/hanzoai/otel-collector
#
# ⚠️ THIS SHA IS A PIN, NOT A WARNING SIGN — and the next bump is wanted.
# otel-collector main CONTAINS this build (`git merge-base --is-ancestor
# e321e94 origin/main` is true), and this build CARRIES the restart fix:
# plane needs: nodeID gained a per-process boot nonce (otel-collector e321e94),
# so a collector that restarts inside its pod arrives as a NEW ZAP peer instead
# of reclaiming an identity the receiver still holds and being refused with EOF
# forever. Four incidents on 2026-08-08 each ended with a manual delete of the
# pod; that is exactly what the nonce removes. Build main and move these pins.
#
# (An earlier revision of this comment said main had diverged onto an OTLP proto
# wire the receiver could not read, and called it measured. It was measured
# against a STALE LOCAL CHECKOUT, not the remote — origin/main has the
# MetricBatch wire and luxfi/zap, same as this image. Check origin, never a
# local main.)
tag: sha-e321e94
digest: sha256:2ec000a6cc6eec78bc5422942284cd33ce5e70013e352f37e3296a6c2edbafb3
pullPolicy: IfNotPresent
imagePullSecrets: []
# EMPTY ON PURPOSE — the chart defaults this to `ghcr-secret`, and on THIS
# cluster that credential is user Darkhorse7stars, whose token the ghcr token
# endpoint answers 403. ghcr.io/hanzoai/otel-collector is public: an
# anonymous pull of this exact tag returns 200, and a credential-free pod on
# a lux node pulls it in seconds. Sending a rejected credential turns a public
# image into ImagePullBackOff, which is how all nine agents and the gateway
# sat unschedulable while the image was freely available the whole time.
#
# (hanzo-k8s has a secret of the SAME NAME holding hanzo-dev, which does
# authenticate — so the chart default is right there and wrong here. lux's
# ghcr-secret being dead is a separate problem: nothing new can be pulled on
# this cluster from a private hanzoai package, and everything still running
# is running from node cache.)
partOf: o11y
component: observability
args:
- --config=/etc/otel/otel-agent.yaml
env:
- name: K8S_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
# kubeletstats dials the node's ADDRESS. Node names are not DNS records on
# DOKS, so ${K8S_NODE_NAME} there fails every interval with "no such host"
# while the collector otherwise looks healthy.
- name: K8S_NODE_IP
valueFrom:
fieldRef:
fieldPath: status.hostIP
- name: OTEL_RESOURCE_ATTRIBUTES
value: k8s.node.name=$(K8S_NODE_NAME)
rbac:
create: true
serviceAccountName: otel-agent
clusterRules:
# k8sattributes: resolve pod/namespace/workload metadata.
- apiGroups: ['']
resources: ['pods', 'namespaces', 'nodes']
verbs: ['get', 'list', 'watch']
- apiGroups: ['apps']
resources: ['replicasets', 'deployments', 'daemonsets', 'statefulsets']
verbs: ['get', 'list', 'watch']
# kubeletstats: this node's kubelet summary API.
- apiGroups: ['']
resources: ['nodes/stats', 'nodes/proxy']
verbs: ['get']
tolerations:
- operator: Exists
ports:
- containerPort: 13133
name: health
- containerPort: 4317
name: otlp-grpc
- containerPort: 4318
name: otlp-http
resources:
requests:
cpu: 50m
memory: 192Mi
ephemeral-storage: 512Mi
limits:
cpu: 500m
memory: 1Gi
readinessProbe:
httpGet:
path: /
port: 13133
initialDelaySeconds: 5
periodSeconds: 15
volumeMounts:
- name: config
mountPath: /etc/otel
readOnly: true
- name: varlogpods
mountPath: /var/log/pods
readOnly: true
- name: checkpoint
mountPath: /var/lib/otel-agent
- name: hostfs
mountPath: /hostfs
readOnly: true
mountPropagation: HostToContainer
volumes:
- name: config
configMap:
name: otel-agent-config
- name: varlogpods
hostPath:
path: /var/log/pods
type: Directory
- name: checkpoint
hostPath:
path: /var/lib/otel-agent
type: DirectoryOrCreate
- name: hostfs
hostPath:
path: /
type: Directory
configMaps:
- name: otel-agent-config
data:
otel-agent.yaml: |
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
filelog:
include:
- /var/log/pods/*/*/*.log
exclude:
- /var/log/pods/zoo-system_otel-agent-*/**/*.log
start_at: end
include_file_path: true
include_file_name: false
retry_on_failure:
enabled: true
operators:
- type: container
add_metadata_from_filepath: true
# Host metrics — what node-exporter used to answer, at a rate this
# datastore can carry. 300s, not 30s: the OTLZ exporter emits one
# message per resource and cloud turns each into one INSERT, fanning
# out through seven tables. At 30s across 45 agents that helped push
# event.metric_30m over its part ceiling and the server rejected every
# metric write for an hour. Five-minute resolution is the right trade —
# the finest rollup in this store is five minutes wide.
hostmetrics:
collection_interval: 300s
root_path: /hostfs
scrapers:
cpu:
load:
memory:
paging:
disk:
filesystem:
network:
# [node] ONLY: pod and container each model as their own resource,
# which is one INSERT each.
kubeletstats:
collection_interval: 300s
auth_type: serviceAccount
# The node's IP, not its name — node names are not DNS on DOKS.
endpoint: https://${env:K8S_NODE_IP}:10250
insecure_skip_verify: true
metric_groups: [node]
processors:
memory_limiter:
check_interval: 5s
limit_mib: 800
spike_limit_mib: 160
k8sattributes:
auth_type: serviceAccount
passthrough: false
extract:
metadata:
- k8s.namespace.name
- k8s.pod.name
- k8s.pod.uid
- k8s.deployment.name
- k8s.statefulset.name
- k8s.daemonset.name
- k8s.node.name
- k8s.container.name
pod_association:
- sources:
- from: resource_attribute
name: k8s.pod.uid
- sources:
- from: connection
resourcedetection:
detectors: [env, system]
timeout: 2s
override: false
resource:
attributes:
# Both clusters write to ONE store, so the cluster has to be on
# the series or a lux node and a hanzo node are indistinguishable.
# vmagent carried this as -remoteWrite.label=cluster=zoo-k8s; this
# is the same fact under its OpenTelemetry name.
- key: k8s.cluster.name
value: zoo-k8s
action: upsert
- key: service.namespace
value: zoo
action: upsert
- key: deployment.environment
value: production
action: upsert
batch:
timeout: 2s
send_batch_size: 2048
send_batch_max_size: 4096
# Metrics get their OWN batch settings, far coarser than logs.
#
# Every insert into event.metric fans out through the fill_metric_5m
# and fill_metric_30m materialized views, so ONE insert becomes THREE
# parts. At batch/timeout 2s across 42 collectors that is ~60 inserts
# a second and ~180 parts a second, and ClickHouse merges cannot keep
# up: event.metric_30m reached 3,015 parts averaging 3.91 KiB and the
# server began answering code 252 "Too many parts" to EVERY metric
# write — killing app telemetry that had been fine for months, not
# just the new host metrics.
#
# Fewer, larger inserts is the fix. 60s/16384 cuts the part rate by
# ~30x and costs a minute of latency on data whose finest rollup is
# five minutes wide.
batch/metrics:
timeout: 60s
send_batch_size: 8192
send_batch_max_size: 16384
exporters:
# One exporter per signal: cloud binds one socket per signal, and the
# ZAP node identity derives from the component ID, so two exporters
# sharing a name would be two nodes claiming one identity and the
# second would be refused.
zap/logs:
endpoint: 10.124.0.71:4318
retry_on_failure:
enabled: true
zap/traces:
endpoint: 10.124.0.71:4317
retry_on_failure:
enabled: true
zap/metrics:
endpoint: 10.124.0.71:4319
retry_on_failure:
enabled: true
extensions:
health_check:
endpoint: 0.0.0.0:13133
service:
extensions: [health_check]
pipelines:
logs:
receivers: [filelog, otlp]
processors: [memory_limiter, k8sattributes, resourcedetection, resource, batch]
exporters: [zap/logs]
traces:
receivers: [otlp]
processors: [memory_limiter, k8sattributes, resourcedetection, resource, batch]
exporters: [zap/traces]
metrics:
receivers: [hostmetrics, kubeletstats]
processors: [memory_limiter, k8sattributes, resourcedetection, resource, batch/metrics]
exporters: [zap/metrics]
telemetry:
logs:
level: info
encoding: json
cd:
automated: true
+99
View File
@@ -0,0 +1,99 @@
# zoo.exchange's own market data.
#
# Zoo's brand.json pointed api.graphql at api-exchange.lux.network — LUX's
# exchange-api — so a Zoo-branded site read Lux's factory and printed Lux's
# number under a ZOO label: "Total ZOO TVL $125,998.38" against the Lux
# factory's $125,998.82, to the cent. One deployment per chain is the fix, and
# it is the shape the image was built for.
#
# NETWORK is the whole configuration. exchange-api's networks.ts is the one
# table of "which chain + which contracts + which graph + which tokens", and it
# now carries zoo: chainId 200200, the explorer's zoo/amm graph,
# api.zoo.network for RPC, ZOO as the native coin, ZOO_TOKENS as the curated
# registry. Restating any of that here would be a second copy free to drift from
# the first.
#
# 2.3.15 is the floor for dropping them. graphql.ts defaulted its AMM graph to a
# hardcoded cchain URL where subgraph.ts used the network's, so with the
# override gone the shaped responses came from Zoo's graph and every raw query
# went to Lux's — $126.0K of PROTOCOL TVL over a table of WLUX and LZOO.
#
# Before the zoo entry existed this ran NETWORK=mainnet with the URLs overridden by
# hand, which declared chainId 96369 over a chain answering 200200: the markets
# gate's chainId assertion fired on every poll and the router quoted against
# Lux's V3 addresses. A rollback below 2.3.13 brings that back AND silently
# resolves to Lux's graph, since an unknown NETWORK falls through to mainnet —
# which is what the digest pin is for.
#
# The digest is not decoration for a second reason: ghcr.io/luxfi/exchange-api
# carries TWO unrelated programs. This one (luxfi/exchange-api, TypeScript,
# /v1/graphql with amm/dex routing, versions 2.x) and the older express
# server.js that luxfi/exchange's release matrix pushes to the same package
# under the SPA's 5.149.x versions. That one has no /v1/graphql route at all.
cd:
automated: false
replicas: 2
image:
repository: ghcr.io/luxfi/exchange-api
tag: 2.3.34
digest: sha256:007beb8353679600b38beaccabdd74fd400e5299f74703fdc787dd5e613cef45
pullPolicy: IfNotPresent
env:
- name: PORT
value: '4000'
- name: NETWORK
value: zoo
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
livenessProbe:
failureThreshold: 3
httpGet:
path: /health
port: 4000
scheme: HTTP
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /health
port: 4000
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
ports:
- containerPort: 4000
# Only the two that exist in this namespace. lux-ns/exchange-api also names
# ghcr-luxfi; there is no such Secret in zoo, and a named-but-absent pull secret
# is a FailedToRetrieveImagePullSecret warning on every pod for nothing.
imagePullSecrets:
- name: ghcr-luxfi-fresh
- name: ghcr-secret
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
topologySpread:
enabled: false
# exchange.zoo.cloud mirrors exchange.lux.cloud: a centralized service answers on
# .cloud, and .network stays the chain's own decentralized surface. No CORS
# annotation — the image already sends Access-Control-Allow-Origin itself, and an
# edge that writes the header a second time is how a page renders empty with no
# failed request to point at.
#
# The A record for this host is in k8s/dns/zoo-cloud.yaml. Nothing here creates
# one: no external-dns runs on this cluster and the zone has no wildcard, so an
# Ingress alone gets a valid certificate for a name that resolves to nothing.
ingress:
enabled: true
hosts:
- exchange.zoo.cloud
tls: true
className: ingress
+94
View File
@@ -0,0 +1,94 @@
# Generated from the live zoo/status Deployment on do-sfo3-lux-k8s
# by hack/live2values.py. Live is the source: a repo of hand-applied
# manifests has unknown currency, the running fleet does not.
replicas: 1
selectorLabels:
app: status
image:
repository: ghcr.io/hanzoai/status
tag: 1.1.6
pullPolicy: Always
# One manifest under that tag, linux/amd64. A property of the image, so it lives
# beside it: no effect on an all-amd64 cluster, and on a mixed one it stops the
# pod binding to a node that has no manifest to run.
nodeSelector:
kubernetes.io/arch: amd64
env:
- name: GATUS_CONFIG_PATH
value: /config/config.yaml
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 50m
memory: 128Mi
volumeMounts:
- mountPath: /config
name: config
readOnly: true
- mountPath: /data
name: data
livenessProbe:
failureThreshold: 3
httpGet:
path: /health
port: 8080
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /health
port: 8080
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
ports:
- containerPort: 8080
name: http
servicePort: 80
# Gatus writes its uptime history here, and the claim was the object
# live2values.py could not reach: it reads the Deployment, and a claim a
# Deployment only MOUNTS is not part of one. So the values named a volume nothing
# declared, and the scheduler answered that no node is available — indefinitely,
# without ever attempting a pull. 2Gi RWO matches the claim this page already has
# wherever it runs; a request below an existing volume's capacity is refused.
#
# storageClass is deliberately absent: naming one would pin this file to a single
# cluster's provisioner, while the default resolves to the block class in the
# cloud and to local-path on k3s. One text, either place.
claims:
- name: status-data
size: 2Gi
accessModes:
- ReadWriteOnce
volumes:
- configMap:
name: status-config
name: config
- name: data
persistentVolumeClaim:
claimName: status-data
imagePullSecrets:
- name: ghcr-secret
strategy: Recreate
topologySpread:
enabled: false
lifecycle: null
terminationGracePeriodSeconds: 30
ingress:
enabled: true
hosts:
- status.zoo.network
tls: true
# The live certificate lives in this Secret. The chart would default to
# <release>-tls, and cert-manager would then issue into a NEW empty Secret
# while the host serves no cert at all until it succeeds.
secretName: status-zoo-tls
className: ingress
+102
View File
@@ -0,0 +1,102 @@
# Generated from the live zoo/zoo-exchange-testnet Deployment on do-sfo3-lux-k8s
# by hack/live2values.py. Live is the source: a repo of hand-applied
# manifests has unknown currency, the running fleet does not.
replicas: 2
selectorLabels:
app: zoo-exchange-testnet
containerName: spa
# The same brand-neutral SPA every other exchange runs, configured for Zoo's
# testnet by the SPA_* values below — one image, one build, one lineage.
#
# This pointed at ghcr.io/zooai/exchange:latest, which is a separately built
# copy that has not been rebuilt since June and carries no version at all: that
# package holds `sha-e9dc558` and `cache`, and no `latest` among them. A
# floating tag names nothing a deployment can be held to.
image:
repository: ghcr.io/luxfi/exchange
tag: 5.149.66
pullPolicy: IfNotPresent
labels:
brand: zoo
env: testnet
env:
- name: PORT
value: '3000'
- name: ROOT
value: /public
- name: SPA_BRAND_NAME
value: Zoo Exchange (Testnet)
- name: SPA_DEFAULT_CHAIN
value: '200201'
- name: SPA_RPC_HOST
value: https://api.zoo-test.network
- name: SPA_API_HOST
value: https://api.zoo-test.network
- name: SPA_GATEWAY_HOST
value: https://api.zoo-test.network
- name: SPA_IAM_HOST
value: https://zoolabs.id
- name: SPA_ID_HOST
value: https://zoolabs.id
- name: SPA_KMS_HOST
value: https://kms.zoo.network
- name: SPA_INSIGHTS_HOST
value: https://insights.hanzo.ai
envFrom:
- secretRef:
name: zoo-exchange-secrets
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 50m
memory: 64Mi
livenessProbe:
failureThreshold: 3
httpGet:
path: /health
port: http
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /health
port: http
scheme: HTTP
initialDelaySeconds: 2
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 1
ports:
- containerPort: 3000
name: http
targetPort: http
imagePullSecrets:
- name: ghcr-luxfi-fresh
- name: ghcr-zoo
- name: ghcr-secret
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
topologySpread:
enabled: false
lifecycle: null
terminationGracePeriodSeconds: 30
ingress:
enabled: true
hosts:
- exchange.zoo-test.network
- exchange-test.zoo.network
tls: true
className: ingress
# Enforcement switch, read by the lux-fleet ApplicationSet's templatePatch --
# not by the chart. selfHeal returns a hand-edited object to what is written
# here; prune stays off, so nothing is ever deleted.
cd:
automated: false
+37
View File
@@ -0,0 +1,37 @@
# zoo.exchange is files: one Middleware that serves them, one route that names it,
# and the endpoint-less Service the route schema requires. No image, so the
# chart renders no Deployment -- a static site is not a pod.
#
# The bundle at s3://hanzo-sites/lux/zoo-exchange is Zoo's own build, not a
# re-skin of lux-exchange: its brand.json names Zoo Exchange, Zoo Labs Foundation
# and zoo.exchange, so the brand travels as a file in the prefix rather than as a
# mounted ConfigMap the pod had to remount at a subPath.
cd:
automated: true
ports:
- name: http
containerPort: 80
middlewares:
- name: exchange
spec:
staticFiles:
root: s3://hanzo-sites/lux/zoo-exchange
spaMode: true
spaIndex: index.html
indexFiles:
- index.html
routes:
- name: exchange
spec:
entryPoints:
- websecure
routes:
- match: Host(`zoo.exchange`) || Host(`www.zoo.exchange`) || Host(`exchange.zoo.network`) || Host(`trade.zoo.exchange`)
kind: Rule
priority: 100
middlewares:
- name: exchange
services:
- name: zoo-exchange
port: 80
tls: {}