Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
131 changes: 131 additions & 0 deletions charts/mcp-stack/OPENSHIFT_DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# OpenShift Deployment Guide for ContextForge MCP Gateway

## Prerequisites

- OpenShift cluster with `oc` and `helm` CLI tools
- NFS PersistentVolume with `ReadWriteMany` access mode
- Docker Hub account (for pulling rate-limited images)

## Quick Deploy
```bash
# 1. Log in to OpenShift
oc login <cluster-api-url> --token=<your-token>
oc project <your-namespace>

# 2. Ensure NFS PV is available
oc get pv | grep nfsx

# If PV is in Released state, clear it:
oc patch pv nfsx-pv --type=json -p '[{"op":"remove","path":"/spec/claimRef"}]'

# 3. Create Docker Hub pull secret (required to avoid rate limits)
oc create secret docker-registry dockerhub-secret \
--docker-server=docker.io \
--docker-username=<your-dockerhub-username> \
--docker-password=<your-dockerhub-password> \
-n <your-namespace>
oc secrets link default dockerhub-secret --for=pull -n <your-namespace>

# 4. Install with Helm
helm install contextforge ./charts/mcp-stack -n <your-namespace> \
-f <your-values-file> \
--set mcpContextForge.replicaCount=1

# 5. Wait for pods (Postgres must start first, then migration, then gateway)
oc get pods -n <your-namespace> -w

# 6. Fix PgBouncer seccomp issue (OpenShift rejects RuntimeDefault)
oc get deployment contextforge-mcp-stack-pgbouncer -n <your-namespace> -o yaml > /tmp/pgbouncer-deploy.yaml
sed -i.bak '/seccompProfile/,/type: RuntimeDefault/d' /tmp/pgbouncer-deploy.yaml
oc replace -f /tmp/pgbouncer-deploy.yaml --force

# Add pull secret to PgBouncer
oc patch deployment contextforge-mcp-stack-pgbouncer -n <your-namespace> \
--type=strategic -p '{"spec":{"template":{"spec":{"imagePullSecrets":[{"name":"dockerhub-secret"}]}}}}'

# 7. Create the route (points to nginx proxy, not directly to gateway)
oc create route edge contextforge \
--service=contextforge-mcp-stack-nginx \
--port=80 \
--insecure-policy=Redirect \
-n <your-namespace>

# 8. Scale gateway to 2 replicas after migration completes
oc scale deployment contextforge-mcp-stack-mcpgateway -n <your-namespace> --replicas=2

# 9. Access the app
oc get route -n <your-namespace>
# Open the URL with https:// and log in with admin@example.com / admin123
```

## values.yaml Changes Required for OpenShift + NFS

The following changes were made to the default `values.yaml` for OpenShift on IBM Fyre with NFS storage:

| Setting | Default | Changed To | Reason |
|---------|---------|------------|--------|
| `pgbouncer.enabled` | `false` | `true` | Enables connection pooling |
| `pgbouncer.pool.mode` | `transaction` | `session` | Alembic migrations require advisory locks which only work in session mode |
| `postgres.persistence.useReadWriteOncePod` | `true` | `false` | NFS volumes only support ReadWriteMany |
| `postgres.persistence.accessModes` | `[ReadWriteOnce]` | `[ReadWriteMany]` | Must match the NFS PV access mode |
| `nginxProxy.enabled` | `false` | `true` | Enables nginx reverse proxy in front of gateway |
| `nginxProxy.persistence.enabled` | `true` | `false` | Only one NFS PV available on test clusters |
| `nginxProxy.image.repository` | `mcpgateway/nginx-cache` | `yosiefeyob1/nginx-cache` | Original image not published; built from infra/nginx/Dockerfile.amd64 |

## Post-Deploy Manual Fixes (Should Be Automated in Chart)

### 1. PgBouncer seccomp Profile
OpenShift's Security Context Constraints (SCC) reject `seccompProfile: RuntimeDefault` for regular service accounts. The PgBouncer deployment template includes this setting, causing pods to fail with `FailedCreate`. Fix by exporting the deployment YAML, removing the seccomp block, and replacing.

**Recommended chart fix:** Make the seccomp profile conditional or remove it for OpenShift compatibility.

### 2. Docker Hub Pull Secret
Docker Hub rate-limits unauthenticated pulls. The PgBouncer (`edoburu/pgbouncer`) and nginx images require authentication. A pull secret must be created and linked to the default service account, then patched into deployments.

**Recommended chart fix:** Support `global.imagePullSecrets` properly so all deployments inherit the pull secret.

### 3. Route Creation
The Helm chart does not create an OpenShift Route. The route must be created manually and should point to the nginx proxy service (not directly to the gateway).

**Recommended chart fix:** Add an optional OpenShift Route template in the chart, gated by a values flag like `route.enabled: true`.

## Nginx Proxy Image

The `mcpgateway/nginx-cache` image referenced in the chart does not exist on Docker Hub. It must be built from the Dockerfile in this repo.

- `infra/nginx/Dockerfile` β€” Original Red Hat UBI 10 base (requires x86 build environment)
- `infra/nginx/Dockerfile.amd64` β€” Alpine-based alternative (can be cross-compiled from ARM Macs using `docker buildx`)

To build from an ARM Mac:
```bash
docker buildx create --name mybuilder --use
docker buildx build --platform linux/amd64 -t <your-registry>/nginx-cache:latest -f infra/nginx/Dockerfile.amd64 infra/nginx/ --push
```

