Add comprehensive Kubernetes migration guide from local to production
This commit adds complete documentation and tooling for migrating from local development (Kind/Colima on macOS) to production deployment (MicroK8s on Ubuntu VPS at Clouding.io). Documentation added: - K8S-MIGRATION-GUIDE.md: Comprehensive step-by-step migration guide covering all phases from VPS setup to post-deployment operations - MIGRATION-CHECKLIST.md: Quick reference checklist for migration tasks - MIGRATION-SUMMARY.md: High-level overview and key changes summary Configuration updates: - Added storage-patch.yaml for MicroK8s storage class compatibility (changes from 'standard' to 'microk8s-hostpath') - Updated prod/kustomization.yaml to include storage patch Helper scripts: - deploy-production.sh: Interactive deployment script with validation - tag-and-push-images.sh: Automated image tagging and registry push - backup-databases.sh: Database backup script for production Key differences addressed: - Ingress: MicroK8s addon vs custom NGINX - Storage: MicroK8s hostpath vs Kind standard storage - Registry: Container registry configuration for production - SSL: Let's Encrypt production certificates - Domains: Real domain configuration vs localhost - Resources: Production-grade resource limits and scaling The migration guide covers: - VPS setup and MicroK8s installation - Configuration adaptations required - Container registry setup options - SSL certificate configuration - Monitoring and backup setup - Troubleshooting common issues - Security hardening checklist - Rollback procedures All existing Kubernetes manifests remain unchanged and compatible.
This commit is contained in:
161
scripts/backup-databases.sh
Executable file
161
scripts/backup-databases.sh
Executable file
@@ -0,0 +1,161 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Database Backup Script for Bakery IA
|
||||
# This script backs up all PostgreSQL databases in the Kubernetes cluster
|
||||
# Designed to run on the VPS via cron
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
BACKUP_ROOT="/backups"
|
||||
NAMESPACE="bakery-ia"
|
||||
RETENTION_DAYS=7
|
||||
DATE=$(date +%Y-%m-%d_%H-%M-%S)
|
||||
BACKUP_DIR="${BACKUP_ROOT}/${DATE}"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Logging
|
||||
log() {
|
||||
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] SUCCESS: $1${NC}"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}"
|
||||
}
|
||||
|
||||
# Create backup directory
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
log "Starting database backup to $BACKUP_DIR"
|
||||
|
||||
# Get all database pods
|
||||
DB_PODS=$(kubectl get pods -n "$NAMESPACE" -l app.kubernetes.io/component=database -o jsonpath='{.items[*].metadata.name}')
|
||||
|
||||
if [ -z "$DB_PODS" ]; then
|
||||
log_error "No database pods found in namespace $NAMESPACE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Found database pods: $DB_PODS"
|
||||
|
||||
# Backup counter
|
||||
SUCCESS_COUNT=0
|
||||
FAILED_COUNT=0
|
||||
FAILED_DBS=()
|
||||
|
||||
# Backup each database
|
||||
for pod in $DB_PODS; do
|
||||
log "Backing up database: $pod"
|
||||
|
||||
# Get database name from pod labels
|
||||
DB_NAME=$(kubectl get pod "$pod" -n "$NAMESPACE" -o jsonpath='{.metadata.labels.app\.kubernetes\.io/name}')
|
||||
|
||||
if [ -z "$DB_NAME" ]; then
|
||||
DB_NAME=$pod
|
||||
fi
|
||||
|
||||
BACKUP_FILE="${BACKUP_DIR}/${DB_NAME}.sql"
|
||||
|
||||
# Perform backup
|
||||
if kubectl exec -n "$NAMESPACE" "$pod" -- pg_dumpall -U postgres > "$BACKUP_FILE" 2>/dev/null; then
|
||||
FILE_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
|
||||
log_success "Backed up $DB_NAME ($FILE_SIZE)"
|
||||
((SUCCESS_COUNT++))
|
||||
else
|
||||
log_error "Failed to backup $DB_NAME"
|
||||
FAILED_DBS+=("$DB_NAME")
|
||||
((FAILED_COUNT++))
|
||||
rm -f "$BACKUP_FILE" # Remove partial backup
|
||||
fi
|
||||
done
|
||||
|
||||
# Also backup Redis if present
|
||||
REDIS_POD=$(kubectl get pods -n "$NAMESPACE" -l app.kubernetes.io/name=redis -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$REDIS_POD" ]; then
|
||||
log "Backing up Redis: $REDIS_POD"
|
||||
REDIS_BACKUP="${BACKUP_DIR}/redis.rdb"
|
||||
|
||||
if kubectl exec -n "$NAMESPACE" "$REDIS_POD" -- redis-cli --rdb /tmp/dump.rdb SAVE > /dev/null 2>&1 && \
|
||||
kubectl cp "$NAMESPACE/$REDIS_POD:/tmp/dump.rdb" "$REDIS_BACKUP" > /dev/null 2>&1; then
|
||||
FILE_SIZE=$(du -h "$REDIS_BACKUP" | cut -f1)
|
||||
log_success "Backed up Redis ($FILE_SIZE)"
|
||||
((SUCCESS_COUNT++))
|
||||
else
|
||||
log_warning "Failed to backup Redis (non-critical)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create backup metadata
|
||||
cat > "${BACKUP_DIR}/backup-info.txt" <<EOF
|
||||
Backup Date: $(date)
|
||||
Namespace: $NAMESPACE
|
||||
Success Count: $SUCCESS_COUNT
|
||||
Failed Count: $FAILED_COUNT
|
||||
Failed Databases: ${FAILED_DBS[@]:-none}
|
||||
Kubernetes Cluster: $(kubectl config current-context)
|
||||
EOF
|
||||
|
||||
# Compress backup
|
||||
log "Compressing backup..."
|
||||
COMPRESSED_FILE="${BACKUP_ROOT}/backup-${DATE}.tar.gz"
|
||||
|
||||
if tar -czf "$COMPRESSED_FILE" -C "$BACKUP_ROOT" "$(basename "$BACKUP_DIR")"; then
|
||||
COMPRESSED_SIZE=$(du -h "$COMPRESSED_FILE" | cut -f1)
|
||||
log_success "Backup compressed: $COMPRESSED_FILE ($COMPRESSED_SIZE)"
|
||||
|
||||
# Remove uncompressed backup
|
||||
rm -rf "$BACKUP_DIR"
|
||||
else
|
||||
log_error "Failed to compress backup"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Cleanup old backups
|
||||
log "Cleaning up backups older than $RETENTION_DAYS days..."
|
||||
DELETED_COUNT=$(find "$BACKUP_ROOT" -name "backup-*.tar.gz" -mtime "+$RETENTION_DAYS" -delete -print | wc -l)
|
||||
|
||||
if [ "$DELETED_COUNT" -gt 0 ]; then
|
||||
log "Deleted $DELETED_COUNT old backup(s)"
|
||||
fi
|
||||
|
||||
# Calculate total backup size
|
||||
TOTAL_SIZE=$(du -sh "$BACKUP_ROOT" | cut -f1)
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
log "========================================="
|
||||
log "Backup Summary"
|
||||
log "========================================="
|
||||
log "Successful backups: $SUCCESS_COUNT"
|
||||
log "Failed backups: $FAILED_COUNT"
|
||||
log "Backup location: $COMPRESSED_FILE"
|
||||
log "Backup size: $COMPRESSED_SIZE"
|
||||
log "Total backup storage: $TOTAL_SIZE"
|
||||
log "========================================="
|
||||
|
||||
if [ $FAILED_COUNT -gt 0 ]; then
|
||||
log_error "Some backups failed: ${FAILED_DBS[*]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "Backup completed successfully!"
|
||||
|
||||
# Optional: Send notification
|
||||
# Uncomment and configure if you want email/slack notifications
|
||||
# send_notification "Bakery IA Backup Completed" "Successfully backed up $SUCCESS_COUNT databases. Size: $COMPRESSED_SIZE"
|
||||
|
||||
exit 0
|
||||
190
scripts/deploy-production.sh
Executable file
190
scripts/deploy-production.sh
Executable file
@@ -0,0 +1,190 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Production Deployment Script for MicroK8s
|
||||
# This script helps deploy Bakery IA to a MicroK8s cluster
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}Bakery IA - Production Deployment${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
# Configuration
|
||||
NAMESPACE="bakery-ia"
|
||||
KUSTOMIZE_PATH="infrastructure/kubernetes/overlays/prod"
|
||||
|
||||
# Check if kubectl is available
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
echo -e "${RED}Error: kubectl not found. Please install kubectl or setup microk8s alias.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Function to check if cluster is accessible
|
||||
check_cluster() {
|
||||
echo -e "${YELLOW}Checking cluster connectivity...${NC}"
|
||||
if ! kubectl cluster-info &> /dev/null; then
|
||||
echo -e "${RED}Error: Cannot connect to Kubernetes cluster.${NC}"
|
||||
echo "Please ensure your kubeconfig is set correctly."
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}✓ Cluster connection successful${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Function to check required addons
|
||||
check_addons() {
|
||||
echo -e "${YELLOW}Checking required MicroK8s addons...${NC}"
|
||||
|
||||
# Check if this is MicroK8s
|
||||
if command -v microk8s &> /dev/null; then
|
||||
REQUIRED_ADDONS=("dns" "hostpath-storage" "ingress" "cert-manager" "metrics-server")
|
||||
|
||||
for addon in "${REQUIRED_ADDONS[@]}"; do
|
||||
if microk8s status | grep -q "$addon: enabled"; then
|
||||
echo -e "${GREEN}✓ $addon enabled${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ $addon not enabled${NC}"
|
||||
echo -e "${YELLOW}Enable with: microk8s enable $addon${NC}"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo -e "${YELLOW}Not running on MicroK8s. Skipping addon check.${NC}"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Function to create namespace
|
||||
create_namespace() {
|
||||
echo -e "${YELLOW}Creating namespace...${NC}"
|
||||
if kubectl get namespace $NAMESPACE &> /dev/null; then
|
||||
echo -e "${GREEN}✓ Namespace $NAMESPACE already exists${NC}"
|
||||
else
|
||||
kubectl create namespace $NAMESPACE
|
||||
echo -e "${GREEN}✓ Namespace $NAMESPACE created${NC}"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Function to apply secrets
|
||||
apply_secrets() {
|
||||
echo -e "${YELLOW}Applying secrets...${NC}"
|
||||
echo -e "${RED}WARNING: Ensure production secrets are updated before deployment!${NC}"
|
||||
read -p "Have you updated production secrets? (yes/no): " confirm
|
||||
|
||||
if [ "$confirm" != "yes" ]; then
|
||||
echo -e "${RED}Deployment cancelled. Please update secrets first.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
kubectl apply -f infrastructure/kubernetes/base/secrets.yaml
|
||||
kubectl apply -f infrastructure/kubernetes/base/secrets/postgres-tls-secret.yaml
|
||||
kubectl apply -f infrastructure/kubernetes/base/secrets/redis-tls-secret.yaml
|
||||
kubectl apply -f infrastructure/kubernetes/base/secrets/demo-internal-api-key-secret.yaml
|
||||
echo -e "${GREEN}✓ Secrets applied${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Function to apply kustomization
|
||||
deploy_application() {
|
||||
echo -e "${YELLOW}Deploying application...${NC}"
|
||||
kubectl apply -k $KUSTOMIZE_PATH
|
||||
echo -e "${GREEN}✓ Application deployed${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Function to wait for deployments
|
||||
wait_for_deployments() {
|
||||
echo -e "${YELLOW}Waiting for deployments to be ready...${NC}"
|
||||
echo "This may take several minutes..."
|
||||
|
||||
# Wait for all deployments
|
||||
kubectl wait --for=condition=available --timeout=600s \
|
||||
deployment --all -n $NAMESPACE
|
||||
|
||||
echo -e "${GREEN}✓ All deployments are ready${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Function to check deployment status
|
||||
check_status() {
|
||||
echo -e "${YELLOW}Deployment Status:${NC}"
|
||||
echo ""
|
||||
|
||||
echo "Pods:"
|
||||
kubectl get pods -n $NAMESPACE
|
||||
echo ""
|
||||
|
||||
echo "Services:"
|
||||
kubectl get svc -n $NAMESPACE
|
||||
echo ""
|
||||
|
||||
echo "Ingress:"
|
||||
kubectl get ingress -n $NAMESPACE
|
||||
echo ""
|
||||
|
||||
echo "Persistent Volume Claims:"
|
||||
kubectl get pvc -n $NAMESPACE
|
||||
echo ""
|
||||
|
||||
echo "Certificates:"
|
||||
kubectl get certificate -n $NAMESPACE
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Function to show access information
|
||||
show_access_info() {
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}Deployment Complete!${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
echo "Access your application at:"
|
||||
|
||||
# Get ingress hosts
|
||||
HOSTS=$(kubectl get ingress bakery-ingress-prod -n $NAMESPACE -o jsonpath='{.spec.rules[*].host}' 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$HOSTS" ]; then
|
||||
for host in $HOSTS; do
|
||||
echo " https://$host"
|
||||
done
|
||||
else
|
||||
echo " Configure your domain in prod-ingress.yaml"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Useful commands:"
|
||||
echo " View logs: kubectl logs -f deployment/gateway -n $NAMESPACE"
|
||||
echo " Check pods: kubectl get pods -n $NAMESPACE"
|
||||
echo " Check events: kubectl get events -n $NAMESPACE --sort-by='.lastTimestamp'"
|
||||
echo " Scale: kubectl scale deployment/gateway --replicas=5 -n $NAMESPACE"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Main deployment flow
|
||||
main() {
|
||||
check_cluster
|
||||
check_addons
|
||||
create_namespace
|
||||
apply_secrets
|
||||
deploy_application
|
||||
|
||||
echo -e "${YELLOW}Do you want to wait for deployments to be ready? (yes/no):${NC}"
|
||||
read -p "> " wait_confirm
|
||||
|
||||
if [ "$wait_confirm" = "yes" ]; then
|
||||
wait_for_deployments
|
||||
fi
|
||||
|
||||
check_status
|
||||
show_access_info
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main
|
||||
154
scripts/tag-and-push-images.sh
Executable file
154
scripts/tag-and-push-images.sh
Executable file
@@ -0,0 +1,154 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to tag and push all Bakery IA images to a container registry
|
||||
# Usage: ./tag-and-push-images.sh [REGISTRY_PREFIX] [TAG]
|
||||
# Example: ./tag-and-push-images.sh myuser/bakery v1.0.0
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Configuration
|
||||
REGISTRY_PREFIX="${1:-}"
|
||||
TAG="${2:-latest}"
|
||||
|
||||
if [ -z "$REGISTRY_PREFIX" ]; then
|
||||
echo -e "${RED}Error: Registry prefix required${NC}"
|
||||
echo "Usage: $0 REGISTRY_PREFIX [TAG]"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " Docker Hub: $0 myusername/bakery v1.0.0"
|
||||
echo " GitHub: $0 ghcr.io/myorg/bakery v1.0.0"
|
||||
echo " MicroK8s: $0 YOUR_VPS_IP:32000/bakery v1.0.0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# List of all services
|
||||
SERVICES=(
|
||||
"gateway"
|
||||
"dashboard"
|
||||
"auth-service"
|
||||
"tenant-service"
|
||||
"training-service"
|
||||
"forecasting-service"
|
||||
"sales-service"
|
||||
"external-service"
|
||||
"notification-service"
|
||||
"inventory-service"
|
||||
"recipes-service"
|
||||
"suppliers-service"
|
||||
"pos-service"
|
||||
"orders-service"
|
||||
"production-service"
|
||||
"procurement-service"
|
||||
"orchestrator-service"
|
||||
"alert-processor"
|
||||
"ai-insights-service"
|
||||
"demo-session-service"
|
||||
"distribution-service"
|
||||
)
|
||||
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}Bakery IA - Image Tagging and Push${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
echo "Registry: $REGISTRY_PREFIX"
|
||||
echo "Tag: $TAG"
|
||||
echo ""
|
||||
|
||||
# Function to tag image
|
||||
tag_image() {
|
||||
local service=$1
|
||||
local local_name="bakery/${service}"
|
||||
local remote_name="${REGISTRY_PREFIX}-${service}:${TAG}"
|
||||
|
||||
echo -e "${YELLOW}Tagging ${local_name} -> ${remote_name}${NC}"
|
||||
|
||||
if docker tag "$local_name" "$remote_name"; then
|
||||
echo -e "${GREEN}✓ Tagged $service${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}✗ Failed to tag $service${NC}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to push image
|
||||
push_image() {
|
||||
local service=$1
|
||||
local remote_name="${REGISTRY_PREFIX}-${service}:${TAG}"
|
||||
|
||||
echo -e "${YELLOW}Pushing ${remote_name}${NC}"
|
||||
|
||||
if docker push "$remote_name"; then
|
||||
echo -e "${GREEN}✓ Pushed $service${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}✗ Failed to push $service${NC}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if user is logged in to registry
|
||||
echo -e "${YELLOW}Checking registry authentication...${NC}"
|
||||
if ! docker info > /dev/null 2>&1; then
|
||||
echo -e "${RED}Error: Docker daemon not running${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Docker is running${NC}"
|
||||
echo ""
|
||||
|
||||
# Ask for confirmation
|
||||
echo -e "${YELLOW}This will tag and push ${#SERVICES[@]} images.${NC}"
|
||||
read -p "Continue? (yes/no): " confirm
|
||||
|
||||
if [ "$confirm" != "yes" ]; then
|
||||
echo "Cancelled."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Starting image tagging and push...${NC}"
|
||||
echo ""
|
||||
|
||||
# Track success/failure
|
||||
SUCCESS_COUNT=0
|
||||
FAILED_SERVICES=()
|
||||
|
||||
# Tag and push all images
|
||||
for service in "${SERVICES[@]}"; do
|
||||
if tag_image "$service" && push_image "$service"; then
|
||||
((SUCCESS_COUNT++))
|
||||
else
|
||||
FAILED_SERVICES+=("$service")
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
|
||||
# Summary
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}Summary${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
echo "Successfully pushed: $SUCCESS_COUNT/${#SERVICES[@]}"
|
||||
|
||||
if [ ${#FAILED_SERVICES[@]} -gt 0 ]; then
|
||||
echo -e "${RED}Failed services:${NC}"
|
||||
for service in "${FAILED_SERVICES[@]}"; do
|
||||
echo -e "${RED} - $service${NC}"
|
||||
done
|
||||
exit 1
|
||||
else
|
||||
echo -e "${GREEN}All images pushed successfully!${NC}"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Update image names in infrastructure/kubernetes/overlays/prod/kustomization.yaml"
|
||||
echo "2. Deploy to production: kubectl apply -k infrastructure/kubernetes/overlays/prod"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
Reference in New Issue
Block a user