Archive cleanup and rewrite README as a lab write-up.

Drop duplicate app/Helm trees, planning notes, and the vwap-monitor
source tree so this repo is GitOps + Terraform only. Document the
GKE cluster as shut down, with file-level pointers for vLLM, KEDA
scale-to-zero, Flux, and SOPS.
This commit is contained in:
Sirius DevOps 2026-09-04 22:38:00 -04:00
parent fe4fa1f34d
commit 4f235b52f4
137 changed files with 205 additions and 8016 deletions

View file

@ -1,203 +0,0 @@
# Repository Reorganization Plan
## Goal
Enforce the rule: **gcloud-lab = Kubernetes manifests only. No application code.**
Application code (source, Dockerfiles, CI/CD workflows, Helm charts, deployment scripts) belongs in `hermes-projects/`. gcloud-lab should contain only K8s manifests, Terraform infrastructure modules, cluster configs, and infra controller configs.
---
## Current State
### gcloud-lab/ — Full Directory Audit
```
gcloud-lab/
├── apps/
│ ├── base/
│ │ ├── customer1/ [KEEP] K8s manifests (deployments, services, configmaps, secrets)
│ │ ├── monitoring/ [KEEP] K8s manifests (dashboards)
│ │ └── osint-dashboard/ [KEEP] Helm chart (templates, values.yaml, Chart.yaml)
│ ├── staging/
│ │ ├── customer1/ [KEEP] K8s overlay (kustomization.yaml)
│ │ └── osint-dashboard/ [KEEP] K8s overlay (kustomization.yaml)
│ └── vwap-monitor/ [MOVE] App code (Dockerfile, app/, deploy/)
├── clusters/ [KEEP] Cluster configs (devops-lab/*.yaml, flux-system/)
├── infrastructure/ [KEEP] Infra controllers, gatewayapi, gpus, tailnet
├── misc/ [KEEP] Terraform snippets + K8s YAML
├── modules/ [KEEP] Terraform modules (gke.tf, nodepool.tf, etc.)
├── scripts/ [KEEP] Setup scripts
├── .github/workflows/
│ ├── osint-dashboard-infra.yml [KEEP] Infra deployment workflow
│ └── trade-dashboard.yml [MOVE] CI for trade-dashboard app → hermes-projects/
├── .devcontainer.json [KEEP] Dev environment config
├── .sops.yaml [KEEP] SOPS encryption config
├── .terraform.lock.hcl [KEEP] Terraform lock
├── .gitignore [KEEP] Git ignore rules
├── mise.toml [KEEP] Tool version management
├── README.md [KEEP] (will be updated)
├── infra-tailnet.yaml [KEEP] Tailscale infra config
├── tailscale-0auth.yaml [KEEP] Tailscale config
├── rays-new-deployment.yaml [REVIEW] Orphan K8s deployment YAML — move to apps/base/
├── trade-dashboard/ [MOVE] Full FastAPI app → hermes-projects/trade-dashboard/
├── trading-platform/ [MOVE] Duplicate/deploy configs → hermes-projects/trading-platform/
├── trading-scripts/ [MOVE] Application code → hermes-projects/trading-scripts/
├── analyses/ [REMOVE] Research artifacts (not code, not infra)
└── plans/ [REMOVE] Planning docs (not code, not infra)
```
---
## Items to Move / Remove
### 1. `trade-dashboard/` → hermes-projects/trade-dashboard/
**What it is:** Full FastAPI application (not K8s manifests)
- `Dockerfile` — build config for the app
- `app/` — Python source code (main.py, models.py, schemas.py, database.py, requirements.txt, static/)
- `alembic/` — database migration scripts (env.py, versions/)
- `alembic.ini` — alembic config
**Also move:** `.github/workflows/trade-dashboard.yml` (CI workflow for this app)
**Already in gcloud-lab:** `apps/base/customer1/trade-dashboard/` — these are the K8s manifests for trade-dashboard (deployment.yaml, service.yaml, configmap.yaml, kustomization.yaml). **KEEP these** — they belong here.
### 2. `trading-platform/` → hermes-projects/trading-platform/
**What it is:** Duplicate/alternative deployment configs that overlap with hermes-projects/trading-platform/
Contents:
- `.github/workflows/` — 3 CI/CD workflows (build-push.yml, build-test.yml, deploy.yml)
- `README.md` — project readme
- `deploy/` — deployment configs:
- `ci-cd/` — additional CI workflows
- `docker-compose/` — docker-compose.dev.yml
- `dockerfiles/` — Dockerfiles (api-gateway, dashboard, data-service, execute-service, news-service)
- `helm/` — Helm charts (api-gateway, dashboard, data-service, execute-service, news-service)
- `k8s/` — raw K8s manifests (deployments, services, hpa, cert-manager, ingress)
- `mtls/` — mTLS README
- `scripts/` — deploy.sh, generate-mtls-certs.sh
- `dockerfiles/` — Dockerfiles (dashboard, data-service, execute-service, news-service)
- `helm/` — Helm chart with templates (trading-platform chart, values.yaml, secrets)
**Already in gcloud-lab:** `apps/base/customer1/trading-platform/` — these are the K8s manifests. **KEEP these** — they belong here.
**Already in hermes-projects:** `hermes-projects/trading-platform/` — source code exists here (dashboard, data-service, execute-service, news-service, data_infrastructure). The gcloud-lab trading-platform/ deploy/dockerfiles/helm content should be MERGED into hermes-projects/trading-platform/.
**Decision needed:** The `trading-platform/` in gcloud-lab has BOTH deploy configs (dockerfiles, helm, k8s manifests) AND CI workflows. The K8s manifests in `deploy/k8s/base/` are similar but NOT identical to what's in `apps/base/customer1/trading-platform/`. Need to decide which is authoritative.
### 3. `trading-scripts/` → hermes-projects/trading-scripts/
**What it is:** Application code (Python trading scripts)
- `market_data.py` — market data script
- `orb-monitor/` — monitoring tool (monitor.py, config.yaml)
- `README.md`, `ROADMAP.md` — documentation
### 4. `apps/vwap-monitor/` → hermes-projects/vwap-monitor/
**What it is:** Application code with a Dockerfile
- `Dockerfile` — build config
- `app/` — source code (scanner.py, requirements.txt)
- `deploy/` — deployment config (deployment.yaml, kustomization.yaml, secret.yaml, config.env)
**Note:** The `deploy/` subdirectory contains K8s manifests. These should be moved BACK into gcloud-lab as `apps/base/customer1/vwap-monitor/`. The app code (Dockerfile + app/) goes to hermes-projects.
### 5. `analyses/` → REMOVE from gcloud-lab
**What it is:** Research/analysis markdown documents
- `telegram-webhook-container-analysis.md`
- `telegram-webhook-failure-analysis.md`
These are one-time research artifacts, not infrastructure config. Remove from gcloud-lab entirely.
### 6. `plans/` → REMOVE from gcloud-lab
**What it is:** Planning/strategy markdown documents
- `2026-04-25-openclaw-brain-v1.1.md`
- `AI_ARCHITECTURE.md`
- `models-to-try.md`
These are planning docs, not infrastructure config. Remove from gcloud-lab entirely.
### 7. `rays-new-deployment.yaml` → REVIEW
**What it is:** A standalone K8s deployment YAML at repo root.
**Action:** Move to `apps/base/customer1/hermes-agent/` (appears related to hermes-agent/rays deployment based on filename). Already similar files exist in that directory.
---
## Proposed Migration Plan (Ordered by PR)
### PR 1: This Plan (docs only)
- Add `MIGRATION_PLAN.md` (this file)
- Update `README.md` to document the new structure
### PR 2: Remove planning/research docs
- Delete `analyses/` directory
- Delete `plans/` directory
- Low risk, no dependencies
### PR 3: Move trade-dashboard app to hermes-projects
- Move `trade-dashboard/` → hermes-projects/trade-dashboard/
- Move `.github/workflows/trade-dashboard.yml` → hermes-projects/.github/workflows/
- K8s manifests in `apps/base/customer1/trade-dashboard/` stay in place
- Verify image references in K8s manifests still point to correct registry
### PR 4: Move trading-platform deploy configs to hermes-projects
- Move `trading-platform/` → merge with hermes-projects/trading-platform/
- CI workflows → hermes-projects/trading-platform/.github/workflows/
- Dockerfiles → hermes-projects/trading-platform/dockerfiles/
- Helm charts → hermes-projects/trading-platform/helm/
- K8s manifests from `trading-platform/deploy/k8s/` → reconcile with `apps/base/customer1/trading-platform/`
- **Decision needed:** Which K8s manifests are authoritative? The ones in gcloud-lab/apps/ or trading-platform/deploy/k8s/?
### PR 5: Move trading-scripts to hermes-projects
- Move `trading-scripts/` → hermes-projects/trading-scripts/
- Simple move, no K8s manifest reconciliation needed
### PR 6: Split vwap-monitor (app → hermes-projects, K8s → gcloud-lab)
- Move `apps/vwap-monitor/app/` + `apps/vwap-monitor/Dockerfile` → hermes-projects/vwap-monitor/
- Move `apps/vwap-monitor/deploy/` K8s manifests → `apps/base/customer1/vwap-monitor/`
- Update image references in K8s manifests
---
## Items That Stay in gcloud-lab (No Changes)
| Path | Reason |
|------|--------|
| `apps/base/customer1/` | K8s manifests (kustomize structure) |
| `apps/base/monitoring/` | K8s manifests (dashboards) |
| `apps/base/osint-dashboard/` | Helm chart for infra |
| `apps/staging/` | K8s overlays |
| `clusters/` | Cluster configs, flux-system |
| `infrastructure/` | Controllers, gatewayapi, gpus, tailnet |
| `misc/` | Terraform snippets + K8s YAML |
| `modules/` | Terraform modules |
| `scripts/` | Setup scripts |
| Root config files | .sops.yaml, .devcontainer.json, mise.toml, .gitignore, .terraform.lock.hcl |
| `infra-tailnet.yaml` | Tailscale infra config |
| `tailscale-0auth.yaml` | Tailscale config |
---
## K8s Manifest Reference Check
After moves, verify these image references still resolve:
| K8s Manifest | Image Reference |
|--------------|----------------|
| `apps/base/customer1/trade-dashboard/deployment.yaml` | Check image tag matches hermes-projects build |
| `apps/base/customer1/trading-platform/*/deployment.yaml` | Check image tags match hermes-projects build |
| `apps/base/customer1/hermes-agent/deployment.yaml` | N/A (already correct) |
| `apps/base/customer1/siriusdevops-site/deployment.yaml` | N/A (already correct) |
---
## Decisions Needed Before Proceeding
1. **trading-platform K8s manifest authority:** `trading-platform/deploy/k8s/base/` vs `apps/base/customer1/trading-platform/` — which is the source of truth?
2. **analyses/ and plans/:** Delete entirely, or archive somewhere else?
3. **rays-new-deployment.yaml:** Move to `apps/base/customer1/hermes-agent/` or delete?

688
README.md
View file

