Skip to content

Commit 081ceda

Browse files
authored
Merge pull request #206 from IBM/helm-job-db-migration
Updated helm chart with db migration job
2 parents 803c769 + 2ab0e91 commit 081ceda

File tree

6 files changed

+383
-23
lines changed

6 files changed

+383
-23
lines changed

charts/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,34 @@ helm rollback mcp-stack 1 -n mcp
182182

183183
---
184184

185+
## Database Migration
186+
187+
The chart includes automatic database migration using **Alembic** that runs before the mcpgateway deployment starts. This ensures your database schema is always up-to-date.
188+
189+
### How It Works
190+
191+
1. **Migration Job** – Runs as a Kubernetes Job alongside other resources
192+
2. **Database Readiness** – Waits for PostgreSQL using the built-in `db_isready.py` script
193+
3. **Schema Migration** – Executes `alembic upgrade head` to apply any pending migrations
194+
4. **Gateway Startup** – mcpgateway uses a startup probe to ensure database is ready before serving traffic
195+
196+
### Configuration
197+
198+
```yaml
199+
migration:
200+
enabled: true # Enable/disable migrations (default: true)
201+
backoffLimit: 3 # Retry attempts on failure
202+
activeDeadlineSeconds: 600 # Job timeout (10 minutes)
203+
204+
image:
205+
repository: ghcr.io/ibm/mcp-context-forge
206+
tag: latest # Should match mcpContextForge.image.tag
207+
208+
command:
209+
waitForDb: "python /app/mcpgateway/utils/db_isready.py --max-tries 30 --interval 2 --timeout 5"
210+
migrate: "alembic upgrade head || echo '⚠️ Migration check failed'"
211+
---
212+
185213
## Uninstall
186214

187215
```bash
@@ -235,11 +263,16 @@ helm template mcp-stack . -f my-values.yaml > /tmp/all.yaml
235263

236264
## Common Values Reference
237265

266+
## Common Values Reference
267+
238268
| Key | Default | Description |
239269
| --------------------------------- | --------------- | ------------------------------ |
240270
| `mcpContextForge.image.tag` | `latest` | Gateway image version |
241271
| `mcpContextForge.ingress.enabled` | `true` | Create Ingress resource |
242272
| `mcpContextForge.ingress.host` | `gateway.local` | External host |
273+
| `mcpContextForge.hpa.enabled` | `true` | Enable Horizontal Pod Autoscaler |
274+
| `migration.enabled` | `true` | Run database migrations |
275+
| `migration.backoffLimit` | `3` | Migration job retry attempts |
243276
| `postgres.credentials.user` | `admin` | DB username |
244277
| `postgres.persistence.enabled` | `true` | Enable PVC |
245278
| `postgres.persistence.size` | `10Gi` | PostgreSQL volume size |

charts/mcp-stack/templates/NOTES.txt

Lines changed: 213 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,104 @@
4242
{{- $redisPort := .Values.redis.service.port | default 6379 }}
4343
{{- $pgAdminPort := .Values.pgadmin.service.port | default 80 }}
4444

