feat: add complete Kubernetes deployment infrastructure
Add production-ready deployment configuration for Raspberry Pi cluster with comprehensive documentation and automation scripts. Kubernetes Manifests (deploy/k8s/): - namespace.yaml - Dedicated namespace for the application - configmap.yaml - Environment configuration (MongoDB URI, ports, URLs) - secrets.yaml.example - Template for sensitive credentials (JWT, Cloudinary, Stripe) - mongodb-statefulset.yaml - MongoDB with persistent storage, placed on Pi 5 nodes (ARM64) - backend-deployment.yaml - Backend with 2 replicas, prefers Pi 5 nodes, health checks - frontend-deployment.yaml - Frontend with 2 replicas, can run on any node, nginx-based - ingress.yaml - Traefik/NGINX ingress for API, Socket.IO, and frontend routing Docker Configuration: - backend/Dockerfile - Multi-stage build for ARM64/ARMv7 with health checks - backend/.dockerignore - Excludes tests, coverage, node_modules from build - frontend/Dockerfile - Multi-stage build with nginx, optimized for ARM - frontend/.dockerignore - Excludes dev files from production build - frontend/nginx.conf - Production nginx config with gzip, caching, React Router support Resource Optimization for Pi Cluster: - MongoDB: 512Mi-2Gi RAM, 250m-1000m CPU (Pi 5 only, ARM64 affinity) - Backend: 256Mi-512Mi RAM, 100m-500m CPU (prefers Pi 5, ARM64) - Frontend: 64Mi-128Mi RAM, 50m-200m CPU (any node, lightweight) - Total: ~3.5GB RAM minimum, perfect for 2x Pi 5 (8GB) + 1x Pi 3B+ (1GB) Automation Scripts (deploy/scripts/): - build.sh - Build multi-arch images (ARM64/ARMv7) and push to registry - deploy.sh - Deploy all Kubernetes resources with health checks and status reporting - Both scripts include error handling, color output, and comprehensive logging Documentation (deploy/README.md): - Complete deployment guide with prerequisites - Step-by-step instructions for building and deploying - Verification commands and troubleshooting guide - Scaling, updating, and rollback procedures - Resource monitoring and cleanup instructions - Security best practices and performance optimization tips Health Endpoints: - Backend: GET /api/health (status, uptime, MongoDB connection) - Frontend: GET /health (nginx health check) - Used by Kubernetes liveness and readiness probes Key Features: - Multi-architecture support (ARM64 for Pi 5, ARMv7 for Pi 3B+) - NodeAffinity places heavy workloads (MongoDB, backend) on Pi 5 nodes - Persistent storage for MongoDB (10Gi PVC) - Horizontal pod autoscaling ready - Zero-downtime deployments with rolling updates - Comprehensive health monitoring - Production-grade nginx with security headers - Ingress routing for API, WebSocket, and static assets Security: - Secrets management with Kubernetes Secrets - secrets.yaml excluded from Git (.gitignore) - Minimal container images (alpine-based) - Health checks prevent unhealthy pods from serving traffic - Security headers in nginx (X-Frame-Options, X-Content-Type-Options, etc.) Usage: 1. Build images: ./deploy/scripts/build.sh 2. Configure secrets: cp deploy/k8s/secrets.yaml.example deploy/k8s/secrets.yaml 3. Deploy: ./deploy/scripts/deploy.sh 4. Monitor: kubectl get all -n adopt-a-street 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
deploy/k8s/secrets.yaml
|
||||||
15
backend/.dockerignore
Normal file
15
backend/.dockerignore
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
coverage
|
||||||
|
__tests__
|
||||||
|
*.test.js
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
README.md
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
uploads/*
|
||||||
|
!uploads/.gitkeep
|
||||||
38
backend/Dockerfile
Normal file
38
backend/Dockerfile
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Multi-stage build for ARM compatibility (Raspberry Pi)
|
||||||
|
FROM node:18-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm ci --only=production
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# --- Production stage ---
|
||||||
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
# Install curl for health checks
|
||||||
|
RUN apk add --no-cache curl
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy dependencies from builder
|
||||||
|
COPY --from=builder /app/node_modules ./node_modules
|
||||||
|
COPY --from=builder /app .
|
||||||
|
|
||||||
|
# Create uploads directory
|
||||||
|
RUN mkdir -p uploads
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s \
|
||||||
|
CMD node -e "require('http').get('http://localhost:5000/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
|
||||||
|
|
||||||
|
# Start server
|
||||||
|
CMD ["node", "server.js"]
|
||||||
@@ -115,6 +115,16 @@ app.use("/api/auth/register", authLimiter);
|
|||||||
app.use("/api/auth/login", authLimiter);
|
app.use("/api/auth/login", authLimiter);
|
||||||
app.use("/api", apiLimiter);
|
app.use("/api", apiLimiter);
|
||||||
|
|
||||||
|
// Health check endpoint (for Kubernetes liveness/readiness probes)
|
||||||
|
app.get("/api/health", (req, res) => {
|
||||||
|
res.status(200).json({
|
||||||
|
status: "healthy",
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
uptime: process.uptime(),
|
||||||
|
mongodb: mongoose.connection.readyState === 1 ? "connected" : "disconnected",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
app.use("/api/auth", authRoutes);
|
app.use("/api/auth", authRoutes);
|
||||||
app.use("/api/streets", streetRoutes);
|
app.use("/api/streets", streetRoutes);
|
||||||
|
|||||||
395
deploy/README.md
Normal file
395
deploy/README.md
Normal file
@@ -0,0 +1,395 @@
|
|||||||
|
# Adopt-a-Street Deployment
|
||||||
|
|
||||||
|
This directory contains deployment configurations for the Adopt-a-Street application on Kubernetes (Raspberry Pi cluster).
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
deploy/
|
||||||
|
├── k8s/ # Kubernetes manifests
|
||||||
|
│ ├── namespace.yaml # Namespace definition
|
||||||
|
│ ├── configmap.yaml # Environment configuration
|
||||||
|
│ ├── secrets.yaml.example # Secret template (COPY TO secrets.yaml)
|
||||||
|
│ ├── mongodb-statefulset.yaml # MongoDB StatefulSet with PVC
|
||||||
|
│ ├── backend-deployment.yaml # Backend Deployment + Service
|
||||||
|
│ ├── frontend-deployment.yaml # Frontend Deployment + Service
|
||||||
|
│ └── ingress.yaml # Ingress for routing
|
||||||
|
├── README.md # This file
|
||||||
|
└── scripts/ # Deployment helper scripts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
### Cluster Requirements
|
||||||
|
- Kubernetes cluster with 3 nodes:
|
||||||
|
- 2x Raspberry Pi 5 (8GB RAM) - ARM64
|
||||||
|
- 1x Raspberry Pi 3B+ (1GB RAM) - ARMv7
|
||||||
|
- kubectl configured to access your cluster
|
||||||
|
- Container registry accessible from cluster
|
||||||
|
- Ingress controller installed (Traefik or NGINX Ingress)
|
||||||
|
- Persistent storage provisioner (local-path, NFS, or Longhorn)
|
||||||
|
|
||||||
|
### Local Requirements
|
||||||
|
- Docker with buildx for multi-arch builds
|
||||||
|
- kubectl CLI tool
|
||||||
|
- Access to container registry (Docker Hub, GitHub Container Registry, or private registry)
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Build Multi-Arch Docker Images
|
||||||
|
|
||||||
|
Build images for both ARM64 (Pi 5) and ARMv7 (Pi 3B+):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From project root
|
||||||
|
cd /home/will/Code/adopt-a-street
|
||||||
|
|
||||||
|
# Create buildx builder (one-time setup)
|
||||||
|
docker buildx create --use --name multiarch-builder
|
||||||
|
|
||||||
|
# Build and push backend
|
||||||
|
docker buildx build --platform linux/arm64,linux/arm/v7 \
|
||||||
|
-t your-registry/adopt-a-street-backend:latest \
|
||||||
|
--push ./backend
|
||||||
|
|
||||||
|
# Build and push frontend
|
||||||
|
docker buildx build --platform linux/arm64,linux/arm/v7 \
|
||||||
|
-t your-registry/adopt-a-street-frontend:latest \
|
||||||
|
--push ./frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Replace `your-registry` with your actual registry (e.g., `docker.io/username` or `ghcr.io/username`)
|
||||||
|
|
||||||
|
### 2. Configure Secrets
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy secrets template
|
||||||
|
cp deploy/k8s/secrets.yaml.example deploy/k8s/secrets.yaml
|
||||||
|
|
||||||
|
# Edit secrets with your actual values
|
||||||
|
nano deploy/k8s/secrets.yaml
|
||||||
|
|
||||||
|
# IMPORTANT: Add secrets.yaml to .gitignore if not already there
|
||||||
|
echo "deploy/k8s/secrets.yaml" >> .gitignore
|
||||||
|
```
|
||||||
|
|
||||||
|
**Required Secrets:**
|
||||||
|
- `JWT_SECRET` - Strong random string for JWT signing
|
||||||
|
- `CLOUDINARY_CLOUD_NAME` - Your Cloudinary cloud name
|
||||||
|
- `CLOUDINARY_API_KEY` - Your Cloudinary API key
|
||||||
|
- `CLOUDINARY_API_SECRET` - Your Cloudinary API secret
|
||||||
|
|
||||||
|
### 3. Update Image References
|
||||||
|
|
||||||
|
Update the image references in deployment files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update backend image reference
|
||||||
|
nano deploy/k8s/backend-deployment.yaml
|
||||||
|
# Change: image: your-registry/adopt-a-street-backend:latest
|
||||||
|
|
||||||
|
# Update frontend image reference
|
||||||
|
nano deploy/k8s/frontend-deployment.yaml
|
||||||
|
# Change: image: your-registry/adopt-a-street-frontend:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Update Domain Name
|
||||||
|
|
||||||
|
Update the ingress host:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nano deploy/k8s/ingress.yaml
|
||||||
|
# Change: host: adopt-a-street.local
|
||||||
|
# To your actual domain or IP
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Deploy to Kubernetes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create namespace
|
||||||
|
kubectl apply -f deploy/k8s/namespace.yaml
|
||||||
|
|
||||||
|
# Create secrets (IMPORTANT: Make sure you've edited secrets.yaml!)
|
||||||
|
kubectl apply -f deploy/k8s/secrets.yaml
|
||||||
|
|
||||||
|
# Create ConfigMap
|
||||||
|
kubectl apply -f deploy/k8s/configmap.yaml
|
||||||
|
|
||||||
|
# Deploy MongoDB
|
||||||
|
kubectl apply -f deploy/k8s/mongodb-statefulset.yaml
|
||||||
|
|
||||||
|
# Wait for MongoDB to be ready (this may take 1-2 minutes)
|
||||||
|
kubectl wait --for=condition=ready pod -l app=mongodb -n adopt-a-street --timeout=120s
|
||||||
|
|
||||||
|
# Deploy backend
|
||||||
|
kubectl apply -f deploy/k8s/backend-deployment.yaml
|
||||||
|
|
||||||
|
# Wait for backend to be ready
|
||||||
|
kubectl wait --for=condition=ready pod -l app=backend -n adopt-a-street --timeout=120s
|
||||||
|
|
||||||
|
# Deploy frontend
|
||||||
|
kubectl apply -f deploy/k8s/frontend-deployment.yaml
|
||||||
|
|
||||||
|
# Deploy ingress
|
||||||
|
kubectl apply -f deploy/k8s/ingress.yaml
|
||||||
|
|
||||||
|
# Check deployment status
|
||||||
|
kubectl get all -n adopt-a-street
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
### Check Pod Status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View all resources
|
||||||
|
kubectl get all -n adopt-a-street
|
||||||
|
|
||||||
|
# Check pod status
|
||||||
|
kubectl get pods -n adopt-a-street
|
||||||
|
|
||||||
|
# Expected output:
|
||||||
|
# NAME READY STATUS RESTARTS AGE
|
||||||
|
# adopt-a-street-backend-xxxxxxxxxx-xxxxx 1/1 Running 0 5m
|
||||||
|
# adopt-a-street-backend-xxxxxxxxxx-xxxxx 1/1 Running 0 5m
|
||||||
|
# adopt-a-street-frontend-xxxxxxxxx-xxxxx 1/1 Running 0 5m
|
||||||
|
# adopt-a-street-frontend-xxxxxxxxx-xxxxx 1/1 Running 0 5m
|
||||||
|
# adopt-a-street-mongodb-0 1/1 Running 0 10m
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Backend logs
|
||||||
|
kubectl logs -f deployment/adopt-a-street-backend -n adopt-a-street
|
||||||
|
|
||||||
|
# Frontend logs
|
||||||
|
kubectl logs -f deployment/adopt-a-street-frontend -n adopt-a-street
|
||||||
|
|
||||||
|
# MongoDB logs
|
||||||
|
kubectl logs -f adopt-a-street-mongodb-0 -n adopt-a-street
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Services
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl get svc -n adopt-a-street
|
||||||
|
|
||||||
|
# Expected output:
|
||||||
|
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||||
|
# adopt-a-street-backend ClusterIP 10.43.x.x <none> 5000/TCP 5m
|
||||||
|
# adopt-a-street-frontend ClusterIP 10.43.x.x <none> 80/TCP 5m
|
||||||
|
# adopt-a-street-mongodb ClusterIP None <none> 27017/TCP 10m
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Ingress
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl get ingress -n adopt-a-street
|
||||||
|
|
||||||
|
# Get ingress details
|
||||||
|
kubectl describe ingress adopt-a-street-ingress -n adopt-a-street
|
||||||
|
```
|
||||||
|
|
||||||
|
### Access the Application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Port forward for testing (if ingress not working)
|
||||||
|
kubectl port-forward svc/adopt-a-street-frontend 3000:80 -n adopt-a-street
|
||||||
|
|
||||||
|
# Then open http://localhost:3000 in your browser
|
||||||
|
```
|
||||||
|
|
||||||
|
## Resource Allocation
|
||||||
|
|
||||||
|
The deployment is optimized for Raspberry Pi hardware:
|
||||||
|
|
||||||
|
### MongoDB (Pi 5 nodes only)
|
||||||
|
- **Requests:** 512Mi RAM, 250m CPU
|
||||||
|
- **Limits:** 2Gi RAM, 1000m CPU
|
||||||
|
- **Storage:** 10Gi persistent volume
|
||||||
|
|
||||||
|
### Backend (prefers Pi 5 nodes)
|
||||||
|
- **Requests:** 256Mi RAM, 100m CPU
|
||||||
|
- **Limits:** 512Mi RAM, 500m CPU
|
||||||
|
- **Replicas:** 2 pods
|
||||||
|
|
||||||
|
### Frontend (any node)
|
||||||
|
- **Requests:** 64Mi RAM, 50m CPU
|
||||||
|
- **Limits:** 128Mi RAM, 200m CPU
|
||||||
|
- **Replicas:** 2 pods
|
||||||
|
|
||||||
|
### Total Cluster Requirements
|
||||||
|
- **Minimum RAM:** ~3.5 GB (1.5GB MongoDB + 1GB backend + 200MB frontend + 800MB system)
|
||||||
|
- **Recommended:** 2x Pi 5 (8GB each) handles this comfortably
|
||||||
|
|
||||||
|
## Scaling
|
||||||
|
|
||||||
|
### Scale Deployments
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Scale backend
|
||||||
|
kubectl scale deployment adopt-a-street-backend --replicas=3 -n adopt-a-street
|
||||||
|
|
||||||
|
# Scale frontend
|
||||||
|
kubectl scale deployment adopt-a-street-frontend --replicas=3 -n adopt-a-street
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** MongoDB is a StatefulSet with 1 replica. Scaling MongoDB requires configuring replication.
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
### Update Images
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build and push new version
|
||||||
|
docker buildx build --platform linux/arm64,linux/arm/v7 \
|
||||||
|
-t your-registry/adopt-a-street-backend:v2.0 \
|
||||||
|
--push ./backend
|
||||||
|
|
||||||
|
# Update deployment
|
||||||
|
kubectl set image deployment/adopt-a-street-backend \
|
||||||
|
backend=your-registry/adopt-a-street-backend:v2.0 \
|
||||||
|
-n adopt-a-street
|
||||||
|
|
||||||
|
# Check rollout status
|
||||||
|
kubectl rollout status deployment/adopt-a-street-backend -n adopt-a-street
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rollback
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Rollback to previous version
|
||||||
|
kubectl rollout undo deployment/adopt-a-street-backend -n adopt-a-street
|
||||||
|
|
||||||
|
# Rollback to specific revision
|
||||||
|
kubectl rollout undo deployment/adopt-a-street-backend --to-revision=2 -n adopt-a-street
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
|
||||||
|
### Resource Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Node resource usage
|
||||||
|
kubectl top nodes
|
||||||
|
|
||||||
|
# Pod resource usage
|
||||||
|
kubectl top pods -n adopt-a-street
|
||||||
|
```
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View recent events
|
||||||
|
kubectl get events -n adopt-a-street --sort-by='.lastTimestamp'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Describe Resources
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Describe pod (useful for troubleshooting)
|
||||||
|
kubectl describe pod <pod-name> -n adopt-a-street
|
||||||
|
|
||||||
|
# Describe deployment
|
||||||
|
kubectl describe deployment adopt-a-street-backend -n adopt-a-street
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Pod Not Starting
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check pod events
|
||||||
|
kubectl describe pod <pod-name> -n adopt-a-street
|
||||||
|
|
||||||
|
# Check logs
|
||||||
|
kubectl logs <pod-name> -n adopt-a-street
|
||||||
|
|
||||||
|
# Check previous logs (if pod crashed)
|
||||||
|
kubectl logs <pod-name> -n adopt-a-street --previous
|
||||||
|
```
|
||||||
|
|
||||||
|
### Image Pull Errors
|
||||||
|
|
||||||
|
- Verify image exists in registry
|
||||||
|
- Check image name and tag in deployment
|
||||||
|
- Verify cluster can access registry
|
||||||
|
- Check if imagePullSecrets are needed
|
||||||
|
|
||||||
|
### MongoDB Connection Issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Shell into backend pod
|
||||||
|
kubectl exec -it <backend-pod-name> -n adopt-a-street -- sh
|
||||||
|
|
||||||
|
# Test MongoDB connection
|
||||||
|
wget -qO- http://adopt-a-street-mongodb:27017
|
||||||
|
```
|
||||||
|
|
||||||
|
### Persistent Volume Issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check PVCs
|
||||||
|
kubectl get pvc -n adopt-a-street
|
||||||
|
|
||||||
|
# Check PVs
|
||||||
|
kubectl get pv
|
||||||
|
|
||||||
|
# Describe PVC
|
||||||
|
kubectl describe pvc mongodb-data-adopt-a-street-mongodb-0 -n adopt-a-street
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cleanup
|
||||||
|
|
||||||
|
### Delete Everything
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Delete all resources in namespace
|
||||||
|
kubectl delete namespace adopt-a-street
|
||||||
|
|
||||||
|
# Or delete resources individually
|
||||||
|
kubectl delete -f deploy/k8s/ingress.yaml
|
||||||
|
kubectl delete -f deploy/k8s/frontend-deployment.yaml
|
||||||
|
kubectl delete -f deploy/k8s/backend-deployment.yaml
|
||||||
|
kubectl delete -f deploy/k8s/mongodb-statefulset.yaml
|
||||||
|
kubectl delete -f deploy/k8s/configmap.yaml
|
||||||
|
kubectl delete -f deploy/k8s/secrets.yaml
|
||||||
|
kubectl delete -f deploy/k8s/namespace.yaml
|
||||||
|
|
||||||
|
# Note: This will also delete the persistent volume data!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Best Practices
|
||||||
|
|
||||||
|
1. **Never commit secrets.yaml** - Always use secrets.yaml.example
|
||||||
|
2. **Use strong JWT_SECRET** - Generate with: `openssl rand -base64 32`
|
||||||
|
3. **Enable TLS/HTTPS** - Uncomment TLS section in ingress.yaml and use cert-manager
|
||||||
|
4. **Restrict ingress** - Use network policies to limit pod communication
|
||||||
|
5. **Use image digests** - Pin images to specific SHA256 digests for production
|
||||||
|
6. **Enable RBAC** - Create service accounts with minimal permissions
|
||||||
|
7. **Scan images** - Use tools like Trivy to scan for vulnerabilities
|
||||||
|
|
||||||
|
## Performance Optimization
|
||||||
|
|
||||||
|
1. **Use imagePullPolicy: IfNotPresent** - After initial deployment to save bandwidth
|
||||||
|
2. **Implement HPA** - Horizontal Pod Autoscaler for dynamic scaling
|
||||||
|
3. **Add Redis** - For caching to reduce MongoDB load
|
||||||
|
4. **Use CDN** - For frontend static assets
|
||||||
|
5. **Enable compression** - Nginx already configured with gzip
|
||||||
|
6. **Monitor resources** - Use Prometheus + Grafana for metrics
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
- [Kubernetes Documentation](https://kubernetes.io/docs/)
|
||||||
|
- [Raspberry Pi Kubernetes Guide](https://github.com/alexellis/k8s-on-raspbian)
|
||||||
|
- [Helm Charts](https://helm.sh/) - Consider migrating to Helm for easier management
|
||||||
|
- [ArgoCD](https://argoproj.github.io/cd/) - GitOps continuous delivery for Kubernetes
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues or questions:
|
||||||
|
1. Check pod logs: `kubectl logs <pod-name> -n adopt-a-street`
|
||||||
|
2. Check events: `kubectl get events -n adopt-a-street`
|
||||||
|
3. Describe resources: `kubectl describe <resource> -n adopt-a-street`
|
||||||
|
4. Review application logs in the backend
|
||||||
79
deploy/k8s/backend-deployment.yaml
Normal file
79
deploy/k8s/backend-deployment.yaml
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: adopt-a-street-backend
|
||||||
|
namespace: adopt-a-street
|
||||||
|
labels:
|
||||||
|
app: backend
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: backend
|
||||||
|
ports:
|
||||||
|
- port: 5000
|
||||||
|
targetPort: 5000
|
||||||
|
name: http
|
||||||
|
type: ClusterIP
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: adopt-a-street-backend
|
||||||
|
namespace: adopt-a-street
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: backend
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: backend
|
||||||
|
spec:
|
||||||
|
# Prefer Pi 5 nodes for backend (more RAM for Node.js)
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- weight: 100
|
||||||
|
preference:
|
||||||
|
matchExpressions:
|
||||||
|
- key: kubernetes.io/arch
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- arm64 # Pi 5 architecture
|
||||||
|
containers:
|
||||||
|
- name: backend
|
||||||
|
# Update with your registry and tag
|
||||||
|
image: your-registry/adopt-a-street-backend:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 5000
|
||||||
|
name: http
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: adopt-a-street-config
|
||||||
|
- secretRef:
|
||||||
|
name: adopt-a-street-secrets
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "256Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
limits:
|
||||||
|
memory: "512Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/health
|
||||||
|
port: 5000
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/health
|
||||||
|
port: 5000
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
timeoutSeconds: 3
|
||||||
|
failureThreshold: 3
|
||||||
15
deploy/k8s/configmap.yaml
Normal file
15
deploy/k8s/configmap.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: adopt-a-street-config
|
||||||
|
namespace: adopt-a-street
|
||||||
|
data:
|
||||||
|
# MongoDB Connection
|
||||||
|
MONGO_URI: "mongodb://adopt-a-street-mongodb:27017/adopt-a-street"
|
||||||
|
|
||||||
|
# Backend Configuration
|
||||||
|
PORT: "5000"
|
||||||
|
NODE_ENV: "production"
|
||||||
|
|
||||||
|
# Frontend URL (update with your actual domain)
|
||||||
|
FRONTEND_URL: "http://adopt-a-street.local"
|
||||||
64
deploy/k8s/frontend-deployment.yaml
Normal file
64
deploy/k8s/frontend-deployment.yaml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: adopt-a-street-frontend
|
||||||
|
namespace: adopt-a-street
|
||||||
|
labels:
|
||||||
|
app: frontend
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: frontend
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
name: http
|
||||||
|
type: ClusterIP
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: adopt-a-street-frontend
|
||||||
|
namespace: adopt-a-street
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: frontend
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: frontend
|
||||||
|
spec:
|
||||||
|
# Frontend can run on any node (lightweight static serving)
|
||||||
|
containers:
|
||||||
|
- name: frontend
|
||||||
|
# Update with your registry and tag
|
||||||
|
image: your-registry/adopt-a-street-frontend:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
name: http
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "64Mi"
|
||||||
|
cpu: "50m"
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "200m"
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 80
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 3
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 80
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
timeoutSeconds: 3
|
||||||
|
failureThreshold: 3
|
||||||
53
deploy/k8s/ingress.yaml
Normal file
53
deploy/k8s/ingress.yaml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: adopt-a-street-ingress
|
||||||
|
namespace: adopt-a-street
|
||||||
|
annotations:
|
||||||
|
# Uncomment the appropriate ingress class for your cluster
|
||||||
|
kubernetes.io/ingress.class: "traefik" # For Traefik
|
||||||
|
# kubernetes.io/ingress.class: "nginx" # For NGINX Ingress
|
||||||
|
|
||||||
|
# Uncomment if using cert-manager for TLS
|
||||||
|
# cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||||
|
|
||||||
|
# Traefik specific annotations (uncomment if using Traefik)
|
||||||
|
# traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
|
||||||
|
# traefik.ingress.kubernetes.io/router.middlewares: default-redirect-https@kubernetescrd
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: adopt-a-street.local # CHANGE THIS to your actual domain
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
# API endpoints
|
||||||
|
- path: /api
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: adopt-a-street-backend
|
||||||
|
port:
|
||||||
|
number: 5000
|
||||||
|
|
||||||
|
# Socket.IO endpoints
|
||||||
|
- path: /socket.io
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: adopt-a-street-backend
|
||||||
|
port:
|
||||||
|
number: 5000
|
||||||
|
|
||||||
|
# Frontend (must be last - catches all other paths)
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: adopt-a-street-frontend
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
|
||||||
|
# Uncomment for TLS/HTTPS
|
||||||
|
# tls:
|
||||||
|
# - hosts:
|
||||||
|
# - adopt-a-street.local
|
||||||
|
# secretName: adopt-a-street-tls
|
||||||
89
deploy/k8s/mongodb-statefulset.yaml
Normal file
89
deploy/k8s/mongodb-statefulset.yaml
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: adopt-a-street-mongodb
|
||||||
|
namespace: adopt-a-street
|
||||||
|
labels:
|
||||||
|
app: mongodb
|
||||||
|
spec:
|
||||||
|
clusterIP: None # Headless service for StatefulSet
|
||||||
|
selector:
|
||||||
|
app: mongodb
|
||||||
|
ports:
|
||||||
|
- port: 27017
|
||||||
|
targetPort: 27017
|
||||||
|
name: mongodb
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: adopt-a-street-mongodb
|
||||||
|
namespace: adopt-a-street
|
||||||
|
spec:
|
||||||
|
serviceName: adopt-a-street-mongodb
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: mongodb
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: mongodb
|
||||||
|
spec:
|
||||||
|
# Place MongoDB on Pi 5 nodes (more RAM)
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: kubernetes.io/arch
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- arm64 # Pi 5 architecture
|
||||||
|
containers:
|
||||||
|
- name: mongodb
|
||||||
|
image: mongo:7.0
|
||||||
|
ports:
|
||||||
|
- containerPort: 27017
|
||||||
|
name: mongodb
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "512Mi"
|
||||||
|
cpu: "250m"
|
||||||
|
limits:
|
||||||
|
memory: "2Gi"
|
||||||
|
cpu: "1000m"
|
||||||
|
volumeMounts:
|
||||||
|
- name: mongodb-data
|
||||||
|
mountPath: /data/db
|
||||||
|
livenessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- mongosh
|
||||||
|
- --eval
|
||||||
|
- "db.adminCommand('ping')"
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- mongosh
|
||||||
|
- --eval
|
||||||
|
- "db.adminCommand('ping')"
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
volumeClaimTemplates:
|
||||||
|
- metadata:
|
||||||
|
name: mongodb-data
|
||||||
|
spec:
|
||||||
|
accessModes: ["ReadWriteOnce"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
# Uncomment and set your storage class if needed
|
||||||
|
# storageClassName: local-path
|
||||||
7
deploy/k8s/namespace.yaml
Normal file
7
deploy/k8s/namespace.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: adopt-a-street
|
||||||
|
labels:
|
||||||
|
name: adopt-a-street
|
||||||
|
environment: production
|
||||||
24
deploy/k8s/secrets.yaml.example
Normal file
24
deploy/k8s/secrets.yaml.example
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: adopt-a-street-secrets
|
||||||
|
namespace: adopt-a-street
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
# JWT Secret - CHANGE THIS IN PRODUCTION!
|
||||||
|
JWT_SECRET: "your-super-secret-jwt-key-change-in-production"
|
||||||
|
|
||||||
|
# Cloudinary Configuration
|
||||||
|
CLOUDINARY_CLOUD_NAME: "your-cloudinary-cloud-name"
|
||||||
|
CLOUDINARY_API_KEY: "your-cloudinary-api-key"
|
||||||
|
CLOUDINARY_API_SECRET: "your-cloudinary-api-secret"
|
||||||
|
|
||||||
|
# Stripe Configuration (optional - currently mocked)
|
||||||
|
# STRIPE_SECRET_KEY: "your-stripe-secret-key"
|
||||||
|
|
||||||
|
---
|
||||||
|
# IMPORTANT:
|
||||||
|
# 1. Copy this file to secrets.yaml
|
||||||
|
# 2. Replace all placeholder values with real secrets
|
||||||
|
# 3. DO NOT commit secrets.yaml to version control
|
||||||
|
# 4. Add secrets.yaml to .gitignore
|
||||||
95
deploy/scripts/build.sh
Executable file
95
deploy/scripts/build.sh
Executable file
@@ -0,0 +1,95 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Adopt-a-Street Multi-Arch Docker Build Script
|
||||||
|
# Builds images for ARM64 (Pi 5) and ARMv7 (Pi 3B+)
|
||||||
|
|
||||||
|
set -e # Exit on error
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
REGISTRY="${DOCKER_REGISTRY:-your-registry}"
|
||||||
|
TAG="${DOCKER_TAG:-latest}"
|
||||||
|
|
||||||
|
echo -e "${GREEN}🐳 Adopt-a-Street Multi-Arch Docker Build${NC}"
|
||||||
|
echo "================================================"
|
||||||
|
echo "Registry: ${REGISTRY}"
|
||||||
|
echo "Tag: ${TAG}"
|
||||||
|
echo "Project Root: ${PROJECT_ROOT}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if docker buildx is available
|
||||||
|
if ! docker buildx version &> /dev/null; then
|
||||||
|
echo -e "${RED}❌ Docker buildx not found. Please install Docker with buildx support.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create buildx builder if it doesn't exist
|
||||||
|
if ! docker buildx inspect multiarch-builder &> /dev/null; then
|
||||||
|
echo "🔨 Creating buildx builder..."
|
||||||
|
docker buildx create --use --name multiarch-builder
|
||||||
|
echo -e "${GREEN}✓${NC} Builder created"
|
||||||
|
else
|
||||||
|
echo "🔨 Using existing buildx builder..."
|
||||||
|
docker buildx use multiarch-builder
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Prompt for registry if using default
|
||||||
|
if [ "${REGISTRY}" = "your-registry" ]; then
|
||||||
|
echo -e "${YELLOW}⚠️ Using default registry 'your-registry'${NC}"
|
||||||
|
echo -e "${YELLOW}Set DOCKER_REGISTRY environment variable to use a different registry:${NC}"
|
||||||
|
echo " export DOCKER_REGISTRY=docker.io/username"
|
||||||
|
echo " export DOCKER_REGISTRY=ghcr.io/username"
|
||||||
|
echo ""
|
||||||
|
read -p "Continue with 'your-registry'? (y/N) " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Aborted."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build backend
|
||||||
|
echo "🔧 Building backend image..."
|
||||||
|
echo " Platforms: linux/arm64, linux/arm/v7"
|
||||||
|
echo " Image: ${REGISTRY}/adopt-a-street-backend:${TAG}"
|
||||||
|
docker buildx build --platform linux/arm64,linux/arm/v7 \
|
||||||
|
-t "${REGISTRY}/adopt-a-street-backend:${TAG}" \
|
||||||
|
--push \
|
||||||
|
"${PROJECT_ROOT}/backend"
|
||||||
|
echo -e "${GREEN}✓${NC} Backend image built and pushed"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Build frontend
|
||||||
|
echo "🎨 Building frontend image..."
|
||||||
|
echo " Platforms: linux/arm64, linux/arm/v7"
|
||||||
|
echo " Image: ${REGISTRY}/adopt-a-street-frontend:${TAG}"
|
||||||
|
docker buildx build --platform linux/arm64,linux/arm/v7 \
|
||||||
|
-t "${REGISTRY}/adopt-a-street-frontend:${TAG}" \
|
||||||
|
--push \
|
||||||
|
"${PROJECT_ROOT}/frontend"
|
||||||
|
echo -e "${GREEN}✓${NC} Frontend image built and pushed"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "================================================"
|
||||||
|
echo -e "${GREEN}✅ Build Complete!${NC}"
|
||||||
|
echo "================================================"
|
||||||
|
echo ""
|
||||||
|
echo "Images pushed:"
|
||||||
|
echo " 📦 ${REGISTRY}/adopt-a-street-backend:${TAG}"
|
||||||
|
echo " 📦 ${REGISTRY}/adopt-a-street-frontend:${TAG}"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}📝 Next Steps:${NC}"
|
||||||
|
echo "1. Update image references in deployment files:"
|
||||||
|
echo " sed -i 's|your-registry|${REGISTRY}|g' deploy/k8s/*.yaml"
|
||||||
|
echo ""
|
||||||
|
echo "2. Deploy to Kubernetes:"
|
||||||
|
echo " ./deploy/scripts/deploy.sh"
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}🎉 Happy deploying!${NC}"
|
||||||
127
deploy/scripts/deploy.sh
Executable file
127
deploy/scripts/deploy.sh
Executable file
@@ -0,0 +1,127 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Adopt-a-Street Kubernetes Deployment Script
|
||||||
|
# This script deploys the Adopt-a-Street application to a Kubernetes cluster
|
||||||
|
|
||||||
|
set -e # Exit on error
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
NAMESPACE="adopt-a-street"
|
||||||
|
DEPLOY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
K8S_DIR="${DEPLOY_DIR}/k8s"
|
||||||
|
|
||||||
|
echo -e "${GREEN}🚀 Adopt-a-Street Kubernetes Deployment${NC}"
|
||||||
|
echo "================================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if kubectl is installed
|
||||||
|
if ! command -v kubectl &> /dev/null; then
|
||||||
|
echo -e "${RED}❌ kubectl not found. Please install kubectl first.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if secrets.yaml exists
|
||||||
|
if [ ! -f "${K8S_DIR}/secrets.yaml" ]; then
|
||||||
|
echo -e "${RED}❌ secrets.yaml not found!${NC}"
|
||||||
|
echo -e "${YELLOW}Please copy secrets.yaml.example to secrets.yaml and fill in your secrets:${NC}"
|
||||||
|
echo " cp ${K8S_DIR}/secrets.yaml.example ${K8S_DIR}/secrets.yaml"
|
||||||
|
echo " nano ${K8S_DIR}/secrets.yaml"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓${NC} Prerequisites check passed"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Function to wait for pods
|
||||||
|
wait_for_pods() {
|
||||||
|
local label=$1
|
||||||
|
local timeout=${2:-120}
|
||||||
|
echo -e "${YELLOW}⏳ Waiting for pods with label ${label} to be ready...${NC}"
|
||||||
|
kubectl wait --for=condition=ready pod -l "${label}" -n "${NAMESPACE}" --timeout="${timeout}s" || {
|
||||||
|
echo -e "${RED}❌ Timeout waiting for pods${NC}"
|
||||||
|
kubectl get pods -n "${NAMESPACE}" -l "${label}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
echo -e "${GREEN}✓${NC} Pods ready"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create namespace
|
||||||
|
echo "📦 Creating namespace..."
|
||||||
|
kubectl apply -f "${K8S_DIR}/namespace.yaml"
|
||||||
|
echo -e "${GREEN}✓${NC} Namespace created"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Apply secrets
|
||||||
|
echo "🔐 Applying secrets..."
|
||||||
|
kubectl apply -f "${K8S_DIR}/secrets.yaml"
|
||||||
|
echo -e "${GREEN}✓${NC} Secrets applied"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Apply configmap
|
||||||
|
echo "⚙️ Applying ConfigMap..."
|
||||||
|
kubectl apply -f "${K8S_DIR}/configmap.yaml"
|
||||||
|
echo -e "${GREEN}✓${NC} ConfigMap applied"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Deploy MongoDB
|
||||||
|
echo "🗄️ Deploying MongoDB..."
|
||||||
|
kubectl apply -f "${K8S_DIR}/mongodb-statefulset.yaml"
|
||||||
|
wait_for_pods "app=mongodb" 180
|
||||||
|
echo -e "${GREEN}✓${NC} MongoDB deployed"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Deploy backend
|
||||||
|
echo "🔧 Deploying backend..."
|
||||||
|
kubectl apply -f "${K8S_DIR}/backend-deployment.yaml"
|
||||||
|
wait_for_pods "app=backend" 120
|
||||||
|
echo -e "${GREEN}✓${NC} Backend deployed"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Deploy frontend
|
||||||
|
echo "🎨 Deploying frontend..."
|
||||||
|
kubectl apply -f "${K8S_DIR}/frontend-deployment.yaml"
|
||||||
|
wait_for_pods "app=frontend" 120
|
||||||
|
echo -e "${GREEN}✓${NC} Frontend deployed"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Deploy ingress
|
||||||
|
echo "🌐 Deploying ingress..."
|
||||||
|
kubectl apply -f "${K8S_DIR}/ingress.yaml"
|
||||||
|
echo -e "${GREEN}✓${NC} Ingress deployed"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Show deployment status
|
||||||
|
echo "================================================"
|
||||||
|
echo -e "${GREEN}✅ Deployment Complete!${NC}"
|
||||||
|
echo "================================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "📊 Current Status:"
|
||||||
|
kubectl get all -n "${NAMESPACE}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "🌐 Ingress:"
|
||||||
|
kubectl get ingress -n "${NAMESPACE}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo -e "${YELLOW}📝 Next Steps:${NC}"
|
||||||
|
echo "1. Check pod logs:"
|
||||||
|
echo " kubectl logs -f deployment/adopt-a-street-backend -n ${NAMESPACE}"
|
||||||
|
echo ""
|
||||||
|
echo "2. Access the application through your ingress URL"
|
||||||
|
echo ""
|
||||||
|
echo "3. Or port-forward for testing:"
|
||||||
|
echo " kubectl port-forward svc/adopt-a-street-frontend 3000:80 -n ${NAMESPACE}"
|
||||||
|
echo " Then open http://localhost:3000"
|
||||||
|
echo ""
|
||||||
|
echo "4. Monitor resources:"
|
||||||
|
echo " kubectl top pods -n ${NAMESPACE}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo -e "${GREEN}🎉 Happy deploying!${NC}"
|
||||||
13
frontend/.dockerignore
Normal file
13
frontend/.dockerignore
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
build
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
README.md
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
coverage
|
||||||
|
src/**/*.test.js
|
||||||
|
src/**/__tests__
|
||||||
37
frontend/Dockerfile
Normal file
37
frontend/Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Multi-stage build for ARM compatibility (Raspberry Pi)
|
||||||
|
FROM node:18-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build production bundle
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# --- Production stage with nginx ---
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Install wget for health checks
|
||||||
|
RUN apk add --no-cache wget
|
||||||
|
|
||||||
|
# Copy built assets
|
||||||
|
COPY --from=builder /app/build /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Copy nginx config
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s \
|
||||||
|
CMD wget --quiet --tries=1 --spider http://localhost:80/health || exit 1
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
63
frontend/nginx.conf
Normal file
63
frontend/nginx.conf
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Gzip compression
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript;
|
||||||
|
|
||||||
|
# Health check endpoint
|
||||||
|
location /health {
|
||||||
|
access_log off;
|
||||||
|
return 200 "healthy\n";
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
}
|
||||||
|
|
||||||
|
# React Router support - serve index.html for all routes
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
|
||||||
|
# Cache static assets
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||||
|
|
||||||
|
# If serving API through frontend nginx (optional - prefer Ingress routing)
|
||||||
|
# Uncomment these if NOT using Kubernetes Ingress for backend routing
|
||||||
|
|
||||||
|
# location /api {
|
||||||
|
# proxy_pass http://adopt-a-street-backend:5000;
|
||||||
|
# proxy_http_version 1.1;
|
||||||
|
# proxy_set_header Upgrade $http_upgrade;
|
||||||
|
# proxy_set_header Connection 'upgrade';
|
||||||
|
# proxy_set_header Host $host;
|
||||||
|
# proxy_cache_bypass $http_upgrade;
|
||||||
|
# proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Socket.IO proxy (optional - prefer Ingress routing)
|
||||||
|
# location /socket.io {
|
||||||
|
# proxy_pass http://adopt-a-street-backend:5000;
|
||||||
|
# proxy_http_version 1.1;
|
||||||
|
# proxy_set_header Upgrade $http_upgrade;
|
||||||
|
# proxy_set_header Connection "upgrade";
|
||||||
|
# proxy_set_header Host $host;
|
||||||
|
# proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
# }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user