betelgeusebytes/k8s/trading/ib-gateway.yaml

542 lines
14 KiB
YAML

apiVersion: v1
kind: Namespace
metadata:
name: trading
labels:
name: trading
environment: production
---
# OPTIONAL: Use this if you want to persist IB Gateway settings/logs
# across pod restarts. For most use cases, this is NOT needed since
# IB Gateway is mostly stateless and credentials are in Secrets.
#
# Only create this PV/PVC if you need to persist:
# - TWS session data
# - Custom workspace layouts
# - Historical API usage logs
apiVersion: v1
kind: PersistentVolume
metadata:
name: ib-gateway-data
labels:
type: local
app: ib-gateway
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /mnt/local-ssd/ib-gateway # Adjust to your local SSD path
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- hetzner-2
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ib-gateway-data
namespace: trading
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: local-storage
selector:
matchLabels:
app: ib-gateway
# To use this PVC, add to Deployment volumeMounts:
# - name: data
# mountPath: /root/Jts
# And to volumes:
# - name: data
# persistentVolumeClaim:
# claimName: ib-gateway-data
---
apiVersion: v1
kind: Secret
metadata:
name: ib-credentials
namespace: trading
type: Opaque
stringData:
# IMPORTANT: Replace these with your actual IB credentials
# For paper trading, use your paper trading account
username: "saladin85"
password: "3Lcd@05041985"
# Trading mode: "paper" or "live"
trading-mode: "paper"
# IB Gateway config (jts.ini equivalent)
# This enables headless mode and configures ports
ibgateway.conf: |
[IBGateway]
TradingMode=paper
ApiOnly=true
ReadOnlyApi=false
TrustedIPs=127.0.0.1
[IBGatewayAPI]
ApiPortNumber=4002
[Logon]
UseRemoteSettings=no
Locale=en
ColorPaletteName=dark
[Display]
ShowSplashScreen=no
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ib-gateway-config
namespace: trading
data:
# Startup script to configure IB Gateway for headless operation
startup.sh: |
#!/bin/bash
set -e
echo "Starting IB Gateway in headless mode..."
echo "Trading Mode: ${TRADING_MODE}"
echo "Port: ${TWS_PORT}"
# Configure based on trading mode
if [ "${TRADING_MODE}" == "live" ]; then
export TWS_PORT=4001
echo "⚠️ LIVE TRADING MODE - USE WITH CAUTION ⚠️"
else
export TWS_PORT=4002
echo "📝 Paper Trading Mode (Safe)"
fi
# IMPORTANT: use the env vars provided by the Deployment
export IB_USERNAME="${TWS_USERID}"
export IB_PASSWORD="${TWS_PASSWORD}"
# Start IB Gateway
exec /opt/ibgateway/ibgateway-latest-standalone-linux-x64.sh \
--tws-path=/root/Jts \
--tws-settings-path=/root \
--user="${IB_USERNAME}" \
--pw="${IB_PASSWORD}" \
--mode="${TRADING_MODE}" \
--port="${TWS_PORT}"
# Health check script
healthcheck.sh: |
#!/bin/bash
# Check if TWS API port is listening
# PORT=${TWS_PORT:-4002}
# nc -z localhost $PORT
# exit $?
#!/bin/sh
# Pure-python TCP check (no nc required)
PORT="${TWS_PORT:-4002}"
python - <<'PY'
import os, socket, sys
port = int(os.environ.get("TWS_PORT", os.environ.get("PORT", "4002")))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(2)
try:
s.connect(("127.0.0.1", port))
sys.exit(0)
except Exception:
sys.exit(1)
finally:
s.close()
PY
---
# apiVersion: apps/v1
# kind: Deployment
# metadata:
# name: ib-gateway
# namespace: trading
# labels:
# app: ib-gateway
# component: trading-infrastructure
# spec:
# replicas: 1 # IB Gateway should only have 1 instance per account
# strategy:
# type: Recreate # Avoid multiple simultaneous logins
# selector:
# matchLabels:
# app: ib-gateway
# template:
# metadata:
# labels:
# app: ib-gateway
# annotations:
# prometheus.io/scrape: "false" # No metrics endpoint by default
# spec:
# # Pin to hetzner-2 (matches your existing pattern)
# nodeSelector:
# kubernetes.io/hostname: hetzner-2
# # Security context
# securityContext:
# runAsNonRoot: false # IB Gateway requires root for VNC (even if unused)
# fsGroup: 1000
# containers:
# - name: ib-gateway
# # Using community-maintained IB Gateway image
# # Alternative: waytrade/ib-gateway:latest
# image: ghcr.io/gnzsnz/ib-gateway:stable
# imagePullPolicy: IfNotPresent
# env:
# - name: TWS_USERID
# valueFrom:
# secretKeyRef:
# name: ib-credentials
# key: username
# - name: TWS_PASSWORD
# valueFrom:
# secretKeyRef:
# name: ib-credentials
# key: password
# - name: TRADING_MODE
# valueFrom:
# secretKeyRef:
# name: ib-credentials
# key: trading-mode
# - name: TWS_PORT
# value: "4002" # Default to paper trading
# - name: READ_ONLY_API
# value: "no"
# # Ports
# ports:
# - name: paper-trading
# containerPort: 4002
# protocol: TCP
# - name: live-trading
# containerPort: 4001
# protocol: TCP
# - name: vnc
# containerPort: 5900
# protocol: TCP # VNC (not exposed externally)
# # Resource limits
# resources:
# requests:
# memory: "1Gi"
# cpu: "500m"
# limits:
# memory: "2Gi"
# cpu: "1000m"
# # Liveness probe (check if API port is responsive)
# startupProbe:
# tcpSocket:
# port: 4002
# initialDelaySeconds: 60 # Wait 60s before first check
# periodSeconds: 10 # Check every 10s
# timeoutSeconds: 5
# failureThreshold: 18 # 60s + (10s * 18) = 240s total startup time
# livenessProbe:
# tcpSocket:
# port: 4002
# initialDelaySeconds: 0 # IB Gateway takes time to start
# periodSeconds: 60
# timeoutSeconds: 5
# failureThreshold: 3
# # Readiness probe
# readinessProbe:
# tcpSocket:
# port: 4002
# initialDelaySeconds: 0
# periodSeconds: 10
# timeoutSeconds: 5
# failureThreshold: 2
# # Volume mounts for config
# volumeMounts:
# - name: ib-config
# mountPath: /root/Jts/jts.ini
# subPath: ibgateway.conf
# - name: startup-script
# mountPath: /startup.sh
# subPath: startup.sh
# - name: data
# mountPath: /root/Jts
# # Logging to stdout (Fluent Bit will collect)
# # IB Gateway logs go to /root/Jts/log by default
# lifecycle:
# postStart:
# exec:
# command:
# - /bin/sh
# - -c
# - |
# mkdir -p /root/Jts/log
# ln -sf /dev/stdout /root/Jts/log/ibgateway.log || true
# volumes:
# - name: ib-config
# secret:
# secretName: ib-credentials
# defaultMode: 0644
# - name: startup-script
# configMap:
# name: ib-gateway-config
# defaultMode: 0755
# - name: data
# persistentVolumeClaim:
# claimName: ib-gateway-data
# # Restart policy
# restartPolicy: Always
# # DNS policy for internal cluster resolution
# dnsPolicy: ClusterFirst
apiVersion: apps/v1
kind: Deployment
metadata:
name: ib-gateway
namespace: trading
labels:
app: ib-gateway
component: trading-infrastructure
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: ib-gateway
template:
metadata:
labels:
app: ib-gateway
annotations:
prometheus.io/scrape: "false"
spec:
nodeSelector:
kubernetes.io/hostname: hetzner-2
securityContext:
runAsNonRoot: false
fsGroup: 1000
# Seed writable jts.ini into the PVC once
initContainers:
- name: seed-jts-config
image: busybox:1.36
command:
- sh
- -c
- |
set -e
mkdir -p /data
if [ ! -f /data/jts.ini ]; then
echo "Seeding jts.ini into PVC"
cp /config/ibgateway.conf /data/jts.ini
chmod 644 /data/jts.ini
else
echo "jts.ini already exists in PVC"
fi
volumeMounts:
- name: ib-config
mountPath: /config
readOnly: true
- name: data
mountPath: /data
containers:
# ------------------------------------------------------------------
# IB Gateway
# ------------------------------------------------------------------
- name: ib-gateway
image: ghcr.io/gnzsnz/ib-gateway:stable
imagePullPolicy: IfNotPresent
env:
- name: TWS_USERID
valueFrom:
secretKeyRef:
name: ib-credentials
key: username
- name: TWS_PASSWORD
valueFrom:
secretKeyRef:
name: ib-credentials
key: password
- name: TRADING_MODE
valueFrom:
secretKeyRef:
name: ib-credentials
key: trading-mode
- name: TWS_PORT
value: "4002"
- name: READ_ONLY_API
value: "no"
ports:
- name: ib-api-local
containerPort: 4002
protocol: TCP
- name: live-trading
containerPort: 4001
protocol: TCP
- name: vnc
containerPort: 5900
protocol: TCP
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
# IMPORTANT: Probes should check the local IB port (4002)
startupProbe:
tcpSocket:
port: 4002
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 18
livenessProbe:
tcpSocket:
port: 4002
periodSeconds: 60
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 4002
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 2
volumeMounts:
- name: data
mountPath: /root/Jts
lifecycle:
postStart:
exec:
command:
- sh
- -c
- |
mkdir -p /root/Jts/log
ln -sf /dev/stdout /root/Jts/log/ibgateway.log || true
# ------------------------------------------------------------------
# Sidecar TCP proxy: accepts cluster traffic, forwards to localhost:4002
# ------------------------------------------------------------------
- name: ib-api-proxy
image: alpine/socat:1.8.0.0
imagePullPolicy: IfNotPresent
args:
- "-d"
- "-d"
- "TCP-LISTEN:4003,fork,reuseaddr"
- "TCP:127.0.0.1:4002"
ports:
- name: ib-api
containerPort: 4003
protocol: TCP
resources:
requests:
memory: "32Mi"
cpu: "10m"
limits:
memory: "128Mi"
cpu: "100m"
# basic probe: is proxy listening
readinessProbe:
tcpSocket:
port: 4003
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
volumes:
- name: ib-config
secret:
secretName: ib-credentials
defaultMode: 0644
- name: data
persistentVolumeClaim:
claimName: ib-gateway-data
restartPolicy: Always
dnsPolicy: ClusterFirst
---
# apiVersion: v1
# kind: Service
# metadata:
# name: ib-gateway
# namespace: trading
# labels:
# app: ib-gateway
# spec:
# type: ClusterIP # Internal-only, not exposed publicly
# clusterIP: None # Headless service (optional, remove if you want a stable ClusterIP)
# selector:
# app: ib-gateway
# ports:
# - name: paper-trading
# port: 4002
# targetPort: 4002
# protocol: TCP
# - name: live-trading
# port: 4001
# targetPort: 4001
# protocol: TCP
# sessionAffinity: ClientIP # Stick to same pod (important for stateful TWS sessions)
# sessionAffinityConfig:
# clientIP:
# timeoutSeconds: 3600 # 1 hour session stickiness
apiVersion: v1
kind: Service
metadata:
name: ib-gateway
namespace: trading
labels:
app: ib-gateway
spec:
type: ClusterIP
selector:
app: ib-gateway
ports:
- name: paper-trading
port: 4002
targetPort: 4003 # <-- proxy sidecar, not the gateway directly
protocol: TCP
- name: live-trading
port: 4001
targetPort: 4001
protocol: TCP
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 3600