45-
🎉 **{{ .Chart.Name }}** has been successfully deployed to namespace **{{ $ns }}**!
45+
{{- /* ─── Deployment context information ─────────────────── */}}
46+
{{- $timestamp := now | date "2006-01-02 15:04:05 UTC" }}
47+
{{- $k8sVersion := .Capabilities.KubeVersion.Version }}
48+
{{- $helmVersion := .Capabilities.HelmVersion.Version }}
49+
50+
🎉 **{{ .Chart.Name }}** has been successfully deployed!
51+
52+
═══════════════════════════════════════════════════════════════════════════════
53+
54+
📋 **Deployment Summary**
55+
• Release Name : {{ .Release.Name }}
56+
• Namespace : {{ $ns }}
57+
• Chart Version : {{ .Chart.Version }}
58+
• App Version : {{ .Chart.AppVersion }}
59+
• Deployed At : {{ $timestamp }}
60+
• Revision : {{ .Release.Revision }}
61+
• Kubernetes : {{ $k8sVersion }}
62+
• Helm Version : {{ $helmVersion }}
63+
{{- with .Values.global.nameOverride }}
64+
• Name Override : {{ . }}
65+
{{- end }}
66+
{{- with .Values.global.fullnameOverride }}
67+
• Full Override : {{ . }}
68+
{{- end }}
69+
70+
🏗️ **Infrastructure Stack**
71+
• MCP Gateway : {{ .Values.mcpContextForge.replicaCount }} replica(s) - {{ .Values.mcpContextForge.image.repository }}:{{ .Values.mcpContextForge.image.tag }}
72+
{{- if .Values.mcpFastTimeServer.enabled }}
73+
• Fast Time Srv : {{ .Values.mcpFastTimeServer.replicaCount }} replica(s) - {{ .Values.mcpFastTimeServer.image.repository }}:{{ .Values.mcpFastTimeServer.image.tag }}
74+
{{- end }}
75+
{{- if .Values.postgres.enabled }}
76+
• PostgreSQL : {{ .Values.postgres.image.repository }}:{{ .Values.postgres.image.tag }}
77+
{{- if .Values.postgres.persistence.enabled }} ({{ .Values.postgres.persistence.size }} storage){{ end }}
78+
{{- end }}
79+
{{- if .Values.redis.enabled }}
80+
• Redis Cache : {{ .Values.redis.image.repository }}:{{ .Values.redis.image.tag }}
81+
{{- end }}
82+
{{- if .Values.pgadmin.enabled }}
83+
• PgAdmin UI : {{ .Values.pgadmin.image.repository }}:{{ .Values.pgadmin.image.tag }}
84+
{{- end }}
85+
{{- if .Values.redisCommander.enabled }}
86+
• Redis UI : {{ .Values.redisCommander.image.repository }}:{{ .Values.redisCommander.image.tag }}
87+
{{- end }}
88+
89+
🔧 **Configuration**
90+
{{- if .Values.mcpContextForge.hpa.enabled }}
91+
• Auto-Scaling : {{ .Values.mcpContextForge.hpa.minReplicas }} - {{ .Values.mcpContextForge.hpa.maxReplicas }} replicas (CPU: {{ .Values.mcpContextForge.hpa.targetCPUUtilizationPercentage }}%)
92+
{{- end }}
93+
{{- if .Values.mcpContextForge.ingress.enabled }}
94+
• Ingress : {{ .Values.mcpContextForge.ingress.className }} @ {{ .Values.mcpContextForge.ingress.host }}
95+
{{- end }}
96+
• Migration : {{ if .Values.migration.enabled }}✅ Enabled{{ else }}❌ Disabled{{ end }}
97+
• Dev Mode : {{ if eq (.Values.mcpContextForge.config.DEV_MODE | default "false") "true" }}⚠️ ENABLED{{ else }}✅ Production{{ end }}
98+
99+
═══════════════════════════════════════════════════════════════════════════════
100+
101+
{{- /* ════════════ Migration Status ════════════ */}}
102+
{{- if .Values.migration.enabled }}
103+
📊 **Database Migration**
104+
• Status : `kubectl get jobs -n {{ $ns }} -l app.kubernetes.io/component=migration`
105+
• Logs : `kubectl logs -n {{ $ns }} -l app.kubernetes.io/component=migration`
106+
• Job Details : `kubectl describe job -n {{ $ns }} {{ $fullName }}-migration`
107+
{{- with (lookup "batch/v1" "Job" $ns (printf "%s-migration" $fullName)) }}
108+
{{- if .status.conditions }}
109+
{{- range .status.conditions }}
110+
{{- if eq .type "Complete" }}
111+
{{- if eq .status "True" }}
112+
• ✅ Migration completed successfully at {{ .lastTransitionTime }}
113+
{{- else }}
114+
• ⏳ Migration in progress...
115+
{{- end }}
116+
{{- else if eq .type "Failed" }}
117+
{{- if eq .status "True" }}
118+
• ❌ Migration failed at {{ .lastTransitionTime }} - check logs above
119+
{{- end }}
120+
{{- end }}
121+
{{- end }}
122+
{{- else }}
123+
• ⏳ Migration job starting...
124+
{{- end }}
125+
{{- else }}
126+
• ⏳ Migration job not found - may still be creating...
127+
{{- end }}
128+
129+
{{- else }}
130+
📊 **Database Migration**
131+
• Database migrations are **disabled** (migration.enabled=false)
132+
• To enable: `helm upgrade {{ .Release.Name }} --set migration.enabled=true -n {{ $ns }}`
133+
{{- end }}
46134