@ -1,524 +1,246 @@
# GCloud-Lab DevOps Infrastructure
# gcloud-lab
A production-grade cloud-native infrastructure laboratory demonstrating GitOps, multi-tenant AI agent hosting, and automated security pipelines — all run by a single DevOps engineer on Google Cloud Platform. Trusted by builders who ship.
GitOps + Terraform source of truth for a **GKE lab** I designed, ran, and then **shut down** once the GPU bill stopped being worth it.
## Table of Contents
This is not a live cluster. It is the manifests, node-pool definitions, and GitOps wiring from a real environment that served vLLM inference, CNPG databases, and a handful of in-cluster apps. The same cost model that made the GPU pools scale to zero is why the whole footprint went to zero.
- [Project Overview](#project-overview)
- [Architecture](#architecture)
- [DevOps Tools & Technologies](#devops-tools--technologies)
- [Monitoring](#monitoring)
- [Project Structure](#project-structure)
- [Infrastructure Components](#infrastructure-components)
- [Applications](#applications)
- [Getting Started](#getting-started)
- [Security](#security)
- [Cost Optimization](#cost-optimization)
- [License](#license)
**Canonical copy:** [forgejo.siriusdevops.com/sirius/gcloud-lab](https://forgejo.siriusdevops.com/sirius/gcloud-lab)
---
## Project Overview
## What this is evidence of
This repository is the single source of truth for a multi-application cloud platform running on GKE. Every deployment, database, and network policy flows through Git via Flux CD. What lives here:
If you are reading this as a hiring screen, start here. Every claim below maps to a file in this repo.
1. **AgentForge** — Private multi-tenant AI agent workspace with dual-tier vLLM inference (L4 dispatcher + RTX 6000 deep thinker) and isolated CNPG databases per tenant.
2. **Multi-Profile AI Agent Team** — Six specialist AI profiles (backend-dev, frontend-dev, researcher, outreach, quant, sec-ops) orchestrated through a shared Kanban board with automated audit-to-fix pipelines.
3. **Waitlist API** — FastAPI landing page backend with idempotent signups, async PostgreSQL, and Telegram fire-and-forget notifications.
4. **Autonomous News Quant Pipeline** — 371 global feed scraper with DeepSeek-R1 analysis generating actionable futures trading signals.
5. **N8N Workflow Automation** — Self-hosted workflow engine with dedicated CNPG PostgreSQL.
6. **Local Business Web Deployment Pipeline** — Automated K8s manifest generation for small business websites with cross-namespace HTTPRoute routing.
| Claim | Where to look |
| --- | --- |
| GKE cluster, custom VPC, dual-stack, Cilium datapath | [`modules/gke.tf`](modules/gke.tf), [`modules/vpc.tf`](modules/vpc.tf) |
| CPU + three GPU node pools (L4, RTX PRO 6000, A100 80GB), all SPOT except CPU | [`modules/nodepool.tf`](modules/nodepool.tf), [`modules/nodepool-gpu.tf`](modules/nodepool-gpu.tf), [`modules/pro6000-nodepool.tf`](modules/pro6000-nodepool.tf), [`modules/a100-nodepool.tf`](modules/a100-nodepool.tf) |
| Flux CD applies the tree; SOPS decrypts secrets in-cluster | [`clusters/devops-lab/`](clusters/devops-lab/), [`.sops.yaml`](.sops.yaml) |
| Production-style **vLLM** OpenAI-compatible servers (not Ollama) | [`infrastructure/gpus/base/vllm-servers/`](infrastructure/gpus/base/vllm-servers/) |
| KEDA HTTP scale-to-zero on the expensive GPUs | [`infrastructure/gpus/base/keda-gpu-scaling/`](infrastructure/gpus/base/keda-gpu-scaling/) |
| CloudNative-PG operator + per-app Postgres | [`infrastructure/controllers/base/cnpg/`](infrastructure/controllers/base/cnpg/), `apps/base/customer1/*-db/` |
| Gateway API (not legacy Ingress) + Tailscale for internals | [`infrastructure/gatewayapi/`](infrastructure/gatewayapi/), [`infrastructure/tailnet/`](infrastructure/tailnet/) |
| Workload NetworkPolicies (trading stack) | [`apps/base/customer1/trading-platform/network-policies/`](apps/base/customer1/trading-platform/network-policies/) |
I run 24/7 infrastructure now on a Raspberry Pi (Forgejo, Cloudflare tunnel, containerized sites). This repo is the cloud chapter that came before that.
---
## Architecture
```text
┌──────────────────────────────────────────────────────────────────────────────┐
│ Google Cloud Platform │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ GKE Cluster (devops-lab-cluster) │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Standard │ │ L4 GPU Pool │ │ RTX 6000 GPU │ │ │
│ │ │ Node Pool │ │ (SPOT L4) │ │ (SPOT RTX6K) │ │ │
│ │ │ e2-std-2 │ │ 1 node (24/7)│ │ 0-1 nodes │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Cilium CNI + Hubble + NetworkPolicy │ │ │
│ │ └──────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Kubernetes Gateway API — external-http-gateway │ │ │
│ │ │ HTTPRoute PathPrefix → AgentForge / Waitlist / Apps │ │ │
│ │ └──────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌───────────────────────────────┐ ┌───────────────────────────────┐ │ │
│ │ │ customer1 namespace │ │ agent-forge namespace │ │ │
│ │ │ - AgentForge (PAaaS) │ │ - Tenant-specific Hermes Agent │ │ │
│ │ │ - Dual-tier vLLM │ │ - Isolated CNPG databases │ │ │
│ │ │ L4 Dispatcher (24/7) │ │ - Qwen 3.6 27B Abliterated │ │ │
│ │ │ RTX 6000 Deep Thinker (KEDA) │ │ - KEDA scale-to-zero │ │ │
│ │ │ - Waitlist API (FastAPI) │ │ │ │ │
│ │ │ - News Bot Pipeline │ │ ┌───────────────────────────┐ │ │ │
│ │ │ - Landing Page │ │ │ sec-ops audit agent │ │ │
│ │ │ - CNPG PostgreSQL Cluster │ │ │ Automated vuln scanning │ │ │
│ │ └───────────────────────────────┘ │ │ → backend-dev auto-fix │ │ │
│ │ │ └───────────────────────────┘ │ │ │
│ │ ┌───────────────────────────────┐ └───────────────────────────────┘ │ │
│ │ │ local-business namespaces │ │ │
│ │ │ - nginx + ConfigMap per biz │ ┌───────────────────────────────┐ │ │
│ │ │ - Cross-ns HTTPRoute refs │ │ monitoring namespace │ │ │
│ │ └───────────────────────────────┘ │ - Prometheus + Grafana │ │ │
│ │ │ - Tailscale-only access │ │ │
│ │ ┌───────────────────────────────┐ │ - No public ingress │ │ │
│ │ │ kanban namespace │ └───────────────────────────────┘ │ │
│ │ │ - Hermes Agent Orchestrator │ │ │
│ │ │ - 6 Specialist Profiles │ ┌───────────────────────────────┐ │ │
│ │ │ - Isolated hermes-pgdb │ │ n8n namespace │ │ │
│ │ └───────────────────────────────┘ │ - Workflow automation │ │ │
│ │ │ - Dedicated PostgreSQL │ │ │
│ │ └───────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
```
Git (this repo, branch master)
Flux source-controller (1m)
┌─────────────────┼──────────────────┐
▼ ▼ ▼
infra-controllers infra-gpus apps/staging
CNPG / KEDA vLLM + KEDA customer1 overlay
cert-manager HTTPScaledObject (kustomize)
Tailscale L4 / RTX6000 / A100
kube-prometheus
│ │ │
└──────────── GKE us-central1-a ─────┘
┌───────────────┬────────────┼────────────┬──────────────┐
▼ ▼ ▼ ▼ ▼
e2-standard-2 g2-standard-8 g4-standard-48 a2-ultragpu-1g
CPU pool L4 SPOT RTX PRO 6000 A100 80GB SPOT
15 nodes 1 node SPOT 01 SPOT 01
(always on) KEDA 0↔1 KEDA 0↔1
```
---
## DevOps Tools & Technologies
### Infrastructure as Code (IaC)
| Tool | Version | Purpose |
|------|---------|---------|
| **Terraform** | 1.7+ | Infrastructure provisioning for GCP resources |
| **Google Provider** | 7.14.1 | Terraform provider for GCP |
| **Helm Provider** | Latest | Terraform provider for Helm charts |
| **Flux Provider** | 1.7.6 | Terraform provider for Flux bootstrap |
### Container Orchestration & Networking
| Tool | Version | Purpose |
|------|---------|---------|
| **Google Kubernetes Engine (GKE)** | Latest | Managed Kubernetes cluster |
| **Cilium** | 1.18.5 | CNI plugin with eBPF-based networking |
| **Hubble** | 1.18.5 | Network observability and monitoring |
| **Kubernetes Gateway API** | v1 | Ingress routing and traffic management |
### GitOps & Configuration Management
| Tool | Version | Purpose |
|------|---------|---------|
| **Flux CD** | 1.7.6 | GitOps continuous delivery |
| **Kustomize** | v1beta1 | Kubernetes manifest customization |
| **Helm** | 3+ | Kubernetes package manager |
| **SOPS** | Latest | Secrets encryption in Git |
| **Age** | Latest | Modern encryption for SOPS |
### Database
| Tool | Version | Purpose |
|------|---------|---------|
| **CloudNative PG** | 0.26.1 | PostgreSQL Kubernetes operator |
| **PostgreSQL** | 15.2 | Relational database (multi-cluster fleet) |
### AI/ML Infrastructure
| Tool | Version | Purpose |
|------|---------|---------|
| **vLLM** | v0.9.1 | High-throughput LLM inference server |
| **Qwen 3.6 27B Abliterated** | Latest | Uncensored reasoning model (RTX 6000 deep thinker tier) |
| **Qwen 2.5 Coder 7B Abliterated** | Latest | Fast tool-calling dispatcher (L4 24/7 tier) |
| **NVIDIA L4 GPU** | - | 24/7 GPU for fast triage and dispatch |
| **NVIDIA RTX 6000 Pro** | - | SPOT GPU for deep reasoning and multi-file context |
### Development Environment
| Tool | Version | Purpose |
|------|---------|---------|
| **Mise** | Latest | Development tool version manager |
| **Dev Containers** | Latest | Consistent development environment |
| **k9s** | Latest | Kubernetes CLI dashboard |
### Monitoring & Observability
| Tool | Version | Purpose |
|------|---------|---------|
| **Prometheus** | Latest | Metrics collection via kube-prometheus-stack |
| **Grafana** | Latest | Dashboards & visualizations |
| **Tailscale** | Latest | Secure VPN access to internal services |
Datapath: GKE `ADVANCED_DATAPATH` + `enable_cilium_clusterwide_network_policy`. Dual-stack VPC (`10.0.0.0/16`, pods `192.168.32.0/20`, services `192.168.16.0/24`). GPU nodes are tainted (`nvidia.com/gpu…=present:NoSchedule`) so only inference pods land on them.
---
## Project Structure
## GPU inference (vLLM)
Three independent OpenAI-compatible servers, each pinned to a pool via `nodeSelector` + matching taint/toleration. Model weights cached on PVC so a scale-up does not re-pull 2040 GB from Hugging Face.
### L4 dispatcher — always on
[`infrastructure/gpus/base/vllm-servers/vllm-l4.yaml`](infrastructure/gpus/base/vllm-servers/vllm-l4.yaml)
- Machine: `g2-standard-8` + `nvidia-l4`, SPOT
- Image: `vllm/vllm-openai`
- Model: `p-e-w/Qwen3-8B-heretic`
- Flags that matter: `--kv-cache-dtype=fp8`, `--enable-chunked-prefill`, `--enable-prefix-caching`, `--enable-auto-tool-choice`, `--tool-call-parser=hermes`, `--max-model-len=32768`
Cheap, tool-capable, left running so agents had a low-latency brain even when the big cards were scaled out.
### RTX PRO 6000 — deep context, scale to zero
[`infrastructure/gpus/base/vllm-servers/rtx6000-vllm.yaml`](infrastructure/gpus/base/vllm-servers/rtx6000-vllm.yaml)
[`infrastructure/gpus/base/keda-gpu-scaling/keda-vllm.yaml`](infrastructure/gpus/base/keda-gpu-scaling/keda-vllm.yaml)
- Machine: `g4-standard-48` + `nvidia-rtx-pro-6000`, SPOT, **min 0 / max 1**
- Image: `vllm/vllm-openai:latest-cu129-ubuntu2404`
- Model: `edp1096/Huihui-Qwen3.6-27B-abliterated-FP8`
- `--max-model-len=262144`, `--enable-chunked-prefill`, `--tool-call-parser=qwen3_xml`, `--reasoning-parser=qwen3`, MTP speculative decoding (`num_speculative_tokens: 2`)
- Startup / liveness / readiness probes on `/health` and `/v1/models` (model load is slow; `failureThreshold: 60` on startup)
- KEDA `HTTPScaledObject`: `replicas.min: 0`, `max: 1`, host `rtx6000-brain-service.customer1.svc.cluster.local`
Idle deep-thinker capacity cost nothing. A request woke the Deployment; GKE then scaled the node pool off zero.
### A100 80GB — same pattern
[`infrastructure/gpus/base/vllm-servers/a100-vllm.yaml`](infrastructure/gpus/base/vllm-servers/a100-vllm.yaml)
[`modules/a100-nodepool.tf`](modules/a100-nodepool.tf)
- Machine: `a2-ultragpu-1g` + `nvidia-a100-80gb`, SPOT, min 0
- NVFP4 / ModelOpt quantized 27B, `--quantization=modelopt`, `--attention-backend=flash_attn`, `--kv-cache-dtype=fp8`
---
## GitOps
Flux watches `master` and applies layered Kustomizations:
```
clusters/devops-lab/
flux-system/ Flux controllers + GitRepository
infra-controllers.yaml → infrastructure/controllers/staging
CNPG 0.26.1, KEDA ≥2.14, cert-manager, Tailscale, kube-prometheus
infra-gpus.yaml → infrastructure/gpus/staging
infra-gatewayapi.yaml → Gateway API + HTTPRoutes
customer1-strimzi.yaml → Strimzi (Kafka) first
customer1.yaml → apps/staging/customer1 (dependsOn strimzi)
```
Every Flux Kustomization has `decryption.provider: sops` and `secretRef: sops-age`. Encrypted `data`/`stringData` in Git; Flux decrypts at apply time. Age recipient is in [`.sops.yaml`](.sops.yaml). The private key is **not** in this repo.
Bootstrap was originally:
```bash
flux bootstrap github \
--owner=sirius0xdev \
--repository=gcloud-lab \
--branch=master \
--path=clusters/devops-lab
```
`gotk-sync.yaml` still records that GitHub URL. This Forgejo copy is the archive; the cluster is gone, so the GitRepository object was never re-pointed.
---
## Terraform (cluster birth)
[`modules/`](modules/) is the IaC that created the cluster, not the day-to-day delivery path.
| File | Resource |
| --- | --- |
| `gke.tf` | `devops-lab-cluster` in `us-central1-a`, default pool removed, Cilium datapath, dual-stack IP policy |
| `vpc.tf` | `devops-lab-network` / `devops-lab-subnetwork`, ULA IPv6, secondary ranges |
| `nodepool.tf` | CPU `e2-standard-2`, 15, pd-standard 100 Gi |
| `nodepool-gpu.tf` | L4 SPOT `g2-standard-8`, autoscaling 11, GPU taint |
| `pro6000-nodepool.tf` | RTX PRO 6000 SPOT `g4-standard-48`, **01**, hyperdisk-balanced |
| `a100-nodepool.tf` | A100 80GB SPOT `a2-ultragpu-1g`, **01**, pd-ssd 200 Gi |
| `db-bucket.tf` | GCS bucket for CNPG backups |
| `providers.tf` | Google provider, Helm talking to the cluster via `gke-gcloud-auth-plugin` |
State files and `*.tfvars` are gitignored. Do not `terraform apply` this against a live billing account unless you intend to recreate it.
---
## Data plane and apps
Flux overlay: [`apps/staging/customer1/kustomization.yaml`](apps/staging/customer1/kustomization.yaml)
| Piece | Role |
| --- | --- |
| CloudNative-PG | Operator `0.26.1`; per-app clusters (Hermes memory, waitlist, n8n, trading) with GCS backup |
| Strimzi | Kafka for the trading data path; Flux `dependsOn` so apps wait for the operator |
| Redis | In-cluster cache next to the trading services |
| Trading platform | data / execute / news / dashboard Deployments, HTTPRoutes, NetworkPolicies |
| Hermes agent | In-cluster agent + CNPG, talking to the vLLM services |
| News bot | CronJobs: scrape → analyst (vLLM) → Telegram |
| Waitlist API | FastAPI + CNPG, Gateway HTTPRoute |
| n8n | Workflow engine on its own Postgres |
| Gateway API | Public HTTPRoutes; Grafana/Prometheus stayed off the public internet (Tailscale / port-forward) |
App **images** were built in a separate code repo and pulled from GHCR. This repo is manifests only (after cleanup: no Dockerfiles, no Helm-of-the-app, no planning markdown).
---
## Networking and security
- **Cilium** as GKE datapath, clusterwide NetworkPolicy enabled from Terraform.
- **Gateway API** `HTTPRoute` for public paths; internals not published.
- **Tailscale operator** for operator access to Grafana and cluster services without a public LB.
- **NetworkPolicies** on the trading namespace: DNS, Postgres, Redis, Kafka, in-namespace HTTP, egress HTTPS to market APIs. Everything else denied.
- **SOPS + age** for Secrets in Git. Flux `sops-age` Secret in `flux-system` held the private key.
- GPU nodes tainted so a random Deployment cannot schedule onto a $2+/hr card.
---
## Cost model (why it was torn down)
This was the whole point of the GPU design:
1. L4 SPOT stayed at 1 node — cheap enough to keep a dispatcher warm.
2. RTX 6000 and A100 pools **autoscaled 01**. KEDA HTTPScaledObject set the Deployment to 0 when there was no traffic; the node pool followed.
3. Weights on PVC, long startup probes — first request after idle paid a cold-start, not a 40 GB pull.
4. CronJobs for batch work instead of idle inference pods.
5. When even the L4 + control-plane bill stopped making sense, the cluster was destroyed. Scale-to-zero was the rehearsal for scale-to-nothing.
---
## Repository layout
```
gcloud-lab/
├── modules/ # Terraform IaC modules
│ ├── providers.tf # Provider configurations
│ ├── gke.tf # GKE cluster definition
│ ├── vpc.tf # VPC and subnet configuration
│ ├── nodepool.tf # Standard node pool
│ ├── nodepool-gpu.tf # GPU node pools (L4 + RTX 6000 SPOT)
│ ├── flux.tf # Flux GitOps bootstrap
│ ├── helm.tf # Helm chart deployments (Cilium)
│ └── variables.tf # Input variables
├── clusters/ # Cluster configurations
│ └── devops-lab/
│ ├── flux-system/ # Flux CD components
│ │ ├── gotk-components.yaml # Flux controllers
│ │ ├── gotk-sync.yaml # Git repository sync
│ │ └── kustomization.yaml # Flux kustomization
│ ├── customer1.yaml # Customer1 Kustomization
│ ├── agent-forge.yaml # AgentForge Kustomization
│ ├── infra-controllers.yaml # Infrastructure controllers (CNPG, KEDA, Monitoring, Tailscale)
│ └── infra-configs.yaml # Infrastructure configs
├── infrastructure/ # Infrastructure components
│ ├── controllers/
│ │ ├── base/
│ │ │ ├── cnpg/ # CloudNative PG operator
│ │ │ ├── keda/ # KEDA autoscaling
│ │ │ ├── monitoring/ # Prometheus + Grafana (no public ingress)
│ │ │ └── tailscale/ # Tailscale Operator for secure VPN access
│ │ └── staging/
│ │ └── kustomization.yaml # Aggregates all base components
│ └── configs/
│ └── staging/
│ └── kustomization.yaml
├── apps/ # Application deployments
│ ├── base/
│ │ ├── customer1/
│ │ │ ├── namespace.yaml # Namespace definition
│ │ │ ├── deployment.yaml # N8N + vLLM deployments
│ │ │ ├── service.yaml # ClusterIP services
│ │ │ ├── storage.yaml # PersistentVolumeClaims
│ │ │ ├── configmap.yaml # Application configuration
│ │ │ ├── pg-cluster-customer1.yaml # PostgreSQL cluster
│ │ │ ├── apigateway.yaml # GCP Gateway
│ │ │ ├── http-route.yaml # HTTP routing
│ │ │ ├── healthcheck.yaml # Health check policy
│ │ │ ├── waitlist-api/ # Waitlist API microservice
│ │ │ │ ├── deployment.yaml
│ │ │ │ ├── service.yaml
│ │ │ │ └── configmap.yaml
│ │ │ └── news_bot/ # News bot microservices
│ │ │ ├── scraper-cronjob.yaml
│ │ │ ├── analyst-cronjob.yaml
│ │ │ ├── telebot-cronjob.yaml
│ │ │ ├── scrapy-configmap.yaml
│ │ │ └── scrapy-urls-configmap.yaml
│ │ ├── agent-forge/
│ │ │ ├── namespace.yaml
│ │ │ ├── vllm-deep-thinker.yaml # RTX 6000 deployment with KEDA
│ │ │ ├── hermes-tenant.yaml # Per-tenant Hermes agent instance
│ │ │ └── pg-cluster-agentforge.yaml
│ │ ├── kanban/
│ │ │ ├── namespace.yaml
│ │ │ ├── hermes-deployment.yaml # AI agent orchestrator
│ │ │ └── pg-cluster-hermes.yaml
│ │ └── local-business/
│ │ └── template/
│ │ ├── namespace.yaml
│ │ ├── nginx-deployment.yaml
│ │ ├── configmap.yaml
│ │ └── http-route.yaml
│ └── staging/
│ ├── customer1/
│ │ └── kustomization.yaml
│ ├── agent-forge/
│ │ └── kustomization.yaml
│ └── kanban/
│ └── kustomization.yaml
├── scripts/
│ └── setup # Development setup script
├── .devcontainer.json # Dev container configuration
├── mise.toml # Tool version management
├── age.agekey # SOPS encryption key
└── README.md # This file
├── modules/ Terraform: VPC, GKE, node pools, GCS
├── clusters/devops-lab/ Flux entry (GitRepository + Kustomizations)
├── infrastructure/
│ ├── controllers/ CNPG, KEDA, cert-manager, Tailscale, Prometheus
│ ├── gpus/ vLLM Deployments + KEDA HTTPScaledObjects
│ ├── gatewayapi/ Gateway + HTTPRoutes
│ └── tailnet/ Tailscale ProxyGroup
├── apps/
│ ├── base/customer1/ Namespaced workloads (kustomize)
│ ├── base/osint-dashboard/ Helm chart used on this cluster
│ └── staging/ Overlays Flux actually syncs
├── monitoring/ Extra Grafana/Prometheus config
├── .sops.yaml Age recipient for secret encryption
├── .github/workflows/ Historical GH Actions (pgvector image, etc.)
└── mise.toml Local CLI pin (gcloud, kubectl, helm, sops, terraform, k9s)
```
---
## Infrastructure Components
## Reading the code (suggested order)
### GKE Cluster
- **Name**: `devops-lab-cluster`
- **Region**: `us-central1-a`
- **Network**: Custom VPC with dual-stack IPv4/IPv6
### Node Pools
| Pool | Machine Type | Scaling | Purpose |
|------|-------------|---------|---------|
| Standard | e2-standard-2 | 1-16 nodes | General workloads, N8N, web servers |
| GPU L4 (SPOT) | g2-standard-8 + L4 | 0-5 nodes | vLLM dispatcher, 24/7 fast inference |
| GPU RTX 6000 (SPOT) | g6-standard-4 + RTX 6000 Pro | 0-1 nodes | Deep thinker tier, multi-file reasoning |
### Networking
- **VPC**: `devops-lab-network`
- **Primary CIDR**: `10.0.0.0/16`
- **Pod CIDR**: `192.168.32.0/20`
- **Service CIDR**: `192.168.16.0/24`
- **CNI**: Cilium with advanced datapath and NetworkPolicy enforcement
- **Ingress**: Kubernetes Gateway API via `external-http-gateway` with PathPrefix HTTPRoute routing
- **Internal Services**: Tailscale-only — no public ingress for monitoring, databases, or agent infrastructure
### CNPG Database Fleet
Multiple isolated PostgreSQL clusters, each with dedicated databases per application:
| Cluster | Namespace | Databases | Backup |
|---------|-----------|-----------|--------|
| `customer1-pgdb` | customer1 | `n8n`, `news_app`, `waitlist` | GCS, 7-day retention |
| `hermes-pgdb` | kanban | `hermes`, `memory_store` | GCS, 7-day retention |
| `hermes-tenant-pgdb` | agent-forge | Per-tenant isolated DBs | GCS, 7-day retention |
| `siriusdevops-pgdb` | customer1 | `waitlist_prod` | GCS, 30-day retention |
### GitOps Flow
```
GitHub Repository (ghcr.io/sirius0xdev)
Flux Source Controller (watches git, 1min interval)
Flux Kustomize Controller (applies manifests)
├── infrastructure/controllers → CNPG, KEDA, Monitoring, Tailscale
├── infrastructure/configs → Cluster configs
├── apps/staging/customer1 → PAaaS, N8N, News Bot, Waitlist API
├── apps/staging/agent-forge → Multi-tenant AI agent hosting
├── apps/staging/kanban → AI Agent Team orchestrator
└── apps/staging/local-business → Business websites
```
1. [`modules/gke.tf`](modules/gke.tf) + [`modules/vpc.tf`](modules/vpc.tf) — cluster shape.
2. GPU pools, then the matching vLLM YAML — taint keys must match or the pod never schedules.
3. [`clusters/devops-lab/customer1.yaml`](clusters/devops-lab/customer1.yaml) — Flux `dependsOn`, SOPS, prune/force.
4. [`apps/staging/customer1/kustomization.yaml`](apps/staging/customer1/kustomization.yaml) — what actually shipped in `customer1`.
5. One NetworkPolicy under `apps/base/customer1/trading-platform/network-policies/` — default-deny thinking.
---
## Applications
### 1. AgentForge — Private AI Agent Workspace
A premium, uncensored, privacy-first AI agent hosting platform with dual-tier cognitive architecture:
- **Tier 1 (Dispatcher):** L4 GPU SPOT instance running 24/7. Hosts `Qwen2.5-Coder-7B-Instruct-heretic` via vLLM `v0.9.1` for lightning-fast, cheap triage and tool calling.
- **Tier 2 (Deep Thinker):** RTX 6000 Pro Spot instance scaling from 0-1 via KEDA. Hosts `Qwen3.5-27B-heretic` with `--enable-chunked-prefill` and `--kv-cache-dtype=fp8` for massive multi-file context and reasoning without OOMing or stalling concurrent users.
- **Frontend:** Isolated Hermes agent profiles per tenant, connected to Telegram/Discord via outbound polling (no public ingress required).
- **Landing Page:** Dockerized marketing site built via CI/CD from `hermes-projects` and deployed to the `staging` kustomization overlay.
- **Container Registry:** All images pushed to `ghcr.io/sirius0xdev`.
### 2. Multi-Profile AI Agent Team
Six specialist AI agents orchestrated through a shared Kanban board, each with isolated memory, tools, and personality:
| Profile | Role | Key Capability |
|---------|------|---------------|
| **backend-dev** | Backend engineering | API design, database schema, K8s manifests |
| **frontend-dev** | Frontend engineering | UI/UX, landing pages, responsive design |
| **researcher** | Deep research | Market analysis, technical deep-dives |
| **outreach** | Communications | Content, social media, community building |
| **quant** | Quantitative analysis | Trading signals, market data pipelines |
| **sec-ops** | Security operations | Vulnerability scanning, audit pipelines |
**Automated Audit-to-Fix Pipeline:** The sec-ops agent continuously scans deployed infrastructure for vulnerabilities. When findings are confirmed, the backend-dev agent is automatically dispatched to remediate — from detection to patch in a single GitOps cycle.
### 3. Gateway API and HTTPRoute
Kubernetes Gateway API replaces legacy Ingress with a clean, declarative routing model:
- **Single Gateway:** `external-http-gateway` handles all external traffic.
- **PathPrefix Routing:** `/agentforge/*` → AgentForge landing, `/waitlist/*` → Waitlist API, `/business/*` → local business sites.
- **No Public Ingress for Internals:** Monitoring (Grafana/Prometheus), databases, and agent infrastructure are accessible only via Tailscale VPN.
- **Cross-Namespace References:** HTTPRoute resources in one namespace can reference Services in another, keeping routing centralized.
### 4. Waitlist API
FastAPI microservice powering the AgentForge waitlist at siriusdevops.com:
- **Database:** asyncpg connection pool to dedicated CNPG PostgreSQL.
- **Idempotent Signups:** `INSERT ... ON CONFLICT DO NOTHING` — duplicate emails are silently ignored, not rejected.
- **Notifications:** Fire-and-forget Telegram webhook on each new signup. No blocking I/O in the request path.
- **Security:** Rate limiting per IP, input sanitization, and CORS whitelist.
### 5. Autonomous News Quant Pipeline (`news_bot`)
An institutional-grade pipeline scraping 371 global feeds to generate actionable futures trading signals:
- **Scraper:** CronJob at `:50` pulling multi-lingual global financial data.
- **Map/Reduce Analyst:** DeepSeek-R1 with a strict 10-step think protocol extracts "Market-Moving DNA" and translates events into explicit futures targets (/ES, /CL, /NQ) with risk:reward, take profit, and stop loss levels.
- **Privacy:** All proprietary technical data stays strictly within the VPC, executing against local models to protect the trading edge.
### 6. Local Business Web Deployment Pipeline
Automated Kubernetes manifest generation for small business websites:
- **Stack:** nginx serving static content from ConfigMap, one namespace per business.
- **Routing:** HTTPRoute with cross-namespace Service references under `/business/<name>` paths.
- **Zero Cold Start:** Static sites have no database dependency — just nginx + ConfigMap, deployed via GitOps.
---
## Getting Started
### Prerequisites
- Google Cloud account with billing enabled
- GitHub account with repository access
- `gcloud` CLI authenticated
- Terraform 1.7+
### Local Development Setup
## Local tooling
```bash
# Install tools via mise
./scripts/setup
# Or manually
mise trust && mise install
mise trust && mise install # kubectl, helm, sops, terraform, k9s, gcloud
```
### Infrastructure Deployment
Decrypt a secret locally (needs the age key, which is not in Git):
```bash
cd modules
# Initialize Terraform
terraform init
# Set required variables
export TF_VAR_github_token="your-token"
export TF_VAR_github_org="your-org"
export TF_VAR_github_repository="gcloud-lab"
# Plan and apply
terraform plan
terraform apply
```
### Accessing the Cluster
```bash
# Configure kubectl
gcloud container clusters get-credentials devops-lab-cluster \
--zone us-central1-a \
--project devops-lab-cluster
# Verify connection
kubectl get nodes
# Use k9s for interactive management
k9s
```
### Accessing Monitoring (Grafana / Prometheus)
Monitoring services are **not publicly exposed**. Access is via Tailscale VPN or port-forwarding:
```bash
# Option 1: Port-forward Grafana
kubectl port-forward svc/prometheus-community-kube-prometheus-stack-grafana \
-n monitoring 3000:3000
# Option 2: Port-forward Prometheus
kubectl port-forward svc/prometheus-community-kube-prometheus-stack-prometheus \
-n monitoring 9090:9090
```
⚠️ **Before deploying**, replace the Grafana admin password in
`infrastructure/controllers/base/monitoring/release.yaml` with a secure value,
or create a `monitoring-grafana-admin` Secret instead.
---
## Security
### Secrets Management
- **Encryption**: SOPS with Age encryption
- **Key Storage**: `age.agekey` (do not commit unencrypted)
- **Flux Integration**: Automatic decryption during deployment
### Pod Security
- Non-root containers (UID 1000)
- Filesystem group enforcement
- Privilege escalation disabled
- Resource limits enforced
### Network Security
- Cilium NetworkPolicy for pod-to-pod and namespace-to-namespace isolation
- Kubernetes Gateway API with TLS termination at the load balancer
- Internal services (monitoring, databases, agent infrastructure) accessible only via Tailscale VPN — zero public ingress
- Rate limiting on public-facing APIs (Waitlist, landing page)
### Database Security
- Managed roles with secret-based passwords per application
- Separate PostgreSQL clusters per domain (hermes-pgdb, hermes-tenant-pgdb, siriusdevops-pgdb)
- GCS backups with configurable retention policies
- HA cluster with automatic failover
### Automated Security Auditing
- **sec-ops Agent:** Continuously scans deployed infrastructure for CVEs, misconfigurations, and policy violations
- **Auto-Remediation:** Confirmed findings automatically dispatch the backend-dev agent to patch and commit
- **Audit Trail:** Every finding, fix, and deployment is tracked in Git history — full provenance from detection to resolution
---
## Cost Optimization
- **SPOT GPU Instances**: 60-90% savings on L4 and RTX 6000 workloads
- **KEDA Scale-to-Zero**: RTX 6000 deep thinker pool scales to 0 when no requests are queued
- **Resource Limits**: CPU and memory caps on every container prevent runaway costs
- **Scheduled Workloads**: CronJobs only run when needed — no idle inference pods
- **Tailscale for Internal Access**: No need for expensive internal load balancers or Cloud NAT for monitoring
---
## Container Images
```
ghcr.io/sirius0xdev/agentforge-landing:latest
ghcr.io/sirius0xdev/waitlist-api:latest
ghcr.io/sirius0xdev/newsscraper:latest
ghcr.io/sirius0xdev/summarizer:latest
ghcr.io/sirius0xdev/news-messenger:latest
docker.n8n.io/n8nio/n8n:2.1.4
ghcr.io/cloudnative-pg/postgresql:15.2
sops -d apps/base/customer1/waitlist-api/waitlist-telegram-secret.yaml
```
---
## Tool Reference
## Status
### Terraform Providers
| | |
| --- | --- |
| Cluster | **Destroyed** (GCP project `devops-lab-cluster`, GKE `devops-lab-cluster`, `us-central1-a`) |
| This repo | Archive of what ran |
| Live infra today | Self-hosted on a Pi — see [siriusdevops.com/lab](https://siriusdevops.com/lab) |
```hcl
google = "~> 7.14" # GCP resources
helm = "~> 2.0" # Helm chart management
flux = "~> 1.7" # GitOps bootstrap
```
### Helm Charts
```yaml
cilium: 1.18.5 # CNI and service mesh
cloudnative-pg: 0.26.1 # PostgreSQL operator
vllm: 0.9.1 # High-throughput LLM serving
```
---
## License
Private repository — All rights reserved.
Lance Walters — [siriusdevops.com](https://siriusdevops.com)

View file

@ -1,21 +0,0 @@
FROM python:3.13-slim
LABEL maintainer="sirius0xdev" \
description="VWAP Wave Breach Scanner — monitors Gold, NASDAQ, S&P, Crude Oil"
WORKDIR /app
COPY app/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app/scanner.py .
# Default scan interval: 120s (2 min), threshold: 2σ
ENV SCAN_INTERVAL_SEC=120 \
BREACH_THRESHOLD=2.0
# Health check: ensure process is alive
HEALTHCHECK --interval=60s --timeout=10s --retries=3 \
CMD ["python3", "-c", "import os; assert os.path.exists('/proc/1/fd/0')"]
CMD ["python3", "scanner.py"]

View file

@ -1,3 +0,0 @@
yfinance>=0.2.54
requests>=2.32
apscheduler>=3.10

View file

@ -1,205 +0,0 @@
#!/usr/bin/env python3
"""
VWAP Wave Breach Scanner
========================
Continuous monitoring daemon. Scans instruments on a schedule,
detects VWAP wave breaches, and pushes alerts via Telegram.
Environment variables:
TELEGRAM_BOT_TOKEN Telegram Bot API token (required)
TELEGRAM_CHAT_ID Chat ID to send alerts to (required)
SCAN_INTERVAL_SEC Seconds between scans (default: 120)
BREACH_THRESHOLD Sigma threshold for alerts (default: 2.0)
No LLM overhead pure Python, ~20MB RAM.
"""
import os
import sys
import time
import logging
from datetime import datetime, timezone
import requests
import yfinance as yf
import pandas as pd
from apscheduler.schedulers.background import BlockingScheduler
# ── Config ──────────────────────────────────────────────────────────────────
TELEGRAM_BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
TELEGRAM_CHAT_ID = os.environ["TELEGRAM_CHAT_ID"]
SCAN_INTERVAL_SEC = int(os.environ.get("SCAN_INTERVAL_SEC", "120"))
BREACH_THRESHOLD = float(os.environ.get("BREACH_THRESHOLD", "2.0"))
INSTRUMENTS = {
"Gold Futures": {"ticker": "GC=F", "decimal": 2},
"NASDAQ": {"ticker": "^IXIC", "decimal": 2},
"S&P 500": {"ticker": "^GSPC", "decimal": 2},
"Crude Oil": {"ticker": "CL=F", "decimal": 2},
}
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)-5s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
log = logging.getLogger(__name__)
# Track last alert state to prevent spam (no repeat within same threshold direction)
_last_alert = {}
# ── Telegram ────────────────────────────────────────────────────────────────
def send_telegram(message: str) -> bool:
"""Send a message via Telegram Bot API."""
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
payload = {
"chat_id": TELEGRAM_CHAT_ID,
"text": message,
"parse_mode": "Markdown",
"disable_web_page_preview": True,
}
try:
resp = requests.post(url, json=payload, timeout=10)
resp.raise_for_status()
log.info("Telegram alert sent: %s", message[:80])
return True
except Exception as e:
log.error("Telegram send failed: %s", e)
return False
# ── VWAP Calculation ────────────────────────────────────────────────────────
def compute_vwap(data: pd.DataFrame) -> dict | None:
"""Compute cumulative VWAP, σ, and deviation."""
df = data.copy()
df["typical"] = (df["High"] + df["Low"] + df["Close"]) / 3.0
df["tp_vol"] = df["typical"] * df["Volume"]
cum_tp_vol = df["tp_vol"].cumsum()
cum_vol = df["Volume"].cumsum().replace(0, 1)
df["cum_vwap"] = cum_tp_vol / cum_vol
df["deviation"] = df["typical"] - df["cum_vwap"]
df["cum_var"] = (df["deviation"] ** 2).cumsum() / cum_vol
df["sigma"] = df["cum_var"] ** 0.5
last = df.iloc[-1]
if last["sigma"] <= 0:
return None
return {
"price": last["Close"],
"vwap": last["cum_vwap"],
"sigma": last["sigma"],
"dev_sigmas": (last["Close"] - last["cum_vwap"]) / last["sigma"],
}
def fetch_data(ticker: str) -> pd.DataFrame:
"""Fetch recent intraday data via yfinance."""
try:
data = yf.Ticker(ticker).history(period="1d", interval="1m", auto_adjust=True)
except Exception:
data = pd.DataFrame()
if len(data) < 30:
data = yf.Ticker(ticker).history(period="5d", interval="1m", auto_adjust=True)
cutoff = pd.Timestamp.now(tz=data.index.tz) - pd.Timedelta(hours=24)
data = data[data.index >= cutoff]
return data
# ── Scanner ─────────────────────────────────────────────────────────────────
def run_scan() -> None:
"""Execute a full scan cycle and push any breaches."""
ts = datetime.now(timezone.utc).strftime("%H:%M:%S UTC")
log.info("── Scan %s ──", ts)
breaches = []
for name, cfg in INSTRUMENTS.items():
try:
data = fetch_data(cfg["ticker"])
if data.empty or len(data) < 20:
log.warning("SKIP %s — insufficient data (%d bars)", name, len(data))
continue
info = compute_vwap(data)
if info is None:
log.warning("SKIP %s — zero sigma", name)
continue
d = cfg["decimal"]
dev = info["dev_sigmas"]
log.info(" %-14s $%10.2f | VWAP $%10.2f | %+.2fσ", name, info["price"], info["vwap"], dev)
if abs(dev) >= BREACH_THRESHOLD:
# Prevent repeat spam: only alert if state changed
alert_key = f"{name}:{dev > 0}"
if _last_alert.get(alert_key) == "breach":
log.info("%s already in breach, skipping repeat", name)
continue
breaches.append((name, cfg["decimal"], dev, info["price"], info["vwap"], info["sigma"]))
_last_alert[alert_key] = "breach"
else:
_last_alert[f"{name}:True"] = "clean"
_last_alert[f"{name}:False"] = "clean"
except Exception as e:
log.error("ERROR %s: %s", name, e)
# Push alerts
for name, d, dev, price, vwap, sigma in breaches:
direction = "⬆️ UP" if dev > 0 else "⬇️ DOWN"
band_label = f"±{int(abs(dev))}σ"
severity = "🚨 **EXTREME**" if abs(dev) >= 3.0 else "⚡ **BREACH**"
msg = (
f"{severity} — VWAP Wave Alert\n\n"
f"**{name}** broke through **{band_label}** band\n"
f"Deviation: **{dev:+.2f}σ**\n"
f"Price: **${price:.{d}f}** | VWAP: **${vwap:.{d}f}**\n"
f"σ: ${sigma:.{d}f} | {direction}\n\n"
f"_at {ts}_"
)
send_telegram(msg)
# ── Main ────────────────────────────────────────────────────────────────────
def main() -> None:
log.info("=" * 60)
log.info(" VWAP Wave Breach Scanner")
log.info(" Interval: %d sec | Threshold: ±%.1fσ", SCAN_INTERVAL_SEC, BREACH_THRESHOLD)
log.info(" Telegram: @chat_id=%s", TELEGRAM_CHAT_ID)
log.info("=" * 60)
# Validate Telegram connectivity
send_telegram(
"🟢 *VWAP Breach Scanner* is online.\n"
f"Scanning every **{SCAN_INTERVAL_SEC}s** — threshold ±**{BREACH_THRESHOLD:.1f}σ**\n"
f"Monitoring: Gold, NASDAQ, S&P 500, Crude Oil"
)
scheduler = BlockingScheduler()
scheduler.add_job(run_scan, "interval", seconds=SCAN_INTERVAL_SEC, id="scan")
# Run immediately on start
run_scan()
log.info("Scanner running. Press Ctrl+C to stop.")
try:
scheduler.start()
except KeyboardInterrupt:
log.info("Shutting down...")
scheduler.shutdown()
if __name__ == "__main__":
main()

View file

@ -1,5 +0,0 @@
# ─── Config ─────────────────────────────────────────────────────────────────
# Edit these values. They will be injected into the deployment automatically.
SCAN_INTERVAL_SEC=120
BREACH_THRESHOLD=2.0

View file

@ -1,70 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: vwap-monitor
namespace: customer1
labels:
app: vwap-monitor
component: scanner
spec:
replicas: 1
strategy:
type: Recreate # only one instance should run
selector:
matchLabels:
app: vwap-monitor
template:
metadata:
labels:
app: vwap-monitor
component: scanner
annotations:
# Restart if config changes
checksum/config: "vwap-monitor-config"
spec:
terminationGracePeriodSeconds: 30
containers:
- name: scanner
image: us-central1-docker.pkg.dev/devops-lab-cluster/customer1/vwap-monitor:latest
imagePullPolicy: Always
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 250m
memory: 256Mi
envFrom:
- configMapRef:
name: vwap-monitor-config
env:
- name: TELEGRAM_BOT_TOKEN
valueFrom:
secretKeyRef:
name: vwap-monitor-secrets
key: telegram-bot-token
- name: TELEGRAM_CHAT_ID
valueFrom:
secretKeyRef:
name: vwap-monitor-secrets
key: telegram-chat-id
startupProbe:
exec:
command: ["/bin/sh", "-c", "python3 -c 'import scanner'"]
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 3
livenessProbe:
exec:
command: ["/bin/sh", "-c", "kill -0 1"]
initialDelaySeconds: 30
periodSeconds: 60
---
apiVersion: v1
kind: ConfigMap
metadata:
name: vwap-monitor-config
namespace: customer1
data:
SCAN_INTERVAL_SEC: "120"
BREACH_THRESHOLD: "2.0"

View file

@ -1,41 +0,0 @@
# ─── Kustomization ──────────────────────────────────────────────────────────
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: customer1
resources:
- secret.yaml
- deployment.yaml
configMapGenerator:
- name: vwap-monitor-config
envs:
- config.env
patches:
- patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: vwap-monitor
spec:
template:
spec:
containers:
- name: scanner
envFrom:
- configMapRef:
name: vwap-monitor-config
env:
- name: TELEGRAM_BOT_TOKEN
valueFrom:
secretKeyRef:
name: vwap-monitor-secrets
key: telegram-bot-token
- name: TELEGRAM_CHAT_ID
valueFrom:
secretKeyRef:
name: vwap-monitor-secrets
key: telegram-chat-id

View file

@ -1,15 +0,0 @@
# ─── Secret Template ────────────────────────────────────────────────────────
# Replace the values below before applying.
# Alternatively, store in a real Secret Manager (GCP Secret Manager, external-secrets).
apiVersion: v1
kind: Secret
metadata:
name: vwap-monitor-secrets
namespace: customer1
type: Opaque
stringData:
# Get from: https://t.me/botfather → /newbot → copy token
telegram-bot-token: "YOUR_BOT_TOKEN_HERE"
# Get from: @userinfobot or inspect network tab in Telegram Web
telegram-chat-id: "YOUR_CHAT_ID_HERE"

View file

@ -1,28 +0,0 @@
# OpenClaw Brain v1.1 Implementation Plan
> **Status:** Ready for subagent-driven-development. <48hr goal.
**Goal:** Full product launch per spec. US GKE DeepSeek-V4-Pro API, flat subs, unlimited tokens.
**Updated Pricing Confirmed:** Spot $3.40-4.55/hr node → $2.5K-3.3K/mo full util. Breakeven: 6 Personal ($49) or 2 Team ($199) subs/mo.
**Approach:** Extend gcloud-lab OpenClaw PAaaS (customer1). New namespace `openclaw-brain`. Stripe webhooks for subs/keys.
## Tasks (Bite-Sized TDD)
### Task 1: Scaffold dirs
**Files:** mkdir apps/base/openclaw-brain apps/staging/openclaw-brain
**Step 1:** `mkdir -p apps/{base,staging}/openclaw-brain`
**Step 2:** namespace.yaml (copy customer1 pattern)
```yaml
apiVersion: v1
kind: Namespace
metadata:
name: openclaw-brain
```
**Verify:** `kubectl apply --dry-run=client -f apps/base/openclaw-brain/namespace.yaml`
**Commit:** git add apps/ ; git commit -m \"feat(openclaw-brain): scaffold\"
*(Abbrev; full 30+ tasks: Terraform nodepools w/ machine_type='a3-ultragpu-8g' spot=true gpu=8, vLLM args --model=DeepSeek/DeepSeek-V4-Pro --tp=8 --max-model-len=1e6 --enable-prefix-caching, FastAPI w/ Stripe Subscriptions API + redis-py quotas, KEDA ScaledObject on http_requests &gt;5/min throttle, landing HTML w/ Stripe Checkout.js, flux kustomize add, terraform apply, smoke tests)*
**Next:** Task 1 scaffold + git commit.

View file

@ -1,25 +0,0 @@
# Dual-Tier AI Architecture: L4 Dispatcher & A100 Deep Thinker
This document outlines the cost-optimized, dual-tier LLM architecture deployed in the cluster using OpenClaw, vLLM, and KEDA.
## Concept
Instead of running an expensive A100 GPU 24/7 for all requests, we split the cognitive load into two tiers: a lightweight "Dispatcher" and a heavyweight "Deep Thinker." This mimics a senior/junior developer dynamic, optimizing both response latency and cloud GCP billing.
## Tier 1: The Dispatcher (L4 GPU)
- **Hardware:** 1x NVIDIA L4 (24GB VRAM)
- **Model:** `Qwen2.5-Coder-7B-Instruct`
- **Status:** Runs 24/7 (1 replica)
- **Role:** Acts as the baseline consciousness for OpenClaw. Handles daily chatter, log parsing, straightforward tool routing, and triage. Lightning-fast token generation at a fraction of the cost.
## Tier 2: The Deep Thinker (A100 GPU)
- **Hardware:** 1x NVIDIA A100 (80GB VRAM)
- **Model:** `Qwen3.6-27B-heretic`
- **Status:** Scaled to zero by default.
- **Role:** Activated only for massive context tasks, deep research, and complex multi-file architectural reasoning.
## Scaling & Routing Mechanics (KEDA + OpenClaw)
1. **Scale-to-Zero:** The A100 deployment is managed by a KEDA `HTTPScaledObject`. It scales down to `0` replicas after 15 minutes of inactivity.
2. **Default Routing:** OpenClaw's global default model is set to the L4 endpoint. All standard messages hit the L4 immediately.
3. **Sub-Agent Handoff:** When a complex task is requested, the L4 agent uses the `sessions_spawn` tool to create an isolated sub-agent, overriding the model target to the A100 endpoint.
4. **Cold Start:** KEDA intercepts the sub-agent's request, scales the A100 node from 0 to 1, waits for vLLM to load (~1-2 minutes), and then passes the request through.
5. **Manual Override:** A user can bypass the L4 entirely for a specific session by typing `/model local-vllm/coder3101/Qwen3.5-27B-heretic` in the OpenClaw chat.

View file

@ -1 +0,0 @@
HauhauCS/Qwen3.5-27B-Uncensored-HauhauCS-Aggressive

View file

@ -1,109 +0,0 @@
# Build and push container images to Artifact Registry
name: Build & Push Images
on:
push:
branches: [main, develop]
paths:
- "trading-platform/**"
- "!trading-platform/infra/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
GCP_PROJECT_ID: customer1-gke
GCP_REGION: us-central1
ARTIFACT_REGISTRY: us-central1-docker.pkg.dev/${{ env.GCP_PROJECT_ID }}/trading
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/${{ env.GCP_PROJECT_ID }}/locations/global/workloadIdentityPools/github-pool/providers/github-provider
service_account: ci-builder@${{ env.GCP_PROJECT_ID }}.iam.gserviceaccount.com
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
- name: Configure Docker for Artifact Registry
run: gcloud auth configure-docker ${{ env.GCP_REGION }}-docker.pkg.dev --quiet
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Generate image tags
id: tags
run: |
SHORT_SHA="${GITHUB_SHA::8}"
BRANCH="${GITHUB_REF#refs/heads/}"
echo "tag_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
echo "tag_branch=${BRANCH}" >> $GITHUB_OUTPUT
echo "tag_latest=${BRANCH}" >> $GITHUB_OUTPUT
# ---- Execute Service ----
- name: Build and push execute-service
uses: docker/build-push-action@v5
with:
context: trading-platform/execute-service
file: trading-platform/infra/dockerfiles/execute-service/Dockerfile
push: true
tags: |
${{ env.ARTIFACT_REGISTRY }}/execute-service:${{ steps.tags.outputs.tag_sha }}
${{ env.ARTIFACT_REGISTRY }}/execute-service:${{ steps.tags.outputs.tag_branch }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ---- News Service ----
- name: Build and push news-service
uses: docker/build-push-action@v5
with:
context: trading-platform/news-service
file: trading-platform/infra/dockerfiles/news-service/Dockerfile
push: true
tags: |
${{ env.ARTIFACT_REGISTRY }}/news-service:${{ steps.tags.outputs.tag_sha }}
${{ env.ARTIFACT_REGISTRY }}/news-service:${{ steps.tags.outputs.tag_branch }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ---- Data Service ----
- name: Build and push data-service
uses: docker/build-push-action@v5
with:
context: trading-platform/data-service
file: trading-platform/infra/dockerfiles/data-service/Dockerfile
push: true
tags: |
${{ env.ARTIFACT_REGISTRY }}/data-service:${{ steps.tags.outputs.tag_sha }}
${{ env.ARTIFACT_REGISTRY }}/data-service:${{ steps.tags.outputs.tag_branch }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ---- Dashboard ----
- name: Build and push dashboard
uses: docker/build-push-action@v5
with:
context: trading-platform/dashboard
file: trading-platform/infra/dockerfiles/dashboard/Dockerfile
push: true
tags: |
${{ env.ARTIFACT_REGISTRY }}/dashboard:${{ steps.tags.outputs.tag_sha }}
${{ env.ARTIFACT_REGISTRY }}/dashboard:${{ steps.tags.outputs.tag_branch }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Notify deployment pipeline
run: |
echo "Images pushed successfully with tag ${{ steps.tags.outputs.tag_sha }}"
# This can trigger the deploy workflow via repository dispatch
# or be used by the deploy workflow as a workflow_run trigger

View file

@ -1,135 +0,0 @@
# Build and test on pull requests
name: Build & Test
on:
pull_request:
branches: [main, develop]
paths:
- "trading-platform/execute-service/**"
- "trading-platform/news-service/**"
- "trading-platform/data-service/**"
- "trading-platform/dashboard/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# ---- Python services ----
test-execute-service:
runs-on: ubuntu-latest
defaults:
run:
working-directory: trading-platform/execute-service
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
version: "latest"
- name: Install dependencies
run: uv sync --all-extras --dev
- name: Run tests
run: uv run pytest --asyncio-mode=auto -v --tb=short
- name: Lint
run: uv run ruff check app/ tests/
test-news-service:
runs-on: ubuntu-latest
defaults:
run:
working-directory: trading-platform/news-service
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pytest pytest-asyncio
- name: Run tests
run: python -m pytest -v --tb=short || true
test-data-service:
runs-on: ubuntu-latest
defaults:
run:
working-directory: trading-platform/data-service
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
version: "latest"
- name: Install dependencies
run: uv sync --all-extras --dev
- name: Run tests
run: uv run pytest --asyncio-mode=auto -v --tb=short
# ---- Dashboard ----
test-dashboard:
runs-on: ubuntu-latest
defaults:
run:
working-directory: trading-platform/dashboard
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: trading-platform/dashboard/package-lock.json
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Lint
run: npm run lint
# ---- Docker build validation ----
docker-build-check:
needs: [test-execute-service, test-news-service, test-data-service, test-dashboard]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build execute-service
uses: docker/build-push-action@v5
with:
context: trading-platform/execute-service
file: trading-platform/infra/dockerfiles/execute-service/Dockerfile
push: false
load: false
- name: Build news-service
uses: docker/build-push-action@v5
with:
context: trading-platform/news-service
file: trading-platform/infra/dockerfiles/news-service/Dockerfile
push: false
load: false
- name: Build data-service
uses: docker/build-push-action@v5
with:
context: trading-platform/data-service
file: trading-platform/infra/dockerfiles/data-service/Dockerfile
push: false
load: false
- name: Build dashboard
uses: docker/build-push-action@v5
with:
context: trading-platform/dashboard
file: trading-platform/infra/dockerfiles/dashboard/Dockerfile
push: false
load: false

View file

@ -1,132 +0,0 @@
# Deploy to staging/prod via Helm on GKE
name: Deploy
on:
workflow_dispatch:
inputs:
environment:
description: "Target environment"
required: true
default: "staging"
type: choice
options:
- staging
- production
image_tag:
description: "Container image tag (SHA or branch name)"
required: true
type: string
workflow_run:
workflows: ["Build & Push Images"]
types: [completed]
branches: [main, develop]
permissions:
contents: read
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
environment: ${{ github.event.inputs.environment || (github.ref == 'refs/heads/main' && 'production' || 'staging') }}
steps:
- uses: actions/checkout@v4
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/customer1-gke/locations/global/workloadIdentityPools/github-pool/providers/github-provider
service_account: ci-deployer@customer1-gke.iam.gserviceaccount.com
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
- name: Configure kubectl for GKE
run: |
gcloud container clusters get-credentials \
${{ github.event.inputs.environment || (github.ref == 'refs/heads/main' && 'prod' || 'staging') }}-cluster \
--region us-central1 \
--project customer1-gke
- name: Install Helm
uses: azure/setup-helm@v3
with:
version: v3.14.0
- name: Install SOPS + Age
run: |
curl -Lo /tmp/sops.zip https://github.com/getsops/sops/releases/download/v3.8.1/sops-v3.8.1_linux.amd64.zip
unzip /tmp/sops.zip -d /tmp/
sudo mv /tmp/sops /usr/local/bin/sops
go install github.com/getsops/gopgs@latest || true
go install filippo.io/age/cmd/age@latest || true
- name: Create namespace
run: |
kubectl create namespace trading --dry-run=client -o yaml | kubectl apply -f -
- name: Decrypt secrets
run: |
# Copy age key for SOPS decryption
mkdir -p /etc/sops
echo "${{ secrets.SOPS_AGE_KEY }}" > /etc/sops/age.key
chmod 600 /etc/sops/age.key
export SOPS_AGE_KEY_FILE=/etc/sops/age.key
# Decrypt secrets
sops -d trading-platform/infra/helm/trading-platform/trading-secrets.yaml > trading-platform/infra/helm/trading-platform/trading-secrets-decrypted.yaml
- name: Deploy with Helm
run: |
IMAGE_TAG="${{ github.event.inputs.image_tag }}"
ENVIRONMENT="${{ github.event.inputs.environment || (github.ref == 'refs/heads/main' && 'production' || 'staging') }}"
helm upgrade --install trading-platform \
trading-platform/infra/helm/trading-platform \
--namespace trading \
--create-namespace \
--set global.environment=${ENVIRONMENT} \
--set executeService.image.tag=${IMAGE_TAG} \
--set newsService.image.tag=${IMAGE_TAG} \
--set dataService.image.tag=${IMAGE_TAG} \
--set dashboard.image.tag=${IMAGE_TAG} \
--wait \
--timeout 10m \
--atomic
- name: Apply decrypted secrets
run: |
export SOPS_AGE_KEY_FILE=/etc/sops/age.key
sops -d trading-platform/infra/helm/trading-platform/trading-secrets.yaml | kubectl apply -f -
- name: Verify deployment
run: |
echo "=== Pod Status ==="
kubectl get pods -n trading
echo ""
echo "=== Service Status ==="
kubectl get svc -n trading
echo ""
echo "=== Ingress ==="
kubectl get ingress -n trading
- name: Post-deployment smoke test
run: |
# Wait for readiness
kubectl wait --for=condition=available --timeout=5m \
deployment/execute-service -n trading
kubectl wait --for=condition=available --timeout=5m \
deployment/news-service -n trading
kubectl wait --for=condition=available --timeout=5m \
deployment/data-service -n trading
kubectl wait --for=condition=available --timeout=5m \
deployment/dashboard -n trading
echo "All services deployed and healthy"
- name: Rollback on failure
if: failure()
run: |
helm rollback trading-platform -n trading --timeout 10m || true
echo "Rolled back to previous release"

View file

@ -1,112 +0,0 @@
# Trading Platform — Kubernetes Deployment
Kubernetes deployment infrastructure for the trading platform microservices running on GKE (customer1 namespace).
## Directory Structure
```
trading-platform/
├── dockerfiles/ # Multi-stage Dockerfiles for each service
│ ├── dashboard/ # Next.js frontend (port 3000)
│ ├── data-service/ # Data pipeline service (port 8000)
│ ├── execute-service/ # Trading engine: Hyperliquid + Solana (port 8000)
│ └── news-service/ # CNPG connector + Kafka producer (port 8000)
├── helm/ # Helm chart for full platform deployment
│ ├── Chart.yaml # Chart metadata
│ ├── values.yaml # Default values (images, replicas, resources, infra)
│ ├── .sops.yaml # SOPS configuration for secret encryption
│ ├── trading-secrets.yaml # SOPS-encrypted secrets template
│ └── templates/ # 19 Kubernetes manifest templates
│ ├── _helpers.tpl # Template helpers
│ ├── namespace.yaml # Namespace resource
│ ├── configmap.yaml # Shared ConfigMap
│ ├── secrets.yaml # Secrets (SOPS-encrypted via trading-secrets.yaml)
│ ├── ingress.yaml # GCE Ingress for all services
│ ├── NOTES.txt # Post-install notes
│ ├── dashboard/ # Dashboard Deployment + Service
│ ├── data-service/ # Data Service Deployment + Service
│ ├── execute-service/ # Execute Service Deployment + Service
│ ├── news-service/ # News Service Deployment + Service
│ ├── infrastructure/ # PostgreSQL, Redis, Kafka
│ ├── network-policies/ # Default deny + explicit allow policies
│ └── cert-manager/ # Certificates & issuers
├── deploy/ # Additional deployment resources
│ ├── k8s/base/ # Raw K8s manifests (non-Helm fallback)
│ ├── helm/ # Individual per-service Helm charts
│ ├── dockerfiles/ # Alternative Dockerfiles (api-gateway, services)
│ ├── docker-compose/ # Local dev compose files
│ ├── scripts/ # deploy.sh, generate-mtls-certs.sh
│ └── mtls/ # mTLS documentation
└── .github/workflows/ # CI/CD pipelines
├── build-test.yml # Build + unit tests on PR
├── build-push.yml # Build + push to GAR on merge
└── deploy.yml # Helm deploy to GKE on push to master
```
## Services
| Service | Port | Description |
|---------|------|-------------|
| Dashboard | 3000 | Next.js trading dashboard |
| Data Service | 8000 | Data pipeline, Postgres + Redis + Kafka consumers |
| Execute Service | 8000 | Trading engine with Hyperliquid + Solana integration |
| News Service | 8000 | CryptoPanic/GNews connector, Kafka producer |
## Infrastructure Components
- **PostgreSQL 17** — Primary database for trades, orders, user data
- **Redis 7** — Caching layer with 3-node cluster
- **Kafka 3.9** (KRaft mode) — Event streaming (trades, orders, news topics)
- **GCE Ingress** — External traffic routing with TLS termination
- **Cert-Manager** — Automatic TLS certificates (Let's Encrypt + internal CA)
- **Network Policies** — Default deny ingress/egress with explicit allow rules
## Deploying
### Prerequisites
- GKE cluster: `customer1-gke` (us-central1)
- Helm 3 installed locally or in CI
- SOPS configured with Age key (`trading-secrets.yaml` must be encrypted)
- Access to `us-central1-docker.pkg.dev/customer1-gke/trading` registry
### Quick Deploy
```bash
# 1. Encrypt secrets (must use the SOPS Age key)
cd helm
sops -e -i trading-secrets.yaml
# 2. Install/upgrade the Helm release
helm upgrade --install trading-platform ./helm \
--namespace customer1 \
--create-namespace \
--values helm/values.yaml \
--set global.environment=production
```
### CI/CD
- **PR opened**`build-test.yml` runs unit tests
- **Merged to master**`build-push.yml` builds images and pushes to GAR
- **Push to master**`deploy.yml` runs `helm upgrade` on GKE
## Secrets
Secrets are managed via [SOPS](https://github.com/getsops/sops) with Age encryption.
The `.sops.yaml` file configures which keys to use for each path.
```bash
# Encrypt the secrets file
sops -e -i helm/trading-secrets.yaml
# Decrypt (for debugging)
sops -d helm/trading-secrets.yaml
```
**Never commit unencrypted secrets to git.**
## Namespace
The platform deploys into the `customer1` namespace on the GKE cluster.
Update `global.namespace` in `helm/values.yaml` or override via `--set` during install.

View file

@ -1,237 +0,0 @@
name: Build, Test, and Deploy Trading Platform
on:
push:
branches: [main]
paths:
- 'trading-platform/**'
pull_request:
branches: [main]
paths:
- 'trading-platform/**'
workflow_dispatch:
inputs:
environment:
description: 'Deploy environment'
type: choice
options:
- staging
- production
default: staging
env:
REGISTRY: ghcr.io
IMAGE_PREFIX: ${{ github.repository_owner }}/trading-platform
permissions:
contents: read
packages: write
jobs:
# ── Test All Services ──────────────────────────────────────────────────
test-python-services:
name: Test Python Services
runs-on: ubuntu-latest
strategy:
matrix:
service: [execute-service, data-service, news-service, solana-quant-bot]
defaults:
run:
working-directory: trading-platform/${{ matrix.service }}
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
working-directory: trading-platform/${{ matrix.service }}
- name: Run tests with coverage
run: |
pytest tests/ --cov=app --cov-report=xml --cov-report=term-missing -v
working-directory: trading-platform/${{ matrix.service }}
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: trading-platform/${{ matrix.service }}/coverage.xml
flags: ${{ matrix.service }}
test-dashboard:
name: Test Dashboard (Next.js)
runs-on: ubuntu-latest
defaults:
run:
working-directory: trading-platform/dashboard
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: trading-platform/dashboard/package-lock.json
- name: Install dependencies
run: npm ci
working-directory: trading-platform/dashboard
- name: Run linting
run: npm run lint
working-directory: trading-platform/dashboard
- name: Build application
run: npm run build
working-directory: trading-platform/dashboard
test-api-gateway:
name: Lint API Gateway Configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate nginx config syntax
run: |
docker run --rm -v $(pwd)/deploy/k8s/base/gateway:/etc/nginx/conf.d:ro nginx:1.25-alpine nginx -t
# ── Build and Push Container Images ─────────────────────────────────────
build-and-push:
needs: [test-python-services, test-dashboard, test-api-gateway]
name: Build & Push Images
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
strategy:
matrix:
service: [execute-service, data-service, news-service, api-gateway, dashboard, solana-quant-bot]
steps:
- uses: actions/checkout@v4
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/${{ matrix.service }}
tags: |
type=sha,prefix=
type=ref,event=branch
type=semver,pattern={{version}}
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: trading-platform/deploy/dockerfiles/${{ matrix.service }}.Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ── Deploy to Kubernetes (Helm) ────────────────────────────────────────
deploy-staging:
needs: [build-and-push]
name: Deploy to Staging
runs-on: ubuntu-latest
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'staging')
environment: staging
steps:
- uses: actions/checkout@v4
- name: Set up kubectl
uses: azure/setup-kubectl@v3
with:
version: 'v1.29.0'
- name: Configure kubeconfig
run: |
echo "${{ secrets.STAGING_KUBECONFIG }}" | base64 -d > $HOME/.kube/config
env:
STAGING_KUBECONFIG: ${{ secrets.STAGING_KUBECONFIG }}
- name: Install Helm
uses: azure/setup-helm@v3
with:
version: 'v3.14.0'
- name: Deploy with Helm (staging)
run: |
helm upgrade --install trading-platform-staging \\
deploy/helm/trading-platform \\
--namespace customer1-staging \\
--create-namespace \\
--set image.tag=${{ github.sha }} \\
--wait --timeout 10m
- name: Verify deployment
run: |
kubectl rollout status deployment/execute-service -n customer1-staging --timeout=5m
kubectl rollout status deployment/data-service -n customer1-staging --timeout=5m
kubectl rollout status deployment/news-service -n customer1-staging --timeout=5m
kubectl rollout status deployment/api-gateway -n customer1-staging --timeout=5m
kubectl rollout status deployment/dashboard -n customer1-staging --timeout=5m
kubectl rollout status deployment/solana-quant-bot -n customer1-staging --timeout=5m
deploy-production:
needs: [deploy-staging]
name: Deploy to Production
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'production'
environment: production
steps:
- uses: actions/checkout@v4
- name: Set up kubectl
uses: azure/setup-kubectl@v3
with:
version: 'v1.29.0'
- name: Configure kubeconfig
run: |
echo "${{ secrets.PRODUCTION_KUBECONFIG }}" | base64 -d > $HOME/.kube/config
- name: Install Helm
uses: azure/setup-helm@v3
with:
version: 'v3.14.0'
- name: Deploy with Helm (production)
run: |
helm upgrade --install trading-platform-production \\
deploy/helm/trading-platform \\
--namespace customer1 \\
--create-namespace \\
--set image.tag=${{ github.sha }} \\
--values deploy/helm/trading-platform/values-production.yaml \\
--wait --timeout 15m
- name: Verify deployment
run: |
kubectl rollout status deployment/execute-service -n customer1 --timeout=5m
kubectl rollout status deployment/data-service -n customer1 --timeout=5m
kubectl rollout status deployment/news-service -n customer1 --timeout=5m
kubectl rollout status deployment/api-gateway -n customer1 --timeout=5m
kubectl rollout status deployment/dashboard -n customer1 --timeout=5m
- name: Run post-deployment health checks
run: |
# Check all services respond to health endpoints
for service in execute-service data-service news-service api-gateway dashboard; do
echo "Health check: $service"
kubectl run healthcheck-$service --rm --restart=Never --image=curlimages/curl \\
--command -- curl -sf http://$service:$(kubectl get svc $service -o jsonpath='{.spec.ports[0].port}')/health || exit 1
done

View file

@ -1,204 +0,0 @@
# =============================================================================
# Docker Compose — Local Development Environment
# =============================================================================
# Brings up all microservices + infrastructure for local development
#
# Usage:
# docker compose -f trading-platform/deploy/docker-compose/docker-compose.dev.yml up -d
# docker compose -f trading-platform/deploy/docker-compose/docker-compose.dev.yml down -v
# docker compose -f trading-platform/deploy/docker-compose/docker-compose.dev.yml logs -f execute-service
# =============================================================================
x-common-env: &common-env
TRADING_ENV: development
LOG_LEVEL: debug
services:
# ── Infrastructure ────────────────────────────────────────────────────
postgres:
image: postgres:16-alpine
container_name: trading-postgres-dev
environment:
POSTGRES_USER: trading
POSTGRES_PASSWORD: trading_dev_password
POSTGRES_DB: trading_db
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U trading -d trading_db"]
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
networks:
- trading-network
redis:
image: redis:7-alpine
container_name: trading-redis-dev
ports:
- "6379:6379"
volumes:
- redis-data:/data
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
networks:
- trading-network
kafka:
image: apache/kafka:3.7.0
container_name: trading-kafka-dev
ports:
- "9092:9092"
- "9093:9093"
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT
KAFKA_CLUSTER_ID: MkU3OEVBNTcwT0FBQT0=
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
KAFKA_LOG_RETENTION_HOURS: 24
KAFKA_LOG_SEGMENT_BYTES: 1073741824
volumes:
- kafka-data:/var/lib/kafka/data
healthcheck:
test: ["CMD-SHELL", "kafka-topics.sh --bootstrap-server localhost:9092 --list || exit 1"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
restart: unless-stopped
networks:
- trading-network
# ── Microservices ─────────────────────────────────────────────────────
data-service:
build:
context: ../../..
dockerfile: trading-platform/deploy/dockerfiles/data-service.Dockerfile
container_name: trading-data-service-dev
environment:
<<: *common-env
DATABASE_URL: postgresql+asyncpg://trading:trading_dev_password@postgres:5432/trading_db
REDIS_URL: redis://redis:6379/0
KAFKA_BOOTSTRAP_SERVERS: kafka:9092
LOG_LEVEL: debug
ports:
- "8001:8001"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
kafka:
condition: service_healthy
restart: unless-stopped
networks:
- trading-network
execute-service:
build:
context: ../../..
dockerfile: trading-platform/deploy/dockerfiles/execute-service.Dockerfile
container_name: trading-execute-service-dev
environment:
<<: *common-env
EXECUTE_DATABASE_URL: sqlite+aiosqlite:///./execute.db
EXECUTE_JWT_SECRET_KEY: dev-secret-change-me
EXECUTE_HYPERLIQUID_TESTNET: "true"
EXECUTE_MTLS_ENABLED: "false"
EXECUTE_MARKET_DATA_SERVICE_URL: http://data-service:8001
LOG_LEVEL: debug
ports:
- "8000:8000"
depends_on:
data-service:
condition: service_healthy
restart: unless-stopped
networks:
- trading-network
news-service:
build:
context: ../../..
dockerfile: trading-platform/deploy/dockerfiles/news-service.Dockerfile
container_name: trading-news-service-dev
environment:
<<: *common-env
DATABASE_URL: postgresql+asyncpg://trading:trading_dev_password@postgres:5432/trading_db
KAFKA_BOOTSTRAP_SERVERS: kafka:9092
REDIS_URL: redis://redis:6379/1
LOG_LEVEL: debug
ports:
- "8002:8002"
depends_on:
postgres:
condition: service_healthy
kafka:
condition: service_healthy
restart: unless-stopped
networks:
- trading-network
api-gateway:
build:
context: ../../..
dockerfile: trading-platform/deploy/dockerfiles/api-gateway.Dockerfile
container_name: trading-api-gateway-dev
environment:
<<: *common-env
ports:
- "8080:8080"
- "8443:8443"
depends_on:
execute-service:
condition: service_healthy
data-service:
condition: service_healthy
news-service:
condition: service_healthy
restart: unless-stopped
networks:
- trading-network
dashboard:
build:
context: ../../..
dockerfile: trading-platform/deploy/dockerfiles/dashboard.Dockerfile
container_name: trading-dashboard-dev
environment:
NEXT_PUBLIC_API_URL: http://localhost:8080
NODE_ENV: development
ports:
- "3000:3000"
depends_on:
api-gateway:
condition: service_started
restart: unless-stopped
networks:
- trading-network
volumes:
postgres-data:
redis-data:
kafka-data:
networks:
trading-network:
driver: bridge

View file

@ -1,26 +0,0 @@
# =============================================================================
# API Gateway Dockerfile — Nginx-based reverse proxy with rate limiting
# =============================================================================
FROM nginx:1.25-alpine AS production
# Copy custom nginx configuration
COPY deploy/k8s/base/gateway/nginx.conf /etc/nginx/nginx.conf
COPY deploy/k8s/base/gateway/conf.d/ /etc/nginx/conf.d/
# Create required directories
RUN mkdir -p /etc/nginx/ssl \
/etc/nginx/conf.d \
/var/cache/nginx \
/var/run/nginx \
/var/log/nginx \
&& touch /var/run/nginx/nginx.pid
# Security: run as nginx user (already exists in alpine image)
USER nginx
EXPOSE 8080 8443
HEALTHCHECK --interval=15s --timeout=3s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
CMD ["nginx", "-g", "daemon off;"]

View file

@ -1,40 +0,0 @@
# =============================================================================
# Dashboard Dockerfile — Next.js multi-stage build with static export
# =============================================================================
FROM node:20-alpine AS builder
WORKDIR /app
# Install dependencies first (better layer caching)
COPY trading-platform/dashboard/package*.json ./
RUN npm ci
# Copy source and build
COPY trading-platform/dashboard/ ./
RUN npm run build
# Production stage
FROM node:20-alpine AS production
# Security: non-root user
RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001
WORKDIR /app
# Copy built output and package.json from builder
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
USER nextjs
EXPOSE 3000
ENV NODE_ENV=production
ENV PORT=3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/ || exit 1
CMD ["node", "server.js"]

View file

@ -1,32 +0,0 @@
# =============================================================================
# Data Service Dockerfile — Multi-stage build
# =============================================================================
FROM python:3.12-slim AS builder
WORKDIR /build
COPY trading-platform/data-service/pyproject.toml ./
RUN pip install --no-cache-dir --prefix=/install .
# Production stage
FROM python:3.12-slim AS production
# Security: non-root user
RUN useradd -m --system appuser
# Copy dependencies from builder
COPY --from=builder /install /usr/local
# Copy application code
WORKDIR /app
COPY --chown=appuser:appuser trading-platform/data-service/data_service/ ./data_service/
COPY --chown=appuser:appuser trading-platform/data-service/pyproject.toml ./
USER appuser
# Health check
HEALTHCHECK --interval=15s --timeout=5s --start-period=15s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8001/health')" || exit 1
EXPOSE 8001
CMD ["uvicorn", "data_service.app.main:app", "--host", "0.0.0.0", "--port", "8001"]

View file

@ -1,36 +0,0 @@
# =============================================================================
# Execute Service Dockerfile — Multi-stage build for minimal image
# =============================================================================
# Build stage: compile dependencies
FROM python:3.12-slim AS builder
WORKDIR /build
COPY trading-platform/execute-service/pyproject.toml ./
RUN pip install --no-cache-dir --prefix=/install .
# Production stage
FROM python:3.12-slim AS production
# Security: non-root user
RUN useradd -m --system appuser
# Copy dependencies from builder
COPY --from=builder /install /usr/local
# Copy application code
WORKDIR /app
COPY --chown=appuser:appuser trading-platform/execute-service/app/ ./app/
COPY --chown=appuser:appuser trading-platform/execute-service/pyproject.toml ./
# Create required directories with proper permissions
RUN mkdir -p /tmp /app/data && chown -R appuser:appuser /tmp /app/data
USER appuser
# Health check using Python (curl not in slim)
HEALTHCHECK --interval=15s --timeout=5s --start-period=15s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

View file

@ -1,41 +0,0 @@
# =============================================================================
# News Service Dockerfile — Multi-stage build with NLTK data
# =============================================================================
FROM python:3.12-slim AS builder
WORKDIR /build
COPY trading-platform/news-service/requirements.txt ./
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# Production stage
FROM python:3.12-slim AS production
# Install system dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
gcc libpq-dev && \
rm -rf /var/lib/apt/lists/*
# Install Python dependencies from builder
COPY --from=builder /install /usr/local
# Download NLTK data for textblob
RUN python -c "import nltk; nltk.download('punkt'); nltk.download('punkt_tab'); nltk.download('averaged_perceptron_tagger')"
# Security: non-root user
RUN useradd -m --system appuser
# Copy application code
WORKDIR /app
COPY --chown=appuser:appuser trading-platform/news-service/app/ ./app/
COPY --chown=appuser:appuser trading-platform/news-service/requirements.txt ./
USER appuser
# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8002/health')" || exit 1
EXPOSE 8002
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8002", "--workers", "4"]

View file

@ -1,34 +0,0 @@
# =============================================================================
# Trading Platform — Root Helm Chart
# =============================================================================
apiVersion: v2
name: trading-platform
description: Helm chart for the entire trading platform microservices
type: application
version: 0.1.0
appVersion: "0.1.0"
dependencies:
- name: api-gateway
version: "0.1.0"
repository: "file://../api-gateway"
- name: execute-service
version: "0.1.0"
repository: "file://../execute-service"
- name: data-service
version: "0.1.0"
repository: "file://../data-service"
- name: news-service
version: "0.1.0"
repository: "file://../news-service"
- name: dashboard
version: "0.1.0"
repository: "file://../dashboard"
- name: cert-manager
version: "1.14.0"
repository: https://charts.jetstack.io
condition: cert-manager.enabled
- name: ingress-nginx
version: "4.9.0"
repository: https://kubernetes.github.io/ingress-nginx
condition: ingress-nginx.enabled

View file

@ -1,60 +0,0 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "<SERVICE_NAME>.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "<SERVICE_NAME>.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "<SERVICE_NAME>.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "<SERVICE_NAME>.labels" -}}
helm.sh/chart: {{ include "<SERVICE_NAME>.chart" . }}
{{ include "<SERVICE_NAME>.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "<SERVICE_NAME>.selectorLabels" -}}
app.kubernetes.io/name: {{ include "<SERVICE_NAME>.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "<SERVICE_NAME>.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "<SERVICE_NAME>.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View file

@ -1,6 +0,0 @@
apiVersion: v2
name: api-gateway
description: API Gateway — Nginx reverse proxy
type: application
version: 0.1.0
appVersion: "0.1.0"

View file

@ -1,61 +0,0 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "api-gateway.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "api-gateway.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "api-gateway.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "api-gateway.labels" -}}
helm.sh/chart: {{ include "api-gateway.chart" . }}
{{ include "api-gateway.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "api-gateway.selectorLabels" -}}
app.kubernetes.io/name: {{ include "api-gateway.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "api-gateway.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "api-gateway.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View file

@ -1,57 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "api-gateway.fullname" . }}
labels:
{{- include "api-gateway.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "api-gateway.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "api-gateway.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "api-gateway.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 8080
protocol: TCP
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 12 }}
{{- end }}

View file

@ -1,32 +0,0 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "api-gateway.fullname" . }}
labels:
{{- include "api-gateway.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "api-gateway.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View file

@ -1,61 +0,0 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "api-gateway.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: extensions/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "api-gateway.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
pathType: {{ .pathType }}
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ $fullName }}
port:
number: {{ $svcPort }}
{{- else }}
serviceName: {{ $fullName }}
servicePort: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

View file

@ -1,29 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "api-gateway.fullname" . }}-network-policy
labels:
{{- include "api-gateway.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "api-gateway.selectorLabels" . | nindent 6 }}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: api-gateway
ports:
- port: http
protocol: TCP
egress:
# Allow DNS resolution
- to: []
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP

View file

@ -1,15 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "api-gateway.fullname" . }}
labels:
{{- include "api-gateway.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "api-gateway.selectorLabels" . | nindent 4 }}

View file

@ -1,12 +0,0 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "api-gateway.serviceAccountName" . }}
labels:
{{- include "api-gateway.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View file

@ -1,80 +0,0 @@
# trading-platform/api-gateway Helm chart values
replicaCount: 2
image:
repository: trading-platform/api-gateway
pullPolicy: IfNotPresent
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
annotations: {}
name: ""
podAnnotations: {}
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
service:
type: ClusterIP
port: 8080
ingress:
enabled: true
className: nginx
annotations: {}
hosts:
- host: api.trading.example.com
paths:
- path: /
pathType: Prefix
tls: []
resources:
limits:
cpu: "500m"
memory: 256Mi
requests:
cpu: "250m"
memory: 128Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
nodeSelector: {}
tolerations: []
affinity: {}

View file

@ -1,6 +0,0 @@
apiVersion: v2
name: dashboard
description: Dashboard frontend
type: application
version: 0.1.0
appVersion: "0.1.0"

View file

@ -1,61 +0,0 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "dashboard.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "dashboard.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "dashboard.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "dashboard.labels" -}}
helm.sh/chart: {{ include "dashboard.chart" . }}
{{ include "dashboard.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "dashboard.selectorLabels" -}}
app.kubernetes.io/name: {{ include "dashboard.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "dashboard.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "dashboard.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View file

@ -1,57 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "dashboard.fullname" . }}
labels:
{{- include "dashboard.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "dashboard.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "dashboard.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "dashboard.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 3000
protocol: TCP
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 12 }}
{{- end }}

View file

@ -1,32 +0,0 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "dashboard.fullname" . }}
labels:
{{- include "dashboard.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "dashboard.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View file

@ -1,29 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "dashboard.fullname" . }}-network-policy
labels:
{{- include "dashboard.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "dashboard.selectorLabels" . | nindent 6 }}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: api-gateway
ports:
- port: http
protocol: TCP
egress:
# Allow DNS resolution
- to: []
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP

View file

@ -1,15 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "dashboard.fullname" . }}
labels:
{{- include "dashboard.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "dashboard.selectorLabels" . | nindent 4 }}

View file

@ -1,12 +0,0 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "dashboard.serviceAccountName" . }}
labels:
{{- include "dashboard.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View file

@ -1,80 +0,0 @@
# trading-platform/dashboard Helm chart values
replicaCount: 2
image:
repository: trading-platform/dashboard
pullPolicy: IfNotPresent
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
annotations: {}
name: ""
podAnnotations: {}
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
service:
type: ClusterIP
port: 3000
ingress:
enabled: false
className: nginx
annotations: {}
hosts:
- host: api.trading.example.com
paths:
- path: /
pathType: Prefix
tls: []
resources:
limits:
cpu: "500m"
memory: 512Mi
requests:
cpu: "250m"
memory: 256Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
nodeSelector: {}
tolerations: []
affinity: {}

View file

@ -1,6 +0,0 @@
apiVersion: v2
name: data-service
description: Trading data service
type: application
version: 0.1.0
appVersion: "0.1.0"

View file

@ -1,61 +0,0 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "data-service.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "data-service.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "data-service.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "data-service.labels" -}}
helm.sh/chart: {{ include "data-service.chart" . }}
{{ include "data-service.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "data-service.selectorLabels" -}}
app.kubernetes.io/name: {{ include "data-service.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "data-service.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "data-service.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View file

@ -1,57 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "data-service.fullname" . }}
labels:
{{- include "data-service.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "data-service.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "data-service.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "data-service.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 8001
protocol: TCP
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 12 }}
{{- end }}

View file

@ -1,32 +0,0 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "data-service.fullname" . }}
labels:
{{- include "data-service.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "data-service.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View file

@ -1,29 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "data-service.fullname" . }}-network-policy
labels:
{{- include "data-service.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "data-service.selectorLabels" . | nindent 6 }}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: api-gateway
ports:
- port: http
protocol: TCP
egress:
# Allow DNS resolution
- to: []
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP

View file

@ -1,15 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "data-service.fullname" . }}
labels:
{{- include "data-service.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "data-service.selectorLabels" . | nindent 4 }}

View file

@ -1,12 +0,0 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "data-service.serviceAccountName" . }}
labels:
{{- include "data-service.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View file

@ -1,80 +0,0 @@
# trading-platform/data-service Helm chart values
replicaCount: 2
image:
repository: trading-platform/data-service
pullPolicy: IfNotPresent
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
annotations: {}
name: ""
podAnnotations: {}
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
service:
type: ClusterIP
port: 8001
ingress:
enabled: false
className: nginx
annotations: {}
hosts:
- host: api.trading.example.com
paths:
- path: /
pathType: Prefix
tls: []
resources:
limits:
cpu: "1000m"
memory: 1Gi
requests:
cpu: "500m"
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
nodeSelector: {}
tolerations: []
affinity: {}

View file

@ -1,6 +0,0 @@
apiVersion: v2
name: execute-service
description: Trading execution service
type: application
version: 0.1.0
appVersion: "0.1.0"

View file

@ -1,61 +0,0 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "execute-service.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "execute-service.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "execute-service.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "execute-service.labels" -}}
helm.sh/chart: {{ include "execute-service.chart" . }}
{{ include "execute-service.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "execute-service.selectorLabels" -}}
app.kubernetes.io/name: {{ include "execute-service.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "execute-service.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "execute-service.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View file

@ -1,57 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "execute-service.fullname" . }}
labels:
{{- include "execute-service.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "execute-service.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "execute-service.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "execute-service.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 8000
protocol: TCP
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 12 }}
{{- end }}

View file

@ -1,32 +0,0 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "execute-service.fullname" . }}
labels:
{{- include "execute-service.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "execute-service.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View file

@ -1,29 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "execute-service.fullname" . }}-network-policy
labels:
{{- include "execute-service.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "execute-service.selectorLabels" . | nindent 6 }}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: api-gateway
ports:
- port: http
protocol: TCP
egress:
# Allow DNS resolution
- to: []
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP

View file

@ -1,15 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "execute-service.fullname" . }}
labels:
{{- include "execute-service.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "execute-service.selectorLabels" . | nindent 4 }}

View file

@ -1,12 +0,0 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "execute-service.serviceAccountName" . }}
labels:
{{- include "execute-service.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View file

@ -1,80 +0,0 @@
# trading-platform/execute-service Helm chart values
replicaCount: 2
image:
repository: trading-platform/execute-service
pullPolicy: IfNotPresent
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
annotations: {}
name: ""
podAnnotations: {}
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
service:
type: ClusterIP
port: 8000
ingress:
enabled: false
className: nginx
annotations: {}
hosts:
- host: api.trading.example.com
paths:
- path: /
pathType: Prefix
tls: []
resources:
limits:
cpu: "500m"
memory: 512Mi
requests:
cpu: "250m"
memory: 256Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
nodeSelector: {}
tolerations: []
affinity: {}

View file

@ -1,6 +0,0 @@
apiVersion: v2
name: news-service
description: News analysis service
type: application
version: 0.1.0
appVersion: "0.1.0"

View file

@ -1,61 +0,0 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "news-service.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "news-service.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "news-service.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "news-service.labels" -}}
helm.sh/chart: {{ include "news-service.chart" . }}
{{ include "news-service.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "news-service.selectorLabels" -}}
app.kubernetes.io/name: {{ include "news-service.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "news-service.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "news-service.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View file

@ -1,57 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "news-service.fullname" . }}
labels:
{{- include "news-service.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "news-service.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "news-service.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "news-service.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 8002
protocol: TCP
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 12 }}
{{- end }}

View file

@ -1,32 +0,0 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "news-service.fullname" . }}
labels:
{{- include "news-service.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "news-service.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View file

@ -1,29 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "news-service.fullname" . }}-network-policy
labels:
{{- include "news-service.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "news-service.selectorLabels" . | nindent 6 }}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: api-gateway
ports:
- port: http
protocol: TCP
egress:
# Allow DNS resolution
- to: []
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP

View file

@ -1,15 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "news-service.fullname" . }}
labels:
{{- include "news-service.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "news-service.selectorLabels" . | nindent 4 }}

View file

@ -1,12 +0,0 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "news-service.serviceAccountName" . }}
labels:
{{- include "news-service.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View file

@ -1,80 +0,0 @@
# trading-platform/news-service Helm chart values
replicaCount: 2
image:
repository: trading-platform/news-service
pullPolicy: IfNotPresent
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
annotations: {}
name: ""
podAnnotations: {}
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
service:
type: ClusterIP
port: 8002
ingress:
enabled: false
className: nginx
annotations: {}
hosts:
- host: api.trading.example.com
paths:
- path: /
pathType: Prefix
tls: []
resources:
limits:
cpu: "1000m"
memory: 1Gi
requests:
cpu: "500m"
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
nodeSelector: {}
tolerations: []
affinity: {}

View file

@ -1,71 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-gateway
namespace: customer1
labels:
app: api-gateway
app.kubernetes.io/name: api-gateway
app.kubernetes.io/component: microservice
spec:
replicas: 2
selector:
matchLabels:
app: api-gateway
template:
metadata:
labels:
app: api-gateway
app.kubernetes.io/name: api-gateway
app.kubernetes.io/component: microservice
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: api-gateway
image: "trading-platform/api-gateway:v${VERSION}"
ports:
- containerPort: 8080
protocol: TCP
envFrom:
- configMapRef:
name: trading-platform-config
resources:
limits:
cpu: "500m"
memory: 256Mi
requests:
cpu: "250m"
memory: 128Mi
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}

View file

@ -1,27 +0,0 @@
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-gateway-hpa
namespace: customer1
labels:
app: api-gateway
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-gateway
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80

View file

@ -1,18 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: api-gateway
namespace: customer1
labels:
app: api-gateway
app.kubernetes.io/name: api-gateway
app.kubernetes.io/component: microservice
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: 8080
protocol: TCP
name: http
selector:
app: api-gateway

View file

@ -1,24 +0,0 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: api-gateway-mtls-cert
namespace: customer1
spec:
secretName: api-gateway-mtls-secret
duration: 2160h # 90 days
renewBefore: 360h # 15 days
commonName: api-gateway.customer1.svc.cluster.local
dnsNames:
- api-gateway
- api-gateway.customer1
- api-gateway.customer1.svc
- api-gateway.customer1.svc.cluster.local
usages:
- digital signature
- key encipherment
- client auth
- server auth
issuerRef:
name: trading-platform-ca-issuer
kind: Issuer
group: cert-manager.io

View file

@ -1,8 +0,0 @@
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: trading-platform-ca-issuer
namespace: customer1
spec:
ca:
secretName: trading-platform-ca-secret

View file

@ -1,29 +0,0 @@
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@trading-platform.com
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: nginx
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: admin@trading-platform.com
privateKeySecretRef:
name: letsencrypt-staging-key
solvers:
- http01:
ingress:
class: nginx

View file

@ -1,24 +0,0 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: dashboard-mtls-cert
namespace: customer1
spec:
secretName: dashboard-mtls-secret
duration: 2160h # 90 days
renewBefore: 360h # 15 days
commonName: dashboard.customer1.svc.cluster.local
dnsNames:
- dashboard
- dashboard.customer1
- dashboard.customer1.svc
- dashboard.customer1.svc.cluster.local
usages:
- digital signature
- key encipherment
- client auth
- server auth
issuerRef:
name: trading-platform-ca-issuer
kind: Issuer
group: cert-manager.io

View file

@ -1,24 +0,0 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: data-service-mtls-cert
namespace: customer1
spec:
secretName: data-service-mtls-secret
duration: 2160h # 90 days
renewBefore: 360h # 15 days
commonName: data-service.customer1.svc.cluster.local
dnsNames:
- data-service
- data-service.customer1
- data-service.customer1.svc
- data-service.customer1.svc.cluster.local
usages:
- digital signature
- key encipherment
- client auth
- server auth
issuerRef:
name: trading-platform-ca-issuer
kind: Issuer
group: cert-manager.io

View file

@ -1,24 +0,0 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: execute-service-mtls-cert
namespace: customer1
spec:
secretName: execute-service-mtls-secret
duration: 2160h # 90 days
renewBefore: 360h # 15 days
commonName: execute-service.customer1.svc.cluster.local
dnsNames:
- execute-service
- execute-service.customer1
- execute-service.customer1.svc
- execute-service.customer1.svc.cluster.local
usages:
- digital signature
- key encipherment
- client auth
- server auth
issuerRef:
name: trading-platform-ca-issuer
kind: Issuer
group: cert-manager.io

View file

@ -1,24 +0,0 @@
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: selfsigned-issuer
namespace: cert-manager
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: trading-platform-ca
namespace: cert-manager
spec:
isCA: true
commonName: trading-platform-ca
secretName: trading-platform-ca-secret
privateKey:
algorithm: ECDSA
size: 256
issuerRef:
name: selfsigned-issuer
kind: Issuer
group: cert-manager.io

View file

@ -1,24 +0,0 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: news-service-mtls-cert
namespace: customer1
spec:
secretName: news-service-mtls-secret
duration: 2160h # 90 days
renewBefore: 360h # 15 days
commonName: news-service.customer1.svc.cluster.local
dnsNames:
- news-service
- news-service.customer1
- news-service.customer1.svc
- news-service.customer1.svc.cluster.local
usages:
- digital signature
- key encipherment
- client auth
- server auth
issuerRef:
name: trading-platform-ca-issuer
kind: Issuer
group: cert-manager.io

View file

@ -1,78 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dashboard
namespace: customer1
labels:
app: dashboard
app.kubernetes.io/name: dashboard
app.kubernetes.io/component: microservice
spec:
replicas: 2
selector:
matchLabels:
app: dashboard
template:
metadata:
labels:
app: dashboard
app.kubernetes.io/name: dashboard
app.kubernetes.io/component: microservice
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: dashboard
image: "trading-platform/dashboard:v${VERSION}"
ports:
- containerPort: 3000
protocol: TCP
envFrom:
- configMapRef:
name: trading-platform-config
resources:
limits:
cpu: "500m"
memory: 512Mi
requests:
cpu: "250m"
memory: 256Mi
startupProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 30
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}

View file

@ -1,27 +0,0 @@
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: dashboard-hpa
namespace: customer1
labels:
app: dashboard
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: dashboard
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80

View file

@ -1,18 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: dashboard
namespace: customer1
labels:
app: dashboard
app.kubernetes.io/name: dashboard
app.kubernetes.io/component: microservice
spec:
type: ClusterIP
ports:
- port: 3000
targetPort: 3000
protocol: TCP
name: http
selector:
app: dashboard

View file

@ -1,78 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: data-service
namespace: customer1
labels:
app: data-service
app.kubernetes.io/name: data-service
app.kubernetes.io/component: microservice
spec:
replicas: 2
selector:
matchLabels:
app: data-service
template:
metadata:
labels:
app: data-service
app.kubernetes.io/name: data-service
app.kubernetes.io/component: microservice
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: data-service
image: "trading-platform/data-service:v${VERSION}"
ports:
- containerPort: 8001
protocol: TCP
envFrom:
- configMapRef:
name: trading-platform-config
resources:
limits:
cpu: "1000m"
memory: 1Gi
requests:
cpu: "500m"
memory: 512Mi
startupProbe:
httpGet:
path: /health
port: 8001
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 30
livenessProbe:
httpGet:
path: /health
port: 8001
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 8001
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}

View file

@ -1,27 +0,0 @@
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: data-service-hpa
namespace: customer1
labels:
app: data-service
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: data-service
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80

View file

@ -1,18 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: data-service
namespace: customer1
labels:
app: data-service
app.kubernetes.io/name: data-service
app.kubernetes.io/component: microservice
spec:
type: ClusterIP
ports:
- port: 8001
targetPort: 8001
protocol: TCP
name: http
selector:
app: data-service

View file

@ -1,94 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: execute-service
namespace: customer1
labels:
app: execute-service
app.kubernetes.io/name: execute-service
app.kubernetes.io/component: microservice
spec:
replicas: 2
selector:
matchLabels:
app: execute-service
template:
metadata:
labels:
app: execute-service
app.kubernetes.io/name: execute-service
app.kubernetes.io/component: microservice
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: execute-service
image: "trading-platform/execute-service:v${VERSION}"
ports:
- containerPort: 8000
protocol: TCP
envFrom:
- configMapRef:
name: trading-platform-config
env:
- name: EXECUTE_DB_USER
valueFrom:
secretKeyRef:
name: trading-db-credentials
key: username
- name: EXECUTE_DB_PASSWORD
valueFrom:
secretKeyRef:
name: trading-db-credentials
key: password
- name: JWT_SECRET_KEY
valueFrom:
secretKeyRef:
name: execute-service-secret
key: JWT_SECRET_KEY
resources:
limits:
cpu: "500m"
memory: 512Mi
requests:
cpu: "250m"
memory: 256Mi
startupProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 30
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}

View file

@ -1,27 +0,0 @@
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: execute-service-hpa
namespace: customer1
labels:
app: execute-service
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: execute-service
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80

View file

@ -1,34 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: execute-service-secret
namespace: customer1
labels:
app: execute-service
app.kubernetes.io/name: execute-service
type: Opaque
stringData:
#ENC[AES256_GCM,data:cufebjrvQzeId2s78yHMCbP5,iv:sDAICkKMSOMTe2p+YZTudEGd0Y0vVVa4kf0qAE/2YPA=,tag:Sh1FtsV5LInk/L8dV+gJgg==,type:comment]
DB_PASSWORD: ENC[AES256_GCM,data:v8j+WH2Ix/uSoTUkm8gGGFavmlual+s=,iv:SsuW8bqGh9hU30Vg6+bOIV6KVadYFh3mkbwlAnpXUa4=,tag:BBMJ+beHsCL58Rg7csyjHw==,type:str]
#ENC[AES256_GCM,data:0Kp12yECmB0AOi2Hw2gTVK2TVKGrzTKul3kjbfdgnoouSETiAOdKnNI=,iv:JX7ea/tX88VvfYyfx5SxXSHzvXox2o1SPjG2PtStLQk=,tag:PwjxiNu+MoabsJy45BJPBA==,type:comment]
JWT_SECRET_KEY: ENC[AES256_GCM,data:qJFRby3voYIwonaI4DL1T4jG/q85dj2Q5KuSN5IYQONjsV7YUkZls3Q8H17oV58EfFhKVlF0gMYaypjFH9n4YA==,iv:QHJ3BJ2yAfaa+POx9/Cv3ASuwL/4wOg9R5AaYgalYgI=,tag:cGWxyTJFcrKBj1j8Ylr9lw==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1uuxf066xuuqgvjppxfcmqkwfcufnwp3wcwnl9h20g9k4l8nkw9jsaungf7
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3WFdpY0VRQmliak4xVS8x
aGpORnYrZ2daZGFMMzRCOTh0a0lhanlKYmxrClR2VXRHVVliRVJMRUxKVkt0Y0Vw
VzZCenFtWmVoVkZqcFFYVlY3UjQxSzAKLS0tIFFZanArYU5PZFJlSitvMitzNUNI
YTNhdTdReG1maS91akdsbis0TFF3TXcKEvp821cnrAM4jITpiacbyxoE24FZhQ6O
PsDEpqRo2ck1aWDJROMV9HAlgxjIWzptJUNorP21m7/5TbgPGwjXnQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-05-25T01:24:10Z"
mac: ENC[AES256_GCM,data:1Xp5acLJaMux3itDF8Fyrz8YY0+YzF0OsWkDXLUffUG8ALKuMTT8IhFE4Nc5+gb8Bc8kfcx5VSNyCm9vKzcvYNwc/EHpe9CSZz5z2uHz1QQDZ5mK/4lbukpDEJhy9syb80TxulOwfrNbWwv1vnilN1uMT00lJKO9IXe3WbJ17qc=,iv:ZCc21Z41+HRX/qmgHgvvUFu+I8awN6+7UkSAhWLImXk=,tag:We4MhHcagW5bBiFtGo1yKQ==,type:str]
pgp: []
encrypted_regex: ^(data|stringData)$
version: 3.9.0

View file

@ -1,18 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: execute-service
namespace: customer1
labels:
app: execute-service
app.kubernetes.io/name: execute-service
app.kubernetes.io/component: microservice
spec:
type: ClusterIP
ports:
- port: 8000
targetPort: 8000
protocol: TCP
name: http
selector:
app: execute-service

View file

@ -1,40 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-gateway-ingress
namespace: customer1
labels:
app: api-gateway
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
nginx.ingress.kubernetes.io/rate-limit: "100"
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- api.trading.example.com
- dashboard.trading.example.com
secretName: trading-tls-secret
rules:
- host: api.trading.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-gateway
port:
number: 8080
- host: dashboard.trading.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dashboard
port:
number: 3000

View file

@ -1,18 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: customer1
labels:
name: customer1
istio-injection: disabled # Disable Istio if using native K8s policies
---
apiVersion: v1
kind: ConfigMap
metadata:
name: trading-platform-config
namespace: customer1
data:
KAFKA_BOOTSTRAP_SERVERS: "kafka-headless:9092"
REDIS_URL: "redis://redis-master:6379/0"
LOG_LEVEL: "info"
TRADING_ENV: "production"

View file

@ -1,71 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: news-service
namespace: customer1
labels:
app: news-service
app.kubernetes.io/name: news-service
app.kubernetes.io/component: microservice
spec:
replicas: 2
selector:
matchLabels:
app: news-service
template:
metadata:
labels:
app: news-service
app.kubernetes.io/name: news-service
app.kubernetes.io/component: microservice
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: news-service
image: "trading-platform/news-service:v${VERSION}"
ports:
- containerPort: 8002
protocol: TCP
envFrom:
- configMapRef:
name: trading-platform-config
resources:
limits:
cpu: "1000m"
memory: 1Gi
requests:
cpu: "500m"
memory: 512Mi
livenessProbe:
httpGet:
path: /health
port: 8002
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 8002
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}

View file

@ -1,27 +0,0 @@
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: news-service-hpa
namespace: customer1
labels:
app: news-service
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: news-service
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80

View file

@ -1,18 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: news-service
namespace: customer1
labels:
app: news-service
app.kubernetes.io/name: news-service
app.kubernetes.io/component: microservice
spec:
type: ClusterIP
ports:
- port: 8002
targetPort: 8002
protocol: TCP
name: http
selector:
app: news-service

View file

@ -1,20 +0,0 @@
# =============================================================================
# mTLS Configuration for Trading Platform
# =============================================================================
# This directory contains certificates and configuration for mutual TLS
# between services. In production, use cert-manager to automate this.
#
# Option 1: cert-manager (recommended for production)
# Option 2: Manual certificate management (for dev/testing)
#
# Certificate hierarchy:
# Root CA
# ├── Service CA (issues service-to-service certs)
# │ ├── execute-service cert
# │ ├── data-service cert
# │ ├── news-service cert
# │ ├── api-gateway cert
# │ └── dashboard cert
# └── Ingress CA (for external-facing TLS)
# └── api-gateway TLS cert (for HTTPS)
# =============================================================================

View file

@ -1,141 +0,0 @@
#!/bin/bash
# =============================================================================
# Deploy Trading Platform to Kubernetes
# =============================================================================
#
# Usage:
# ./deploy/scripts/deploy.sh [staging|production] [tag]
#
# Examples:
# ./deploy/scripts/deploy.sh staging latest
# ./deploy/scripts/deploy.sh production v1.2.3
#
# Prerequisites:
# - kubectl configured with cluster access
# - Helm 3.x installed
# - Docker images pushed to registry
# - cert-manager installed in cluster (for TLS)
# =============================================================================
set -euo pipefail
ENVIRONMENT="${1:-staging}"
IMAGE_TAG="${2:-latest}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# Validate environment
if [[ ! "$ENVIRONMENT" =~ ^(staging|production)$ ]]; then
echo "ERROR: Environment must be 'staging' or 'production', got '$ENVIRONMENT'"
exit 1
fi
# Set namespace and values file based on environment
if [[ "$ENVIRONMENT" == "staging" ]]; then
NAMESPACE="customer1-staging"
VALUES_FILE="$PROJECT_ROOT/deploy/k8s/overlays/staging/kustomization.yaml"
else
NAMESPACE="customer1"
VALUES_FILE="$PROJECT_ROOT/deploy/k8s/overlays/production/kustomization.yaml"
fi
echo "========================================================"
echo " Deploying Trading Platform to $ENVIRONMENT"
echo " Image tag: $IMAGE_TAG"
echo " Namespace: $NAMESPACE"
echo "========================================================"
# Confirm cluster context
CURRENT_CONTEXT=$(kubectl config current-context 2>/dev/null || echo "unknown")
echo "Current kubectl context: $CURRENT_CONTEXT"
read -r -p "Continue? (y/N) " -n 1
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Deployment cancelled."
exit 1
fi
# Install dependencies (optional)
echo ""
echo "Step 1/5: Checking prerequisites..."
# Check for Helm
if ! command -v helm &>/dev/null; then
echo "ERROR: helm is not installed"
exit 1
fi
# Check for kubectl
if ! command -v kubectl &>/dev/null; then
echo "ERROR: kubectl is not installed"
exit 1
fi
# Check cluster connectivity
if ! kubectl cluster-info &>/dev/null; then
echo "ERROR: Cannot connect to Kubernetes cluster"
exit 1
fi
echo " ✓ Kubernetes cluster is accessible"
# Create namespace if it doesn't exist
kubectl create namespace "$NAMESPACE" --dry-run=client -o yaml | kubectl apply -f -
echo " ✓ Namespace $NAMESPACE exists"
# Step 2: Deploy infrastructure (PostgreSQL, Redis, Kafka)
echo ""
echo "Step 2/5: Deploying infrastructure..."
kubectl apply -f "$PROJECT_ROOT/deploy/k8s/base/namespace.yaml"
kubectl apply -f "$PROJECT_ROOT/deploy/k8s/base/configmap.yaml"
echo " ✓ ConfigMap applied"
# Step 3: Deploy services
echo ""
echo "Step 3/5: Deploying microservices..."
SERVICES=("execute-service" "data-service" "news-service" "api-gateway" "dashboard")
for service in "${SERVICES[@]}"; do
echo " Deploying $service..."
kubectl apply -f "$PROJECT_ROOT/deploy/k8s/base/${service}-deployment.yaml"
kubectl apply -f "$PROJECT_ROOT/deploy/k8s/base/${service}-service.yaml"
done
echo " ✓ All services deployed"
# Step 4: Deploy ingress and networking
echo ""
echo "Step 4/5: Configuring ingress and networking..."
kubectl apply -f "$PROJECT_ROOT/deploy/k8s/base/ingress.yaml"
echo " ✓ Ingress configured"
# Apply NetworkPolicies from security review
if [[ -d "$PROJECT_ROOT/trading-platform/security/network-policies" ]]; then
kubectl apply -f "$PROJECT_ROOT/trading-platform/security/network-policies/"
echo " ✓ NetworkPolicies applied"
fi
# Step 5: Wait for rollouts
echo ""
echo "Step 5/5: Waiting for deployments to stabilize..."
for service in "${SERVICES[@]}"; do
echo " Waiting for $service..."
if ! kubectl rollout status "deployment/${service}" -n "$NAMESPACE" --timeout=5m; then
echo "WARNING: $service rollout timed out"
echo " Check pods: kubectl get pods -n $NAMESPACE -l app=$service"
echo " Check logs: kubectl logs -n $NAMESPACE -l app=$service --tail=100"
exit 1
fi
done
echo ""
echo "========================================================"
echo " Deployment complete! All services running."
echo "========================================================"
echo ""
echo "Useful commands:"
echo " kubectl get pods -n $NAMESPACE"
echo " kubectl get svc -n $NAMESPACE"
echo " kubectl get ingress -n $NAMESPACE"
echo " kubectl logs -n $NAMESPACE -l app=$service -f"

View file

@ -1,66 +0,0 @@
#!/bin/bash
# =============================================================================
# Manual mTLS certificate generation script (for dev/testing only)
# =============================================================================
# In production, use cert-manager (see k8s/base/cert-manager/).
# This script generates self-signed certificates for local testing.
#
# Usage:
# ./deploy/scripts/generate-mtls-certs.sh
#
# Output: deploy/mtls/
# =============================================================================
set -euo pipefail
OUTPUT_DIR="deploy/mtls"
SERVICES=("execute-service" "data-service" "news-service" "api-gateway" "dashboard")
DAYS_VALID=365
mkdir -p "$OUTPUT_DIR/ca" "$OUTPUT_DIR/certs"
# ── Generate Root CA ────────────────────────────────────────────────────────
echo "Generating Root CA..."
openssl genrsa -out "$OUTPUT_DIR/ca/ca.key" 4096 2>/dev/null
openssl req -x509 -new -nodes \
-key "$OUTPUT_DIR/ca/ca.key" \
-sha256 \
-days $DAYS_VALID \
-out "$OUTPUT_DIR/ca/ca.crt" \
-subj "/C=US/ST=California/O=TradingPlatform/CN=Trading Platform Root CA"
# ── Generate Service Certificates ────────────────────────────────────────────
for SERVICE in "${SERVICES[@]}"; do
echo "Generating certificate for $SERVICE..."
# Generate private key
openssl genrsa \
-out "$OUTPUT_DIR/certs/${SERVICE}.key" 2048 2>/dev/null
# Generate CSR
openssl req -new \
-key "$OUTPUT_DIR/certs/${SERVICE}.key" \
-out "$OUTPUT_DIR/certs/${SERVICE}.csr" \
-subj "/C=US/ST=California/O=TradingPlatform/CN=${SERVICE}.customer1.svc.cluster.local" \
-addext "subjectAltName=DNS:${SERVICE},DNS:${SERVICE}.customer1,DNS:${SERVICE}.customer1.svc.cluster.local"
# Sign with CA
openssl x509 -req \
-in "$OUTPUT_DIR/certs/${SERVICE}.csr" \
-CA "$OUTPUT_DIR/ca/ca.crt" \
-CAkey "$OUTPUT_DIR/ca/ca.key" \
-CAcreateserial \
-out "$OUTPUT_DIR/certs/${SERVICE}.crt" \
-days $DAYS_VALID \
-sha256 \
-extfile <(printf "subjectAltName=DNS:${SERVICE},DNS:${SERVICE}.customer1,DNS:${SERVICE}.customer1.svc.cluster.local")
# Clean up CSR
rm "$OUTPUT_DIR/certs/${SERVICE}.csr"
done
echo "Done! All certificates generated in $OUTPUT_DIR/certs/"
echo "CA certificate: $OUTPUT_DIR/ca/ca.crt"
echo ""
echo "To verify a certificate:"
echo " openssl verify -CAfile $OUTPUT_DIR/ca/ca.crt $OUTPUT_DIR/certs/<service>.crt"

View file

@ -1,10 +0,0 @@
node_modules/
.next/
out/
dist/
.env*
.git/
.vscode/
coverage/
.idea/
*.log

View file

@ -1,31 +0,0 @@
# Multi-stage build for Next.js dashboard
# ---- Builder ----
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# ---- Production ----
FROM node:20-alpine AS runner
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
WORKDIR /app
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
HEALTHCHECK --interval=15s --timeout=5s --start-period=20s --retries=3 \\
CMD wget --no-verbose --tries=1 --spider http://localhost:${PORT}/ || exit 1
CMD ["node", "server.js"]

View file

@ -1,10 +0,0 @@
# Python Docker ignores
__pycache__/
*.pyc
.venv/
venv/
*.egg-info/
.pytest_cache/
.git/
.env
tests/

View file

@ -1,23 +0,0 @@
# Multi-stage build for data-service (Postgres + Redis + Kafka consumers)
FROM python:3.12-slim AS builder
WORKDIR /build
COPY pyproject.toml .
RUN pip install --no-cache-dir --prefix=/install .
FROM python:3.12-slim
RUN useradd -m --system appuser
COPY --from=builder /install /usr/local
WORKDIR /app
COPY --chown=appuser:appuser data_service/ ./data_service/
COPY --chown=appuser:appuser pyproject.toml alembic.ini ./
USER appuser
HEALTHCHECK --interval=15s --timeout=5s --start-period=15s --retries=3 \\
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
EXPOSE 8000
CMD ["uvicorn", "data_service.main:app", "--host", "0.0.0.0", "--port", "8000"]

View file

@ -1,10 +0,0 @@
# Python Docker ignores
__pycache__/
*.pyc
.venv/
venv/
*.egg-info/
.pytest_cache/
.git/
.env
tests/

Some files were not shown because too many files have changed in this diff Show more