#!/bin/bash # ERPNext EKS Deployment Script for AWS Managed Services # This script automates the deployment of ERPNext on Amazon EKS with RDS and MemoryDB 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 # Default configuration AWS_REGION=${AWS_REGION:-us-east-1} AWS_PROFILE=${AWS_PROFILE:-default} CLUSTER_NAME=${CLUSTER_NAME:-erpnext-cluster} PROJECT_NAME=${PROJECT_NAME:-erpnext} DOMAIN_NAME=${DOMAIN_NAME:-erpnext.yourdomain.com} ENVIRONMENT=${ENVIRONMENT:-production} # EKS configuration KUBERNETES_VERSION=${KUBERNETES_VERSION:-1.28} NODE_INSTANCE_TYPE=${NODE_INSTANCE_TYPE:-t3.medium} NODE_GROUP_MIN_SIZE=${NODE_GROUP_MIN_SIZE:-2} NODE_GROUP_MAX_SIZE=${NODE_GROUP_MAX_SIZE:-10} NODE_GROUP_DESIRED_SIZE=${NODE_GROUP_DESIRED_SIZE:-3} # Function to print colored output print_status() { echo -e "${GREEN}[INFO]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } print_header() { echo -e "${BLUE}[DEPLOY]${NC} $1" } # Function to check prerequisites check_prerequisites() { print_header "Checking prerequisites..." # Check required tools local required_tools=("aws" "kubectl" "eksctl" "helm" "jq") 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 AWS credentials if ! aws sts get-caller-identity --profile $AWS_PROFILE &> /dev/null; then print_error "AWS credentials not configured properly." exit 1 fi # Get Account ID ACCOUNT_ID=$(aws sts get-caller-identity --profile $AWS_PROFILE --query Account --output text) print_status "AWS Account ID: $ACCOUNT_ID" print_status "AWS Region: $AWS_REGION" print_status "AWS Profile: $AWS_PROFILE" } # Function to check if VPC exists check_vpc() { print_header "Checking VPC configuration..." VPC_ID=$(aws ec2 describe-vpcs \ --filters "Name=tag:Name,Values=${PROJECT_NAME}-vpc" \ --query "Vpcs[0].VpcId" \ --output text \ --region $AWS_REGION \ --profile $AWS_PROFILE 2>/dev/null || echo "None") if [ "$VPC_ID" = "None" ] || [ -z "$VPC_ID" ]; then print_error "VPC not found. Please run the prerequisites setup first." exit 1 fi print_status "Found VPC: $VPC_ID" # Get subnet IDs PRIVATE_SUBNET_1A=$(aws ec2 describe-subnets \ --filters "Name=tag:Name,Values=${PROJECT_NAME}-private-subnet-1a" \ --query "Subnets[0].SubnetId" \ --output text \ --region $AWS_REGION \ --profile $AWS_PROFILE) PRIVATE_SUBNET_1B=$(aws ec2 describe-subnets \ --filters "Name=tag:Name,Values=${PROJECT_NAME}-private-subnet-1b" \ --query "Subnets[0].SubnetId" \ --output text \ --region $AWS_REGION \ --profile $AWS_PROFILE) PUBLIC_SUBNET_1A=$(aws ec2 describe-subnets \ --filters "Name=tag:Name,Values=${PROJECT_NAME}-public-subnet-1a" \ --query "Subnets[0].SubnetId" \ --output text \ --region $AWS_REGION \ --profile $AWS_PROFILE) PUBLIC_SUBNET_1B=$(aws ec2 describe-subnets \ --filters "Name=tag:Name,Values=${PROJECT_NAME}-public-subnet-1b" \ --query "Subnets[0].SubnetId" \ --output text \ --region $AWS_REGION \ --profile $AWS_PROFILE) print_status "Private Subnets: $PRIVATE_SUBNET_1A, $PRIVATE_SUBNET_1B" print_status "Public Subnets: $PUBLIC_SUBNET_1A, $PUBLIC_SUBNET_1B" } # Function to get security group IDs get_security_groups() { print_header "Getting security group IDs..." APP_SG=$(aws ec2 describe-security-groups \ --filters "Name=tag:Name,Values=${PROJECT_NAME}-app-sg" \ --query "SecurityGroups[0].GroupId" \ --output text \ --region $AWS_REGION \ --profile $AWS_PROFILE) print_status "Application Security Group: $APP_SG" } # Function to get database endpoints get_database_endpoints() { print_header "Getting database endpoints..." # Get RDS endpoint DB_HOST=$(aws rds describe-db-instances \ --db-instance-identifier ${PROJECT_NAME}-db \ --query "DBInstances[0].Endpoint.Address" \ --output text \ --region $AWS_REGION \ --profile $AWS_PROFILE 2>/dev/null || echo "") if [ -z "$DB_HOST" ]; then print_error "RDS instance not found. Please create it first." exit 1 fi # Get Redis endpoint REDIS_HOST=$(aws memorydb describe-clusters \ --cluster-name ${PROJECT_NAME}-redis \ --query "Clusters[0].ClusterEndpoint.Address" \ --output text \ --region $AWS_REGION \ --profile $AWS_PROFILE 2>/dev/null || echo "") if [ -z "$REDIS_HOST" ]; then print_error "MemoryDB cluster not found. Please create it first." exit 1 fi print_status "Database Host: $DB_HOST" print_status "Redis Host: $REDIS_HOST" } # Function to create EFS file system create_efs() { print_header "Creating EFS file system..." # Check if EFS already exists EFS_ID=$(aws efs describe-file-systems \ --query "FileSystems[?Tags[?Key=='Name' && Value=='${PROJECT_NAME}-sites-efs']].FileSystemId" \ --output text \ --region $AWS_REGION \ --profile $AWS_PROFILE 2>/dev/null || echo "") if [ -z "$EFS_ID" ]; then print_status "Creating EFS file system..." EFS_ID=$(aws efs create-file-system \ --creation-token ${PROJECT_NAME}-sites-$(date +%s) \ --performance-mode generalPurpose \ --throughput-mode provisioned \ --provisioned-throughput-in-mibps 100 \ --encrypted \ --tags Key=Name,Value=${PROJECT_NAME}-sites-efs Key=Application,Value=ERPNext \ --query "FileSystemId" \ --output text \ --region $AWS_REGION \ --profile $AWS_PROFILE) print_status "Created EFS: $EFS_ID" # Wait for EFS to be available print_status "Waiting for EFS to be available..." aws efs wait file-system-available --file-system-id $EFS_ID --region $AWS_REGION --profile $AWS_PROFILE # Create mount targets print_status "Creating EFS mount targets..." aws efs create-mount-target \ --file-system-id $EFS_ID \ --subnet-id $PRIVATE_SUBNET_1A \ --security-groups $APP_SG \ --region $AWS_REGION \ --profile $AWS_PROFILE aws efs create-mount-target \ --file-system-id $EFS_ID \ --subnet-id $PRIVATE_SUBNET_1B \ --security-groups $APP_SG \ --region $AWS_REGION \ --profile $AWS_PROFILE else print_status "Using existing EFS: $EFS_ID" fi } # Function to create EKS cluster configuration create_cluster_config() { print_header "Creating EKS cluster configuration..." cat > /tmp/erpnext-eks-cluster.yaml </dev/null || echo "") if [ "$CLUSTER_STATUS" = "ACTIVE" ]; then print_status "EKS cluster already exists and is active" else print_status "Creating EKS cluster (this may take 15-20 minutes)..." eksctl create cluster -f /tmp/erpnext-eks-cluster.yaml --profile $AWS_PROFILE print_status "EKS cluster created successfully" fi # Update kubeconfig print_status "Updating kubeconfig..." aws eks update-kubeconfig \ --region $AWS_REGION \ --name $CLUSTER_NAME \ --profile $AWS_PROFILE # Verify cluster access print_status "Verifying cluster access..." kubectl cluster-info kubectl get nodes } # Function to install required add-ons install_addons() { print_header "Installing required add-ons..." # Install AWS Load Balancer Controller install_aws_load_balancer_controller # Install EFS CSI Driver install_efs_csi_driver # Install External Secrets Operator install_external_secrets_operator # Install metrics server install_metrics_server } # Function to install AWS Load Balancer Controller install_aws_load_balancer_controller() { print_status "Installing AWS Load Balancer Controller..." # Download IAM policy curl -o /tmp/iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.7.2/docs/install/iam_policy.json # Create IAM policy (ignore if already exists) aws iam create-policy \ --policy-name AWSLoadBalancerControllerIAMPolicy \ --policy-document file:///tmp/iam_policy.json \ --profile $AWS_PROFILE 2>/dev/null || true # Install using Helm helm repo add eks https://aws.github.io/eks-charts helm repo update helm upgrade --install aws-load-balancer-controller eks/aws-load-balancer-controller \ -n kube-system \ --set clusterName=$CLUSTER_NAME \ --set serviceAccount.create=false \ --set serviceAccount.name=aws-load-balancer-controller \ --wait print_status "AWS Load Balancer Controller installed" } # Function to install EFS CSI Driver install_efs_csi_driver() { print_status "Installing EFS CSI Driver..." helm repo add aws-efs-csi-driver https://kubernetes-sigs.github.io/aws-efs-csi-driver/ helm repo update helm upgrade --install aws-efs-csi-driver aws-efs-csi-driver/aws-efs-csi-driver \ --namespace kube-system \ --set controller.serviceAccount.create=false \ --set controller.serviceAccount.name=efs-csi-controller-sa \ --wait print_status "EFS CSI Driver installed" } # Function to install External Secrets Operator install_external_secrets_operator() { print_status "Installing External Secrets Operator..." helm repo add external-secrets https://charts.external-secrets.io helm repo update helm upgrade --install external-secrets external-secrets/external-secrets \ --namespace external-secrets \ --create-namespace \ --set installCRDs=true \ --wait print_status "External Secrets Operator installed" } # Function to install metrics server install_metrics_server() { print_status "Installing Metrics Server..." kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml # Wait for metrics server to be ready kubectl wait --for=condition=available --timeout=300s deployment/metrics-server -n kube-system print_status "Metrics Server installed" } # Function to create Kubernetes manifests create_kubernetes_manifests() { print_header "Creating Kubernetes manifests..." # Create manifest directory mkdir -p /tmp/k8s-manifests # Create namespace and storage manifests create_namespace_manifest create_storage_manifest create_configmap_manifest create_secrets_manifest create_backend_manifest create_frontend_manifest create_workers_manifest create_ingress_manifest create_jobs_manifest } # Function to create namespace manifest create_namespace_manifest() { cat > /tmp/k8s-manifests/namespace.yaml < /tmp/k8s-manifests/storage.yaml < /tmp/k8s-manifests/configmap.yaml < /tmp/k8s-manifests/secrets.yaml < /tmp/k8s-manifests/backend.yaml < /tmp/k8s-manifests/frontend.yaml < /tmp/k8s-manifests/workers.yaml < /tmp/k8s-manifests/ingress.yaml < /tmp/k8s-manifests/jobs.yaml </dev/null || echo "") echo "" print_status "ERPNext EKS deployment completed successfully!" echo "" print_status "Access Information:" if [ -n "$ALB_ENDPOINT" ]; then print_status " Application URL: http://$ALB_ENDPOINT" else print_status " Application URL: Check ALB endpoint with 'kubectl get ingress -n erpnext'" fi print_status " Domain: $DOMAIN_NAME (configure DNS to point to ALB endpoint)" print_status " Admin Username: Administrator" print_status " Admin Password: Check AWS Secrets Manager (${PROJECT_NAME}/admin/password)" echo "" print_status "AWS Resources Created:" print_status " EKS Cluster: $CLUSTER_NAME" print_status " EFS File System: $EFS_ID" print_status " VPC: $VPC_ID" echo "" print_status "Kubernetes Resources:" print_status " Namespace: erpnext" print_status " Deployments: erpnext-backend, erpnext-frontend, erpnext-queue-default, erpnext-scheduler" print_status " Services: erpnext-backend, erpnext-frontend" print_status " Ingress: erpnext-ingress" echo "" print_status "Next Steps:" print_status " 1. Configure DNS to point $DOMAIN_NAME to ALB endpoint" print_status " 2. Set up SSL certificate in ACM and update ingress" print_status " 3. Configure monitoring with Prometheus/Grafana" print_status " 4. Set up backup procedures" echo "" print_status "Useful Commands:" print_status " kubectl get pods -n erpnext" print_status " kubectl logs -f deployment/erpnext-backend -n erpnext" print_status " kubectl get ingress -n erpnext" echo "" } # Function to clean up temporary files cleanup() { print_status "Cleaning up temporary files..." rm -rf /tmp/erpnext-eks-cluster.yaml /tmp/k8s-manifests /tmp/iam_policy.json } # Main execution function main() { print_header "Starting ERPNext EKS Deployment" # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in --region) AWS_REGION="$2" shift 2 ;; --profile) AWS_PROFILE="$2" shift 2 ;; --cluster-name) CLUSTER_NAME="$2" shift 2 ;; --project-name) PROJECT_NAME="$2" shift 2 ;; --domain) DOMAIN_NAME="$2" shift 2 ;; --kubernetes-version) KUBERNETES_VERSION="$2" shift 2 ;; --node-type) NODE_INSTANCE_TYPE="$2" shift 2 ;; --help) echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" echo " --region REGION AWS region (default: us-east-1)" echo " --profile PROFILE AWS profile (default: default)" echo " --cluster-name NAME EKS cluster name (default: erpnext-cluster)" echo " --project-name NAME Project name prefix (default: erpnext)" echo " --domain DOMAIN Domain name (default: erpnext.yourdomain.com)" echo " --kubernetes-version VERSION Kubernetes version (default: 1.28)" echo " --node-type TYPE EC2 instance type for nodes (default: t3.medium)" echo " --help Show this help message" exit 0 ;; *) print_error "Unknown option: $1" exit 1 ;; esac done # Execute deployment steps check_prerequisites check_vpc get_security_groups get_database_endpoints create_efs create_cluster_config create_eks_cluster install_addons create_kubernetes_manifests deploy_to_kubernetes configure_autoscaling display_summary cleanup print_header "Deployment completed successfully!" } # Set trap to cleanup on exit trap cleanup EXIT # Execute main function with all arguments main "$@"