47135
{{- /* ════════════ MCP Gateway ════════════ */}}
48136
🔗 **MCP Gateway**
49137
{{- if .Values.mcpContextForge.ingress.enabled }}
50-
• Ingress URL : https://{{ .Values.mcpContextForge.ingress.host }}{{ .Values.mcpContextForge.ingress.path | default "/" }}
138+
• Primary URL : https://{{ .Values.mcpContextForge.ingress.host }}{{ .Values.mcpContextForge.ingress.path | default "/" }}
139+
• Health Check : https://{{ .Values.mcpContextForge.ingress.host }}/health
51140
{{- else }}
52-
• Service URL : http://{{ $gatewaySvc }}.{{ $ns }}.svc.cluster.local:{{ $gwPort }}
141+
• Service URL : http://{{ $gatewaySvc }}.{{ $ns }}.svc.cluster.local:{{ $gwPort }}
142+
• Health Check : http://{{ $gatewaySvc }}.{{ $ns }}.svc.cluster.local:{{ $gwPort }}/health
53143
{{- end }}
54144
• Basic-Auth :
55145
user = {{ .Values.mcpContextForge.secret.BASIC_AUTH_USER }}
@@ -68,52 +158,157 @@
68158
• Port-forward : `kubectl -n {{ $ns }} port-forward svc/{{ $gatewaySvc }} 4444:{{ $gwPort }}`
69159

70160
{{- /* ════════════ Fast-Time-Server ════════════ */}}
161+
{{- if .Values.mcpFastTimeServer.enabled }}
71162
🔗 **Fast-Time-Server (SSE)**
72163
• Cluster URL : http://{{ $ftSvc }}.{{ $ns }}.svc.cluster.local
73164
• Via Gateway : {{- if .Values.mcpContextForge.ingress.enabled }} https://{{ .Values.mcpContextForge.ingress.host }}/fast-time {{- else }} http://localhost:4444/fast-time {{- end }}
165+
• Port-forward : `kubectl -n {{ $ns }} port-forward svc/{{ $ftSvc }} 8080:80`
166+
{{- end }}
74167

75168
{{- /* ════════════ Datastores ════════════ */}}
76-
💾 **Postgres**
169+
{{- if .Values.postgres.enabled }}
170+
💾 **PostgreSQL Database**
77171
• Host / Port : {{ $postgresSvc }}.{{ $ns }}.svc.cluster.local:{{ $pgPort }}
78-
DB : {{ .Values.postgres.credentials.database }}
79-
User : {{ .Values.postgres.credentials.user }}
172+
Database : {{ .Values.postgres.credentials.database }}
173+
Username : {{ .Values.postgres.credentials.user }}
80174
{{- if $showSecrets }}
81175
• Password : {{ $pgPass | default "<secret-not-yet-created>" }}
82176
{{- else }}
83177
• Password : <hidden>
84178
{{- end }}
85179
(kubectl = `kubectl -n {{ $ns }} get secret {{ $pgSecret }} -o jsonpath="{.data.POSTGRES_PASSWORD}" | base64 -d`)
180+
{{- if .Values.postgres.persistence.enabled }}
181+
• Storage : {{ .Values.postgres.persistence.size }} ({{ .Values.postgres.persistence.storageClassName }})
182+
{{- end }}
183+
• Port-forward : `kubectl -n {{ $ns }} port-forward svc/{{ $postgresSvc }} 5432:{{ $pgPort }}`
184+
{{- end }}
86185

87-
🔑 **Redis**
186+
{{- if .Values.redis.enabled }}
187+
🔑 **Redis Cache**
88188
• Host / Port : {{ $redisSvc }}.{{ $ns }}.svc.cluster.local:{{ $redisPort }}
189+
• Port-forward : `kubectl -n {{ $ns }} port-forward svc/{{ $redisSvc }} 6379:{{ $redisPort }}`
190+
{{- end }}
191+
192+
{{- /* ════════════ Management UIs ════════════ */}}
193+
{{- if .Values.pgadmin.enabled }}
194+
📊 **PgAdmin (Database UI)**
195+
• Internal URL : http://{{ $pgadminSvc }}.{{ $ns }}.svc.cluster.local:{{ $pgAdminPort }}
196+
• Login Email : {{ .Values.pgadmin.env.email }}
197+
• Password : (same as PostgreSQL password above)
198+
• Port-forward : `kubectl -n {{ $ns }} port-forward svc/{{ $pgadminSvc }} 8080:{{ $pgAdminPort }}`
199+
• Local Access: http://localhost:8080 (after port-forward)
200+
{{- end }}
89201

