Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 33 additions & 23 deletions .github/workflows/publish-mcp-helm-chart.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ on:
push:
branches:
- main
paths:
paths: &chart_paths
Comment thread
isolomatov-gd marked this conversation as resolved.
- '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
Expand All @@ -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 }}" \
Expand Down
57 changes: 55 additions & 2 deletions DEPLOYMENT_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,52 @@ 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` CRD (`<fullname>-rate-limit`) in the
release namespace and wires it into the Ingress annotation automatically.

Comment thread
Copilot marked this conversation as resolved.
Outdated
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).
Expand All @@ -273,9 +318,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

Expand All @@ -300,7 +348,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"
```

Expand All @@ -317,6 +365,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 |
Comment thread
kkhristenko51 marked this conversation as resolved.
| `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 |
Expand Down
4 changes: 2 additions & 2 deletions src/helm-charts/rosetta-mcp-server/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 24 additions & 1 deletion src/helm-charts/rosetta-mcp-server/templates/ingress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Comment thread
Copilot marked this conversation as resolved.
Comment thread
kkhristenko51 marked this conversation as resolved.
{{- 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 }}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 }}
85 changes: 85 additions & 0 deletions src/helm-charts/rosetta-mcp-server/tests/deployment_test.yaml
Original file line number Diff line number Diff line change
@@ -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
Loading