MCP_GATEWAY_OFFICIAL_CHART_MIGRATION.md
Rama master · réplica de lectura del repositorio original.
# MCP Gateway Official Chart Migration - READY_FOR_REVIEW
**Status:** READY_FOR_REVIEW
**Date:** 2026-11-09
**Priority:** P0 - Official chart migration required before MVP backend deployment
**Current:** app-template based deployment (commit a146c5ee9, healthy)
**Target:** Official agentgateway-standalone v1.7.0 chart
---
## Chart Provenance (VERIFIED)
**Official Chart:** `oci://cr.agentgateway.dev/charts/agentgateway-standalone:1.7.0`
**Digest:** `sha256:8c3fcfb7f35366b86ba9ddbac30df3f542dcca1cd089092227e6f24c33742947`
**Source:** https://github.com/agentgateway/agentgateway
**Docs:** https://agentgateway.dev/docs/standalone/latest/documentation/setup/install/helm/
**Verification:**
- Chart pulled and validated against standalone binary v1.7.0
- NOT the Gateway API control plane chart
- Matches agentgateway/agentgateway GitHub repository
- Helm template renders correctly with proper values structure
---
## Critical Migration Issues
### 1. Session Key Incompatibility (BLOCKER - RESOLVED)
**Problem:**
- Current ESO secret has key `SESSION_KEY`
- Official chart expects key `key` (hardcoded in deployment.yaml:86)
- Cannot override via values
**Solution:**
- Updated `templates/session-key-generator.yaml`: `secretKeys: [key]`
- Migration requires deleting existing ExternalSecret and Secret
- ESO will recreate with correct key name on next sync
**Impact:**
- In-flight MCP sessions invalidated (acceptable for foundation with no routes)
- Owner accepted short outage for migration
### 2. Deployment Selector Change (IMMUTABLE FIELD)
**Current selector:**
```yaml
app.kubernetes.io/controller: agentgateway
app.kubernetes.io/instance: mcp-gateway
app.kubernetes.io/name: mcp-gateway
```
**New selector:**
```yaml
app.kubernetes.io/name: agentgateway-standalone
app.kubernetes.io/instance: mcp-gateway
app.kubernetes.io/component: standalone
```
**Impact:**
- Deployment selector is immutable - cannot update in place
- ArgoCD will delete old Deployment, create new one
- Short outage during transition (acceptable)
- Service DNS name unchanged (fullnameOverride: mcp-gateway)
### 3. Service Ports (PRESERVED)
**Current:** 3000 (http), 15020 (metrics), 15021 (healthz)
**New:** 3000 (http), 15020 (metrics), 15021 (healthz)
No change - Service DNS and port numbers preserved. Ingress backend reference unchanged.
### 4. Monitoring: ServiceMonitor → PodMonitor
**Current:** ServiceMonitor (app-template)
**New:** PodMonitor (official chart)
**Impact:**
- Brief monitoring gap during transition (acceptable)
- Prometheus continues scraping same endpoint (15020/metrics)
- PodMonitor has `release: monitoring` label for Prometheus selector
### 5. ConfigMap Name (PRESERVED)
**Current:** `mcp-gateway-config`
**New:** `mcp-gateway-config` (fullnameOverride)
No change - ConfigMap name preserved.
### 6. NetworkPolicy
**Current:** Gated by `networkIsolation: false` (not rendered)
**New:** Chart has no NetworkPolicy templates
**Migration:** None needed - no NP rendered in either case.
---
## Resource Identity Changes
| Resource | Current | Official | Impact |
|----------|---------|----------|--------|
| Deployment name | mcp-gateway | mcp-gateway | None |
| Deployment selector | controller=agentgateway | component=standalone | **Recreate** |
| Service name | mcp-gateway | mcp-gateway | None |
| Service ports | 3000/15020/15021 | 3000/15020/15021 | None |
| ConfigMap name | mcp-gateway-config | mcp-gateway-config | None |
| Secret name | mcp-gateway-session-key | mcp-gateway-session-key | None |
| Secret key | SESSION_KEY | key | **Recreate** |
| ServiceMonitor | mcp-gateway | (deleted) | **Prune** |
| PodMonitor | (none) | mcp-gateway | **Create** |
| Ingress | mcp-gateway | (wrapper template) | None |
---
## Files to Commit
### apps/mcp-gateway/
- `Chart.yaml` - Switch dependency to agentgateway-standalone
- `values.yaml` - Official chart values (replace current, nested under `agentgateway-standalone:`)
- `templates/session-key-generator.yaml` - Change secretKeys to `[key]`
- `templates/session-key-externalsecret.yaml` - No change (ESO manages lifecycle)
- `templates/kanidm-group.yaml` - No change
- `templates/kanidm-oauth2-client.yaml` - No change
- `templates/ingress.yaml` - Wrapper template for Ingress (chart has none)
- `templates/configmap.yaml` - Delete (chart provides it)
- `templates/networkpolicy.yaml` - Delete (chart has no NP, networkIsolation=false)
### Not tracked (gitignored)
- `Chart.lock` - Generated by helm dependency build
- `charts/` - Vendored chart tarball (not committed)
---
## Validation Results
### helm template
```bash
helm template mcp-gateway apps/mcp-gateway/ --namespace mcp-gateway --include-crds
```
✓ Renders 11 resources:
- 1 ServiceAccount
- 1 ConfigMap (mcp-gateway-config)
- 1 Service (ClusterIP, ports 3000/15020/15021)
- 1 Deployment (image v1.7.0, selector component=standalone)
- 1 PodMonitor (release: monitoring)
- 1 Ingress (wrapper template)
- 1 ExternalSecret (session key)
- 1 Password generator (secretKeys: [key])
- 1 KanidmGroup
- 1 KanidmOAuth2Client
✓ SESSION_KEY env var references secret with key `key`
✓ nodeSelector: kubernetes.io/hostname: prusik
✓ resources: 10m/64Mi requests, 256Mi limits
✓ securityContext: runAsNonRoot, readOnlyRootFilesystem, drop ALL
✓ config.yaml: adminAddr: "off", gateways.default.port: 3000, mcp.targets: []
✓ No NetworkPolicy rendered
### helm lint
```bash
helm lint apps/mcp-gateway/
```
✓ 1 chart(s) linted, 0 chart(s) failed
### pre-commit
```bash
pre-commit run --files apps/mcp-gateway/Chart.yaml apps/mcp-gateway/values.yaml apps/mcp-gateway/templates/session-key-generator.yaml apps/mcp-gateway/templates/ingress.yaml
```
✓ All checks passed (yamllint, helmlint, etc.)
---
## Rollback Plan
**If migration fails:**
1. Revert to commit a146c5ee9 (app-template chart)
2. ArgoCD sync (prune new resources, recreate old)
3. ESO secret recreated with key `SESSION_KEY`
4. Deployment recreated with old selector
5. Verify pod starts, Service DNS resolves, Ingress routes
**Rollback impact:** Short outage during resource recreation (acceptable)
---
## Migration Execution Steps
**Pre-deployment:**
1. Commit whitelist (see above)
2. Push to origin/master
3. Wait for ArgoCD to detect changes
**Deployment:**
1. Delete existing ExternalSecret:
```bash
kubectl --context=grigri delete externalsecret mcp-gateway-session-key -n mcp-gateway
```
2. Delete existing Secret:
```bash
kubectl --context=grigri delete secret mcp-gateway-session-key -n mcp-gateway
```
3. ArgoCD sync with prune=true (already configured)
- Old Deployment deleted (selector mismatch)
- New Deployment created
- Old ServiceMonitor pruned
- New PodMonitor created
- ESO recreates Secret with key `key`
**Post-deployment verification:**
1. Verify new Deployment created:
```bash
kubectl --context=grigri get deployment mcp-gateway -n mcp-gateway
```
2. Verify pod starts, Ready 1/1:
```bash
kubectl --context=grigri get pods -n mcp-gateway -l app.kubernetes.io/component=standalone
```
3. Verify ESO secret created with key `key`:
```bash
kubectl --context=grigri get secret mcp-gateway-session-key -n mcp-gateway -o jsonpath='{.data}' | python3 -c "import sys, json; print(list(json.loads(sys.stdin.read()).keys()))"
```
4. Verify Service DNS resolves:
```bash
kubectl --context=grigri run -it --rm debug --image=busybox -- nslookup mcp-gateway.mcp-gateway.svc.cluster.local
```
5. Verify Ingress routes (404 expected, no backends):
```bash
curl -I https://mcp.grigri.cloud/
```
6. Verify TLS valid:
```bash
curl -vI https://mcp.grigri.cloud/ 2>&1 | grep "SSL certificate verify ok"
```
7. Verify no NetworkPolicy:
```bash
kubectl --context=grigri get networkpolicy -n mcp-gateway
```
8. Verify PodMonitor created:
```bash
kubectl --context=grigri get podmonitor mcp-gateway -n mcp-gateway
```
9. Wait 180s stable window
10. Capture UTC timestamps for deployment completion
---
## Owner Decisions Required
1. **Session key migration:** Delete existing secret (invalidates sessions) - **ACCEPTED**
2. **Deployment recreation:** Short outage during selector change - **ACCEPTED**
3. **Monitoring gap:** Brief gap during ServiceMonitor→PodMonitor - **ACCEPTED**
4. **No rollback to app-template:** Official chart is mandatory going forward - **CONFIRMED**
---
## Next Steps After Migration
1. **MVP Backend Deployment (blocked until migration complete):**
- Grafana route: `/grafana/mcp` with scope `mcp_grafana`, native per-user Kanidm ACCESS JWT forwarded
- Context7 route: `/context7/mcp` with scope `mcp_context7`, strip incoming Authorization header
- Both routes stateless sessions
- Require actual security evidence (JWT validation, scope enforcement, header stripping)
2. **Client Integration (separate agent):**
- Hermes: 2 routes (`/grafana/mcp`, `/context7/mcp`)
- OpenCode: 2 routes (`/grafana/mcp`, `/context7/mcp`)
- Single client mcp-gateway audience (already resolved)
3. **Security CNP (after MVP working):**
- CiliumNetworkPolicy for gateway egress
- DNS-based FQDN allowlist (JWKS ingress 443, Grafana backend pod 3000, Context7 FQDN 443)
- No broad world/443 allow
- Separate follow-up, not part of this migration
---
## Blockers
**None.** Official chart migration is ready for review and deployment.
**Future blockers (not blocking this migration):**
- Grafana identity linking (separate agent will resolve)
- Context7 auth stripping proof (requires synthetic test against local HTTP MCP capture)
- Client integration (separate agent preparing)
---
## Summary
Official agentgateway-standalone v1.7.0 chart migration is complete and validated. All resources render correctly, helm lint passes, pre-commit passes. Migration requires deleting existing session secret (invalidates in-flight sessions, acceptable for foundation) and recreating Deployment (immutable selector change, short outage acceptable).
**Ready for main review release. No commits/push/deploy until released.**