90-
📊 **PGAdmin**
91-
• URL : http://{{ $pgadminSvc }}.{{ $ns }}.svc.cluster.local:{{ $pgAdminPort }}
92-
• Login : {{ .Values.pgadmin.env.email }} / (same Postgres password)
202+
{{- if .Values.redisCommander.enabled }}
203+
📊 **Redis Commander (Cache UI)**
204+
• Internal URL : http://{{ $redisSvc }}-commander.{{ $ns }}.svc.cluster.local:8081
205+
• Port-forward : `kubectl -n {{ $ns }} port-forward svc/{{ $fullName }}-redis-commander 8081:8081`
206+
• Local Access: http://localhost:8081 (after port-forward)
207+
{{- end }}
208+
209+
{{- /* ════════════ Cluster Information ════════════ */}}
210+
🏢 **Cluster Context**
211+
```bash
212+
# Current cluster context
213+
kubectl config current-context
214+
215+
# Verify deployment
216+
kubectl get all -n {{ $ns }}
217+
helm status {{ .Release.Name }} -n {{ $ns }}
218+
219+
# Resource usage
220+
kubectl top pods -n {{ $ns }}
221+
kubectl get pvc -n {{ $ns }}
222+
```
223+
224+
{{- /* ════════════ Troubleshooting ════════════ */}}
225+
🔧 **Troubleshooting & Monitoring**
226+
227+
```bash
228+
# === Resource Status ===
229+
kubectl get all -n {{ $ns }}
230+
kubectl get pvc -n {{ $ns }}
231+
kubectl get secrets -n {{ $ns }}
232+
233+
# === Migration Debugging ===
234+
{{- if .Values.migration.enabled }}
235+
kubectl get jobs -n {{ $ns }} -l app.kubernetes.io/component=migration
236+
kubectl describe job -n {{ $ns }} {{ $fullName }}-migration
237+
kubectl logs -n {{ $ns }} -l app.kubernetes.io/component=migration --tail=50
238+
{{- end }}
239+
240+
# === Gateway Debugging ===
241+
kubectl get pods -n {{ $ns }} -l app={{ $gatewaySvc }}
242+
kubectl describe deployment -n {{ $ns }} {{ $gatewaySvc }}
243+
kubectl logs -n {{ $ns }} -l app={{ $gatewaySvc }} --tail=50
244+
245+
# === Database Connectivity ===
246+
kubectl exec -n {{ $ns }} deployment/{{ $gatewaySvc }} -- python /app/mcpgateway/utils/db_isready.py
247+
{{- if .Values.postgres.enabled }}
248+
kubectl exec -n {{ $ns }} deployment/{{ $postgresSvc }} -- pg_isready -U {{ .Values.postgres.credentials.user }}
249+
{{- end }}
250+
251+
# === Performance Monitoring ===
252+
kubectl top pods -n {{ $ns }}
253+
kubectl get hpa -n {{ $ns }}
254+
kubectl get events -n {{ $ns }} --sort-by='.lastTimestamp' --field-selector type!=Normal
255+
256+
# === Port Forwarding for Local Access ===
257+
kubectl -n {{ $ns }} port-forward svc/{{ $gatewaySvc }} 4444:{{ $gwPort }}
258+
{{- if .Values.pgadmin.enabled }}
259+
kubectl -n {{ $ns }} port-forward svc/{{ $pgadminSvc }} 8080:{{ $pgAdminPort }}
260+
{{- end }}
261+
{{- if .Values.postgres.enabled }}
262+
kubectl -n {{ $ns }} port-forward svc/{{ $postgresSvc }} 5432:{{ $pgPort }}
263+
{{- end }}
264+
```
93265

94266
{{- /* ════════════ Quick-start ════════════ */}}
95-
🚀 **Quick-start**
267+
🚀 **Quick-start Guide**
96268

