Cloud managed services (Cloud SQL for MySQL and Memorystore for Redis). Here's what I've delivered: 📚 Complete Documentation Suite Core Documentation 1. 00-prerequisites-managed.md - Prerequisites with VPC, managed services setup 2. 01-gke-managed-deployment.md - GKE deployment using Cloud SQL & Memorystore 3. 02-cloud-run-deployment.md - Serverless Cloud Run deployment guide 4. 03-production-managed-setup.md - Enterprise production hardening 5. README.md - Comprehensive overview with decision matrix Production-Ready Kubernetes Manifests - namespace.yaml - Optimized resource quotas for managed services - storage.yaml - Reduced storage needs (no database volumes) - configmap.yaml - Configuration for managed DB connections - secrets.yaml - External Secrets Operator integration - erpnext-backend.yaml - Backend with Cloud SQL Proxy sidecars - erpnext-frontend.yaml - Nginx frontend optimized for managed services - erpnext-workers.yaml - Queue workers with managed DB connections - ingress.yaml - Enhanced ingress with security and performance optimizations - jobs.yaml - Site creation and backup jobs for managed environment Automation Scripts - deploy-managed.sh - Complete GKE deployment automation - cloud-run-deploy.sh - Serverless Cloud Run deployment automation 🎯 Key Features & Benefits Managed Services Architecture - Cloud SQL: Fully managed MySQL with 99.95% SLA - Memorystore: Managed Redis with automatic scaling - Cloud Storage: File storage with lifecycle management - VPC Integration: Secure private connectivity - Cloud SQL Proxy: Secure database connections Two Deployment Options Option 1: GKE + Managed Services - Full Kubernetes control with managed databases - Auto-scaling with HPA and VPA - Production-grade security and monitoring - Estimated cost: -/month Option 2: Cloud Run + Managed Services - Serverless with zero operational overhead - Automatic scaling from 0 to 1000+ instances - Pay-per-use pricing model - Estimated cost: -/month Enterprise Features - Workload Identity for secure GCP API access - External Secrets Operator for credential management - Network Policies for micro-segmentation - Binary Authorization for container security - Multi-region backup with automated DR testing - SLO/SLI monitoring with custom dashboards Cost Optimization - Reduced infrastructure footprint (no self-hosted DB) - Automatic storage scaling without over-provisioning - Right-sizing recommendations based on usage - Committed use discounts for predictable workloads Operational Benefits - Zero database administration - Google manages it all - Automatic security patches and updates - Point-in-time recovery for disaster scenarios - Performance insights with query optimization - Automated backups with cross-region replication 🚀 Quick Start Commands For GKE Deployment: cd documentation/deployment-guides/gcp-managed/scripts/ export PROJECT_ID=your-gcp-project export DOMAIN=erpnext.yourdomain.com ./deploy-managed.sh deploy For Cloud Run Deployment: cd documentation/deployment-guides/gcp-managed/scripts/ export PROJECT_ID=your-gcp-project export DOMAIN=erpnext.yourdomain.com ./cloud-run-deploy.sh deploy 📊 Architecture Comparison The managed services approach provides: - Better reliability (99.95% SLA vs self-managed) - Lower operational overhead (zero DB admin) - Enhanced security (Google's security team manages DB) - Cost predictability (no over-provisioning) - Faster scaling (automatic storage scaling) - Better disaster recovery (built-in backup and replication) This documentation provides organizations with enterprise-ready deployment options that leverage Google Cloud's managed services for maximum reliability and minimum operational overhead while maintaining full ERPNext functionality.
683 lines
24 KiB
Bash
Executable File
683 lines
24 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# ERPNext GKE Deployment Script with Managed Services
|
|
# This script automates the deployment of ERPNext on GKE using Cloud SQL and Memorystore
|
|
|
|
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-managed-cluster"}
|
|
ZONE=${ZONE:-"us-central1-a"}
|
|
REGION=${REGION:-"us-central1"}
|
|
PROJECT_ID=${PROJECT_ID:-""}
|
|
DOMAIN=${DOMAIN:-"erpnext.yourdomain.com"}
|
|
EMAIL=${EMAIL:-"admin@yourdomain.com"}
|
|
NAMESPACE=${NAMESPACE:-"erpnext"}
|
|
|
|
# Managed services configuration
|
|
DB_INSTANCE_NAME=${DB_INSTANCE_NAME:-"erpnext-db"}
|
|
REDIS_INSTANCE_NAME=${REDIS_INSTANCE_NAME:-"erpnext-redis"}
|
|
VPC_NAME=${VPC_NAME:-"erpnext-vpc"}
|
|
VPC_CONNECTOR=${VPC_CONNECTOR:-"erpnext-connector"}
|
|
|
|
# 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 for managed services deployment..."
|
|
|
|
# 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 check managed services
|
|
check_managed_services() {
|
|
print_status "Checking managed services status..."
|
|
|
|
# Check Cloud SQL instance
|
|
if ! gcloud sql instances describe "$DB_INSTANCE_NAME" &> /dev/null; then
|
|
print_error "Cloud SQL instance '$DB_INSTANCE_NAME' not found. Please create it first."
|
|
exit 1
|
|
fi
|
|
|
|
# Check Memorystore Redis instance
|
|
if ! gcloud redis instances describe "$REDIS_INSTANCE_NAME" --region="$REGION" &> /dev/null; then
|
|
print_error "Memorystore Redis instance '$REDIS_INSTANCE_NAME' not found. Please create it first."
|
|
exit 1
|
|
fi
|
|
|
|
# Check VPC network
|
|
if ! gcloud compute networks describe "$VPC_NAME" &> /dev/null; then
|
|
print_error "VPC network '$VPC_NAME' not found. Please create it first."
|
|
exit 1
|
|
fi
|
|
|
|
print_success "All managed services are available"
|
|
}
|
|
|
|
# Function to get managed services information
|
|
get_managed_services_info() {
|
|
print_status "Gathering managed services information..."
|
|
|
|
# Get Cloud SQL connection name
|
|
DB_CONNECTION_NAME=$(gcloud sql instances describe "$DB_INSTANCE_NAME" --format="value(connectionName)")
|
|
print_status "Cloud SQL connection name: $DB_CONNECTION_NAME"
|
|
|
|
# Get Redis host IP
|
|
REDIS_HOST=$(gcloud redis instances describe "$REDIS_INSTANCE_NAME" --region="$REGION" --format="value(host)")
|
|
print_status "Redis host IP: $REDIS_HOST"
|
|
|
|
# Get VPC connector
|
|
if ! gcloud compute networks vpc-access connectors describe "$VPC_CONNECTOR" --region="$REGION" &> /dev/null; then
|
|
print_warning "VPC connector '$VPC_CONNECTOR' not found. This is needed for Cloud Run deployment."
|
|
fi
|
|
|
|
print_success "Managed services information gathered"
|
|
}
|
|
|
|
# Function to create GKE cluster for managed services
|
|
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=15 \
|
|
--enable-autorepair \
|
|
--enable-autoupgrade \
|
|
--enable-network-policy \
|
|
--enable-ip-alias \
|
|
--network="$VPC_NAME" \
|
|
--subnetwork=erpnext-subnet \
|
|
--enable-private-nodes \
|
|
--master-ipv4-cidr-block=172.16.0.0/28 \
|
|
--enable-cloud-logging \
|
|
--enable-cloud-monitoring \
|
|
--workload-pool="$PROJECT_ID.svc.id.goog" \
|
|
--enable-shielded-nodes \
|
|
--enable-image-streaming \
|
|
--logging=SYSTEM,WORKLOAD,API_SERVER \
|
|
--monitoring=SYSTEM,WORKLOAD,STORAGE,POD,DEPLOYMENT,STATEFULSET,DAEMONSET,HPA,CADVISOR,KUBELET
|
|
|
|
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 required operators and controllers
|
|
install_operators() {
|
|
print_status "Installing required operators and controllers..."
|
|
|
|
# Install External Secrets Operator
|
|
helm repo add external-secrets https://charts.external-secrets.io
|
|
helm repo update
|
|
helm install external-secrets external-secrets/external-secrets \
|
|
-n external-secrets-system \
|
|
--create-namespace \
|
|
--wait
|
|
|
|
# Install 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
|
|
|
|
# Install 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 "Operators and controllers installed"
|
|
}
|
|
|
|
# Function to create namespace and basic resources
|
|
create_namespace() {
|
|
print_status "Creating namespace and basic resources..."
|
|
|
|
# Update namespace.yaml with correct labels
|
|
cp ../kubernetes-manifests/namespace.yaml /tmp/namespace-updated.yaml
|
|
|
|
kubectl apply -f /tmp/namespace-updated.yaml
|
|
rm /tmp/namespace-updated.yaml
|
|
|
|
print_success "Namespace created"
|
|
}
|
|
|
|
# Function to create service account and workload identity
|
|
setup_workload_identity() {
|
|
print_status "Setting up Workload Identity..."
|
|
|
|
# Create Kubernetes service account
|
|
kubectl create serviceaccount erpnext-ksa --namespace="$NAMESPACE" --dry-run=client -o yaml | kubectl apply -f -
|
|
|
|
# Create Google Cloud service account if it doesn't exist
|
|
if ! gcloud iam service-accounts describe "erpnext-managed@$PROJECT_ID.iam.gserviceaccount.com" &> /dev/null; then
|
|
gcloud iam service-accounts create erpnext-managed \
|
|
--display-name="ERPNext Managed Services Account"
|
|
fi
|
|
|
|
# Grant necessary permissions
|
|
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
|
|
--member="serviceAccount:erpnext-managed@$PROJECT_ID.iam.gserviceaccount.com" \
|
|
--role="roles/cloudsql.client"
|
|
|
|
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
|
|
--member="serviceAccount:erpnext-managed@$PROJECT_ID.iam.gserviceaccount.com" \
|
|
--role="roles/redis.editor"
|
|
|
|
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
|
|
--member="serviceAccount:erpnext-managed@$PROJECT_ID.iam.gserviceaccount.com" \
|
|
--role="roles/secretmanager.secretAccessor"
|
|
|
|
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
|
|
--member="serviceAccount:erpnext-managed@$PROJECT_ID.iam.gserviceaccount.com" \
|
|
--role="roles/storage.admin"
|
|
|
|
# Bind service accounts
|
|
gcloud iam service-accounts add-iam-policy-binding \
|
|
--role roles/iam.workloadIdentityUser \
|
|
--member "serviceAccount:$PROJECT_ID.svc.id.goog[$NAMESPACE/erpnext-ksa]" \
|
|
"erpnext-managed@$PROJECT_ID.iam.gserviceaccount.com"
|
|
|
|
# Annotate Kubernetes service account
|
|
kubectl annotate serviceaccount erpnext-ksa \
|
|
--namespace="$NAMESPACE" \
|
|
"iam.gke.io/gcp-service-account=erpnext-managed@$PROJECT_ID.iam.gserviceaccount.com" \
|
|
--overwrite
|
|
|
|
print_success "Workload Identity configured"
|
|
}
|
|
|
|
# Function to create secrets using External Secrets Operator
|
|
create_secrets() {
|
|
print_status "Creating secrets with External Secrets Operator..."
|
|
|
|
# Generate random passwords if secrets don't exist in Secret Manager
|
|
if ! gcloud secrets describe erpnext-admin-password &> /dev/null; then
|
|
local admin_password=${ADMIN_PASSWORD:-$(openssl rand -base64 32)}
|
|
gcloud secrets create erpnext-admin-password --data-file=<(echo -n "$admin_password")
|
|
print_warning "Admin password: $admin_password"
|
|
fi
|
|
|
|
if ! gcloud secrets describe erpnext-db-password &> /dev/null; then
|
|
local db_password=${DB_PASSWORD:-$(openssl rand -base64 32)}
|
|
gcloud secrets create erpnext-db-password --data-file=<(echo -n "$db_password")
|
|
print_warning "Database password: $db_password"
|
|
fi
|
|
|
|
if ! gcloud secrets describe erpnext-api-key &> /dev/null; then
|
|
local api_key=${API_KEY:-$(openssl rand -hex 32)}
|
|
gcloud secrets create erpnext-api-key --data-file=<(echo -n "$api_key")
|
|
fi
|
|
|
|
if ! gcloud secrets describe erpnext-api-secret &> /dev/null; then
|
|
local api_secret=${API_SECRET:-$(openssl rand -hex 32)}
|
|
gcloud secrets create erpnext-api-secret --data-file=<(echo -n "$api_secret")
|
|
fi
|
|
|
|
# Create connection name secret
|
|
gcloud secrets create erpnext-db-connection-name --data-file=<(echo -n "$DB_CONNECTION_NAME") --quiet || \
|
|
gcloud secrets versions add erpnext-db-connection-name --data-file=<(echo -n "$DB_CONNECTION_NAME")
|
|
|
|
# Get Redis AUTH string if enabled
|
|
local redis_auth=""
|
|
if gcloud redis instances describe "$REDIS_INSTANCE_NAME" --region="$REGION" --format="value(authEnabled)" | grep -q "True"; then
|
|
redis_auth=$(gcloud redis instances describe "$REDIS_INSTANCE_NAME" --region="$REGION" --format="value(authString)")
|
|
gcloud secrets create redis-auth-string --data-file=<(echo -n "$redis_auth") --quiet || \
|
|
gcloud secrets versions add redis-auth-string --data-file=<(echo -n "$redis_auth")
|
|
fi
|
|
|
|
# Apply External Secrets configuration
|
|
cp ../kubernetes-manifests/secrets.yaml /tmp/secrets-updated.yaml
|
|
sed -i "s/PROJECT_ID/$PROJECT_ID/g" /tmp/secrets-updated.yaml
|
|
sed -i "s/REGION/$REGION/g" /tmp/secrets-updated.yaml
|
|
sed -i "s/erpnext-managed-cluster/$CLUSTER_NAME/g" /tmp/secrets-updated.yaml
|
|
|
|
kubectl apply -f /tmp/secrets-updated.yaml
|
|
rm /tmp/secrets-updated.yaml
|
|
|
|
# Wait for External Secrets to sync
|
|
print_status "Waiting for secrets to be synced..."
|
|
sleep 30
|
|
|
|
# Create service account key for Cloud SQL Proxy
|
|
if ! kubectl get secret gcp-service-account-key -n "$NAMESPACE" &> /dev/null; then
|
|
local key_file="/tmp/erpnext-managed-key.json"
|
|
gcloud iam service-accounts keys create "$key_file" \
|
|
--iam-account="erpnext-managed@$PROJECT_ID.iam.gserviceaccount.com"
|
|
|
|
kubectl create secret generic gcp-service-account-key \
|
|
--namespace="$NAMESPACE" \
|
|
--from-file=key.json="$key_file"
|
|
|
|
rm "$key_file"
|
|
fi
|
|
|
|
print_success "Secrets created"
|
|
print_warning "Please save the generated credentials securely!"
|
|
}
|
|
|
|
# Function to update ConfigMap with managed services configuration
|
|
update_configmap() {
|
|
print_status "Updating ConfigMap with managed services configuration..."
|
|
|
|
# Copy and update configmap
|
|
cp ../kubernetes-manifests/configmap.yaml /tmp/configmap-updated.yaml
|
|
sed -i "s/erpnext.yourdomain.com/$DOMAIN/g" /tmp/configmap-updated.yaml
|
|
sed -i "s/PROJECT_ID/$PROJECT_ID/g" /tmp/configmap-updated.yaml
|
|
sed -i "s/REGION/$REGION/g" /tmp/configmap-updated.yaml
|
|
sed -i "s/REDIS_HOST/$REDIS_HOST/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 ERPNext application with managed services
|
|
deploy_application() {
|
|
print_status "Deploying ERPNext application with managed services..."
|
|
|
|
# Update manifests with managed services configuration
|
|
local manifests=("erpnext-backend.yaml" "erpnext-frontend.yaml" "erpnext-workers.yaml")
|
|
|
|
for manifest in "${manifests[@]}"; do
|
|
cp "../kubernetes-manifests/$manifest" "/tmp/${manifest%.yaml}-updated.yaml"
|
|
sed -i "s/PROJECT_ID/$PROJECT_ID/g" "/tmp/${manifest%.yaml}-updated.yaml"
|
|
sed -i "s/REDIS_HOST/$REDIS_HOST/g" "/tmp/${manifest%.yaml}-updated.yaml"
|
|
|
|
kubectl apply -f "/tmp/${manifest%.yaml}-updated.yaml"
|
|
rm "/tmp/${manifest%.yaml}-updated.yaml"
|
|
done
|
|
|
|
# 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 with managed services..."
|
|
|
|
# Update jobs manifest
|
|
cp ../kubernetes-manifests/jobs.yaml /tmp/jobs-updated.yaml
|
|
sed -i "s/PROJECT_ID_PLACEHOLDER/$PROJECT_ID/g" /tmp/jobs-updated.yaml
|
|
sed -i "s/REDIS_HOST_PLACEHOLDER/$REDIS_HOST/g" /tmp/jobs-updated.yaml
|
|
|
|
# Apply site creation job
|
|
kubectl apply -f /tmp/jobs-updated.yaml
|
|
rm /tmp/jobs-updated.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=1200s
|
|
|
|
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 with managed services"
|
|
else
|
|
print_error "Site creation failed. Check job logs:"
|
|
kubectl logs job/erpnext-create-site -n "$NAMESPACE"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Function to deploy ingress with managed services optimization
|
|
deploy_ingress() {
|
|
print_status "Deploying ingress with managed services optimization..."
|
|
|
|
# Update ingress with correct domains and managed services configuration
|
|
cp ../kubernetes-manifests/ingress.yaml /tmp/ingress-updated.yaml
|
|
sed -i "s/erpnext.yourdomain.com/$DOMAIN/g" /tmp/ingress-updated.yaml
|
|
sed -i "s/api.yourdomain.com/api.$DOMAIN/g" /tmp/ingress-updated.yaml
|
|
sed -i "s/admin@yourdomain.com/$EMAIL/g" /tmp/ingress-updated.yaml
|
|
sed -i "s/PROJECT_ID/$PROJECT_ID/g" /tmp/ingress-updated.yaml
|
|
|
|
kubectl apply -f /tmp/ingress-updated.yaml
|
|
rm /tmp/ingress-updated.yaml
|
|
|
|
print_success "Ingress deployed with managed services optimization"
|
|
}
|
|
|
|
# Function to setup monitoring for managed services
|
|
setup_monitoring() {
|
|
print_status "Setting up monitoring for managed services..."
|
|
|
|
# Install Prometheus stack with managed services configuration
|
|
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
|
|
helm repo update
|
|
|
|
# Create values file for managed services monitoring
|
|
cat > /tmp/prometheus-values.yaml <<EOF
|
|
prometheus:
|
|
prometheusSpec:
|
|
storageSpec:
|
|
volumeClaimTemplate:
|
|
spec:
|
|
resources:
|
|
requests:
|
|
storage: 50Gi
|
|
retention: 30d
|
|
additionalScrapeConfigs:
|
|
- job_name: 'cloud-sql-exporter'
|
|
static_configs:
|
|
- targets: ['cloud-sql-exporter:9308']
|
|
- job_name: 'redis-exporter'
|
|
static_configs:
|
|
- targets: ['redis-exporter:9121']
|
|
|
|
grafana:
|
|
adminPassword: SecurePassword123!
|
|
dashboardProviders:
|
|
dashboardproviders.yaml:
|
|
apiVersion: 1
|
|
providers:
|
|
- name: 'erpnext-managed'
|
|
orgId: 1
|
|
folder: 'ERPNext Managed Services'
|
|
type: file
|
|
disableDeletion: false
|
|
editable: true
|
|
options:
|
|
path: /var/lib/grafana/dashboards/erpnext-managed
|
|
dashboards:
|
|
erpnext-managed:
|
|
cloud-sql-dashboard:
|
|
gnetId: 14114
|
|
revision: 1
|
|
datasource: Prometheus
|
|
redis-dashboard:
|
|
gnetId: 763
|
|
revision: 4
|
|
datasource: Prometheus
|
|
|
|
alertmanager:
|
|
alertmanagerSpec:
|
|
storage:
|
|
volumeClaimTemplate:
|
|
spec:
|
|
resources:
|
|
requests:
|
|
storage: 10Gi
|
|
EOF
|
|
|
|
helm install prometheus prometheus-community/kube-prometheus-stack \
|
|
--namespace monitoring \
|
|
--create-namespace \
|
|
--values /tmp/prometheus-values.yaml \
|
|
--wait
|
|
|
|
rm /tmp/prometheus-values.yaml
|
|
|
|
print_success "Monitoring setup completed for managed services"
|
|
}
|
|
|
|
# Function to create backup bucket and setup backup jobs
|
|
setup_backup() {
|
|
print_status "Setting up backup for managed services..."
|
|
|
|
# Create backup bucket
|
|
gsutil mb gs://erpnext-backups-$PROJECT_ID || true
|
|
|
|
# Set lifecycle policy
|
|
gsutil lifecycle set - gs://erpnext-backups-$PROJECT_ID <<EOF
|
|
{
|
|
"lifecycle": {
|
|
"rule": [
|
|
{
|
|
"action": {"type": "Delete"},
|
|
"condition": {"age": 90}
|
|
},
|
|
{
|
|
"action": {"type": "SetStorageClass", "storageClass": "NEARLINE"},
|
|
"condition": {"age": 30}
|
|
},
|
|
{
|
|
"action": {"type": "SetStorageClass", "storageClass": "COLDLINE"},
|
|
"condition": {"age": 60}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
EOF
|
|
|
|
# Backup jobs are already included in jobs.yaml and will be deployed
|
|
print_success "Backup setup completed (Cloud SQL automated backups + file backups)"
|
|
}
|
|
|
|
# Function to get deployment status
|
|
get_status() {
|
|
print_status "Getting deployment status..."
|
|
|
|
echo ""
|
|
echo "=== Cluster Information ==="
|
|
kubectl cluster-info
|
|
|
|
echo ""
|
|
echo "=== Managed Services Status ==="
|
|
echo "Cloud SQL Instance:"
|
|
gcloud sql instances describe "$DB_INSTANCE_NAME" --format="table(name,state,region,databaseVersion)"
|
|
|
|
echo ""
|
|
echo "Redis Instance:"
|
|
gcloud redis instances describe "$REDIS_INSTANCE_NAME" --region="$REGION" --format="table(name,state,host,port)"
|
|
|
|
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
|
|
|
|
echo ""
|
|
echo "=== Secrets Status ==="
|
|
kubectl get externalsecret -n "$NAMESPACE" 2>/dev/null || echo "External Secrets not found"
|
|
}
|
|
|
|
# Function to cleanup deployment
|
|
cleanup() {
|
|
print_warning "This will delete the entire ERPNext deployment but preserve managed services. 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_warning "Managed services (Cloud SQL, Redis) are preserved."
|
|
print_warning "To delete them manually:"
|
|
print_warning " gcloud sql instances delete $DB_INSTANCE_NAME"
|
|
print_warning " gcloud redis instances delete $REDIS_INSTANCE_NAME --region=$REGION"
|
|
|
|
print_success "Cleanup completed"
|
|
else
|
|
print_status "Cleanup cancelled"
|
|
fi
|
|
}
|
|
|
|
# Function to show help
|
|
show_help() {
|
|
echo "ERPNext GKE Deployment Script with Managed Services"
|
|
echo ""
|
|
echo "Usage: $0 [COMMAND]"
|
|
echo ""
|
|
echo "Commands:"
|
|
echo " deploy - Full deployment with managed services (default)"
|
|
echo " status - Show deployment status"
|
|
echo " cleanup - Delete deployment (preserves managed services)"
|
|
echo " help - Show this help"
|
|
echo ""
|
|
echo "Environment Variables:"
|
|
echo " PROJECT_ID - GCP Project ID"
|
|
echo " CLUSTER_NAME - GKE cluster name (default: erpnext-managed-cluster)"
|
|
echo " ZONE - GCP zone (default: us-central1-a)"
|
|
echo " REGION - GCP region (default: us-central1)"
|
|
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 " DB_INSTANCE_NAME - Cloud SQL instance name (default: erpnext-db)"
|
|
echo " REDIS_INSTANCE_NAME - Memorystore instance name (default: erpnext-redis)"
|
|
echo " VPC_NAME - VPC network name (default: erpnext-vpc)"
|
|
echo ""
|
|
echo "Prerequisites:"
|
|
echo " - Complete setup in 00-prerequisites-managed.md"
|
|
echo " - Cloud SQL and Memorystore instances must exist"
|
|
echo " - VPC network with proper configuration"
|
|
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 with managed services..."
|
|
|
|
check_prerequisites
|
|
check_managed_services
|
|
get_managed_services_info
|
|
create_cluster
|
|
configure_kubectl
|
|
install_operators
|
|
create_namespace
|
|
setup_workload_identity
|
|
create_secrets
|
|
update_configmap
|
|
deploy_storage
|
|
deploy_application
|
|
create_site
|
|
deploy_ingress
|
|
setup_monitoring
|
|
setup_backup
|
|
|
|
print_success "Deployment completed successfully with managed services!"
|
|
echo ""
|
|
print_status "Access your ERPNext instance at: https://$DOMAIN"
|
|
print_status "API endpoint: https://api.$DOMAIN"
|
|
echo ""
|
|
print_status "Managed Services:"
|
|
print_status " Cloud SQL: $DB_CONNECTION_NAME"
|
|
print_status " Redis: $REDIS_HOST:6379"
|
|
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"
|
|
echo ""
|
|
print_status "Default credentials are stored in Secret Manager."
|
|
print_status "Retrieve admin password with: gcloud secrets versions access latest --secret=erpnext-admin-password"
|
|
}
|
|
|
|
# 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 |