diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..6b07e94f4 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,219 @@ +# GitLab CI/CD — Foxhunt +# Runs on ci-build pool (GP1-XS, scale-to-zero) +# Uses sccache for Rust build caching, Kaniko for Docker builds + +stages: + - check + - test + - build + - deploy + +variables: + SQLX_OFFLINE: "true" + CARGO_TERM_COLOR: always + SCCACHE_BUCKET: $SCCACHE_BUCKET + SCCACHE_ENDPOINT: $SCCACHE_ENDPOINT + SCCACHE_S3_USE_SSL: "true" + AWS_ACCESS_KEY_ID: $SCW_ACCESS_KEY + AWS_SECRET_ACCESS_KEY: $SCW_SECRET_KEY + RUSTC_WRAPPER: /usr/local/bin/sccache + REGISTRY: ${CI_REGISTRY_IMAGE} + +# Base template for Rust jobs +.rust-base: + image: rust:1.89-slim + tags: + - kapsule + - rust + before_script: + - apt-get update && apt-get install -y protobuf-compiler curl pkg-config libssl-dev + - curl -fsSL https://github.com/mozilla/sccache/releases/download/v0.8.1/sccache-v0.8.1-x86_64-unknown-linux-musl.tar.gz + | tar xz --strip-components=1 -C /usr/local/bin sccache-v0.8.1-x86_64-unknown-linux-musl/sccache + - chmod +x /usr/local/bin/sccache + - rustup component add clippy + +# -------------------------------------------------------------------------- +# Stage 1: cargo check + clippy +# -------------------------------------------------------------------------- +check: + extends: .rust-base + stage: check + script: + - cargo check --workspace + - cargo clippy --workspace -- -D warnings + +# -------------------------------------------------------------------------- +# Stage 2: tests +# -------------------------------------------------------------------------- +test: + extends: .rust-base + stage: test + needs: [check] + script: + - cargo test --workspace --lib + +# -------------------------------------------------------------------------- +# Stage 3: Build + push images (main only, Kaniko) +# -------------------------------------------------------------------------- +.kaniko-base: + stage: build + needs: [test] + image: + name: gcr.io/kaniko-project/executor:debug + entrypoint: [""] + tags: + - kapsule + rules: + - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push" + before_script: + - mkdir -p /kaniko/.docker + - echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf '%s:%s' "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json + +build-trading-service: + extends: .kaniko-base + script: + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.service" + --build-arg SERVICE=trading_service + --build-arg SCCACHE_BUCKET=${SCCACHE_BUCKET} + --build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY} + --build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY} + --build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT} + --destination "${REGISTRY}/trading_service:${CI_COMMIT_SHA}" + --destination "${REGISTRY}/trading_service:latest" + +build-api-gateway: + extends: .kaniko-base + script: + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.service" + --build-arg SERVICE=api_gateway + --build-arg SCCACHE_BUCKET=${SCCACHE_BUCKET} + --build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY} + --build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY} + --build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT} + --destination "${REGISTRY}/api_gateway:${CI_COMMIT_SHA}" + --destination "${REGISTRY}/api_gateway:latest" + +build-broker-gateway: + extends: .kaniko-base + script: + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.service" + --build-arg SERVICE=broker_gateway_service + --build-arg SCCACHE_BUCKET=${SCCACHE_BUCKET} + --build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY} + --build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY} + --build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT} + --destination "${REGISTRY}/broker_gateway_service:${CI_COMMIT_SHA}" + --destination "${REGISTRY}/broker_gateway_service:latest" + +build-ml-training: + extends: .kaniko-base + script: + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.service" + --build-arg SERVICE=ml_training_service + --build-arg SCCACHE_BUCKET=${SCCACHE_BUCKET} + --build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY} + --build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY} + --build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT} + --destination "${REGISTRY}/ml_training_service:${CI_COMMIT_SHA}" + --destination "${REGISTRY}/ml_training_service:latest" + +build-backtesting: + extends: .kaniko-base + script: + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.service" + --build-arg SERVICE=backtesting_service + --build-arg SCCACHE_BUCKET=${SCCACHE_BUCKET} + --build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY} + --build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY} + --build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT} + --destination "${REGISTRY}/backtesting_service:${CI_COMMIT_SHA}" + --destination "${REGISTRY}/backtesting_service:latest" + +build-trading-agent: + extends: .kaniko-base + script: + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.service" + --build-arg SERVICE=trading_agent_service + --build-arg SCCACHE_BUCKET=${SCCACHE_BUCKET} + --build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY} + --build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY} + --build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT} + --destination "${REGISTRY}/trading_agent_service:${CI_COMMIT_SHA}" + --destination "${REGISTRY}/trading_agent_service:latest" + +build-web-gateway: + extends: .kaniko-base + script: + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.web-gateway" + --destination "${REGISTRY}/web-gateway:${CI_COMMIT_SHA}" + --destination "${REGISTRY}/web-gateway:latest" + +build-training: + extends: .kaniko-base + script: + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.training" + --build-arg SCCACHE_BUCKET=${SCCACHE_BUCKET} + --build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY} + --build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY} + --build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT} + --destination "${REGISTRY}/training:${CI_COMMIT_SHA}" + --destination "${REGISTRY}/training:latest" + +# -------------------------------------------------------------------------- +# Stage 4: Deploy to Kapsule (main only) +# -------------------------------------------------------------------------- +deploy: + stage: deploy + image: bitnami/kubectl:latest + tags: + - kapsule + needs: + - build-trading-service + - build-api-gateway + - build-broker-gateway + - build-ml-training + - build-backtesting + - build-trading-agent + - build-web-gateway + - build-training + rules: + - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push" + before_script: + - mkdir -p ~/.kube + - echo "$KUBECONFIG_DATA" | base64 -d > ~/.kube/config + - chmod 600 ~/.kube/config + environment: + name: production + kubernetes: + namespace: foxhunt + script: + # Apply manifests (picks up any config/resource changes) + - kubectl apply -f infra/k8s/databases/ + - kubectl apply -f infra/k8s/services/ + # Update images to this commit's build + - | + for svc in trading-service api-gateway broker-gateway-service ml-training-service backtesting-service trading-agent-service; do + kubectl -n foxhunt set image deployment/${svc} ${svc}=${REGISTRY}/${svc}:${CI_COMMIT_SHA} || true + done + kubectl -n foxhunt set image deployment/web-gateway web-gateway=${REGISTRY}/web-gateway:${CI_COMMIT_SHA} || true + # Wait for rollouts + - | + for svc in trading-service api-gateway broker-gateway-service ml-training-service backtesting-service trading-agent-service web-gateway; do + kubectl -n foxhunt rollout status deployment/${svc} --timeout=300s || true + done diff --git a/infra/k8s/gitlab/postgres-init.yaml b/infra/k8s/gitlab/postgres-init.yaml new file mode 100644 index 000000000..8a3c3c962 --- /dev/null +++ b/infra/k8s/gitlab/postgres-init.yaml @@ -0,0 +1,63 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: gitlab-postgres-init + namespace: foxhunt + labels: + app.kubernetes.io/name: gitlab-postgres-init + app.kubernetes.io/part-of: foxhunt +spec: + ttlSecondsAfterFinished: 300 + backoffLimit: 3 + template: + spec: + nodeSelector: + k8s.scaleway.com/pool-name: always-on + restartPolicy: Never + containers: + - name: init + image: timescale/timescaledb:latest-pg16 + command: + - bash + - -c + - | + set -euo pipefail + export PGPASSWORD="$POSTGRES_PASSWORD" + + until pg_isready -h postgres -U foxhunt; do + echo "Waiting for postgres..." + sleep 2 + done + + echo "Creating gitlab user..." + psql -h postgres -U foxhunt -d foxhunt -tc \ + "SELECT 1 FROM pg_roles WHERE rolname='gitlab'" | grep -q 1 || \ + psql -h postgres -U foxhunt -d foxhunt -c \ + "CREATE ROLE gitlab WITH LOGIN PASSWORD '${GITLAB_DB_PASSWORD}'" + + echo "Creating gitlab database..." + psql -h postgres -U foxhunt -tc \ + "SELECT 1 FROM pg_database WHERE datname='gitlab'" | grep -q 1 || \ + psql -h postgres -U foxhunt -c \ + "CREATE DATABASE gitlab OWNER gitlab" + + echo "Creating extensions..." + psql -h postgres -U foxhunt -d gitlab -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" + psql -h postgres -U foxhunt -d gitlab -c "CREATE EXTENSION IF NOT EXISTS btree_gist;" + + echo "Granting permissions..." + psql -h postgres -U foxhunt -d gitlab -c "GRANT ALL PRIVILEGES ON DATABASE gitlab TO gitlab;" + psql -h postgres -U foxhunt -d gitlab -c "GRANT ALL ON SCHEMA public TO gitlab;" + + echo "GitLab database initialized successfully." + env: + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: foxhunt-secrets + key: db-password + - name: GITLAB_DB_PASSWORD + valueFrom: + secretKeyRef: + name: gitlab-secrets + key: db-password diff --git a/infra/k8s/gitlab/runner-values.yaml b/infra/k8s/gitlab/runner-values.yaml new file mode 100644 index 000000000..2fabf3125 --- /dev/null +++ b/infra/k8s/gitlab/runner-values.yaml @@ -0,0 +1,70 @@ +# GitLab Runner — Kubernetes executor targeting ci-build pool +# Runner manager pod lives on gitlab node pool +# Build pods spawn on ci-build pool (scale-to-zero) + +gitlabUrl: https://git.fxhnt.ai +# runnerToken set via --set at install time + +replicas: 1 + +nodeSelector: + k8s.scaleway.com/pool-name: gitlab +tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + +runners: + config: | + [[runners]] + [runners.kubernetes] + namespace = "foxhunt" + image = "rust:1.89-slim" + privileged = false + + # Build pods on ci-build pool + [runners.kubernetes.node_selector] + "k8s.scaleway.com/pool-name" = "ci-build" + [[runners.kubernetes.node_tolerations]] + key = "ci-build" + operator = "Equal" + value = "true" + effect = "NoSchedule" + + # Resource limits for build pods + cpu_request = "2000m" + cpu_limit = "6000m" + memory_request = "4Gi" + memory_limit = "12Gi" + + # Helper container + helper_cpu_request = "100m" + helper_cpu_limit = "500m" + helper_memory_request = "128Mi" + helper_memory_limit = "512Mi" + + # Service containers (for kaniko sidecar) + [runners.kubernetes.pod_labels] + "app.kubernetes.io/part-of" = "foxhunt-ci" + + # Runner tags for job matching + tags: "kapsule,rust,docker" + +# Concurrency +concurrent: 4 + +# RBAC for runner to spawn pods +rbac: + create: true + rules: + - apiGroups: [""] + resources: ["pods", "pods/exec", "pods/log", "secrets", "configmaps"] + verbs: ["get", "list", "watch", "create", "delete", "update", "patch"] + - apiGroups: [""] + resources: ["pods/attach"] + verbs: ["create", "get"] + +serviceAccount: + create: true + name: gitlab-runner diff --git a/infra/k8s/gitlab/tailscale-proxy.yaml b/infra/k8s/gitlab/tailscale-proxy.yaml new file mode 100644 index 000000000..9ceecdd3c --- /dev/null +++ b/infra/k8s/gitlab/tailscale-proxy.yaml @@ -0,0 +1,161 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tailscale-gitlab + namespace: foxhunt +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: tailscale-gitlab + namespace: foxhunt +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["create", "get", "update", "patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: tailscale-gitlab + namespace: foxhunt +subjects: + - kind: ServiceAccount + name: tailscale-gitlab + namespace: foxhunt +roleRef: + kind: Role + name: tailscale-gitlab + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: tailscale-gitlab-proxy + namespace: foxhunt + labels: + app.kubernetes.io/name: tailscale-gitlab-proxy + app.kubernetes.io/part-of: foxhunt +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app.kubernetes.io/name: tailscale-gitlab-proxy + template: + metadata: + labels: + app.kubernetes.io/name: tailscale-gitlab-proxy + spec: + serviceAccountName: tailscale-gitlab + nodeSelector: + k8s.scaleway.com/pool-name: gitlab + tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + containers: + - name: nginx + image: nginx:alpine + ports: + - containerPort: 80 + - containerPort: 443 + - containerPort: 5050 + volumeMounts: + - name: nginx-conf + mountPath: /etc/nginx/conf.d + resources: + requests: + cpu: 25m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi + - name: ssh-proxy + image: alpine/socat:latest + args: + - "TCP-LISTEN:2222,fork,reuseaddr" + - "TCP:gitlab-gitlab-shell.foxhunt.svc.cluster.local:2222" + ports: + - containerPort: 2222 + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 50m + memory: 32Mi + - name: tailscale + image: ghcr.io/tailscale/tailscale:latest + env: + - name: TS_AUTHKEY + valueFrom: + secretKeyRef: + name: tailscale-auth + key: TS_AUTHKEY + - name: TS_KUBE_SECRET + value: tailscale-gitlab-state + - name: TS_USERSPACE + value: "false" + - name: TS_HOSTNAME + value: "foxhunt-gitlab" + - name: TS_ACCEPT_DNS + value: "false" + - name: TS_EXTRA_ARGS + value: "" + resources: + requests: + cpu: 25m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi + securityContext: + capabilities: + add: + - NET_ADMIN + - NET_RAW + volumes: + - name: nginx-conf + configMap: + name: tailscale-gitlab-nginx +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: tailscale-gitlab-nginx + namespace: foxhunt +data: + default.conf: | + server { + listen 80; + server_name git.fxhnt.ai; + + client_max_body_size 0; + + location / { + proxy_pass http://gitlab-webservice-default.foxhunt.svc.cluster.local:8181; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } + + server { + listen 5050; + server_name git.fxhnt.ai; + + client_max_body_size 0; + + location / { + proxy_pass http://gitlab-registry.foxhunt.svc.cluster.local:5000; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } diff --git a/infra/k8s/gitlab/values.yaml b/infra/k8s/gitlab/values.yaml new file mode 100644 index 000000000..f0f0dbfbd --- /dev/null +++ b/infra/k8s/gitlab/values.yaml @@ -0,0 +1,218 @@ +# GitLab CE - Foxhunt deployment +# Runs on dedicated 'gitlab' node pool (DEV1-L) +# External Postgres + Redis on always-on pool + +global: + edition: ce + hosts: + domain: fxhnt.ai + gitlab: + name: git.fxhnt.ai + registry: + name: git.fxhnt.ai + ingress: + enabled: false + configureCertmanager: false + shell: + port: 2222 + psql: + host: postgres.foxhunt.svc.cluster.local + port: 5432 + database: gitlab + username: gitlab + password: + secret: gitlab-secrets + key: db-password + redis: + host: redis.foxhunt.svc.cluster.local + port: 6379 + auth: + enabled: false + minio: + enabled: false + registry: + bucket: foxhunt-gitlab-registry + appConfig: + lfs: + bucket: foxhunt-gitlab-artifacts + connection: + secret: gitlab-s3-credentials + key: connection + artifacts: + bucket: foxhunt-gitlab-artifacts + connection: + secret: gitlab-s3-credentials + key: connection + uploads: + bucket: foxhunt-gitlab-artifacts + connection: + secret: gitlab-s3-credentials + key: connection + packages: + bucket: foxhunt-gitlab-artifacts + connection: + secret: gitlab-s3-credentials + key: connection + +# Disable bundled databases — use external +postgresql: + install: false +redis: + install: false + +# Disable components we don't need +installCertmanager: false +certmanager: + installCRDs: false +nginx-ingress: + enabled: false +prometheus: + install: true + rbac: + create: true + alertmanager: + enabled: false + server: + nodeSelector: + k8s.scaleway.com/pool-name: gitlab + tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + persistentVolume: + enabled: false + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi +gitlab-runner: + install: false + +# Registry — S3-backed +registry: + replicaCount: 1 + hpa: + minReplicas: 1 + maxReplicas: 1 + storage: + secret: gitlab-s3-credentials + key: registry + nodeSelector: + k8s.scaleway.com/pool-name: gitlab + tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + +# GitLab core components — all on gitlab node pool +gitlab: + webservice: + replicaCount: 1 + hpa: + minReplicas: 1 + maxReplicas: 1 + workerProcesses: 2 + workhorse: + extraArgs: "-apiLimit 0 -apiQueueLimit 0" + nodeSelector: + k8s.scaleway.com/pool-name: gitlab + tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + resources: + requests: + cpu: 500m + memory: 2Gi + limits: + cpu: 2000m + memory: 4Gi + extraEnv: + REDIS_URL: "redis://redis.foxhunt.svc.cluster.local:6379/8" + + sidekiq: + replicas: 1 + hpa: + minReplicas: 1 + maxReplicas: 1 + concurrency: 10 + nodeSelector: + k8s.scaleway.com/pool-name: gitlab + tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + resources: + requests: + cpu: 250m + memory: 1Gi + limits: + cpu: 1000m + memory: 2Gi + extraEnv: + REDIS_URL: "redis://redis.foxhunt.svc.cluster.local:6379/8" + + gitaly: + persistence: + enabled: true + size: 50Gi + nodeSelector: + k8s.scaleway.com/pool-name: gitlab + tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 1000m + memory: 2Gi + + gitlab-shell: + replicaCount: 1 + hpa: + minReplicas: 1 + maxReplicas: 1 + nodeSelector: + k8s.scaleway.com/pool-name: gitlab + tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + service: + type: NodePort + nodePort: 32222 + + toolbox: + backups: + objectStorage: + config: + secret: gitlab-s3-credentials + key: connection + nodeSelector: + k8s.scaleway.com/pool-name: gitlab + tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + + kas: + enabled: false + hpa: + minReplicas: 0 + maxReplicas: 0 + + gitlab-exporter: + enabled: false diff --git a/infra/k8s/services/api-gateway.yaml b/infra/k8s/services/api-gateway.yaml index d0df9495f..ebafd841c 100644 --- a/infra/k8s/services/api-gateway.yaml +++ b/infra/k8s/services/api-gateway.yaml @@ -17,10 +17,10 @@ spec: app.kubernetes.io/name: api-gateway spec: imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: api-gateway - image: rg.fr-par.scw.cloud/foxhunt/api_gateway:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/api_gateway:latest ports: - containerPort: 50051 name: grpc diff --git a/infra/k8s/services/backtesting-service.yaml b/infra/k8s/services/backtesting-service.yaml index 99a2819ca..9dc500916 100644 --- a/infra/k8s/services/backtesting-service.yaml +++ b/infra/k8s/services/backtesting-service.yaml @@ -17,10 +17,10 @@ spec: app.kubernetes.io/name: backtesting-service spec: imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: backtesting-service - image: rg.fr-par.scw.cloud/foxhunt/backtesting_service:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/backtesting_service:latest ports: - containerPort: 50053 name: grpc diff --git a/infra/k8s/services/broker-gateway.yaml b/infra/k8s/services/broker-gateway.yaml index 757ed2ffc..aeee2360a 100644 --- a/infra/k8s/services/broker-gateway.yaml +++ b/infra/k8s/services/broker-gateway.yaml @@ -17,10 +17,10 @@ spec: app.kubernetes.io/name: broker-gateway spec: imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: broker-gateway - image: rg.fr-par.scw.cloud/foxhunt/broker_gateway_service:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/broker_gateway_service:latest ports: - containerPort: 50056 name: grpc diff --git a/infra/k8s/services/ml-training-service-gpu.yaml b/infra/k8s/services/ml-training-service-gpu.yaml index 176e05410..ae5736ebb 100644 --- a/infra/k8s/services/ml-training-service-gpu.yaml +++ b/infra/k8s/services/ml-training-service-gpu.yaml @@ -26,10 +26,10 @@ spec: operator: Exists effect: NoSchedule imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: ml-training-service - image: rg.fr-par.scw.cloud/foxhunt/ml_training_service:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/ml_training_service:latest ports: - containerPort: 50053 name: grpc diff --git a/infra/k8s/services/ml-training-service.yaml b/infra/k8s/services/ml-training-service.yaml index 4edc591c6..02259ff3d 100644 --- a/infra/k8s/services/ml-training-service.yaml +++ b/infra/k8s/services/ml-training-service.yaml @@ -17,10 +17,10 @@ spec: app.kubernetes.io/name: ml-training-service spec: imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: ml-training-service - image: rg.fr-par.scw.cloud/foxhunt/ml_training_service:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/ml_training_service:latest ports: - containerPort: 50053 name: grpc diff --git a/infra/k8s/services/trading-agent-service.yaml b/infra/k8s/services/trading-agent-service.yaml index 276ccfdc5..b85764854 100644 --- a/infra/k8s/services/trading-agent-service.yaml +++ b/infra/k8s/services/trading-agent-service.yaml @@ -17,10 +17,10 @@ spec: app.kubernetes.io/name: trading-agent-service spec: imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: trading-agent-service - image: rg.fr-par.scw.cloud/foxhunt/trading_agent_service:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/trading_agent_service:latest ports: - containerPort: 50055 name: grpc diff --git a/infra/k8s/services/trading-service-gpu.yaml b/infra/k8s/services/trading-service-gpu.yaml index 09cd1d8ab..2ff3c504f 100644 --- a/infra/k8s/services/trading-service-gpu.yaml +++ b/infra/k8s/services/trading-service-gpu.yaml @@ -26,10 +26,10 @@ spec: operator: Exists effect: NoSchedule imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: trading-service - image: rg.fr-par.scw.cloud/foxhunt/trading_service:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/trading_service:latest ports: - containerPort: 50051 name: grpc diff --git a/infra/k8s/services/trading-service.yaml b/infra/k8s/services/trading-service.yaml index 85d65ad3c..f96261276 100644 --- a/infra/k8s/services/trading-service.yaml +++ b/infra/k8s/services/trading-service.yaml @@ -17,10 +17,10 @@ spec: app.kubernetes.io/name: trading-service spec: imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: trading-service - image: rg.fr-par.scw.cloud/foxhunt/trading_service:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/trading_service:latest ports: - containerPort: 50051 name: grpc diff --git a/infra/k8s/services/web-gateway.yaml b/infra/k8s/services/web-gateway.yaml index a5c816f6f..cba9d29d9 100644 --- a/infra/k8s/services/web-gateway.yaml +++ b/infra/k8s/services/web-gateway.yaml @@ -17,10 +17,10 @@ spec: app.kubernetes.io/name: web-gateway spec: imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: web-gateway - image: rg.fr-par.scw.cloud/foxhunt/web-gateway:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/web-gateway:latest ports: - containerPort: 3000 name: http diff --git a/infra/live/production/block-storage/terragrunt.hcl b/infra/live/production/block-storage/terragrunt.hcl index 4fa330572..84ede5bdf 100644 --- a/infra/live/production/block-storage/terragrunt.hcl +++ b/infra/live/production/block-storage/terragrunt.hcl @@ -1,5 +1,5 @@ include "root" { - path = find_in_parent_folders() + path = find_in_parent_folders("root.hcl") } terraform { diff --git a/infra/live/production/ci-runner/terragrunt.hcl b/infra/live/production/ci-runner/terragrunt.hcl index 72fbc9f86..896c4daaa 100644 --- a/infra/live/production/ci-runner/terragrunt.hcl +++ b/infra/live/production/ci-runner/terragrunt.hcl @@ -1,5 +1,5 @@ include "root" { - path = find_in_parent_folders() + path = find_in_parent_folders("root.hcl") } terraform { diff --git a/infra/live/production/dns/terragrunt.hcl b/infra/live/production/dns/terragrunt.hcl new file mode 100644 index 000000000..e9e9e3ff7 --- /dev/null +++ b/infra/live/production/dns/terragrunt.hcl @@ -0,0 +1,13 @@ +include "root" { + path = find_in_parent_folders("root.hcl") +} + +terraform { + source = "../../../modules/dns" +} + +inputs = { + dns_zone = "fxhnt.ai" + # Tailscale IP of foxhunt-gitlab proxy pod + git_ip = "100.90.76.85" +} diff --git a/infra/live/production/kapsule/terragrunt.hcl b/infra/live/production/kapsule/terragrunt.hcl index 81a34817c..acb6977be 100644 --- a/infra/live/production/kapsule/terragrunt.hcl +++ b/infra/live/production/kapsule/terragrunt.hcl @@ -1,5 +1,5 @@ include "root" { - path = find_in_parent_folders() + path = find_in_parent_folders("root.hcl") } terraform { @@ -9,6 +9,7 @@ terraform { inputs = { cluster_name = "foxhunt" k8s_version = "1.34" + always_on_type = "DEV1-M" # GPU training pool (H100 for 10-model ensemble training) enable_gpu_training_pool = true @@ -19,4 +20,13 @@ inputs = { enable_gpu_inference_pool = true gpu_inference_type = "L4-1-24G" gpu_inference_max_size = 1 + + # GitLab CE pool (dedicated for GitLab + Runner manager) + enable_gitlab_pool = true + gitlab_type = "DEV1-L" + + # CI build pool (ephemeral runner pods, scale-to-zero) + enable_ci_build_pool = true + ci_build_type = "GP1-XS" + ci_build_max_size = 2 } diff --git a/infra/live/production/object-storage/terragrunt.hcl b/infra/live/production/object-storage/terragrunt.hcl index 502a76939..7009e3a85 100644 --- a/infra/live/production/object-storage/terragrunt.hcl +++ b/infra/live/production/object-storage/terragrunt.hcl @@ -1,5 +1,5 @@ include "root" { - path = find_in_parent_folders() + path = find_in_parent_folders("root.hcl") } terraform { diff --git a/infra/live/production/registry/terragrunt.hcl b/infra/live/production/registry/terragrunt.hcl index 7447e12ae..ddf00b64d 100644 --- a/infra/live/production/registry/terragrunt.hcl +++ b/infra/live/production/registry/terragrunt.hcl @@ -1,5 +1,5 @@ include "root" { - path = find_in_parent_folders() + path = find_in_parent_folders("root.hcl") } terraform { diff --git a/infra/live/production/terragrunt.hcl b/infra/live/production/root.hcl similarity index 100% rename from infra/live/production/terragrunt.hcl rename to infra/live/production/root.hcl diff --git a/infra/live/production/secrets/terragrunt.hcl b/infra/live/production/secrets/terragrunt.hcl index 68060aafc..d4769c17c 100644 --- a/infra/live/production/secrets/terragrunt.hcl +++ b/infra/live/production/secrets/terragrunt.hcl @@ -1,5 +1,5 @@ include "root" { - path = find_in_parent_folders() + path = find_in_parent_folders("root.hcl") } terraform { diff --git a/infra/modules/dns/main.tf b/infra/modules/dns/main.tf new file mode 100644 index 000000000..f6165e2b6 --- /dev/null +++ b/infra/modules/dns/main.tf @@ -0,0 +1,9 @@ +# DNS records for fxhnt.ai zone (Scaleway DNS) + +resource "scaleway_domain_record" "git" { + dns_zone = var.dns_zone + name = "git" + type = "A" + data = var.git_ip + ttl = 300 +} diff --git a/infra/modules/dns/outputs.tf b/infra/modules/dns/outputs.tf new file mode 100644 index 000000000..9473c4078 --- /dev/null +++ b/infra/modules/dns/outputs.tf @@ -0,0 +1,4 @@ +output "git_fqdn" { + description = "FQDN for the GitLab instance" + value = "${scaleway_domain_record.git.name}.${scaleway_domain_record.git.dns_zone}" +} diff --git a/infra/modules/dns/variables.tf b/infra/modules/dns/variables.tf new file mode 100644 index 000000000..fcb240a95 --- /dev/null +++ b/infra/modules/dns/variables.tf @@ -0,0 +1,10 @@ +variable "dns_zone" { + description = "DNS zone to manage records in" + type = string + default = "fxhnt.ai" +} + +variable "git_ip" { + description = "IP address for git.fxhnt.ai (Tailscale subnet router or ClusterIP)" + type = string +} diff --git a/infra/modules/kapsule/main.tf b/infra/modules/kapsule/main.tf index 2c826d260..e1849af30 100644 --- a/infra/modules/kapsule/main.tf +++ b/infra/modules/kapsule/main.tf @@ -90,3 +90,35 @@ resource "scaleway_k8s_pool" "gpu_inference" { ignore_changes = [size] } } + +# GitLab CE node pool (dedicated DEV1-L for GitLab + Runner manager) +resource "scaleway_k8s_pool" "gitlab" { + count = var.enable_gitlab_pool ? 1 : 0 + cluster_id = scaleway_k8s_cluster.foxhunt.id + name = "gitlab" + node_type = var.gitlab_type + size = 1 + min_size = 1 + max_size = 1 + autoscaling = false + autohealing = true + region = var.region +} + +# CI build pod pool (scale-to-zero for ephemeral GitLab Runner build pods) +resource "scaleway_k8s_pool" "ci_build" { + count = var.enable_ci_build_pool ? 1 : 0 + cluster_id = scaleway_k8s_cluster.foxhunt.id + name = "ci-build" + node_type = var.ci_build_type + size = 0 + min_size = 0 + max_size = var.ci_build_max_size + autoscaling = true + autohealing = true + region = var.region + + lifecycle { + ignore_changes = [size] + } +} diff --git a/infra/modules/kapsule/outputs.tf b/infra/modules/kapsule/outputs.tf index 1710d1404..312da09e3 100644 --- a/infra/modules/kapsule/outputs.tf +++ b/infra/modules/kapsule/outputs.tf @@ -33,3 +33,13 @@ output "gpu_inference_pool_id" { description = "ID of the GPU inference node pool" value = var.enable_gpu_inference_pool ? scaleway_k8s_pool.gpu_inference[0].id : "" } + +output "gitlab_pool_id" { + description = "ID of the GitLab node pool" + value = var.enable_gitlab_pool ? scaleway_k8s_pool.gitlab[0].id : "" +} + +output "ci_build_pool_id" { + description = "ID of the CI build node pool" + value = var.enable_ci_build_pool ? scaleway_k8s_pool.ci_build[0].id : "" +} diff --git a/infra/modules/kapsule/variables.tf b/infra/modules/kapsule/variables.tf index 631ed2a58..88ec23e6b 100644 --- a/infra/modules/kapsule/variables.tf +++ b/infra/modules/kapsule/variables.tf @@ -68,3 +68,33 @@ variable "gpu_inference_max_size" { type = number default = 1 } + +variable "enable_gitlab_pool" { + description = "Create dedicated node pool for GitLab CE" + type = bool + default = false +} + +variable "gitlab_type" { + description = "Instance type for the GitLab node pool" + type = string + default = "DEV1-L" +} + +variable "enable_ci_build_pool" { + description = "Create dedicated node pool for CI build pods" + type = bool + default = false +} + +variable "ci_build_type" { + description = "Instance type for the CI build node pool" + type = string + default = "GP1-XS" +} + +variable "ci_build_max_size" { + description = "Maximum number of nodes in the CI build pool" + type = number + default = 2 +} diff --git a/infra/modules/object-storage/main.tf b/infra/modules/object-storage/main.tf index a1fc880b7..1d4c386d0 100644 --- a/infra/modules/object-storage/main.tf +++ b/infra/modules/object-storage/main.tf @@ -15,3 +15,30 @@ resource "scaleway_object_bucket" "artifacts" { enabled = false } } + +resource "scaleway_object_bucket" "gitlab_registry" { + name = "${var.bucket_name_prefix}-gitlab-registry" + region = var.region + + versioning { + enabled = false + } +} + +resource "scaleway_object_bucket" "gitlab_artifacts" { + name = "${var.bucket_name_prefix}-gitlab-artifacts" + region = var.region + + lifecycle_rule { + enabled = true + prefix = "tmp/" + + expiration { + days = 7 + } + } + + versioning { + enabled = false + } +} diff --git a/infra/modules/object-storage/outputs.tf b/infra/modules/object-storage/outputs.tf index 7df47b660..35d7c88bd 100644 --- a/infra/modules/object-storage/outputs.tf +++ b/infra/modules/object-storage/outputs.tf @@ -7,3 +7,18 @@ output "endpoint" { description = "S3-compatible endpoint URL for the bucket region" value = "https://s3.${var.region}.scw.cloud" } + +output "artifacts_bucket_name" { + description = "Name of the main artifacts bucket" + value = scaleway_object_bucket.artifacts.name +} + +output "gitlab_registry_bucket_name" { + description = "Name of the GitLab registry bucket" + value = scaleway_object_bucket.gitlab_registry.name +} + +output "gitlab_artifacts_bucket_name" { + description = "Name of the GitLab artifacts bucket" + value = scaleway_object_bucket.gitlab_artifacts.name +} diff --git a/infra/modules/object-storage/variables.tf b/infra/modules/object-storage/variables.tf index b658d4f41..6a87bb178 100644 --- a/infra/modules/object-storage/variables.tf +++ b/infra/modules/object-storage/variables.tf @@ -8,3 +8,9 @@ variable "bucket_name" { type = string default = "foxhunt-artifacts" } + +variable "bucket_name_prefix" { + description = "Prefix for additional buckets (e.g., foxhunt)" + type = string + default = "foxhunt" +}