From 8894bbf56e407cfb3feaa2d398207409797bdc89 Mon Sep 17 00:00:00 2001 From: Brian Tan Seng Date: Fri, 22 Aug 2025 19:15:03 +0800 Subject: [PATCH] =?UTF-8?q?=E2=8F=BA=20Perfect!=20I=20have=20successfully?= =?UTF-8?q?=20created=20the=20complete=20AWS=20equivalent=20of=20the=20GCP?= =?UTF-8?q?=20managed=20services=20=20=20deployment=20documentation.=20Her?= =?UTF-8?q?e's=20a=20summary=20of=20what=20was=20created:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 📁 AWS Managed Services Documentation Structure Main Documentation Files: 1. README.md - Comprehensive overview and decision guide 2. 00-prerequisites-managed.md - AWS setup, VPC, security, and managed services 3. 01-ecs-managed-deployment.md - Amazon ECS with Fargate deployment 4. 02-eks-managed-deployment.md - Amazon EKS deployment 5. 03-production-managed-setup.md - Production hardening and advanced features Kubernetes Manifests (kubernetes-manifests/): - namespace.yaml - Namespace with resource quotas and network policies - storage.yaml - EFS and EBS storage classes and PVCs - configmap.yaml - Application configuration and Nginx config - secrets.yaml - External Secrets Operator integration with AWS Secrets Manager - erpnext-backend.yaml - Backend deployment with RDS connectivity - erpnext-frontend.yaml - Frontend deployment with load balancing - erpnext-workers.yaml - Worker deployments for different queues - ingress.yaml - AWS Load Balancer Controller ingress configuration - jobs.yaml - Site creation and backup automation jobs Deployment Scripts (scripts/): - deploy-ecs.sh - Automated ECS deployment script - deploy-eks.sh - Automated EKS deployment script 🔄 Key AWS Managed Services Used: Instead of GCP → AWS Equivalent: - Cloud SQL → Amazon RDS for MySQL - Memorystore → Amazon MemoryDB for Redis - Cloud Run → Amazon ECS with Fargate - GKE → Amazon EKS - Cloud Storage → Amazon S3 - Secret Manager → AWS Secrets Manager - VPC Access Connector → VPC Endpoints/NAT Gateway 🎯 Key Features Included: Production-Ready Features: - ✅ High Availability - Multi-AZ RDS and MemoryDB deployment - ✅ Auto-scaling - ECS Service Auto Scaling and EKS HPA - ✅ Security - VPC isolation, IAM roles, WAF, encryption - ✅ Monitoring - CloudWatch, X-Ray, custom metrics - ✅ Backup & DR - Automated backups, cross-region replication - ✅ Cost Optimization - Reserved instances, spot instances, right-sizing Deployment Options: - 🚀 Amazon ECS with Fargate - Serverless containers, minimal ops - ⚙️ Amazon EKS - Full Kubernetes with advanced features - 🛡️ Production Hardening - WAF, enhanced monitoring, security Automation Scripts: - 📜 One-click deployment scripts for both ECS and EKS - 🔧 Infrastructure as Code approach - 📊 Cost estimation and optimization guidance The documentation provides a complete migration path from GCP to AWS with equivalent managed services, maintaining the same level of reliability and operational efficiency while leveraging AWS-native services and best practices. --- .../aws-managed/00-prerequisites-managed.md | 705 ++++++++ .../aws-managed/01-ecs-managed-deployment.md | 1119 +++++++++++++ .../aws-managed/02-eks-managed-deployment.md | 1467 +++++++++++++++++ .../03-production-managed-setup.md | 1442 ++++++++++++++++ .../deployment-guides/aws-managed/README.md | 418 +++++ .../kubernetes-manifests/configmap.yaml | 228 +++ .../kubernetes-manifests/erpnext-backend.yaml | 328 ++++ .../erpnext-frontend.yaml | 247 +++ .../kubernetes-manifests/erpnext-workers.yaml | 542 ++++++ .../kubernetes-manifests/ingress.yaml | 274 +++ .../kubernetes-manifests/jobs.yaml | 436 +++++ .../kubernetes-manifests/namespace.yaml | 92 ++ .../kubernetes-manifests/secrets.yaml | 209 +++ .../kubernetes-manifests/storage.yaml | 101 ++ .../aws-managed/scripts/deploy-ecs.sh | 1182 +++++++++++++ .../aws-managed/scripts/deploy-eks.sh | 1278 ++++++++++++++ 16 files changed, 10068 insertions(+) create mode 100644 documentation/deployment-guides/aws-managed/00-prerequisites-managed.md create mode 100644 documentation/deployment-guides/aws-managed/01-ecs-managed-deployment.md create mode 100644 documentation/deployment-guides/aws-managed/02-eks-managed-deployment.md create mode 100644 documentation/deployment-guides/aws-managed/03-production-managed-setup.md create mode 100644 documentation/deployment-guides/aws-managed/README.md create mode 100644 documentation/deployment-guides/aws-managed/kubernetes-manifests/configmap.yaml create mode 100644 documentation/deployment-guides/aws-managed/kubernetes-manifests/erpnext-backend.yaml create mode 100644 documentation/deployment-guides/aws-managed/kubernetes-manifests/erpnext-frontend.yaml create mode 100644 documentation/deployment-guides/aws-managed/kubernetes-manifests/erpnext-workers.yaml create mode 100644 documentation/deployment-guides/aws-managed/kubernetes-manifests/ingress.yaml create mode 100644 documentation/deployment-guides/aws-managed/kubernetes-manifests/jobs.yaml create mode 100644 documentation/deployment-guides/aws-managed/kubernetes-manifests/namespace.yaml create mode 100644 documentation/deployment-guides/aws-managed/kubernetes-manifests/secrets.yaml create mode 100644 documentation/deployment-guides/aws-managed/kubernetes-manifests/storage.yaml create mode 100755 documentation/deployment-guides/aws-managed/scripts/deploy-ecs.sh create mode 100755 documentation/deployment-guides/aws-managed/scripts/deploy-eks.sh diff --git a/documentation/deployment-guides/aws-managed/00-prerequisites-managed.md b/documentation/deployment-guides/aws-managed/00-prerequisites-managed.md new file mode 100644 index 0000000..eb315fd --- /dev/null +++ b/documentation/deployment-guides/aws-managed/00-prerequisites-managed.md @@ -0,0 +1,705 @@ +# AWS Prerequisites for ERPNext with Managed Services + +## Overview + +This guide covers the prerequisites and initial setup required for deploying ERPNext on Amazon Web Services (AWS) using managed database services: Amazon RDS for MySQL and Amazon MemoryDB for Redis. This approach provides better reliability, security, and operational efficiency compared to self-hosted databases. + +## 🔧 Required Tools + +### 1. AWS CLI +```bash +# Install AWS CLI v2 +curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" +unzip awscliv2.zip +sudo ./aws/install + +# Configure AWS CLI +aws configure +# Enter your Access Key ID, Secret Access Key, Default region, and output format +``` + +### 2. kubectl (Kubernetes CLI) - For EKS Option +```bash +# Install kubectl +curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" +sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl + +# Verify installation +kubectl version --client +``` + +### 3. eksctl (EKS CLI) - For EKS Option +```bash +# Install eksctl +curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp +sudo mv /tmp/eksctl /usr/local/bin + +# Verify installation +eksctl version +``` + +### 4. Docker (for local testing and ECS) +```bash +# Install Docker +curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh + +# Enable Docker BuildKit +export DOCKER_BUILDKIT=1 +``` + +### 5. AWS ECS CLI - For ECS Option +```bash +# Install ECS CLI +sudo curl -Lo /usr/local/bin/ecs-cli https://amazon-ecs-cli.s3.amazonaws.com/ecs-cli-linux-amd64-latest +sudo chmod +x /usr/local/bin/ecs-cli + +# Verify installation +ecs-cli --version +``` + +### 6. Helm (for EKS Kubernetes package management) +```bash +# Install Helm +curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash + +# Verify installation +helm version +``` + +## 🏗️ AWS Account Setup + +### 1. Create or Configure AWS Account +```bash +# Verify AWS account and permissions +aws sts get-caller-identity + +# Set default region +export AWS_DEFAULT_REGION=us-east-1 +aws configure set default.region us-east-1 + +# Verify configuration +aws configure list +``` + +### 2. Enable Required AWS Services +```bash +# Verify access to required services +aws ec2 describe-regions --output table +aws rds describe-db-instances --output table +aws elasticache describe-cache-clusters --output table +aws ecs list-clusters --output table +aws eks list-clusters --output table +``` + +### 3. Create IAM Policies and Roles +```bash +# Create IAM policy for ERPNext managed services +cat > erpnext-managed-policy.json < ecs-task-role-trust.json < ecs-task-execution-role-trust.json < ecs-secrets-policy.json < erpnext-backend-task.json < erpnext-frontend-task.json < erpnext-worker-task.json < erpnext-scheduler-task.json < erpnext-create-site-task.json < backup-plan.json < backup-role-trust.json < backup-selection.json < erpnext-eks-cluster.yaml < efs-storageclass.yaml < erpnext-efs-pv.yaml < ebs-gp3-storageclass.yaml < aws-secretstore.yaml < erpnext-external-secrets.yaml < erpnext-configmap.yaml < erpnext-pvcs.yaml < erpnext-backend.yaml < erpnext-frontend.yaml < erpnext-workers.yaml < erpnext-ingress.yaml < erpnext-create-site-job.yaml < erpnext-hpa.yaml < erpnext-backup-cronjob.yaml < waf-web-acl.json < rds-monitoring-role-trust.json < vpc-flowlogs-role-trust.json < vpc-flowlogs-policy.json < erpnext-dashboard.json < xray-daemon-container.json < xray-permissions.json < replication-config.json < s3-replication-role-trust.json < snapshot-management.py <<'EOF' +import boto3 +import json +from datetime import datetime, timedelta + +def lambda_handler(event, context): + rds = boto3.client('rds') + + # Create manual snapshot + snapshot_id = f"erpnext-manual-{datetime.now().strftime('%Y%m%d-%H%M%S')}" + + try: + response = rds.create_db_snapshot( + DBSnapshotIdentifier=snapshot_id, + DBInstanceIdentifier='erpnext-db', + Tags=[ + {'Key': 'Application', 'Value': 'ERPNext'}, + {'Key': 'Type', 'Value': 'manual'}, + {'Key': 'CreatedBy', 'Value': 'lambda-automation'} + ] + ) + + # Delete old manual snapshots (keep last 7) + snapshots = rds.describe_db_snapshots( + DBInstanceIdentifier='erpnext-db', + SnapshotType='manual' + ) + + manual_snapshots = [s for s in snapshots['DBSnapshots'] + if s['DBSnapshotIdentifier'].startswith('erpnext-manual-')] + + # Sort by creation time and delete old ones + manual_snapshots.sort(key=lambda x: x['SnapshotCreateTime']) + + if len(manual_snapshots) > 7: + for snapshot in manual_snapshots[:-7]: + rds.delete_db_snapshot( + DBSnapshotIdentifier=snapshot['DBSnapshotIdentifier'] + ) + print(f"Deleted old snapshot: {snapshot['DBSnapshotIdentifier']}") + + return { + 'statusCode': 200, + 'body': json.dumps(f'Snapshot created: {snapshot_id}') + } + + except Exception as e: + print(f"Error: {str(e)}") + return { + 'statusCode': 500, + 'body': json.dumps(f'Error creating snapshot: {str(e)}') + } +EOF + +# Create Lambda deployment package +zip snapshot-management.zip snapshot-management.py + +# Create Lambda function +aws lambda create-function \ + --function-name erpnext-snapshot-management \ + --runtime python3.9 \ + --role arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):role/ERPNextSnapshotLambdaRole \ + --handler snapshot-management.lambda_handler \ + --zip-file fileb://snapshot-management.zip \ + --description "Automated RDS snapshot management for ERPNext" \ + --timeout 300 \ + --tags Application=ERPNext,Purpose=backup-automation + +# Create EventBridge rule for daily execution +aws events put-rule \ + --name erpnext-daily-snapshot \ + --schedule-expression "cron(0 6 * * ? *)" \ + --description "Daily ERPNext database snapshot" \ + --state ENABLED + +# Add Lambda permission for EventBridge +aws lambda add-permission \ + --function-name erpnext-snapshot-management \ + --statement-id allow-eventbridge \ + --action lambda:InvokeFunction \ + --principal events.amazonaws.com \ + --source-arn arn:aws:events:us-east-1:$(aws sts get-caller-identity --query Account --output text):rule/erpnext-daily-snapshot + +# Create target for EventBridge rule +aws events put-targets \ + --rule erpnext-daily-snapshot \ + --targets "Id"="1","Arn"="arn:aws:lambda:us-east-1:$(aws sts get-caller-identity --query Account --output text):function:erpnext-snapshot-management" +``` + +### 3. Disaster Recovery Testing Automation +```bash +# Create Lambda function for DR testing +cat > dr-test.py <<'EOF' +import boto3 +import json +from datetime import datetime + +def lambda_handler(event, context): + rds = boto3.client('rds') + ecs = boto3.client('ecs') + + try: + # Get latest snapshot + snapshots = rds.describe_db_snapshots( + DBInstanceIdentifier='erpnext-db', + SnapshotType='automated' + ) + + latest_snapshot = max(snapshots['DBSnapshots'], + key=lambda x: x['SnapshotCreateTime']) + + # Create test instance from snapshot + test_instance_id = f"erpnext-dr-test-{datetime.now().strftime('%Y%m%d%H%M%S')}" + + response = rds.restore_db_instance_from_db_snapshot( + DBInstanceIdentifier=test_instance_id, + DBSnapshotIdentifier=latest_snapshot['DBSnapshotIdentifier'], + DBInstanceClass='db.t3.small', + VpcSecurityGroupIds=[ + 'sg-xxxxxxxxx' # Replace with your security group + ], + DBSubnetGroupName='erpnext-db-subnet-group', + Tags=[ + {'Key': 'Application', 'Value': 'ERPNext'}, + {'Key': 'Purpose', 'Value': 'DR-Test'}, + {'Key': 'DeleteAfter', 'Value': datetime.now().strftime('%Y-%m-%d')} + ] + ) + + # Schedule cleanup in 4 hours + # Implementation depends on your cleanup strategy + + return { + 'statusCode': 200, + 'body': json.dumps(f'DR test instance created: {test_instance_id}') + } + + except Exception as e: + return { + 'statusCode': 500, + 'body': json.dumps(f'DR test failed: {str(e)}') + } +EOF + +# Create DR test Lambda function +zip dr-test.zip dr-test.py + +aws lambda create-function \ + --function-name erpnext-dr-test \ + --runtime python3.9 \ + --role arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):role/ERPNextDRTestLambdaRole \ + --handler dr-test.lambda_handler \ + --zip-file fileb://dr-test.zip \ + --description "Automated DR testing for ERPNext" \ + --timeout 300 + +# Schedule monthly DR tests +aws events put-rule \ + --name erpnext-monthly-dr-test \ + --schedule-expression "cron(0 10 1 * ? *)" \ + --description "Monthly ERPNext DR test" \ + --state ENABLED + +aws events put-targets \ + --rule erpnext-monthly-dr-test \ + --targets "Id"="1","Arn"="arn:aws:lambda:us-east-1:$(aws sts get-caller-identity --query Account --output text):function:erpnext-dr-test" +``` + +## 🚀 Performance Optimization + +### 1. RDS Performance Tuning +```bash +# Create custom parameter group for MySQL optimization +aws rds create-db-parameter-group \ + --db-parameter-group-name erpnext-mysql8-params \ + --db-parameter-group-family mysql8.0 \ + --description "Optimized parameters for ERPNext MySQL 8.0" + +# Set optimized parameters +aws rds modify-db-parameter-group \ + --db-parameter-group-name erpnext-mysql8-params \ + --parameters \ + "ParameterName=innodb_buffer_pool_size,ParameterValue={DBInstanceClassMemory*3/4},ApplyMethod=pending-reboot" \ + "ParameterName=innodb_log_file_size,ParameterValue=268435456,ApplyMethod=pending-reboot" \ + "ParameterName=innodb_flush_log_at_trx_commit,ParameterValue=2,ApplyMethod=immediate" \ + "ParameterName=innodb_io_capacity,ParameterValue=2000,ApplyMethod=immediate" \ + "ParameterName=innodb_read_io_threads,ParameterValue=8,ApplyMethod=pending-reboot" \ + "ParameterName=innodb_write_io_threads,ParameterValue=8,ApplyMethod=pending-reboot" \ + "ParameterName=max_connections,ParameterValue=200,ApplyMethod=immediate" \ + "ParameterName=query_cache_size,ParameterValue=67108864,ApplyMethod=pending-reboot" \ + "ParameterName=query_cache_type,ParameterValue=1,ApplyMethod=pending-reboot" \ + "ParameterName=slow_query_log,ParameterValue=1,ApplyMethod=immediate" \ + "ParameterName=long_query_time,ParameterValue=2,ApplyMethod=immediate" + +# Apply parameter group to RDS instance +aws rds modify-db-instance \ + --db-instance-identifier erpnext-db \ + --db-parameter-group-name erpnext-mysql8-params \ + --apply-immediately + +# Upgrade to larger instance class for production +aws rds modify-db-instance \ + --db-instance-identifier erpnext-db \ + --db-instance-class db.r5.xlarge \ + --allocated-storage 500 \ + --storage-type gp3 \ + --iops 3000 \ + --apply-immediately +``` + +### 2. MemoryDB Performance Optimization +```bash +# Scale MemoryDB cluster for production workload +aws memorydb update-cluster \ + --cluster-name erpnext-redis \ + --node-type db.r6g.large \ + --num-shards 2 \ + --num-replicas-per-shard 1 + +# Configure Redis parameters for ERPNext +aws memorydb create-parameter-group \ + --parameter-group-name erpnext-redis-params \ + --family memorydb_redis6 \ + --description "Optimized parameters for ERPNext Redis" + +aws memorydb update-parameter-group \ + --parameter-group-name erpnext-redis-params \ + --parameter-name-values \ + "ParameterName=maxmemory-policy,ParameterValue=allkeys-lru" \ + "ParameterName=timeout,ParameterValue=300" \ + "ParameterName=tcp-keepalive,ParameterValue=60" \ + "ParameterName=maxclients,ParameterValue=20000" + +# Apply parameter group to cluster +aws memorydb update-cluster \ + --cluster-name erpnext-redis \ + --parameter-group-name erpnext-redis-params +``` + +### 3. ECS/EKS Performance Optimization +```bash +# For ECS: Update task definitions with performance optimizations +cat > erpnext-backend-optimized-task.json < deploy-blue-green.sh <<'EOF' +#!/bin/bash +set -e + +APPLICATION_NAME="ERPNextECS" +DEPLOYMENT_GROUP="ERPNextBlueGreen" +NEW_IMAGE="$1" + +if [ -z "$NEW_IMAGE" ]; then + echo "Usage: $0 " + exit 1 +fi + +echo "Starting blue-green deployment with image: $NEW_IMAGE" + +# Create new task definition with updated image +NEW_TASK_DEF=$(aws ecs describe-task-definition \ + --task-definition erpnext-backend \ + --query 'taskDefinition' | \ + jq --arg IMAGE "$NEW_IMAGE" '.containerDefinitions[0].image = $IMAGE' | \ + jq 'del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .placementConstraints, .compatibilities, .registeredAt, .registeredBy)') + +# Register new task definition +NEW_REVISION=$(echo $NEW_TASK_DEF | aws ecs register-task-definition --cli-input-json file:///dev/stdin --query 'taskDefinition.revision') + +echo "New task definition revision: $NEW_REVISION" + +# Create CodeDeploy deployment +aws deploy create-deployment \ + --application-name $APPLICATION_NAME \ + --deployment-group-name $DEPLOYMENT_GROUP \ + --revision '{ + "revisionType": "S3", + "s3Location": { + "bucket": "your-deployment-bucket", + "key": "appspec.yaml", + "bundleType": "YAML" + } + }' + +echo "Blue-green deployment initiated" +EOF + +chmod +x deploy-blue-green.sh +``` + +### 2. Canary Deployment for EKS +```bash +# Install Argo Rollouts for advanced deployment strategies +kubectl create namespace argo-rollouts +kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml + +# Create Rollout resource for canary deployments +cat > erpnext-rollout.yaml <= 0.95 + failureLimit: 3 + provider: + prometheus: + address: http://prometheus.monitoring.svc.cluster.local:9090 + query: | + sum(rate( + nginx_ingress_controller_requests{service="{{args.service-name}}",status!~"5.*"}[2m] + )) / + sum(rate( + nginx_ingress_controller_requests{service="{{args.service-name}}"}[2m] + )) +EOF + +kubectl apply -f erpnext-rollout.yaml +``` + +### 3. Automated Scaling Based on Custom Metrics +```bash +# Create Lambda function for custom scaling logic +cat > custom-scaler.py <<'EOF' +import boto3 +import json +import os + +def lambda_handler(event, context): + cloudwatch = boto3.client('cloudwatch') + ecs = boto3.client('ecs') + + cluster_name = os.environ['CLUSTER_NAME'] + service_name = os.environ['SERVICE_NAME'] + + # Get queue depth from CloudWatch + response = cloudwatch.get_metric_statistics( + Namespace='AWS/MemoryDB', + MetricName='CommandLatency', + Dimensions=[ + { + 'Name': 'ClusterName', + 'Value': 'erpnext-redis' + }, + { + 'Name': 'Command', + 'Value': 'llen' + } + ], + StartTime=datetime.utcnow() - timedelta(minutes=5), + EndTime=datetime.utcnow(), + Period=300, + Statistics=['Average'] + ) + + if response['Datapoints']: + avg_latency = response['Datapoints'][-1]['Average'] + + # Scale based on latency + if avg_latency > 100: # milliseconds + target_count = min(10, int(avg_latency / 50)) + else: + target_count = 2 + + # Update ECS service + ecs.update_service( + cluster=cluster_name, + service=service_name, + desiredCount=target_count + ) + + return { + 'statusCode': 200, + 'body': json.dumps(f'Scaled {service_name} to {target_count} tasks') + } + + return { + 'statusCode': 200, + 'body': json.dumps('No scaling action needed') + } +EOF + +# Deploy custom scaler +zip custom-scaler.zip custom-scaler.py + +aws lambda create-function \ + --function-name erpnext-custom-scaler \ + --runtime python3.9 \ + --role arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):role/ERPNextCustomScalerRole \ + --handler custom-scaler.lambda_handler \ + --zip-file fileb://custom-scaler.zip \ + --environment Variables='{CLUSTER_NAME=erpnext-cluster,SERVICE_NAME=erpnext-worker}' \ + --timeout 60 + +# Schedule every 5 minutes +aws events put-rule \ + --name erpnext-custom-scaling \ + --schedule-expression "rate(5 minutes)" \ + --state ENABLED + +aws events put-targets \ + --rule erpnext-custom-scaling \ + --targets "Id"="1","Arn"="arn:aws:lambda:us-east-1:$(aws sts get-caller-identity --query Account --output text):function:erpnext-custom-scaler" +``` + +## 🔍 Compliance and Governance + +### 1. AWS Config for Compliance Monitoring +```bash +# Enable AWS Config +aws configservice put-configuration-recorder \ + --configuration-recorder name=default,roleARN=arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):role/aws-config-role \ + --recording-group allSupported=true,includeGlobalResourceTypes=true + +aws configservice put-delivery-channel \ + --delivery-channel name=default,s3BucketName=config-bucket-$(aws sts get-caller-identity --query Account --output text) + +# Start configuration recorder +aws configservice start-configuration-recorder --configuration-recorder-name default + +# Add compliance rules +aws configservice put-config-rule \ + --config-rule '{ + "ConfigRuleName": "rds-encryption-enabled", + "Source": { + "Owner": "AWS", + "SourceIdentifier": "RDS_STORAGE_ENCRYPTED" + } + }' + +aws configservice put-config-rule \ + --config-rule '{ + "ConfigRuleName": "efs-encrypted", + "Source": { + "Owner": "AWS", + "SourceIdentifier": "EFS_ENCRYPTED_CHECK" + } + }' +``` + +### 2. Security Hub for Centralized Security Findings +```bash +# Enable Security Hub +aws securityhub enable-security-hub \ + --enable-default-standards + +# Enable specific standards +aws securityhub batch-enable-standards \ + --standards-subscription-requests StandardsArn=arn:aws:securityhub:us-east-1::standard/aws-foundational-security-standard/v/1.0.0,StandardsArn=arn:aws:securityhub:us-east-1::standard/cis-aws-foundations-benchmark/v/1.2.0 + +# Create custom insights +aws securityhub create-insight \ + --name "ERPNext High Severity Findings" \ + --filters '{ + "ProductArn": [{"Value": "arn:aws:securityhub:us-east-1::product/aws/securityhub", "Comparison": "EQUALS"}], + "SeverityLabel": [{"Value": "HIGH", "Comparison": "EQUALS"}, {"Value": "CRITICAL", "Comparison": "EQUALS"}], + "ResourceTags": [{"Key": "Application", "Value": "ERPNext", "Comparison": "EQUALS"}] + }' \ + --group-by-attribute "ResourceId" +``` + +### 3. AWS Systems Manager for Patch Management +```bash +# Create patch baseline for ECS instances +aws ssm create-patch-baseline \ + --name "ERPNext-Amazon-Linux-Baseline" \ + --operating-system AMAZON_LINUX_2 \ + --approval-rules '{ + "PatchRules": [ + { + "PatchFilterGroup": { + "PatchFilters": [ + { + "Key": "CLASSIFICATION", + "Values": ["Security", "Bugfix", "Critical"] + }, + { + "Key": "SEVERITY", + "Values": ["Critical", "Important"] + } + ] + }, + "ApproveAfterDays": 0, + "EnableNonSecurity": false + } + ] + }' \ + --description "Patch baseline for ERPNext infrastructure" + +# Create maintenance window +aws ssm create-maintenance-window \ + --name "ERPNext-Maintenance-Window" \ + --schedule "cron(0 2 ? * SUN *)" \ + --duration 4 \ + --cutoff 1 \ + --allow-unassociated-targets \ + --description "Weekly maintenance window for ERPNext infrastructure" +``` + +## 📈 Advanced Performance Monitoring + +### 1. Custom CloudWatch Metrics for Business KPIs +```bash +# Create custom metric for ERPNext-specific metrics +aws logs put-metric-filter \ + --log-group-name "/aws/ecs/erpnext-backend" \ + --filter-name "ERPNext-User-Logins" \ + --filter-pattern "[timestamp, request_id, level=\"INFO\", message=\"User*logged*in*\"]" \ + --metric-transformations \ + metricName=UserLogins,metricNamespace=ERPNext/Business,metricValue=1,defaultValue=0 + +aws logs put-metric-filter \ + --log-group-name "/aws/ecs/erpnext-backend" \ + --filter-name "ERPNext-Sales-Orders" \ + --filter-pattern "[timestamp, request_id, level=\"INFO\", message=\"*Sales Order*created*\"]" \ + --metric-transformations \ + metricName=SalesOrdersCreated,metricNamespace=ERPNext/Business,metricValue=1,defaultValue=0 + +aws logs put-metric-filter \ + --log-group-name "/aws/ecs/erpnext-backend" \ + --filter-name "ERPNext-Payment-Errors" \ + --filter-pattern "[timestamp, request_id, level=\"ERROR\", message=\"*payment*failed*\"]" \ + --metric-transformations \ + metricName=PaymentErrors,metricNamespace=ERPNext/Business,metricValue=1,defaultValue=0 +``` + +### 2. Integration with Prometheus and Grafana (for EKS) +```bash +# Install kube-prometheus-stack using Helm +helm repo add prometheus-community https://prometheus-community.github.io/helm-charts +helm repo update + +helm install prometheus-stack prometheus-community/kube-prometheus-stack \ + --namespace monitoring \ + --create-namespace \ + --values - < erpnext-servicemonitor.yaml < security-updater.py <<'EOF' +import boto3 +import json + +def lambda_handler(event, context): + ecs = boto3.client('ecs') + ecr = boto3.client('ecr') + + # Get latest image from ECR + response = ecr.describe_images( + repositoryName='erpnext/backend', + imageIds=[{'imageTag': 'latest-security'}] + ) + + if response['imageDetails']: + latest_image = f"{response['imageDetails'][0]['registryId']}.dkr.ecr.us-east-1.amazonaws.com/erpnext/backend:latest-security" + + # Update ECS service + ecs.update_service( + cluster='erpnext-cluster', + service='erpnext-backend', + forceNewDeployment=True, + taskDefinition='erpnext-backend:LATEST' + ) + + return { + 'statusCode': 200, + 'body': json.dumps('Security update deployed successfully') + } + + return { + 'statusCode': 200, + 'body': json.dumps('No security updates available') + } +EOF + +# Schedule weekly security updates +aws events put-rule \ + --name erpnext-security-updates \ + --schedule-expression "cron(0 4 ? * MON *)" \ + --description "Weekly security updates for ERPNext" \ + --state ENABLED +``` + +### 2. Resource Cleanup and Cost Optimization +```bash +# Create cleanup Lambda function +cat > resource-cleanup.py <<'EOF' +import boto3 +from datetime import datetime, timedelta + +def lambda_handler(event, context): + rds = boto3.client('rds') + logs = boto3.client('logs') + s3 = boto3.client('s3') + + cleanup_results = [] + + # Clean up old RDS snapshots (keep last 30) + snapshots = rds.describe_db_snapshots( + DBInstanceIdentifier='erpnext-db', + SnapshotType='manual' + ) + + sorted_snapshots = sorted(snapshots['DBSnapshots'], + key=lambda x: x['SnapshotCreateTime']) + + if len(sorted_snapshots) > 30: + for snapshot in sorted_snapshots[:-30]: + rds.delete_db_snapshot( + DBSnapshotIdentifier=snapshot['DBSnapshotIdentifier'] + ) + cleanup_results.append(f"Deleted snapshot: {snapshot['DBSnapshotIdentifier']}") + + # Clean up old CloudWatch logs (older than 30 days) + cutoff_date = datetime.now() - timedelta(days=30) + + log_groups = logs.describe_log_groups( + logGroupNamePrefix='/aws/ecs/erpnext' + ) + + for log_group in log_groups['logGroups']: + logs.put_retention_policy( + logGroupName=log_group['logGroupName'], + retentionInDays=30 + ) + cleanup_results.append(f"Set retention for: {log_group['logGroupName']}") + + # Clean up old S3 backup objects (older than 90 days) + bucket_name = f"erpnext-backups-{boto3.client('sts').get_caller_identity()['Account']}" + + response = s3.list_objects_v2( + Bucket=bucket_name, + Prefix='backups/' + ) + + for obj in response.get('Contents', []): + if obj['LastModified'].replace(tzinfo=None) < cutoff_date: + s3.delete_object( + Bucket=bucket_name, + Key=obj['Key'] + ) + cleanup_results.append(f"Deleted S3 object: {obj['Key']}") + + return { + 'statusCode': 200, + 'body': json.dumps({ + 'message': 'Cleanup completed', + 'results': cleanup_results + }) + } +EOF + +# Schedule monthly cleanup +aws events put-rule \ + --name erpnext-monthly-cleanup \ + --schedule-expression "cron(0 6 1 * ? *)" \ + --description "Monthly resource cleanup for ERPNext" \ + --state ENABLED +``` + +This production setup guide provides comprehensive coverage of security hardening, advanced monitoring, disaster recovery, performance optimization, and operational procedures for ERPNext running on AWS managed services. The configuration supports both ECS and EKS deployments with enterprise-grade reliability and security. + +## ➡️ Next Steps + +1. **Security Audit**: Conduct regular security assessments using AWS Security Hub +2. **Performance Baseline**: Establish performance benchmarks using CloudWatch and custom metrics +3. **Disaster Recovery Testing**: Schedule and automate regular DR drills +4. **Cost Optimization**: Monthly cost reviews using AWS Cost Explorer and Trusted Advisor +5. **Compliance Monitoring**: Regular compliance checks using AWS Config and Security Hub +6. **Documentation Updates**: Keep operational runbooks current with infrastructure changes + +--- + +**⚠️ Important**: Regular reviews and updates of these configurations are essential for maintaining security, performance, and cost-effectiveness in production environments. Monitor AWS Service Health Dashboard for service updates and security advisories. \ No newline at end of file diff --git a/documentation/deployment-guides/aws-managed/README.md b/documentation/deployment-guides/aws-managed/README.md new file mode 100644 index 0000000..f484300 --- /dev/null +++ b/documentation/deployment-guides/aws-managed/README.md @@ -0,0 +1,418 @@ +# ERPNext AWS Deployment with Managed Services + +## Overview + +This directory contains comprehensive guides and resources for deploying ERPNext on Amazon Web Services (AWS) using **managed database services**: Amazon RDS for MySQL and Amazon MemoryDB for Redis. This approach provides better reliability, security, and operational efficiency compared to self-hosted databases. + +## 🏗️ Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Amazon Web Services │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ Amazon ECS │ │ Amazon EKS │ │ +│ │ (Fargate) │ │ (Kubernetes) │ │ +│ │ │ │ │ │ +│ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ +│ │ │ Frontend │ │ │ │ Pods │ │ │ +│ │ │ Backend │ │ │ │ - Frontend │ │ │ +│ │ │ Workers │ │ │ │ - Backend │ │ │ +│ │ │ Scheduler │ │ │ │ - Workers │ │ │ +│ │ └─────────────┘ │ │ │ - Scheduler │ │ │ +│ └─────────────────┘ │ └─────────────┘ │ │ +│ └─────────────────┘ │ +│ │ │ +│ ┌─────────────────────────────┼─────────────────────────────┐ │ +│ │ Managed Services │ │ │ +│ │ │ │ │ +│ │ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐ │ │ +│ │ │ Amazon RDS │ │ MemoryDB │ │ Amazon S3 │ │ │ +│ │ │ (MySQL) │ │ (Redis) │ │ (Files) │ │ │ +│ │ └──────────────┘ └─────────────┘ └──────────────┘ │ │ +│ └─────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## 📁 Directory Structure + +``` +aws-managed/ +├── README.md # This file +├── 00-prerequisites-managed.md # Prerequisites for managed services +├── 01-ecs-managed-deployment.md # ECS with managed databases +├── 02-eks-managed-deployment.md # EKS with managed databases +├── 03-production-managed-setup.md # Production hardening +├── kubernetes-manifests/ # K8s manifests for managed services +│ ├── namespace.yaml # Namespace with security policies +│ ├── storage.yaml # EFS and EBS storage classes +│ ├── configmap.yaml # Config for managed services +│ ├── secrets.yaml # External Secrets integration +│ ├── erpnext-backend.yaml # Backend with RDS connection +│ ├── erpnext-frontend.yaml # Optimized frontend +│ ├── erpnext-workers.yaml # Workers with managed DB +│ ├── ingress.yaml # ALB ingress controller +│ └── jobs.yaml # Site creation and backup jobs +└── scripts/ # Automation scripts + ├── deploy-ecs.sh # ECS deployment script + └── deploy-eks.sh # EKS deployment script +``` + +## 🚀 Quick Start + +### Option 1: Amazon ECS with Fargate (Recommended for Simplicity) + +```bash +# 1. Complete prerequisites +cd aws-managed/ +# Follow 00-prerequisites-managed.md + +# 2. Deploy to ECS +cd scripts/ +export AWS_REGION="us-east-1" +export PROJECT_NAME="erpnext" +export DOMAIN_NAME="erpnext.yourdomain.com" +./deploy-ecs.sh --project-name $PROJECT_NAME --domain $DOMAIN_NAME +``` + +### Option 2: Amazon EKS (Recommended for Production) + +```bash +# 1. Complete prerequisites +cd aws-managed/ +# Follow 00-prerequisites-managed.md + +# 2. Deploy to EKS +cd scripts/ +export AWS_REGION="us-east-1" +export PROJECT_NAME="erpnext" +export DOMAIN_NAME="erpnext.yourdomain.com" +./deploy-eks.sh --project-name $PROJECT_NAME --domain $DOMAIN_NAME +``` + +## 🎯 Key Benefits of AWS Managed Services + +### 🛡️ Enhanced Reliability +- **99.95% SLA** for RDS and MemoryDB +- **Multi-AZ deployment** with automatic failover +- **Point-in-time recovery** for databases +- **Automated backups** with cross-region replication + +### 🔧 Operational Efficiency +- **Zero database administration** overhead +- **Automatic security patches** and updates +- **Performance insights** and optimization recommendations +- **Built-in monitoring** with CloudWatch + +### 🔒 Enterprise Security +- **VPC isolation** with private subnets +- **Encryption at rest and in transit** by default +- **IAM integration** for access control +- **AWS WAF** for application protection + +### 💰 Cost Optimization +- **Pay-as-you-scale** pricing model +- **Reserved Instance discounts** available +- **Automatic storage scaling** without downtime +- **Spot instances** for non-critical workloads + +## 📊 Deployment Options Comparison + +| Feature | ECS + Managed DB | EKS + Managed DB | Self-Hosted DB | +|---------|------------------|------------------|-----------------| +| **Scalability** | Auto (Fargate) | Manual/Auto HPA | Manual | +| **Operational Overhead** | Low | Medium | High | +| **Database Reliability** | 99.95% SLA | 99.95% SLA | Depends on setup | +| **Cost (Small)** | ~$250/month | ~$350/month | ~$200/month | +| **Cost (Large)** | ~$500/month | ~$700/month | ~$450/month | +| **Cold Start** | 1-2 seconds | None | None | +| **Customization** | Medium | High | Very High | +| **Kubernetes Native** | No | Yes | Yes | +| **Multi-tenancy** | Limited | Supported | Supported | + +## 🛠️ Managed Services Configuration + +### Amazon RDS (MySQL) +- **Instance Types**: db.t3.micro to db.r5.24xlarge +- **Storage**: 20GB to 64TB, automatic scaling +- **Backup**: Automated daily backups with 35-day retention +- **High Availability**: Multi-AZ deployment with automatic failover +- **Security**: VPC isolation, encryption, IAM database authentication + +### Amazon MemoryDB for Redis +- **Node Types**: db.t4g.micro to db.r6g.16xlarge +- **Features**: Redis 6.x compatibility, persistence, clustering +- **Performance**: Up to 100+ million requests per second +- **Monitoring**: Built-in CloudWatch metrics and alerts + +### Additional AWS Services +- **Amazon S3**: File storage and backups +- **AWS Secrets Manager**: Secure credential management +- **AWS Systems Manager**: Parameter Store for configuration +- **Amazon EFS**: Shared file storage for EKS +- **AWS Lambda**: Automation and maintenance tasks +- **Amazon EventBridge**: Scheduled tasks and triggers + +## 🔧 Advanced Features + +### Auto-scaling Configuration +- **ECS**: Service auto scaling based on CPU/memory +- **EKS**: Horizontal Pod Autoscaler + Cluster Autoscaler +- **Database**: Automatic storage scaling, manual compute scaling +- **Redis**: Manual scaling with zero-downtime + +### Security Hardening +- **Network isolation** with private subnets and security groups +- **IAM roles** for service accounts (IRSA for EKS) +- **AWS WAF** for application-layer protection +- **VPC Flow Logs** for network monitoring +- **AWS Config** for compliance monitoring + +### Monitoring & Observability +- **CloudWatch** for metrics, logs, and alerts +- **AWS X-Ray** for distributed tracing +- **Custom dashboards** for ERPNext-specific metrics +- **Performance Insights** for database monitoring +- **Container Insights** for ECS/EKS monitoring + +### Backup & Disaster Recovery +- **RDS**: Automated backups with point-in-time recovery +- **Application files**: Automated backup to S3 +- **Cross-region replication** for disaster recovery +- **Automated DR testing** with validation +- **Lambda-based backup automation** + +## 💰 Cost Estimation & Optimization + +### Typical Monthly Costs (US-East-1) + +#### Small Deployment (< 50 users) - ECS +``` +RDS (db.t3.medium): $67 +MemoryDB (1 node): $45 +ECS Fargate (2 tasks): $30 +ALB: $22 +EFS: $3 +NAT Gateway: $45 +Total: ~$212/month +``` + +#### Medium Deployment (50-200 users) - EKS +``` +RDS (db.r5.large): $150 +MemoryDB (2 nodes): $90 +EKS Control Plane: $73 +EC2 (3 t3.medium): $100 +ALB: $22 +EFS: $10 +NAT Gateway: $45 +Total: ~$490/month +``` + +#### Large Deployment (200+ users) - EKS +``` +RDS (db.r5.xlarge): $300 +MemoryDB (3 nodes): $135 +EKS Control Plane: $73 +EC2 (6 t3.large): $300 +ALB: $22 +EFS: $25 +NAT Gateway: $90 +Total: ~$945/month +``` + +### Cost Optimization Strategies +1. **Use Reserved Instances** (up to 75% savings for predictable workloads) +2. **Implement Spot Instances** for non-critical worker nodes +3. **Right-size instances** based on CloudWatch metrics +4. **Use S3 Intelligent Tiering** for file storage +5. **Schedule scaling** during off-hours + +## 🚨 Migration Path from Self-Hosted + +### Phase 1: Assessment and Planning (Week 1) +- [ ] Audit current infrastructure and data size +- [ ] Identify custom configurations and dependencies +- [ ] Plan migration windows and rollback procedures +- [ ] Set up AWS managed services in parallel + +### Phase 2: Infrastructure Setup (Week 2) +- [ ] Deploy VPC, subnets, and security groups +- [ ] Create RDS and MemoryDB instances +- [ ] Set up ECS/EKS cluster and supporting services +- [ ] Configure monitoring and alerting + +### Phase 3: Data Migration (Week 3) +- [ ] Export data from existing MySQL/Redis +- [ ] Import to RDS/MemoryDB with validation +- [ ] Migrate file storage to S3/EFS +- [ ] Update connection strings and test thoroughly + +### Phase 4: Application Migration (Week 4) +- [ ] Deploy ERPNext with managed services +- [ ] Conduct comprehensive testing +- [ ] DNS cutover to new deployment +- [ ] Monitor performance and optimize + +### Phase 5: Optimization and Cleanup (Week 5) +- [ ] Optimize resource allocation based on metrics +- [ ] Implement cost optimization measures +- [ ] Decommission old infrastructure +- [ ] Update backup and DR procedures + +## 🔍 Troubleshooting Common Issues + +### RDS Connection Issues +```bash +# Test connectivity from ECS/EKS +# For ECS +aws ecs run-task --cluster erpnext-cluster \ + --task-definition erpnext-backend \ + --overrides '{"containerOverrides":[{"name":"erpnext-backend","command":["mysql","-h","RDS_ENDPOINT","-u","admin","-p"]}]}' + +# For EKS +kubectl run mysql-test --rm -i --tty --image=mysql:8.0 -- mysql -h RDS_ENDPOINT -u admin -p +``` + +### MemoryDB Connection Issues +```bash +# Test Redis connectivity +# For EKS +kubectl run redis-test --rm -i --tty --image=redis:alpine -- redis-cli -h REDIS_ENDPOINT ping + +# Check AUTH configuration +aws memorydb describe-clusters --cluster-name erpnext-redis --region us-east-1 +``` + +### Performance Issues +```bash +# Check RDS performance +aws rds describe-db-instances --db-instance-identifier erpnext-db +aws cloudwatch get-metric-statistics --namespace AWS/RDS --metric-name CPUUtilization + +# Monitor MemoryDB metrics +aws cloudwatch get-metric-statistics --namespace AWS/MemoryDB --metric-name CPUUtilization +``` + +### Cost Issues +```bash +# Analyze costs with AWS CLI +aws ce get-cost-and-usage --time-period Start=2024-01-01,End=2024-01-31 \ + --granularity MONTHLY --metrics BlendedCost + +# Get cost recommendations +aws support describe-trusted-advisor-checks --language en +``` + +## 📚 Additional Resources + +### AWS Documentation +- [Amazon RDS User Guide](https://docs.aws.amazon.com/rds/) +- [Amazon MemoryDB User Guide](https://docs.aws.amazon.com/memorydb/) +- [Amazon ECS Developer Guide](https://docs.aws.amazon.com/ecs/) +- [Amazon EKS User Guide](https://docs.aws.amazon.com/eks/) +- [AWS Well-Architected Framework](https://aws.amazon.com/architecture/well-architected/) + +### ERPNext Specific +- [ERPNext Documentation](https://docs.erpnext.com/) +- [Frappe Framework Documentation](https://frappeframework.com/docs) +- [ERPNext GitHub Repository](https://github.com/frappe/erpnext) + +### AWS Tools and SDKs +- [AWS CLI Documentation](https://docs.aws.amazon.com/cli/) +- [eksctl Documentation](https://eksctl.io/) +- [AWS CDK Documentation](https://docs.aws.amazon.com/cdk/) + +### Monitoring & Operations +- [AWS CloudWatch User Guide](https://docs.aws.amazon.com/cloudwatch/) +- [AWS X-Ray Developer Guide](https://docs.aws.amazon.com/xray/) +- [Kubernetes Monitoring Best Practices](https://kubernetes.io/docs/tasks/debug-application-cluster/resource-usage-monitoring/) + +## 🎯 Decision Matrix + +### Choose ECS + Managed Services if: +- ✅ Want minimal operational overhead +- ✅ Team has limited Kubernetes experience +- ✅ Need rapid deployment and scaling +- ✅ Prefer AWS-native container orchestration +- ✅ Want to minimize infrastructure complexity + +### Choose EKS + Managed Services if: +- ✅ Need advanced Kubernetes features +- ✅ Plan to run multiple applications +- ✅ Require fine-grained control over scheduling +- ✅ Have existing Kubernetes expertise +- ✅ Need advanced networking capabilities +- ✅ Want cloud-agnostic deployment patterns + +## 📞 Support & Contributing + +### Getting Help +- **Documentation Issues**: Create issues in the repository +- **AWS Support**: Use AWS Support Center for service issues +- **Community**: ERPNext Community Forum and GitHub Discussions +- **Professional Services**: AWS Professional Services for complex deployments + +### Contributing +- **Documentation improvements**: Submit pull requests +- **Script enhancements**: Share automation improvements +- **Best practices**: Contribute lessons learned from production deployments +- **Cost optimizations**: Share optimization strategies and findings + +### Feedback +We welcome feedback on these deployment guides. Please open an issue or submit a pull request with: +- Improvements to documentation clarity +- Additional troubleshooting scenarios +- Cost optimization techniques +- Security enhancements +- Performance optimization tips + +## ⚡ Quick Commands Reference + +### ECS Operations +```bash +# Check service status +aws ecs describe-services --cluster erpnext-cluster --services erpnext-backend + +# View task logs +aws logs get-log-events --log-group-name /aws/ecs/erpnext-backend + +# Scale service +aws ecs update-service --cluster erpnext-cluster --service erpnext-backend --desired-count 5 +``` + +### EKS Operations +```bash +# Check pod status +kubectl get pods -n erpnext + +# View logs +kubectl logs -f deployment/erpnext-backend -n erpnext + +# Scale deployment +kubectl scale deployment erpnext-backend --replicas=5 -n erpnext +``` + +### Database Operations +```bash +# Create RDS snapshot +aws rds create-db-snapshot --db-instance-identifier erpnext-db --db-snapshot-identifier manual-backup-$(date +%Y%m%d) + +# Monitor MemoryDB +aws memorydb describe-clusters --cluster-name erpnext-redis +``` + +--- + +**⚠️ Important Notes**: +- Managed services incur continuous costs even when applications are idle +- Always test deployments thoroughly in staging before production +- Monitor costs regularly using AWS Cost Explorer +- Keep credentials secure and rotate regularly +- Follow AWS security best practices and compliance requirements +- Review and update security groups and IAM policies regularly + +**🎯 Recommendation**: For most production deployments, EKS with managed services provides the best balance of control, reliability, and operational efficiency, while ECS offers simplicity for teams new to container orchestration. + +**🔄 Maintenance**: These guides are actively maintained. Check for updates regularly and ensure your AWS CLI, kubectl, and other tools are up to date. \ No newline at end of file diff --git a/documentation/deployment-guides/aws-managed/kubernetes-manifests/configmap.yaml b/documentation/deployment-guides/aws-managed/kubernetes-manifests/configmap.yaml new file mode 100644 index 0000000..f103b64 --- /dev/null +++ b/documentation/deployment-guides/aws-managed/kubernetes-manifests/configmap.yaml @@ -0,0 +1,228 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: erpnext-config + namespace: erpnext + labels: + app: erpnext + component: config +data: + # ERPNext Application Configuration + APP_VERSION: "v14" + APP_URL: "erpnext.yourdomain.com" + APP_USER: "Administrator" + APP_DB_PARAM: "db" + DEVELOPER_MODE: "0" + ENABLE_SCHEDULER: "1" + SOCKETIO_PORT: "9000" + + # Database Configuration (AWS RDS) + DB_HOST: "${DB_HOST}" # Replace with actual RDS endpoint + DB_PORT: "3306" + DB_NAME: "erpnext" + DB_USER: "admin" + DB_TIMEOUT: "60" + DB_CHARSET: "utf8mb4" + + # Redis Configuration (AWS MemoryDB) + REDIS_CACHE_URL: "redis://${REDIS_HOST}:6379/0" + REDIS_QUEUE_URL: "redis://${REDIS_HOST}:6379/1" + REDIS_SOCKETIO_URL: "redis://${REDIS_HOST}:6379/2" + + # Performance Configuration + WORKERS: "4" + THREADS: "2" + MAX_REQUESTS: "1000" + MAX_REQUESTS_JITTER: "100" + WORKER_TIMEOUT: "120" + KEEPALIVE: "5" + + # AWS Configuration + AWS_DEFAULT_REGION: "us-east-1" + AWS_S3_BUCKET: "erpnext-files-${ACCOUNT_ID}" + + # Logging Configuration + LOG_LEVEL: "INFO" + STRUCTURED_LOGS: "true" + + # Security Configuration + FORCE_HTTPS: "true" + COOKIE_SECURE: "true" + SESSION_COOKIE_SAMESITE: "Lax" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: erpnext-nginx-config + namespace: erpnext + labels: + app: erpnext + component: frontend +data: + nginx.conf: | + user nginx; + worker_processes auto; + error_log /var/log/nginx/error.log warn; + pid /var/run/nginx.pid; + + events { + worker_connections 1024; + use epoll; + multi_accept on; + } + + http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging format + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + # Performance settings + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + client_max_body_size 50M; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied any; + gzip_comp_level 6; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + + # Security headers + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + upstream backend { + server erpnext-backend:8000; + keepalive 32; + } + + upstream socketio { + server erpnext-backend:9000; + keepalive 32; + } + + server { + listen 8080; + server_name _; + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # Socket.IO + location /socket.io/ { + proxy_pass http://socketio; + 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; + proxy_cache_bypass $http_upgrade; + proxy_buffering off; + } + + # API endpoints + location /api/ { + proxy_pass http://backend; + 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; + proxy_read_timeout 300; + proxy_connect_timeout 300; + } + + # Static assets + location /assets/ { + root /home/frappe/frappe-bench/sites; + expires 1y; + add_header Cache-Control "public, immutable"; + add_header X-Content-Type-Options nosniff; + } + + # Files + location /files/ { + root /home/frappe/frappe-bench/sites; + expires 1y; + add_header Cache-Control "public"; + add_header X-Content-Type-Options nosniff; + } + + # Everything else to backend + location / { + proxy_pass http://backend; + 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; + proxy_read_timeout 300; + proxy_connect_timeout 300; + } + } + } +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: fluent-bit-config + namespace: erpnext + labels: + app: erpnext + component: logging +data: + fluent-bit.conf: | + [SERVICE] + Flush 1 + Log_Level info + Daemon off + Parsers_File parsers.conf + + [INPUT] + Name tail + Path /home/frappe/frappe-bench/logs/*.log + Parser erpnext + Tag erpnext.* + Refresh_Interval 5 + + [OUTPUT] + Name cloudwatch_logs + Match * + region us-east-1 + log_group_name /aws/eks/erpnext + log_stream_prefix erpnext- + auto_create_group true + + parsers.conf: | + [PARSER] + Name erpnext + Format regex + Regex ^(?\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}),(?\d{3}) (?\w+) (?.*)$ + Time_Key timestamp + Time_Format %Y-%m-%d %H:%M:%S \ No newline at end of file diff --git a/documentation/deployment-guides/aws-managed/kubernetes-manifests/erpnext-backend.yaml b/documentation/deployment-guides/aws-managed/kubernetes-manifests/erpnext-backend.yaml new file mode 100644 index 0000000..d62e8b4 --- /dev/null +++ b/documentation/deployment-guides/aws-managed/kubernetes-manifests/erpnext-backend.yaml @@ -0,0 +1,328 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: erpnext-backend + namespace: erpnext + labels: + app: erpnext-backend + component: backend + environment: production + version: v14 + annotations: + deployment.kubernetes.io/revision: "1" + description: "ERPNext backend application server" +spec: + replicas: 3 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + selector: + matchLabels: + app: erpnext-backend + template: + metadata: + labels: + app: erpnext-backend + component: backend + environment: production + version: v14 + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8000" + prometheus.io/path: "/metrics" + spec: + serviceAccountName: erpnext-sa + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + fsGroupChangePolicy: "OnRootMismatch" + initContainers: + - name: wait-for-db + image: busybox:1.35 + imagePullPolicy: IfNotPresent + command: + - sh + - -c + - | + echo 'Waiting for database to be ready...' + until nc -z $DB_HOST $DB_PORT; do + echo 'Waiting for database...' + sleep 10 + done + echo 'Database is ready!' + + echo 'Waiting for Redis to be ready...' + until nc -z $(echo $REDIS_CACHE_URL | cut -d'/' -f3 | cut -d':' -f1) 6379; do + echo 'Waiting for Redis...' + sleep 10 + done + echo 'Redis is ready!' + envFrom: + - configMapRef: + name: erpnext-config + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + containers: + - name: erpnext-backend + image: frappe/erpnext-worker:v14 + imagePullPolicy: Always + ports: + - containerPort: 8000 + name: http + protocol: TCP + - containerPort: 9000 + name: socketio + protocol: TCP + envFrom: + - configMapRef: + name: erpnext-config + env: + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: erpnext-db-secret + key: password + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: erpnext-redis-secret + key: password + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + volumeMounts: + - name: sites-data + mountPath: /home/frappe/frappe-bench/sites + - name: backups-data + mountPath: /home/frappe/frappe-bench/sites/backups + - name: logs-data + mountPath: /home/frappe/frappe-bench/logs + - name: tmp-volume + mountPath: /tmp + resources: + requests: + memory: "1Gi" + cpu: "500m" + ephemeral-storage: "1Gi" + limits: + memory: "2Gi" + cpu: "1000m" + ephemeral-storage: "2Gi" + livenessProbe: + httpGet: + path: /api/method/ping + port: 8000 + scheme: HTTP + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + readinessProbe: + httpGet: + path: /api/method/ping + port: 8000 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + startupProbe: + httpGet: + path: /api/method/ping + port: 8000 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 30 + successThreshold: 1 + lifecycle: + preStop: + exec: + command: + - /bin/sh + - -c + - sleep 10 + # Sidecar container for log collection + - name: fluent-bit + image: fluent/fluent-bit:2.2.0 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 2020 + name: metrics + protocol: TCP + env: + - name: AWS_REGION + value: "us-east-1" + - name: CLUSTER_NAME + value: "erpnext-cluster" + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + volumeMounts: + - name: fluent-bit-config + mountPath: /fluent-bit/etc + - name: logs-data + mountPath: /home/frappe/frappe-bench/logs + readOnly: true + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + volumes: + - name: sites-data + persistentVolumeClaim: + claimName: erpnext-sites-pvc + - name: backups-data + persistentVolumeClaim: + claimName: erpnext-backups-pvc + - name: logs-data + persistentVolumeClaim: + claimName: erpnext-logs-pvc + - name: fluent-bit-config + configMap: + name: fluent-bit-config + - name: tmp-volume + emptyDir: + sizeLimit: 1Gi + nodeSelector: + node-type: worker + kubernetes.io/arch: amd64 + tolerations: + - key: "node.kubernetes.io/not-ready" + operator: "Exists" + effect: "NoExecute" + tolerationSeconds: 300 + - key: "node.kubernetes.io/unreachable" + operator: "Exists" + effect: "NoExecute" + tolerationSeconds: 300 + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - erpnext-backend + topologyKey: kubernetes.io/hostname + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: node-type + operator: In + values: + - worker + terminationGracePeriodSeconds: 30 +--- +apiVersion: v1 +kind: Service +metadata: + name: erpnext-backend + namespace: erpnext + labels: + app: erpnext-backend + component: backend + annotations: + service.beta.kubernetes.io/aws-load-balancer-type: "nlb" + service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http" + prometheus.io/scrape: "true" + prometheus.io/port: "8000" + prometheus.io/path: "/metrics" +spec: + selector: + app: erpnext-backend + ports: + - name: http + port: 8000 + targetPort: 8000 + protocol: TCP + - name: socketio + port: 9000 + targetPort: 9000 + protocol: TCP + type: ClusterIP + sessionAffinity: None +--- +apiVersion: v1 +kind: Service +metadata: + name: erpnext-backend-headless + namespace: erpnext + labels: + app: erpnext-backend + component: backend + service-type: headless +spec: + selector: + app: erpnext-backend + ports: + - name: http + port: 8000 + targetPort: 8000 + protocol: TCP + - name: socketio + port: 9000 + targetPort: 9000 + protocol: TCP + type: ClusterIP + clusterIP: None +--- +# ServiceMonitor for Prometheus monitoring +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: erpnext-backend + namespace: erpnext + labels: + app: erpnext-backend + component: backend + monitoring: prometheus +spec: + selector: + matchLabels: + app: erpnext-backend + endpoints: + - port: http + path: /metrics + interval: 30s + scrapeTimeout: 10s + honorLabels: true + - port: socketio + path: /metrics + interval: 30s + scrapeTimeout: 10s + honorLabels: true \ No newline at end of file diff --git a/documentation/deployment-guides/aws-managed/kubernetes-manifests/erpnext-frontend.yaml b/documentation/deployment-guides/aws-managed/kubernetes-manifests/erpnext-frontend.yaml new file mode 100644 index 0000000..b355f31 --- /dev/null +++ b/documentation/deployment-guides/aws-managed/kubernetes-manifests/erpnext-frontend.yaml @@ -0,0 +1,247 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: erpnext-frontend + namespace: erpnext + labels: + app: erpnext-frontend + component: frontend + environment: production + version: v14 + annotations: + deployment.kubernetes.io/revision: "1" + description: "ERPNext Nginx frontend web server" +spec: + replicas: 2 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + selector: + matchLabels: + app: erpnext-frontend + template: + metadata: + labels: + app: erpnext-frontend + component: frontend + environment: production + version: v14 + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8080" + prometheus.io/path: "/nginx_status" + spec: + serviceAccountName: erpnext-sa + securityContext: + runAsNonRoot: true + runAsUser: 101 # nginx user + runAsGroup: 101 + fsGroup: 1000 + fsGroupChangePolicy: "OnRootMismatch" + initContainers: + - name: wait-for-backend + image: busybox:1.35 + imagePullPolicy: IfNotPresent + command: + - sh + - -c + - | + echo 'Waiting for backend to be ready...' + until nc -z erpnext-backend 8000; do + echo 'Waiting for backend...' + sleep 5 + done + echo 'Backend is ready!' + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + containers: + - name: erpnext-frontend + image: frappe/erpnext-nginx:v14 + imagePullPolicy: Always + ports: + - containerPort: 8080 + name: http + protocol: TCP + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + volumeMounts: + - name: sites-data + mountPath: /home/frappe/frappe-bench/sites + readOnly: true + - name: nginx-config + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + readOnly: true + - name: nginx-cache + mountPath: /var/cache/nginx + - name: nginx-run + mountPath: /var/run + - name: tmp-volume + mountPath: /tmp + resources: + requests: + memory: "256Mi" + cpu: "100m" + ephemeral-storage: "500Mi" + limits: + memory: "512Mi" + cpu: "500m" + ephemeral-storage: "1Gi" + livenessProbe: + httpGet: + path: /health + port: 8080 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + readinessProbe: + httpGet: + path: /health + port: 8080 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + startupProbe: + httpGet: + path: /health + port: 8080 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 12 + successThreshold: 1 + lifecycle: + preStop: + exec: + command: + - /bin/sh + - -c + - sleep 15 + volumes: + - name: sites-data + persistentVolumeClaim: + claimName: erpnext-sites-pvc + - name: nginx-config + configMap: + name: erpnext-nginx-config + items: + - key: nginx.conf + path: nginx.conf + - name: nginx-cache + emptyDir: + sizeLimit: 500Mi + - name: nginx-run + emptyDir: + sizeLimit: 100Mi + - name: tmp-volume + emptyDir: + sizeLimit: 500Mi + nodeSelector: + node-type: worker + kubernetes.io/arch: amd64 + tolerations: + - key: "node.kubernetes.io/not-ready" + operator: "Exists" + effect: "NoExecute" + tolerationSeconds: 300 + - key: "node.kubernetes.io/unreachable" + operator: "Exists" + effect: "NoExecute" + tolerationSeconds: 300 + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - erpnext-frontend + topologyKey: kubernetes.io/hostname + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: node-type + operator: In + values: + - worker + terminationGracePeriodSeconds: 30 +--- +apiVersion: v1 +kind: Service +metadata: + name: erpnext-frontend + namespace: erpnext + labels: + app: erpnext-frontend + component: frontend + annotations: + service.beta.kubernetes.io/aws-load-balancer-type: "nlb" + service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http" + prometheus.io/scrape: "true" + prometheus.io/port: "8080" + prometheus.io/path: "/nginx_status" +spec: + selector: + app: erpnext-frontend + ports: + - name: http + port: 8080 + targetPort: 8080 + protocol: TCP + type: ClusterIP + sessionAffinity: None +--- +# ServiceMonitor for Prometheus monitoring +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: erpnext-frontend + namespace: erpnext + labels: + app: erpnext-frontend + component: frontend + monitoring: prometheus +spec: + selector: + matchLabels: + app: erpnext-frontend + endpoints: + - port: http + path: /nginx_status + interval: 30s + scrapeTimeout: 10s + honorLabels: true \ No newline at end of file diff --git a/documentation/deployment-guides/aws-managed/kubernetes-manifests/erpnext-workers.yaml b/documentation/deployment-guides/aws-managed/kubernetes-manifests/erpnext-workers.yaml new file mode 100644 index 0000000..95fe0c5 --- /dev/null +++ b/documentation/deployment-guides/aws-managed/kubernetes-manifests/erpnext-workers.yaml @@ -0,0 +1,542 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: erpnext-queue-default + namespace: erpnext + labels: + app: erpnext-queue-default + component: worker + queue: default + environment: production + version: v14 + annotations: + deployment.kubernetes.io/revision: "1" + description: "ERPNext default queue worker" +spec: + replicas: 2 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + selector: + matchLabels: + app: erpnext-queue-default + template: + metadata: + labels: + app: erpnext-queue-default + component: worker + queue: default + environment: production + version: v14 + spec: + serviceAccountName: erpnext-sa + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + fsGroupChangePolicy: "OnRootMismatch" + initContainers: + - name: wait-for-backend + image: busybox:1.35 + imagePullPolicy: IfNotPresent + command: + - sh + - -c + - | + echo 'Waiting for backend to be ready...' + until nc -z erpnext-backend 8000; do + echo 'Waiting for backend...' + sleep 10 + done + echo 'Backend is ready!' + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + containers: + - name: queue-worker + image: frappe/erpnext-worker:v14 + imagePullPolicy: Always + command: ["bench", "worker", "--queue", "default"] + envFrom: + - configMapRef: + name: erpnext-config + env: + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: erpnext-db-secret + key: password + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: erpnext-redis-secret + key: password + - name: QUEUE_NAME + value: "default" + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + volumeMounts: + - name: sites-data + mountPath: /home/frappe/frappe-bench/sites + resources: + requests: + memory: "512Mi" + cpu: "250m" + ephemeral-storage: "1Gi" + limits: + memory: "1Gi" + cpu: "500m" + ephemeral-storage: "2Gi" + livenessProbe: + exec: + command: + - pgrep + - -f + - "bench worker" + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 10 + failureThreshold: 3 + readinessProbe: + exec: + command: + - pgrep + - -f + - "bench worker" + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + volumes: + - name: sites-data + persistentVolumeClaim: + claimName: erpnext-sites-pvc + nodeSelector: + node-type: worker + tolerations: + - key: "node.kubernetes.io/not-ready" + operator: "Exists" + effect: "NoExecute" + tolerationSeconds: 300 + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - erpnext-queue-default + topologyKey: kubernetes.io/hostname + terminationGracePeriodSeconds: 30 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: erpnext-queue-long + namespace: erpnext + labels: + app: erpnext-queue-long + component: worker + queue: long + environment: production + version: v14 +spec: + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + selector: + matchLabels: + app: erpnext-queue-long + template: + metadata: + labels: + app: erpnext-queue-long + component: worker + queue: long + environment: production + version: v14 + spec: + serviceAccountName: erpnext-sa + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + fsGroupChangePolicy: "OnRootMismatch" + initContainers: + - name: wait-for-backend + image: busybox:1.35 + imagePullPolicy: IfNotPresent + command: + - sh + - -c + - | + echo 'Waiting for backend to be ready...' + until nc -z erpnext-backend 8000; do + echo 'Waiting for backend...' + sleep 10 + done + echo 'Backend is ready!' + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + containers: + - name: queue-worker + image: frappe/erpnext-worker:v14 + imagePullPolicy: Always + command: ["bench", "worker", "--queue", "long"] + envFrom: + - configMapRef: + name: erpnext-config + env: + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: erpnext-db-secret + key: password + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: erpnext-redis-secret + key: password + - name: QUEUE_NAME + value: "long" + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + volumeMounts: + - name: sites-data + mountPath: /home/frappe/frappe-bench/sites + resources: + requests: + memory: "512Mi" + cpu: "250m" + ephemeral-storage: "1Gi" + limits: + memory: "1Gi" + cpu: "500m" + ephemeral-storage: "2Gi" + livenessProbe: + exec: + command: + - pgrep + - -f + - "bench worker" + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 10 + failureThreshold: 3 + readinessProbe: + exec: + command: + - pgrep + - -f + - "bench worker" + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + volumes: + - name: sites-data + persistentVolumeClaim: + claimName: erpnext-sites-pvc + nodeSelector: + node-type: worker + tolerations: + - key: "node.kubernetes.io/not-ready" + operator: "Exists" + effect: "NoExecute" + tolerationSeconds: 300 + terminationGracePeriodSeconds: 60 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: erpnext-queue-short + namespace: erpnext + labels: + app: erpnext-queue-short + component: worker + queue: short + environment: production + version: v14 +spec: + replicas: 2 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + selector: + matchLabels: + app: erpnext-queue-short + template: + metadata: + labels: + app: erpnext-queue-short + component: worker + queue: short + environment: production + version: v14 + spec: + serviceAccountName: erpnext-sa + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + fsGroupChangePolicy: "OnRootMismatch" + initContainers: + - name: wait-for-backend + image: busybox:1.35 + imagePullPolicy: IfNotPresent + command: + - sh + - -c + - | + echo 'Waiting for backend to be ready...' + until nc -z erpnext-backend 8000; do + echo 'Waiting for backend...' + sleep 10 + done + echo 'Backend is ready!' + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + containers: + - name: queue-worker + image: frappe/erpnext-worker:v14 + imagePullPolicy: Always + command: ["bench", "worker", "--queue", "short"] + envFrom: + - configMapRef: + name: erpnext-config + env: + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: erpnext-db-secret + key: password + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: erpnext-redis-secret + key: password + - name: QUEUE_NAME + value: "short" + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + volumeMounts: + - name: sites-data + mountPath: /home/frappe/frappe-bench/sites + resources: + requests: + memory: "256Mi" + cpu: "100m" + ephemeral-storage: "500Mi" + limits: + memory: "512Mi" + cpu: "250m" + ephemeral-storage: "1Gi" + livenessProbe: + exec: + command: + - pgrep + - -f + - "bench worker" + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 10 + failureThreshold: 3 + readinessProbe: + exec: + command: + - pgrep + - -f + - "bench worker" + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + volumes: + - name: sites-data + persistentVolumeClaim: + claimName: erpnext-sites-pvc + nodeSelector: + node-type: worker + tolerations: + - key: "node.kubernetes.io/not-ready" + operator: "Exists" + effect: "NoExecute" + tolerationSeconds: 300 + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - erpnext-queue-short + topologyKey: kubernetes.io/hostname + terminationGracePeriodSeconds: 30 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: erpnext-scheduler + namespace: erpnext + labels: + app: erpnext-scheduler + component: scheduler + environment: production + version: v14 +spec: + replicas: 1 + strategy: + type: Recreate # Only one scheduler should run at a time + selector: + matchLabels: + app: erpnext-scheduler + template: + metadata: + labels: + app: erpnext-scheduler + component: scheduler + environment: production + version: v14 + spec: + serviceAccountName: erpnext-sa + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + fsGroupChangePolicy: "OnRootMismatch" + initContainers: + - name: wait-for-backend + image: busybox:1.35 + imagePullPolicy: IfNotPresent + command: + - sh + - -c + - | + echo 'Waiting for backend to be ready...' + until nc -z erpnext-backend 8000; do + echo 'Waiting for backend...' + sleep 10 + done + echo 'Backend is ready!' + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + containers: + - name: scheduler + image: frappe/erpnext-worker:v14 + imagePullPolicy: Always + command: ["bench", "schedule"] + envFrom: + - configMapRef: + name: erpnext-config + env: + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: erpnext-db-secret + key: password + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: erpnext-redis-secret + key: password + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + volumeMounts: + - name: sites-data + mountPath: /home/frappe/frappe-bench/sites + resources: + requests: + memory: "256Mi" + cpu: "100m" + ephemeral-storage: "500Mi" + limits: + memory: "512Mi" + cpu: "250m" + ephemeral-storage: "1Gi" + livenessProbe: + exec: + command: + - pgrep + - -f + - "bench schedule" + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 10 + failureThreshold: 3 + readinessProbe: + exec: + command: + - pgrep + - -f + - "bench schedule" + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + volumes: + - name: sites-data + persistentVolumeClaim: + claimName: erpnext-sites-pvc + nodeSelector: + node-type: worker + kubernetes.io/arch: amd64 + tolerations: + - key: "node.kubernetes.io/not-ready" + operator: "Exists" + effect: "NoExecute" + tolerationSeconds: 300 + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: node-type + operator: In + values: + - worker + terminationGracePeriodSeconds: 30 \ No newline at end of file diff --git a/documentation/deployment-guides/aws-managed/kubernetes-manifests/ingress.yaml b/documentation/deployment-guides/aws-managed/kubernetes-manifests/ingress.yaml new file mode 100644 index 0000000..60e5efa --- /dev/null +++ b/documentation/deployment-guides/aws-managed/kubernetes-manifests/ingress.yaml @@ -0,0 +1,274 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: erpnext-ingress + namespace: erpnext + labels: + app: erpnext + component: ingress + environment: production + annotations: + # AWS Load Balancer Controller annotations + kubernetes.io/ingress.class: alb + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/backend-protocol: HTTP + alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]' + alb.ingress.kubernetes.io/ssl-redirect: "443" + + # SSL/TLS Configuration + alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:${ACCOUNT_ID}:certificate/${CERT_ID} + alb.ingress.kubernetes.io/ssl-policy: ELBSecurityPolicy-TLS-1-2-2017-01 + + # Load Balancer Attributes + alb.ingress.kubernetes.io/load-balancer-attributes: | + routing.http2.enabled=true, + idle_timeout.timeout_seconds=60, + access_logs.s3.enabled=true, + access_logs.s3.bucket=erpnext-alb-logs-${ACCOUNT_ID}, + access_logs.s3.prefix=erpnext-alb + + # Health Check Configuration + alb.ingress.kubernetes.io/healthcheck-grace-period-seconds: "60" + alb.ingress.kubernetes.io/healthcheck-interval-seconds: "15" + alb.ingress.kubernetes.io/healthcheck-timeout-seconds: "5" + alb.ingress.kubernetes.io/healthy-threshold-count: "2" + alb.ingress.kubernetes.io/unhealthy-threshold-count: "3" + alb.ingress.kubernetes.io/success-codes: "200,201,202" + + # Security and Performance + alb.ingress.kubernetes.io/security-groups: sg-${SECURITY_GROUP_ID} + alb.ingress.kubernetes.io/subnets: subnet-${PUBLIC_SUBNET_1A},subnet-${PUBLIC_SUBNET_1B} + alb.ingress.kubernetes.io/wafv2-acl-arn: arn:aws:wafv2:us-east-1:${ACCOUNT_ID}:regional/webacl/ERPNextWAF/${WAF_ID} + + # Tags for Cost Allocation + alb.ingress.kubernetes.io/tags: | + Environment=production, + Application=ERPNext, + Team=Infrastructure, + CostCenter=IT, + Project=ERP + + # Additional annotations for performance + nginx.ingress.kubernetes.io/proxy-body-size: "50m" + nginx.ingress.kubernetes.io/proxy-read-timeout: "600" + nginx.ingress.kubernetes.io/proxy-connect-timeout: "600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "600" + nginx.ingress.kubernetes.io/client-body-buffer-size: "1m" + nginx.ingress.kubernetes.io/proxy-buffering: "on" + nginx.ingress.kubernetes.io/proxy-buffer-size: "8k" + nginx.ingress.kubernetes.io/proxy-buffers: "8 8k" + + # Security headers + nginx.ingress.kubernetes.io/server-snippet: | + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss:"; +spec: + tls: + - hosts: + - erpnext.yourdomain.com + - www.erpnext.yourdomain.com + secretName: erpnext-ssl-certs + rules: + - host: erpnext.yourdomain.com + http: + paths: + # Socket.IO traffic - highest priority + - path: /socket.io + pathType: Prefix + backend: + service: + name: erpnext-backend + port: + number: 9000 + + # API endpoints + - path: /api + pathType: Prefix + backend: + service: + name: erpnext-backend + port: + number: 8000 + + # File downloads and uploads + - path: /files + pathType: Prefix + backend: + service: + name: erpnext-frontend + port: + number: 8080 + + # Static assets + - path: /assets + pathType: Prefix + backend: + service: + name: erpnext-frontend + port: + number: 8080 + + # Health check endpoint + - path: /health + pathType: Exact + backend: + service: + name: erpnext-frontend + port: + number: 8080 + + # All other traffic to frontend + - path: / + pathType: Prefix + backend: + service: + name: erpnext-frontend + port: + number: 8080 + + # Redirect www to non-www + - host: www.erpnext.yourdomain.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: erpnext-frontend + port: + number: 8080 +--- +# Additional ingress for internal monitoring (optional) +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: erpnext-monitoring-ingress + namespace: erpnext + labels: + app: erpnext + component: monitoring + environment: production + annotations: + kubernetes.io/ingress.class: alb + alb.ingress.kubernetes.io/scheme: internal + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/backend-protocol: HTTP + alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]' + alb.ingress.kubernetes.io/subnets: subnet-${PRIVATE_SUBNET_1A},subnet-${PRIVATE_SUBNET_1B} + alb.ingress.kubernetes.io/security-groups: sg-${MONITORING_SECURITY_GROUP_ID} + alb.ingress.kubernetes.io/tags: | + Environment=production, + Application=ERPNext, + Component=Monitoring, + Access=Internal +spec: + rules: + - host: erpnext-monitoring.internal + http: + paths: + # Prometheus metrics + - path: /metrics + pathType: Prefix + backend: + service: + name: erpnext-backend + port: + number: 8000 + + # Health checks + - path: /health + pathType: Prefix + backend: + service: + name: erpnext-frontend + port: + number: 8080 +--- +# Network Policy to restrict ingress traffic +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: erpnext-ingress-policy + namespace: erpnext + labels: + app: erpnext + component: network-policy +spec: + podSelector: + matchLabels: + app: erpnext-frontend + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + name: kube-system + - podSelector: + matchLabels: + app.kubernetes.io/name: aws-load-balancer-controller + ports: + - protocol: TCP + port: 8080 + - from: + - namespaceSelector: + matchLabels: + name: erpnext + - podSelector: + matchLabels: + component: backend + ports: + - protocol: TCP + port: 8080 +--- +# HorizontalPodAutoscaler for frontend based on ALB metrics +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: erpnext-frontend-hpa + namespace: erpnext + labels: + app: erpnext-frontend + component: autoscaling +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: erpnext-frontend + minReplicas: 2 + maxReplicas: 8 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 + behavior: + scaleDown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 50 + periodSeconds: 60 + scaleUp: + stabilizationWindowSeconds: 60 + policies: + - type: Percent + value: 100 + periodSeconds: 60 + - type: Pods + value: 2 + periodSeconds: 60 + selectPolicy: Max \ No newline at end of file diff --git a/documentation/deployment-guides/aws-managed/kubernetes-manifests/jobs.yaml b/documentation/deployment-guides/aws-managed/kubernetes-manifests/jobs.yaml new file mode 100644 index 0000000..f46015d --- /dev/null +++ b/documentation/deployment-guides/aws-managed/kubernetes-manifests/jobs.yaml @@ -0,0 +1,436 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: erpnext-create-site + namespace: erpnext + labels: + app: erpnext + component: setup + job-type: create-site + annotations: + description: "Initialize ERPNext site with database and default configuration" +spec: + backoffLimit: 3 + completions: 1 + parallelism: 1 + activeDeadlineSeconds: 3600 # 1 hour timeout + template: + metadata: + labels: + app: erpnext + component: setup + job-type: create-site + spec: + serviceAccountName: erpnext-sa + restartPolicy: Never + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + fsGroupChangePolicy: "OnRootMismatch" + initContainers: + - name: wait-for-services + image: busybox:1.35 + imagePullPolicy: IfNotPresent + command: + - sh + - -c + - | + echo 'Waiting for database to be ready...' + until nc -z $DB_HOST $DB_PORT; do + echo 'Waiting for database...' + sleep 10 + done + echo 'Database is ready!' + + echo 'Waiting for Redis to be ready...' + REDIS_HOST=$(echo $REDIS_CACHE_URL | cut -d'/' -f3 | cut -d':' -f1) + until nc -z $REDIS_HOST 6379; do + echo 'Waiting for Redis...' + sleep 10 + done + echo 'Redis is ready!' + + # Additional wait for services to stabilize + echo 'Waiting for services to stabilize...' + sleep 30 + echo 'Services are ready!' + envFrom: + - configMapRef: + name: erpnext-config + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + containers: + - name: create-site + image: frappe/erpnext-worker:v14 + imagePullPolicy: Always + command: + - bash + - -c + - | + set -e + echo "Starting ERPNext site creation process..." + + # Check if site already exists + if [ -d "/home/frappe/frappe-bench/sites/frontend" ]; then + echo "Site 'frontend' already exists. Checking if it's properly configured..." + + # Verify site configuration + if bench --site frontend list-apps | grep -q erpnext; then + echo "Site is properly configured with ERPNext. Skipping creation." + exit 0 + else + echo "Site exists but ERPNext is not installed. Installing ERPNext..." + bench --site frontend install-app erpnext + echo "ERPNext installation completed." + exit 0 + fi + fi + + echo "Creating new ERPNext site 'frontend'..." + + # Set database connection parameters + export DB_HOST="$DB_HOST" + export DB_PORT="$DB_PORT" + export DB_NAME="$DB_NAME" + export DB_USER="$DB_USER" + + # Create the site with ERPNext + bench new-site frontend \ + --admin-password "$ADMIN_PASSWORD" \ + --mariadb-root-password "$DB_PASSWORD" \ + --install-app erpnext \ + --set-default \ + --verbose + + # Configure site for production + echo "Configuring site for production..." + + # Set maintenance mode off + bench --site frontend set-maintenance-mode off + + # Clear cache + bench --site frontend clear-cache + + # Set up site configuration + bench --site frontend set-config developer_mode 0 + bench --site frontend set-config maintenance_mode 0 + bench --site frontend set-config enable_scheduler 1 + + # Create a backup + echo "Creating initial backup..." + bench --site frontend backup --with-files + + echo "Site creation and configuration completed successfully!" + echo "Site URL: $APP_URL" + echo "Admin User: $APP_USER" + echo "Site is ready for use." + envFrom: + - configMapRef: + name: erpnext-config + env: + - name: ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: erpnext-admin-secret + key: password + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: erpnext-db-secret + key: password + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: erpnext-redis-secret + key: password + volumeMounts: + - name: sites-data + mountPath: /home/frappe/frappe-bench/sites + - name: backups-data + mountPath: /home/frappe/frappe-bench/sites/backups + resources: + requests: + memory: "1Gi" + cpu: "500m" + ephemeral-storage: "2Gi" + limits: + memory: "2Gi" + cpu: "1000m" + ephemeral-storage: "4Gi" + volumes: + - name: sites-data + persistentVolumeClaim: + claimName: erpnext-sites-pvc + - name: backups-data + persistentVolumeClaim: + claimName: erpnext-backups-pvc + nodeSelector: + node-type: worker + kubernetes.io/arch: amd64 + tolerations: + - key: "node.kubernetes.io/not-ready" + operator: "Exists" + effect: "NoExecute" + tolerationSeconds: 300 +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: erpnext-backup + namespace: erpnext + labels: + app: erpnext + component: backup + schedule-type: daily + annotations: + description: "Daily backup of ERPNext database and files to S3" +spec: + schedule: "0 2 * * *" # Daily at 2 AM UTC + timeZone: "UTC" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + startingDeadlineSeconds: 300 + jobTemplate: + metadata: + labels: + app: erpnext + component: backup + schedule-type: daily + spec: + backoffLimit: 2 + activeDeadlineSeconds: 7200 # 2 hours timeout + template: + metadata: + labels: + app: erpnext + component: backup + schedule-type: daily + spec: + serviceAccountName: erpnext-sa + restartPolicy: OnFailure + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + fsGroupChangePolicy: "OnRootMismatch" + containers: + - name: backup + image: frappe/erpnext-worker:v14 + imagePullPolicy: Always + command: + - bash + - -c + - | + set -e + + BACKUP_DATE=$(date +%Y%m%d_%H%M%S) + BACKUP_DIR="/home/frappe/frappe-bench/sites/backups" + S3_BUCKET="${AWS_S3_BUCKET:-erpnext-backups-${ACCOUNT_ID}}" + + echo "Starting backup process at $BACKUP_DATE" + + # Ensure backup directory exists + mkdir -p "$BACKUP_DIR" + + # Create database backup with files + echo "Creating database backup..." + bench --site frontend backup --with-files --backup-path "$BACKUP_DIR" + + # List created backup files + echo "Backup files created:" + ls -la "$BACKUP_DIR" + + # Upload to S3 if configured + if [ -n "$S3_BUCKET" ] && command -v aws >/dev/null 2>&1; then + echo "Uploading backups to S3 bucket: $S3_BUCKET" + + # Create S3 path with date + S3_PATH="s3://$S3_BUCKET/backups/$BACKUP_DATE/" + + # Upload all backup files + aws s3 cp "$BACKUP_DIR/" "$S3_PATH" --recursive --exclude "*" --include "*.sql.gz" --include "*.tar" + + if [ $? -eq 0 ]; then + echo "Backup successfully uploaded to $S3_PATH" + + # Clean up local backup files older than 7 days + find "$BACKUP_DIR" -name "*.sql.gz" -mtime +7 -delete + find "$BACKUP_DIR" -name "*.tar" -mtime +7 -delete + echo "Cleaned up local backup files older than 7 days" + else + echo "Failed to upload backup to S3" + exit 1 + fi + else + echo "S3 upload not configured or AWS CLI not available" + echo "Keeping backups locally" + fi + + # Generate backup report + echo "Backup Summary:" + echo "- Backup Date: $BACKUP_DATE" + echo "- Backup Location: $BACKUP_DIR" + if [ -n "$S3_BUCKET" ]; then + echo "- S3 Location: $S3_PATH" + fi + echo "- Backup Status: SUCCESS" + + echo "Backup process completed successfully" + envFrom: + - configMapRef: + name: erpnext-config + env: + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: erpnext-db-secret + key: password + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: erpnext-redis-secret + key: password + - name: ACCOUNT_ID + value: "${ACCOUNT_ID}" + volumeMounts: + - name: sites-data + mountPath: /home/frappe/frappe-bench/sites + - name: backups-data + mountPath: /home/frappe/frappe-bench/sites/backups + resources: + requests: + memory: "512Mi" + cpu: "250m" + ephemeral-storage: "2Gi" + limits: + memory: "1Gi" + cpu: "500m" + ephemeral-storage: "4Gi" + volumes: + - name: sites-data + persistentVolumeClaim: + claimName: erpnext-sites-pvc + - name: backups-data + persistentVolumeClaim: + claimName: erpnext-backups-pvc + nodeSelector: + node-type: worker + kubernetes.io/arch: amd64 + tolerations: + - key: "node.kubernetes.io/not-ready" + operator: "Exists" + effect: "NoExecute" + tolerationSeconds: 300 +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: erpnext-maintenance + namespace: erpnext + labels: + app: erpnext + component: maintenance + schedule-type: weekly + annotations: + description: "Weekly maintenance tasks for ERPNext" +spec: + schedule: "0 3 * * 0" # Weekly on Sunday at 3 AM UTC + timeZone: "UTC" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 2 + failedJobsHistoryLimit: 2 + startingDeadlineSeconds: 600 + jobTemplate: + metadata: + labels: + app: erpnext + component: maintenance + schedule-type: weekly + spec: + backoffLimit: 1 + activeDeadlineSeconds: 3600 # 1 hour timeout + template: + metadata: + labels: + app: erpnext + component: maintenance + schedule-type: weekly + spec: + serviceAccountName: erpnext-sa + restartPolicy: OnFailure + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + containers: + - name: maintenance + image: frappe/erpnext-worker:v14 + imagePullPolicy: Always + command: + - bash + - -c + - | + set -e + + echo "Starting weekly maintenance tasks..." + + # Clear cache and optimize + echo "Clearing cache..." + bench --site frontend clear-cache + + # Run database optimization + echo "Running database maintenance..." + bench --site frontend execute "frappe.utils.bench_manager.run_patches()" + + # Clean up logs + echo "Cleaning up old logs..." + find /home/frappe/frappe-bench/logs -name "*.log" -mtime +30 -delete + + # Generate system health report + echo "Generating system health report..." + bench --site frontend execute "frappe.utils.doctor.get_system_info()" + + echo "Weekly maintenance completed successfully" + envFrom: + - configMapRef: + name: erpnext-config + env: + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: erpnext-db-secret + key: password + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: erpnext-redis-secret + key: password + volumeMounts: + - name: sites-data + mountPath: /home/frappe/frappe-bench/sites + resources: + requests: + memory: "256Mi" + cpu: "100m" + ephemeral-storage: "1Gi" + limits: + memory: "512Mi" + cpu: "250m" + ephemeral-storage: "2Gi" + volumes: + - name: sites-data + persistentVolumeClaim: + claimName: erpnext-sites-pvc + nodeSelector: + node-type: worker + kubernetes.io/arch: amd64 \ No newline at end of file diff --git a/documentation/deployment-guides/aws-managed/kubernetes-manifests/namespace.yaml b/documentation/deployment-guides/aws-managed/kubernetes-manifests/namespace.yaml new file mode 100644 index 0000000..305ae83 --- /dev/null +++ b/documentation/deployment-guides/aws-managed/kubernetes-manifests/namespace.yaml @@ -0,0 +1,92 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: erpnext + labels: + name: erpnext + application: erpnext + environment: production + annotations: + description: "ERPNext application namespace for AWS EKS deployment" +--- +apiVersion: v1 +kind: ResourceQuota +metadata: + name: erpnext-quota + namespace: erpnext +spec: + hard: + requests.cpu: "10" + requests.memory: 20Gi + limits.cpu: "20" + limits.memory: 40Gi + pods: "50" + persistentvolumeclaims: "10" + services: "10" + secrets: "20" + configmaps: "10" +--- +apiVersion: v1 +kind: LimitRange +metadata: + name: erpnext-limits + namespace: erpnext +spec: + limits: + - default: + cpu: "1000m" + memory: "2Gi" + defaultRequest: + cpu: "100m" + memory: "128Mi" + type: Container + - max: + cpu: "4000m" + memory: "8Gi" + min: + cpu: "50m" + memory: "64Mi" + type: Container +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: erpnext-network-policy + namespace: erpnext +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - namespaceSelector: + matchLabels: + name: kube-system + - namespaceSelector: + matchLabels: + name: erpnext + - namespaceSelector: + matchLabels: + name: ingress-nginx + egress: + - to: [] + ports: + - protocol: TCP + port: 53 + - protocol: UDP + port: 53 + - to: [] + ports: + - protocol: TCP + port: 443 + - protocol: TCP + port: 80 + - to: [] + ports: + - protocol: TCP + port: 3306 # RDS MySQL + - to: [] + ports: + - protocol: TCP + port: 6379 # MemoryDB Redis \ No newline at end of file diff --git a/documentation/deployment-guides/aws-managed/kubernetes-manifests/secrets.yaml b/documentation/deployment-guides/aws-managed/kubernetes-manifests/secrets.yaml new file mode 100644 index 0000000..f0ad8aa --- /dev/null +++ b/documentation/deployment-guides/aws-managed/kubernetes-manifests/secrets.yaml @@ -0,0 +1,209 @@ +# External Secrets Operator configuration for AWS Secrets Manager integration +apiVersion: external-secrets.io/v1beta1 +kind: SecretStore +metadata: + name: aws-secretstore + namespace: erpnext + labels: + app: erpnext + component: secrets +spec: + provider: + aws: + service: SecretsManager + region: us-east-1 + auth: + jwt: + serviceAccountRef: + name: erpnext-sa +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: erpnext-db-secret + namespace: erpnext + labels: + app: erpnext + component: database +spec: + refreshInterval: 15s + secretStoreRef: + name: aws-secretstore + kind: SecretStore + target: + name: erpnext-db-secret + creationPolicy: Owner + template: + type: Opaque + metadata: + labels: + app: erpnext + component: database + data: + password: "{{ .password }}" + # Additional database connection string templates + mysql-uri: "mysql://admin:{{ .password }}@${DB_HOST}:3306/erpnext" + data: + - secretKey: password + remoteRef: + key: erpnext/database/password +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: erpnext-redis-secret + namespace: erpnext + labels: + app: erpnext + component: cache +spec: + refreshInterval: 15s + secretStoreRef: + name: aws-secretstore + kind: SecretStore + target: + name: erpnext-redis-secret + creationPolicy: Owner + template: + type: Opaque + metadata: + labels: + app: erpnext + component: cache + data: + password: "{{ .password }}" + # Redis connection strings with auth + redis-cache-url: "redis://:{{ .password }}@${REDIS_HOST}:6379/0" + redis-queue-url: "redis://:{{ .password }}@${REDIS_HOST}:6379/1" + redis-socketio-url: "redis://:{{ .password }}@${REDIS_HOST}:6379/2" + data: + - secretKey: password + remoteRef: + key: erpnext/redis/password +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: erpnext-admin-secret + namespace: erpnext + labels: + app: erpnext + component: admin +spec: + refreshInterval: 15s + secretStoreRef: + name: aws-secretstore + kind: SecretStore + target: + name: erpnext-admin-secret + creationPolicy: Owner + template: + type: Opaque + metadata: + labels: + app: erpnext + component: admin + data: + password: "{{ .password }}" + data: + - secretKey: password + remoteRef: + key: erpnext/admin/password +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: erpnext-api-secret + namespace: erpnext + labels: + app: erpnext + component: api +spec: + refreshInterval: 15s + secretStoreRef: + name: aws-secretstore + kind: SecretStore + target: + name: erpnext-api-secret + creationPolicy: Owner + template: + type: Opaque + metadata: + labels: + app: erpnext + component: api + data: + api-key: "{{ .api_key }}" + api-secret: "{{ .api_secret }}" + data: + - secretKey: api_key + remoteRef: + key: erpnext/api/credentials + property: api_key + - secretKey: api_secret + remoteRef: + key: erpnext/api/credentials + property: api_secret +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: erpnext-ssl-certs + namespace: erpnext + labels: + app: erpnext + component: ssl +spec: + refreshInterval: 24h + secretStoreRef: + name: aws-secretstore + kind: SecretStore + target: + name: erpnext-ssl-certs + creationPolicy: Owner + template: + type: kubernetes.io/tls + metadata: + labels: + app: erpnext + component: ssl + data: + tls.crt: "{{ .certificate }}" + tls.key: "{{ .private_key }}" + ca.crt: "{{ .ca_certificate }}" + data: + - secretKey: certificate + remoteRef: + key: erpnext/ssl/certificate + - secretKey: private_key + remoteRef: + key: erpnext/ssl/private_key + - secretKey: ca_certificate + remoteRef: + key: erpnext/ssl/ca_certificate +--- +# Service Account with IRSA (IAM Roles for Service Accounts) annotations +apiVersion: v1 +kind: ServiceAccount +metadata: + name: erpnext-sa + namespace: erpnext + labels: + app: erpnext + component: serviceaccount + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::${ACCOUNT_ID}:role/eksctl-erpnext-cluster-addon-iamserviceaccount-erpnext-erpnext-sa-Role1-${ROLE_SUFFIX} +automountServiceAccountToken: true +--- +# Additional service account for external secrets operator +apiVersion: v1 +kind: ServiceAccount +metadata: + name: external-secrets-sa + namespace: erpnext + labels: + app: erpnext + component: external-secrets + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::${ACCOUNT_ID}:role/eksctl-erpnext-cluster-addon-iamserviceaccount-external-secrets-external-secrets-operator-Role1-${ROLE_SUFFIX} +automountServiceAccountToken: true \ No newline at end of file diff --git a/documentation/deployment-guides/aws-managed/kubernetes-manifests/storage.yaml b/documentation/deployment-guides/aws-managed/kubernetes-manifests/storage.yaml new file mode 100644 index 0000000..3e4416e --- /dev/null +++ b/documentation/deployment-guides/aws-managed/kubernetes-manifests/storage.yaml @@ -0,0 +1,101 @@ +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: efs-sc + annotations: + storageclass.kubernetes.io/is-default-class: "false" +provisioner: efs.csi.aws.com +parameters: + provisioningMode: efs-ap + fileSystemId: ${EFS_ID} # Replace with actual EFS ID + directoryPerms: "700" + gidRangeStart: "1000" + gidRangeEnd: "2000" + basePath: "/dynamic_provisioning" +volumeBindingMode: Immediate +allowVolumeExpansion: true +--- +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: ebs-gp3 + annotations: + storageclass.kubernetes.io/is-default-class: "true" +provisioner: ebs.csi.aws.com +volumeBindingMode: WaitForFirstConsumer +allowVolumeExpansion: true +parameters: + type: gp3 + iops: "3000" + throughput: "125" + encrypted: "true" + fsType: ext4 +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: erpnext-sites-pv + labels: + app: erpnext + component: storage +spec: + capacity: + storage: 100Gi + volumeMode: Filesystem + accessModes: + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + storageClassName: efs-sc + csi: + driver: efs.csi.aws.com + volumeHandle: ${EFS_ID} # Replace with actual EFS ID + volumeAttributes: + path: "/" +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: erpnext-sites-pvc + namespace: erpnext + labels: + app: erpnext + component: storage +spec: + accessModes: + - ReadWriteMany + storageClassName: efs-sc + resources: + requests: + storage: 50Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: erpnext-backups-pvc + namespace: erpnext + labels: + app: erpnext + component: backups +spec: + accessModes: + - ReadWriteOnce + storageClassName: ebs-gp3 + resources: + requests: + storage: 100Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: erpnext-logs-pvc + namespace: erpnext + labels: + app: erpnext + component: logs +spec: + accessModes: + - ReadWriteOnce + storageClassName: ebs-gp3 + resources: + requests: + storage: 20Gi \ No newline at end of file diff --git a/documentation/deployment-guides/aws-managed/scripts/deploy-ecs.sh b/documentation/deployment-guides/aws-managed/scripts/deploy-ecs.sh new file mode 100755 index 0000000..fd97610 --- /dev/null +++ b/documentation/deployment-guides/aws-managed/scripts/deploy-ecs.sh @@ -0,0 +1,1182 @@ +#!/bin/bash + +# ERPNext ECS Deployment Script for AWS Managed Services +# This script automates the deployment of ERPNext on Amazon ECS 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} + +# Infrastructure settings +DB_INSTANCE_CLASS=${DB_INSTANCE_CLASS:-db.t3.medium} +REDIS_NODE_TYPE=${REDIS_NODE_TYPE:-db.t4g.small} +ECS_TASK_CPU=${ECS_TASK_CPU:-1024} +ECS_TASK_MEMORY=${ECS_TASK_MEMORY:-2048} + +# 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 AWS CLI + if ! command -v aws &> /dev/null; then + print_error "AWS CLI is not installed. Please install it first." + exit 1 + fi + + # Check jq + if ! command -v jq &> /dev/null; then + print_error "jq is not installed. Please install it first." + exit 1 + fi + + # 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..." + + ALB_SG=$(aws ec2 describe-security-groups \ + --filters "Name=tag:Name,Values=${PROJECT_NAME}-alb-sg" \ + --query "SecurityGroups[0].GroupId" \ + --output text \ + --region $AWS_REGION \ + --profile $AWS_PROFILE) + + 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 "ALB Security Group: $ALB_SG" + 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 + + # Create access point + ACCESS_POINT_ID=$(aws efs create-access-point \ + --file-system-id $EFS_ID \ + --posix-user Uid=1000,Gid=1000 \ + --root-directory Path="/sites",CreationInfo='{OwnerUid=1000,OwnerGid=1000,Permissions=755}' \ + --tags Key=Name,Value=${PROJECT_NAME}-sites-access-point \ + --query "AccessPointId" \ + --output text \ + --region $AWS_REGION \ + --profile $AWS_PROFILE) + + print_status "Created EFS Access Point: $ACCESS_POINT_ID" + else + print_status "Using existing EFS: $EFS_ID" + + # Get access point ID + ACCESS_POINT_ID=$(aws efs describe-access-points \ + --file-system-id $EFS_ID \ + --query "AccessPoints[?Tags[?Key=='Name' && Value=='${PROJECT_NAME}-sites-access-point']].AccessPointId" \ + --output text \ + --region $AWS_REGION \ + --profile $AWS_PROFILE) + fi +} + +# Function to create ECS cluster +create_ecs_cluster() { + print_header "Creating ECS cluster..." + + # Check if cluster exists + CLUSTER_EXISTS=$(aws ecs describe-clusters \ + --clusters $CLUSTER_NAME \ + --query "clusters[0].status" \ + --output text \ + --region $AWS_REGION \ + --profile $AWS_PROFILE 2>/dev/null || echo "") + + if [ "$CLUSTER_EXISTS" != "ACTIVE" ]; then + print_status "Creating ECS cluster: $CLUSTER_NAME" + aws ecs create-cluster \ + --cluster-name $CLUSTER_NAME \ + --capacity-providers FARGATE FARGATE_SPOT \ + --default-capacity-provider-strategy capacityProvider=FARGATE,weight=1 \ + --settings name=containerInsights,value=enabled \ + --tags key=Name,value=$CLUSTER_NAME key=Application,value=ERPNext \ + --region $AWS_REGION \ + --profile $AWS_PROFILE + else + print_status "Using existing ECS cluster: $CLUSTER_NAME" + fi +} + +# Function to create ALB +create_alb() { + print_header "Creating Application Load Balancer..." + + # Check if ALB exists + ALB_ARN=$(aws elbv2 describe-load-balancers \ + --names ${PROJECT_NAME}-alb \ + --query "LoadBalancers[0].LoadBalancerArn" \ + --output text \ + --region $AWS_REGION \ + --profile $AWS_PROFILE 2>/dev/null || echo "") + + if [ -z "$ALB_ARN" ] || [ "$ALB_ARN" = "None" ]; then + print_status "Creating Application Load Balancer..." + ALB_ARN=$(aws elbv2 create-load-balancer \ + --name ${PROJECT_NAME}-alb \ + --subnets $PUBLIC_SUBNET_1A $PUBLIC_SUBNET_1B \ + --security-groups $ALB_SG \ + --scheme internet-facing \ + --type application \ + --ip-address-type ipv4 \ + --tags Key=Name,Value=${PROJECT_NAME}-alb Key=Application,Value=ERPNext \ + --query "LoadBalancers[0].LoadBalancerArn" \ + --output text \ + --region $AWS_REGION \ + --profile $AWS_PROFILE) + + print_status "Created ALB: $ALB_ARN" + + # Wait for ALB to be active + print_status "Waiting for ALB to be active..." + aws elbv2 wait load-balancer-available --load-balancer-arns $ALB_ARN --region $AWS_REGION --profile $AWS_PROFILE + else + print_status "Using existing ALB: $ALB_ARN" + fi + + # Create target groups + create_target_groups + + # Create listeners + create_listeners +} + +# Function to create target groups +create_target_groups() { + print_header "Creating target groups..." + + # Frontend target group + FRONTEND_TG_ARN=$(aws elbv2 create-target-group \ + --name ${PROJECT_NAME}-frontend-tg \ + --protocol HTTP \ + --port 8080 \ + --vpc-id $VPC_ID \ + --target-type ip \ + --health-check-path /health \ + --health-check-interval-seconds 30 \ + --health-check-timeout-seconds 5 \ + --healthy-threshold-count 2 \ + --unhealthy-threshold-count 5 \ + --tags Key=Name,Value=${PROJECT_NAME}-frontend-tg \ + --query "TargetGroups[0].TargetGroupArn" \ + --output text \ + --region $AWS_REGION \ + --profile $AWS_PROFILE 2>/dev/null || \ + aws elbv2 describe-target-groups \ + --names ${PROJECT_NAME}-frontend-tg \ + --query "TargetGroups[0].TargetGroupArn" \ + --output text \ + --region $AWS_REGION \ + --profile $AWS_PROFILE) + + # Backend target group + BACKEND_TG_ARN=$(aws elbv2 create-target-group \ + --name ${PROJECT_NAME}-backend-tg \ + --protocol HTTP \ + --port 8000 \ + --vpc-id $VPC_ID \ + --target-type ip \ + --health-check-path /api/method/ping \ + --health-check-interval-seconds 30 \ + --health-check-timeout-seconds 5 \ + --healthy-threshold-count 2 \ + --unhealthy-threshold-count 5 \ + --tags Key=Name,Value=${PROJECT_NAME}-backend-tg \ + --query "TargetGroups[0].TargetGroupArn" \ + --output text \ + --region $AWS_REGION \ + --profile $AWS_PROFILE 2>/dev/null || \ + aws elbv2 describe-target-groups \ + --names ${PROJECT_NAME}-backend-tg \ + --query "TargetGroups[0].TargetGroupArn" \ + --output text \ + --region $AWS_REGION \ + --profile $AWS_PROFILE) + + # Socket.IO target group + SOCKETIO_TG_ARN=$(aws elbv2 create-target-group \ + --name ${PROJECT_NAME}-socketio-tg \ + --protocol HTTP \ + --port 9000 \ + --vpc-id $VPC_ID \ + --target-type ip \ + --health-check-path /socket.io/ \ + --health-check-interval-seconds 30 \ + --health-check-timeout-seconds 5 \ + --healthy-threshold-count 2 \ + --unhealthy-threshold-count 5 \ + --tags Key=Name,Value=${PROJECT_NAME}-socketio-tg \ + --query "TargetGroups[0].TargetGroupArn" \ + --output text \ + --region $AWS_REGION \ + --profile $AWS_PROFILE 2>/dev/null || \ + aws elbv2 describe-target-groups \ + --names ${PROJECT_NAME}-socketio-tg \ + --query "TargetGroups[0].TargetGroupArn" \ + --output text \ + --region $AWS_REGION \ + --profile $AWS_PROFILE) + + print_status "Target Groups created:" + print_status " Frontend: $FRONTEND_TG_ARN" + print_status " Backend: $BACKEND_TG_ARN" + print_status " Socket.IO: $SOCKETIO_TG_ARN" +} + +# Function to create listeners +create_listeners() { + print_header "Creating ALB listeners..." + + # HTTP listener (redirects to HTTPS) + LISTENER_ARN=$(aws elbv2 create-listener \ + --load-balancer-arn $ALB_ARN \ + --protocol HTTP \ + --port 80 \ + --default-actions Type=forward,TargetGroupArn=$FRONTEND_TG_ARN \ + --tags Key=Name,Value=${PROJECT_NAME}-alb-listener-80 \ + --query "Listeners[0].ListenerArn" \ + --output text \ + --region $AWS_REGION \ + --profile $AWS_PROFILE 2>/dev/null || \ + aws elbv2 describe-listeners \ + --load-balancer-arn $ALB_ARN \ + --query "Listeners[?Port==\`80\`].ListenerArn" \ + --output text \ + --region $AWS_REGION \ + --profile $AWS_PROFILE) + + # Create listener rules + create_listener_rules $LISTENER_ARN +} + +# Function to create listener rules +create_listener_rules() { + local listener_arn=$1 + + print_header "Creating listener rules..." + + # Rule for Socket.IO traffic + aws elbv2 create-rule \ + --listener-arn $listener_arn \ + --priority 100 \ + --conditions Field=path-pattern,Values="/socket.io/*" \ + --actions Type=forward,TargetGroupArn=$SOCKETIO_TG_ARN \ + --region $AWS_REGION \ + --profile $AWS_PROFILE 2>/dev/null || true + + # Rule for API traffic + aws elbv2 create-rule \ + --listener-arn $listener_arn \ + --priority 200 \ + --conditions Field=path-pattern,Values="/api/*" \ + --actions Type=forward,TargetGroupArn=$BACKEND_TG_ARN \ + --region $AWS_REGION \ + --profile $AWS_PROFILE 2>/dev/null || true +} + +# Function to register task definitions +register_task_definitions() { + print_header "Registering ECS task definitions..." + + # Create backend task definition + create_backend_task_definition + + # Create frontend task definition + create_frontend_task_definition + + # Create worker task definition + create_worker_task_definition + + # Create scheduler task definition + create_scheduler_task_definition +} + +# Function to create backend task definition +create_backend_task_definition() { + print_status "Creating backend task definition..." + + cat > /tmp/erpnext-backend-task.json < /dev/null + + print_status "Backend task definition registered" +} + +# Function to create frontend task definition +create_frontend_task_definition() { + print_status "Creating frontend task definition..." + + cat > /tmp/erpnext-frontend-task.json < /dev/null + + print_status "Frontend task definition registered" +} + +# Function to create worker task definition +create_worker_task_definition() { + print_status "Creating worker task definition..." + + cat > /tmp/erpnext-worker-task.json < /dev/null + + print_status "Worker task definition registered" +} + +# Function to create scheduler task definition +create_scheduler_task_definition() { + print_status "Creating scheduler task definition..." + + cat > /tmp/erpnext-scheduler-task.json < /dev/null + + print_status "Scheduler task definition registered" +} + +# Function to create ECS services +create_ecs_services() { + print_header "Creating ECS services..." + + # Create backend service + print_status "Creating backend service..." + aws ecs create-service \ + --cluster $CLUSTER_NAME \ + --service-name ${PROJECT_NAME}-backend \ + --task-definition ${PROJECT_NAME}-backend:1 \ + --desired-count 2 \ + --launch-type FARGATE \ + --platform-version LATEST \ + --network-configuration "awsvpcConfiguration={subnets=[$PRIVATE_SUBNET_1A,$PRIVATE_SUBNET_1B],securityGroups=[$APP_SG],assignPublicIp=DISABLED}" \ + --load-balancers targetGroupArn=$BACKEND_TG_ARN,containerName=${PROJECT_NAME}-backend,containerPort=8000 targetGroupArn=$SOCKETIO_TG_ARN,containerName=${PROJECT_NAME}-backend,containerPort=9000 \ + --health-check-grace-period-seconds 60 \ + --tags key=Name,value=${PROJECT_NAME}-backend key=Application,value=ERPNext \ + --region $AWS_REGION \ + --profile $AWS_PROFILE > /dev/null + + # Create frontend service + print_status "Creating frontend service..." + aws ecs create-service \ + --cluster $CLUSTER_NAME \ + --service-name ${PROJECT_NAME}-frontend \ + --task-definition ${PROJECT_NAME}-frontend:1 \ + --desired-count 2 \ + --launch-type FARGATE \ + --platform-version LATEST \ + --network-configuration "awsvpcConfiguration={subnets=[$PRIVATE_SUBNET_1A,$PRIVATE_SUBNET_1B],securityGroups=[$APP_SG],assignPublicIp=DISABLED}" \ + --load-balancers targetGroupArn=$FRONTEND_TG_ARN,containerName=${PROJECT_NAME}-frontend,containerPort=8080 \ + --health-check-grace-period-seconds 30 \ + --tags key=Name,value=${PROJECT_NAME}-frontend key=Application,value=ERPNext \ + --region $AWS_REGION \ + --profile $AWS_PROFILE > /dev/null + + # Create worker service + print_status "Creating worker service..." + aws ecs create-service \ + --cluster $CLUSTER_NAME \ + --service-name ${PROJECT_NAME}-worker \ + --task-definition ${PROJECT_NAME}-worker:1 \ + --desired-count 2 \ + --launch-type FARGATE \ + --platform-version LATEST \ + --network-configuration "awsvpcConfiguration={subnets=[$PRIVATE_SUBNET_1A,$PRIVATE_SUBNET_1B],securityGroups=[$APP_SG],assignPublicIp=DISABLED}" \ + --tags key=Name,value=${PROJECT_NAME}-worker key=Application,value=ERPNext \ + --region $AWS_REGION \ + --profile $AWS_PROFILE > /dev/null + + # Create scheduler service + print_status "Creating scheduler service..." + aws ecs create-service \ + --cluster $CLUSTER_NAME \ + --service-name ${PROJECT_NAME}-scheduler \ + --task-definition ${PROJECT_NAME}-scheduler:1 \ + --desired-count 1 \ + --launch-type FARGATE \ + --platform-version LATEST \ + --network-configuration "awsvpcConfiguration={subnets=[$PRIVATE_SUBNET_1A,$PRIVATE_SUBNET_1B],securityGroups=[$APP_SG],assignPublicIp=DISABLED}" \ + --tags key=Name,value=${PROJECT_NAME}-scheduler key=Application,value=ERPNext \ + --region $AWS_REGION \ + --profile $AWS_PROFILE > /dev/null +} + +# Function to create site +create_site() { + print_header "Creating ERPNext site..." + + print_status "Running site creation task..." + + # Create site task definition (temporary) + cat > /tmp/erpnext-create-site-task.json < /dev/null + + # Run the task + TASK_ARN=$(aws ecs run-task \ + --cluster $CLUSTER_NAME \ + --task-definition ${PROJECT_NAME}-create-site:1 \ + --launch-type FARGATE \ + --platform-version LATEST \ + --network-configuration "awsvpcConfiguration={subnets=[$PRIVATE_SUBNET_1A],securityGroups=[$APP_SG],assignPublicIp=DISABLED}" \ + --query "tasks[0].taskArn" \ + --output text \ + --region $AWS_REGION \ + --profile $AWS_PROFILE) + + print_status "Site creation task started: $TASK_ARN" + print_status "Waiting for site creation to complete..." + + # Wait for task to complete + aws ecs wait tasks-stopped \ + --cluster $CLUSTER_NAME \ + --tasks $TASK_ARN \ + --region $AWS_REGION \ + --profile $AWS_PROFILE + + # Check task exit code + EXIT_CODE=$(aws ecs describe-tasks \ + --cluster $CLUSTER_NAME \ + --tasks $TASK_ARN \ + --query "tasks[0].containers[0].exitCode" \ + --output text \ + --region $AWS_REGION \ + --profile $AWS_PROFILE) + + if [ "$EXIT_CODE" = "0" ]; then + print_status "Site creation completed successfully!" + else + print_error "Site creation failed with exit code: $EXIT_CODE" + print_error "Check the logs for more details:" + print_error "aws logs get-log-events --log-group-name /aws/ecs/${PROJECT_NAME}-backend --log-stream-name ecs/create-site/$(echo $TASK_ARN | cut -d'/' -f3) --region $AWS_REGION" + exit 1 + fi +} + +# Function to display deployment summary +display_summary() { + print_header "Deployment Summary" + + # Get ALB DNS name + ALB_DNS=$(aws elbv2 describe-load-balancers \ + --names ${PROJECT_NAME}-alb \ + --query "LoadBalancers[0].DNSName" \ + --output text \ + --region $AWS_REGION \ + --profile $AWS_PROFILE) + + echo "" + print_status "ERPNext ECS deployment completed successfully!" + echo "" + print_status "Access Information:" + print_status " Application URL: http://$ALB_DNS" + print_status " Domain: $DOMAIN_NAME (configure DNS to point to $ALB_DNS)" + 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 " ECS Cluster: $CLUSTER_NAME" + print_status " Load Balancer: ${PROJECT_NAME}-alb" + print_status " EFS File System: $EFS_ID" + print_status " VPC: $VPC_ID" + echo "" + print_status "Next Steps:" + print_status " 1. Configure DNS to point $DOMAIN_NAME to $ALB_DNS" + print_status " 2. Set up SSL certificate in ACM and update ALB listener" + print_status " 3. Configure monitoring and alerts" + print_status " 4. Set up backup procedures" + echo "" + print_warning "Note: This deployment uses HTTP only. Configure HTTPS for production use." +} + +# Function to clean up temporary files +cleanup() { + print_status "Cleaning up temporary files..." + rm -f /tmp/erpnext-*-task.json +} + +# Main execution function +main() { + print_header "Starting ERPNext ECS 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 + ;; + --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 ECS cluster name (default: erpnext-cluster)" + echo " --project-name NAME Project name prefix (default: erpnext)" + echo " --domain DOMAIN Domain name (default: erpnext.yourdomain.com)" + 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_ecs_cluster + create_alb + register_task_definitions + create_ecs_services + + # Wait for services to stabilize + print_header "Waiting for services to stabilize..." + sleep 60 + + create_site + display_summary + cleanup + + print_header "Deployment completed successfully!" +} + +# Set trap to cleanup on exit +trap cleanup EXIT + +# Execute main function with all arguments +main "$@" \ No newline at end of file diff --git a/documentation/deployment-guides/aws-managed/scripts/deploy-eks.sh b/documentation/deployment-guides/aws-managed/scripts/deploy-eks.sh new file mode 100755 index 0000000..46a41e1 --- /dev/null +++ b/documentation/deployment-guides/aws-managed/scripts/deploy-eks.sh @@ -0,0 +1,1278 @@ +#!/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 "$@" \ No newline at end of file