Add Mode6Test: copy of Mode5Test running on port 3000
Build & Deploy / Test & Lint (push) Has been cancelled
Build & Deploy / Security Scan (push) Has been cancelled
Code Quality / Code Quality Checks (push) Has been cancelled
Code Quality / Dependency Vulnerabilities (push) Has been cancelled
Code Quality / Performance Testing (push) Has been cancelled
Build & Deploy / Build Docker Images (push) Has been cancelled
Build & Deploy / Deploy to Kubernetes (push) Has been cancelled
Build & Deploy / Test & Lint (push) Has been cancelled
Build & Deploy / Security Scan (push) Has been cancelled
Code Quality / Code Quality Checks (push) Has been cancelled
Code Quality / Dependency Vulnerabilities (push) Has been cancelled
Code Quality / Performance Testing (push) Has been cancelled
Build & Deploy / Build Docker Images (push) Has been cancelled
Build & Deploy / Deploy to Kubernetes (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,926 @@
|
||||
# Deployment Guide - Job Info Application
|
||||
|
||||
## Phase 9: Production Deployment
|
||||
|
||||
This guide covers preparing the Job Info application for production deployment including Docker configuration, environment setup, and CI/CD pipeline.
|
||||
|
||||
---
|
||||
|
||||
## 1. Deployment Architecture
|
||||
|
||||
### Production Stack
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Client Browser (Edge) │
|
||||
│ - Svelte SPA (hash-based routing) │
|
||||
│ - Service Worker (offline + refresh) │
|
||||
│ - IndexedDB (client-side cache) │
|
||||
└─────────────────┬───────────────────────┘
|
||||
│ HTTPS
|
||||
↓
|
||||
┌─────────────────────────────────────────┐
|
||||
│ CDN (CloudFlare/CloudFront) │
|
||||
│ - Cache static assets (CSS, JS, HTML) │
|
||||
│ - Serve from edge location │
|
||||
│ - 30-day cache for assets │
|
||||
└─────────────────┬───────────────────────┘
|
||||
│ HTTPS
|
||||
↓
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Load Balancer (ALB/NLB or Ingress) │
|
||||
│ - Route /api to backend │
|
||||
│ - Route static to CDN │
|
||||
│ - Health checks every 30s │
|
||||
└─────────────────┬───────────────────────┘
|
||||
│ HTTPS
|
||||
┌───────┴──────────┐
|
||||
↓ ↓
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ Backend #1 │ │ Backend #2 │
|
||||
│ Hono Server │ │ Hono Server │
|
||||
│ Port 3005 │ │ Port 3005 │
|
||||
└──────┬───────┘ └──────┬───────┘
|
||||
│ │
|
||||
└────────┬────────┘
|
||||
↓
|
||||
┌──────────────────────┐
|
||||
│ PostgreSQL/Redis │
|
||||
│ (Persistent Storage)│
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Docker Setup
|
||||
|
||||
### 2.1 Dockerfile - Backend
|
||||
|
||||
Create `Mode3Test/Dockerfile`:
|
||||
|
||||
```dockerfile
|
||||
# Build stage
|
||||
FROM oven/bun:latest as builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package.json bun.lockb ./
|
||||
|
||||
# Install dependencies
|
||||
RUN bun install --production
|
||||
|
||||
# Copy source
|
||||
COPY backend/ ./backend/
|
||||
COPY .env ./
|
||||
|
||||
# Runtime stage
|
||||
FROM oven/bun:latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy from builder
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder /app/backend ./backend
|
||||
COPY --from=builder /app/.env ./
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3005
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD bun --eval "fetch('http://localhost:3005/health').then(r => r.status === 200 ? process.exit(0) : process.exit(1))"
|
||||
|
||||
# Start server
|
||||
CMD ["bun", "run", "backend/server.ts"]
|
||||
```
|
||||
|
||||
### 2.2 Dockerfile - Frontend
|
||||
|
||||
Create `frontend/Dockerfile`:
|
||||
|
||||
```dockerfile
|
||||
# Build stage
|
||||
FROM oven/bun:latest as builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package.json bun.lockb ./
|
||||
|
||||
# Install dependencies
|
||||
RUN bun install
|
||||
|
||||
# Copy source
|
||||
COPY . .
|
||||
|
||||
# Build
|
||||
RUN bun run build
|
||||
|
||||
# Runtime stage - lightweight
|
||||
FROM nginx:alpine
|
||||
|
||||
# Copy built assets
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
|
||||
# Copy nginx config
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD wget --quiet --tries=1 --spider http://localhost:80/index.html || exit 1
|
||||
|
||||
# Start nginx
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
```
|
||||
|
||||
### 2.3 Nginx Configuration
|
||||
|
||||
Create `frontend/nginx.conf`:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
# Security headers
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https://graph.microsoft.com https://*.microsoft.com" always;
|
||||
|
||||
# Compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1000;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss;
|
||||
|
||||
# Root location
|
||||
root /usr/share/nginx/html;
|
||||
|
||||
# SPA routing - serve index.html for all non-asset requests
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
expires -1;
|
||||
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
|
||||
}
|
||||
|
||||
# Static assets with long cache
|
||||
location ~* \.(js|css|woff|woff2|eot|ttf|otf|svg|png|jpg|jpeg|gif|ico)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# API proxy
|
||||
location /api/ {
|
||||
proxy_pass http://backend:3005;
|
||||
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;
|
||||
|
||||
# CORS headers
|
||||
add_header Access-Control-Allow-Origin "*" always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
|
||||
|
||||
if ($request_method = 'OPTIONS') {
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
# Health check endpoint
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "healthy\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 Docker Compose - Development
|
||||
|
||||
Create `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# PostgreSQL Database
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
POSTGRES_USER: job_info_user
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD:-dev_password}
|
||||
POSTGRES_DB: job_info_db
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U job_info_user"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# Redis Cache
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# Backend API
|
||||
backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Mode3Test/Dockerfile
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 3005
|
||||
DATABASE_URL: postgresql://job_info_user:${DB_PASSWORD:-dev_password}@postgres:5432/job_info_db
|
||||
REDIS_URL: redis://redis:6379
|
||||
MICROSOFT_TENANT_ID: ${MICROSOFT_TENANT_ID}
|
||||
MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID}
|
||||
MICROSOFT_CLIENT_SECRET: ${MICROSOFT_CLIENT_SECRET}
|
||||
MICROSOFT_REDIRECT_URI: ${MICROSOFT_REDIRECT_URI:-http://localhost:3005/api/auth/callback}
|
||||
ports:
|
||||
- "3005:3005"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3005/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# Frontend Web Server
|
||||
frontend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: frontend/Dockerfile
|
||||
ports:
|
||||
- "80:80"
|
||||
depends_on:
|
||||
- backend
|
||||
environment:
|
||||
BACKEND_URL: http://backend:3005
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/index.html"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: job-info-network
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Environment Configuration
|
||||
|
||||
### 3.1 Production .env
|
||||
|
||||
Create `.env.production`:
|
||||
|
||||
```bash
|
||||
# Application
|
||||
NODE_ENV=production
|
||||
PORT=3005
|
||||
|
||||
# Database (update with production credentials)
|
||||
DATABASE_URL=postgresql://user:password@prod-db:5432/job_info_prod
|
||||
REDIS_URL=redis://prod-redis:6379
|
||||
|
||||
# Microsoft OAuth (register app in Azure)
|
||||
MICROSOFT_TENANT_ID=your-tenant-id
|
||||
MICROSOFT_CLIENT_ID=your-client-id
|
||||
MICROSOFT_CLIENT_SECRET=your-client-secret
|
||||
MICROSOFT_REDIRECT_URI=https://app.example.com/api/auth/callback
|
||||
|
||||
# Security
|
||||
JWT_SECRET=your-super-secret-key-min-32-chars
|
||||
SESSION_SECRET=your-session-secret-key
|
||||
|
||||
# Frontend
|
||||
VITE_API_URL=https://app.example.com/api
|
||||
VITE_GRAPH_API_BASE=https://graph.microsoft.com/v1.0
|
||||
VITE_OAUTH_CLIENT_ID=your-client-id
|
||||
VITE_OAUTH_TENANT_ID=your-tenant-id
|
||||
VITE_OAUTH_REDIRECT_URI=https://app.example.com/#/callback
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=info
|
||||
SENTRY_DSN=https://your-sentry-key@sentry.io/project-id
|
||||
|
||||
# Monitoring
|
||||
DATADOG_API_KEY=your-datadog-key (optional)
|
||||
```
|
||||
|
||||
### 3.2 Secrets Management
|
||||
|
||||
**Option A: AWS Secrets Manager**
|
||||
```bash
|
||||
# Store in AWS Secrets Manager
|
||||
aws secretsmanager create-secret \
|
||||
--name job-info/prod \
|
||||
--secret-string '{
|
||||
"db_password": "...",
|
||||
"jwt_secret": "...",
|
||||
"microsoft_client_secret": "..."
|
||||
}'
|
||||
|
||||
# Reference in deployment
|
||||
aws secretsmanager get-secret-value --secret-id job-info/prod
|
||||
```
|
||||
|
||||
**Option B: Kubernetes Secrets**
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: job-info-secrets
|
||||
type: Opaque
|
||||
stringData:
|
||||
database_password: "..."
|
||||
jwt_secret: "..."
|
||||
microsoft_client_secret: "..."
|
||||
```
|
||||
|
||||
**Option C: Environment Variables**
|
||||
```bash
|
||||
# Set directly in container runtime
|
||||
docker run \
|
||||
-e DATABASE_URL="postgresql://..." \
|
||||
-e JWT_SECRET="..." \
|
||||
job-info-backend:latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Kubernetes Deployment
|
||||
|
||||
### 4.1 Deployment Manifests
|
||||
|
||||
Create `k8s/backend-deployment.yaml`:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: job-info-backend
|
||||
labels:
|
||||
app: job-info-backend
|
||||
spec:
|
||||
replicas: 3 # Run 3 backend instances
|
||||
selector:
|
||||
matchLabels:
|
||||
app: job-info-backend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: job-info-backend
|
||||
spec:
|
||||
containers:
|
||||
- name: backend
|
||||
image: your-registry/job-info-backend:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 3005
|
||||
name: http
|
||||
env:
|
||||
- name: NODE_ENV
|
||||
value: "production"
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: job-info-secrets
|
||||
key: database_url
|
||||
- name: MICROSOFT_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: job-info-secrets
|
||||
key: microsoft_client_secret
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 3005
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 3005
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: job-info-backend-service
|
||||
spec:
|
||||
selector:
|
||||
app: job-info-backend
|
||||
ports:
|
||||
- port: 3005
|
||||
targetPort: 3005
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: job-info-backend-hpa
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: job-info-backend
|
||||
minReplicas: 3
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
```
|
||||
|
||||
Create `k8s/frontend-deployment.yaml`:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: job-info-frontend
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: job-info-frontend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: job-info-frontend
|
||||
spec:
|
||||
containers:
|
||||
- name: frontend
|
||||
image: your-registry/job-info-frontend:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 80
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "250m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /index.html
|
||||
port: 80
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: job-info-frontend-service
|
||||
spec:
|
||||
selector:
|
||||
app: job-info-frontend
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
type: LoadBalancer
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: job-info-ingress
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: app.example.com
|
||||
http:
|
||||
paths:
|
||||
- path: /api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: job-info-backend-service
|
||||
port:
|
||||
number: 3005
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: job-info-frontend-service
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. CI/CD Pipeline
|
||||
|
||||
### 5.1 GitHub Actions Workflow
|
||||
|
||||
Create `.github/workflows/deploy.yml`:
|
||||
|
||||
```yaml
|
||||
name: Deploy to Production
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd frontend && bun install
|
||||
cd ../Mode3Test && bun install
|
||||
|
||||
- name: Run tests
|
||||
run: bun test
|
||||
|
||||
- name: Lint
|
||||
run: |
|
||||
cd frontend && bun run lint || true
|
||||
cd ../Mode3Test && bun run lint || true
|
||||
|
||||
build:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
|
||||
- name: Build frontend
|
||||
run: |
|
||||
cd frontend
|
||||
bun install
|
||||
bun run build
|
||||
|
||||
- name: Build Docker images
|
||||
run: |
|
||||
docker build -t job-info-backend:${{ github.sha }} -f Mode3Test/Dockerfile .
|
||||
docker build -t job-info-frontend:${{ github.sha }} -f frontend/Dockerfile .
|
||||
|
||||
- name: Push to registry
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
docker tag job-info-backend:${{ github.sha }} your-registry/job-info-backend:latest
|
||||
docker tag job-info-frontend:${{ github.sha }} your-registry/job-info-frontend:latest
|
||||
|
||||
docker push your-registry/job-info-backend:latest
|
||||
docker push your-registry/job-info-frontend:latest
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Deploy to Kubernetes
|
||||
run: |
|
||||
kubectl apply -f k8s/
|
||||
kubectl set image deployment/job-info-backend \
|
||||
job-info-backend=your-registry/job-info-backend:latest
|
||||
kubectl set image deployment/job-info-frontend \
|
||||
job-info-frontend=your-registry/job-info-frontend:latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Monitoring & Observability
|
||||
|
||||
### 6.1 Logging
|
||||
|
||||
Setup structured logging:
|
||||
|
||||
```typescript
|
||||
// backend/logging.ts
|
||||
import pino from 'pino';
|
||||
|
||||
export const logger = pino({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
transport: {
|
||||
targets: [
|
||||
{
|
||||
target: 'pino/file',
|
||||
options: { destination: '/var/log/job-info.log' }
|
||||
},
|
||||
{
|
||||
target: 'pino-stackdriver',
|
||||
options: { projectId: 'your-gcp-project' }
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 6.2 Metrics
|
||||
|
||||
Setup Prometheus metrics:
|
||||
|
||||
```typescript
|
||||
// backend/metrics.ts
|
||||
import promClient from 'prom-client';
|
||||
|
||||
export const httpRequestDuration = new promClient.Histogram({
|
||||
name: 'http_request_duration_seconds',
|
||||
help: 'Duration of HTTP requests in seconds',
|
||||
labelNames: ['method', 'route', 'status_code'],
|
||||
});
|
||||
|
||||
export const activeAuthSessions = new promClient.Gauge({
|
||||
name: 'active_auth_sessions',
|
||||
help: 'Number of active authentication sessions',
|
||||
});
|
||||
```
|
||||
|
||||
### 6.3 Error Tracking
|
||||
|
||||
Setup Sentry:
|
||||
|
||||
```typescript
|
||||
// backend/sentry.ts
|
||||
import * as Sentry from "@sentry/node";
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.SENTRY_DSN,
|
||||
environment: process.env.NODE_ENV,
|
||||
tracesSampleRate: 1.0,
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Backup & Disaster Recovery
|
||||
|
||||
### 7.1 Database Backup
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# backup-postgres.sh
|
||||
|
||||
BACKUP_DIR="/backups/postgres"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
|
||||
# Create backup
|
||||
pg_dump -U job_info_user job_info_db | gzip > $BACKUP_DIR/backup_$DATE.sql.gz
|
||||
|
||||
# Upload to S3
|
||||
aws s3 cp $BACKUP_DIR/backup_$DATE.sql.gz s3://job-info-backups/
|
||||
|
||||
# Keep only last 30 days
|
||||
find $BACKUP_DIR -mtime +30 -delete
|
||||
```
|
||||
|
||||
### 7.2 Recovery Procedure
|
||||
|
||||
```bash
|
||||
# Restore from backup
|
||||
gunzip < /backups/postgres/backup_20240101_000000.sql.gz | \
|
||||
psql -U job_info_user job_info_db
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Security Hardening
|
||||
|
||||
### 8.1 TLS/SSL Certificate
|
||||
|
||||
```bash
|
||||
# Using Let's Encrypt with Certbot
|
||||
certbot certonly --standalone \
|
||||
-d app.example.com \
|
||||
-d www.app.example.com
|
||||
|
||||
# Auto-renew
|
||||
certbot renew --dry-run
|
||||
```
|
||||
|
||||
### 8.2 DDoS Protection
|
||||
|
||||
```nginx
|
||||
# In nginx.conf
|
||||
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
|
||||
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
|
||||
|
||||
location / {
|
||||
limit_req zone=general burst=20 nodelay;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
limit_req zone=api burst=200 nodelay;
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 Security Headers
|
||||
|
||||
```yaml
|
||||
# Kubernetes SecurityPolicy
|
||||
apiVersion: policy/v1beta1
|
||||
kind: PodSecurityPolicy
|
||||
metadata:
|
||||
name: job-info-psp
|
||||
spec:
|
||||
privileged: false
|
||||
allowPrivilegeEscalation: false
|
||||
requiredDropCapabilities:
|
||||
- ALL
|
||||
volumes:
|
||||
- 'configMap'
|
||||
- 'emptyDir'
|
||||
- 'projected'
|
||||
- 'secret'
|
||||
- 'downwardAPI'
|
||||
- 'persistentVolumeClaim'
|
||||
hostNetwork: false
|
||||
hostIPC: false
|
||||
hostPID: false
|
||||
runAsUser:
|
||||
rule: 'MustRunAsNonRoot'
|
||||
seLinux:
|
||||
rule: 'MustRunAs'
|
||||
seLinuxOptions:
|
||||
level: "s0:c123,c456"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Scaling & Performance Tuning
|
||||
|
||||
### 9.1 Database Optimization
|
||||
|
||||
```sql
|
||||
-- Create indexes for common queries
|
||||
CREATE INDEX idx_jobs_location ON jobs(location);
|
||||
CREATE INDEX idx_jobs_company ON jobs(company_id);
|
||||
CREATE INDEX idx_users_email ON users(email);
|
||||
|
||||
-- Monitor slow queries
|
||||
SET log_min_duration_statement = 1000; -- Log queries > 1s
|
||||
|
||||
-- Connection pooling with PgBouncer
|
||||
[databases]
|
||||
job_info_db = host=prod-db port=5432 user=job_info_user password=...
|
||||
```
|
||||
|
||||
### 9.2 Redis Caching
|
||||
|
||||
```typescript
|
||||
// Implement caching layer
|
||||
async function getJobsWithCache(userId: string): Promise<Job[]> {
|
||||
const cacheKey = `jobs:${userId}`;
|
||||
|
||||
// Check cache first
|
||||
const cached = await redis.get(cacheKey);
|
||||
if (cached) return JSON.parse(cached);
|
||||
|
||||
// Query database
|
||||
const jobs = await database.query(`SELECT * FROM jobs WHERE user_id = $1`, [userId]);
|
||||
|
||||
// Cache for 5 minutes
|
||||
await redis.setex(cacheKey, 300, JSON.stringify(jobs));
|
||||
|
||||
return jobs;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Deployment Checklist
|
||||
|
||||
**Pre-Deployment**:
|
||||
- [ ] All tests passing (unit, integration, e2e)
|
||||
- [ ] Code review approved
|
||||
- [ ] Security scan completed (SAST, dependency check)
|
||||
- [ ] Database migration tested
|
||||
- [ ] Environment variables configured
|
||||
- [ ] Backup taken
|
||||
- [ ] Rollback plan prepared
|
||||
|
||||
**Deployment**:
|
||||
- [ ] Deploy to staging first
|
||||
- [ ] Run smoke tests on staging
|
||||
- [ ] Verify metrics and logs
|
||||
- [ ] Deploy to production (canary)
|
||||
- [ ] Monitor error rate (< 0.1%)
|
||||
- [ ] Monitor response time (< 200ms p95)
|
||||
- [ ] Monitor CPU/Memory usage
|
||||
|
||||
**Post-Deployment**:
|
||||
- [ ] Verify all endpoints responding
|
||||
- [ ] Test OAuth flow
|
||||
- [ ] Verify token refresh
|
||||
- [ ] Check job loading
|
||||
- [ ] Validate user experience
|
||||
- [ ] Monitor for 24 hours
|
||||
- [ ] Document any issues
|
||||
|
||||
---
|
||||
|
||||
## 11. Rollback Procedure
|
||||
|
||||
If deployment fails or causes issues:
|
||||
|
||||
```bash
|
||||
# Kubernetes rollback
|
||||
kubectl rollout undo deployment/job-info-backend
|
||||
kubectl rollout undo deployment/job-info-frontend
|
||||
|
||||
# Docker rollback
|
||||
docker service update --image job-info-backend:previous job-info-backend
|
||||
|
||||
# Database rollback (if needed)
|
||||
psql -U job_info_user job_info_db < /backups/postgres/backup_20240101_000000.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. Production Monitoring Dashboard
|
||||
|
||||
Setup Grafana dashboard with:
|
||||
- Request rate (req/sec)
|
||||
- Error rate (%)
|
||||
- Response time (p50, p95, p99)
|
||||
- CPU/Memory usage
|
||||
- Database connections
|
||||
- Redis hit rate
|
||||
- Active sessions
|
||||
|
||||
**Alerts to configure**:
|
||||
- Error rate > 1%
|
||||
- Response time p95 > 1s
|
||||
- CPU > 80%
|
||||
- Memory > 85%
|
||||
- Database connections > 80%
|
||||
- Disk space < 10%
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Deployment checklist:
|
||||
✅ Docker images built and tested
|
||||
✅ Environment variables configured
|
||||
✅ Database initialized with backups
|
||||
✅ Kubernetes manifests created
|
||||
✅ CI/CD pipeline configured
|
||||
✅ Monitoring and logging setup
|
||||
✅ Security hardening applied
|
||||
✅ Scaling policies defined
|
||||
✅ Rollback procedures documented
|
||||
✅ On-call procedures established
|
||||
|
||||
**Next Steps**:
|
||||
1. Set up production infrastructure
|
||||
2. Run through deployment checklist
|
||||
3. Deploy to staging first
|
||||
4. Execute smoke tests
|
||||
5. Deploy to production
|
||||
6. Monitor for first 24 hours
|
||||
Reference in New Issue
Block a user