## Traffic Flow
```
Browser β†’ OpenShift Route β†’ Nginx Proxy (cache + reverse proxy) β†’ Gateway Pods β†’ PgBouncer β†’ PostgreSQL
```

## Troubleshooting
```bash
# Check all pods
oc get pods -n <your-namespace>

# Check pod logs
oc logs <pod-name> -n <your-namespace> --tail=50

# Check PVC binding
oc get pvc -n <your-namespace>
oc get pv

# Check route
oc get route -n <your-namespace>

# Check secrets
oc get secret -n <your-namespace>

# Full teardown and redeploy
helm uninstall contextforge -n <your-namespace>
oc patch pv nfsx-pv --type=json -p '[{"op":"remove","path":"/spec/claimRef"}]'
```
12 changes: 6 additions & 6 deletions charts/mcp-stack/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1043,9 +1043,9 @@ postgres:
# GKE: "standard", "ssd", "premium-rw"
# Azure AKS: "default", "managed-premium", "azurefile"
# Bare Metal: "local-path", "nfs-client"
useReadWriteOncePod: true # Prefer strict single-pod mount semantics when supported by your CSI/storage class
useReadWriteOncePod: false # # Disabled β€” NFS volumes only support ReadWriteMany
# Set false to fall back to accessModes below on clusters/storage classes without RWOP support
accessModes: [ReadWriteOnce]
accessModes: [ReadWriteMany]
size: 5Gi
reclaimPolicy: Retain # Retain prevents data loss when PVC is deleted (manual cleanup required after uninstall)
annotations: {} # Optional annotations for backup tools
Expand Down Expand Up @@ -1132,7 +1132,7 @@ postgres:
# Reduces connection overhead, improves throughput under high concurrency.
########################################################################
pgbouncer:
enabled: false # Set to true to route DB traffic through PgBouncer
enabled: true # Set to true to route DB traffic through PgBouncer

image:
repository: edoburu/pgbouncer
Expand Down Expand Up @@ -1503,10 +1503,10 @@ mcpFastTimeServer:
# NGINX PROXY - Reverse proxy / cache layer in front of the gateway
########################################################################
nginxProxy:
enabled: false
enabled: true

image:
repository: mcpgateway/nginx-cache
repository: yosiefeyob1/nginx-cache
tag: latest
pullPolicy: IfNotPresent

Expand All @@ -1516,7 +1516,7 @@ nginxProxy:

# Nginx cache persistence
persistence:
enabled: true
enabled: false
storageClassName: ""
accessModes: [ReadWriteOnce]
size: 2Gi
Expand Down
5 changes: 5 additions & 0 deletions charts/openshift-registry-setup/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: v2
name: openshift-registry-setup
description: Sets up shared image registry namespace, team group, and access policies
version: 1.0.0
type: application
28 changes: 28 additions & 0 deletions charts/openshift-registry-setup/templates/buildconfig.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{{- range .Values.builds }}
apiVersion: build.openshift.io/v1
kind: BuildConfig
metadata:
name: {{ .name }}
namespace: {{ $.Values.registryNamespace }}
spec:
output:
to:
kind: ImageStreamTag
name: {{ .name }}:latest
source:
type: Binary
binary: {}
strategy:
type: Docker
dockerStrategy:
dockerfilePath: {{ .dockerfilePath | default "Dockerfile" }}
---
apiVersion: image.openshift.io/v1
kind: ImageStream
metadata:
name: {{ .name }}
namespace: {{ $.Values.registryNamespace }}
spec:
lookupPolicy:
local: false
{{- end }}
8 changes: 8 additions & 0 deletions charts/openshift-registry-setup/templates/group.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: user.openshift.io/v1
kind: Group
metadata:
name: {{ .Values.team.name }}
users:
{{- range .Values.team.members }}
- {{ . }}
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{{- if .Values.globalPullAccess }}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: global-image-puller
namespace: {{ .Values.registryNamespace }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:image-puller
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:serviceaccounts
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ .Values.team.name }}-edit
namespace: {{ .Values.registryNamespace }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: edit
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: {{ .Values.team.name }}
19 changes: 19 additions & 0 deletions charts/openshift-registry-setup/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Namespace for storing shared images
registryNamespace: contextforge-images

# Team group configuration
team:
name: contextforge-team
members:
- Yosief.Eyob@ibm.com
- akshay.shinde26@ibm.com
- brian.hussey@ie.ibm.com
- Claudia.Gray@ibm.com

# Build configurations for images to host
builds:
- name: mcp-context-forge
dockerfilePath: Containerfile.lite

# Grant all namespaces pull access
globalPullAccess: true
16 changes: 16 additions & 0 deletions infra/nginx/Dockerfile.amd64
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM nginx:1.26-alpine
RUN apk add --no-cache curl ca-certificates
RUN mkdir -p /var/cache/nginx/static \
/var/cache/nginx/api \
/var/cache/nginx/schema \
/var/log/nginx \
/run/nginx && \
chown -R nginx:nginx /var/cache/nginx /var/log/nginx /run/nginx && \
chmod -R 755 /var/cache/nginx
COPY nginx.conf /etc/nginx/nginx.conf
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
EXPOSE 80 443
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost/health || curl -fk https://localhost/health || exit 1
ENTRYPOINT ["/docker-entrypoint.sh"]
Loading