backend redesign

This commit is contained in:
2026-03-11 18:47:11 -04:00
parent 8ff277c8c6
commit e99ef5d2dd
210 changed files with 12147 additions and 155 deletions

View File

@@ -1,38 +0,0 @@
FROM python:3.14-alpine
# Set working directory
WORKDIR /app
# Copy requirements first for better caching
COPY backend/requirements.txt /app/requirements.txt
# Install TA-Lib C library and build dependencies, then install Python dependencies and clean up
RUN apk add --no-cache --virtual .build-deps \
gcc \
g++ \
make \
musl-dev \
wget \
tar \
cargo \
rust \
&& wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz \
&& tar -xzf ta-lib-0.4.0-src.tar.gz \
&& cd ta-lib/ \
&& ./configure --prefix=/usr \
&& make \
&& make install \
&& cd .. \
&& rm -rf ta-lib ta-lib-0.4.0-src.tar.gz \
&& pip install --no-cache-dir -r requirements.txt \
&& apk del .build-deps \
&& rm -rf /var/cache/apk/* /root/.cache /root/.cargo /root/.rustup
# Copy application code
COPY backend/src /app/src
# Expose port
EXPOSE 8000
# Run the application
CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@@ -1,65 +0,0 @@
FROM python:3.12-slim
ARG CONFIG=production
# Install TA-Lib C library early for better layer caching
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
gcc \
g++ \
make \
wget \
ca-certificates \
&& wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz \
&& tar -xzf ta-lib-0.4.0-src.tar.gz \
&& cd ta-lib/ \
&& ./configure --prefix=/usr \
&& make \
&& make install \
&& cd .. \
&& rm -rf ta-lib ta-lib-0.4.0-src.tar.gz \
&& apt-get purge -y --auto-remove gcc g++ make wget \
&& rm -rf /var/lib/apt/lists/*
# Install Python build dependencies early for better layer caching
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
gcc \
g++ \
cargo \
rustc
# Install compiled packages - separate layer so requirements.txt changes don't trigger recompilation
COPY backend/requirements-pre.txt /app/requirements-pre.txt
RUN --mount=type=cache,target=/root/.cache/pip \
--mount=type=cache,target=/root/.cargo \
pip install --no-cache-dir -r /app/requirements-pre.txt \
&& apt-get purge -y --auto-remove gcc g++ cargo rustc \
&& rm -rf /var/lib/apt/lists/* /root/.rustup /tmp/*
# Set working directory
WORKDIR /app
# Copy and install remaining requirements
COPY backend/requirements.txt /app/requirements.txt
# Install Python dependencies and clean up
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY backend/src /app/src
COPY backend/config*.yaml /tmp/
RUN if [ -f /tmp/config-${CONFIG}.yaml ]; then \
cp /tmp/config-${CONFIG}.yaml /app/config.yaml; \
else \
cp /tmp/config.yaml /app/config.yaml; \
fi && rm -rf /tmp/config*.yaml
# Add src to PYTHONPATH for correct module resolution
ENV PYTHONPATH=/app/src
# Expose port
EXPOSE 8000
# Run the application
CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@@ -1,19 +0,0 @@
FROM node:20-alpine
# Set working directory
WORKDIR /app
# Copy package files first for better caching
COPY web/package*.json /app/
# Install dependencies
RUN npm install
# Copy application code
COPY web /app/
# Expose port
EXPOSE 5173
# Run dev server (for development/debug)
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]

View File

@@ -1,38 +0,0 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ai-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- dexorder.ai
secretName: dexorder-ai-tls
rules:
- host: dexorder.ai
http:
paths:
- path: /charting_library
pathType: Prefix
backend:
service:
name: ai-web
port:
number: 5173
- path: /cryptochimp
pathType: Prefix
backend:
service:
name: ai-web
port:
number: 5173
- path: /ws
pathType: Prefix
backend:
service:
name: ai-backend
port:
number: 8000

287
deploy/k8s/README.md Normal file
View File

@@ -0,0 +1,287 @@
# Kubernetes Deployment
This directory contains Kubernetes manifests using [Kustomize](https://kustomize.io/) for managing dev and production environments.
## Structure
```
deploy/k8s/
├── base/ # Base manifests (shared)
│ ├── backend.yaml
│ ├── web.yaml
│ ├── ingress.yaml
│ ├── init.yaml
│ └── kustomization.yaml
├── dev/ # Dev overlay (minikube)
│ ├── infrastructure.yaml # Kafka, Postgres, MinIO, Flink, Relay, Ingestor
│ ├── ingress-dev.yaml # Dev ingress (dexorder.local)
│ ├── patches.yaml # Dev-specific patches
│ ├── kustomization.yaml
│ └── secrets/
│ ├── *.yaml # Actual secrets (gitignored)
│ └── *.yaml.example # Templates
├── prod/ # Production overlay
│ ├── patches.yaml # Prod patches (replicas, resources, gVisor)
│ ├── kustomization.yaml
│ └── secrets/
│ ├── *.yaml # Actual secrets (gitignored)
│ └── *.yaml.example # Templates
└── configmaps/ # Shared ConfigMaps
├── relay-config.yaml
├── ingestor-config.yaml
└── flink-config.yaml
```
## Dev Environment (Minikube)
### Prerequisites
- [minikube](https://minikube.sigs.k8s.io/docs/start/)
- [kubectl](https://kubernetes.io/docs/tasks/tools/)
- Docker
### Quick Start
```bash
# Start everything
bin/dev start
# Access the application
# Web UI: http://dexorder.local/cryptochimp/
# Backend: ws://dexorder.local/ws
# In another terminal, start tunnel for ingress
bin/dev tunnel
```
### Managing Dev Environment
```bash
# Rebuild images after code changes
bin/dev rebuild
# Redeploy services
bin/dev deploy
# Full restart (rebuild + redeploy)
bin/dev restart
# View status
bin/dev status
# View logs
bin/dev logs relay
bin/dev logs ingestor
bin/dev logs flink-jobmanager
# Open shell in pod
bin/dev shell relay
# Clean everything
bin/dev clean
# Stop minikube
bin/dev stop
```
### Setting Up Secrets (Dev)
```bash
# Copy example secrets
cd deploy/k8s/dev/secrets/
cp ai-secrets.yaml.example ai-secrets.yaml
cp postgres-secret.yaml.example postgres-secret.yaml
cp minio-secret.yaml.example minio-secret.yaml
cp ingestor-secrets.yaml.example ingestor-secrets.yaml
# Edit with actual values
vim ai-secrets.yaml # Add your Anthropic API key
# Apply to cluster
bin/secret-update dev
# Or update a specific secret
bin/secret-update dev ai-secrets
```
### Updating Configs (Dev)
```bash
# Edit config files
vim deploy/configmaps/relay-config.yaml
# Apply changes
bin/config-update dev
# Or update specific config
bin/config-update dev relay-config
```
### Dev vs Docker Compose
The minikube dev environment mirrors production more closely than docker-compose:
| Feature | docker-compose | minikube |
|---------|---------------|----------|
| Environment parity | ❌ Different from prod | ✅ Same as prod |
| Secrets management | `.env` files | K8s Secrets |
| Configuration | Volume mounts | ConfigMaps |
| Service discovery | DNS by service name | K8s Services |
| Ingress/routing | Port mapping | nginx-ingress |
| Resource limits | Limited support | Full K8s resources |
| Init containers | No | Yes |
| Readiness probes | No | Yes |
## Production Environment
### Prerequisites
- Access to production Kubernetes cluster
- `kubectl` configured with production context
- Production secrets prepared
### Setting Up Secrets (Prod)
```bash
# Copy example secrets
cd deploy/k8s/prod/secrets/
cp ai-secrets.yaml.example ai-secrets.yaml
cp postgres-secret.yaml.example postgres-secret.yaml
# ... etc
# Edit with production values
vim ai-secrets.yaml
# Apply to cluster (will prompt for confirmation)
bin/secret-update prod
# Or update specific secret
bin/secret-update prod ai-secrets
```
### Updating Configs (Prod)
```bash
# Edit production configs if needed
vim deploy/configmaps/relay-config.yaml
# Apply changes (will prompt for confirmation)
bin/config-update prod
```
### Deploying to Production
```bash
# Verify kubectl context
kubectl config current-context
# Apply manifests
kubectl apply -k deploy/k8s/prod/
# Check rollout status
kubectl rollout status statefulset/ai-backend
kubectl rollout status deployment/ai-web
# View status
kubectl get pods,svc,ingress
```
## Kustomize Overlays
### Dev Overlay
- **imagePullPolicy: Never** - Uses locally built images
- **Infrastructure services** - Kafka, Postgres, MinIO, Flink, Relay, Ingestor
- **Local ingress** - `dexorder.local` (requires `/etc/hosts` entry)
- **No gVisor** - RuntimeClass removed (not available in minikube)
- **Single replicas** - Minimal resource usage
### Prod Overlay
- **imagePullPolicy: Always** - Pulls from registry
- **Multiple replicas** - HA configuration
- **Resource limits** - CPU/memory constraints
- **gVisor** - Security sandbox via RuntimeClass
- **Production ingress** - `dexorder.ai` with TLS
## Infrastructure Services (Dev Only)
These services are included in the dev environment but are expected to be managed separately in production:
- **Kafka** - KRaft mode (no Zookeeper), single broker
- **PostgreSQL** - Iceberg catalog metadata
- **MinIO** - S3-compatible object storage
- **Iceberg REST Catalog** - Table metadata
- **Flink** - JobManager + TaskManager
- **Relay** - ZMQ message router
- **Ingestor** - CCXT data fetcher
In production, you would typically use:
- Managed Kafka (Confluent Cloud, MSK, etc.)
- Managed PostgreSQL (RDS, Cloud SQL, etc.)
- Object storage (S3, GCS, Azure Blob)
- Flink Kubernetes Operator or managed Flink
## Troubleshooting
### Minikube not starting
```bash
minikube delete
minikube start --cpus=6 --memory=12g --driver=docker
```
### Images not found
Make sure you're using minikube's docker daemon:
```bash
eval $(minikube docker-env)
bin/dev rebuild
```
### Ingress not working
Start minikube tunnel in another terminal:
```bash
bin/dev tunnel
```
### Secrets not found
Create secrets from examples:
```bash
cd deploy/k8s/dev/secrets/
cp *.example *.yaml
vim ai-secrets.yaml # Edit with actual values
bin/secret-update dev
```
### Pods not starting
Check events and logs:
```bash
kubectl get events --sort-by=.metadata.creationTimestamp
kubectl describe pod <pod-name>
kubectl logs <pod-name>
```
## CI/CD Integration
For automated deployments, you can use:
```bash
# Build and push images
docker build -t registry.example.com/dexorder/ai-web:$TAG .
docker push registry.example.com/dexorder/ai-web:$TAG
# Update kustomization with new tag
cd deploy/k8s/prod
kustomize edit set image dexorder/ai-web=registry.example.com/dexorder/ai-web:$TAG
# Deploy
kubectl apply -k deploy/k8s/prod/
```

View File

@@ -0,0 +1,17 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ai-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- dexorder.ai
secretName: dexorder-ai-tls
rules:
- host: dexorder.ai
http:
paths: []

View File

@@ -0,0 +1,9 @@
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: gvisor
overhead:
podFixed:
memory: "64Mi"
cpu: "0m"

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources: []
# ingress.yaml - removed until we have services to expose

View File

@@ -0,0 +1,40 @@
# Flink Job Configuration
# ZeroMQ bind address and ports
zmq_bind_address: "tcp://*"
zmq_ingestor_work_queue_port: 5555
zmq_ingestor_response_port: 5556
zmq_ingestor_control_port: 5557
zmq_market_data_pub_port: 5558
zmq_client_request_port: 5559
zmq_cep_webhook_port: 5560
# Notification endpoints
# Task managers PUSH to job manager PULL socket at this address
notification_publish_endpoint: "tcp://flink-jobmanager:5561"
# Job manager binds PULL socket on this port to receive from task managers
notification_pull_port: 5561
# Kafka configuration
kafka_bootstrap_servers: "kafka:9092"
kafka_tick_topic: "market-tick"
kafka_ohlc_topic: "market-ohlc"
kafka_topics_file: "/topics-dev.yaml" # Use topics-dev.yaml for single broker dev environment
# Iceberg catalog
iceberg_catalog_uri: "http://iceberg-catalog:8181"
iceberg_warehouse: "s3://trading-warehouse/"
iceberg_namespace: "trading"
iceberg_table_prefix: "market"
hadoop_conf_dir: "/etc/hadoop/conf"
# Flink configuration
flink_parallelism: 1
flink_checkpoint_interval_ms: 60000
# Flink memory configuration (required)
jobmanager.memory.process.size: 1600m
taskmanager.memory.process.size: 1728m
taskmanager.numberOfTaskSlots: 2
jobmanager.rpc.address: flink-jobmanager
jobmanager.rpc.port: 6123

View File

@@ -0,0 +1,24 @@
# CCXT Ingestor Configuration
# Relay ZMQ endpoints (relay is the well-known gateway)
flink_hostname: relay
ingestor_work_port: 5555 # SUB - receives DataRequest with exchange prefix
# Note: No response port needed - async architecture via Kafka!
# Supported exchanges (subscribe to these prefixes)
supported_exchanges:
- BINANCE
- COINBASE
- KRAKEN
# Kafka configuration
kafka_brokers:
- kafka:9092
kafka_topic: market-ohlc
# Worker configuration
max_concurrent: 10
poll_interval_ms: 10000
# Logging
log_level: info

View File

@@ -0,0 +1,19 @@
# ZMQ Relay Configuration
# Bind address for all relay sockets
bind_address: "tcp://*"
# Client-facing ports
client_request_port: 5559 # ROUTER - Client historical data requests
market_data_pub_port: 5558 # XPUB - Market data fanout to clients
# Ingestor-facing ports
ingestor_work_port: 5555 # PUB - Distribute work with exchange prefix
ingestor_response_port: 5556 # ROUTER - Receive responses from ingestors
# Flink connection
flink_market_data_endpoint: "tcp://flink-jobmanager:5558" # XSUB - Subscribe to Flink market data (MARKET_DATA_PUB)
# Timeouts and limits
request_timeout_secs: 30 # Timeout for pending client requests
high_water_mark: 10000 # ZMQ high water mark for all sockets

View File

@@ -0,0 +1,519 @@
---
# Kafka (KRaft mode - no Zookeeper needed)
# Using apache/kafka:3.9.0 instead of confluentinc/cp-kafka because:
# - cp-kafka's entrypoint script has issues with KRaft configuration
# - apache/kafka allows explicit command configuration
# - For production, use Strimzi operator (see kafka/ directory)
apiVersion: v1
kind: Service
metadata:
name: kafka
spec:
selector:
app: kafka
ports:
- name: broker
protocol: TCP
port: 9092
targetPort: 9092
- name: controller
protocol: TCP
port: 9093
targetPort: 9093
type: ClusterIP
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: kafka
spec:
serviceName: kafka
replicas: 1
selector:
matchLabels:
app: kafka
template:
metadata:
labels:
app: kafka
spec:
containers:
- name: kafka
image: apache/kafka:3.9.0
ports:
- containerPort: 9092
name: broker
- containerPort: 9093
name: controller
command:
- sh
- -c
- |
CLUSTER_ID="dexorder-dev-cluster"
if [ ! -f /var/lib/kafka/data/meta.properties ]; then
/opt/kafka/bin/kafka-storage.sh format -t $CLUSTER_ID -c /opt/kafka/config/kraft/server.properties
fi
/opt/kafka/bin/kafka-server-start.sh /opt/kafka/config/kraft/server.properties \
--override node.id=1 \
--override process.roles=broker,controller \
--override listeners=PLAINTEXT://:9092,CONTROLLER://:9093 \
--override advertised.listeners=PLAINTEXT://kafka:9092 \
--override controller.quorum.voters=1@kafka:9093 \
--override controller.listener.names=CONTROLLER \
--override listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT \
--override log.dirs=/var/lib/kafka/data \
--override offsets.topic.replication.factor=1 \
--override transaction.state.log.replication.factor=1 \
--override transaction.state.log.min.isr=1
env: []
volumeMounts:
- name: kafka-data
mountPath: /var/lib/kafka/data
volumeClaimTemplates:
- metadata:
name: kafka-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
---
# PostgreSQL (for Iceberg catalog metadata)
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
selector:
app: postgres
ports:
- protocol: TCP
port: 5432
targetPort: 5432
type: ClusterIP
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15
ports:
- containerPort: 5432
env:
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
- name: POSTGRES_DB
value: iceberg
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 2Gi
---
# MinIO (S3-compatible object storage)
apiVersion: v1
kind: Service
metadata:
name: minio
spec:
selector:
app: minio
ports:
- name: api
protocol: TCP
port: 9000
targetPort: 9000
- name: console
protocol: TCP
port: 9001
targetPort: 9001
type: ClusterIP
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: minio
spec:
serviceName: minio
replicas: 1
selector:
matchLabels:
app: minio
template:
metadata:
labels:
app: minio
spec:
containers:
- name: minio
image: minio/minio:latest
args:
- server
- /data
- --console-address
- ":9001"
ports:
- containerPort: 9000
name: api
- containerPort: 9001
name: console
env:
- name: MINIO_ROOT_USER
valueFrom:
secretKeyRef:
name: minio-secret
key: root-user
- name: MINIO_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: minio-secret
key: root-password
volumeMounts:
- name: minio-data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: minio-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
---
# Iceberg REST Catalog
apiVersion: v1
kind: Service
metadata:
name: iceberg-catalog
spec:
selector:
app: iceberg-catalog
ports:
- protocol: TCP
port: 8181
targetPort: 8181
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: iceberg-catalog
spec:
replicas: 1
selector:
matchLabels:
app: iceberg-catalog
template:
metadata:
labels:
app: iceberg-catalog
spec:
initContainers:
- name: wait-for-postgres
image: busybox:1.36
command: ['sh', '-c', 'until nc -z postgres 5432; do echo waiting for postgres; sleep 2; done;']
- name: wait-for-minio
image: busybox:1.36
command: ['sh', '-c', 'until nc -z minio 9000; do echo waiting for minio; sleep 2; done;']
containers:
- name: iceberg-catalog
image: tabulario/iceberg-rest:latest
ports:
- containerPort: 8181
env:
- name: CATALOG_WAREHOUSE
value: "s3://warehouse/"
- name: CATALOG_IO__IMPL
value: "org.apache.iceberg.aws.s3.S3FileIO"
- name: CATALOG_S3_ENDPOINT
value: "http://minio:9000"
- name: CATALOG_S3_ACCESS__KEY__ID
valueFrom:
secretKeyRef:
name: minio-secret
key: root-user
- name: CATALOG_S3_SECRET__ACCESS__KEY
valueFrom:
secretKeyRef:
name: minio-secret
key: root-password
- name: CATALOG_S3_PATH__STYLE__ACCESS
value: "true"
- name: AWS_REGION
value: "us-east-1"
---
# Flink JobManager
apiVersion: v1
kind: Service
metadata:
name: flink-jobmanager
spec:
selector:
app: flink-jobmanager
ports:
- name: rpc
protocol: TCP
port: 6123
targetPort: 6123
- name: ui
protocol: TCP
port: 8081
targetPort: 8081
- name: zmq-market-data
protocol: TCP
port: 5558
targetPort: 5558
- name: zmq-notif-pull
protocol: TCP
port: 5561
targetPort: 5561
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: flink-jobmanager
spec:
replicas: 1
selector:
matchLabels:
app: flink-jobmanager
template:
metadata:
labels:
app: flink-jobmanager
spec:
initContainers:
- name: wait-for-kafka
image: busybox:1.36
command: ['sh', '-c', 'until nc -z kafka 9092; do echo waiting for kafka; sleep 2; done;']
containers:
- name: flink-jobmanager
image: dexorder/flink:latest
imagePullPolicy: Never
args: ["standalone-job", "--job-classname", "com.dexorder.flink.TradingFlinkApp"]
ports:
- containerPort: 6123
name: rpc
- containerPort: 8081
name: ui
- containerPort: 5558
name: zmq-market-data
- containerPort: 5561
name: zmq-notif-pull
env:
- name: JOB_MANAGER_RPC_ADDRESS
value: flink-jobmanager
- name: AWS_REGION
value: us-east-1
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: flink-secrets
key: minio-access-key
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: flink-secrets
key: minio-secret-key
volumeMounts:
- name: flink-config
mountPath: /etc/config/config.yaml
subPath: config.yaml
- name: flink-secrets
mountPath: /etc/secrets
volumes:
- name: flink-config
configMap:
name: flink-config
- name: flink-secrets
secret:
secretName: flink-secrets
---
# Flink TaskManager
apiVersion: apps/v1
kind: Deployment
metadata:
name: flink-taskmanager
spec:
replicas: 1
selector:
matchLabels:
app: flink-taskmanager
template:
metadata:
labels:
app: flink-taskmanager
spec:
initContainers:
- name: wait-for-jobmanager
image: busybox:1.36
command: ['sh', '-c', 'until nc -z flink-jobmanager 6123; do echo waiting for jobmanager; sleep 2; done;']
containers:
- name: flink-taskmanager
image: dexorder/flink:latest
imagePullPolicy: Never
args: ["taskmanager"]
env:
- name: JOB_MANAGER_RPC_ADDRESS
value: flink-jobmanager
- name: AWS_REGION
value: us-east-1
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: flink-secrets
key: minio-access-key
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: flink-secrets
key: minio-secret-key
volumeMounts:
- name: flink-config
mountPath: /etc/config/config.yaml
subPath: config.yaml
- name: flink-secrets
mountPath: /etc/secrets
volumes:
- name: flink-config
configMap:
name: flink-config
- name: flink-secrets
secret:
secretName: flink-secrets
---
# Relay (ZMQ router)
apiVersion: v1
kind: Service
metadata:
name: relay
spec:
selector:
app: relay
ports:
- name: work-queue
protocol: TCP
port: 5555
targetPort: 5555
- name: responses
protocol: TCP
port: 5556
targetPort: 5556
- name: market-data
protocol: TCP
port: 5558
targetPort: 5558
- name: client-requests
protocol: TCP
port: 5559
targetPort: 5559
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: relay
spec:
replicas: 1
selector:
matchLabels:
app: relay
template:
metadata:
labels:
app: relay
spec:
containers:
- name: relay
image: dexorder/relay:latest
imagePullPolicy: Never
ports:
- containerPort: 5555
name: work-queue
- containerPort: 5556
name: responses
- containerPort: 5558
name: market-data
- containerPort: 5559
name: client-requests
env:
- name: RUST_LOG
value: relay=info
- name: CONFIG_PATH
value: /config/config.yaml
volumeMounts:
- name: relay-config
mountPath: /config
volumes:
- name: relay-config
configMap:
name: relay-config
---
# Ingestor (CCXT data fetcher)
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingestor
spec:
replicas: 1
selector:
matchLabels:
app: ingestor
template:
metadata:
labels:
app: ingestor
spec:
initContainers:
- name: wait-for-relay
image: busybox:1.36
command: ['sh', '-c', 'until nc -z relay 5555; do echo waiting for relay; sleep 2; done;']
- name: wait-for-kafka
image: busybox:1.36
command: ['sh', '-c', 'until nc -z kafka 9092; do echo waiting for kafka; sleep 2; done;']
containers:
- name: ingestor
image: dexorder/ingestor:latest
imagePullPolicy: Never
env:
- name: LOG_LEVEL
value: info
- name: CONFIG_PATH
value: /config/config.yaml
volumeMounts:
- name: ingestor-config
mountPath: /config
- name: ingestor-secrets
mountPath: /secrets
volumes:
- name: ingestor-config
configMap:
name: ingestor-config
- name: ingestor-secrets
secret:
secretName: ingestor-secrets

View File

@@ -0,0 +1,11 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ai-ingress
spec:
ingressClassName: nginx
rules:
- host: dexorder.local
http:
paths: []

View File

@@ -0,0 +1,32 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: default
# Base resources
resources:
- ../base
- infrastructure.yaml
# No patches needed currently
patches: []
# ingress-dev.yaml - removed until we have services to expose
# ConfigMaps for service configs
configMapGenerator:
- name: relay-config
files:
- config.yaml=configs/relay-config.yaml
- name: ingestor-config
files:
- config.yaml=configs/ingestor-config.yaml
- name: flink-config
files:
- config.yaml=configs/flink-config.yaml
# Secrets (managed via kubectl, not committed)
# These are created by bin/secret-update
secretGenerator: []
generatorOptions:
disableNameSuffixHash: true

View File

@@ -0,0 +1,7 @@
apiVersion: v1
kind: Secret
metadata:
name: ai-secrets
type: Opaque
stringData:
anthropic-api-key: "sk-ant-YOUR_KEY_HERE"

View File

@@ -0,0 +1,9 @@
apiVersion: v1
kind: Secret
metadata:
name: flink-secrets
type: Opaque
stringData:
# MinIO/S3 credentials for Iceberg S3FileIO
minio-access-key: "minio"
minio-secret-key: "minio123"

View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: Secret
metadata:
name: ingestor-secrets
type: Opaque
stringData:
# Exchange API keys (if needed for authenticated endpoints)
binance-api-key: ""
binance-api-secret: ""
coinbase-api-key: ""
coinbase-api-secret: ""
kraken-api-key: ""
kraken-api-secret: ""

View File

@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: minio-secret
type: Opaque
stringData:
root-user: "minio"
root-password: "minio123"

View File

@@ -0,0 +1,7 @@
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
type: Opaque
stringData:
password: "password"

View File

@@ -0,0 +1,30 @@
# Flink Job Configuration
# ZeroMQ bind address and ports
zmq_bind_address: "tcp://*"
zmq_ingestor_work_queue_port: 5555
zmq_ingestor_response_port: 5556
zmq_ingestor_control_port: 5557
zmq_market_data_pub_port: 5558
zmq_client_request_port: 5559
zmq_cep_webhook_port: 5560
# Notification publisher endpoint (Flink → Relay)
# Relay connects XSUB to this endpoint and proxies to clients
notification_publish_endpoint: "tcp://*:5557"
# Kafka configuration
kafka_bootstrap_servers: "kafka:9092"
kafka_tick_topic: "market-tick"
kafka_ohlc_topic: "market-ohlc"
# Iceberg catalog
iceberg_catalog_uri: "http://iceberg-catalog:8181"
iceberg_warehouse: "s3://trading-warehouse/"
iceberg_namespace: "trading"
iceberg_table_prefix: "market"
hadoop_conf_dir: "/etc/hadoop/conf"
# Flink configuration
flink_parallelism: 2
flink_checkpoint_interval_ms: 60000

View File

@@ -0,0 +1,24 @@
# CCXT Ingestor Configuration
# Relay ZMQ endpoints (relay is the well-known gateway)
flink_hostname: relay
ingestor_work_port: 5555 # SUB - receives DataRequest with exchange prefix
# Note: No response port needed - async architecture via Kafka!
# Supported exchanges (subscribe to these prefixes)
supported_exchanges:
- BINANCE
- COINBASE
- KRAKEN
# Kafka configuration
kafka_brokers:
- kafka:9092
kafka_topic: market-0
# Worker configuration
max_concurrent: 10
poll_interval_ms: 10000
# Logging
log_level: info

View File

@@ -0,0 +1,19 @@
# ZMQ Relay Configuration
# Bind address for all relay sockets
bind_address: "tcp://*"
# Client-facing ports
client_request_port: 5559 # ROUTER - Client historical data requests
market_data_pub_port: 5558 # XPUB - Market data fanout to clients
# Ingestor-facing ports
ingestor_work_port: 5555 # PUB - Distribute work with exchange prefix
ingestor_response_port: 5556 # ROUTER - Receive responses from ingestors
# Flink connection
flink_market_data_endpoint: "tcp://flink-jobmanager:5557" # XSUB - Subscribe to Flink market data
# Timeouts and limits
request_timeout_secs: 30 # Timeout for pending client requests
high_water_mark: 10000 # ZMQ high water mark for all sockets

View File

@@ -0,0 +1,40 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: default
# Base resources (backend, web, ingress, init/gVisor)
resources:
- ../base
# Production patches
patches:
- path: patches.yaml
# ConfigMaps for service configs
# In production, these might come from external sources
# or be managed separately, but we'll include them here for consistency
configMapGenerator:
- name: relay-config
files:
- config.yaml=../../configmaps/relay-config.yaml
- name: ingestor-config
files:
- config.yaml=../../configmaps/ingestor-config.yaml
- name: flink-config
files:
- config.yaml=../../configmaps/flink-config.yaml
# Secrets (managed via kubectl, not committed)
# These are created by bin/secret-update prod
secretGenerator: []
generatorOptions:
disableNameSuffixHash: true
# Images
images:
- name: dexorder/ai-backend
newTag: latest
- name: dexorder/ai-web
newTag: latest

View File

@@ -0,0 +1,52 @@
---
# Production backend patches
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: ai-backend
spec:
replicas: 2
template:
spec:
runtimeClassName: gvisor
containers:
- name: ai-backend
image: dexorder/ai-backend:latest
imagePullPolicy: Always
env:
- name: CONFIG
value: "prod"
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "2000m"
---
# Production web patches
apiVersion: apps/v1
kind: Deployment
metadata:
name: ai-web
spec:
replicas: 2
template:
spec:
runtimeClassName: gvisor
containers:
- name: ai-web
image: dexorder/ai-web:latest
imagePullPolicy: Always
env:
- name: VITE_BASE_PATH
value: "/cryptochimp/"
- name: VITE_WS_URL
value: "wss://dexorder.ai/ws"
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"

View File

@@ -0,0 +1,7 @@
apiVersion: v1
kind: Secret
metadata:
name: ai-secrets
type: Opaque
stringData:
anthropic-api-key: "sk-ant-YOUR_KEY_HERE"

View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: Secret
metadata:
name: ingestor-secrets
type: Opaque
stringData:
# Exchange API keys (if needed for authenticated endpoints)
binance-api-key: ""
binance-api-secret: ""
coinbase-api-key: ""
coinbase-api-secret: ""
kraken-api-key: ""
kraken-api-secret: ""

View File

@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: minio-secret
type: Opaque
stringData:
root-user: "CHANGE_THIS_IN_PRODUCTION"
root-password: "CHANGE_THIS_IN_PRODUCTION"

View File

@@ -0,0 +1,7 @@
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
type: Opaque
stringData:
password: "CHANGE_THIS_IN_PRODUCTION"