feat(server): Add /live & /health endpoints
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
name: Release Charts
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
# depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions
|
||||||
|
# see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Configure Git
|
||||||
|
run: |
|
||||||
|
git config user.name "$GITHUB_ACTOR"
|
||||||
|
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||||
|
|
||||||
|
- name: Run chart-releaser
|
||||||
|
uses: helm/chart-releaser-action@v1.7.0
|
||||||
|
env:
|
||||||
|
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
@@ -157,6 +157,21 @@ ingress:
|
|||||||
| `autoscaling.maxReplicas` | Maximum replicas | `10` |
|
| `autoscaling.maxReplicas` | Maximum replicas | `10` |
|
||||||
| `autoscaling.targetCPUUtilizationPercentage` | Target CPU % | `80` |
|
| `autoscaling.targetCPUUtilizationPercentage` | Target CPU % | `80` |
|
||||||
|
|
||||||
|
#### Health Probes
|
||||||
|
|
||||||
|
| Parameter | Description | Default |
|
||||||
|
|-----------|-------------|---------|
|
||||||
|
| `livenessProbe.httpGet.path` | Liveness probe endpoint | `/health/live` |
|
||||||
|
| `livenessProbe.initialDelaySeconds` | Initial delay for liveness | `30` |
|
||||||
|
| `livenessProbe.periodSeconds` | Check interval for liveness | `10` |
|
||||||
|
| `readinessProbe.httpGet.path` | Readiness probe endpoint | `/health/ready` |
|
||||||
|
| `readinessProbe.initialDelaySeconds` | Initial delay for readiness | `10` |
|
||||||
|
| `readinessProbe.periodSeconds` | Check interval for readiness | `5` |
|
||||||
|
|
||||||
|
The application exposes HTTP health check endpoints:
|
||||||
|
- `/health/live` - Liveness probe (checks if application is running)
|
||||||
|
- `/health/ready` - Readiness probe (checks if application is ready to serve traffic)
|
||||||
|
|
||||||
#### Document Processing (Optional)
|
#### Document Processing (Optional)
|
||||||
|
|
||||||
| Parameter | Description | Default |
|
| Parameter | Description | Default |
|
||||||
@@ -382,14 +397,41 @@ kubectl get pods -l app.kubernetes.io/name=nextcloud-mcp-server
|
|||||||
kubectl logs -l app.kubernetes.io/name=nextcloud-mcp-server --tail=100 -f
|
kubectl logs -l app.kubernetes.io/name=nextcloud-mcp-server --tail=100 -f
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test connectivity to Nextcloud
|
### Check health endpoints
|
||||||
|
|
||||||
|
The application exposes health check endpoints for monitoring:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Port forward to the service
|
# Port forward to the service
|
||||||
kubectl port-forward svc/nextcloud-mcp 8000:8000
|
kubectl port-forward svc/nextcloud-mcp 8000:8000
|
||||||
|
|
||||||
# In another terminal, test the connection
|
# Check liveness (if app is running)
|
||||||
curl http://localhost:8000/
|
curl http://localhost:8000/health/live
|
||||||
|
|
||||||
|
# Check readiness (if app is ready to serve traffic)
|
||||||
|
curl http://localhost:8000/health/ready
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example responses:**
|
||||||
|
|
||||||
|
Liveness (always returns 200 if running):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "alive",
|
||||||
|
"mode": "basic"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Readiness (returns 200 if ready, 503 if not ready):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ready",
|
||||||
|
"checks": {
|
||||||
|
"nextcloud_configured": "ok",
|
||||||
|
"auth_mode": "basic",
|
||||||
|
"auth_configured": "ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Common Issues
|
### Common Issues
|
||||||
|
|||||||
@@ -202,18 +202,24 @@ resources:
|
|||||||
memory: 128Mi
|
memory: 128Mi
|
||||||
|
|
||||||
# Liveness probe configuration
|
# Liveness probe configuration
|
||||||
|
# Checks if the application process is running
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
tcpSocket:
|
httpGet:
|
||||||
|
path: /health/live
|
||||||
port: http
|
port: http
|
||||||
|
scheme: HTTP
|
||||||
initialDelaySeconds: 30
|
initialDelaySeconds: 30
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
timeoutSeconds: 5
|
timeoutSeconds: 5
|
||||||
failureThreshold: 3
|
failureThreshold: 3
|
||||||
|
|
||||||
# Readiness probe configuration
|
# Readiness probe configuration
|
||||||
|
# Checks if the application is ready to serve traffic
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
tcpSocket:
|
httpGet:
|
||||||
|
path: /health/ready
|
||||||
port: http
|
port: http
|
||||||
|
scheme: HTTP
|
||||||
initialDelaySeconds: 10
|
initialDelaySeconds: 10
|
||||||
periodSeconds: 5
|
periodSeconds: 5
|
||||||
timeoutSeconds: 3
|
timeoutSeconds: 3
|
||||||
|
|||||||
@@ -648,8 +648,71 @@ def get_app(transport: str = "sse", enabled_apps: list[str] | None = None):
|
|||||||
await stack.enter_async_context(mcp.session_manager.run())
|
await stack.enter_async_context(mcp.session_manager.run())
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
# Health check endpoints for Kubernetes probes
|
||||||
|
def health_live(request):
|
||||||
|
"""Liveness probe endpoint.
|
||||||
|
|
||||||
|
Returns 200 OK if the application process is running.
|
||||||
|
This is a simple check that doesn't verify external dependencies.
|
||||||
|
"""
|
||||||
|
return JSONResponse(
|
||||||
|
{
|
||||||
|
"status": "alive",
|
||||||
|
"mode": "oauth" if oauth_enabled else "basic",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
async def health_ready(request):
|
||||||
|
"""Readiness probe endpoint.
|
||||||
|
|
||||||
|
Returns 200 OK if the application is ready to serve traffic.
|
||||||
|
Checks that required configuration is present.
|
||||||
|
"""
|
||||||
|
checks = {}
|
||||||
|
is_ready = True
|
||||||
|
|
||||||
|
# Check Nextcloud host configuration
|
||||||
|
nextcloud_host = os.getenv("NEXTCLOUD_HOST")
|
||||||
|
if nextcloud_host:
|
||||||
|
checks["nextcloud_configured"] = "ok"
|
||||||
|
else:
|
||||||
|
checks["nextcloud_configured"] = "error: NEXTCLOUD_HOST not set"
|
||||||
|
is_ready = False
|
||||||
|
|
||||||
|
# Check authentication configuration
|
||||||
|
if oauth_enabled:
|
||||||
|
# OAuth mode - just verify we got this far (token_verifier initialized in lifespan)
|
||||||
|
checks["auth_mode"] = "oauth"
|
||||||
|
checks["auth_configured"] = "ok"
|
||||||
|
else:
|
||||||
|
# BasicAuth mode - verify credentials are set
|
||||||
|
username = os.getenv("NEXTCLOUD_USERNAME")
|
||||||
|
password = os.getenv("NEXTCLOUD_PASSWORD")
|
||||||
|
if username and password:
|
||||||
|
checks["auth_mode"] = "basic"
|
||||||
|
checks["auth_configured"] = "ok"
|
||||||
|
else:
|
||||||
|
checks["auth_mode"] = "basic"
|
||||||
|
checks["auth_configured"] = "error: credentials not set"
|
||||||
|
is_ready = False
|
||||||
|
|
||||||
|
status_code = 200 if is_ready else 503
|
||||||
|
return JSONResponse(
|
||||||
|
{
|
||||||
|
"status": "ready" if is_ready else "not_ready",
|
||||||
|
"checks": checks,
|
||||||
|
},
|
||||||
|
status_code=status_code,
|
||||||
|
)
|
||||||
|
|
||||||
# Add Protected Resource Metadata (PRM) endpoint for OAuth mode
|
# Add Protected Resource Metadata (PRM) endpoint for OAuth mode
|
||||||
routes = []
|
routes = []
|
||||||
|
|
||||||
|
# Add health check routes (available in both OAuth and BasicAuth modes)
|
||||||
|
routes.append(Route("/health/live", health_live, methods=["GET"]))
|
||||||
|
routes.append(Route("/health/ready", health_ready, methods=["GET"]))
|
||||||
|
logger.info("Health check endpoints enabled: /health/live, /health/ready")
|
||||||
|
|
||||||
if oauth_enabled:
|
if oauth_enabled:
|
||||||
|
|
||||||
def oauth_protected_resource_metadata(request):
|
def oauth_protected_resource_metadata(request):
|
||||||
|
|||||||
Reference in New Issue
Block a user