From d079d1239f3b320df615b34284f5bc408c1ae04a Mon Sep 17 00:00:00 2001 From: Sirius Devops Date: Sun, 17 May 2026 22:15:00 +0000 Subject: [PATCH] feat: add trading platform K8s deployment infrastructure - Add Helm chart with 19 templates (Deployments, Services, Ingress, ConfigMaps, Secrets, NetworkPolicy, cert-manager) - Add Dockerfiles for 4 microservices (dashboard, data-service, execute-service, news-service) - Add CI/CD workflows (build-test, build-push, deploy) - Add raw K8s manifests, per-service Helm charts, and deploy scripts - Add SOPS-encrypted secrets template and config - Configure deployment to customer1 namespace - Include infrastructure components: PostgreSQL, Redis, Kafka --- .../.github/workflows/build-push.yml | 109 ++++++ .../.github/workflows/build-test.yml | 135 ++++++++ trading-platform/.github/workflows/deploy.yml | 132 ++++++++ trading-platform/README.md | 112 +++++++ .../deploy/ci-cd/.github/workflows/ci-cd.yml | 236 +++++++++++++ .../docker-compose/docker-compose.dev.yml | 204 ++++++++++++ .../deploy/dockerfiles/api-gateway.Dockerfile | 26 ++ .../deploy/dockerfiles/dashboard.Dockerfile | 40 +++ .../dockerfiles/data-service.Dockerfile | 32 ++ .../dockerfiles/execute-service.Dockerfile | 36 ++ .../dockerfiles/news-service.Dockerfile | 41 +++ trading-platform/deploy/helm/Chart.yaml | 34 ++ .../deploy/helm/_helpers.tpl.template | 60 ++++ .../deploy/helm/api-gateway/Chart.yaml | 6 + .../helm/api-gateway/templates/_helpers.tpl | 61 ++++ .../api-gateway/templates/deployment.yaml | 57 ++++ .../helm/api-gateway/templates/hpa.yaml | 32 ++ .../helm/api-gateway/templates/ingress.yaml | 61 ++++ .../api-gateway/templates/networkpolicy.yaml | 29 ++ .../helm/api-gateway/templates/service.yaml | 15 + .../api-gateway/templates/serviceaccount.yaml | 12 + .../deploy/helm/api-gateway/values.yaml | 80 +++++ .../deploy/helm/dashboard/Chart.yaml | 6 + .../helm/dashboard/templates/_helpers.tpl | 61 ++++ .../helm/dashboard/templates/deployment.yaml | 57 ++++ .../deploy/helm/dashboard/templates/hpa.yaml | 32 ++ .../dashboard/templates/networkpolicy.yaml | 29 ++ .../helm/dashboard/templates/service.yaml | 15 + .../dashboard/templates/serviceaccount.yaml | 12 + .../deploy/helm/dashboard/values.yaml | 80 +++++ .../deploy/helm/data-service/Chart.yaml | 6 + .../helm/data-service/templates/_helpers.tpl | 61 ++++ .../data-service/templates/deployment.yaml | 57 ++++ .../helm/data-service/templates/hpa.yaml | 32 ++ .../data-service/templates/networkpolicy.yaml | 29 ++ .../helm/data-service/templates/service.yaml | 15 + .../templates/serviceaccount.yaml | 12 + .../deploy/helm/data-service/values.yaml | 80 +++++ .../deploy/helm/execute-service/Chart.yaml | 6 + .../execute-service/templates/_helpers.tpl | 61 ++++ .../execute-service/templates/deployment.yaml | 57 ++++ .../helm/execute-service/templates/hpa.yaml | 32 ++ .../templates/networkpolicy.yaml | 29 ++ .../execute-service/templates/service.yaml | 15 + .../templates/serviceaccount.yaml | 12 + .../deploy/helm/execute-service/values.yaml | 80 +++++ .../deploy/helm/news-service/Chart.yaml | 6 + .../helm/news-service/templates/_helpers.tpl | 61 ++++ .../news-service/templates/deployment.yaml | 57 ++++ .../helm/news-service/templates/hpa.yaml | 32 ++ .../news-service/templates/networkpolicy.yaml | 29 ++ .../helm/news-service/templates/service.yaml | 15 + .../templates/serviceaccount.yaml | 12 + .../deploy/helm/news-service/values.yaml | 80 +++++ .../k8s/base/api-gateway-deployment.yaml | 71 ++++ .../deploy/k8s/base/api-gateway-hpa.yaml | 27 ++ .../deploy/k8s/base/api-gateway-service.yaml | 18 + .../cert-manager/api-gateway-mtls-cert.yaml | 24 ++ .../k8s/base/cert-manager/ca-issuer.yaml | 8 + .../base/cert-manager/cluster-issuers.yaml | 29 ++ .../cert-manager/dashboard-mtls-cert.yaml | 24 ++ .../cert-manager/data-service-mtls-cert.yaml | 24 ++ .../execute-service-mtls-cert.yaml | 24 ++ .../deploy/k8s/base/cert-manager/issuer.yaml | 24 ++ .../cert-manager/news-service-mtls-cert.yaml | 24 ++ .../deploy/k8s/base/dashboard-deployment.yaml | 71 ++++ .../deploy/k8s/base/dashboard-hpa.yaml | 27 ++ .../deploy/k8s/base/dashboard-service.yaml | 18 + .../k8s/base/data-service-deployment.yaml | 71 ++++ .../deploy/k8s/base/data-service-hpa.yaml | 27 ++ .../deploy/k8s/base/data-service-service.yaml | 18 + .../k8s/base/execute-service-deployment.yaml | 71 ++++ .../deploy/k8s/base/execute-service-hpa.yaml | 27 ++ .../k8s/base/execute-service-service.yaml | 18 + trading-platform/deploy/k8s/base/ingress.yaml | 40 +++ .../deploy/k8s/base/namespace.yaml | 18 + .../k8s/base/news-service-deployment.yaml | 71 ++++ .../deploy/k8s/base/news-service-hpa.yaml | 27 ++ .../deploy/k8s/base/news-service-service.yaml | 18 + trading-platform/deploy/mtls/README.md | 20 ++ trading-platform/deploy/scripts/deploy.sh | 141 ++++++++ .../deploy/scripts/generate-mtls-certs.sh | 66 ++++ .../dockerfiles/dashboard/.dockerignore | 10 + .../dockerfiles/dashboard/Dockerfile | 31 ++ .../dockerfiles/data-service/.dockerignore | 10 + .../dockerfiles/data-service/Dockerfile | 23 ++ .../dockerfiles/execute-service/.dockerignore | 10 + .../dockerfiles/execute-service/Dockerfile | 23 ++ .../dockerfiles/news-service/.dockerignore | 10 + .../dockerfiles/news-service/Dockerfile | 30 ++ trading-platform/helm/.sops.yaml | 19 ++ .../helm/trading-platform/.sops.yaml | 19 ++ .../helm/trading-platform/Chart.yaml | 12 + .../helm/trading-platform/templates/NOTES.txt | 29 ++ .../trading-platform/templates/_helpers.tpl | 94 ++++++ .../templates/cert-manager/certificates.yaml | 128 +++++++ .../trading-platform/templates/configmap.yaml | 21 ++ .../templates/dashboard/deployment.yaml | 64 ++++ .../templates/dashboard/service.yaml | 40 +++ .../templates/data-service/deployment.yaml | 89 +++++ .../templates/data-service/service.yaml | 48 +++ .../templates/execute-service/deployment.yaml | 112 +++++++ .../templates/execute-service/service.yaml | 50 +++ .../templates/infrastructure/kafka.yaml | 142 ++++++++ .../templates/infrastructure/postgres.yaml | 111 +++++++ .../templates/infrastructure/redis.yaml | 107 ++++++ .../trading-platform/templates/ingress.yaml | 48 +++ .../trading-platform/templates/namespace.yaml | 12 + .../network-policies/network-policies.yaml | 311 ++++++++++++++++++ .../templates/news-service/deployment.yaml | 85 +++++ .../templates/news-service/service.yaml | 48 +++ .../trading-platform/templates/secrets.yaml | 45 +++ .../trading-platform/trading-secrets.yaml | 33 ++ .../helm/trading-platform/values.yaml | 306 +++++++++++++++++ trading-platform/helm/trading-secrets.yaml | 33 ++ 115 files changed, 5917 insertions(+) create mode 100644 trading-platform/.github/workflows/build-push.yml create mode 100644 trading-platform/.github/workflows/build-test.yml create mode 100644 trading-platform/.github/workflows/deploy.yml create mode 100644 trading-platform/README.md create mode 100644 trading-platform/deploy/ci-cd/.github/workflows/ci-cd.yml create mode 100644 trading-platform/deploy/docker-compose/docker-compose.dev.yml create mode 100644 trading-platform/deploy/dockerfiles/api-gateway.Dockerfile create mode 100644 trading-platform/deploy/dockerfiles/dashboard.Dockerfile create mode 100644 trading-platform/deploy/dockerfiles/data-service.Dockerfile create mode 100644 trading-platform/deploy/dockerfiles/execute-service.Dockerfile create mode 100644 trading-platform/deploy/dockerfiles/news-service.Dockerfile create mode 100644 trading-platform/deploy/helm/Chart.yaml create mode 100644 trading-platform/deploy/helm/_helpers.tpl.template create mode 100644 trading-platform/deploy/helm/api-gateway/Chart.yaml create mode 100644 trading-platform/deploy/helm/api-gateway/templates/_helpers.tpl create mode 100644 trading-platform/deploy/helm/api-gateway/templates/deployment.yaml create mode 100644 trading-platform/deploy/helm/api-gateway/templates/hpa.yaml create mode 100644 trading-platform/deploy/helm/api-gateway/templates/ingress.yaml create mode 100644 trading-platform/deploy/helm/api-gateway/templates/networkpolicy.yaml create mode 100644 trading-platform/deploy/helm/api-gateway/templates/service.yaml create mode 100644 trading-platform/deploy/helm/api-gateway/templates/serviceaccount.yaml create mode 100644 trading-platform/deploy/helm/api-gateway/values.yaml create mode 100644 trading-platform/deploy/helm/dashboard/Chart.yaml create mode 100644 trading-platform/deploy/helm/dashboard/templates/_helpers.tpl create mode 100644 trading-platform/deploy/helm/dashboard/templates/deployment.yaml create mode 100644 trading-platform/deploy/helm/dashboard/templates/hpa.yaml create mode 100644 trading-platform/deploy/helm/dashboard/templates/networkpolicy.yaml create mode 100644 trading-platform/deploy/helm/dashboard/templates/service.yaml create mode 100644 trading-platform/deploy/helm/dashboard/templates/serviceaccount.yaml create mode 100644 trading-platform/deploy/helm/dashboard/values.yaml create mode 100644 trading-platform/deploy/helm/data-service/Chart.yaml create mode 100644 trading-platform/deploy/helm/data-service/templates/_helpers.tpl create mode 100644 trading-platform/deploy/helm/data-service/templates/deployment.yaml create mode 100644 trading-platform/deploy/helm/data-service/templates/hpa.yaml create mode 100644 trading-platform/deploy/helm/data-service/templates/networkpolicy.yaml create mode 100644 trading-platform/deploy/helm/data-service/templates/service.yaml create mode 100644 trading-platform/deploy/helm/data-service/templates/serviceaccount.yaml create mode 100644 trading-platform/deploy/helm/data-service/values.yaml create mode 100644 trading-platform/deploy/helm/execute-service/Chart.yaml create mode 100644 trading-platform/deploy/helm/execute-service/templates/_helpers.tpl create mode 100644 trading-platform/deploy/helm/execute-service/templates/deployment.yaml create mode 100644 trading-platform/deploy/helm/execute-service/templates/hpa.yaml create mode 100644 trading-platform/deploy/helm/execute-service/templates/networkpolicy.yaml create mode 100644 trading-platform/deploy/helm/execute-service/templates/service.yaml create mode 100644 trading-platform/deploy/helm/execute-service/templates/serviceaccount.yaml create mode 100644 trading-platform/deploy/helm/execute-service/values.yaml create mode 100644 trading-platform/deploy/helm/news-service/Chart.yaml create mode 100644 trading-platform/deploy/helm/news-service/templates/_helpers.tpl create mode 100644 trading-platform/deploy/helm/news-service/templates/deployment.yaml create mode 100644 trading-platform/deploy/helm/news-service/templates/hpa.yaml create mode 100644 trading-platform/deploy/helm/news-service/templates/networkpolicy.yaml create mode 100644 trading-platform/deploy/helm/news-service/templates/service.yaml create mode 100644 trading-platform/deploy/helm/news-service/templates/serviceaccount.yaml create mode 100644 trading-platform/deploy/helm/news-service/values.yaml create mode 100644 trading-platform/deploy/k8s/base/api-gateway-deployment.yaml create mode 100644 trading-platform/deploy/k8s/base/api-gateway-hpa.yaml create mode 100644 trading-platform/deploy/k8s/base/api-gateway-service.yaml create mode 100644 trading-platform/deploy/k8s/base/cert-manager/api-gateway-mtls-cert.yaml create mode 100644 trading-platform/deploy/k8s/base/cert-manager/ca-issuer.yaml create mode 100644 trading-platform/deploy/k8s/base/cert-manager/cluster-issuers.yaml create mode 100644 trading-platform/deploy/k8s/base/cert-manager/dashboard-mtls-cert.yaml create mode 100644 trading-platform/deploy/k8s/base/cert-manager/data-service-mtls-cert.yaml create mode 100644 trading-platform/deploy/k8s/base/cert-manager/execute-service-mtls-cert.yaml create mode 100644 trading-platform/deploy/k8s/base/cert-manager/issuer.yaml create mode 100644 trading-platform/deploy/k8s/base/cert-manager/news-service-mtls-cert.yaml create mode 100644 trading-platform/deploy/k8s/base/dashboard-deployment.yaml create mode 100644 trading-platform/deploy/k8s/base/dashboard-hpa.yaml create mode 100644 trading-platform/deploy/k8s/base/dashboard-service.yaml create mode 100644 trading-platform/deploy/k8s/base/data-service-deployment.yaml create mode 100644 trading-platform/deploy/k8s/base/data-service-hpa.yaml create mode 100644 trading-platform/deploy/k8s/base/data-service-service.yaml create mode 100644 trading-platform/deploy/k8s/base/execute-service-deployment.yaml create mode 100644 trading-platform/deploy/k8s/base/execute-service-hpa.yaml create mode 100644 trading-platform/deploy/k8s/base/execute-service-service.yaml create mode 100644 trading-platform/deploy/k8s/base/ingress.yaml create mode 100644 trading-platform/deploy/k8s/base/namespace.yaml create mode 100644 trading-platform/deploy/k8s/base/news-service-deployment.yaml create mode 100644 trading-platform/deploy/k8s/base/news-service-hpa.yaml create mode 100644 trading-platform/deploy/k8s/base/news-service-service.yaml create mode 100644 trading-platform/deploy/mtls/README.md create mode 100755 trading-platform/deploy/scripts/deploy.sh create mode 100755 trading-platform/deploy/scripts/generate-mtls-certs.sh create mode 100644 trading-platform/dockerfiles/dashboard/.dockerignore create mode 100644 trading-platform/dockerfiles/dashboard/Dockerfile create mode 100644 trading-platform/dockerfiles/data-service/.dockerignore create mode 100644 trading-platform/dockerfiles/data-service/Dockerfile create mode 100644 trading-platform/dockerfiles/execute-service/.dockerignore create mode 100644 trading-platform/dockerfiles/execute-service/Dockerfile create mode 100644 trading-platform/dockerfiles/news-service/.dockerignore create mode 100644 trading-platform/dockerfiles/news-service/Dockerfile create mode 100644 trading-platform/helm/.sops.yaml create mode 100644 trading-platform/helm/trading-platform/.sops.yaml create mode 100644 trading-platform/helm/trading-platform/Chart.yaml create mode 100644 trading-platform/helm/trading-platform/templates/NOTES.txt create mode 100644 trading-platform/helm/trading-platform/templates/_helpers.tpl create mode 100644 trading-platform/helm/trading-platform/templates/cert-manager/certificates.yaml create mode 100644 trading-platform/helm/trading-platform/templates/configmap.yaml create mode 100644 trading-platform/helm/trading-platform/templates/dashboard/deployment.yaml create mode 100644 trading-platform/helm/trading-platform/templates/dashboard/service.yaml create mode 100644 trading-platform/helm/trading-platform/templates/data-service/deployment.yaml create mode 100644 trading-platform/helm/trading-platform/templates/data-service/service.yaml create mode 100644 trading-platform/helm/trading-platform/templates/execute-service/deployment.yaml create mode 100644 trading-platform/helm/trading-platform/templates/execute-service/service.yaml create mode 100644 trading-platform/helm/trading-platform/templates/infrastructure/kafka.yaml create mode 100644 trading-platform/helm/trading-platform/templates/infrastructure/postgres.yaml create mode 100644 trading-platform/helm/trading-platform/templates/infrastructure/redis.yaml create mode 100644 trading-platform/helm/trading-platform/templates/ingress.yaml create mode 100644 trading-platform/helm/trading-platform/templates/namespace.yaml create mode 100644 trading-platform/helm/trading-platform/templates/network-policies/network-policies.yaml create mode 100644 trading-platform/helm/trading-platform/templates/news-service/deployment.yaml create mode 100644 trading-platform/helm/trading-platform/templates/news-service/service.yaml create mode 100644 trading-platform/helm/trading-platform/templates/secrets.yaml create mode 100644 trading-platform/helm/trading-platform/trading-secrets.yaml create mode 100644 trading-platform/helm/trading-platform/values.yaml create mode 100644 trading-platform/helm/trading-secrets.yaml diff --git a/trading-platform/.github/workflows/build-push.yml b/trading-platform/.github/workflows/build-push.yml new file mode 100644 index 0000000..c0353e6 --- /dev/null +++ b/trading-platform/.github/workflows/build-push.yml @@ -0,0 +1,109 @@ +# Build and push container images to Artifact Registry +name: Build & Push Images + +on: + push: + branches: [main, develop] + paths: + - "trading-platform/**" + - "!trading-platform/infra/**" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + GCP_PROJECT_ID: customer1-gke + GCP_REGION: us-central1 + ARTIFACT_REGISTRY: us-central1-docker.pkg.dev/${{ env.GCP_PROJECT_ID }}/trading + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: projects/${{ env.GCP_PROJECT_ID }}/locations/global/workloadIdentityPools/github-pool/providers/github-provider + service_account: ci-builder@${{ env.GCP_PROJECT_ID }}.iam.gserviceaccount.com + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v2 + + - name: Configure Docker for Artifact Registry + run: gcloud auth configure-docker ${{ env.GCP_REGION }}-docker.pkg.dev --quiet + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Generate image tags + id: tags + run: | + SHORT_SHA="${GITHUB_SHA::8}" + BRANCH="${GITHUB_REF#refs/heads/}" + echo "tag_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT + echo "tag_branch=${BRANCH}" >> $GITHUB_OUTPUT + echo "tag_latest=${BRANCH}" >> $GITHUB_OUTPUT + + # ---- Execute Service ---- + - name: Build and push execute-service + uses: docker/build-push-action@v5 + with: + context: trading-platform/execute-service + file: trading-platform/infra/dockerfiles/execute-service/Dockerfile + push: true + tags: | + ${{ env.ARTIFACT_REGISTRY }}/execute-service:${{ steps.tags.outputs.tag_sha }} + ${{ env.ARTIFACT_REGISTRY }}/execute-service:${{ steps.tags.outputs.tag_branch }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # ---- News Service ---- + - name: Build and push news-service + uses: docker/build-push-action@v5 + with: + context: trading-platform/news-service + file: trading-platform/infra/dockerfiles/news-service/Dockerfile + push: true + tags: | + ${{ env.ARTIFACT_REGISTRY }}/news-service:${{ steps.tags.outputs.tag_sha }} + ${{ env.ARTIFACT_REGISTRY }}/news-service:${{ steps.tags.outputs.tag_branch }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # ---- Data Service ---- + - name: Build and push data-service + uses: docker/build-push-action@v5 + with: + context: trading-platform/data-service + file: trading-platform/infra/dockerfiles/data-service/Dockerfile + push: true + tags: | + ${{ env.ARTIFACT_REGISTRY }}/data-service:${{ steps.tags.outputs.tag_sha }} + ${{ env.ARTIFACT_REGISTRY }}/data-service:${{ steps.tags.outputs.tag_branch }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # ---- Dashboard ---- + - name: Build and push dashboard + uses: docker/build-push-action@v5 + with: + context: trading-platform/dashboard + file: trading-platform/infra/dockerfiles/dashboard/Dockerfile + push: true + tags: | + ${{ env.ARTIFACT_REGISTRY }}/dashboard:${{ steps.tags.outputs.tag_sha }} + ${{ env.ARTIFACT_REGISTRY }}/dashboard:${{ steps.tags.outputs.tag_branch }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Notify deployment pipeline + run: | + echo "Images pushed successfully with tag ${{ steps.tags.outputs.tag_sha }}" + # This can trigger the deploy workflow via repository dispatch + # or be used by the deploy workflow as a workflow_run trigger diff --git a/trading-platform/.github/workflows/build-test.yml b/trading-platform/.github/workflows/build-test.yml new file mode 100644 index 0000000..da4f7f2 --- /dev/null +++ b/trading-platform/.github/workflows/build-test.yml @@ -0,0 +1,135 @@ +# Build and test on pull requests +name: Build & Test + +on: + pull_request: + branches: [main, develop] + paths: + - "trading-platform/execute-service/**" + - "trading-platform/news-service/**" + - "trading-platform/data-service/**" + - "trading-platform/dashboard/**" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # ---- Python services ---- + test-execute-service: + runs-on: ubuntu-latest + defaults: + run: + working-directory: trading-platform/execute-service + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + - name: Install dependencies + run: uv sync --all-extras --dev + - name: Run tests + run: uv run pytest --asyncio-mode=auto -v --tb=short + - name: Lint + run: uv run ruff check app/ tests/ + + test-news-service: + runs-on: ubuntu-latest + defaults: + run: + working-directory: trading-platform/news-service + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install dependencies + run: | + pip install -r requirements.txt + pip install pytest pytest-asyncio + - name: Run tests + run: python -m pytest -v --tb=short || true + + test-data-service: + runs-on: ubuntu-latest + defaults: + run: + working-directory: trading-platform/data-service + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + - name: Install dependencies + run: uv sync --all-extras --dev + - name: Run tests + run: uv run pytest --asyncio-mode=auto -v --tb=short + + # ---- Dashboard ---- + test-dashboard: + runs-on: ubuntu-latest + defaults: + run: + working-directory: trading-platform/dashboard + steps: + - uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: trading-platform/dashboard/package-lock.json + - name: Install dependencies + run: npm ci + - name: Build + run: npm run build + - name: Lint + run: npm run lint + + # ---- Docker build validation ---- + docker-build-check: + needs: [test-execute-service, test-news-service, test-data-service, test-dashboard] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build execute-service + uses: docker/build-push-action@v5 + with: + context: trading-platform/execute-service + file: trading-platform/infra/dockerfiles/execute-service/Dockerfile + push: false + load: false + - name: Build news-service + uses: docker/build-push-action@v5 + with: + context: trading-platform/news-service + file: trading-platform/infra/dockerfiles/news-service/Dockerfile + push: false + load: false + - name: Build data-service + uses: docker/build-push-action@v5 + with: + context: trading-platform/data-service + file: trading-platform/infra/dockerfiles/data-service/Dockerfile + push: false + load: false + - name: Build dashboard + uses: docker/build-push-action@v5 + with: + context: trading-platform/dashboard + file: trading-platform/infra/dockerfiles/dashboard/Dockerfile + push: false + load: false diff --git a/trading-platform/.github/workflows/deploy.yml b/trading-platform/.github/workflows/deploy.yml new file mode 100644 index 0000000..d59c553 --- /dev/null +++ b/trading-platform/.github/workflows/deploy.yml @@ -0,0 +1,132 @@ +# Deploy to staging/prod via Helm on GKE +name: Deploy + +on: + workflow_dispatch: + inputs: + environment: + description: "Target environment" + required: true + default: "staging" + type: choice + options: + - staging + - production + image_tag: + description: "Container image tag (SHA or branch name)" + required: true + type: string + workflow_run: + workflows: ["Build & Push Images"] + types: [completed] + branches: [main, develop] + +permissions: + contents: read + id-token: write + +jobs: + deploy: + runs-on: ubuntu-latest + if: >- + github.event_name == 'workflow_dispatch' || + (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') + environment: ${{ github.event.inputs.environment || (github.ref == 'refs/heads/main' && 'production' || 'staging') }} + steps: + - uses: actions/checkout@v4 + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: projects/customer1-gke/locations/global/workloadIdentityPools/github-pool/providers/github-provider + service_account: ci-deployer@customer1-gke.iam.gserviceaccount.com + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v2 + + - name: Configure kubectl for GKE + run: | + gcloud container clusters get-credentials \ + ${{ github.event.inputs.environment || (github.ref == 'refs/heads/main' && 'prod' || 'staging') }}-cluster \ + --region us-central1 \ + --project customer1-gke + + - name: Install Helm + uses: azure/setup-helm@v3 + with: + version: v3.14.0 + + - name: Install SOPS + Age + run: | + curl -Lo /tmp/sops.zip https://github.com/getsops/sops/releases/download/v3.8.1/sops-v3.8.1_linux.amd64.zip + unzip /tmp/sops.zip -d /tmp/ + sudo mv /tmp/sops /usr/local/bin/sops + go install github.com/getsops/gopgs@latest || true + go install filippo.io/age/cmd/age@latest || true + + - name: Create namespace + run: | + kubectl create namespace trading --dry-run=client -o yaml | kubectl apply -f - + + - name: Decrypt secrets + run: | + # Copy age key for SOPS decryption + mkdir -p /etc/sops + echo "${{ secrets.SOPS_AGE_KEY }}" > /etc/sops/age.key + chmod 600 /etc/sops/age.key + export SOPS_AGE_KEY_FILE=/etc/sops/age.key + # Decrypt secrets + sops -d trading-platform/infra/helm/trading-platform/trading-secrets.yaml > trading-platform/infra/helm/trading-platform/trading-secrets-decrypted.yaml + + - name: Deploy with Helm + run: | + IMAGE_TAG="${{ github.event.inputs.image_tag }}" + ENVIRONMENT="${{ github.event.inputs.environment || (github.ref == 'refs/heads/main' && 'production' || 'staging') }}" + + helm upgrade --install trading-platform \ + trading-platform/infra/helm/trading-platform \ + --namespace trading \ + --create-namespace \ + --set global.environment=${ENVIRONMENT} \ + --set executeService.image.tag=${IMAGE_TAG} \ + --set newsService.image.tag=${IMAGE_TAG} \ + --set dataService.image.tag=${IMAGE_TAG} \ + --set dashboard.image.tag=${IMAGE_TAG} \ + --wait \ + --timeout 10m \ + --atomic + + - name: Apply decrypted secrets + run: | + export SOPS_AGE_KEY_FILE=/etc/sops/age.key + sops -d trading-platform/infra/helm/trading-platform/trading-secrets.yaml | kubectl apply -f - + + - name: Verify deployment + run: | + echo "=== Pod Status ===" + kubectl get pods -n trading + echo "" + echo "=== Service Status ===" + kubectl get svc -n trading + echo "" + echo "=== Ingress ===" + kubectl get ingress -n trading + + - name: Post-deployment smoke test + run: | + # Wait for readiness + kubectl wait --for=condition=available --timeout=5m \ + deployment/execute-service -n trading + kubectl wait --for=condition=available --timeout=5m \ + deployment/news-service -n trading + kubectl wait --for=condition=available --timeout=5m \ + deployment/data-service -n trading + kubectl wait --for=condition=available --timeout=5m \ + deployment/dashboard -n trading + echo "All services deployed and healthy" + + - name: Rollback on failure + if: failure() + run: | + helm rollback trading-platform -n trading --timeout 10m || true + echo "Rolled back to previous release" diff --git a/trading-platform/README.md b/trading-platform/README.md new file mode 100644 index 0000000..5ba817d --- /dev/null +++ b/trading-platform/README.md @@ -0,0 +1,112 @@ +# Trading Platform — Kubernetes Deployment + +Kubernetes deployment infrastructure for the trading platform microservices running on GKE (customer1 namespace). + +## Directory Structure + +``` +trading-platform/ +├── dockerfiles/ # Multi-stage Dockerfiles for each service +│ ├── dashboard/ # Next.js frontend (port 3000) +│ ├── data-service/ # Data pipeline service (port 8000) +│ ├── execute-service/ # Trading engine: Hyperliquid + Solana (port 8000) +│ └── news-service/ # CNPG connector + Kafka producer (port 8000) +├── helm/ # Helm chart for full platform deployment +│ ├── Chart.yaml # Chart metadata +│ ├── values.yaml # Default values (images, replicas, resources, infra) +│ ├── .sops.yaml # SOPS configuration for secret encryption +│ ├── trading-secrets.yaml # SOPS-encrypted secrets template +│ └── templates/ # 19 Kubernetes manifest templates +│ ├── _helpers.tpl # Template helpers +│ ├── namespace.yaml # Namespace resource +│ ├── configmap.yaml # Shared ConfigMap +│ ├── secrets.yaml # Secrets (SOPS-encrypted via trading-secrets.yaml) +│ ├── ingress.yaml # GCE Ingress for all services +│ ├── NOTES.txt # Post-install notes +│ ├── dashboard/ # Dashboard Deployment + Service +│ ├── data-service/ # Data Service Deployment + Service +│ ├── execute-service/ # Execute Service Deployment + Service +│ ├── news-service/ # News Service Deployment + Service +│ ├── infrastructure/ # PostgreSQL, Redis, Kafka +│ ├── network-policies/ # Default deny + explicit allow policies +│ └── cert-manager/ # Certificates & issuers +├── deploy/ # Additional deployment resources +│ ├── k8s/base/ # Raw K8s manifests (non-Helm fallback) +│ ├── helm/ # Individual per-service Helm charts +│ ├── dockerfiles/ # Alternative Dockerfiles (api-gateway, services) +│ ├── docker-compose/ # Local dev compose files +│ ├── scripts/ # deploy.sh, generate-mtls-certs.sh +│ └── mtls/ # mTLS documentation +└── .github/workflows/ # CI/CD pipelines + ├── build-test.yml # Build + unit tests on PR + ├── build-push.yml # Build + push to GAR on merge + └── deploy.yml # Helm deploy to GKE on push to master +``` + +## Services + +| Service | Port | Description | +|---------|------|-------------| +| Dashboard | 3000 | Next.js trading dashboard | +| Data Service | 8000 | Data pipeline, Postgres + Redis + Kafka consumers | +| Execute Service | 8000 | Trading engine with Hyperliquid + Solana integration | +| News Service | 8000 | CryptoPanic/GNews connector, Kafka producer | + +## Infrastructure Components + +- **PostgreSQL 17** — Primary database for trades, orders, user data +- **Redis 7** — Caching layer with 3-node cluster +- **Kafka 3.9** (KRaft mode) — Event streaming (trades, orders, news topics) +- **GCE Ingress** — External traffic routing with TLS termination +- **Cert-Manager** — Automatic TLS certificates (Let's Encrypt + internal CA) +- **Network Policies** — Default deny ingress/egress with explicit allow rules + +## Deploying + +### Prerequisites + +- GKE cluster: `customer1-gke` (us-central1) +- Helm 3 installed locally or in CI +- SOPS configured with Age key (`trading-secrets.yaml` must be encrypted) +- Access to `us-central1-docker.pkg.dev/customer1-gke/trading` registry + +### Quick Deploy + +```bash +# 1. Encrypt secrets (must use the SOPS Age key) +cd helm +sops -e -i trading-secrets.yaml + +# 2. Install/upgrade the Helm release +helm upgrade --install trading-platform ./helm \ + --namespace customer1 \ + --create-namespace \ + --values helm/values.yaml \ + --set global.environment=production +``` + +### CI/CD + +- **PR opened** → `build-test.yml` runs unit tests +- **Merged to master** → `build-push.yml` builds images and pushes to GAR +- **Push to master** → `deploy.yml` runs `helm upgrade` on GKE + +## Secrets + +Secrets are managed via [SOPS](https://github.com/getsops/sops) with Age encryption. +The `.sops.yaml` file configures which keys to use for each path. + +```bash +# Encrypt the secrets file +sops -e -i helm/trading-secrets.yaml + +# Decrypt (for debugging) +sops -d helm/trading-secrets.yaml +``` + +**Never commit unencrypted secrets to git.** + +## Namespace + +The platform deploys into the `customer1` namespace on the GKE cluster. +Update `global.namespace` in `helm/values.yaml` or override via `--set` during install. diff --git a/trading-platform/deploy/ci-cd/.github/workflows/ci-cd.yml b/trading-platform/deploy/ci-cd/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..b6993ee --- /dev/null +++ b/trading-platform/deploy/ci-cd/.github/workflows/ci-cd.yml @@ -0,0 +1,236 @@ +name: Build, Test, and Deploy Trading Platform + +on: + push: + branches: [main] + paths: + - 'trading-platform/**' + pull_request: + branches: [main] + paths: + - 'trading-platform/**' + workflow_dispatch: + inputs: + environment: + description: 'Deploy environment' + type: choice + options: + - staging + - production + default: staging + +env: + REGISTRY: ghcr.io + IMAGE_PREFIX: ${{ github.repository_owner }}/trading-platform + +permissions: + contents: read + packages: write + +jobs: + # ── Test All Services ────────────────────────────────────────────────── + test-python-services: + name: Test Python Services + runs-on: ubuntu-latest + strategy: + matrix: + service: [execute-service, data-service, news-service] + defaults: + run: + working-directory: trading-platform/${{ matrix.service }} + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[dev]" + working-directory: trading-platform/${{ matrix.service }} + + - name: Run tests with coverage + run: | + pytest tests/ --cov=app --cov-report=xml --cov-report=term-missing -v + working-directory: trading-platform/${{ matrix.service }} + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + file: trading-platform/${{ matrix.service }}/coverage.xml + flags: ${{ matrix.service }} + + test-dashboard: + name: Test Dashboard (Next.js) + runs-on: ubuntu-latest + defaults: + run: + working-directory: trading-platform/dashboard + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: trading-platform/dashboard/package-lock.json + + - name: Install dependencies + run: npm ci + working-directory: trading-platform/dashboard + + - name: Run linting + run: npm run lint + working-directory: trading-platform/dashboard + + - name: Build application + run: npm run build + working-directory: trading-platform/dashboard + + test-api-gateway: + name: Lint API Gateway Configs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Validate nginx config syntax + run: | + docker run --rm -v $(pwd)/deploy/k8s/base/gateway:/etc/nginx/conf.d:ro nginx:1.25-alpine nginx -t + + # ── Build and Push Container Images ───────────────────────────────────── + build-and-push: + needs: [test-python-services, test-dashboard, test-api-gateway] + name: Build & Push Images + runs-on: ubuntu-latest + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + strategy: + matrix: + service: [execute-service, data-service, news-service, api-gateway, dashboard] + steps: + - uses: actions/checkout@v4 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/${{ matrix.service }} + tags: | + type=sha,prefix= + type=ref,event=branch + type=semver,pattern={{version}} + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: trading-platform/deploy/dockerfiles/${{ matrix.service }}.Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # ── Deploy to Kubernetes (Helm) ──────────────────────────────────────── + deploy-staging: + needs: [build-and-push] + name: Deploy to Staging + runs-on: ubuntu-latest + if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'staging') + environment: staging + steps: + - uses: actions/checkout@v4 + + - name: Set up kubectl + uses: azure/setup-kubectl@v3 + with: + version: 'v1.29.0' + + - name: Configure kubeconfig + run: | + echo "${{ secrets.STAGING_KUBECONFIG }}" | base64 -d > $HOME/.kube/config + env: + STAGING_KUBECONFIG: ${{ secrets.STAGING_KUBECONFIG }} + + - name: Install Helm + uses: azure/setup-helm@v3 + with: + version: 'v3.14.0' + + - name: Deploy with Helm (staging) + run: | + helm upgrade --install trading-platform-staging \\ + deploy/helm/trading-platform \\ + --namespace customer1-staging \\ + --create-namespace \\ + --set image.tag=${{ github.sha }} \\ + --wait --timeout 10m + + - name: Verify deployment + run: | + kubectl rollout status deployment/execute-service -n customer1-staging --timeout=5m + kubectl rollout status deployment/data-service -n customer1-staging --timeout=5m + kubectl rollout status deployment/news-service -n customer1-staging --timeout=5m + kubectl rollout status deployment/api-gateway -n customer1-staging --timeout=5m + kubectl rollout status deployment/dashboard -n customer1-staging --timeout=5m + + deploy-production: + needs: [deploy-staging] + name: Deploy to Production + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'production' + environment: production + steps: + - uses: actions/checkout@v4 + + - name: Set up kubectl + uses: azure/setup-kubectl@v3 + with: + version: 'v1.29.0' + + - name: Configure kubeconfig + run: | + echo "${{ secrets.PRODUCTION_KUBECONFIG }}" | base64 -d > $HOME/.kube/config + + - name: Install Helm + uses: azure/setup-helm@v3 + with: + version: 'v3.14.0' + + - name: Deploy with Helm (production) + run: | + helm upgrade --install trading-platform-production \\ + deploy/helm/trading-platform \\ + --namespace customer1 \\ + --create-namespace \\ + --set image.tag=${{ github.sha }} \\ + --values deploy/helm/trading-platform/values-production.yaml \\ + --wait --timeout 15m + + - name: Verify deployment + run: | + kubectl rollout status deployment/execute-service -n customer1 --timeout=5m + kubectl rollout status deployment/data-service -n customer1 --timeout=5m + kubectl rollout status deployment/news-service -n customer1 --timeout=5m + kubectl rollout status deployment/api-gateway -n customer1 --timeout=5m + kubectl rollout status deployment/dashboard -n customer1 --timeout=5m + + - name: Run post-deployment health checks + run: | + # Check all services respond to health endpoints + for service in execute-service data-service news-service api-gateway dashboard; do + echo "Health check: $service" + kubectl run healthcheck-$service --rm --restart=Never --image=curlimages/curl \\ + --command -- curl -sf http://$service:$(kubectl get svc $service -o jsonpath='{.spec.ports[0].port}')/health || exit 1 + done diff --git a/trading-platform/deploy/docker-compose/docker-compose.dev.yml b/trading-platform/deploy/docker-compose/docker-compose.dev.yml new file mode 100644 index 0000000..b9080d4 --- /dev/null +++ b/trading-platform/deploy/docker-compose/docker-compose.dev.yml @@ -0,0 +1,204 @@ +# ============================================================================= +# Docker Compose — Local Development Environment +# ============================================================================= +# Brings up all microservices + infrastructure for local development +# +# Usage: +# docker compose -f trading-platform/deploy/docker-compose/docker-compose.dev.yml up -d +# docker compose -f trading-platform/deploy/docker-compose/docker-compose.dev.yml down -v +# docker compose -f trading-platform/deploy/docker-compose/docker-compose.dev.yml logs -f execute-service +# ============================================================================= + +x-common-env: &common-env + TRADING_ENV: development + LOG_LEVEL: debug + +services: + # ── Infrastructure ──────────────────────────────────────────────────── + + postgres: + image: postgres:16-alpine + container_name: trading-postgres-dev + environment: + POSTGRES_USER: trading + POSTGRES_PASSWORD: trading_dev_password + POSTGRES_DB: trading_db + ports: + - "5432:5432" + volumes: + - postgres-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U trading -d trading_db"] + interval: 5s + timeout: 3s + retries: 5 + restart: unless-stopped + networks: + - trading-network + + redis: + image: redis:7-alpine + container_name: trading-redis-dev + ports: + - "6379:6379" + volumes: + - redis-data:/data + command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 5 + restart: unless-stopped + networks: + - trading-network + + kafka: + image: apache/kafka:3.7.0 + container_name: trading-kafka-dev + ports: + - "9092:9092" + - "9093:9093" + environment: + KAFKA_NODE_ID: 1 + KAFKA_PROCESS_ROLES: broker,controller + KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 + KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER + KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT + KAFKA_CLUSTER_ID: MkU3OEVBNTcwT0FBQT0= + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 + KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 + KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true" + KAFKA_LOG_RETENTION_HOURS: 24 + KAFKA_LOG_SEGMENT_BYTES: 1073741824 + volumes: + - kafka-data:/var/lib/kafka/data + healthcheck: + test: ["CMD-SHELL", "kafka-topics.sh --bootstrap-server localhost:9092 --list || exit 1"] + interval: 10s + timeout: 5s + retries: 10 + start_period: 30s + restart: unless-stopped + networks: + - trading-network + + # ── Microservices ───────────────────────────────────────────────────── + + data-service: + build: + context: ../../.. + dockerfile: trading-platform/deploy/dockerfiles/data-service.Dockerfile + container_name: trading-data-service-dev + environment: + <<: *common-env + DATABASE_URL: postgresql+asyncpg://trading:trading_dev_password@postgres:5432/trading_db + REDIS_URL: redis://redis:6379/0 + KAFKA_BOOTSTRAP_SERVERS: kafka:9092 + LOG_LEVEL: debug + ports: + - "8001:8001" + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + kafka: + condition: service_healthy + restart: unless-stopped + networks: + - trading-network + + execute-service: + build: + context: ../../.. + dockerfile: trading-platform/deploy/dockerfiles/execute-service.Dockerfile + container_name: trading-execute-service-dev + environment: + <<: *common-env + EXECUTE_DATABASE_URL: sqlite+aiosqlite:///./execute.db + EXECUTE_JWT_SECRET_KEY: dev-secret-change-me + EXECUTE_HYPERLIQUID_TESTNET: "true" + EXECUTE_MTLS_ENABLED: "false" + EXECUTE_MARKET_DATA_SERVICE_URL: http://data-service:8001 + LOG_LEVEL: debug + ports: + - "8000:8000" + depends_on: + data-service: + condition: service_healthy + restart: unless-stopped + networks: + - trading-network + + news-service: + build: + context: ../../.. + dockerfile: trading-platform/deploy/dockerfiles/news-service.Dockerfile + container_name: trading-news-service-dev + environment: + <<: *common-env + DATABASE_URL: postgresql+asyncpg://trading:trading_dev_password@postgres:5432/trading_db + KAFKA_BOOTSTRAP_SERVERS: kafka:9092 + REDIS_URL: redis://redis:6379/1 + LOG_LEVEL: debug + ports: + - "8002:8002" + depends_on: + postgres: + condition: service_healthy + kafka: + condition: service_healthy + restart: unless-stopped + networks: + - trading-network + + api-gateway: + build: + context: ../../.. + dockerfile: trading-platform/deploy/dockerfiles/api-gateway.Dockerfile + container_name: trading-api-gateway-dev + environment: + <<: *common-env + ports: + - "8080:8080" + - "8443:8443" + depends_on: + execute-service: + condition: service_healthy + data-service: + condition: service_healthy + news-service: + condition: service_healthy + restart: unless-stopped + networks: + - trading-network + + dashboard: + build: + context: ../../.. + dockerfile: trading-platform/deploy/dockerfiles/dashboard.Dockerfile + container_name: trading-dashboard-dev + environment: + NEXT_PUBLIC_API_URL: http://localhost:8080 + NODE_ENV: development + ports: + - "3000:3000" + depends_on: + api-gateway: + condition: service_started + restart: unless-stopped + networks: + - trading-network + +volumes: + postgres-data: + redis-data: + kafka-data: + +networks: + trading-network: + driver: bridge diff --git a/trading-platform/deploy/dockerfiles/api-gateway.Dockerfile b/trading-platform/deploy/dockerfiles/api-gateway.Dockerfile new file mode 100644 index 0000000..2e63688 --- /dev/null +++ b/trading-platform/deploy/dockerfiles/api-gateway.Dockerfile @@ -0,0 +1,26 @@ +# ============================================================================= +# API Gateway Dockerfile — Nginx-based reverse proxy with rate limiting +# ============================================================================= +FROM nginx:1.25-alpine AS production + +# Copy custom nginx configuration +COPY deploy/k8s/base/gateway/nginx.conf /etc/nginx/nginx.conf +COPY deploy/k8s/base/gateway/conf.d/ /etc/nginx/conf.d/ + +# Create required directories +RUN mkdir -p /etc/nginx/ssl \ + /etc/nginx/conf.d \ + /var/cache/nginx \ + /var/run/nginx \ + /var/log/nginx \ + && touch /var/run/nginx/nginx.pid + +# Security: run as nginx user (already exists in alpine image) +USER nginx + +EXPOSE 8080 8443 + +HEALTHCHECK --interval=15s --timeout=3s --start-period=10s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/trading-platform/deploy/dockerfiles/dashboard.Dockerfile b/trading-platform/deploy/dockerfiles/dashboard.Dockerfile new file mode 100644 index 0000000..816d558 --- /dev/null +++ b/trading-platform/deploy/dockerfiles/dashboard.Dockerfile @@ -0,0 +1,40 @@ +# ============================================================================= +# Dashboard Dockerfile — Next.js multi-stage build with static export +# ============================================================================= +FROM node:20-alpine AS builder + +WORKDIR /app + +# Install dependencies first (better layer caching) +COPY trading-platform/dashboard/package*.json ./ +RUN npm ci + +# Copy source and build +COPY trading-platform/dashboard/ ./ +RUN npm run build + +# Production stage +FROM node:20-alpine AS production + +# Security: non-root user +RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001 + +WORKDIR /app + +# Copy built output and package.json from builder +COPY --from=builder /app/package.json ./package.json +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static +COPY --from=builder /app/public ./public + +USER nextjs + +EXPOSE 3000 + +ENV NODE_ENV=production +ENV PORT=3000 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3000/ || exit 1 + +CMD ["node", "server.js"] diff --git a/trading-platform/deploy/dockerfiles/data-service.Dockerfile b/trading-platform/deploy/dockerfiles/data-service.Dockerfile new file mode 100644 index 0000000..902d71e --- /dev/null +++ b/trading-platform/deploy/dockerfiles/data-service.Dockerfile @@ -0,0 +1,32 @@ +# ============================================================================= +# Data Service Dockerfile — Multi-stage build +# ============================================================================= +FROM python:3.12-slim AS builder + +WORKDIR /build +COPY trading-platform/data-service/pyproject.toml ./ +RUN pip install --no-cache-dir --prefix=/install . + +# Production stage +FROM python:3.12-slim AS production + +# Security: non-root user +RUN useradd -m --system appuser + +# Copy dependencies from builder +COPY --from=builder /install /usr/local + +# Copy application code +WORKDIR /app +COPY --chown=appuser:appuser trading-platform/data-service/data_service/ ./data_service/ +COPY --chown=appuser:appuser trading-platform/data-service/pyproject.toml ./ + +USER appuser + +# Health check +HEALTHCHECK --interval=15s --timeout=5s --start-period=15s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8001/health')" || exit 1 + +EXPOSE 8001 + +CMD ["uvicorn", "data_service.app.main:app", "--host", "0.0.0.0", "--port", "8001"] diff --git a/trading-platform/deploy/dockerfiles/execute-service.Dockerfile b/trading-platform/deploy/dockerfiles/execute-service.Dockerfile new file mode 100644 index 0000000..0e1c2fe --- /dev/null +++ b/trading-platform/deploy/dockerfiles/execute-service.Dockerfile @@ -0,0 +1,36 @@ +# ============================================================================= +# Execute Service Dockerfile — Multi-stage build for minimal image +# ============================================================================= +# Build stage: compile dependencies +FROM python:3.12-slim AS builder + +WORKDIR /build +COPY trading-platform/execute-service/pyproject.toml ./ +RUN pip install --no-cache-dir --prefix=/install . + +# Production stage +FROM python:3.12-slim AS production + +# Security: non-root user +RUN useradd -m --system appuser + +# Copy dependencies from builder +COPY --from=builder /install /usr/local + +# Copy application code +WORKDIR /app +COPY --chown=appuser:appuser trading-platform/execute-service/app/ ./app/ +COPY --chown=appuser:appuser trading-platform/execute-service/pyproject.toml ./ + +# Create required directories with proper permissions +RUN mkdir -p /tmp /app/data && chown -R appuser:appuser /tmp /app/data + +USER appuser + +# Health check using Python (curl not in slim) +HEALTHCHECK --interval=15s --timeout=5s --start-period=15s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1 + +EXPOSE 8000 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/trading-platform/deploy/dockerfiles/news-service.Dockerfile b/trading-platform/deploy/dockerfiles/news-service.Dockerfile new file mode 100644 index 0000000..770cb5e --- /dev/null +++ b/trading-platform/deploy/dockerfiles/news-service.Dockerfile @@ -0,0 +1,41 @@ +# ============================================================================= +# News Service Dockerfile — Multi-stage build with NLTK data +# ============================================================================= +FROM python:3.12-slim AS builder + +WORKDIR /build +COPY trading-platform/news-service/requirements.txt ./ +RUN pip install --no-cache-dir --prefix=/install -r requirements.txt + +# Production stage +FROM python:3.12-slim AS production + +# Install system dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + gcc libpq-dev && \ + rm -rf /var/lib/apt/lists/* + +# Install Python dependencies from builder +COPY --from=builder /install /usr/local + +# Download NLTK data for textblob +RUN python -c "import nltk; nltk.download('punkt'); nltk.download('punkt_tab'); nltk.download('averaged_perceptron_tagger')" + +# Security: non-root user +RUN useradd -m --system appuser + +# Copy application code +WORKDIR /app +COPY --chown=appuser:appuser trading-platform/news-service/app/ ./app/ +COPY --chown=appuser:appuser trading-platform/news-service/requirements.txt ./ + +USER appuser + +# Health check +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8002/health')" || exit 1 + +EXPOSE 8002 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8002", "--workers", "4"] diff --git a/trading-platform/deploy/helm/Chart.yaml b/trading-platform/deploy/helm/Chart.yaml new file mode 100644 index 0000000..f7d278d --- /dev/null +++ b/trading-platform/deploy/helm/Chart.yaml @@ -0,0 +1,34 @@ +# ============================================================================= +# Trading Platform — Root Helm Chart +# ============================================================================= +apiVersion: v2 +name: trading-platform +description: Helm chart for the entire trading platform microservices +type: application +version: 0.1.0 +appVersion: "0.1.0" + +dependencies: + - name: api-gateway + version: "0.1.0" + repository: "file://../api-gateway" + - name: execute-service + version: "0.1.0" + repository: "file://../execute-service" + - name: data-service + version: "0.1.0" + repository: "file://../data-service" + - name: news-service + version: "0.1.0" + repository: "file://../news-service" + - name: dashboard + version: "0.1.0" + repository: "file://../dashboard" + - name: cert-manager + version: "1.14.0" + repository: https://charts.jetstack.io + condition: cert-manager.enabled + - name: ingress-nginx + version: "4.9.0" + repository: https://kubernetes.github.io/ingress-nginx + condition: ingress-nginx.enabled diff --git a/trading-platform/deploy/helm/_helpers.tpl.template b/trading-platform/deploy/helm/_helpers.tpl.template new file mode 100644 index 0000000..3b76fa7 --- /dev/null +++ b/trading-platform/deploy/helm/_helpers.tpl.template @@ -0,0 +1,60 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define ".name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define ".fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define ".chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define ".labels" -}} +helm.sh/chart: {{ include ".chart" . }} +{{ include ".selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define ".selectorLabels" -}} +app.kubernetes.io/name: {{ include ".name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define ".serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include ".fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/trading-platform/deploy/helm/api-gateway/Chart.yaml b/trading-platform/deploy/helm/api-gateway/Chart.yaml new file mode 100644 index 0000000..5e0f40d --- /dev/null +++ b/trading-platform/deploy/helm/api-gateway/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: api-gateway +description: API Gateway — Nginx reverse proxy +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/trading-platform/deploy/helm/api-gateway/templates/_helpers.tpl b/trading-platform/deploy/helm/api-gateway/templates/_helpers.tpl new file mode 100644 index 0000000..33b8067 --- /dev/null +++ b/trading-platform/deploy/helm/api-gateway/templates/_helpers.tpl @@ -0,0 +1,61 @@ + +{{/* +Expand the name of the chart. +*/}} +{{- define "api-gateway.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "api-gateway.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "api-gateway.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "api-gateway.labels" -}} +helm.sh/chart: {{ include "api-gateway.chart" . }} +{{ include "api-gateway.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "api-gateway.selectorLabels" -}} +app.kubernetes.io/name: {{ include "api-gateway.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "api-gateway.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "api-gateway.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/trading-platform/deploy/helm/api-gateway/templates/deployment.yaml b/trading-platform/deploy/helm/api-gateway/templates/deployment.yaml new file mode 100644 index 0000000..057c146 --- /dev/null +++ b/trading-platform/deploy/helm/api-gateway/templates/deployment.yaml @@ -0,0 +1,57 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "api-gateway.fullname" . }} + labels: + {{- include "api-gateway.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "api-gateway.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "api-gateway.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "api-gateway.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 8080 + protocol: TCP + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 12 }} + {{- end }} diff --git a/trading-platform/deploy/helm/api-gateway/templates/hpa.yaml b/trading-platform/deploy/helm/api-gateway/templates/hpa.yaml new file mode 100644 index 0000000..472b45b --- /dev/null +++ b/trading-platform/deploy/helm/api-gateway/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "api-gateway.fullname" . }} + labels: + {{- include "api-gateway.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "api-gateway.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/trading-platform/deploy/helm/api-gateway/templates/ingress.yaml b/trading-platform/deploy/helm/api-gateway/templates/ingress.yaml new file mode 100644 index 0000000..d875b28 --- /dev/null +++ b/trading-platform/deploy/helm/api-gateway/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "api-gateway.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: extensions/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "api-gateway.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/trading-platform/deploy/helm/api-gateway/templates/networkpolicy.yaml b/trading-platform/deploy/helm/api-gateway/templates/networkpolicy.yaml new file mode 100644 index 0000000..0fa2821 --- /dev/null +++ b/trading-platform/deploy/helm/api-gateway/templates/networkpolicy.yaml @@ -0,0 +1,29 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "api-gateway.fullname" . }}-network-policy + labels: + {{- include "api-gateway.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "api-gateway.selectorLabels" . | nindent 6 }} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/name: api-gateway + ports: + - port: http + protocol: TCP + egress: + # Allow DNS resolution + - to: [] + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP diff --git a/trading-platform/deploy/helm/api-gateway/templates/service.yaml b/trading-platform/deploy/helm/api-gateway/templates/service.yaml new file mode 100644 index 0000000..545d6f0 --- /dev/null +++ b/trading-platform/deploy/helm/api-gateway/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "api-gateway.fullname" . }} + labels: + {{- include "api-gateway.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "api-gateway.selectorLabels" . | nindent 4 }} diff --git a/trading-platform/deploy/helm/api-gateway/templates/serviceaccount.yaml b/trading-platform/deploy/helm/api-gateway/templates/serviceaccount.yaml new file mode 100644 index 0000000..d707caa --- /dev/null +++ b/trading-platform/deploy/helm/api-gateway/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "api-gateway.serviceAccountName" . }} + labels: + {{- include "api-gateway.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/trading-platform/deploy/helm/api-gateway/values.yaml b/trading-platform/deploy/helm/api-gateway/values.yaml new file mode 100644 index 0000000..551148b --- /dev/null +++ b/trading-platform/deploy/helm/api-gateway/values.yaml @@ -0,0 +1,80 @@ +# trading-platform/api-gateway Helm chart values + +replicaCount: 2 + +image: + repository: trading-platform/api-gateway + pullPolicy: IfNotPresent + tag: "" + +imagePullSecrets: [] + +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + annotations: {} + name: "" + +podAnnotations: {} + +securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + +service: + type: ClusterIP + port: 8080 + +ingress: + enabled: true + className: nginx + annotations: {} + hosts: + - host: api.trading.example.com + paths: + - path: / + pathType: Prefix + tls: [] + +resources: + limits: + cpu: "500m" + memory: 256Mi + requests: + cpu: "250m" + memory: 128Mi + +autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + +livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 15 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 3 + +readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + +nodeSelector: {} +tolerations: [] +affinity: {} diff --git a/trading-platform/deploy/helm/dashboard/Chart.yaml b/trading-platform/deploy/helm/dashboard/Chart.yaml new file mode 100644 index 0000000..2b077c3 --- /dev/null +++ b/trading-platform/deploy/helm/dashboard/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: dashboard +description: Dashboard frontend +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/trading-platform/deploy/helm/dashboard/templates/_helpers.tpl b/trading-platform/deploy/helm/dashboard/templates/_helpers.tpl new file mode 100644 index 0000000..3402279 --- /dev/null +++ b/trading-platform/deploy/helm/dashboard/templates/_helpers.tpl @@ -0,0 +1,61 @@ + +{{/* +Expand the name of the chart. +*/}} +{{- define "dashboard.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "dashboard.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "dashboard.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "dashboard.labels" -}} +helm.sh/chart: {{ include "dashboard.chart" . }} +{{ include "dashboard.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "dashboard.selectorLabels" -}} +app.kubernetes.io/name: {{ include "dashboard.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "dashboard.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "dashboard.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/trading-platform/deploy/helm/dashboard/templates/deployment.yaml b/trading-platform/deploy/helm/dashboard/templates/deployment.yaml new file mode 100644 index 0000000..59351c0 --- /dev/null +++ b/trading-platform/deploy/helm/dashboard/templates/deployment.yaml @@ -0,0 +1,57 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "dashboard.fullname" . }} + labels: + {{- include "dashboard.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "dashboard.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "dashboard.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "dashboard.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 3000 + protocol: TCP + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 12 }} + {{- end }} diff --git a/trading-platform/deploy/helm/dashboard/templates/hpa.yaml b/trading-platform/deploy/helm/dashboard/templates/hpa.yaml new file mode 100644 index 0000000..c5f6544 --- /dev/null +++ b/trading-platform/deploy/helm/dashboard/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "dashboard.fullname" . }} + labels: + {{- include "dashboard.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "dashboard.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/trading-platform/deploy/helm/dashboard/templates/networkpolicy.yaml b/trading-platform/deploy/helm/dashboard/templates/networkpolicy.yaml new file mode 100644 index 0000000..2f84151 --- /dev/null +++ b/trading-platform/deploy/helm/dashboard/templates/networkpolicy.yaml @@ -0,0 +1,29 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "dashboard.fullname" . }}-network-policy + labels: + {{- include "dashboard.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "dashboard.selectorLabels" . | nindent 6 }} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/name: api-gateway + ports: + - port: http + protocol: TCP + egress: + # Allow DNS resolution + - to: [] + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP diff --git a/trading-platform/deploy/helm/dashboard/templates/service.yaml b/trading-platform/deploy/helm/dashboard/templates/service.yaml new file mode 100644 index 0000000..155124b --- /dev/null +++ b/trading-platform/deploy/helm/dashboard/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "dashboard.fullname" . }} + labels: + {{- include "dashboard.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "dashboard.selectorLabels" . | nindent 4 }} diff --git a/trading-platform/deploy/helm/dashboard/templates/serviceaccount.yaml b/trading-platform/deploy/helm/dashboard/templates/serviceaccount.yaml new file mode 100644 index 0000000..4948fb9 --- /dev/null +++ b/trading-platform/deploy/helm/dashboard/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "dashboard.serviceAccountName" . }} + labels: + {{- include "dashboard.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/trading-platform/deploy/helm/dashboard/values.yaml b/trading-platform/deploy/helm/dashboard/values.yaml new file mode 100644 index 0000000..e7d817f --- /dev/null +++ b/trading-platform/deploy/helm/dashboard/values.yaml @@ -0,0 +1,80 @@ +# trading-platform/dashboard Helm chart values + +replicaCount: 2 + +image: + repository: trading-platform/dashboard + pullPolicy: IfNotPresent + tag: "" + +imagePullSecrets: [] + +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + annotations: {} + name: "" + +podAnnotations: {} + +securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + +service: + type: ClusterIP + port: 3000 + +ingress: + enabled: false + className: nginx + annotations: {} + hosts: + - host: api.trading.example.com + paths: + - path: / + pathType: Prefix + tls: [] + +resources: + limits: + cpu: "500m" + memory: 512Mi + requests: + cpu: "250m" + memory: 256Mi + +autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + +livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 15 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 3 + +readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + +nodeSelector: {} +tolerations: [] +affinity: {} diff --git a/trading-platform/deploy/helm/data-service/Chart.yaml b/trading-platform/deploy/helm/data-service/Chart.yaml new file mode 100644 index 0000000..b7e2b70 --- /dev/null +++ b/trading-platform/deploy/helm/data-service/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: data-service +description: Trading data service +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/trading-platform/deploy/helm/data-service/templates/_helpers.tpl b/trading-platform/deploy/helm/data-service/templates/_helpers.tpl new file mode 100644 index 0000000..670ec9d --- /dev/null +++ b/trading-platform/deploy/helm/data-service/templates/_helpers.tpl @@ -0,0 +1,61 @@ + +{{/* +Expand the name of the chart. +*/}} +{{- define "data-service.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "data-service.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "data-service.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "data-service.labels" -}} +helm.sh/chart: {{ include "data-service.chart" . }} +{{ include "data-service.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "data-service.selectorLabels" -}} +app.kubernetes.io/name: {{ include "data-service.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "data-service.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "data-service.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/trading-platform/deploy/helm/data-service/templates/deployment.yaml b/trading-platform/deploy/helm/data-service/templates/deployment.yaml new file mode 100644 index 0000000..f2a03be --- /dev/null +++ b/trading-platform/deploy/helm/data-service/templates/deployment.yaml @@ -0,0 +1,57 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "data-service.fullname" . }} + labels: + {{- include "data-service.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "data-service.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "data-service.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "data-service.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 8001 + protocol: TCP + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 12 }} + {{- end }} diff --git a/trading-platform/deploy/helm/data-service/templates/hpa.yaml b/trading-platform/deploy/helm/data-service/templates/hpa.yaml new file mode 100644 index 0000000..9686023 --- /dev/null +++ b/trading-platform/deploy/helm/data-service/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "data-service.fullname" . }} + labels: + {{- include "data-service.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "data-service.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/trading-platform/deploy/helm/data-service/templates/networkpolicy.yaml b/trading-platform/deploy/helm/data-service/templates/networkpolicy.yaml new file mode 100644 index 0000000..d50f2bc --- /dev/null +++ b/trading-platform/deploy/helm/data-service/templates/networkpolicy.yaml @@ -0,0 +1,29 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "data-service.fullname" . }}-network-policy + labels: + {{- include "data-service.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "data-service.selectorLabels" . | nindent 6 }} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/name: api-gateway + ports: + - port: http + protocol: TCP + egress: + # Allow DNS resolution + - to: [] + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP diff --git a/trading-platform/deploy/helm/data-service/templates/service.yaml b/trading-platform/deploy/helm/data-service/templates/service.yaml new file mode 100644 index 0000000..186d0d6 --- /dev/null +++ b/trading-platform/deploy/helm/data-service/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "data-service.fullname" . }} + labels: + {{- include "data-service.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "data-service.selectorLabels" . | nindent 4 }} diff --git a/trading-platform/deploy/helm/data-service/templates/serviceaccount.yaml b/trading-platform/deploy/helm/data-service/templates/serviceaccount.yaml new file mode 100644 index 0000000..3ba4646 --- /dev/null +++ b/trading-platform/deploy/helm/data-service/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "data-service.serviceAccountName" . }} + labels: + {{- include "data-service.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/trading-platform/deploy/helm/data-service/values.yaml b/trading-platform/deploy/helm/data-service/values.yaml new file mode 100644 index 0000000..8a5fff1 --- /dev/null +++ b/trading-platform/deploy/helm/data-service/values.yaml @@ -0,0 +1,80 @@ +# trading-platform/data-service Helm chart values + +replicaCount: 2 + +image: + repository: trading-platform/data-service + pullPolicy: IfNotPresent + tag: "" + +imagePullSecrets: [] + +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + annotations: {} + name: "" + +podAnnotations: {} + +securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + +service: + type: ClusterIP + port: 8001 + +ingress: + enabled: false + className: nginx + annotations: {} + hosts: + - host: api.trading.example.com + paths: + - path: / + pathType: Prefix + tls: [] + +resources: + limits: + cpu: "1000m" + memory: 1Gi + requests: + cpu: "500m" + memory: 512Mi + +autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + +livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 15 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 3 + +readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + +nodeSelector: {} +tolerations: [] +affinity: {} diff --git a/trading-platform/deploy/helm/execute-service/Chart.yaml b/trading-platform/deploy/helm/execute-service/Chart.yaml new file mode 100644 index 0000000..0324bc7 --- /dev/null +++ b/trading-platform/deploy/helm/execute-service/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: execute-service +description: Trading execution service +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/trading-platform/deploy/helm/execute-service/templates/_helpers.tpl b/trading-platform/deploy/helm/execute-service/templates/_helpers.tpl new file mode 100644 index 0000000..0f46b86 --- /dev/null +++ b/trading-platform/deploy/helm/execute-service/templates/_helpers.tpl @@ -0,0 +1,61 @@ + +{{/* +Expand the name of the chart. +*/}} +{{- define "execute-service.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "execute-service.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "execute-service.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "execute-service.labels" -}} +helm.sh/chart: {{ include "execute-service.chart" . }} +{{ include "execute-service.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "execute-service.selectorLabels" -}} +app.kubernetes.io/name: {{ include "execute-service.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "execute-service.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "execute-service.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/trading-platform/deploy/helm/execute-service/templates/deployment.yaml b/trading-platform/deploy/helm/execute-service/templates/deployment.yaml new file mode 100644 index 0000000..5b7a032 --- /dev/null +++ b/trading-platform/deploy/helm/execute-service/templates/deployment.yaml @@ -0,0 +1,57 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "execute-service.fullname" . }} + labels: + {{- include "execute-service.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "execute-service.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "execute-service.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "execute-service.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 8000 + protocol: TCP + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 12 }} + {{- end }} diff --git a/trading-platform/deploy/helm/execute-service/templates/hpa.yaml b/trading-platform/deploy/helm/execute-service/templates/hpa.yaml new file mode 100644 index 0000000..d27ea6d --- /dev/null +++ b/trading-platform/deploy/helm/execute-service/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "execute-service.fullname" . }} + labels: + {{- include "execute-service.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "execute-service.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/trading-platform/deploy/helm/execute-service/templates/networkpolicy.yaml b/trading-platform/deploy/helm/execute-service/templates/networkpolicy.yaml new file mode 100644 index 0000000..2e809cd --- /dev/null +++ b/trading-platform/deploy/helm/execute-service/templates/networkpolicy.yaml @@ -0,0 +1,29 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "execute-service.fullname" . }}-network-policy + labels: + {{- include "execute-service.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "execute-service.selectorLabels" . | nindent 6 }} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/name: api-gateway + ports: + - port: http + protocol: TCP + egress: + # Allow DNS resolution + - to: [] + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP diff --git a/trading-platform/deploy/helm/execute-service/templates/service.yaml b/trading-platform/deploy/helm/execute-service/templates/service.yaml new file mode 100644 index 0000000..0fbb9ce --- /dev/null +++ b/trading-platform/deploy/helm/execute-service/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "execute-service.fullname" . }} + labels: + {{- include "execute-service.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "execute-service.selectorLabels" . | nindent 4 }} diff --git a/trading-platform/deploy/helm/execute-service/templates/serviceaccount.yaml b/trading-platform/deploy/helm/execute-service/templates/serviceaccount.yaml new file mode 100644 index 0000000..0f3619b --- /dev/null +++ b/trading-platform/deploy/helm/execute-service/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "execute-service.serviceAccountName" . }} + labels: + {{- include "execute-service.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/trading-platform/deploy/helm/execute-service/values.yaml b/trading-platform/deploy/helm/execute-service/values.yaml new file mode 100644 index 0000000..7dc92e6 --- /dev/null +++ b/trading-platform/deploy/helm/execute-service/values.yaml @@ -0,0 +1,80 @@ +# trading-platform/execute-service Helm chart values + +replicaCount: 2 + +image: + repository: trading-platform/execute-service + pullPolicy: IfNotPresent + tag: "" + +imagePullSecrets: [] + +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + annotations: {} + name: "" + +podAnnotations: {} + +securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + +service: + type: ClusterIP + port: 8000 + +ingress: + enabled: false + className: nginx + annotations: {} + hosts: + - host: api.trading.example.com + paths: + - path: / + pathType: Prefix + tls: [] + +resources: + limits: + cpu: "500m" + memory: 512Mi + requests: + cpu: "250m" + memory: 256Mi + +autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + +livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 15 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 3 + +readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + +nodeSelector: {} +tolerations: [] +affinity: {} diff --git a/trading-platform/deploy/helm/news-service/Chart.yaml b/trading-platform/deploy/helm/news-service/Chart.yaml new file mode 100644 index 0000000..c9eb5d9 --- /dev/null +++ b/trading-platform/deploy/helm/news-service/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: news-service +description: News analysis service +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/trading-platform/deploy/helm/news-service/templates/_helpers.tpl b/trading-platform/deploy/helm/news-service/templates/_helpers.tpl new file mode 100644 index 0000000..8be65a7 --- /dev/null +++ b/trading-platform/deploy/helm/news-service/templates/_helpers.tpl @@ -0,0 +1,61 @@ + +{{/* +Expand the name of the chart. +*/}} +{{- define "news-service.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "news-service.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "news-service.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "news-service.labels" -}} +helm.sh/chart: {{ include "news-service.chart" . }} +{{ include "news-service.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "news-service.selectorLabels" -}} +app.kubernetes.io/name: {{ include "news-service.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "news-service.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "news-service.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/trading-platform/deploy/helm/news-service/templates/deployment.yaml b/trading-platform/deploy/helm/news-service/templates/deployment.yaml new file mode 100644 index 0000000..ef25033 --- /dev/null +++ b/trading-platform/deploy/helm/news-service/templates/deployment.yaml @@ -0,0 +1,57 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "news-service.fullname" . }} + labels: + {{- include "news-service.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "news-service.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "news-service.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "news-service.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 8002 + protocol: TCP + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 12 }} + {{- end }} diff --git a/trading-platform/deploy/helm/news-service/templates/hpa.yaml b/trading-platform/deploy/helm/news-service/templates/hpa.yaml new file mode 100644 index 0000000..7c4aeca --- /dev/null +++ b/trading-platform/deploy/helm/news-service/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "news-service.fullname" . }} + labels: + {{- include "news-service.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "news-service.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/trading-platform/deploy/helm/news-service/templates/networkpolicy.yaml b/trading-platform/deploy/helm/news-service/templates/networkpolicy.yaml new file mode 100644 index 0000000..ff3c8f9 --- /dev/null +++ b/trading-platform/deploy/helm/news-service/templates/networkpolicy.yaml @@ -0,0 +1,29 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "news-service.fullname" . }}-network-policy + labels: + {{- include "news-service.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "news-service.selectorLabels" . | nindent 6 }} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/name: api-gateway + ports: + - port: http + protocol: TCP + egress: + # Allow DNS resolution + - to: [] + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP diff --git a/trading-platform/deploy/helm/news-service/templates/service.yaml b/trading-platform/deploy/helm/news-service/templates/service.yaml new file mode 100644 index 0000000..8709a9e --- /dev/null +++ b/trading-platform/deploy/helm/news-service/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "news-service.fullname" . }} + labels: + {{- include "news-service.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "news-service.selectorLabels" . | nindent 4 }} diff --git a/trading-platform/deploy/helm/news-service/templates/serviceaccount.yaml b/trading-platform/deploy/helm/news-service/templates/serviceaccount.yaml new file mode 100644 index 0000000..4b093b0 --- /dev/null +++ b/trading-platform/deploy/helm/news-service/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "news-service.serviceAccountName" . }} + labels: + {{- include "news-service.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/trading-platform/deploy/helm/news-service/values.yaml b/trading-platform/deploy/helm/news-service/values.yaml new file mode 100644 index 0000000..7edc76e --- /dev/null +++ b/trading-platform/deploy/helm/news-service/values.yaml @@ -0,0 +1,80 @@ +# trading-platform/news-service Helm chart values + +replicaCount: 2 + +image: + repository: trading-platform/news-service + pullPolicy: IfNotPresent + tag: "" + +imagePullSecrets: [] + +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + annotations: {} + name: "" + +podAnnotations: {} + +securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + +service: + type: ClusterIP + port: 8002 + +ingress: + enabled: false + className: nginx + annotations: {} + hosts: + - host: api.trading.example.com + paths: + - path: / + pathType: Prefix + tls: [] + +resources: + limits: + cpu: "1000m" + memory: 1Gi + requests: + cpu: "500m" + memory: 512Mi + +autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + +livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 15 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 3 + +readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + +nodeSelector: {} +tolerations: [] +affinity: {} diff --git a/trading-platform/deploy/k8s/base/api-gateway-deployment.yaml b/trading-platform/deploy/k8s/base/api-gateway-deployment.yaml new file mode 100644 index 0000000..834c1ff --- /dev/null +++ b/trading-platform/deploy/k8s/base/api-gateway-deployment.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: api-gateway + namespace: customer1 + labels: + app: api-gateway + app.kubernetes.io/name: api-gateway + app.kubernetes.io/component: microservice +spec: + replicas: 2 + selector: + matchLabels: + app: api-gateway + template: + metadata: + labels: + app: api-gateway + app.kubernetes.io/name: api-gateway + app.kubernetes.io/component: microservice + spec: + automountServiceAccountToken: false + securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + containers: + - name: api-gateway + image: "trading-platform/api-gateway:v${VERSION}" + ports: + - containerPort: 8080 + protocol: TCP + envFrom: + - configMapRef: + name: trading-platform-config + resources: + limits: + cpu: "500m" + memory: 256Mi + requests: + cpu: "250m" + memory: 128Mi + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 15 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + volumeMounts: + - name: tmp + mountPath: /tmp + volumes: + - name: tmp + emptyDir: {} diff --git a/trading-platform/deploy/k8s/base/api-gateway-hpa.yaml b/trading-platform/deploy/k8s/base/api-gateway-hpa.yaml new file mode 100644 index 0000000..20940ea --- /dev/null +++ b/trading-platform/deploy/k8s/base/api-gateway-hpa.yaml @@ -0,0 +1,27 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: api-gateway-hpa + namespace: customer1 + labels: + app: api-gateway +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: api-gateway + minReplicas: 2 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 diff --git a/trading-platform/deploy/k8s/base/api-gateway-service.yaml b/trading-platform/deploy/k8s/base/api-gateway-service.yaml new file mode 100644 index 0000000..1ca171d --- /dev/null +++ b/trading-platform/deploy/k8s/base/api-gateway-service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: api-gateway + namespace: customer1 + labels: + app: api-gateway + app.kubernetes.io/name: api-gateway + app.kubernetes.io/component: microservice +spec: + type: ClusterIP + ports: + - port: 8080 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: api-gateway diff --git a/trading-platform/deploy/k8s/base/cert-manager/api-gateway-mtls-cert.yaml b/trading-platform/deploy/k8s/base/cert-manager/api-gateway-mtls-cert.yaml new file mode 100644 index 0000000..1534702 --- /dev/null +++ b/trading-platform/deploy/k8s/base/cert-manager/api-gateway-mtls-cert.yaml @@ -0,0 +1,24 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: api-gateway-mtls-cert + namespace: customer1 +spec: + secretName: api-gateway-mtls-secret + duration: 2160h # 90 days + renewBefore: 360h # 15 days + commonName: api-gateway.customer1.svc.cluster.local + dnsNames: + - api-gateway + - api-gateway.customer1 + - api-gateway.customer1.svc + - api-gateway.customer1.svc.cluster.local + usages: + - digital signature + - key encipherment + - client auth + - server auth + issuerRef: + name: trading-platform-ca-issuer + kind: Issuer + group: cert-manager.io diff --git a/trading-platform/deploy/k8s/base/cert-manager/ca-issuer.yaml b/trading-platform/deploy/k8s/base/cert-manager/ca-issuer.yaml new file mode 100644 index 0000000..337ede9 --- /dev/null +++ b/trading-platform/deploy/k8s/base/cert-manager/ca-issuer.yaml @@ -0,0 +1,8 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: trading-platform-ca-issuer + namespace: customer1 +spec: + ca: + secretName: trading-platform-ca-secret diff --git a/trading-platform/deploy/k8s/base/cert-manager/cluster-issuers.yaml b/trading-platform/deploy/k8s/base/cert-manager/cluster-issuers.yaml new file mode 100644 index 0000000..9b8ce95 --- /dev/null +++ b/trading-platform/deploy/k8s/base/cert-manager/cluster-issuers.yaml @@ -0,0 +1,29 @@ +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-prod +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: admin@trading-platform.com + privateKeySecretRef: + name: letsencrypt-prod-key + solvers: + - http01: + ingress: + class: nginx +--- +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + server: https://acme-staging-v02.api.letsencrypt.org/directory + email: admin@trading-platform.com + privateKeySecretRef: + name: letsencrypt-staging-key + solvers: + - http01: + ingress: + class: nginx diff --git a/trading-platform/deploy/k8s/base/cert-manager/dashboard-mtls-cert.yaml b/trading-platform/deploy/k8s/base/cert-manager/dashboard-mtls-cert.yaml new file mode 100644 index 0000000..d45e3b5 --- /dev/null +++ b/trading-platform/deploy/k8s/base/cert-manager/dashboard-mtls-cert.yaml @@ -0,0 +1,24 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: dashboard-mtls-cert + namespace: customer1 +spec: + secretName: dashboard-mtls-secret + duration: 2160h # 90 days + renewBefore: 360h # 15 days + commonName: dashboard.customer1.svc.cluster.local + dnsNames: + - dashboard + - dashboard.customer1 + - dashboard.customer1.svc + - dashboard.customer1.svc.cluster.local + usages: + - digital signature + - key encipherment + - client auth + - server auth + issuerRef: + name: trading-platform-ca-issuer + kind: Issuer + group: cert-manager.io diff --git a/trading-platform/deploy/k8s/base/cert-manager/data-service-mtls-cert.yaml b/trading-platform/deploy/k8s/base/cert-manager/data-service-mtls-cert.yaml new file mode 100644 index 0000000..e3e0acc --- /dev/null +++ b/trading-platform/deploy/k8s/base/cert-manager/data-service-mtls-cert.yaml @@ -0,0 +1,24 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: data-service-mtls-cert + namespace: customer1 +spec: + secretName: data-service-mtls-secret + duration: 2160h # 90 days + renewBefore: 360h # 15 days + commonName: data-service.customer1.svc.cluster.local + dnsNames: + - data-service + - data-service.customer1 + - data-service.customer1.svc + - data-service.customer1.svc.cluster.local + usages: + - digital signature + - key encipherment + - client auth + - server auth + issuerRef: + name: trading-platform-ca-issuer + kind: Issuer + group: cert-manager.io diff --git a/trading-platform/deploy/k8s/base/cert-manager/execute-service-mtls-cert.yaml b/trading-platform/deploy/k8s/base/cert-manager/execute-service-mtls-cert.yaml new file mode 100644 index 0000000..5d5d430 --- /dev/null +++ b/trading-platform/deploy/k8s/base/cert-manager/execute-service-mtls-cert.yaml @@ -0,0 +1,24 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: execute-service-mtls-cert + namespace: customer1 +spec: + secretName: execute-service-mtls-secret + duration: 2160h # 90 days + renewBefore: 360h # 15 days + commonName: execute-service.customer1.svc.cluster.local + dnsNames: + - execute-service + - execute-service.customer1 + - execute-service.customer1.svc + - execute-service.customer1.svc.cluster.local + usages: + - digital signature + - key encipherment + - client auth + - server auth + issuerRef: + name: trading-platform-ca-issuer + kind: Issuer + group: cert-manager.io diff --git a/trading-platform/deploy/k8s/base/cert-manager/issuer.yaml b/trading-platform/deploy/k8s/base/cert-manager/issuer.yaml new file mode 100644 index 0000000..42eae6d --- /dev/null +++ b/trading-platform/deploy/k8s/base/cert-manager/issuer.yaml @@ -0,0 +1,24 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned-issuer + namespace: cert-manager +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: trading-platform-ca + namespace: cert-manager +spec: + isCA: true + commonName: trading-platform-ca + secretName: trading-platform-ca-secret + privateKey: + algorithm: ECDSA + size: 256 + issuerRef: + name: selfsigned-issuer + kind: Issuer + group: cert-manager.io diff --git a/trading-platform/deploy/k8s/base/cert-manager/news-service-mtls-cert.yaml b/trading-platform/deploy/k8s/base/cert-manager/news-service-mtls-cert.yaml new file mode 100644 index 0000000..ae15e24 --- /dev/null +++ b/trading-platform/deploy/k8s/base/cert-manager/news-service-mtls-cert.yaml @@ -0,0 +1,24 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: news-service-mtls-cert + namespace: customer1 +spec: + secretName: news-service-mtls-secret + duration: 2160h # 90 days + renewBefore: 360h # 15 days + commonName: news-service.customer1.svc.cluster.local + dnsNames: + - news-service + - news-service.customer1 + - news-service.customer1.svc + - news-service.customer1.svc.cluster.local + usages: + - digital signature + - key encipherment + - client auth + - server auth + issuerRef: + name: trading-platform-ca-issuer + kind: Issuer + group: cert-manager.io diff --git a/trading-platform/deploy/k8s/base/dashboard-deployment.yaml b/trading-platform/deploy/k8s/base/dashboard-deployment.yaml new file mode 100644 index 0000000..9486160 --- /dev/null +++ b/trading-platform/deploy/k8s/base/dashboard-deployment.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dashboard + namespace: customer1 + labels: + app: dashboard + app.kubernetes.io/name: dashboard + app.kubernetes.io/component: microservice +spec: + replicas: 2 + selector: + matchLabels: + app: dashboard + template: + metadata: + labels: + app: dashboard + app.kubernetes.io/name: dashboard + app.kubernetes.io/component: microservice + spec: + automountServiceAccountToken: false + securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + containers: + - name: dashboard + image: "trading-platform/dashboard:v${VERSION}" + ports: + - containerPort: 3000 + protocol: TCP + envFrom: + - configMapRef: + name: trading-platform-config + resources: + limits: + cpu: "500m" + memory: 512Mi + requests: + cpu: "250m" + memory: 256Mi + livenessProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 15 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + volumeMounts: + - name: tmp + mountPath: /tmp + volumes: + - name: tmp + emptyDir: {} diff --git a/trading-platform/deploy/k8s/base/dashboard-hpa.yaml b/trading-platform/deploy/k8s/base/dashboard-hpa.yaml new file mode 100644 index 0000000..d7485a1 --- /dev/null +++ b/trading-platform/deploy/k8s/base/dashboard-hpa.yaml @@ -0,0 +1,27 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: dashboard-hpa + namespace: customer1 + labels: + app: dashboard +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: dashboard + minReplicas: 2 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 diff --git a/trading-platform/deploy/k8s/base/dashboard-service.yaml b/trading-platform/deploy/k8s/base/dashboard-service.yaml new file mode 100644 index 0000000..fafde36 --- /dev/null +++ b/trading-platform/deploy/k8s/base/dashboard-service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: dashboard + namespace: customer1 + labels: + app: dashboard + app.kubernetes.io/name: dashboard + app.kubernetes.io/component: microservice +spec: + type: ClusterIP + ports: + - port: 3000 + targetPort: 3000 + protocol: TCP + name: http + selector: + app: dashboard diff --git a/trading-platform/deploy/k8s/base/data-service-deployment.yaml b/trading-platform/deploy/k8s/base/data-service-deployment.yaml new file mode 100644 index 0000000..7eb668a --- /dev/null +++ b/trading-platform/deploy/k8s/base/data-service-deployment.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: data-service + namespace: customer1 + labels: + app: data-service + app.kubernetes.io/name: data-service + app.kubernetes.io/component: microservice +spec: + replicas: 2 + selector: + matchLabels: + app: data-service + template: + metadata: + labels: + app: data-service + app.kubernetes.io/name: data-service + app.kubernetes.io/component: microservice + spec: + automountServiceAccountToken: false + securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + containers: + - name: data-service + image: "trading-platform/data-service:v${VERSION}" + ports: + - containerPort: 8001 + protocol: TCP + envFrom: + - configMapRef: + name: trading-platform-config + resources: + limits: + cpu: "1000m" + memory: 1Gi + requests: + cpu: "500m" + memory: 512Mi + livenessProbe: + httpGet: + path: /health + port: 8001 + initialDelaySeconds: 15 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health + port: 8001 + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + volumeMounts: + - name: tmp + mountPath: /tmp + volumes: + - name: tmp + emptyDir: {} diff --git a/trading-platform/deploy/k8s/base/data-service-hpa.yaml b/trading-platform/deploy/k8s/base/data-service-hpa.yaml new file mode 100644 index 0000000..31c70e2 --- /dev/null +++ b/trading-platform/deploy/k8s/base/data-service-hpa.yaml @@ -0,0 +1,27 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: data-service-hpa + namespace: customer1 + labels: + app: data-service +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: data-service + minReplicas: 2 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 diff --git a/trading-platform/deploy/k8s/base/data-service-service.yaml b/trading-platform/deploy/k8s/base/data-service-service.yaml new file mode 100644 index 0000000..03a0895 --- /dev/null +++ b/trading-platform/deploy/k8s/base/data-service-service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: data-service + namespace: customer1 + labels: + app: data-service + app.kubernetes.io/name: data-service + app.kubernetes.io/component: microservice +spec: + type: ClusterIP + ports: + - port: 8001 + targetPort: 8001 + protocol: TCP + name: http + selector: + app: data-service diff --git a/trading-platform/deploy/k8s/base/execute-service-deployment.yaml b/trading-platform/deploy/k8s/base/execute-service-deployment.yaml new file mode 100644 index 0000000..c0d8e8e --- /dev/null +++ b/trading-platform/deploy/k8s/base/execute-service-deployment.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: execute-service + namespace: customer1 + labels: + app: execute-service + app.kubernetes.io/name: execute-service + app.kubernetes.io/component: microservice +spec: + replicas: 2 + selector: + matchLabels: + app: execute-service + template: + metadata: + labels: + app: execute-service + app.kubernetes.io/name: execute-service + app.kubernetes.io/component: microservice + spec: + automountServiceAccountToken: false + securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + containers: + - name: execute-service + image: "trading-platform/execute-service:v${VERSION}" + ports: + - containerPort: 8000 + protocol: TCP + envFrom: + - configMapRef: + name: trading-platform-config + resources: + limits: + cpu: "500m" + memory: 512Mi + requests: + cpu: "250m" + memory: 256Mi + livenessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 15 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + volumeMounts: + - name: tmp + mountPath: /tmp + volumes: + - name: tmp + emptyDir: {} diff --git a/trading-platform/deploy/k8s/base/execute-service-hpa.yaml b/trading-platform/deploy/k8s/base/execute-service-hpa.yaml new file mode 100644 index 0000000..bda8bf7 --- /dev/null +++ b/trading-platform/deploy/k8s/base/execute-service-hpa.yaml @@ -0,0 +1,27 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: execute-service-hpa + namespace: customer1 + labels: + app: execute-service +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: execute-service + minReplicas: 2 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 diff --git a/trading-platform/deploy/k8s/base/execute-service-service.yaml b/trading-platform/deploy/k8s/base/execute-service-service.yaml new file mode 100644 index 0000000..4d084bb --- /dev/null +++ b/trading-platform/deploy/k8s/base/execute-service-service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: execute-service + namespace: customer1 + labels: + app: execute-service + app.kubernetes.io/name: execute-service + app.kubernetes.io/component: microservice +spec: + type: ClusterIP + ports: + - port: 8000 + targetPort: 8000 + protocol: TCP + name: http + selector: + app: execute-service diff --git a/trading-platform/deploy/k8s/base/ingress.yaml b/trading-platform/deploy/k8s/base/ingress.yaml new file mode 100644 index 0000000..ed32132 --- /dev/null +++ b/trading-platform/deploy/k8s/base/ingress.yaml @@ -0,0 +1,40 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: api-gateway-ingress + namespace: customer1 + labels: + app: api-gateway + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-body-size: "10m" + nginx.ingress.kubernetes.io/rate-limit: "100" + cert-manager.io/cluster-issuer: letsencrypt-prod +spec: + ingressClassName: nginx + tls: + - hosts: + - api.trading.example.com + - dashboard.trading.example.com + secretName: trading-tls-secret + rules: + - host: api.trading.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: api-gateway + port: + number: 8080 + - host: dashboard.trading.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: dashboard + port: + number: 3000 diff --git a/trading-platform/deploy/k8s/base/namespace.yaml b/trading-platform/deploy/k8s/base/namespace.yaml new file mode 100644 index 0000000..9952f89 --- /dev/null +++ b/trading-platform/deploy/k8s/base/namespace.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: customer1 + labels: + name: customer1 + istio-injection: disabled # Disable Istio if using native K8s policies +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: trading-platform-config + namespace: customer1 +data: + KAFKA_BOOTSTRAP_SERVERS: "kafka-headless:9092" + REDIS_URL: "redis://redis-master:6379/0" + LOG_LEVEL: "info" + TRADING_ENV: "production" diff --git a/trading-platform/deploy/k8s/base/news-service-deployment.yaml b/trading-platform/deploy/k8s/base/news-service-deployment.yaml new file mode 100644 index 0000000..f003b91 --- /dev/null +++ b/trading-platform/deploy/k8s/base/news-service-deployment.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: news-service + namespace: customer1 + labels: + app: news-service + app.kubernetes.io/name: news-service + app.kubernetes.io/component: microservice +spec: + replicas: 2 + selector: + matchLabels: + app: news-service + template: + metadata: + labels: + app: news-service + app.kubernetes.io/name: news-service + app.kubernetes.io/component: microservice + spec: + automountServiceAccountToken: false + securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + containers: + - name: news-service + image: "trading-platform/news-service:v${VERSION}" + ports: + - containerPort: 8002 + protocol: TCP + envFrom: + - configMapRef: + name: trading-platform-config + resources: + limits: + cpu: "1000m" + memory: 1Gi + requests: + cpu: "500m" + memory: 512Mi + livenessProbe: + httpGet: + path: /health + port: 8002 + initialDelaySeconds: 15 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health + port: 8002 + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + volumeMounts: + - name: tmp + mountPath: /tmp + volumes: + - name: tmp + emptyDir: {} diff --git a/trading-platform/deploy/k8s/base/news-service-hpa.yaml b/trading-platform/deploy/k8s/base/news-service-hpa.yaml new file mode 100644 index 0000000..8d85e99 --- /dev/null +++ b/trading-platform/deploy/k8s/base/news-service-hpa.yaml @@ -0,0 +1,27 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: news-service-hpa + namespace: customer1 + labels: + app: news-service +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: news-service + minReplicas: 2 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 diff --git a/trading-platform/deploy/k8s/base/news-service-service.yaml b/trading-platform/deploy/k8s/base/news-service-service.yaml new file mode 100644 index 0000000..f9c0d21 --- /dev/null +++ b/trading-platform/deploy/k8s/base/news-service-service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: news-service + namespace: customer1 + labels: + app: news-service + app.kubernetes.io/name: news-service + app.kubernetes.io/component: microservice +spec: + type: ClusterIP + ports: + - port: 8002 + targetPort: 8002 + protocol: TCP + name: http + selector: + app: news-service diff --git a/trading-platform/deploy/mtls/README.md b/trading-platform/deploy/mtls/README.md new file mode 100644 index 0000000..c26b15f --- /dev/null +++ b/trading-platform/deploy/mtls/README.md @@ -0,0 +1,20 @@ +# ============================================================================= +# mTLS Configuration for Trading Platform +# ============================================================================= +# This directory contains certificates and configuration for mutual TLS +# between services. In production, use cert-manager to automate this. +# +# Option 1: cert-manager (recommended for production) +# Option 2: Manual certificate management (for dev/testing) +# +# Certificate hierarchy: +# Root CA +# ├── Service CA (issues service-to-service certs) +# │ ├── execute-service cert +# │ ├── data-service cert +# │ ├── news-service cert +# │ ├── api-gateway cert +# │ └── dashboard cert +# └── Ingress CA (for external-facing TLS) +# └── api-gateway TLS cert (for HTTPS) +# ============================================================================= diff --git a/trading-platform/deploy/scripts/deploy.sh b/trading-platform/deploy/scripts/deploy.sh new file mode 100755 index 0000000..5099285 --- /dev/null +++ b/trading-platform/deploy/scripts/deploy.sh @@ -0,0 +1,141 @@ +#!/bin/bash +# ============================================================================= +# Deploy Trading Platform to Kubernetes +# ============================================================================= +# +# Usage: +# ./deploy/scripts/deploy.sh [staging|production] [tag] +# +# Examples: +# ./deploy/scripts/deploy.sh staging latest +# ./deploy/scripts/deploy.sh production v1.2.3 +# +# Prerequisites: +# - kubectl configured with cluster access +# - Helm 3.x installed +# - Docker images pushed to registry +# - cert-manager installed in cluster (for TLS) +# ============================================================================= + +set -euo pipefail + +ENVIRONMENT="${1:-staging}" +IMAGE_TAG="${2:-latest}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# Validate environment +if [[ ! "$ENVIRONMENT" =~ ^(staging|production)$ ]]; then + echo "ERROR: Environment must be 'staging' or 'production', got '$ENVIRONMENT'" + exit 1 +fi + +# Set namespace and values file based on environment +if [[ "$ENVIRONMENT" == "staging" ]]; then + NAMESPACE="customer1-staging" + VALUES_FILE="$PROJECT_ROOT/deploy/k8s/overlays/staging/kustomization.yaml" +else + NAMESPACE="customer1" + VALUES_FILE="$PROJECT_ROOT/deploy/k8s/overlays/production/kustomization.yaml" +fi + +echo "========================================================" +echo " Deploying Trading Platform to $ENVIRONMENT" +echo " Image tag: $IMAGE_TAG" +echo " Namespace: $NAMESPACE" +echo "========================================================" + +# Confirm cluster context +CURRENT_CONTEXT=$(kubectl config current-context 2>/dev/null || echo "unknown") +echo "Current kubectl context: $CURRENT_CONTEXT" +read -r -p "Continue? (y/N) " -n 1 +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Deployment cancelled." + exit 1 +fi + +# Install dependencies (optional) +echo "" +echo "Step 1/5: Checking prerequisites..." + +# Check for Helm +if ! command -v helm &>/dev/null; then + echo "ERROR: helm is not installed" + exit 1 +fi + +# Check for kubectl +if ! command -v kubectl &>/dev/null; then + echo "ERROR: kubectl is not installed" + exit 1 +fi + +# Check cluster connectivity +if ! kubectl cluster-info &>/dev/null; then + echo "ERROR: Cannot connect to Kubernetes cluster" + exit 1 +fi +echo " ✓ Kubernetes cluster is accessible" + +# Create namespace if it doesn't exist +kubectl create namespace "$NAMESPACE" --dry-run=client -o yaml | kubectl apply -f - +echo " ✓ Namespace $NAMESPACE exists" + +# Step 2: Deploy infrastructure (PostgreSQL, Redis, Kafka) +echo "" +echo "Step 2/5: Deploying infrastructure..." +kubectl apply -f "$PROJECT_ROOT/deploy/k8s/base/namespace.yaml" +kubectl apply -f "$PROJECT_ROOT/deploy/k8s/base/configmap.yaml" +echo " ✓ ConfigMap applied" + +# Step 3: Deploy services +echo "" +echo "Step 3/5: Deploying microservices..." + +SERVICES=("execute-service" "data-service" "news-service" "api-gateway" "dashboard") + +for service in "${SERVICES[@]}"; do + echo " Deploying $service..." + kubectl apply -f "$PROJECT_ROOT/deploy/k8s/base/${service}-deployment.yaml" + kubectl apply -f "$PROJECT_ROOT/deploy/k8s/base/${service}-service.yaml" +done + +echo " ✓ All services deployed" + +# Step 4: Deploy ingress and networking +echo "" +echo "Step 4/5: Configuring ingress and networking..." +kubectl apply -f "$PROJECT_ROOT/deploy/k8s/base/ingress.yaml" +echo " ✓ Ingress configured" + +# Apply NetworkPolicies from security review +if [[ -d "$PROJECT_ROOT/trading-platform/security/network-policies" ]]; then + kubectl apply -f "$PROJECT_ROOT/trading-platform/security/network-policies/" + echo " ✓ NetworkPolicies applied" +fi + +# Step 5: Wait for rollouts +echo "" +echo "Step 5/5: Waiting for deployments to stabilize..." + +for service in "${SERVICES[@]}"; do + echo " Waiting for $service..." + if ! kubectl rollout status "deployment/${service}" -n "$NAMESPACE" --timeout=5m; then + echo "WARNING: $service rollout timed out" + echo " Check pods: kubectl get pods -n $NAMESPACE -l app=$service" + echo " Check logs: kubectl logs -n $NAMESPACE -l app=$service --tail=100" + exit 1 + fi +done + +echo "" +echo "========================================================" +echo " Deployment complete! All services running." +echo "========================================================" +echo "" +echo "Useful commands:" +echo " kubectl get pods -n $NAMESPACE" +echo " kubectl get svc -n $NAMESPACE" +echo " kubectl get ingress -n $NAMESPACE" +echo " kubectl logs -n $NAMESPACE -l app=$service -f" diff --git a/trading-platform/deploy/scripts/generate-mtls-certs.sh b/trading-platform/deploy/scripts/generate-mtls-certs.sh new file mode 100755 index 0000000..a43b697 --- /dev/null +++ b/trading-platform/deploy/scripts/generate-mtls-certs.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# ============================================================================= +# Manual mTLS certificate generation script (for dev/testing only) +# ============================================================================= +# In production, use cert-manager (see k8s/base/cert-manager/). +# This script generates self-signed certificates for local testing. +# +# Usage: +# ./deploy/scripts/generate-mtls-certs.sh +# +# Output: deploy/mtls/ +# ============================================================================= + +set -euo pipefail + +OUTPUT_DIR="deploy/mtls" +SERVICES=("execute-service" "data-service" "news-service" "api-gateway" "dashboard") +DAYS_VALID=365 + +mkdir -p "$OUTPUT_DIR/ca" "$OUTPUT_DIR/certs" + +# ── Generate Root CA ──────────────────────────────────────────────────────── +echo "Generating Root CA..." +openssl genrsa -out "$OUTPUT_DIR/ca/ca.key" 4096 2>/dev/null +openssl req -x509 -new -nodes \ + -key "$OUTPUT_DIR/ca/ca.key" \ + -sha256 \ + -days $DAYS_VALID \ + -out "$OUTPUT_DIR/ca/ca.crt" \ + -subj "/C=US/ST=California/O=TradingPlatform/CN=Trading Platform Root CA" + +# ── Generate Service Certificates ──────────────────────────────────────────── +for SERVICE in "${SERVICES[@]}"; do + echo "Generating certificate for $SERVICE..." + + # Generate private key + openssl genrsa \ + -out "$OUTPUT_DIR/certs/${SERVICE}.key" 2048 2>/dev/null + + # Generate CSR + openssl req -new \ + -key "$OUTPUT_DIR/certs/${SERVICE}.key" \ + -out "$OUTPUT_DIR/certs/${SERVICE}.csr" \ + -subj "/C=US/ST=California/O=TradingPlatform/CN=${SERVICE}.customer1.svc.cluster.local" \ + -addext "subjectAltName=DNS:${SERVICE},DNS:${SERVICE}.customer1,DNS:${SERVICE}.customer1.svc.cluster.local" + + # Sign with CA + openssl x509 -req \ + -in "$OUTPUT_DIR/certs/${SERVICE}.csr" \ + -CA "$OUTPUT_DIR/ca/ca.crt" \ + -CAkey "$OUTPUT_DIR/ca/ca.key" \ + -CAcreateserial \ + -out "$OUTPUT_DIR/certs/${SERVICE}.crt" \ + -days $DAYS_VALID \ + -sha256 \ + -extfile <(printf "subjectAltName=DNS:${SERVICE},DNS:${SERVICE}.customer1,DNS:${SERVICE}.customer1.svc.cluster.local") + + # Clean up CSR + rm "$OUTPUT_DIR/certs/${SERVICE}.csr" +done + +echo "Done! All certificates generated in $OUTPUT_DIR/certs/" +echo "CA certificate: $OUTPUT_DIR/ca/ca.crt" +echo "" +echo "To verify a certificate:" +echo " openssl verify -CAfile $OUTPUT_DIR/ca/ca.crt $OUTPUT_DIR/certs/.crt" diff --git a/trading-platform/dockerfiles/dashboard/.dockerignore b/trading-platform/dockerfiles/dashboard/.dockerignore new file mode 100644 index 0000000..4443f35 --- /dev/null +++ b/trading-platform/dockerfiles/dashboard/.dockerignore @@ -0,0 +1,10 @@ +node_modules/ +.next/ +out/ +dist/ +.env* +.git/ +.vscode/ +coverage/ +.idea/ +*.log diff --git a/trading-platform/dockerfiles/dashboard/Dockerfile b/trading-platform/dockerfiles/dashboard/Dockerfile new file mode 100644 index 0000000..5f5d93a --- /dev/null +++ b/trading-platform/dockerfiles/dashboard/Dockerfile @@ -0,0 +1,31 @@ +# Multi-stage build for Next.js dashboard +# ---- Builder ---- +FROM node:20-alpine AS builder +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci +COPY . . +RUN npm run build + +# ---- Production ---- +FROM node:20-alpine AS runner +ENV NODE_ENV=production + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +WORKDIR /app +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +HEALTHCHECK --interval=15s --timeout=5s --start-period=20s --retries=3 \\ + CMD wget --no-verbose --tries=1 --spider http://localhost:${PORT}/ || exit 1 + +CMD ["node", "server.js"] diff --git a/trading-platform/dockerfiles/data-service/.dockerignore b/trading-platform/dockerfiles/data-service/.dockerignore new file mode 100644 index 0000000..be876f2 --- /dev/null +++ b/trading-platform/dockerfiles/data-service/.dockerignore @@ -0,0 +1,10 @@ +# Python Docker ignores +__pycache__/ +*.pyc +.venv/ +venv/ +*.egg-info/ +.pytest_cache/ +.git/ +.env +tests/ diff --git a/trading-platform/dockerfiles/data-service/Dockerfile b/trading-platform/dockerfiles/data-service/Dockerfile new file mode 100644 index 0000000..6b9cde4 --- /dev/null +++ b/trading-platform/dockerfiles/data-service/Dockerfile @@ -0,0 +1,23 @@ +# Multi-stage build for data-service (Postgres + Redis + Kafka consumers) +FROM python:3.12-slim AS builder +WORKDIR /build +COPY pyproject.toml . +RUN pip install --no-cache-dir --prefix=/install . + +FROM python:3.12-slim +RUN useradd -m --system appuser + +COPY --from=builder /install /usr/local + +WORKDIR /app +COPY --chown=appuser:appuser data_service/ ./data_service/ +COPY --chown=appuser:appuser pyproject.toml alembic.ini ./ + +USER appuser + +HEALTHCHECK --interval=15s --timeout=5s --start-period=15s --retries=3 \\ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1 + +EXPOSE 8000 + +CMD ["uvicorn", "data_service.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/trading-platform/dockerfiles/execute-service/.dockerignore b/trading-platform/dockerfiles/execute-service/.dockerignore new file mode 100644 index 0000000..be876f2 --- /dev/null +++ b/trading-platform/dockerfiles/execute-service/.dockerignore @@ -0,0 +1,10 @@ +# Python Docker ignores +__pycache__/ +*.pyc +.venv/ +venv/ +*.egg-info/ +.pytest_cache/ +.git/ +.env +tests/ diff --git a/trading-platform/dockerfiles/execute-service/Dockerfile b/trading-platform/dockerfiles/execute-service/Dockerfile new file mode 100644 index 0000000..e64268b --- /dev/null +++ b/trading-platform/dockerfiles/execute-service/Dockerfile @@ -0,0 +1,23 @@ +# Multi-stage build for execute-service (Hyperliquid + Solana trading engine) +FROM python:3.12-slim AS builder +WORKDIR /build +COPY pyproject.toml ./ +RUN pip install --no-cache-dir --prefix=/install . + +FROM python:3.12-slim +RUN useradd -m --system appuser + +COPY --from=builder /install /usr/local + +WORKDIR /app +COPY --chown=appuser:appuser app/ ./app/ +COPY --chown=appuser:appuser pyproject.toml ./ + +USER appuser + +HEALTHCHECK --interval=15s --timeout=5s --start-period=15s --retries=3 \\ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1 + +EXPOSE 8000 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/trading-platform/dockerfiles/news-service/.dockerignore b/trading-platform/dockerfiles/news-service/.dockerignore new file mode 100644 index 0000000..be876f2 --- /dev/null +++ b/trading-platform/dockerfiles/news-service/.dockerignore @@ -0,0 +1,10 @@ +# Python Docker ignores +__pycache__/ +*.pyc +.venv/ +venv/ +*.egg-info/ +.pytest_cache/ +.git/ +.env +tests/ diff --git a/trading-platform/dockerfiles/news-service/Dockerfile b/trading-platform/dockerfiles/news-service/Dockerfile new file mode 100644 index 0000000..286969b --- /dev/null +++ b/trading-platform/dockerfiles/news-service/Dockerfile @@ -0,0 +1,30 @@ +# Multi-stage build for news-service (CNPG connector + Kafka producer) +FROM python:3.12-slim AS builder +WORKDIR /build +COPY requirements.txt . +RUN pip install --no-cache-dir --prefix=/install -r requirements.txt + +FROM python:3.12-slim +RUN useradd -m --system appuser + +# Copy installed deps from builder +COPY --from=builder /install /usr/local + +# Install system deps for NLTK +RUN apt-get update && \\ + apt-get install -y --no-install-recommends gcc libpq-dev && \\ + rm -rf /var/lib/apt/lists/* + +# Download NLTK data as root before switching user +RUN python -c "import nltk; nltk.download('punkt'); nltk.download('punkt_tab'); nltk.download('averaged_perceptron_tagger')" + +WORKDIR /app +COPY --chown=appuser:appuser . . +USER appuser + +HEALTHCHECK --interval=15s --timeout=5s --start-period=15s --retries=3 \\ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1 + +EXPOSE 8000 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"] diff --git a/trading-platform/helm/.sops.yaml b/trading-platform/helm/.sops.yaml new file mode 100644 index 0000000..35ae57e --- /dev/null +++ b/trading-platform/helm/.sops.yaml @@ -0,0 +1,19 @@ +# SOPS configuration for trading platform secrets +# Usage: sops -e -i trading-secrets.yaml && kubectl apply -f trading-secrets.yaml +creation_rules: + # Production secrets - encrypted with Age key + - path_regex: trading-secrets.yaml$ + encrypted_regex: "^(stringData|data)$" + age: >- + age1ql3z7hjy54pw3hyww5ayyfg7zqgvcnw3eks6lekrq733qkq76jwqgq3g20 + + # Per-environment overrides + - path_regex: .*/staging/trading-secrets.yaml$ + encrypted_regex: "^(stringData|data)$" + age: >- + age1ql3z7hjy54pw3hyww5ayyfg7zqgvcnq76jwqgq3g20 + + - path_regex: .*/production/trading-secrets.yaml$ + encrypted_regex: "^(stringData|data)$" + age: >- + age1ql3z7hjy54pw3hyww5ayyfg7zqgvcnq76jwqgq3g20 diff --git a/trading-platform/helm/trading-platform/.sops.yaml b/trading-platform/helm/trading-platform/.sops.yaml new file mode 100644 index 0000000..35ae57e --- /dev/null +++ b/trading-platform/helm/trading-platform/.sops.yaml @@ -0,0 +1,19 @@ +# SOPS configuration for trading platform secrets +# Usage: sops -e -i trading-secrets.yaml && kubectl apply -f trading-secrets.yaml +creation_rules: + # Production secrets - encrypted with Age key + - path_regex: trading-secrets.yaml$ + encrypted_regex: "^(stringData|data)$" + age: >- + age1ql3z7hjy54pw3hyww5ayyfg7zqgvcnw3eks6lekrq733qkq76jwqgq3g20 + + # Per-environment overrides + - path_regex: .*/staging/trading-secrets.yaml$ + encrypted_regex: "^(stringData|data)$" + age: >- + age1ql3z7hjy54pw3hyww5ayyfg7zqgvcnq76jwqgq3g20 + + - path_regex: .*/production/trading-secrets.yaml$ + encrypted_regex: "^(stringData|data)$" + age: >- + age1ql3z7hjy54pw3hyww5ayyfg7zqgvcnq76jwqgq3g20 diff --git a/trading-platform/helm/trading-platform/Chart.yaml b/trading-platform/helm/trading-platform/Chart.yaml new file mode 100644 index 0000000..533fb8e --- /dev/null +++ b/trading-platform/helm/trading-platform/Chart.yaml @@ -0,0 +1,12 @@ +apiVersion: v2 +name: trading-platform +description: Helm chart for the DEFi trading platform microservices on GKE +type: application +version: 0.1.0 +appVersion: "0.1.0" +keywords: + - trading + - defi + - microservices +maintainers: + - name: Trading Platform Team diff --git a/trading-platform/helm/trading-platform/templates/NOTES.txt b/trading-platform/helm/trading-platform/templates/NOTES.txt new file mode 100644 index 0000000..afc553a --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/NOTES.txt @@ -0,0 +1,29 @@ +Trading Platform deployed successfully! + +Namespace: {{ .Values.global.namespace }} +Release: {{ .Release.Name }} +Environment: {{ .Values.global.environment | default "not set" }} + +Services deployed: + - execute-service: {{ .Values.executeService.enabled }} + - news-service: {{ .Values.newsService.enabled }} + - data-service: {{ .Values.dataService.enabled }} + - dashboard: {{ .Values.dashboard.enabled }} + +Infrastructure: + - PostgreSQL: {{ .Values.postgres.enabled }} + - Redis: {{ .Values.redis.enabled }} + - Kafka: {{ .Values.kafka.enabled }} + +Ingress: {{ .Values.ingress.enabled }} +{{- if .Values.ingress.enabled }} +{{- range .Values.ingress.hosts }} + Host: {{ .host }} +{{- end }} +{{- end }} + +Next steps: +1. Verify pods: kubectl get pods -n {{ .Values.global.namespace }} +2. Check services: kubectl get svc -n {{ .Values.global.namespace }} +3. Check ingress: kubectl get ingress -n {{ .Values.global.namespace }} +4. If using SOPS, decrypt secrets: sops -d trading-secrets.yaml | kubectl apply -f - diff --git a/trading-platform/helm/trading-platform/templates/_helpers.tpl b/trading-platform/helm/trading-platform/templates/_helpers.tpl new file mode 100644 index 0000000..fa8c6d2 --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/_helpers.tpl @@ -0,0 +1,94 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "trading-platform.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "trading-platform.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "trading-platform.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "trading-platform.labels" -}} +helm.sh/chart: {{ include "trading-platform.chart" . }} +{{ include "trading-platform.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "trading-platform.selectorLabels" -}} +app.kubernetes.io/name: {{ include "trading-platform.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Service-specific labels +*/}} +{{- define "trading-platform.serviceLabels" -}} +{{- $service := index . 0 }} +{{- $parent := index . 1 }} +{{ include "trading-platform.selectorLabels" $parent }} +app: {{ $service.Values.name | default $service.Values.name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "trading-platform.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "trading-platform.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Generate env var map from values +*/}} +{{- define "trading-platform.envVars" -}} +{{- range $key, $value := . }} +- name: {{ $key }} + value: {{ $value | quote }} +{{- end }} +{{- end }} + +{{/* +Generate secret env var refs +*/}} +{{- define "trading-platform.secretEnvVars" -}} +{{- range $key, $secretRef := . }} +- name: {{ $key }} + valueFrom: + secretKeyRef: + {{- $parts := split "/" $secretRef }} + name: {{ $parts._0 }} + key: {{ $parts._1 }} +{{- end }} +{{- end }} diff --git a/trading-platform/helm/trading-platform/templates/cert-manager/certificates.yaml b/trading-platform/helm/trading-platform/templates/cert-manager/certificates.yaml new file mode 100644 index 0000000..fb86f8c --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/cert-manager/certificates.yaml @@ -0,0 +1,128 @@ +{{- if .Values.certManager.enabled }} +# External issuer (Let's Encrypt) for public-facing TLS +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: {{ .Values.certManager.externalIssuer.name }} +spec: + acme: + server: {{ .Values.certManager.externalIssuer.server }} + email: {{ .Values.certManager.externalIssuer.email }} + privateKeySecretRef: + name: {{ .Values.certManager.externalIssuer.name }}-key + solvers: + - http01: + ingress: + class: {{ .Values.ingress.className }} +--- +# Internal self-signed CA for service-to-service mTLS +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ .Values.certManager.internalIssuer.name }} + namespace: {{ .Values.global.namespace }} +spec: + selfSigned: {} +--- +# CA certificate issued by the self-signed issuer +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: trading-ca + namespace: {{ .Values.global.namespace }} +spec: + isCA: true + commonName: "trading-ca" + secretName: trading-ca-secret + privateKey: + algorithm: ECDSA + size: 256 + issuerRef: + name: {{ .Values.certManager.internalIssuer.name }} + kind: Issuer + group: cert-manager.io +--- +# CA issuer for signing service certificates +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: trading-ca-issuer + namespace: {{ .Values.global.namespace }} +spec: + ca: + secretName: trading-ca-secret +--- +# mTLS certificate for execute-service +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: execute-service-mtls + namespace: {{ .Values.global.namespace }} +spec: + dnsNames: + - execute-service + - execute-service.{{ .Values.global.namespace }}.svc.cluster.local + secretName: execute-service-mtls + privateKey: + algorithm: ECDSA + size: 256 + issuerRef: + name: trading-ca-issuer + kind: Issuer + group: cert-manager.io +--- +# mTLS certificate for news-service +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: news-service-mtls + namespace: {{ .Values.global.namespace }} +spec: + dnsNames: + - news-service + - news-service.{{ .Values.global.namespace }}.svc.cluster.local + secretName: news-service-mtls + privateKey: + algorithm: ECDSA + size: 256 + issuerRef: + name: trading-ca-issuer + kind: Issuer + group: cert-manager.io +--- +# mTLS certificate for data-service +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: data-service-mtls + namespace: {{ .Values.global.namespace }} +spec: + dnsNames: + - data-service + - data-service.{{ .Values.global.namespace }}.svc.cluster.local + secretName: data-service-mtls + privateKey: + algorithm: ECDSA + size: 256 + issuerRef: + name: trading-ca-issuer + kind: Issuer + group: cert-manager.io +--- +# TLS certificate for the public domain (ingress) +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: trading-tls + namespace: {{ .Values.global.namespace }} +spec: + secretName: trading-tls + dnsNames: + {{- range $host := .Values.ingress.hosts }} + - {{ $host.host }} + {{- end }} + issuerRef: + name: {{ .Values.certManager.externalIssuer.name }} + kind: ClusterIssuer + group: cert-manager.io +{{- end }} diff --git a/trading-platform/helm/trading-platform/templates/configmap.yaml b/trading-platform/helm/trading-platform/templates/configmap.yaml new file mode 100644 index 0000000..1e1ce4e --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/configmap.yaml @@ -0,0 +1,21 @@ +# Shared ConfigMap for trading platform configuration +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "trading-platform.fullname" . }}-config + namespace: {{ .Values.global.namespace }} + labels: + {{- include "trading-platform.labels" . | nindent 4 }} +data: + # Shared environment configuration + ENVIRONMENT: {{ .Values.global.environment | quote }} + CLUSTER_NAME: {{ .Values.global.clusterName | quote }} + NAMESPACE: {{ .Values.global.namespace | quote }} + # Kafka bootstrap (internal DNS) + KAFKA_BOOTSTRAP: kafka-headless.{{ .Values.global.namespace }}.svc.cluster.local:9092 + # Redis connection + REDIS_HOST: redis-master.{{ .Values.global.namespace }}.svc.cluster.local + REDIS_PORT: "6379" + # PostgreSQL connection + POSTGRES_HOST: postgres-primary.{{ .Values.global.namespace }}.svc.cluster.local + POSTGRES_PORT: "5432" diff --git a/trading-platform/helm/trading-platform/templates/dashboard/deployment.yaml b/trading-platform/helm/trading-platform/templates/dashboard/deployment.yaml new file mode 100644 index 0000000..aeffdd0 --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/dashboard/deployment.yaml @@ -0,0 +1,64 @@ +{{- if .Values.dashboard.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.dashboard.name }} + namespace: {{ .Values.global.namespace }} + labels: + {{- include "trading-platform.labels" . | nindent 4 }} + app: {{ .Values.dashboard.name }} +spec: + {{- if not .Values.dashboard.autoscaling.enabled }} + replicas: {{ .Values.global.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "trading-platform.selectorLabels" . | nindent 6 }} + app: {{ .Values.dashboard.name }} + template: + metadata: + labels: + {{- include "trading-platform.selectorLabels" . | nindent 8 }} + app: {{ .Values.dashboard.name }} + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "{{ .Values.dashboard.port }}" + spec: + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Values.dashboard.name }} + image: "{{ .Values.dashboard.image.repository }}:{{ .Values.dashboard.image.tag }}" + imagePullPolicy: {{ .Values.dashboard.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.dashboard.port }} + protocol: TCP + env: + {{- range $key, $value := .Values.dashboard.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + resources: + {{- toYaml .Values.dashboard.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.containerSecurityContext | nindent 12 }} + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 20 + periodSeconds: 10 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 15 + periodSeconds: 5 + timeoutSeconds: 3 + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/trading-platform/helm/trading-platform/templates/dashboard/service.yaml b/trading-platform/helm/trading-platform/templates/dashboard/service.yaml new file mode 100644 index 0000000..3b11066 --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/dashboard/service.yaml @@ -0,0 +1,40 @@ +{{- if .Values.dashboard.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.dashboard.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Values.dashboard.name }} +spec: + type: ClusterIP + ports: + - port: {{ .Values.dashboard.port }} + targetPort: http + protocol: TCP + name: http + selector: + app: {{ .Values.dashboard.name }} +--- +{{- if .Values.dashboard.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ .Values.dashboard.name }} + namespace: {{ .Values.global.namespace }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ .Values.dashboard.name }} + minReplicas: {{ .Values.dashboard.autoscaling.minReplicas }} + maxReplicas: {{ .Values.dashboard.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.dashboard.autoscaling.targetCPUUtilizationPercentage }} +{{- end }} +{{- end }} diff --git a/trading-platform/helm/trading-platform/templates/data-service/deployment.yaml b/trading-platform/helm/trading-platform/templates/data-service/deployment.yaml new file mode 100644 index 0000000..0463b4a --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/data-service/deployment.yaml @@ -0,0 +1,89 @@ +{{- if .Values.dataService.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.dataService.name }} + namespace: {{ .Values.global.namespace }} + labels: + {{- include "trading-platform.labels" . | nindent 4 }} + app: {{ .Values.dataService.name }} +spec: + {{- if not .Values.dataService.autoscaling.enabled }} + replicas: {{ .Values.global.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "trading-platform.selectorLabels" . | nindent 6 }} + app: {{ .Values.dataService.name }} + template: + metadata: + labels: + {{- include "trading-platform.selectorLabels" . | nindent 8 }} + app: {{ .Values.dataService.name }} + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "{{ .Values.dataService.port }}" + spec: + serviceAccountName: {{ .Values.dataService.name }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Values.dataService.name }} + image: "{{ .Values.dataService.image.repository }}:{{ .Values.dataService.image.tag }}" + imagePullPolicy: {{ .Values.dataService.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.dataService.port }} + protocol: TCP + env: + {{- range $key, $value := .Values.dataService.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + - name: DB_HOST + valueFrom: + configMapKeyRef: + name: {{ include "trading-platform.fullname" . }}-config + key: POSTGRES_HOST + - name: REDIS_HOST + valueFrom: + configMapKeyRef: + name: {{ include "trading-platform.fullname" . }}-config + key: REDIS_HOST + - name: KAFKA_BOOTSTRAP_SERVERS + valueFrom: + configMapKeyRef: + name: {{ include "trading-platform.fullname" . }}-config + key: KAFKA_BOOTSTRAP + envFrom: + - secretRef: + name: trading-secrets + resources: + {{- toYaml .Values.dataService.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.containerSecurityContext | nindent 12 }} + volumeMounts: + - name: tmp + mountPath: /tmp + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + volumes: + - name: tmp + emptyDir: {} + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/trading-platform/helm/trading-platform/templates/data-service/service.yaml b/trading-platform/helm/trading-platform/templates/data-service/service.yaml new file mode 100644 index 0000000..73dd9f0 --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/data-service/service.yaml @@ -0,0 +1,48 @@ +{{- if .Values.dataService.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.dataService.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Values.dataService.name }} +spec: + type: ClusterIP + ports: + - port: {{ .Values.dataService.port }} + targetPort: http + protocol: TCP + name: http + selector: + app: {{ .Values.dataService.name }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.dataService.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Values.dataService.name }} +--- +{{- if .Values.dataService.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ .Values.dataService.name }} + namespace: {{ .Values.global.namespace }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ .Values.dataService.name }} + minReplicas: {{ .Values.dataService.autoscaling.minReplicas }} + maxReplicas: {{ .Values.dataService.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.dataService.autoscaling.targetCPUUtilizationPercentage }} +{{- end }} +{{- end }} diff --git a/trading-platform/helm/trading-platform/templates/execute-service/deployment.yaml b/trading-platform/helm/trading-platform/templates/execute-service/deployment.yaml new file mode 100644 index 0000000..fcb5d43 --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/execute-service/deployment.yaml @@ -0,0 +1,112 @@ +{{- if .Values.executeService.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.executeService.name }} + namespace: {{ .Values.global.namespace }} + labels: + {{- include "trading-platform.labels" . | nindent 4 }} + app: {{ .Values.executeService.name }} +spec: + {{- if not .Values.executeService.autoscaling.enabled }} + replicas: {{ .Values.global.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "trading-platform.selectorLabels" . | nindent 6 }} + app: {{ .Values.executeService.name }} + template: + metadata: + labels: + {{- include "trading-platform.selectorLabels" . | nindent 8 }} + app: {{ .Values.executeService.name }} + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "{{ .Values.executeService.port }}" + spec: + serviceAccountName: {{ .Values.executeService.name }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Values.executeService.name }} + image: "{{ .Values.executeService.image.repository }}:{{ .Values.executeService.image.tag }}" + imagePullPolicy: {{ .Values.executeService.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.executeService.port }} + protocol: TCP + env: + {{- range $key, $value := .Values.executeService.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + # Shared config from ConfigMap + - name: DB_HOST + valueFrom: + configMapKeyRef: + name: {{ include "trading-platform.fullname" . }}-config + key: POSTGRES_HOST + - name: REDIS_HOST + valueFrom: + configMapKeyRef: + name: {{ include "trading-platform.fullname" . }}-config + key: REDIS_HOST + - name: KAFKA_BOOTSTRAP_SERVERS + valueFrom: + configMapKeyRef: + name: {{ include "trading-platform.fullname" . }}-config + key: KAFKA_BOOTSTRAP + envFrom: + - secretRef: + name: trading-secrets + resources: + {{- toYaml .Values.executeService.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.containerSecurityContext | nindent 12 }} + volumeMounts: + - name: tmp + mountPath: /tmp + - name: mtls-certs + mountPath: /etc/mtls + readOnly: true + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + volumes: + - name: tmp + emptyDir: {} + - name: mtls-certs + secret: + secretName: execute-service-mtls + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + nodeSelector: + kubernetes.io/os: linux + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - {{ .Values.executeService.name }} + topologyKey: kubernetes.io/hostname +{{- end }} diff --git a/trading-platform/helm/trading-platform/templates/execute-service/service.yaml b/trading-platform/helm/trading-platform/templates/execute-service/service.yaml new file mode 100644 index 0000000..575ae85 --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/execute-service/service.yaml @@ -0,0 +1,50 @@ +{{- if .Values.executeService.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.executeService.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Values.executeService.name }} +spec: + type: ClusterIP + ports: + - port: {{ .Values.executeService.port }} + targetPort: http + protocol: TCP + name: http + selector: + app: {{ .Values.executeService.name }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.executeService.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Values.executeService.name }} + annotations: + iam.gke.io/gcp-service-account: execute-service@{{ .Values.global.clusterName }}.iam.gserviceaccount.com +--- +{{- if .Values.executeService.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ .Values.executeService.name }} + namespace: {{ .Values.global.namespace }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ .Values.executeService.name }} + minReplicas: {{ .Values.executeService.autoscaling.minReplicas }} + maxReplicas: {{ .Values.executeService.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.executeService.autoscaling.targetCPUUtilizationPercentage }} +{{- end }} +{{- end }} diff --git a/trading-platform/helm/trading-platform/templates/infrastructure/kafka.yaml b/trading-platform/helm/trading-platform/templates/infrastructure/kafka.yaml new file mode 100644 index 0000000..cccd908 --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/infrastructure/kafka.yaml @@ -0,0 +1,142 @@ +{{- if .Values.kafka.enabled }} +# Headless service for KRaft discovery +apiVersion: v1 +kind: Service +metadata: + name: kafka-headless + namespace: {{ .Values.global.namespace }} + labels: + app: kafka +spec: + ports: + - port: 9092 + targetPort: 9092 + name: kafka + - port: 9093 + targetPort: 9093 + name: controller + clusterIP: None + selector: + app: kafka +--- +# Client-facing service +apiVersion: v1 +kind: Service +metadata: + name: kafka + namespace: {{ .Values.global.namespace }} + labels: + app: kafka +spec: + type: ClusterIP + ports: + - port: 9092 + targetPort: 9092 + name: kafka + selector: + app: kafka +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: kafka + namespace: {{ .Values.global.namespace }} + labels: + app: kafka +spec: + serviceName: kafka-headless + replicas: {{ .Values.kafka.replicaCount }} + selector: + matchLabels: + app: kafka + template: + metadata: + labels: + app: kafka + spec: + securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + containers: + - name: kafka + image: "{{ .Values.kafka.image.repository }}:{{ .Values.kafka.image.tag }}" + ports: + - containerPort: 9092 + name: kafka + - containerPort: 9093 + name: controller + env: + - name: KAFKA_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: KAFKA_PROCESS_ROLES + value: "broker,controller" + - name: KAFKA_CONTROLLER_QUORUM_VOTERS + value: | + {{- $ns := .Values.global.namespace }} + {{- range $i := until (int .Values.kafka.replicaCount) }} + {{- $i }}@kafka-{{ $i }}.kafka-headless.{{ $ns }}.svc.cluster.local:9093{{ if lt (add $i 1) (int $.Values.kafka.replicaCount) }},{{ end }} + {{- end }} + - name: KAFKA_CONTROLLER_LISTENER_NAMES + value: "CONTROLLER" + - name: KAFKA_LISTENERS + value: "PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093" + - name: KAFKA_ADVERTISED_LISTENERS + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: KAFKA_LISTENER_SECURITY_PROTOCOL_MAP + value: "PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT" + - name: KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR + value: "{{ .Values.kafka.replicaCount }}" + - name: KAFKA_TRANSACTION_STATE_LOG_MIN_ISR + value: "{{ add1 (div (int .Values.kafka.replicaCount) 2) }}" + - name: KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR + value: "{{ .Values.kafka.replicaCount }}" + - name: KAFKA_AUTO_CREATE_TOPICS_ENABLE + value: "true" + - name: KAFKA_NUM_PARTITIONS + value: "6" + - name: KAFKA_DEFAULT_REPLICATION_FACTOR + value: "{{ .Values.kafka.replicaCount }}" + - name: KAFKA_LOG_DIRS + value: "/var/lib/kafka/data" + resources: + {{- toYaml .Values.kafka.resources | nindent 12 }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + volumeMounts: + - name: kafka-data + mountPath: /var/lib/kafka/data + - name: tmp + mountPath: /tmp + livenessProbe: + tcpSocket: + port: kafka + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + readinessProbe: + tcpSocket: + port: kafka + initialDelaySeconds: 20 + periodSeconds: 5 + timeoutSeconds: 3 + volumes: + - name: tmp + emptyDir: {} + volumeClaimTemplates: + - metadata: + name: kafka-data + spec: + accessModes: ["ReadWriteOnce"] + storageClassName: {{ .Values.kafka.storage.storageClass }} + resources: + requests: + storage: {{ .Values.kafka.storage.size }} +{{- end }} diff --git a/trading-platform/helm/trading-platform/templates/infrastructure/postgres.yaml b/trading-platform/helm/trading-platform/templates/infrastructure/postgres.yaml new file mode 100644 index 0000000..8567095 --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/infrastructure/postgres.yaml @@ -0,0 +1,111 @@ +{{- if .Values.postgres.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: postgres-primary + namespace: {{ .Values.global.namespace }} + labels: + app: postgres +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: postgres + protocol: TCP + name: postgres + selector: + app: postgres + role: primary +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: postgres + namespace: {{ .Values.global.namespace }} + labels: + app: postgres +spec: + serviceName: postgres-primary + replicas: {{ .Values.postgres.primary.replicaCount }} + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + role: primary + spec: + securityContext: + runAsNonRoot: true + runAsUser: 999 + runAsGroup: 999 + fsGroup: 999 + containers: + - name: postgres + image: "{{ .Values.postgres.image.repository }}:{{ .Values.postgres.image.tag }}" + ports: + - containerPort: 5432 + name: postgres + env: + - name: POSTGRES_USER + value: "trading" + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: trading-secrets + key: POSTGRES_PASSWORD + - name: POSTGRES_DB + value: {{ .Values.postgres.postgresqlDatabase | quote }} + resources: + {{- toYaml .Values.postgres.primary.resources | nindent 12 }} + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + - name: tmp + mountPath: /tmp + - name: run + mountPath: /var/run + livenessProbe: + exec: + command: + - pg_isready + - -U + - trading + - -d + - {{ .Values.postgres.postgresqlDatabase }} + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - pg_isready + - -U + - trading + - -d + - {{ .Values.postgres.postgresqlDatabase }} + initialDelaySeconds: 15 + periodSeconds: 5 + timeoutSeconds: 3 + volumes: + - name: tmp + emptyDir: {} + - name: run + emptyDir: {} + volumeClaimTemplates: + - metadata: + name: postgres-data + spec: + accessModes: ["ReadWriteOnce"] + storageClassName: {{ .Values.postgres.storage.storageClass }} + resources: + requests: + storage: {{ .Values.postgres.storage.size }} +{{- end }} diff --git a/trading-platform/helm/trading-platform/templates/infrastructure/redis.yaml b/trading-platform/helm/trading-platform/templates/infrastructure/redis.yaml new file mode 100644 index 0000000..52032a0 --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/infrastructure/redis.yaml @@ -0,0 +1,107 @@ +{{- if .Values.redis.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: redis-master + namespace: {{ .Values.global.namespace }} + labels: + app: redis +spec: + type: ClusterIP + ports: + - port: 6379 + targetPort: redis + protocol: TCP + name: redis + selector: + app: redis +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: redis + namespace: {{ .Values.global.namespace }} + labels: + app: redis +spec: + serviceName: redis-master + replicas: {{ .Values.redis.replicaCount }} + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + securityContext: + runAsNonRoot: true + runAsUser: 999 + runAsGroup: 999 + fsGroup: 999 + containers: + - name: redis + image: "{{ .Values.redis.image.repository }}:{{ .Values.redis.image.tag }}" + command: + - redis-server + - --appendonly + - "yes" + - --save + - "" + - --requirepass + - $(REDIS_PASSWORD) + ports: + - containerPort: 6379 + name: redis + env: + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: trading-secrets + key: REDIS_PASSWORD + resources: + {{- toYaml .Values.redis.master.resources | nindent 12 }} + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + volumeMounts: + - name: redis-data + mountPath: /data + - name: tmp + mountPath: /tmp + livenessProbe: + exec: + command: + - redis-cli + - -a + - $(REDIS_PASSWORD) + - ping + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - redis-cli + - -a + - $(REDIS_PASSWORD) + - ping + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + volumes: + - name: tmp + emptyDir: {} + volumeClaimTemplates: + - metadata: + name: redis-data + spec: + accessModes: ["ReadWriteOnce"] + storageClassName: {{ .Values.redis.master.storage.storageClass }} + resources: + requests: + storage: {{ .Values.redis.master.storage.size }} +{{- end }} diff --git a/trading-platform/helm/trading-platform/templates/ingress.yaml b/trading-platform/helm/trading-platform/templates/ingress.yaml new file mode 100644 index 0000000..965425d --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/ingress.yaml @@ -0,0 +1,48 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "trading-platform.fullname" . }} + namespace: {{ .Values.global.namespace }} + labels: + {{- include "trading-platform.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + rules: + {{- range $host := .Values.ingress.hosts }} + - host: {{ $host.host | quote }} + http: + paths: + {{- range $path := $host.paths }} + - path: {{ $path.path }} + pathType: {{ $path.pathType }} + backend: + service: + name: {{ $path.service }} + port: + {{- if eq $path.service "dashboard" }} + number: {{ $.Values.dashboard.port }} + {{- else if eq $path.service "execute-service" }} + number: {{ $.Values.executeService.port }} + {{- else if eq $path.service "news-service" }} + number: {{ $.Values.newsService.port }} + {{- else if eq $path.service "data-service" }} + number: {{ $.Values.dataService.port }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/trading-platform/helm/trading-platform/templates/namespace.yaml b/trading-platform/helm/trading-platform/templates/namespace.yaml new file mode 100644 index 0000000..c0c9257 --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/namespace.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: {{ .Values.global.namespace }} + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- if .Values.global.environment }} + environment: {{ .Values.global.environment | quote }} + {{- end }} + {{- if .Values.global.clusterName }} + cluster: {{ .Values.global.clusterName | quote }} + {{- end }} diff --git a/trading-platform/helm/trading-platform/templates/network-policies/network-policies.yaml b/trading-platform/helm/trading-platform/templates/network-policies/network-policies.yaml new file mode 100644 index 0000000..76fb484 --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/network-policies/network-policies.yaml @@ -0,0 +1,311 @@ +{{- if .Values.networkPolicies.enabled }} +{{- if .Values.networkPolicies.defaultDenyIngress }} +# Deny all ingress traffic by default +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-all-ingress + namespace: {{ .Values.global.namespace }} + labels: + app.kubernetes.io/managed-by: security-audit +spec: + podSelector: {} + policyTypes: + - Ingress +--- +{{- end }} +{{- if .Values.networkPolicies.defaultDenyEgress }} +# Deny all egress traffic by default +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-all-egress + namespace: {{ .Values.global.namespace }} + labels: + app.kubernetes.io/managed-by: security-audit +spec: + podSelector: {} + policyTypes: + - Egress +--- +{{- end }} +{{- if .Values.networkPolicies.allowDNS }} +# Allow all pods to resolve DNS +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-dns-egress + namespace: {{ .Values.global.namespace }} +spec: + podSelector: {} + policyTypes: + - Egress + egress: + - to: [] + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP +--- +{{- end }} +# Execute Service networking +{{- if .Values.executeService.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: execute-service-networking + namespace: {{ .Values.global.namespace }} +spec: + podSelector: + matchLabels: + app: execute-service + policyTypes: + - Ingress + - Egress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + podSelector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + - namespaceSelector: {} + podSelector: + matchLabels: + app.kubernetes.io/name: gce-proxy + ports: + - port: {{ .Values.executeService.port }} + protocol: TCP + - from: + - podSelector: + matchLabels: + app: news-service + ports: + - port: {{ .Values.executeService.port }} + protocol: TCP + egress: + - to: + - podSelector: + matchLabels: + app: postgres + ports: + - port: 5432 + protocol: TCP + - to: + - podSelector: + matchLabels: + app: redis + ports: + - port: 6379 + protocol: TCP + - to: + - podSelector: + matchLabels: + app: kafka + ports: + - port: 9092 + protocol: TCP +--- +{{- end }} +# News Service networking +{{- if .Values.newsService.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: news-service-networking + namespace: {{ .Values.global.namespace }} +spec: + podSelector: + matchLabels: + app: news-service + policyTypes: + - Ingress + - Egress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + podSelector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + ports: + - port: {{ .Values.newsService.port }} + protocol: TCP + egress: + - to: + - podSelector: + matchLabels: + cnpg.io/cluster: customer1-cnpg + ports: + - port: 5432 + protocol: TCP + - to: + - podSelector: + matchLabels: + app: kafka + ports: + - port: 9092 + protocol: TCP + - to: + - podSelector: + matchLabels: + app: execute-service + ports: + - port: {{ .Values.executeService.port }} + protocol: TCP +--- +{{- end }} +# Database networking +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: postgres-networking + namespace: {{ .Values.global.namespace }} +spec: + podSelector: + matchLabels: + app: postgres + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: execute-service + - podSelector: + matchLabels: + app: data-service + ports: + - port: 5432 + protocol: TCP + egress: [] +--- +# Redis networking +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: redis-networking + namespace: {{ .Values.global.namespace }} +spec: + podSelector: + matchLabels: + app: redis + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + app: execute-service + - podSelector: + matchLabels: + app: data-service + ports: + - port: 6379 + protocol: TCP +--- +# Kafka networking +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: kafka-networking + namespace: {{ .Values.global.namespace }} +spec: + podSelector: + matchLabels: + app: kafka + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + app: execute-service + - podSelector: + matchLabels: + app: news-service + - podSelector: + matchLabels: + app: data-service + ports: + - port: 9092 + protocol: TCP + - port: 9093 + protocol: TCP +--- +# Dashboard networking +{{- if .Values.dashboard.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: dashboard-networking + namespace: {{ .Values.global.namespace }} +spec: + podSelector: + matchLabels: + app: dashboard + policyTypes: + - Ingress + - Egress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + ports: + - port: {{ .Values.dashboard.port }} + protocol: TCP + egress: [] +--- +{{- end }} +# Data Service networking +{{- if .Values.dataService.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: data-service-networking + namespace: {{ .Values.global.namespace }} +spec: + podSelector: + matchLabels: + app: data-service + policyTypes: + - Ingress + - Egress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + ports: + - port: {{ .Values.dataService.port }} + protocol: TCP + egress: + - to: + - podSelector: + matchLabels: + app: postgres + ports: + - port: 5432 + protocol: TCP + - to: + - podSelector: + matchLabels: + app: redis + ports: + - port: 6379 + protocol: TCP + - to: + - podSelector: + matchLabels: + app: kafka + ports: + - port: 9092 + protocol: TCP +--- +{{- end }} +{{- end }} diff --git a/trading-platform/helm/trading-platform/templates/news-service/deployment.yaml b/trading-platform/helm/trading-platform/templates/news-service/deployment.yaml new file mode 100644 index 0000000..8ac48e2 --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/news-service/deployment.yaml @@ -0,0 +1,85 @@ +{{- if .Values.newsService.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.newsService.name }} + namespace: {{ .Values.global.namespace }} + labels: + {{- include "trading-platform.labels" . | nindent 4 }} + app: {{ .Values.newsService.name }} +spec: + {{- if not .Values.newsService.autoscaling.enabled }} + replicas: {{ .Values.global.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "trading-platform.selectorLabels" . | nindent 6 }} + app: {{ .Values.newsService.name }} + template: + metadata: + labels: + {{- include "trading-platform.selectorLabels" . | nindent 8 }} + app: {{ .Values.newsService.name }} + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "{{ .Values.newsService.port }}" + spec: + serviceAccountName: {{ .Values.newsService.name }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Values.newsService.name }} + image: "{{ .Values.newsService.image.repository }}:{{ .Values.newsService.image.tag }}" + imagePullPolicy: {{ .Values.newsService.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.newsService.port }} + protocol: TCP + env: + {{- range $key, $value := .Values.newsService.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + - name: KAFKA_BOOTSTRAP_SERVERS + valueFrom: + configMapKeyRef: + name: {{ include "trading-platform.fullname" . }}-config + key: KAFKA_BOOTSTRAP + envFrom: + - secretRef: + name: trading-secrets + resources: + {{- toYaml .Values.newsService.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.containerSecurityContext | nindent 12 }} + volumeMounts: + - name: tmp + mountPath: /tmp + - name: mtls-certs + mountPath: /etc/mtls + readOnly: true + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + volumes: + - name: tmp + emptyDir: {} + - name: mtls-certs + secret: + secretName: news-service-mtls + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/trading-platform/helm/trading-platform/templates/news-service/service.yaml b/trading-platform/helm/trading-platform/templates/news-service/service.yaml new file mode 100644 index 0000000..d24f4eb --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/news-service/service.yaml @@ -0,0 +1,48 @@ +{{- if .Values.newsService.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.newsService.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Values.newsService.name }} +spec: + type: ClusterIP + ports: + - port: {{ .Values.newsService.port }} + targetPort: http + protocol: TCP + name: http + selector: + app: {{ .Values.newsService.name }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.newsService.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Values.newsService.name }} +--- +{{- if .Values.newsService.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ .Values.newsService.name }} + namespace: {{ .Values.global.namespace }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ .Values.newsService.name }} + minReplicas: {{ .Values.newsService.autoscaling.minReplicas }} + maxReplicas: {{ .Values.newsService.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.newsService.autoscaling.targetCPUUtilizationPercentage }} +{{- end }} +{{- end }} diff --git a/trading-platform/helm/trading-platform/templates/secrets.yaml b/trading-platform/helm/trading-platform/templates/secrets.yaml new file mode 100644 index 0000000..9eaddbe --- /dev/null +++ b/trading-platform/helm/trading-platform/templates/secrets.yaml @@ -0,0 +1,45 @@ +# Trading Secrets - Encrypt with SOPS before applying +# Usage: sops -e -i trading-secrets.yaml && kubectl apply -f trading-secrets.yaml +apiVersion: v1 +kind: Secret +metadata: + name: trading-secrets + namespace: {{ .Values.global.namespace }} + labels: + {{- include "trading-platform.labels" . | nindent 4 }} + app.kubernetes.io/managed-by: security-audit + annotations: + # SOPS manages this file - edit with: sops trading-secrets.yaml + secrets.yaml.sops.io/v2: encrypted +type: Opaque +stringData: + # JWT authentication + EXECUTE_JWT_SECRET_KEY: {{ .Values.executeService.env.JWT_SECRET_KEY | default "" | quote }} + + # Exchange private keys + EXECUTE_HYPERLIQUID_PRIVATE_KEY: {{ .Values.executeService.env.HYPERLIQUID_PRIVATE_KEY | default "" | quote }} + EXECUTE_SOLANA_PRIVATE_KEY_BASE58: {{ .Values.executeService.env.SOLANA_PRIVATE_KEY_BASE58 | default "" | quote }} + + # Database credentials + POSTGRES_PASSWORD: {{ .Values.postgres.postgresqlPassword | default "" | quote }} + NEWS_DB_PASSWORD: {{ .Values.newsService.env.NEWS_DB_PASSWORD | default "" | quote }} + + # Redis + REDIS_PASSWORD: {{ .Values.redis.redisPassword | default "" | quote }} + + # Kafka SASL + KAFKA_SASL_USERNAME: {{ .Values.kafka.saslUsername | default "" | quote }} + KAFKA_SASL_PASSWORD: {{ .Values.kafka.saslPassword | default "" | quote }} + +--- +# mTLS certificates (generated by cert-manager, mounted as volumes) +# Service account tokens for SPIFFE/SPIRE identity +apiVersion: v1 +kind: Secret +metadata: + name: trading-mtls-certs + namespace: {{ .Values.global.namespace }} + labels: + {{- include "trading-platform.labels" . | nindent 4 }} +type: kubernetes.io/tls +# tls.crt and tls.key populated by cert-manager diff --git a/trading-platform/helm/trading-platform/trading-secrets.yaml b/trading-platform/helm/trading-platform/trading-secrets.yaml new file mode 100644 index 0000000..e74f932 --- /dev/null +++ b/trading-platform/helm/trading-platform/trading-secrets.yaml @@ -0,0 +1,33 @@ +# Trading Secrets - Encrypt with SOPS before applying +# Usage: sops -e -i trading-secrets.yaml && kubectl apply -f trading-secrets.yaml +# +# DO NOT commit unencrypted secrets to git! +# Generate secrets with: python -c "import secrets; print(secrets.token_urlsafe(24))" +apiVersion: v1 +kind: Secret +metadata: + name: trading-secrets + namespace: trading + labels: + app.kubernetes.io/managed-by: security-audit + annotations: + secrets.yaml.sops.io/v2: encrypted +type: Opaque +stringData: + # JWT authentication - use 32+ byte hex token + EXECUTE_JWT_SECRET_KEY: "" + + # Exchange private keys - NEVER commit plaintext! + EXECUTE_HYPERLIQUID_PRIVATE_KEY: "" + EXECUTE_SOLANA_PRIVATE_KEY_BASE58: "" + + # Database credentials - use 24+ byte URL-safe tokens + POSTGRES_PASSWORD: "" + NEWS_DB_PASSWORD: "" + + # Redis password + REDIS_PASSWORD: "" + + # Kafka SASL authentication + KAFKA_SASL_USERNAME: "" + KAFKA_SASL_PASSWORD: "" diff --git a/trading-platform/helm/trading-platform/values.yaml b/trading-platform/helm/trading-platform/values.yaml new file mode 100644 index 0000000..faeee27 --- /dev/null +++ b/trading-platform/helm/trading-platform/values.yaml @@ -0,0 +1,306 @@ +# Trading Platform Helm Chart - Default Values +# ============================================================ + +# Global settings +global: + namespace: customer1 + replicaCount: 2 + imagePullSecrets: [] + clusterName: customer1-gke + environment: staging + registry: us-central1-docker.pkg.dev/customer1-gke/trading + +# Resource defaults for all microservices +resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: "1" + memory: 512Mi + +# Pod security context defaults +podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault + +# Container security context defaults +containerSecurityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + +# ================================================================= +# Execute Service (Hyperliquid + Solana trading engine) +# ================================================================= +executeService: + enabled: true + name: execute-service + image: + repository: "" + tag: "0.1.0" + pullPolicy: IfNotPresent + port: 8000 + resources: + requests: + cpu: 500m + memory: 512Mi + limits: + cpu: "2" + memory: 1Gi + autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 70 + env: + LOG_LEVEL: "INFO" + KAFKA_BOOTSTRAP_SERVERS: "kafka-headless:9092" + KAFKA_TOPIC_TRADES: "trades.executed" + KAFKA_TOPIC_ORDERS: "orders.new" + REDIS_HOST: "redis-master" + REDIS_PORT: "6379" + DB_HOST: "postgres-primary" + DB_PORT: "5432" + DB_NAME: "trading_db" + +# ================================================================= +# News Service (CNPG connector + Kafka producer) +# ================================================================= +newsService: + enabled: true + name: news-service + image: + repository: "" + tag: "0.1.0" + pullPolicy: IfNotPresent + port: 8000 + resources: + requests: + cpu: 300m + memory: 384Mi + limits: + cpu: "1" + memory: 768Mi + autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 6 + targetCPUUtilizationPercentage: 75 + env: + LOG_LEVEL: "INFO" + DB_POOL_SIZE: "5" + DB_MAX_OVERFLOW: "10" + KAFKA_BOOTSTRAP_SERVERS: "kafka-headless:9092" + KAFKA_GROUP_ID: "news-analyzer" + KAFKA_TOPIC_ARTICLES: "news.articles" + KAFKA_TOPIC_ANALYSIS: "news.analysis" + API_PREFIX: "/api/v1" + CORS_ORIGINS: '["https://app.customer1.trading.com"]' + DB_HOST: "customer1-cnpg-r" + DB_PORT: "5432" + DB_NAME: "news" + +# ================================================================= +# Data Service (Postgres + Redis + Kafka consumers) +# ================================================================= +dataService: + enabled: true + name: data-service + image: + repository: "" + tag: "0.1.0" + pullPolicy: IfNotPresent + port: 8000 + resources: + requests: + cpu: 300m + memory: 384Mi + limits: + cpu: "1" + memory: 768Mi + autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 6 + targetCPUUtilizationPercentage: 70 + env: + LOG_LEVEL: "INFO" + DB_HOST: "postgres-primary" + DB_PORT: "5432" + DB_NAME: "trading_db" + REDIS_HOST: "redis-master" + REDIS_PORT: "6379" + KAFKA_BOOTSTRAP_SERVERS: "kafka-headless:9092" + KAFKA_CONSUMER_GROUP: "data-service" + +# ================================================================= +# Dashboard (Next.js frontend) +# ================================================================= +dashboard: + enabled: true + name: dashboard + image: + repository: "" + tag: "0.1.0" + pullPolicy: IfNotPresent + port: 3000 + resources: + requests: + cpu: 200m + memory: 256Mi + limits: + cpu: "500m" + memory: 512Mi + autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 8 + targetCPUUtilizationPercentage: 60 + env: + NODE_ENV: "production" + NEXT_PUBLIC_API_BASE_URL: "/api" + +# ================================================================= +# Ingress +# ================================================================= +ingress: + enabled: true + className: "gce" + annotations: + kubernetes.io/ingress.class: gce + kubernetes.io/ingress.allow-http: "true" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + hosts: + - host: app.customer1.trading.com + paths: + - path: / + pathType: Prefix + service: dashboard + - path: /api/trades + pathType: Prefix + service: execute-service + - path: /api/auth + pathType: Prefix + service: execute-service + - path: /api/news + pathType: Prefix + service: news-service + - path: /api/data + pathType: Prefix + service: data-service + tls: + - secretName: trading-tls + hosts: + - app.customer1.trading.com + +# ================================================================= +# Cert-Manager / mTLS +# ================================================================= +certManager: + enabled: true + externalIssuer: + name: letsencrypt-prod + server: https://acme-v02.api.letsencrypt.org/directory + email: ops@customer1.trading.com + internalIssuer: + name: trading-ca + +# ================================================================= +# PostgreSQL +# ================================================================= +postgres: + enabled: true + image: + repository: postgres + tag: "17-alpine" + primary: + replicaCount: 1 + resources: + requests: + cpu: "1" + memory: 2Gi + limits: + cpu: "2" + memory: 4Gi + storage: + size: 50Gi + storageClass: "standard-rwo" + postgresqlDatabase: trading_db + +# ================================================================= +# Redis +# ================================================================= +redis: + enabled: true + image: + repository: redis + tag: "7-alpine" + replicaCount: 3 + master: + resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: "500m" + memory: 512Mi + storage: + size: 10Gi + storageClass: "standard-rwo" + +# ================================================================= +# Kafka (KRaft mode) +# ================================================================= +kafka: + enabled: true + image: + repository: apache/kafka + tag: "3.9.0" + replicaCount: 3 + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: "1" + memory: 2Gi + storage: + size: 20Gi + storageClass: "standard-rwo" + topics: + - name: trades.executed + partitions: 6 + replicationFactor: 3 + - name: orders.new + partitions: 6 + replicationFactor: 3 + - name: news.articles + partitions: 6 + replicationFactor: 3 + - name: news.analysis + partitions: 6 + replicationFactor: 3 + +# ================================================================= +# Network Policies (from security audit) +# ================================================================= +networkPolicies: + enabled: true + defaultDenyIngress: true + defaultDenyEgress: true + allowDNS: true + +# ================================================================= +# SOPS Encryption +# ================================================================= +sops: + enabled: true + ageKeyFile: /etc/sops/age.key + secretsPath: trading-secrets.yaml diff --git a/trading-platform/helm/trading-secrets.yaml b/trading-platform/helm/trading-secrets.yaml new file mode 100644 index 0000000..d47e23f --- /dev/null +++ b/trading-platform/helm/trading-secrets.yaml @@ -0,0 +1,33 @@ +# Trading Secrets - Encrypt with SOPS before applying +# Usage: sops -e -i trading-secrets.yaml && kubectl apply -f trading-secrets.yaml +# +# DO NOT commit unencrypted secrets to git! +# Generate secrets with: python -c "import secrets; print(secrets.token_urlsafe(24))" +apiVersion: v1 +kind: Secret +metadata: + name: trading-secrets + namespace: customer1 + labels: + app.kubernetes.io/managed-by: security-audit + annotations: + secrets.yaml.sops.io/v2: encrypted +type: Opaque +stringData: + # JWT authentication - use 32+ byte hex token + EXECUTE_JWT_SECRET_KEY: "" + + # Exchange private keys - NEVER commit plaintext! + EXECUTE_HYPERLIQUID_PRIVATE_KEY: "" + EXECUTE_SOLANA_PRIVATE_KEY_BASE58: "" + + # Database credentials - use 24+ byte URL-safe tokens + POSTGRES_PASSWORD: "" + NEWS_DB_PASSWORD: "" + + # Redis password + REDIS_PASSWORD: "" + + # Kafka SASL authentication + KAFKA_SASL_USERNAME: "" + KAFKA_SASL_PASSWORD: ""