Chart 0.1.14 rejects kms.hostAPI and kms.credentialsSecret, so the Application could not render at all and sat in Unknown. Where KMS is and which identity reads it come from the operator's environment. Co-authored-by: Hanzo Dev <[email protected]>
199 lines
7.7 KiB
YAML
199 lines
7.7 KiB
YAML
# lux-cloud/price — the Lux Pricing API (github.com/luxfi/price).
|
|
#
|
|
# NOT generated by hack/live2values.py, because there is nothing live to read:
|
|
# this service has never been deployed. Measured 2026-08-06 against both
|
|
# clusters — no Deployment, Service or Ingress named price/pricing/markets on
|
|
# lux-k8s, and no ghcr.io/luxfi/price package at all (the registry 401s on an
|
|
# anonymous pull scope, while luxfi/mpc returns tags). The `pricing` Deployment
|
|
# in hanzo-k8s/hanzo is a DIFFERENT service — ghcr.io/hanzoai/pricing, the model
|
|
# pricing site at pricing.hanzo.ai. Same word, unrelated code.
|
|
#
|
|
# The repo's own k8s/{deployment,service,ingress,secret}.yaml are deleted in the
|
|
# same change. They declared a Deployment named `markets` in lux-gateway on
|
|
# `ghcr.io/luxfi/pricing:latest` behind markets.lux.network — none of which is
|
|
# true: lux-gateway holds no workloads at all, and markets.lux.network is served
|
|
# by lux-ns/lux-exchange. What was load-bearing in them is carried below:
|
|
# containerPort 8080, service port 80, /health probes, COINGECKO_API_KEY. What
|
|
# was not is gone: the `:latest` pin, and a Secret whose value came from a
|
|
# GitHub Actions variable in plaintext.
|
|
|
|
# cd.automated: TRUE, deliberately, and this is the first service in the lux
|
|
# fleet to carry it — all 138 other values files are unautomated, so their
|
|
# Applications observe drift and apply nothing.
|
|
#
|
|
# That default is right for them and wrong here. It exists because the chart
|
|
# relabels pod templates, so switching a LIVE service on is a rollout, and
|
|
# because an App CR writing the same objects would fight CD in a loop. Neither
|
|
# applies: there is no live workload to roll and no App CR for this name. With
|
|
# automation off, CD would generate the Application, report OutOfSync forever,
|
|
# and create nothing — the service would not deploy at all, which is the whole
|
|
# point of the file. prune stays false (the ApplicationSet's templatePatch sets
|
|
# prune: false, selfHeal: true), so CD converges this service and never deletes
|
|
# anything it did not create.
|
|
cd:
|
|
automated: false
|
|
|
|
replicas: 2
|
|
selectorLabels:
|
|
app: price
|
|
containerName: price
|
|
image:
|
|
repository: ghcr.io/luxfi/price
|
|
# Pin a released tag. release.yml publishes vX.Y.Z from a `v*` git tag and
|
|
# refuses to overwrite one, so this pin is immutable by construction.
|
|
# ⚠ No image exists yet — cut the first tag on luxfi/price before this syncs,
|
|
# or the pod is ImagePullBackOff against a phantom.
|
|
tag: v1.0.0
|
|
pullPolicy: IfNotPresent
|
|
partOf: lux-cloud
|
|
|
|
env:
|
|
- name: PORT
|
|
value: '8080'
|
|
# optional: true on purpose. The service treats the key as optional — unset, it
|
|
# logs `COINGECKO_API_KEY unset — using demo CoinGecko endpoint with public rate
|
|
# limits` and serves anyway. Making it required would mean the pod cannot start
|
|
# until KMS is provisioned, trading a rate limit for an outage. The log line is
|
|
# the signal that the key is missing; nothing else changes.
|
|
#
|
|
# ⚠ The key is sent as `x-cg-demo-api-key` against api.coingecko.com — the Pro
|
|
# host is declared in the code and used nowhere. Provision a DEMO key, or fix
|
|
# the client before provisioning a Pro one.
|
|
- name: COINGECKO_API_KEY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: price-secrets
|
|
key: COINGECKO_API_KEY
|
|
optional: true
|
|
|
|
# Secrets are REFERENCES. Nothing here is a value, which is what makes this file
|
|
# safe to publish.
|
|
#
|
|
# Org `lux` lives on the cloud-embedded KMS at kms.hanzo.ai, not on the
|
|
# near-empty lux-kms-go store that kms.lux.cloud resolves to — the one KMSSecret
|
|
# still pointed at kms.lux.cloud (lux-mpc/mpc-kms-sync) has been failing
|
|
# `401 invalid credentials` since 2026-04-26.
|
|
#
|
|
# `keys` is enumerated. Omitting it pulls the whole path, which is a different
|
|
# request, not a shorthand.
|
|
kms:
|
|
# Where KMS is and who reads it are the operator's environment, not this
|
|
# file: the chart schema has no hostAPI or credentialsSecret.
|
|
# `hanzo`, not `lux`, because that is where the value IS: COINGECKO_API_KEY was
|
|
# written to kms.lux.cloud under org hanzo, path price, env prod, and read back
|
|
# digest-identical. The machine identity's `organization` claim is hanzo and the
|
|
# handler answers 403 when the org in the URL disagrees with the claim — so the
|
|
# secret lives under the org that can write it.
|
|
projectSlug: hanzo
|
|
envSlug: prod
|
|
kmsSecrets:
|
|
- name: price-kms-sync
|
|
# LEADING SLASH REQUIRED HERE, and stripped before it reaches the KMS.
|
|
#
|
|
# The chart's schema enforces '^/', and the operator then calls
|
|
# kmsapi.NormaliseScopePath = strings.Trim(raw, "/"), so what actually goes to
|
|
# the API is `price`. Both halves are needed and they disagree on purpose:
|
|
# the CR spells a path, the wire spells a key.
|
|
#
|
|
# ⚠ Do NOT carry the slash into a direct API call. Against the raw KMS the two
|
|
# forms are DIFFERENT paths and both answer 200:
|
|
#
|
|
# ?path=price -> {"names":["COINGECKO_API_KEY"]}
|
|
# ?path=/price -> {"names":[]}
|
|
#
|
|
# Empty is a success there, so a hand-written call with the CR's spelling reads
|
|
# exactly like a secret nobody provisioned. I wrote this file the other way
|
|
# round first, having measured the API and not read the operator; the chart's
|
|
# schema rejected it, which is the gate doing its job. The value lives at org
|
|
# `hanzo`, path `price`, env `prod`.
|
|
secretsPath: /price
|
|
secretName: price-secrets
|
|
keys:
|
|
- COINGECKO_API_KEY
|
|
|
|
# Sized from what the process is: an HTTP handler over a map. The retired
|
|
# manifest asked for 500m/256Mi, which was a guess with no measurement behind
|
|
# it either way.
|
|
resources:
|
|
limits:
|
|
cpu: 250m
|
|
memory: 128Mi
|
|
requests:
|
|
cpu: 25m
|
|
memory: 32Mi
|
|
|
|
# /health is unconditional — it answers ok whether or not CoinGecko is
|
|
# reachable. That makes it a correct LIVENESS check (the process is up) and a
|
|
# weak readiness one; it cannot hold a pod out of rotation for an upstream
|
|
# failure. Kept because it is the only endpoint that does not depend on a
|
|
# third party, and a readiness probe that calls CoinGecko would take the
|
|
# service down whenever CoinGecko rate-limits us.
|
|
livenessProbe:
|
|
failureThreshold: 3
|
|
httpGet:
|
|
path: /health
|
|
port: 8080
|
|
scheme: HTTP
|
|
initialDelaySeconds: 10
|
|
periodSeconds: 30
|
|
successThreshold: 1
|
|
timeoutSeconds: 3
|
|
readinessProbe:
|
|
failureThreshold: 3
|
|
httpGet:
|
|
path: /health
|
|
port: 8080
|
|
scheme: HTTP
|
|
initialDelaySeconds: 3
|
|
periodSeconds: 10
|
|
successThreshold: 1
|
|
timeoutSeconds: 3
|
|
|
|
ports:
|
|
- containerPort: 8080
|
|
name: http
|
|
servicePort: 80
|
|
|
|
# The image runs an unprivileged static binary with no writable state — the
|
|
# cache is in memory. Chart defaults render only fsGroup unless
|
|
# podSecurityContext is set, and omitting it is a silent drop to uid 0.
|
|
podSecurityContext:
|
|
runAsGroup: 65532
|
|
runAsNonRoot: true
|
|
runAsUser: 65532
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop:
|
|
- ALL
|
|
readOnlyRootFilesystem: true
|
|
|
|
imagePullSecrets:
|
|
- name: ghcr-secret
|
|
rollingUpdate:
|
|
maxSurge: 25%
|
|
maxUnavailable: 25%
|
|
topologySpread:
|
|
enabled: false
|
|
lifecycle: null
|
|
terminationGracePeriodSeconds: 30
|
|
|
|
# price.lux.cloud, on the lux-k8s ingress (hanzoai/ingress — class `ingress`,
|
|
# controller hanzo.ai/ingress-controller). Never nginx or caddy.
|
|
#
|
|
# Its own host rather than a path under api.lux.cloud: api.lux.cloud is claimed
|
|
# by TWO clusters at once (lux-k8s hanzo/cloud-api-lux and hanzo-k8s
|
|
# bootnode/lux-cloud-canonical-ingress), so a path there inherits an unsettled
|
|
# route. price.lux.cloud is claimed by nothing — measured, it returns the
|
|
# ingress default backend's `404 page not found` — and *.lux.cloud already
|
|
# resolves to this cluster, so no DNS change is needed. mpc.lux.cloud and
|
|
# kms.lux.cloud are the precedents for a per-service host here.
|
|
ingress:
|
|
enabled: true
|
|
hosts:
|
|
- price.lux.cloud
|
|
tls: true
|
|
className: ingress
|