Files
poimen-memory/docs/OPENSEARCH_DEPLOYMENT_GUIDE.md
T

559 lines
14 KiB
Markdown
Raw Normal View History

# OpenSearch + Dashboards Deployment Guide
## Status: ✅ DEPLOYED
OpenSearch cluster + Dashboards UI are now running on K8s cluster `poimen` namespace.
**Deployment Command (already run):**
```bash
kubectl apply -k k8s/infra/databases/
```
**Commit:** `630a125` — deploy: OpenSearch + Dashboards StatefulSet
---
## 📊 What's Running
### OpenSearch Cluster (2-node HA)
```
opensearch-0 1/1 Running ← Primary node
opensearch-1 1/1 Running ← Replica node
opensearch-internal:9200 Ready ← API endpoint (Memory Service connects here)
```
**Configuration:**
- **Image:** opensearchproject/opensearch:2.11.0
- **Storage:** 30Gi per pod (Longhorn)
- **Resources:** 512Mi-1Gi memory, 250m-500m CPU each
- **Network:** K8s internal only (NetworkPolicy restricts access)
- **Security:** plugins.security.disabled (secured by K8s network)
### OpenSearch Dashboards (UI)
```
opensearch-dashboards-* 1/1 Running ← Dashboard pod
opensearch-dashboards:5601 Ready ← Web UI (port-forward for local access)
```
**Configuration:**
- **Image:** opensearchproject/opensearch-dashboards:2.11.0
- **Login:** admin / admin (⚠️ change in production)
- **Memory:** 256Mi-512Mi
- **CPU:** 100m-500m
---
## 🚀 Next Steps (Quick Start)
### 1. Verify OpenSearch Cluster is Healthy
```bash
# Port-forward to OpenSearch API
kubectl port-forward -n poimen svc/opensearch-internal 9200:9200 &
# Check cluster status
curl http://localhost:9200/_cluster/health
```
**Expected Output:**
```json
{
"cluster_name": "poimen-memory",
"status": "green",
"timed_out": false,
"number_of_nodes": 2,
"number_of_data_nodes": 2,
"active_primary_shards": 0,
"active_shards": 0,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0,
"delayed_unassigned_shards": 0,
"number_of_pending_tasks": 0,
"number_of_in_flight_fetch": 0,
"task_max_waiting_in_queue_millis": 0,
"active_shards_percent_as_number": 100.0
}
```
**green** = cluster healthy
⚠️ **yellow** = some replicas unavailable (wait 1-2 min)
**red** = cluster unhealthy (check pod logs)
### 2. Access Dashboards UI (Local Development)
```bash
# Port-forward to Dashboards
kubectl port-forward -n poimen svc/opensearch-dashboards 5601:5601 &
# Open in browser
open http://localhost:5601
# or
firefox http://localhost:5601
```
**Login:**
- **Username:** admin
- **Password:** admin
**First Time Setup:**
1. Dashboards auto-creates `.opensearch_dashboards` index
2. Accept default settings
3. Explore → Dev Tools → Console (for manual BM25 queries)
### 3. Configure Memory Service to Use OpenSearch
```bash
# Set environment variable
kubectl set env deployment poimen-memory -n poimen \
OPENSEARCH_HOSTS=opensearch-internal.poimen.svc.cluster.local:9200
# Restart Memory Service pods
kubectl rollout restart deployment poimen-memory -n poimen
# Wait for rollout
kubectl rollout status deployment poimen-memory -n poimen
```
**Verify Connection:**
```bash
# Check Memory Service logs
kubectl logs -n poimen -l app.kubernetes.io/name=poimen-memory --tail=50 | grep -i opensearch
# Should see: "Initialized OpenSearch client: opensearch-internal.poimen.svc.cluster.local:9200"
```
### 4. Test Vault Endpoints
```bash
# Get JWT token from Authentik
TOKEN=$(curl -s -X POST https://authentik.riotpiao.com/application/o/token/ \
-d "client_id=poimen-memory" \
-d "client_secret=$AUTHENTIK_SECRET" \
-d "grant_type=client_credentials" | jq -r .access_token)
# List projects
curl -H "Authorization: Bearer $TOKEN" \
https://vault.riotpiao.com/memory/vault
# List files in project
curl -H "Authorization: Bearer $TOKEN" \
'https://vault.riotpiao.com/memory/vault?project=poimen'
# Get specific file
curl -H "Authorization: Bearer $TOKEN" \
'https://vault.riotpiao.com/memory/vault/poimen/index' | jq .
```
**Expected:**
- 200 OK with JSON response
- If 401: check JWT token is valid
- If 403: check JWT has required scopes
### 5. Test Hybrid Search
```bash
# Semantic only (always works)
curl -H "Authorization: Bearer $TOKEN" \
'https://memory.riotpiao.com/memory/query?project=poimen&query=kubernetes&method=semantic'
# Hybrid search (now with OpenSearch)
curl -H "Authorization: Bearer $TOKEN" \
'https://memory.riotpiao.com/memory/query?project=poimen&query=kubernetes'
# Force strict hybrid (fail if OpenSearch down)
curl -H "Authorization: Bearer $TOKEN" \
'https://memory.riotpiao.com/memory/query?project=poimen&query=kubernetes&method=hybrid&strict=true'
```
**Expected Responses:**
**Hybrid (Fallback to Semantic if OpenSearch unavailable):**
```json
{
"method": "hybrid",
"query": "kubernetes",
"project": "poimen",
"results": [
{
"level": "L1",
"score": 0.992,
"text": "...",
"source": "claude",
"provenance": ["pi-1"]
}
]
}
```
⚠️ **Semantic Fallback (if OpenSearch down):**
```json
{
"method": "semantic_fallback",
"query": "kubernetes",
"project": "poimen",
"results": [...]
}
```
---
## 🛠️ Operations
### Monitor Cluster Health
```bash
# Watch pod status
kubectl get pods -n poimen -l app.kubernetes.io/name=opensearch -w
# Check OpenSearch logs
kubectl logs -n poimen opensearch-0 --tail=100
kubectl logs -n poimen opensearch-1 --tail=100
# Check Dashboards logs
kubectl logs -n poimen -l app.kubernetes.io/name=opensearch-dashboards --tail=50
```
### Common Issues
#### ❌ Pods not starting
**Symptom:** `Pending` or `CrashLoopBackOff`
**Check:**
```bash
kubectl describe pod -n poimen opensearch-0
```
**Common Causes:**
1. **vm.max_map_count too low** → Init container should fix (wait 30s)
2. **PVC not provisioned** → Check Longhorn: `kubectl get pvc -n poimen`
3. **Memory limit exceeded** → Increase `limits.memory` in StatefulSet
4. **Node affinity** → Check node labels: `kubectl get nodes --show-labels`
**Fix:**
```bash
# Force pod recreation
kubectl delete pod -n poimen opensearch-0
# Scheduler will restart it
# Check logs after restart
kubectl logs -n poimen opensearch-0 --tail=100
```
#### ❌ Cluster status = yellow
**Symptom:** Only 1 node showing, shards unassigned
**Cause:** Waiting for second node to start (normal during initial deployment)
**Fix:** Wait 1-2 minutes
```bash
# Watch until green
kubectl get pods -n poimen -l app.kubernetes.io/name=opensearch -w
```
#### ❌ Cluster status = red
**Symptom:** Cluster health = red, both nodes showing but unhealthy
**Debug:**
```bash
# Check node logs
kubectl logs -n poimen opensearch-0 --tail=200 | grep -i error
# Check if nodes can communicate
kubectl exec -n poimen opensearch-0 -- curl http://opensearch-1.opensearch.poimen.svc.cluster.local:9300/
```
**Common Causes:**
- Network Policy blocking communication
- Disk/memory pressure
- JVM out of memory
**Recovery:**
```bash
# Reset cluster (deletes data, be careful in production!)
kubectl delete pvc opensearch-data-opensearch-0 opensearch-data-opensearch-1 -n poimen
kubectl delete pod opensearch-0 opensearch-1 -n poimen
# Wait ~3 minutes for recovery
```
#### ❌ Dashboards can't connect to OpenSearch
**Symptom:** Dashboards UI shows "Cannot connect to Elasticsearch"
**Check:**
```bash
kubectl logs -n poimen -l app.kubernetes.io/name=opensearch-dashboards --tail=50
```
**Fix:**
```bash
# Verify OpenSearch is healthy
kubectl port-forward -n poimen svc/opensearch-internal 9200:9200 &
curl http://localhost:9200/_cluster/health
# Check Dashboards config
kubectl describe cm opensearch-dashboards-config -n poimen
# Should show: opensearch.hosts = ["http://opensearch-internal.poimen.svc.cluster.local:9200"]
# Restart Dashboards
kubectl rollout restart deployment opensearch-dashboards -n poimen
```
### Backup & Recovery
#### Export OpenSearch Indices
```bash
# List all indices
curl http://localhost:9200/_cat/indices
# Snapshot creation (requires S3/backup config)
# Docs: https://opensearch.org/docs/latest/tuning-your-cluster/availability-and-resilience/snapshots/snapshot-restore/
```
#### Restore from Backup
```bash
# Restore index from snapshot
curl -X POST http://localhost:9200/_snapshot/backup/snapshot-1/_restore
```
---
## 📈 Performance Tuning
### OpenSearch JVM Memory
**Current:** 512Mi-1Gi per pod
**For larger datasets (>1M vectors):**
```yaml
# Edit StatefulSet
kubectl edit statefulset opensearch -n poimen
# Update resources:
resources:
requests:
memory: "1Gi" # was 512Mi
limits:
memory: "2Gi" # was 1Gi
```
### OpenSearch Shard Configuration
**Current:** Auto-configured by Dashboards
**Optimize for hybrid search (many small shards):**
```bash
# After indices are created
curl -X PUT http://localhost:9200/vault-poimen/_settings -d '{
"index": {
"number_of_shards": 3,
"number_of_replicas": 1,
"codec": "best_compression",
"refresh_interval": "30s"
}
}'
```
### IVFFlat Index Parameters (pgvector side)
```sql
-- Optimize for 1M+ vectors
CREATE INDEX ON memory_vector
USING ivfflat (embedding vector_cosine_ops)
WITH (lists=1000); -- sqrt(1000000) ≈ 1000
-- Current: lists=100 (good for <100k vectors)
```
---
## 🔐 Security (Production Checklist)
### ⚠️ TODO: Production Security
- [ ] Change Dashboards password in secret:
```bash
kubectl patch secret opensearch-dashboards-secret -n poimen \
-p '{"stringData":{"password":"YOUR_SECURE_PASSWORD"}}'
kubectl rollout restart deployment opensearch-dashboards -n poimen
```
- [ ] Enable OpenSearch security plugin:
```yaml
# In opensearch.yml ConfigMap
plugins.security.disabled: "false"
plugins.security.ssl.http.enabled: "true"
plugins.security.ssl.http.keystore_filepath: "/usr/share/opensearch/config/certs/keystore.jks"
# ... (requires cert generation)
```
- [ ] Setup Dashboards OAuth2/SAML:
```yaml
# opensearch_dashboards.yml
opensearch.username: null # Remove hardcoded auth
opensearch.password: null
xpack.security.auth.providers: ["saml"]
# ... (requires IdP config)
```
- [ ] Setup NetworkPolicy for Ingress access (if exposing publicly)
- [ ] Enable audit logging:
```yaml
# opensearch.yml
plugins.security.audit.type: internal_opensearch
plugins.security.audit.config.http_endpoints: ["opensearch:9200"]
```
---
## 📚 Useful Commands
```bash
# Cluster status
curl http://localhost:9200/_cluster/health | jq .
# List indices
curl http://localhost:9200/_cat/indices | jq .
# Index stats
curl http://localhost:9200/_stats | jq .indices
# Node info
curl http://localhost:9200/_nodes | jq '.nodes | length'
# Clear query cache
curl -X POST http://localhost:9200/_cache/clear
# Force merge indices (maintenance)
curl -X POST http://localhost:9200/vault-*/_forcemerge?max_num_segments=1
# Pod resource usage
kubectl top pods -n poimen -l app.kubernetes.io/name=opensearch
# PVC usage
kubectl exec -n poimen opensearch-0 -- df -h /usr/share/opensearch/data
```
---
## 📞 Support
### Check Status Anytime
```bash
# Everything OK?
kubectl get pods -n poimen -l "app.kubernetes.io/name in (opensearch, opensearch-dashboards)"
# Cluster green?
kubectl port-forward -n poimen svc/opensearch-internal 9200:9200 &
curl http://localhost:9200/_cluster/health | jq .status
# Should show: "green"
```
### Logs
```bash
# Real-time OpenSearch logs
kubectl logs -n poimen opensearch-0 -f
# Real-time Dashboards logs
kubectl logs -n poimen -l app.kubernetes.io/name=opensearch-dashboards -f
# Search for errors
kubectl logs -n poimen opensearch-0 | grep -i error | tail -20
```
---
## 🎯 Integration with Memory Service
### Architecture
```
User Request (JWT)
┌────────────────────────────────┐
│ poimen-memory Pod │
│ (2 replicas) │
├────────────────────────────────┤
│ GET /memory/query │
│ ├─ Query pgvector (60%) │
│ └─ Query OpenSearch (40%) │
│ (BM25 full-text search) │
│ └─ Fusion & rerank │
└──────────┬──────────────────┬──┘
│ │
↓ ↓
pgvector(Postgres) OpenSearch Cluster
(768-dim embed) (2 nodes, HA)
IVFFlat index BM25 indices
```
### Query Flow
1. User sends: `GET /memory/query?project=X&query=Y`
2. Memory Service receives JWT, validates scopes
3. **Parallel queries:**
- pgvector: "SELECT ... ORDER BY embedding <-> query_embedding LIMIT 50"
- OpenSearch: "POST vault-X/_search" with BM25 query
4. **Fusion:** Combine top-50 results from each, score: `0.6*sem + 0.4*lex`
5. **Return:** Top-10 merged results with method indicator (hybrid / semantic_fallback / semantic)
### Graceful Degradation
- ✅ **OpenSearch healthy:** Hybrid search (60% + 40% fusion)
- ⚠️ **OpenSearch slow:** Timeout → fallback to semantic only
- ⚠️ **OpenSearch down:** Fallback to semantic only (no error)
- ❌ **pgvector down:** All queries fail (core dependency)
---
## 📋 Deployment Checklist Summary
### Phase 1: ✅ OpenSearch Deployed
- [x] StatefulSet: 2 replicas
- [x] Services: opensearch, opensearch-internal
- [x] ConfigMap: opensearch.yml
- [x] PVC: 30Gi storage
- [x] Dashboards: UI ready
### Phase 2: 🔄 Configure Memory Service (NEXT)
- [ ] Set OPENSEARCH_HOSTS env var
- [ ] Restart Memory Service pods
- [ ] Verify pod logs show connection success
### Phase 3: 🔄 Test API Endpoints
- [ ] Test vault endpoints
- [ ] Test semantic search
- [ ] Test hybrid search
### Phase 4: ⏳ Production Hardening (Future)
- [ ] Change Dashboards password
- [ ] Enable OpenSearch security plugin
- [ ] Setup OAuth2/SAML for Dashboards
- [ ] Enable audit logging
- [ ] Configure backup/recovery
---
## ✨ What's Next
**After Memory Service is configured with OPENSEARCH_HOSTS:**
1. **Vault Endpoints:** Test `vault.riotpiao.com` for file browsing
2. **Hybrid Search:** Test `memory.riotpiao.com/query` with hybrid results
3. **Frontend:** Deploy React app for UI (vault browser, search form)
4. **GRC Workflow:** Implement git + merge endpoints
5. **Agent Streaming:** WebSocket endpoint for real-time agent execution
---
Last Updated: Commit `630a125`