42ff3e8f33
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
927 lines
21 KiB
Markdown
927 lines
21 KiB
Markdown
# 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
|