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 CARGO_TERM_COLOR: always
# CUDA_COMPUTE_CAP is set at runtime by detect_gpu() — L4=89, H100=90 # 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 # 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" JWT_SECRET: "CiTestSecret_Kx7mP9nR2sW5vY8bC3fG6jH1kL4pQ7tZ0uN9dM5eV8xS2wT6yA4zB_64chars_min"
# Training data on block storage PVC (mounted at /mnt/training-data by runner) # Training data on block storage PVC (mounted at /mnt/training-data by runner)
FOXHUNT_DATA_DIR: "/mnt/training-data/futures-baseline" FOXHUNT_DATA_DIR: "/mnt/training-data/futures-baseline"
@@ -448,14 +448,14 @@ test:
# Each service compiled independently with dependency-aware changes: filters. # Each service compiled independently with dependency-aware changes: filters.
# sccache shared across jobs — incremental crate compilation is cached. # sccache shared across jobs — incremental crate compilation is cached.
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
compile-api-gateway: compile-api:
extends: .compile-service-base extends: .compile-service-base
variables: variables:
SERVICE_PACKAGE: api-gateway SERVICE_PACKAGE: api
rules: &rules-api-gateway rules: &rules-api
- if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push" - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"
changes: changes:
- services/api_gateway/** - services/api/**
- crates/common/** - crates/common/**
- crates/config/** - crates/config/**
- crates/trading_engine/** - crates/trading_engine/**
@@ -572,19 +572,6 @@ compile-data-acquisition-service:
- Cargo.lock - Cargo.lock
- if: $CI_PIPELINE_SOURCE == "web" || $CI_PIPELINE_SOURCE == "api" - 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) # 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 # Rules use YAML anchors shared with compile jobs — ensures compile+deploy
# are created/skipped together. # are created/skipped together.
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
deploy-api-gateway: deploy-api:
extends: .deploy-service-base extends: .deploy-service-base
needs: [{job: compile-api-gateway, artifacts: true}] needs: [{job: compile-api, artifacts: true}]
variables: variables:
SERVICE_PACKAGE: api-gateway SERVICE_PACKAGE: api
rules: *rules-api-gateway rules: *rules-api
deploy-trading-service: deploy-trading-service:
extends: .deploy-service-base extends: .deploy-service-base
@@ -1686,13 +1673,6 @@ deploy-data-acquisition-service:
SERVICE_PACKAGE: data-acquisition-service SERVICE_PACKAGE: data-acquisition-service
rules: *rules-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 # Stage 4b: Deploy training binaries to MinIO
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------

111
Cargo.lock generated
View File

@@ -250,82 +250,7 @@ dependencies = [
] ]
[[package]] [[package]]
name = "api-gateway" name = "api_load_tests"
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"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
@@ -1442,7 +1367,6 @@ checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum-core 0.4.5", "axum-core 0.4.5",
"base64 0.22.1",
"bytes", "bytes",
"futures-util", "futures-util",
"http 1.3.1", "http 1.3.1",
@@ -1461,10 +1385,8 @@ dependencies = [
"serde_json", "serde_json",
"serde_path_to_error", "serde_path_to_error",
"serde_urlencoded", "serde_urlencoded",
"sha1",
"sync_wrapper 1.0.2", "sync_wrapper 1.0.2",
"tokio", "tokio",
"tokio-tungstenite",
"tower 0.5.2", "tower 0.5.2",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
@@ -11359,7 +11281,7 @@ name = "trading-service"
version = "1.0.0" version = "1.0.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"api-gateway", "api",
"async-stream", "async-stream",
"async-trait", "async-trait",
"axum 0.7.9", "axum 0.7.9",
@@ -11999,35 +11921,6 @@ dependencies = [
"web-sys", "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]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.81" version = "0.3.81"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,20 +10,20 @@ groups:
interval: 15s interval: 15s
rules: rules:
# P99 Latency > 100ms - CRITICAL # P99 Latency > 100ms - CRITICAL
- alert: CriticalP99LatencyAPIGateway - alert: CriticalP99LatencyAPI
expr: | expr: |
histogram_quantile(0.99, 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 ) > 0.1
for: 1m for: 1m
labels: labels:
severity: critical severity: critical
component: latency component: latency
service: api_gateway service: api
annotations: annotations:
summary: "CRITICAL: API Gateway P99 latency exceeds 100ms" summary: "CRITICAL: API P99 latency exceeds 100ms"
description: | description: |
API Gateway P99 latency is {{ $value | humanizeDuration }} API P99 latency is {{ $value | humanizeDuration }}
Target: < 100ms Target: < 100ms
Current: {{ $value | humanizeDuration }} Current: {{ $value | humanizeDuration }}
This impacts all client requests. This impacts all client requests.
@@ -69,7 +69,7 @@ groups:
# Service Down - CRITICAL # Service Down - CRITICAL
- alert: CriticalServiceDown - 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 for: 30s
labels: labels:
severity: critical severity: critical
@@ -90,22 +90,22 @@ groups:
interval: 15s interval: 15s
rules: rules:
# Error Rate > 1% - WARNING # Error Rate > 1% - WARNING
- alert: HighErrorRateAPIGateway - alert: HighErrorRateAPI
expr: | 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 for: 3m
labels: labels:
severity: warning severity: warning
component: errors component: errors
service: api_gateway service: api
annotations: annotations:
summary: "WARNING: API Gateway error rate exceeds 1%" summary: "WARNING: API error rate exceeds 1%"
description: | description: |
Error rate: {{ $value | humanizePercentage }} Error rate: {{ $value | humanizePercentage }}
Target: < 1% 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. Review error logs immediately.
runbook_url: "https://wiki.foxhunt.local/runbooks/high-error-rate" runbook_url: "https://wiki.foxhunt.local/runbooks/high-error-rate"
@@ -431,9 +431,9 @@ groups:
- alert: DegradedSystemHealth - alert: DegradedSystemHealth
expr: | 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 ) < 0.75
for: 2m for: 2m
labels: labels:

View File

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

View File

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

View File

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

View File

@@ -253,7 +253,7 @@ data:
ssl_protocols TLSv1.2 TLSv1.3; ssl_protocols TLSv1.2 TLSv1.3;
location / { 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_read_timeout 3600s;
grpc_send_timeout 3600s; grpc_send_timeout 3600s;
grpc_connect_timeout 30s; grpc_connect_timeout 30s;
@@ -271,7 +271,7 @@ data:
ssl_protocols TLSv1.2 TLSv1.3; ssl_protocols TLSv1.2 TLSv1.3;
location / { 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_read_timeout 3600s;
grpc_send_timeout 3600s; grpc_send_timeout 3600s;
grpc_connect_timeout 30s; grpc_connect_timeout 30s;

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ spec:
- name: scw-registry - name: scw-registry
containers: containers:
- name: migrate - 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"] command: ["sqlx", "migrate", "run"]
env: env:
- name: DB_PASSWORD - name: DB_PASSWORD

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -72,7 +72,7 @@ DNS.1 = localhost
DNS.2 = trading.foxhunt.local DNS.2 = trading.foxhunt.local
DNS.3 = backtesting.foxhunt.local DNS.3 = backtesting.foxhunt.local
DNS.4 = ml-training.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.1 = 127.0.0.1
IP.2 = 0.0.0.0 IP.2 = 0.0.0.0
EOF EOF