97269
```bash
98-
# 1) Forward the Gateway locally (skip if using ingress):
270+
# 1) Verify deployment is ready
271+
kubectl get pods -n {{ $ns }} -w
272+
273+
# 2) Forward the Gateway locally (skip if using ingress)
99274
kubectl -n {{ $ns }} port-forward svc/{{ $gatewaySvc }} 4444:{{ $gwPort }} &
100275

101-
# 2) Obtain a JWT via Basic-Auth (requires 'jq'):
276+
# 3) Get credentials and obtain JWT
102277
{{- if $showSecrets }}
103278
export GW_TOKEN=$(curl -s -u '{{ .Values.mcpContextForge.secret.BASIC_AUTH_USER }}:{{ $basicAuthPass }}' \
104279
-X POST http://localhost:4444/auth/login | jq -r '.access_token')
105280
{{- else }}
106-
# export GW_TOKEN=(fetch after you retrieve the password with kubectl)
281+
export GW_PASS=$(kubectl -n {{ $ns }} get secret {{ $gwSecret }} -o jsonpath="{.data.BASIC_AUTH_PASSWORD}" | base64 -d)
282+
export GW_TOKEN=$(curl -s -u '{{ .Values.mcpContextForge.secret.BASIC_AUTH_USER }}:$GW_PASS' \
283+
-X POST http://localhost:4444/auth/login | jq -r '.access_token')
107284
{{- end }}
108285

109-
# 3) Register the Fast-Time-Server with the Gateway:
286+
# 4) Test the gateway health
287+
curl -s http://localhost:4444/health | jq .
288+
289+
{{- if .Values.mcpFastTimeServer.enabled }}
290+
# 5) Register the Fast-Time-Server with the Gateway
110291
curl -s -X POST \
111292
-H "Authorization: Bearer $GW_TOKEN" \
112293
-H "Content-Type: application/json" \
113294
-d '{"name":"local_time","url":"http://{{ $ftSvc }}.{{ $ns }}.svc.cluster.local/sse"}' \
114295
http://localhost:4444/gateways
296+
297+
# 6) Test the time server registration
298+
curl -s -H "Authorization: Bearer $GW_TOKEN" http://localhost:4444/gateways | jq .
299+
{{- end }}
115300
```
116301

117-
📚 **Further reading**
118-
* [https://ibm.github.io/mcp-context-forge/deployment/helm/](https://ibm.github.io/mcp-context-forge/deployment/helm/)
119-
* [https://ibm.github.io/mcp-context-forge/testing/basic/](https://ibm.github.io/mcp-context-forge/testing/basic/)
302+
═══════════════════════════════════════════════════════════════════════════════
303+
304+
📚 **Documentation & Support**
305+
• Helm Chart : https://github.com/IBM/mcp-context-forge/tree/main/charts/mcp-stack
306+
• Documentation : https://ibm.github.io/mcp-context-forge/deployment/helm/
307+
• API Testing : https://ibm.github.io/mcp-context-forge/testing/basic/
308+
• Issues : https://github.com/IBM/mcp-context-forge/issues
309+
310+
📋 **Next Steps**
311+
1. Verify all pods are Running: `kubectl get pods -n {{ $ns }}`
312+
2. Check gateway health: `curl http://localhost:4444/health` (after port-forward)
313+
3. Register MCP servers with the gateway using the API
314+
4. Configure your MCP clients to use: {{ if .Values.mcpContextForge.ingress.enabled }}https://{{ .Values.mcpContextForge.ingress.host }}{{ else }}http://localhost:4444{{ end }}

charts/mcp-stack/templates/deployment-mcpgateway.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,32 @@ spec:
8888
################################################################
8989
# HEALTH & READINESS PROBES
9090
################################################################
91+
{{- if .Values.migration.enabled }}
92+
startupProbe:
93+
exec:
94+
command:
95+
- /bin/sh
96+
- -c
97+
- |
98+
# Check if migration check already passed
99+
if [ -f /tmp/migration_check_done ]; then
100+
exit 0
101+
fi
102+
# Wait for database to be ready (implies migration completed)
103+
python /app/mcpgateway/utils/db_isready.py --max-tries 1 --interval 1 --timeout 2 || exit 1
104+
# Mark check as done to avoid repeated database calls
105+
touch /tmp/migration_check_done
106+
exit 0
107+
initialDelaySeconds: 10
108+
periodSeconds: 5
109+
timeoutSeconds: 10
110+
failureThreshold: 60 # Allow up to 5 minutes for migration to complete
111+
{{- else }}
91112
{{- with .Values.mcpContextForge.probes.startup }}
92113
startupProbe:
93114
{{- include "helpers.renderProbe" (dict "probe" . "root" $) | nindent 12 }}
94115
{{- end }}
116+
{{- end }}
95117

96118
{{- with .Values.mcpContextForge.probes.readiness }}
97119
readinessProbe:

0 commit comments

Comments
 (0)