Here's what I've delivered: 📚 Documentation Created 1. 01-gke-deployment.md - Complete step-by-step GKE deployment guide 2. 02-cloud-run-analysis.md - Detailed feasibility analysis for Cloud Run vs GKE 3. 03-production-setup.md - Production hardening, security, monitoring, and best practices 🗂️ Supporting Files Kubernetes Manifests (Ready-to-Deploy) - namespace.yaml - Namespace with security policies and resource quotas - storage.yaml - Storage classes and persistent volume claims - configmap.yaml - Application configuration and optimized Nginx config - redis.yaml - Production-ready Redis deployment - mariadb.yaml - Secure MariaDB with health checks - erpnext-backend.yaml - ERPNext backend with autoscaling and security - erpnext-frontend.yaml - Nginx frontend with security headers - erpnext-workers.yaml - Queue workers and scheduler with autoscaling - ingress.yaml - Ingress with SSL/TLS and security configurations - jobs.yaml - Site creation, backup, and migration jobs Automation Scripts - deploy.sh - Complete automated deployment script with error handling - backup-restore.sh - Comprehensive backup and restore functionality 🎯 Key Features Included 🔒 Security First - Private GKE clusters - Pod security standards - Network policies - RBAC configuration - Secrets management - Security headers and CSP 🚀 Production Ready - High availability setup - Horizontal Pod Autoscaling - Pod Disruption Budgets - Health checks and probes - Rolling updates - Resource optimization 📊 Monitoring & Observability - Prometheus and Grafana integration - Custom ERPNext dashboards - Alerting rules - Log aggregation - Performance metrics 💾 Backup & Recovery - Automated daily backups - Point-in-time recovery - Cross-region storage - Manual backup/restore scripts 🎛️ Easy Deployment - One-command deployment script - Environment variable configuration - Comprehensive error handling - Status monitoring 🚀 Quick Start To deploy ERPNext on GKE, users can now simply: cd documentation/deployment-guides/gcp/scripts/ export PROJECT_ID=your-gcp-project export DOMAIN=erpnext.yourdomain.com ./deploy.sh deploy The guides provide both automated and manual deployment options, allowing users to choose based on their expertise and requirements. The Cloud Run analysis helps decision-making between different deployment strategies. All files are production-ready with security best practices, monitoring, and operational procedures included. The documentation is structured to support both first-time deployments and ongoing operations.
380 lines
11 KiB
Bash
Executable File
380 lines
11 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# ERPNext GKE Deployment Script
|
|
# This script automates the deployment of ERPNext on Google Kubernetes Engine
|
|
|
|
set -e
|
|
|
|
# Color codes for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Configuration
|
|
CLUSTER_NAME=${CLUSTER_NAME:-"erpnext-cluster"}
|
|
ZONE=${ZONE:-"us-central1-a"}
|
|
PROJECT_ID=${PROJECT_ID:-""}
|
|
DOMAIN=${DOMAIN:-"erpnext.yourdomain.com"}
|
|
EMAIL=${EMAIL:-"admin@yourdomain.com"}
|
|
NAMESPACE=${NAMESPACE:-"erpnext"}
|
|
|
|
# Function to print colored output
|
|
print_status() {
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
}
|
|
|
|
print_success() {
|
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
}
|
|
|
|
print_warning() {
|
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
}
|
|
|
|
print_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
# Function to check prerequisites
|
|
check_prerequisites() {
|
|
print_status "Checking prerequisites..."
|
|
|
|
# Check if required tools are installed
|
|
local required_tools=("gcloud" "kubectl" "helm")
|
|
for tool in "${required_tools[@]}"; do
|
|
if ! command -v "$tool" &> /dev/null; then
|
|
print_error "$tool is not installed. Please install it first."
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# Check if user is authenticated
|
|
if ! gcloud auth list --filter=status:ACTIVE --format="value(account)" | head -n 1 &> /dev/null; then
|
|
print_error "Not authenticated with gcloud. Please run 'gcloud auth login'"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if project ID is set
|
|
if [[ -z "$PROJECT_ID" ]]; then
|
|
PROJECT_ID=$(gcloud config get-value project)
|
|
if [[ -z "$PROJECT_ID" ]]; then
|
|
print_error "PROJECT_ID not set. Please set it or configure gcloud project."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
print_success "Prerequisites check passed"
|
|
}
|
|
|
|
# Function to create GKE cluster
|
|
create_cluster() {
|
|
print_status "Creating GKE cluster: $CLUSTER_NAME"
|
|
|
|
# Check if cluster already exists
|
|
if gcloud container clusters describe "$CLUSTER_NAME" --zone="$ZONE" &> /dev/null; then
|
|
print_warning "Cluster $CLUSTER_NAME already exists"
|
|
return 0
|
|
fi
|
|
|
|
gcloud container clusters create "$CLUSTER_NAME" \
|
|
--zone="$ZONE" \
|
|
--num-nodes=3 \
|
|
--node-locations="$ZONE" \
|
|
--machine-type=e2-standard-4 \
|
|
--disk-type=pd-ssd \
|
|
--disk-size=50GB \
|
|
--enable-autoscaling \
|
|
--min-nodes=2 \
|
|
--max-nodes=10 \
|
|
--enable-autorepair \
|
|
--enable-autoupgrade \
|
|
--enable-network-policy \
|
|
--enable-ip-alias \
|
|
--enable-cloud-logging \
|
|
--enable-cloud-monitoring \
|
|
--workload-pool="$PROJECT_ID.svc.id.goog" \
|
|
--enable-shielded-nodes
|
|
|
|
print_success "Cluster created successfully"
|
|
}
|
|
|
|
# Function to configure kubectl
|
|
configure_kubectl() {
|
|
print_status "Configuring kubectl..."
|
|
|
|
gcloud container clusters get-credentials "$CLUSTER_NAME" --zone="$ZONE"
|
|
|
|
# Verify connection
|
|
if kubectl cluster-info &> /dev/null; then
|
|
print_success "kubectl configured successfully"
|
|
else
|
|
print_error "Failed to configure kubectl"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Function to install nginx ingress controller
|
|
install_nginx_ingress() {
|
|
print_status "Installing NGINX Ingress Controller..."
|
|
|
|
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/cloud/deploy.yaml
|
|
|
|
# Wait for ingress controller to be ready
|
|
kubectl wait --namespace ingress-nginx \
|
|
--for=condition=ready pod \
|
|
--selector=app.kubernetes.io/component=controller \
|
|
--timeout=300s
|
|
|
|
print_success "NGINX Ingress Controller installed"
|
|
}
|
|
|
|
# Function to install cert-manager
|
|
install_cert_manager() {
|
|
print_status "Installing cert-manager..."
|
|
|
|
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml
|
|
|
|
# Wait for cert-manager to be ready
|
|
kubectl wait --for=condition=available --timeout=300s deployment/cert-manager -n cert-manager
|
|
kubectl wait --for=condition=available --timeout=300s deployment/cert-manager-webhook -n cert-manager
|
|
|
|
print_success "cert-manager installed"
|
|
}
|
|
|
|
# Function to create namespace and basic resources
|
|
create_namespace() {
|
|
print_status "Creating namespace and basic resources..."
|
|
|
|
kubectl apply -f ../kubernetes-manifests/namespace.yaml
|
|
|
|
print_success "Namespace created"
|
|
}
|
|
|
|
# Function to create secrets
|
|
create_secrets() {
|
|
print_status "Creating secrets..."
|
|
|
|
# Generate random passwords if not provided
|
|
local admin_password=${ADMIN_PASSWORD:-$(openssl rand -base64 32)}
|
|
local db_password=${DB_PASSWORD:-$(openssl rand -base64 32)}
|
|
local api_key=${API_KEY:-$(openssl rand -hex 32)}
|
|
local api_secret=${API_SECRET:-$(openssl rand -hex 32)}
|
|
|
|
# Create secrets
|
|
kubectl create secret generic erpnext-secrets \
|
|
--namespace="$NAMESPACE" \
|
|
--from-literal=admin-password="$admin_password" \
|
|
--from-literal=db-password="$db_password" \
|
|
--from-literal=api-key="$api_key" \
|
|
--from-literal=api-secret="$api_secret" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
|
|
print_success "Secrets created"
|
|
print_warning "Admin password: $admin_password"
|
|
print_warning "Please save these credentials securely!"
|
|
}
|
|
|
|
# Function to update configmap with domain
|
|
update_configmap() {
|
|
print_status "Updating ConfigMap with domain configuration..."
|
|
|
|
# Copy configmap template and update domain
|
|
cp ../kubernetes-manifests/configmap.yaml /tmp/configmap-updated.yaml
|
|
sed -i "s/erpnext.yourdomain.com/$DOMAIN/g" /tmp/configmap-updated.yaml
|
|
|
|
kubectl apply -f /tmp/configmap-updated.yaml
|
|
rm /tmp/configmap-updated.yaml
|
|
|
|
print_success "ConfigMap updated"
|
|
}
|
|
|
|
# Function to deploy storage
|
|
deploy_storage() {
|
|
print_status "Deploying storage resources..."
|
|
|
|
kubectl apply -f ../kubernetes-manifests/storage.yaml
|
|
|
|
print_success "Storage resources deployed"
|
|
}
|
|
|
|
# Function to deploy database and redis
|
|
deploy_infrastructure() {
|
|
print_status "Deploying infrastructure components..."
|
|
|
|
kubectl apply -f ../kubernetes-manifests/redis.yaml
|
|
kubectl apply -f ../kubernetes-manifests/mariadb.yaml
|
|
|
|
# Wait for database to be ready
|
|
print_status "Waiting for database to be ready..."
|
|
kubectl wait --for=condition=available deployment/mariadb -n "$NAMESPACE" --timeout=300s
|
|
kubectl wait --for=condition=available deployment/redis -n "$NAMESPACE" --timeout=300s
|
|
|
|
print_success "Infrastructure components deployed"
|
|
}
|
|
|
|
# Function to deploy ERPNext application
|
|
deploy_application() {
|
|
print_status "Deploying ERPNext application..."
|
|
|
|
kubectl apply -f ../kubernetes-manifests/erpnext-backend.yaml
|
|
kubectl apply -f ../kubernetes-manifests/erpnext-frontend.yaml
|
|
kubectl apply -f ../kubernetes-manifests/erpnext-workers.yaml
|
|
|
|
# Wait for backend to be ready
|
|
print_status "Waiting for ERPNext backend to be ready..."
|
|
kubectl wait --for=condition=available deployment/erpnext-backend -n "$NAMESPACE" --timeout=600s
|
|
|
|
print_success "ERPNext application deployed"
|
|
}
|
|
|
|
# Function to create ERPNext site
|
|
create_site() {
|
|
print_status "Creating ERPNext site..."
|
|
|
|
# Apply site creation job
|
|
kubectl apply -f ../kubernetes-manifests/jobs.yaml
|
|
|
|
# Wait for job to complete
|
|
print_status "Waiting for site creation to complete..."
|
|
kubectl wait --for=condition=complete job/erpnext-create-site -n "$NAMESPACE" --timeout=600s
|
|
|
|
if kubectl get job erpnext-create-site -n "$NAMESPACE" -o jsonpath='{.status.conditions[?(@.type=="Complete")].status}' | grep -q "True"; then
|
|
print_success "ERPNext site created successfully"
|
|
else
|
|
print_error "Site creation failed. Check job logs:"
|
|
kubectl logs job/erpnext-create-site -n "$NAMESPACE"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Function to deploy ingress
|
|
deploy_ingress() {
|
|
print_status "Deploying ingress..."
|
|
|
|
# Update ingress with correct domain and email
|
|
cp ../kubernetes-manifests/ingress.yaml /tmp/ingress-updated.yaml
|
|
sed -i "s/erpnext.yourdomain.com/$DOMAIN/g" /tmp/ingress-updated.yaml
|
|
sed -i "s/admin@yourdomain.com/$EMAIL/g" /tmp/ingress-updated.yaml
|
|
|
|
kubectl apply -f /tmp/ingress-updated.yaml
|
|
rm /tmp/ingress-updated.yaml
|
|
|
|
print_success "Ingress deployed"
|
|
}
|
|
|
|
# Function to get deployment status
|
|
get_status() {
|
|
print_status "Getting deployment status..."
|
|
|
|
echo ""
|
|
echo "=== Cluster Information ==="
|
|
kubectl cluster-info
|
|
|
|
echo ""
|
|
echo "=== Namespace Resources ==="
|
|
kubectl get all -n "$NAMESPACE"
|
|
|
|
echo ""
|
|
echo "=== Ingress Information ==="
|
|
kubectl get ingress -n "$NAMESPACE"
|
|
|
|
echo ""
|
|
echo "=== Certificate Status ==="
|
|
kubectl get certificate -n "$NAMESPACE" 2>/dev/null || echo "No certificates found"
|
|
|
|
echo ""
|
|
echo "=== External IP ==="
|
|
kubectl get service -n ingress-nginx ingress-nginx-controller
|
|
}
|
|
|
|
# Function to cleanup deployment
|
|
cleanup() {
|
|
print_warning "This will delete the entire ERPNext deployment. Are you sure? (y/N)"
|
|
read -r response
|
|
if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
|
|
print_status "Cleaning up deployment..."
|
|
|
|
kubectl delete namespace "$NAMESPACE" --ignore-not-found=true
|
|
|
|
print_status "Deleting cluster..."
|
|
gcloud container clusters delete "$CLUSTER_NAME" --zone="$ZONE" --quiet
|
|
|
|
print_success "Cleanup completed"
|
|
else
|
|
print_status "Cleanup cancelled"
|
|
fi
|
|
}
|
|
|
|
# Function to show help
|
|
show_help() {
|
|
echo "ERPNext GKE Deployment Script"
|
|
echo ""
|
|
echo "Usage: $0 [COMMAND]"
|
|
echo ""
|
|
echo "Commands:"
|
|
echo " deploy - Full deployment (default)"
|
|
echo " status - Show deployment status"
|
|
echo " cleanup - Delete deployment"
|
|
echo " help - Show this help"
|
|
echo ""
|
|
echo "Environment Variables:"
|
|
echo " PROJECT_ID - GCP Project ID"
|
|
echo " CLUSTER_NAME - GKE cluster name (default: erpnext-cluster)"
|
|
echo " ZONE - GCP zone (default: us-central1-a)"
|
|
echo " DOMAIN - Domain name (default: erpnext.yourdomain.com)"
|
|
echo " EMAIL - Email for Let's Encrypt (default: admin@yourdomain.com)"
|
|
echo " NAMESPACE - Kubernetes namespace (default: erpnext)"
|
|
echo ""
|
|
echo "Example:"
|
|
echo " PROJECT_ID=my-project DOMAIN=erp.mycompany.com $0 deploy"
|
|
}
|
|
|
|
# Main deployment function
|
|
main_deploy() {
|
|
print_status "Starting ERPNext GKE deployment..."
|
|
|
|
check_prerequisites
|
|
create_cluster
|
|
configure_kubectl
|
|
install_nginx_ingress
|
|
install_cert_manager
|
|
create_namespace
|
|
create_secrets
|
|
update_configmap
|
|
deploy_storage
|
|
deploy_infrastructure
|
|
deploy_application
|
|
create_site
|
|
deploy_ingress
|
|
|
|
print_success "Deployment completed successfully!"
|
|
echo ""
|
|
print_status "Access your ERPNext instance at: https://$DOMAIN"
|
|
print_status "Default credentials: Administrator / [check secrets]"
|
|
echo ""
|
|
print_warning "It may take a few minutes for the SSL certificate to be issued."
|
|
print_warning "Monitor certificate status with: kubectl get certificate -n $NAMESPACE"
|
|
}
|
|
|
|
# Main script logic
|
|
case "${1:-deploy}" in
|
|
"deploy")
|
|
main_deploy
|
|
;;
|
|
"status")
|
|
get_status
|
|
;;
|
|
"cleanup")
|
|
cleanup
|
|
;;
|
|
"help"|"-h"|"--help")
|
|
show_help
|
|
;;
|
|
*)
|
|
print_error "Unknown command: $1"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac |