refactor: update K8s, CI, Docker, Prometheus, scripts, and FXT CLI for api rename

- K8s: rename api-gateway → api manifests, delete web-gateway, update network policies
- CI: rename compile/deploy jobs, delete web-gateway jobs
- Docker: rename service in compose files
- Prometheus: update scrape targets and alert rules
- Scripts: update binary references in build/test/cert scripts
- FXT CLI: rename api_gateway_url → api_url (with serde alias for compat)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-03-04 23:46:36 +01:00
parent 3492b61faf
commit 57e22c01a8
37 changed files with 142 additions and 517 deletions

View File

@@ -37,7 +37,7 @@ variables:
CARGO_TERM_COLOR: always
# CUDA_COMPUTE_CAP is set at runtime by detect_gpu() — L4=89, H100=90
# Do NOT hardcode here; sccache is partitioned by compute cap below
# Stable JWT secret for api_gateway tests (prevents env-var race between parallel tests)
# Stable JWT secret for api tests (prevents env-var race between parallel tests)
JWT_SECRET: "CiTestSecret_Kx7mP9nR2sW5vY8bC3fG6jH1kL4pQ7tZ0uN9dM5eV8xS2wT6yA4zB_64chars_min"
# Training data on block storage PVC (mounted at /mnt/training-data by runner)
FOXHUNT_DATA_DIR: "/mnt/training-data/futures-baseline"
@@ -448,14 +448,14 @@ test:
# Each service compiled independently with dependency-aware changes: filters.
# sccache shared across jobs — incremental crate compilation is cached.
# --------------------------------------------------------------------------
compile-api-gateway:
compile-api:
extends: .compile-service-base
variables:
SERVICE_PACKAGE: api-gateway
rules: &rules-api-gateway
SERVICE_PACKAGE: api
rules: &rules-api
- if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"
changes:
- services/api_gateway/**
- services/api/**
- crates/common/**
- crates/config/**
- crates/trading_engine/**
@@ -572,19 +572,6 @@ compile-data-acquisition-service:
- Cargo.lock
- if: $CI_PIPELINE_SOURCE == "web" || $CI_PIPELINE_SOURCE == "api"
compile-web-gateway:
extends: .compile-service-base
variables:
SERVICE_PACKAGE: web-gateway
rules: &rules-web-gateway
- if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"
changes:
- crates/web-gateway/**
- crates/common/**
- Cargo.toml
- Cargo.lock
- if: $CI_PIPELINE_SOURCE == "web" || $CI_PIPELINE_SOURCE == "api"
# --------------------------------------------------------------------------
# Stage 3a: Compile training binaries (CPU node, CUDA stubs from ci-builder image)
# --------------------------------------------------------------------------
@@ -1637,12 +1624,12 @@ infra-drift-check:
# Rules use YAML anchors shared with compile jobs — ensures compile+deploy
# are created/skipped together.
# --------------------------------------------------------------------------
deploy-api-gateway:
deploy-api:
extends: .deploy-service-base
needs: [{job: compile-api-gateway, artifacts: true}]
needs: [{job: compile-api, artifacts: true}]
variables:
SERVICE_PACKAGE: api-gateway
rules: *rules-api-gateway
SERVICE_PACKAGE: api
rules: *rules-api
deploy-trading-service:
extends: .deploy-service-base
@@ -1686,13 +1673,6 @@ deploy-data-acquisition-service:
SERVICE_PACKAGE: data-acquisition-service
rules: *rules-data-acquisition-service
deploy-web-gateway:
extends: .deploy-service-base
needs: [{job: compile-web-gateway, artifacts: true}]
variables:
SERVICE_PACKAGE: web-gateway
rules: *rules-web-gateway
# --------------------------------------------------------------------------
# Stage 4b: Deploy training binaries to MinIO
# --------------------------------------------------------------------------

111
Cargo.lock generated
View File

@@ -250,82 +250,7 @@ dependencies = [
]
[[package]]
name = "api-gateway"
version = "1.0.0"
dependencies = [
"anyhow",
"async-stream",
"async-trait",
"axum 0.7.9",
"base32",
"base64 0.22.1",
"bytes",
"chrono",
"clap",
"common",
"config",
"const-oid",
"criterion",
"dashmap 6.1.0",
"futures",
"fxt",
"governor",
"hdrhistogram",
"hex",
"hmac",
"http 1.3.1",
"http-body 1.0.1",
"http-body-util",
"hyper 1.7.0",
"hyper-util",
"image 0.25.8",
"jsonwebtoken",
"k8s-openapi 0.27.0",
"kube 3.0.1",
"lru",
"num-traits",
"ocsp",
"once_cell",
"prometheus",
"prost 0.14.1",
"prost-build",
"qrcode",
"rand 0.8.5",
"redis",
"regex",
"reqwest 0.12.23",
"rust_decimal",
"rustls 0.23.32",
"secrecy 0.8.0",
"serde",
"serde_json",
"sha1",
"sha2",
"sqlx",
"thiserror 1.0.69",
"tokio",
"tokio-stream",
"tokio-test",
"tonic 0.14.2",
"tonic-health",
"tonic-prost",
"tonic-prost-build",
"tonic-reflection",
"totp-rs",
"tower 0.4.13",
"tower-layer",
"tower-service",
"tracing",
"tracing-subscriber",
"trading_engine",
"urlencoding",
"uuid",
"x509-parser",
"zeroize",
]
[[package]]
name = "api_gateway_load_tests"
name = "api_load_tests"
version = "0.1.0"
dependencies = [
"anyhow",
@@ -1442,7 +1367,6 @@ checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
dependencies = [
"async-trait",
"axum-core 0.4.5",
"base64 0.22.1",
"bytes",
"futures-util",
"http 1.3.1",
@@ -1461,10 +1385,8 @@ dependencies = [
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sha1",
"sync_wrapper 1.0.2",
"tokio",
"tokio-tungstenite",
"tower 0.5.2",
"tower-layer",
"tower-service",
@@ -11359,7 +11281,7 @@ name = "trading-service"
version = "1.0.0"
dependencies = [
"anyhow",
"api-gateway",
"api",
"async-stream",
"async-trait",
"axum 0.7.9",
@@ -11999,35 +11921,6 @@ dependencies = [
"web-sys",
]
[[package]]
name = "web-gateway"
version = "1.0.0"
dependencies = [
"anyhow",
"axum 0.7.9",
"chrono",
"common",
"futures-util",
"jsonwebtoken",
"once_cell",
"prometheus",
"prost 0.14.1",
"serde",
"serde_json",
"thiserror 1.0.69",
"tokio",
"tokio-stream",
"tokio-test",
"tonic 0.14.2",
"tonic-prost",
"tonic-prost-build",
"tower 0.4.13",
"tower-http 0.5.2",
"tracing",
"tracing-subscriber",
"uuid",
]
[[package]]
name = "web-sys"
version = "0.3.81"

View File

@@ -1,7 +1,7 @@
//! JWT Token Generation for FXT
//!
//! Provides JWT token generation for simulated authentication flows.
//! Uses the same configuration as API Gateway for compatibility.
//! Uses the same configuration as the unified API for compatibility.
use anyhow::Result;
use jsonwebtoken::{encode, EncodingKey, Header};
@@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
use uuid::Uuid;
/// JWT claims structure matching API Gateway expectations
/// JWT claims structure matching API expectations
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JwtClaims {
/// JWT ID (unique identifier for revocation)
@@ -38,7 +38,7 @@ pub struct JwtClaims {
pub session_id: Option<String>,
}
/// JWT configuration matching API Gateway
/// JWT configuration matching the unified API
pub struct JwtConfig {
pub secret: String,
pub issuer: String,
@@ -62,7 +62,7 @@ impl Default for JwtConfig {
Self {
secret: resolved_secret,
issuer: std::env::var("JWT_ISSUER")
.unwrap_or_else(|_| "foxhunt-api-gateway".to_owned()),
.unwrap_or_else(|_| "foxhunt-api".to_owned()),
audience: std::env::var("JWT_AUDIENCE")
.unwrap_or_else(|_| "foxhunt-services".to_owned()),
}

View File

@@ -62,9 +62,9 @@ pub struct RefreshResponse {
pub expires_at: u64,
}
/// Login client for API Gateway authentication
/// Login client for API authentication
pub struct LoginClient {
/// gRPC channel to the API Gateway (used for future gRPC auth calls)
/// gRPC channel to the API (used for future gRPC auth calls)
gateway_channel: Channel,
}
@@ -79,9 +79,9 @@ impl LoginClient {
&self.gateway_channel
}
/// Check whether an API Gateway URL has been configured in the environment
fn is_api_gateway_available() -> bool {
std::env::var("API_GATEWAY_URL").is_ok()
/// Check whether an API URL has been configured in the environment
fn is_api_available() -> bool {
std::env::var("API_URL").is_ok()
}
/// Perform interactive login flow
@@ -108,11 +108,11 @@ impl LoginClient {
// Attempt login
let _login_request = LoginRequest { username, password };
// TODO: Call API Gateway login endpoint via gRPC
// TODO: Call API login endpoint via gRPC
// For now, simulate a successful login response
if Self::is_api_gateway_available() {
if Self::is_api_available() {
tracing::warn!(
"API Gateway configured but gRPC client not yet implemented -- using simulation"
"API configured but gRPC client not yet implemented -- using simulation"
);
}
tracing::warn!(
@@ -154,11 +154,11 @@ impl LoginClient {
password: password.to_owned(),
};
// TODO: Call API Gateway login endpoint via gRPC
// TODO: Call API login endpoint via gRPC
// For now, simulate a successful login response
if Self::is_api_gateway_available() {
if Self::is_api_available() {
tracing::warn!(
"API Gateway configured but gRPC client not yet implemented -- using simulation"
"API configured but gRPC client not yet implemented -- using simulation"
);
}
tracing::warn!(
@@ -209,10 +209,10 @@ impl LoginClient {
totp_code,
};
// TODO: Call API Gateway MFA endpoint via gRPC
if Self::is_api_gateway_available() {
// TODO: Call API MFA endpoint via gRPC
if Self::is_api_available() {
tracing::warn!(
"API Gateway configured but gRPC client not yet implemented -- using simulation"
"API configured but gRPC client not yet implemented -- using simulation"
);
}
tracing::warn!(
@@ -250,10 +250,10 @@ impl LoginClient {
refresh_token: refresh_token.clone(),
};
// TODO: Call API Gateway refresh endpoint via gRPC
if Self::is_api_gateway_available() {
// TODO: Call API refresh endpoint via gRPC
if Self::is_api_available() {
tracing::warn!(
"API Gateway configured but gRPC client not yet implemented -- using simulation"
"API configured but gRPC client not yet implemented -- using simulation"
);
}
tracing::warn!(

View File

@@ -257,7 +257,7 @@ impl ConfigCommand {
.unwrap_or_else(|| "~/.foxhunt/config.toml".to_owned());
let result = EnvResult {
api_url: config.api_gateway_url,
api_url: config.api_url,
config_file: config_path,
log_level: config.log_level,
token_storage: config.token_storage,

View File

@@ -10,7 +10,7 @@
//! ## Example Config File
//! ```toml
//! # ~/.foxhunt/config.toml
//! api_gateway_url = "https://api.fxhnt.ai"
//! api_url = "https://api.fxhnt.ai"
//! log_level = "info"
//! token_storage = "keyring"
//! ```
@@ -85,9 +85,9 @@ impl Default for BrokerConfig {
/// by CLI arguments or environment variables.
#[derive(Debug, Serialize, Deserialize)]
pub struct FxtConfig {
/// API Gateway URL (default: <http://localhost:50051>)
#[serde(default = "default_api_gateway_url")]
pub api_gateway_url: String,
/// Unified API URL (default: <https://api.fxhnt.ai>)
#[serde(default = "default_api_url", alias = "api_gateway_url")]
pub api_url: String,
/// Log level (default: info)
#[serde(default = "default_log_level")]
@@ -106,7 +106,7 @@ pub struct FxtConfig {
pub broker: BrokerConfig,
}
fn default_api_gateway_url() -> String {
fn default_api_url() -> String {
"https://api.fxhnt.ai".to_owned()
}
@@ -121,7 +121,7 @@ fn default_token_storage() -> String {
impl Default for FxtConfig {
fn default() -> Self {
Self {
api_gateway_url: default_api_gateway_url(),
api_url: default_api_url(),
log_level: default_log_level(),
token_storage: default_token_storage(),
jwt_secret: None,
@@ -141,7 +141,7 @@ impl FxtConfig {
/// use fxt::config::FxtConfig;
///
/// let config = FxtConfig::load().unwrap();
/// println!("API Gateway: {}", config.api_gateway_url);
/// println!("API URL: {}", config.api_url);
/// ```
pub fn load() -> Result<Self> {
let config_path = Self::config_path()?;
@@ -168,7 +168,7 @@ impl FxtConfig {
/// use fxt::config::FxtConfig;
///
/// let config = FxtConfig {
/// api_gateway_url: "http://localhost:50051".to_string(),
/// api_url: "http://localhost:50051".to_string(),
/// log_level: "debug".to_string(),
/// token_storage: "keyring".to_string(),
/// jwt_secret: None,
@@ -210,7 +210,7 @@ mod tests {
fn test_default_config() {
// Test that Default trait provides correct values
let config = FxtConfig::default();
assert_eq!(config.api_gateway_url, "https://api.fxhnt.ai");
assert_eq!(config.api_url, "https://api.fxhnt.ai");
assert_eq!(config.log_level, "info");
assert_eq!(config.token_storage, "keyring");
}
@@ -218,7 +218,7 @@ mod tests {
#[test]
fn test_config_serialization() {
let config = FxtConfig {
api_gateway_url: "http://example.com:50051".to_owned(),
api_url: "http://example.com:50051".to_owned(),
log_level: "debug".to_owned(),
token_storage: "file".to_owned(),
jwt_secret: None,
@@ -228,7 +228,7 @@ mod tests {
let toml_str = toml::to_string(&config).unwrap();
let parsed: FxtConfig = toml::from_str(&toml_str).unwrap();
assert_eq!(config.api_gateway_url, parsed.api_gateway_url);
assert_eq!(config.api_url, parsed.api_url);
assert_eq!(config.log_level, parsed.log_level);
assert_eq!(config.token_storage, parsed.token_storage);
}
@@ -246,15 +246,23 @@ mod tests {
// Test that empty TOML string deserializes to defaults
let empty_toml = "";
let config: FxtConfig = toml::from_str(empty_toml).unwrap();
assert_eq!(config.api_gateway_url, "https://api.fxhnt.ai");
assert_eq!(config.api_url, "https://api.fxhnt.ai");
assert_eq!(config.log_level, "info");
assert_eq!(config.token_storage, "keyring");
}
#[test]
fn test_backwards_compat_alias() {
// Old config files using api_gateway_url must still deserialize correctly.
let toml_str = r#"api_gateway_url = "http://legacy:50051""#;
let config: FxtConfig = toml::from_str(toml_str).unwrap();
assert_eq!(config.api_url, "http://legacy:50051");
}
#[test]
fn test_config_with_broker_section() {
let toml_str = r#"
api_gateway_url = "http://localhost:50051"
api_url = "http://localhost:50051"
[broker]
gateway_url = "http://broker-gw:50060"

View File

@@ -54,7 +54,7 @@ impl Cockpit for PodsCockpit {
/// Shorten a K8s pod name to its trailing random suffix.
///
/// `api-gateway-7686d84bdd-zl4mn` becomes `zl4mn`.
/// `api-7686d84bdd-zl4mn` becomes `zl4mn`.
/// Falls back to the full name if there is no `-` separator.
fn shorten_pod_name(name: &str) -> &str {
name.rsplit('-').next().unwrap_or(name)

View File

@@ -1429,14 +1429,14 @@ mod tests {
#[test]
fn group_pods_groups_by_service() {
let pods = vec![
make_pod("api-1", "api-gateway", true),
make_pod("api-2", "api-gateway", true),
make_pod("api-1", "api", true),
make_pod("api-2", "api", true),
make_pod("trade-1", "trading-service", true),
make_pod("trade-2", "trading-service", false),
];
let groups = group_pods(pods);
assert_eq!(groups.len(), 2);
assert_eq!(groups[0].service, "api-gateway");
assert_eq!(groups[0].service, "api");
assert_eq!(groups[0].total_count, 2);
assert_eq!(groups[0].ready_count, 2);
assert_eq!(groups[1].service, "trading-service");
@@ -1448,14 +1448,14 @@ mod tests {
fn group_pods_empty_service_becomes_other() {
let pods = vec![
make_pod("orphan-1", "", true),
make_pod("api-1", "api-gateway", true),
make_pod("api-1", "api", true),
];
let groups = group_pods(pods);
assert_eq!(groups.len(), 2);
// BTreeMap sorts alphabetically: "(other)" < "api-gateway"
// BTreeMap sorts alphabetically: "(other)" < "api"
assert_eq!(groups[0].service, "(other)");
assert_eq!(groups[0].total_count, 1);
assert_eq!(groups[1].service, "api-gateway");
assert_eq!(groups[1].service, "api");
}
#[test]

View File

@@ -1079,7 +1079,7 @@ mod tests {
let mut state = AppState::default();
let services = vec![
ServiceData {
name: "api-gateway".into(),
name: "api".into(),
healthy: true,
uptime: "5d".into(),
version: "v1.0".into(),

View File

@@ -10,15 +10,15 @@ scrape_configs:
static_configs:
- targets: ['localhost:9090']
- job_name: 'api_gateway'
- job_name: 'api'
static_configs:
- targets: ['api_gateway:9091']
- targets: ['api:9091']
metrics_path: '/metrics'
scrape_interval: 5s
relabel_configs:
- source_labels: [__address__]
target_label: instance
replacement: 'api_gateway'
replacement: 'api'
- job_name: 'trading_service'
static_configs:

View File

@@ -10,20 +10,20 @@ groups:
interval: 15s
rules:
# P99 Latency > 100ms - CRITICAL
- alert: CriticalP99LatencyAPIGateway
- alert: CriticalP99LatencyAPI
expr: |
histogram_quantile(0.99,
rate(grpc_server_handling_seconds_bucket{job="api_gateway"}[1m])
rate(grpc_server_handling_seconds_bucket{job="api"}[1m])
) > 0.1
for: 1m
labels:
severity: critical
component: latency
service: api_gateway
service: api
annotations:
summary: "CRITICAL: API Gateway P99 latency exceeds 100ms"
summary: "CRITICAL: API P99 latency exceeds 100ms"
description: |
API Gateway P99 latency is {{ $value | humanizeDuration }}
API P99 latency is {{ $value | humanizeDuration }}
Target: < 100ms
Current: {{ $value | humanizeDuration }}
This impacts all client requests.
@@ -69,7 +69,7 @@ groups:
# Service Down - CRITICAL
- alert: CriticalServiceDown
expr: up{job=~"api_gateway|trading_service|trading_agent_service"} == 0
expr: up{job=~"api|trading_service|trading_agent_service"} == 0
for: 30s
labels:
severity: critical
@@ -90,22 +90,22 @@ groups:
interval: 15s
rules:
# Error Rate > 1% - WARNING
- alert: HighErrorRateAPIGateway
- alert: HighErrorRateAPI
expr: |
sum(rate(grpc_server_handled_total{job="api_gateway",grpc_code!="OK"}[5m]))
sum(rate(grpc_server_handled_total{job="api",grpc_code!="OK"}[5m]))
/
sum(rate(grpc_server_handled_total{job="api_gateway"}[5m])) > 0.01
sum(rate(grpc_server_handled_total{job="api"}[5m])) > 0.01
for: 3m
labels:
severity: warning
component: errors
service: api_gateway
service: api
annotations:
summary: "WARNING: API Gateway error rate exceeds 1%"
summary: "WARNING: API error rate exceeds 1%"
description: |
Error rate: {{ $value | humanizePercentage }}
Target: < 1%
Total errors in last 5m: {{ with query "sum(increase(grpc_server_handled_total{job='api_gateway',grpc_code!='OK'}[5m]))" }}{{ . | first | value | humanize }}{{ end }}
Total errors in last 5m: {{ with query "sum(increase(grpc_server_handled_total{job='api',grpc_code!='OK'}[5m]))" }}{{ . | first | value | humanize }}{{ end }}
Review error logs immediately.
runbook_url: "https://wiki.foxhunt.local/runbooks/high-error-rate"
@@ -431,9 +431,9 @@ groups:
- alert: DegradedSystemHealth
expr: |
(
sum(up{job=~"api_gateway|trading_service|backtesting_service|ml_training_service"})
sum(up{job=~"api|trading_service|backtesting_service|ml_training_service"})
/
count(up{job=~"api_gateway|trading_service|backtesting_service|ml_training_service"})
count(up{job=~"api|trading_service|backtesting_service|ml_training_service"})
) < 0.75
for: 2m
labels:

View File

@@ -4,15 +4,15 @@ groups:
- name: foxhunt-service-health
rules:
# Individual Service Health
- alert: APIGatewayDown
expr: up{job="api_gateway"} == 0
- alert: APIDown
expr: up{job="api"} == 0
for: 1m
labels:
severity: critical
service: api_gateway
service: api
annotations:
summary: "API Gateway is down"
description: "API Gateway has been down for more than 1 minute. All client access is blocked."
summary: "API is down"
description: "API has been down for more than 1 minute. All client access is blocked."
- alert: TradingServiceDown
expr: up{job="trading_service"} == 0

View File

@@ -1,7 +1,7 @@
version: '3.8'
services:
api_gateway:
api:
environment:
- JWT_SECRET=YZg5/mpqzH0NehGJXiR1yUgUg74HqdOUj/q9tnVSX+gqZvuzHKI1n0NhL4yP8CkUx7WyrVs3X86OSSxIUA6sxQ==

View File

@@ -426,11 +426,11 @@ services:
restart: unless-stopped
# API Gateway - Auth + routing (port 50051)
api_gateway:
api:
build:
context: .
dockerfile: services/api_gateway/Dockerfile
container_name: foxhunt-api-gateway
dockerfile: services/api/Dockerfile
container_name: foxhunt-api
env_file:
- .env # Load JWT_SECRET and other config from .env (Wave 147)
ports:
@@ -446,8 +446,9 @@ services:
- BACKTESTING_SERVICE_URL=https://backtesting_service:50053
- ML_TRAINING_SERVICE_URL=https://ml_training_service:50053
- JWT_SECRET=${JWT_SECRET:-dev_secret_key_change_in_production}
- JWT_ISSUER=foxhunt-api-gateway
- JWT_ISSUER=foxhunt-api
- JWT_AUDIENCE=foxhunt-services
- CORS_ORIGINS=http://localhost:5173
# TLS Certificate Configuration for Backtesting Service (mTLS)
# Using dev CA-signed certificates (matching Backtesting Service CA)
- BACKTESTING_TLS_CA_CERT=/tmp/foxhunt/certs/ca/ca-cert.pem

View File

@@ -253,7 +253,7 @@ data:
ssl_protocols TLSv1.2 TLSv1.3;
location / {
grpc_pass grpc://api-gateway.foxhunt.svc.cluster.local:50051;
grpc_pass grpc://api.foxhunt.svc.cluster.local:50051;
grpc_read_timeout 3600s;
grpc_send_timeout 3600s;
grpc_connect_timeout 30s;
@@ -271,7 +271,7 @@ data:
ssl_protocols TLSv1.2 TLSv1.3;
location / {
grpc_pass grpc://api-gateway.foxhunt.svc.cluster.local:50051;
grpc_pass grpc://api.foxhunt.svc.cluster.local:50051;
grpc_read_timeout 3600s;
grpc_send_timeout 3600s;
grpc_connect_timeout 30s;

View File

@@ -116,7 +116,7 @@ spec:
name: jwt-secret
key: secret
- name: JWT_ISSUER
value: foxhunt-api-gateway
value: foxhunt-api
- name: JWT_AUDIENCE
value: foxhunt-services
- name: S3_ENDPOINT

View File

@@ -115,7 +115,7 @@ spec:
name: jwt-secret
key: secret
- name: JWT_ISSUER
value: foxhunt-api-gateway
value: foxhunt-api
- name: JWT_AUDIENCE
value: foxhunt-services
- name: QUESTDB_ILP_HOST

View File

@@ -15,7 +15,7 @@ spec:
- name: scw-registry
containers:
- name: migrate
image: rg.fr-par.scw.cloud/foxhunt/api-gateway:latest
image: rg.fr-par.scw.cloud/foxhunt/api:latest
command: ["sqlx", "migrate", "run"]
env:
- name: DB_PASSWORD

View File

@@ -1,12 +1,12 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-gateway
name: api
namespace: foxhunt
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: api-gateway
app.kubernetes.io/name: api
policyTypes:
- Ingress
- Egress
@@ -36,14 +36,6 @@ spec:
ports:
- protocol: TCP
port: 6379
# Web gateway
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: web-gateway
ports:
- protocol: TCP
port: 3000
# Downstream gRPC services
- to:
- podSelector:

View File

@@ -14,7 +14,7 @@ spec:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: api-gateway
app.kubernetes.io/name: api
ports:
- protocol: TCP
port: 50053

View File

@@ -14,13 +14,10 @@ spec:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: api-gateway
app.kubernetes.io/name: api
- podSelector:
matchLabels:
app.kubernetes.io/name: trading-service
- podSelector:
matchLabels:
app.kubernetes.io/name: web-gateway
ports:
- protocol: TCP
port: 50056

View File

@@ -14,7 +14,7 @@ spec:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: api-gateway
app.kubernetes.io/name: api
ports:
- protocol: TCP
port: 50057

View File

@@ -14,7 +14,7 @@ spec:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: api-gateway
app.kubernetes.io/name: api
ports:
- protocol: TCP
port: 50053

View File

@@ -14,7 +14,7 @@ spec:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: api-gateway
app.kubernetes.io/name: api
ports:
- protocol: TCP
port: 50055

View File

@@ -14,7 +14,7 @@ spec:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: api-gateway
app.kubernetes.io/name: api
- podSelector:
matchLabels:
app.kubernetes.io/name: trading-agent-service

View File

@@ -1,69 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: web-gateway
namespace: foxhunt
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: web-gateway
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: api-gateway
- podSelector:
matchLabels:
app.kubernetes.io/name: tailscale-gitlab-proxy
ports:
- protocol: TCP
port: 3000
egress:
# api-gateway gRPC upstream
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: api-gateway
ports:
- protocol: TCP
port: 50051
# Direct gRPC to downstream services
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: backtesting-service
ports:
- protocol: TCP
port: 50053
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: ml-training-service
ports:
- protocol: TCP
port: 50053
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: trading-agent-service
ports:
- protocol: TCP
port: 50055
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: broker-gateway
ports:
- protocol: TCP
port: 50056
# Minio (initContainer binary fetch)
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: minio
ports:
- protocol: TCP
port: 9000

View File

@@ -1,10 +1,10 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-gateway
name: api
namespace: foxhunt
labels:
app.kubernetes.io/name: api-gateway
app.kubernetes.io/name: api
app.kubernetes.io/part-of: foxhunt
spec:
replicas: 1
@@ -15,7 +15,7 @@ spec:
maxUnavailable: 1
selector:
matchLabels:
app.kubernetes.io/name: api-gateway
app.kubernetes.io/name: api
template:
metadata:
annotations:
@@ -23,10 +23,10 @@ spec:
gitlab.com/prometheus_port: "9091"
gitlab.com/prometheus_path: "/metrics"
labels:
app.kubernetes.io/name: api-gateway
app.kubernetes.io/name: api
app.kubernetes.io/part-of: foxhunt
spec:
serviceAccountName: api-gateway
serviceAccountName: api
securityContext:
runAsNonRoot: true
runAsUser: 1000
@@ -46,16 +46,16 @@ spec:
- |
set -e
rclone copyto \
":s3:foxhunt-binaries/services/api-gateway" \
"/binaries/api-gateway" \
":s3:foxhunt-binaries/services/api" \
"/binaries/api" \
--s3-provider=Minio \
--s3-endpoint=https://minio.foxhunt.svc.cluster.local:9000 \
--s3-access-key-id="${MINIO_ACCESS_KEY}" \
--s3-secret-access-key="${MINIO_SECRET_KEY}" \
--s3-no-check-bucket \
--no-check-certificate
chmod +x /binaries/api-gateway
echo "Fetched api-gateway ($(stat -c%s /binaries/api-gateway) bytes)"
chmod +x /binaries/api
echo "Fetched api ($(stat -c%s /binaries/api) bytes)"
env:
- name: MINIO_ACCESS_KEY
valueFrom:
@@ -78,14 +78,14 @@ spec:
cpu: 500m
memory: 128Mi
containers:
- name: api-gateway
- name: api
image: rg.fr-par.scw.cloud/foxhunt-ci/foxhunt-runtime:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
command: ["/binaries/api-gateway"]
command: ["/binaries/api"]
ports:
- containerPort: 50051
name: grpc
@@ -107,7 +107,7 @@ spec:
name: jwt-secret
key: secret
- name: JWT_ISSUER
value: foxhunt-api-gateway
value: foxhunt-api
- name: JWT_AUDIENCE
value: foxhunt-services
- name: TRADING_SERVICE_URL
@@ -130,6 +130,8 @@ spec:
value: "info,opentelemetry=warn,h2=warn,tonic=warn,hyper=warn"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://tempo.foxhunt.svc.cluster.local:4317"
- name: CORS_ORIGINS
value: "https://dashboard.fxhnt.ai,http://localhost:5173"
volumeMounts:
- name: binaries
mountPath: /binaries
@@ -169,14 +171,14 @@ spec:
apiVersion: v1
kind: Service
metadata:
name: api-gateway
name: api
namespace: foxhunt
labels:
app.kubernetes.io/name: api-gateway
app.kubernetes.io/name: api
app.kubernetes.io/part-of: foxhunt
spec:
selector:
app.kubernetes.io/name: api-gateway
app.kubernetes.io/name: api
ports:
- port: 50051
targetPort: 50051
@@ -188,16 +190,16 @@ spec:
apiVersion: v1
kind: ServiceAccount
metadata:
name: api-gateway
name: api
namespace: foxhunt
labels:
app.kubernetes.io/name: api-gateway
app.kubernetes.io/name: api
app.kubernetes.io/part-of: foxhunt
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: api-gateway-pod-reader
name: api-pod-reader
namespace: foxhunt
rules:
- apiGroups: [""]
@@ -210,13 +212,13 @@ rules:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: api-gateway-pod-reader
name: api-pod-reader
namespace: foxhunt
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: api-gateway-pod-reader
name: api-pod-reader
subjects:
- kind: ServiceAccount
name: api-gateway
name: api
namespace: foxhunt

View File

@@ -106,7 +106,7 @@ spec:
name: jwt-secret
key: secret
- name: JWT_ISSUER
value: foxhunt-api-gateway
value: foxhunt-api
- name: JWT_AUDIENCE
value: foxhunt-services
- name: BENZINGA_API_KEY

View File

@@ -106,7 +106,7 @@ spec:
name: jwt-secret
key: secret
- name: JWT_ISSUER
value: foxhunt-api-gateway
value: foxhunt-api
- name: JWT_AUDIENCE
value: foxhunt-services
- name: IBKR_HOST

View File

@@ -149,7 +149,7 @@ spec:
name: jwt-secret
key: secret
- name: JWT_ISSUER
value: foxhunt-api-gateway
value: foxhunt-api
- name: JWT_AUDIENCE
value: foxhunt-services
- name: S3_ENDPOINT

View File

@@ -106,7 +106,7 @@ spec:
name: jwt-secret
key: secret
- name: JWT_ISSUER
value: foxhunt-api-gateway
value: foxhunt-api
- name: JWT_AUDIENCE
value: foxhunt-services
- name: RUST_LOG

View File

@@ -106,7 +106,7 @@ spec:
name: jwt-secret
key: secret
- name: JWT_ISSUER
value: foxhunt-api-gateway
value: foxhunt-api
- name: JWT_AUDIENCE
value: foxhunt-services
- name: QUESTDB_ILP_HOST

View File

@@ -1,161 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-gateway
namespace: foxhunt
labels:
app.kubernetes.io/name: web-gateway
app.kubernetes.io/part-of: foxhunt
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
selector:
matchLabels:
app.kubernetes.io/name: web-gateway
template:
metadata:
annotations:
gitlab.com/prometheus_scrape: "true"
gitlab.com/prometheus_port: "9098"
gitlab.com/prometheus_path: "/metrics"
labels:
app.kubernetes.io/name: web-gateway
app.kubernetes.io/part-of: foxhunt
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
imagePullSecrets:
- name: scw-registry
nodeSelector:
k8s.scaleway.com/pool-name: foxhunt
initContainers:
- name: fetch-binary
image: rg.fr-par.scw.cloud/foxhunt-ci/foxhunt-runtime:latest
command: ["/bin/sh", "-c"]
args:
- |
set -e
RCLONE_FLAGS="--s3-provider=Minio --s3-endpoint=https://minio.foxhunt.svc.cluster.local:9000 --s3-access-key-id=${MINIO_ACCESS_KEY} --s3-secret-access-key=${MINIO_SECRET_KEY} --s3-no-check-bucket --no-check-certificate"
rclone copyto ":s3:foxhunt-binaries/services/web-gateway" "/binaries/web-gateway" $RCLONE_FLAGS
chmod +x /binaries/web-gateway
echo "Fetched web-gateway ($(stat -c%s /binaries/web-gateway) bytes)"
rclone copy ":s3:foxhunt-binaries/static/" "/binaries/static/" $RCLONE_FLAGS
echo "Fetched static assets"
env:
- name: MINIO_ACCESS_KEY
valueFrom:
secretKeyRef:
name: minio-credentials
key: access-key
- name: MINIO_SECRET_KEY
valueFrom:
secretKeyRef:
name: minio-credentials
key: secret-key
volumeMounts:
- name: binaries
mountPath: /binaries
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 500m
memory: 256Mi
containers:
- name: web-gateway
image: rg.fr-par.scw.cloud/foxhunt-ci/foxhunt-runtime:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
command: ["/binaries/web-gateway"]
ports:
- containerPort: 3000
name: http
- containerPort: 9098
name: metrics
env:
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: jwt-secret
key: secret
- name: TRADING_SERVICE_URL
value: "http://api-gateway:50051"
- name: BACKTESTING_SERVICE_URL
value: "http://backtesting-service:50053"
- name: ML_TRAINING_SERVICE_URL
value: "http://ml-training-service:50053"
- name: TRADING_AGENT_SERVICE_URL
value: "http://trading-agent-service:50055"
- name: BROKER_GATEWAY_SERVICE_URL
value: "http://broker-gateway:50056"
- name: API_GATEWAY_URL
value: "http://api-gateway:50051"
- name: RUST_LOG
value: "info,opentelemetry=warn,h2=warn,tonic=warn,hyper=warn"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://tempo.foxhunt.svc.cluster.local:4317"
volumeMounts:
- name: binaries
mountPath: /binaries
readOnly: true
- name: tmp
mountPath: /tmp
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 15
failureThreshold: 5
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
volumes:
- name: binaries
emptyDir:
sizeLimit: 500Mi
- name: tmp
emptyDir:
sizeLimit: 50Mi
---
apiVersion: v1
kind: Service
metadata:
name: web-gateway
namespace: foxhunt
labels:
app.kubernetes.io/name: web-gateway
app.kubernetes.io/part-of: foxhunt
spec:
selector:
app.kubernetes.io/name: web-gateway
ports:
- port: 3000
targetPort: 3000
name: http
- port: 9098
targetPort: 9098
name: metrics

View File

@@ -23,7 +23,7 @@ TIMESTAMP=$(date -u +"%Y%m%d-%H%M%S")
# Services built with Dockerfile.service (--build-arg SERVICE=<name>)
STANDARD_SERVICES=(
api-gateway
api
trading-service
trading-agent-service
ml-training-service
@@ -37,7 +37,6 @@ DRY_RUN=false
PARALLEL=1
SELECTED_SERVICES=()
SKIP_STANDARD=false
SKIP_WEBGW=false
SKIP_TRAINING=false
TAG_LATEST=true
@@ -49,7 +48,6 @@ Options:
--no-push Build only, don't push to registry
--dry-run Show what would be built
--services "s1 s2" Build only these standard services
--skip-webgw Skip web-gateway build
--skip-training Skip training image build
--parallel N Run N builds concurrently (default: 1)
--no-latest Don't tag images as :latest
@@ -62,7 +60,6 @@ while [[ $# -gt 0 ]]; do
--no-push) DO_PUSH=false; shift ;;
--dry-run) DRY_RUN=true; shift ;;
--services) IFS=' ' read -ra SELECTED_SERVICES <<< "$2"; shift 2 ;;
--skip-webgw) SKIP_WEBGW=true; shift ;;
--skip-training) SKIP_TRAINING=true; shift ;;
--parallel) PARALLEL="$2"; shift 2 ;;
--no-latest) TAG_LATEST=false; shift ;;
@@ -74,7 +71,6 @@ done
# If --services was given, use only those; otherwise use all standard services
if [[ ${#SELECTED_SERVICES[@]} -gt 0 ]]; then
SERVICES=("${SELECTED_SERVICES[@]}")
SKIP_WEBGW=true
SKIP_TRAINING=true
else
SERVICES=("${STANDARD_SERVICES[@]}")
@@ -195,16 +191,6 @@ else
done
fi
# ---------------------------------------------------------------------------
# Build web-gateway (Dockerfile.web-gateway)
# ---------------------------------------------------------------------------
if [[ "$SKIP_WEBGW" != "true" ]]; then
echo ""
echo "--- Web Gateway ---"
build_and_tag "web-gateway" "infra/docker/Dockerfile.web-gateway" || true
fi
# ---------------------------------------------------------------------------
# Build training image (Dockerfile.training)
# ---------------------------------------------------------------------------
@@ -227,10 +213,6 @@ if [[ "$DO_PUSH" == "true" ]]; then
push_image "$svc"
done
if [[ "$SKIP_WEBGW" != "true" ]]; then
push_image "web-gateway"
fi
if [[ "$SKIP_TRAINING" != "true" ]]; then
push_image "training"
fi

View File

@@ -87,23 +87,23 @@ check "QuestDB HTTP health" \
"
# --- 4. Services ----------------------------------------------------------
section "Services (gRPC + web-gateway)"
section "Services (gRPC + API gateway)"
for svc in "${GRPC_SERVICES[@]}"; do
check "Pod '${svc}' is Running" \
bash -c "kubectl -n ${NAMESPACE} get pods -l app=${svc} --field-selector=status.phase=Running --no-headers 2>/dev/null | grep -q ."
done
check "Pod 'web-gateway' is Running" \
bash -c "kubectl -n ${NAMESPACE} get pods -l app=web-gateway --field-selector=status.phase=Running --no-headers 2>/dev/null | grep -q ."
check "Pod 'api' is Running" \
bash -c "kubectl -n ${NAMESPACE} get pods -l app=api --field-selector=status.phase=Running --no-headers 2>/dev/null | grep -q ."
# --- 5. Web gateway health ------------------------------------------------
section "Web Gateway Health"
# --- 5. API gateway health ------------------------------------------------
section "API Gateway Health"
check "web-gateway /health returns 200" \
check "api /health returns 200" \
bash -c "
CLUSTER_IP=\$(kubectl -n ${NAMESPACE} get svc web-gateway -o jsonpath='{.spec.clusterIP}' 2>/dev/null)
PORT=\$(kubectl -n ${NAMESPACE} get svc web-gateway -o jsonpath='{.spec.ports[0].port}' 2>/dev/null)
CLUSTER_IP=\$(kubectl -n ${NAMESPACE} get svc api -o jsonpath='{.spec.clusterIP}' 2>/dev/null)
PORT=\$(kubectl -n ${NAMESPACE} get svc api -o jsonpath='{.spec.ports[0].port}' 2>/dev/null)
kubectl -n ${NAMESPACE} run smoke-curl --rm -i --restart=Never --image=curlimages/curl -- \
curl -sf http://\${CLUSTER_IP}:\${PORT}/health
"

View File

@@ -29,7 +29,7 @@ PRODUCTION_MODULES=(
"config"
"common"
"services/trading_service"
"services/api_gateway"
"services/api"
)
# Print colored message

View File

@@ -72,7 +72,7 @@ DNS.1 = localhost
DNS.2 = trading.foxhunt.local
DNS.3 = backtesting.foxhunt.local
DNS.4 = ml-training.foxhunt.local
DNS.5 = api-gateway.foxhunt.local
DNS.5 = api.foxhunt.local
IP.1 = 127.0.0.1
IP.2 = 0.0.0.0
EOF