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:
@@ -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
111
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
api_gateway:
|
||||
api:
|
||||
environment:
|
||||
- JWT_SECRET=YZg5/mpqzH0NehGJXiR1yUgUg74HqdOUj/q9tnVSX+gqZvuzHKI1n0NhL4yP8CkUx7WyrVs3X86OSSxIUA6sxQ==
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: api-gateway
|
||||
app.kubernetes.io/name: api
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 50053
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: api-gateway
|
||||
app.kubernetes.io/name: api
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 50057
|
||||
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: api-gateway
|
||||
app.kubernetes.io/name: api
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 50053
|
||||
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: api-gateway
|
||||
app.kubernetes.io/name: api
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 50055
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
"
|
||||
|
||||
@@ -29,7 +29,7 @@ PRODUCTION_MODULES=(
|
||||
"config"
|
||||
"common"
|
||||
"services/trading_service"
|
||||
"services/api_gateway"
|
||||
"services/api"
|
||||
)
|
||||
|
||||
# Print colored message
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user