diff --git a/.github/workflows/publish-mcp-helm-chart.yml b/.github/workflows/publish-mcp-helm-chart.yml index cf465ee8..e03d1ec4 100644 --- a/.github/workflows/publish-mcp-helm-chart.yml +++ b/.github/workflows/publish-mcp-helm-chart.yml @@ -4,13 +4,16 @@ on: push: branches: - main - paths: + paths: &chart_paths - 'src/helm-charts/rosetta-mcp-server/**' - '.github/workflows/publish-mcp-helm-chart.yml' + pull_request: + paths: *chart_paths + workflow_dispatch: jobs: - package-helm-chart: + validate: permissions: contents: read runs-on: ubuntu-latest @@ -23,35 +26,42 @@ jobs: with: version: v3.20.2 - - name: Set environment variables - id: vars + - name: Install helm-unittest plugin + run: helm plugin install https://github.com/helm-unittest/helm-unittest.git --version 0.6.3 + + - name: Lint, template, and unit test chart run: | + CHART_PATH=./src/helm-charts/rosetta-mcp-server + helm lint "$CHART_PATH" + helm template helm-test "$CHART_PATH" >/dev/null + helm unittest "$CHART_PATH" + + publish: + needs: validate + if: github.event_name == 'push' && github.ref_name == 'main' + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd + + - name: Set up Helm + uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 + with: + version: v3.20.2 + + - name: Package and push Helm chart (OCI / Docker Hub) + run: | + set -euo pipefail CHART_PATH=./src/helm-charts/rosetta-mcp-server VERSION="$(grep -E '^version:' "${CHART_PATH}/Chart.yaml" | awk '{print $2}' | tr -d '"')" CHART_NAME="$(grep -E '^name:' "${CHART_PATH}/Chart.yaml" | awk '{print $2}' | tr -d '"')" REPOSITORY="registry-1.docker.io/griddynamics" - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - echo "chart_name=${CHART_NAME}" >> "$GITHUB_OUTPUT" - echo "chart_path=${CHART_PATH}" >> "$GITHUB_OUTPUT" - echo "repository=${REPOSITORY}" >> "$GITHUB_OUTPUT" echo "Package version: ${VERSION}" echo "Chart name: ${CHART_NAME}" echo "OCI registry prefix: oci://${REPOSITORY}" - - - name: Lint and syntax check chart - run: | - helm lint "${{ steps.vars.outputs.chart_path }}" - helm template helm-test "${{ steps.vars.outputs.chart_path }}" >/dev/null - - - name: Package and push Helm chart (OCI / Docker Hub) - if: github.ref_name == 'main' - run: | - set -euo pipefail - CHART="${{ steps.vars.outputs.chart_path }}" - VERSION="${{ steps.vars.outputs.version }}" - CHART_NAME="${{ steps.vars.outputs.chart_name }}" - REPOSITORY="${{ steps.vars.outputs.repository }}" - helm package "$CHART" --version "${VERSION}" + helm package "$CHART_PATH" --version "${VERSION}" PKG="${CHART_NAME}-${VERSION}.tgz" echo "${{ secrets.DOCKERHUB_OAT }}" | helm registry login registry-1.docker.io \ -u "${{ secrets.DOCKERHUB_LOGIN }}" \ diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md index 22f39d0a..a559095e 100644 --- a/DEPLOYMENT_GUIDE.md +++ b/DEPLOYMENT_GUIDE.md @@ -248,7 +248,53 @@ helm install rosetta-mcp ./src/helm-charts/rosetta-mcp-server \ 1. **Image** — `image.repository` defaults to `griddynamics/rosetta-mcp`; set `image.tag` or rely on the chart defaulting the tag to [`appVersion`](src/helm-charts/rosetta-mcp-server/Chart.yaml) in the Deployment template. 2. **Rosetta backend** — Set `env.vars` so `ROSETTA_SERVER_URL` resolves to Rosetta Server (in-cluster DNS or ingress URL). 3. **API key** — Supply `ROSETTA_API_KEY` via `env.secrets` (`secretKeyRef`). Create the Kubernetes Secret first or use `eso` to sync it. -4. **Ingress** — Set `ingress.host` and annotations. Defaults use an NGINX-style controller and placeholder host `rosetta-mcp.local`. +4. **Ingress** — Set `ingress.host`. The chart defaults to **Traefik** and also supports **nginx** as an alternative. + + **Traefik** (default) — rate limiting is enabled out of the box: + + ```yaml + ingress: + className: traefik + host: rosetta.example.com + traefik: + rateLimit: + average: 100 + burst: 200 + period: "1s" + ``` + + The chart creates a Traefik `Middleware` custom resource (`-rate-limit`) in the + release namespace and wires it into the Ingress annotation automatically (requires Traefik CRDs + to be installed in the cluster). + + To reference additional external middlewares (e.g. a platform-wide chain managed + outside this chart), list them in `ingress.traefik.middlewares`: + + ```yaml + ingress: + traefik: + middlewares: + - "traefik-my-chain@kubernetescrd" + rateLimit: + average: 100 + burst: 200 + period: "1s" + ``` + + External middlewares are rendered first in the annotation, followed by the + per-release rate limit. + + **nginx** (alternative) — set `className: nginx` and replace the `traefik` block with nginx annotations: + + ```yaml + ingress: + className: nginx + host: rosetta.example.com + annotations: + nginx.ingress.kubernetes.io/proxy-body-size: "10m" + nginx.ingress.kubernetes.io/limit-rps: "100" + nginx.ingress.kubernetes.io/limit-burst-multiplier: "2" + ``` 5. **TLS (production)** — Enable encrypted client traffic before production use. Uncomment and complete the [`ingress.tls`](src/helm-charts/rosetta-mcp-server/values.yaml) block in your overlay so Ingress terminates HTTPS with a TLS `Secret` (or terminate TLS upstream and align hostnames). HTTP-only defaults are unsuitable for production; OAuth and user trust depend on HTTPS. Full environment-variable semantics for OAuth, Redis, analytics, and modes are the same as the application runtime; see [rosetta-mcp-server — Configuration](src/rosetta-mcp-server/README.md#configuration). @@ -273,9 +319,12 @@ helm upgrade --install rosetta-mcp ./src/helm-charts/rosetta-mcp-server \ | [`templates/deployment.yaml`](src/helm-charts/rosetta-mcp-server/templates/deployment.yaml) | Deployment, env, resources | | [`templates/service.yaml`](src/helm-charts/rosetta-mcp-server/templates/service.yaml) | ClusterIP and session affinity | | [`templates/ingress.yaml`](src/helm-charts/rosetta-mcp-server/templates/ingress.yaml) | Optional Ingress | +| [`templates/traefik-middlewares.yaml`](src/helm-charts/rosetta-mcp-server/templates/traefik-middlewares.yaml) | Traefik Middleware CRDs (when `className: traefik`) | | [`templates/hpa.yaml`](src/helm-charts/rosetta-mcp-server/templates/hpa.yaml) | Optional HPA | +| [`templates/poddisruptionbudget.yaml`](src/helm-charts/rosetta-mcp-server/templates/poddisruptionbudget.yaml) | Optional PDB (when `replicaCount > 1`) | | [`templates/external-secret.yaml`](src/helm-charts/rosetta-mcp-server/templates/external-secret.yaml) | Optional ExternalSecret (`eso.*`) | | [`templates/serviceaccount.yaml`](src/helm-charts/rosetta-mcp-server/templates/serviceaccount.yaml) | ServiceAccount | +| [`tests/`](src/helm-charts/rosetta-mcp-server/tests/) | helm-unittest test suites | #### Deployment characteristics & defaults @@ -300,7 +349,7 @@ sessionAffinityConfig: If `ClientIP` is insufficient behind certain proxies or high fan-out IPs, try ingress affinity on the MCP session header: ```yaml -# NGINX Ingress (optional alternative) +# nginx Ingress (when using className: nginx) nginx.ingress.kubernetes.io/upstream-hash-by: "$http_mcp_session_id" ``` @@ -317,6 +366,11 @@ Base keys in [`src/helm-charts/rosetta-mcp-server/values.yaml`](src/helm-charts/ | `replicaCount` | `1` | Static replicas when HPA disabled | | `autoscaling.enabled` | `false` | HPA toggle | | `ingress.enabled` | `true` | Ingress resource | +| `ingress.className` | `traefik` | Ingress controller (`traefik` or `nginx`) | +| `ingress.traefik.middlewares` | `[]` | External Traefik middleware references | +| `ingress.traefik.rateLimit.average` | `100` | Rate limit — requests per period | +| `ingress.traefik.rateLimit.burst` | `200` | Rate limit — max burst size | +| `ingress.traefik.rateLimit.period` | `"1s"` | Rate limit — period | | `ingress.tls` | Commented in base [`values.yaml`](src/helm-charts/rosetta-mcp-server/values.yaml); enable for production | HTTPS termination at Ingress | | `service.sessionAffinity` | `ClientIP` | Pod stickiness | | `eso.enabled` | `false` | External Secrets Operator sync | diff --git a/src/helm-charts/rosetta-mcp-server/Chart.yaml b/src/helm-charts/rosetta-mcp-server/Chart.yaml index 7fdde5e3..2d6f552d 100644 --- a/src/helm-charts/rosetta-mcp-server/Chart.yaml +++ b/src/helm-charts/rosetta-mcp-server/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: rosetta-mcp-helm-chart description: Helm chart for Rosetta MCP server - the consulting layer between IDEs and Rosetta knowledge base (RAGFlow) type: application -version: 0.3.0 -appVersion: "2.0.0" +version: 0.4.0 +appVersion: "2.0.25" keywords: - rosetta - mcp diff --git a/src/helm-charts/rosetta-mcp-server/templates/ingress.yaml b/src/helm-charts/rosetta-mcp-server/templates/ingress.yaml index 2f897e27..6ac13b9d 100644 --- a/src/helm-charts/rosetta-mcp-server/templates/ingress.yaml +++ b/src/helm-charts/rosetta-mcp-server/templates/ingress.yaml @@ -6,9 +6,32 @@ metadata: namespace: {{ .Release.Namespace }} labels: {{- include "rosetta-mcp.labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} + {{- $traefikMw := "" }} + {{- if eq (.Values.ingress.className | default "") "traefik" }} + {{- $traefik := .Values.ingress.traefik | default dict }} + {{- $middlewares := list }} + {{- range ($traefik.middlewares | default list) }} + {{- $middlewares = append $middlewares . }} + {{- end }} + {{- if and $traefik.rateLimit $traefik.rateLimit.average }} + {{- $middlewares = append $middlewares (printf "%s@kubernetescrd" (regexReplaceAll "-{2,}" (printf "%s-rate-limit" (include "rosetta-mcp.fullname" $)) "-")) }} + {{- end }} + {{- if $middlewares }} + {{- $traefikMw = join "," $middlewares }} + {{- end }} + {{- end }} + {{- $userAnnotations := .Values.ingress.annotations | default dict }} + {{- if $traefikMw }} + {{- $userAnnotations = omit $userAnnotations "traefik.ingress.kubernetes.io/router.middlewares" }} + {{- end }} + {{- if or $traefikMw $userAnnotations }} annotations: + {{- if $traefikMw }} + traefik.ingress.kubernetes.io/router.middlewares: {{ $traefikMw }} + {{- end }} + {{- with $userAnnotations }} {{- toYaml . | nindent 4 }} + {{- end }} {{- end }} spec: {{- if .Values.ingress.className }} diff --git a/src/helm-charts/rosetta-mcp-server/templates/traefik-middlewares.yaml b/src/helm-charts/rosetta-mcp-server/templates/traefik-middlewares.yaml new file mode 100644 index 00000000..59176e63 --- /dev/null +++ b/src/helm-charts/rosetta-mcp-server/templates/traefik-middlewares.yaml @@ -0,0 +1,23 @@ +{{- if and .Values.ingress.enabled (eq (.Values.ingress.className | default "") "traefik") }} + +{{- if and .Values.ingress.traefik .Values.ingress.traefik.rateLimit .Values.ingress.traefik.rateLimit.average }} +--- +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: {{ regexReplaceAll "-{2,}" (printf "%s-rate-limit" (include "rosetta-mcp.fullname" .)) "-" }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "rosetta-mcp.labels" . | nindent 4 }} +spec: + rateLimit: + average: {{ .Values.ingress.traefik.rateLimit.average }} + {{- with .Values.ingress.traefik.rateLimit.burst }} + burst: {{ . }} + {{- end }} + {{- with .Values.ingress.traefik.rateLimit.period }} + period: {{ . | quote }} + {{- end }} +{{- end }} + +{{- end }} diff --git a/src/helm-charts/rosetta-mcp-server/tests/deployment_test.yaml b/src/helm-charts/rosetta-mcp-server/tests/deployment_test.yaml new file mode 100644 index 00000000..f3605d6e --- /dev/null +++ b/src/helm-charts/rosetta-mcp-server/tests/deployment_test.yaml @@ -0,0 +1,85 @@ +suite: Deployment +templates: + - deployment.yaml +tests: + - it: creates a Deployment + asserts: + - isKind: + of: Deployment + + - it: sets replicas from replicaCount + set: + replicaCount: 3 + asserts: + - equal: + path: spec.replicas + value: 3 + + - it: uses the configured image + set: + image.repository: my-registry/my-app + image.tag: "1.2.3" + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: "my-registry/my-app:1.2.3" + + - it: defaults image tag to Chart.appVersion when tag is not set + asserts: + - matchRegex: + path: spec.template.spec.containers[0].image + pattern: "^griddynamics/rosetta-mcp:.+" + + - it: applies pod security context + asserts: + - equal: + path: spec.template.spec.securityContext.runAsNonRoot + value: true + - equal: + path: spec.template.spec.securityContext.runAsUser + value: 1000 + + - it: applies container security context + asserts: + - equal: + path: spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation + value: false + - contains: + path: spec.template.spec.containers[0].securityContext.capabilities.drop + content: ALL + + - it: sets resource requests and limits + asserts: + - equal: + path: spec.template.spec.containers[0].resources.requests.cpu + value: 250m + - equal: + path: spec.template.spec.containers[0].resources.requests.memory + value: 512Mi + - equal: + path: spec.template.spec.containers[0].resources.limits.cpu + value: 1000m + - equal: + path: spec.template.spec.containers[0].resources.limits.memory + value: 1Gi + + - it: configures rolling update strategy + asserts: + - equal: + path: spec.strategy.type + value: RollingUpdate + - equal: + path: spec.strategy.rollingUpdate.maxSurge + value: 1 + - equal: + path: spec.strategy.rollingUpdate.maxUnavailable + value: 0 + + - it: renders liveness and readiness probes + asserts: + - isNotNull: + path: spec.template.spec.containers[0].livenessProbe + - isNotNull: + path: spec.template.spec.containers[0].readinessProbe + - isNotNull: + path: spec.template.spec.containers[0].startupProbe diff --git a/src/helm-charts/rosetta-mcp-server/tests/ingress_test.yaml b/src/helm-charts/rosetta-mcp-server/tests/ingress_test.yaml new file mode 100644 index 00000000..3bc8dda1 --- /dev/null +++ b/src/helm-charts/rosetta-mcp-server/tests/ingress_test.yaml @@ -0,0 +1,84 @@ +suite: Ingress +templates: + - ingress.yaml +tests: + - it: creates an Ingress with traefik class by default + asserts: + - isKind: + of: Ingress + - equal: + path: spec.ingressClassName + value: "traefik" + + - it: sets the host from values + set: + ingress.host: myapp.example.com + asserts: + - equal: + path: spec.rules[0].host + value: "myapp.example.com" + + - it: adds rate-limit middleware annotation when traefik rateLimit is configured + asserts: + - isNotNull: + path: metadata.annotations["traefik.ingress.kubernetes.io/router.middlewares"] + - matchRegex: + path: metadata.annotations["traefik.ingress.kubernetes.io/router.middlewares"] + pattern: ".*-rate-limit@kubernetescrd$" + + - it: includes external middlewares in annotation + set: + ingress.traefik.middlewares: + - "traefik-chain-public@kubernetescrd" + asserts: + - matchRegex: + path: metadata.annotations["traefik.ingress.kubernetes.io/router.middlewares"] + pattern: "^traefik-chain-public@kubernetescrd," + + - it: does not render middleware annotation when className is nginx + set: + ingress.className: nginx + ingress.annotations: + nginx.ingress.kubernetes.io/limit-rps: "100" + asserts: + - isNull: + path: metadata.annotations["traefik.ingress.kubernetes.io/router.middlewares"] + - equal: + path: metadata.annotations["nginx.ingress.kubernetes.io/limit-rps"] + value: "100" + + - it: does not set ingressClassName when className is empty + set: + ingress.className: "" + asserts: + - isNull: + path: spec.ingressClassName + + - it: renders TLS when configured + set: + ingress.tls: + - secretName: my-tls + hosts: + - rosetta.example.com + asserts: + - equal: + path: spec.tls[0].secretName + value: my-tls + - equal: + path: spec.tls[0].hosts[0] + value: rosetta.example.com + + - it: is not rendered when ingress is disabled + set: + ingress.enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: normalizes consecutive dashes in middleware annotation + release: + name: my--app + asserts: + - equal: + path: metadata.annotations["traefik.ingress.kubernetes.io/router.middlewares"] + value: "my-app-rosetta-mcp-rate-limit@kubernetescrd" diff --git a/src/helm-charts/rosetta-mcp-server/tests/pdb_test.yaml b/src/helm-charts/rosetta-mcp-server/tests/pdb_test.yaml new file mode 100644 index 00000000..c69d6134 --- /dev/null +++ b/src/helm-charts/rosetta-mcp-server/tests/pdb_test.yaml @@ -0,0 +1,27 @@ +suite: PodDisruptionBudget +templates: + - poddisruptionbudget.yaml +tests: + - it: is not rendered when disabled (default) + asserts: + - hasDocuments: + count: 0 + + - it: creates a PDB when enabled with multiple replicas + set: + podDisruptionBudget.enabled: true + replicaCount: 3 + asserts: + - isKind: + of: PodDisruptionBudget + - equal: + path: spec.maxUnavailable + value: 1 + + - it: is not rendered when replicaCount is 1 even if enabled + set: + podDisruptionBudget.enabled: true + replicaCount: 1 + asserts: + - hasDocuments: + count: 0 diff --git a/src/helm-charts/rosetta-mcp-server/tests/service_test.yaml b/src/helm-charts/rosetta-mcp-server/tests/service_test.yaml new file mode 100644 index 00000000..61d39db5 --- /dev/null +++ b/src/helm-charts/rosetta-mcp-server/tests/service_test.yaml @@ -0,0 +1,26 @@ +suite: Service +templates: + - service.yaml +tests: + - it: creates a ClusterIP Service + asserts: + - isKind: + of: Service + - equal: + path: spec.type + value: ClusterIP + + - it: configures session affinity for stateful MCP sessions + asserts: + - equal: + path: spec.sessionAffinity + value: ClientIP + - equal: + path: spec.sessionAffinityConfig.clientIP.timeoutSeconds + value: 3600 + + - it: exposes the configured port + asserts: + - equal: + path: spec.ports[0].port + value: 8000 diff --git a/src/helm-charts/rosetta-mcp-server/tests/traefik_middlewares_test.yaml b/src/helm-charts/rosetta-mcp-server/tests/traefik_middlewares_test.yaml new file mode 100644 index 00000000..ef434fcf --- /dev/null +++ b/src/helm-charts/rosetta-mcp-server/tests/traefik_middlewares_test.yaml @@ -0,0 +1,89 @@ +suite: Traefik Middlewares +templates: + - traefik-middlewares.yaml +tests: + - it: creates rate-limit Middleware when traefik class and rateLimit configured + asserts: + - isKind: + of: Middleware + - equal: + path: apiVersion + value: traefik.io/v1alpha1 + - equal: + path: spec.rateLimit.average + value: 100 + - equal: + path: spec.rateLimit.burst + value: 200 + - equal: + path: spec.rateLimit.period + value: "1s" + + - it: does not render any annotations by default + asserts: + - isNull: + path: metadata.annotations + + - it: includes chart labels + asserts: + - exists: + path: metadata.labels["helm.sh/chart"] + - exists: + path: metadata.labels["app.kubernetes.io/name"] + + - it: is not rendered when className is nginx + set: + ingress.className: nginx + asserts: + - hasDocuments: + count: 0 + + - it: is not rendered when ingress is disabled + set: + ingress.enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: is not rendered when rateLimit.average is not set + set: + ingress.traefik.rateLimit.average: null + asserts: + - hasDocuments: + count: 0 + + - it: renders with only average when burst and period are empty + set: + ingress.traefik.rateLimit: + average: 50 + burst: 0 + period: "" + asserts: + - equal: + path: spec.rateLimit.average + value: 50 + + - it: renders custom rate-limit values + set: + ingress.traefik.rateLimit: + average: 50 + burst: 100 + period: "10s" + asserts: + - equal: + path: spec.rateLimit.average + value: 50 + - equal: + path: spec.rateLimit.burst + value: 100 + - equal: + path: spec.rateLimit.period + value: "10s" + + - it: normalizes consecutive dashes in CRD name + release: + name: my--app + asserts: + - equal: + path: metadata.name + value: my-app-rosetta-mcp-rate-limit diff --git a/src/helm-charts/rosetta-mcp-server/values-dev.example.yaml b/src/helm-charts/rosetta-mcp-server/values-dev.example.yaml index 13ecc8f1..12e5cc74 100644 --- a/src/helm-charts/rosetta-mcp-server/values-dev.example.yaml +++ b/src/helm-charts/rosetta-mcp-server/values-dev.example.yaml @@ -3,10 +3,20 @@ # Replace placeholder hostnames and URLs with your dev environment values. image: - tag: "2.0.0" + tag: "2.0.25" ingress: host: rosetta-dev.example.com + traefik: + rateLimit: + average: 20 + burst: 200 + period: "1s" + # nginx alternative (uncomment to switch from traefik): + # className: nginx + # annotations: + # nginx.ingress.kubernetes.io/limit-rps: "20" + # nginx.ingress.kubernetes.io/limit-burst-multiplier: "10" env: enabled: true diff --git a/src/helm-charts/rosetta-mcp-server/values-prod.example.yaml b/src/helm-charts/rosetta-mcp-server/values-prod.example.yaml index d923a9b2..8e6978c9 100644 --- a/src/helm-charts/rosetta-mcp-server/values-prod.example.yaml +++ b/src/helm-charts/rosetta-mcp-server/values-prod.example.yaml @@ -2,10 +2,20 @@ # Use with: helm install -f values.yaml -f values-prod.yaml ... image: - tag: "2.0.0" + tag: "2.0.25" ingress: host: rosetta.example.com + traefik: + rateLimit: + average: 100 + burst: 200 + period: "1s" + # nginx alternative (uncomment to switch from traefik): + # className: nginx + # annotations: + # nginx.ingress.kubernetes.io/limit-rps: "100" + # nginx.ingress.kubernetes.io/limit-burst-multiplier: "2" env: enabled: true diff --git a/src/helm-charts/rosetta-mcp-server/values.yaml b/src/helm-charts/rosetta-mcp-server/values.yaml index e5e67b5c..032b0414 100644 --- a/src/helm-charts/rosetta-mcp-server/values.yaml +++ b/src/helm-charts/rosetta-mcp-server/values.yaml @@ -3,30 +3,29 @@ fullnameOverride: "" replicaCount: 1 -# Health probes (optional) -# Example (edit as needed): -# livenessProbe: -# tcpSocket: -# port: port8000 -# periodSeconds: 30 -# timeoutSeconds: 5 -# failureThreshold: 3 - -# readinessProbe: -# httpGet: -# path: /healthz -# port: port8000 -# periodSeconds: 10 -# timeoutSeconds: 5 -# failureThreshold: 3 - -# startupProbe: -# httpGet: -# path: /healthz -# port: port8000 -# periodSeconds: 10 -# timeoutSeconds: 5 -# failureThreshold: 40 +# Health probes +livenessProbe: + tcpSocket: + port: port8000 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + +readinessProbe: + httpGet: + path: /healthz + port: port8000 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + +startupProbe: + httpGet: + path: /healthz + port: port8000 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 40 # PodDisruptionBudget — enable when replicaCount > 1 (or autoscaling minReplicas > 1) podDisruptionBudget: @@ -93,13 +92,31 @@ service: ingress: enabled: true - className: nginx + className: traefik host: rosetta-mcp.local - annotations: - kubernetes.io/ingress.class: nginx + annotations: {} paths: - / - # Production: enable TLS below (or terminate TLS upstream). Defaults leave MCP on HTTP at the Ingress until configured. + # Traefik-specific settings (applied when className is "traefik"). + traefik: + # External middleware references (fully-qualified, e.g. "namespace-name@kubernetescrd"). + # Rendered first in the middleware chain annotation. + middlewares: [] + # Per-release rate limiting — creates a Traefik Middleware (custom resource) in the release namespace (requires Traefik CRDs installed). + rateLimit: + average: 100 + burst: 200 + period: "1s" + # --- nginx alternative --- + # To use nginx instead of Traefik, set: + # className: nginx + # annotations: + # nginx.ingress.kubernetes.io/proxy-body-size: "10m" + # nginx.ingress.kubernetes.io/limit-rps: "100" + # nginx.ingress.kubernetes.io/limit-burst-multiplier: "2" + # and remove the traefik section above. + # + # TLS: enable below or terminate TLS upstream (e.g. cloud LB, cert-manager). # tls: # - secretName: rosetta-mcp-tls # hosts: