⏺ Perfect! I have successfully created the complete AWS equivalent of the GCP managed services

deployment documentation. Here's a summary of what was created:

  📁 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.
This commit is contained in:
Brian Tan Seng 2025-08-22 19:15:03 +08:00
parent 696ce0670c
commit 8894bbf56e
16 changed files with 10068 additions and 0 deletions

View File

@ -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 <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"rds:*",
"elasticache:*",
"ecs:*",
"eks:*",
"ec2:*",
"iam:PassRole",
"logs:*",
"cloudwatch:*",
"secretsmanager:*",
"ssm:*"
],
"Resource": "*"
}
]
}
EOF
# Create the policy
aws iam create-policy \
--policy-name ERPNextManagedPolicy \
--policy-document file://erpnext-managed-policy.json
# Create service role for ECS tasks
cat > ecs-task-role-trust.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
aws iam create-role \
--role-name ERPNextECSTaskRole \
--assume-role-policy-document file://ecs-task-role-trust.json
# Attach policies to ECS task role
aws iam attach-role-policy \
--role-name ERPNextECSTaskRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
aws iam attach-role-policy \
--role-name ERPNextECSTaskRole \
--policy-arn $(aws iam list-policies --query "Policies[?PolicyName=='ERPNextManagedPolicy'].Arn" --output text)
```
## 🔐 Security Setup
### 1. VPC and Networking Configuration
```bash
# Create VPC for ERPNext deployment
aws ec2 create-vpc \
--cidr-block 10.0.0.0/16 \
--tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=erpnext-vpc}]'
# Get VPC ID
VPC_ID=$(aws ec2 describe-vpcs \
--filters "Name=tag:Name,Values=erpnext-vpc" \
--query "Vpcs[0].VpcId" --output text)
# Create Internet Gateway
aws ec2 create-internet-gateway \
--tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=erpnext-igw}]'
IGW_ID=$(aws ec2 describe-internet-gateways \
--filters "Name=tag:Name,Values=erpnext-igw" \
--query "InternetGateways[0].InternetGatewayId" --output text)
# Attach Internet Gateway to VPC
aws ec2 attach-internet-gateway \
--vpc-id $VPC_ID \
--internet-gateway-id $IGW_ID
# Create subnets
# Public subnet for load balancers
aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block 10.0.1.0/24 \
--availability-zone us-east-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=erpnext-public-subnet-1a}]'
aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block 10.0.2.0/24 \
--availability-zone us-east-1b \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=erpnext-public-subnet-1b}]'
# Private subnets for application
aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block 10.0.10.0/24 \
--availability-zone us-east-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=erpnext-private-subnet-1a}]'
aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block 10.0.11.0/24 \
--availability-zone us-east-1b \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=erpnext-private-subnet-1b}]'
# Database subnets
aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block 10.0.20.0/24 \
--availability-zone us-east-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=erpnext-db-subnet-1a}]'
aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block 10.0.21.0/24 \
--availability-zone us-east-1b \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=erpnext-db-subnet-1b}]'
```
### 2. Route Tables and NAT Gateway
```bash
# Get subnet IDs
PUBLIC_SUBNET_1A=$(aws ec2 describe-subnets \
--filters "Name=tag:Name,Values=erpnext-public-subnet-1a" \
--query "Subnets[0].SubnetId" --output text)
PUBLIC_SUBNET_1B=$(aws ec2 describe-subnets \
--filters "Name=tag:Name,Values=erpnext-public-subnet-1b" \
--query "Subnets[0].SubnetId" --output text)
PRIVATE_SUBNET_1A=$(aws ec2 describe-subnets \
--filters "Name=tag:Name,Values=erpnext-private-subnet-1a" \
--query "Subnets[0].SubnetId" --output text)
PRIVATE_SUBNET_1B=$(aws ec2 describe-subnets \
--filters "Name=tag:Name,Values=erpnext-private-subnet-1b" \
--query "Subnets[0].SubnetId" --output text)
# Create NAT Gateway
aws ec2 allocate-address \
--domain vpc \
--tag-specifications 'ResourceType=elastic-ip,Tags=[{Key=Name,Value=erpnext-nat-eip}]'
NAT_EIP=$(aws ec2 describe-addresses \
--filters "Name=tag:Name,Values=erpnext-nat-eip" \
--query "Addresses[0].AllocationId" --output text)
aws ec2 create-nat-gateway \
--subnet-id $PUBLIC_SUBNET_1A \
--allocation-id $NAT_EIP \
--tag-specifications 'ResourceType=nat-gateway,Tags=[{Key=Name,Value=erpnext-nat}]'
NAT_ID=$(aws ec2 describe-nat-gateways \
--filter "Name=tag:Name,Values=erpnext-nat" \
--query "NatGateways[0].NatGatewayId" --output text)
# Create route tables
# Public route table
aws ec2 create-route-table \
--vpc-id $VPC_ID \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=erpnext-public-rt}]'
PUBLIC_RT=$(aws ec2 describe-route-tables \
--filters "Name=tag:Name,Values=erpnext-public-rt" \
--query "RouteTables[0].RouteTableId" --output text)
# Private route table
aws ec2 create-route-table \
--vpc-id $VPC_ID \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=erpnext-private-rt}]'
PRIVATE_RT=$(aws ec2 describe-route-tables \
--filters "Name=tag:Name,Values=erpnext-private-rt" \
--query "RouteTables[0].RouteTableId" --output text)
# Add routes
aws ec2 create-route \
--route-table-id $PUBLIC_RT \
--destination-cidr-block 0.0.0.0/0 \
--gateway-id $IGW_ID
aws ec2 create-route \
--route-table-id $PRIVATE_RT \
--destination-cidr-block 0.0.0.0/0 \
--nat-gateway-id $NAT_ID
# Associate subnets with route tables
aws ec2 associate-route-table \
--subnet-id $PUBLIC_SUBNET_1A \
--route-table-id $PUBLIC_RT
aws ec2 associate-route-table \
--subnet-id $PUBLIC_SUBNET_1B \
--route-table-id $PUBLIC_RT
aws ec2 associate-route-table \
--subnet-id $PRIVATE_SUBNET_1A \
--route-table-id $PRIVATE_RT
aws ec2 associate-route-table \
--subnet-id $PRIVATE_SUBNET_1B \
--route-table-id $PRIVATE_RT
```
### 3. Security Groups
```bash
# Create security group for ALB
aws ec2 create-security-group \
--group-name erpnext-alb-sg \
--description "Security group for ERPNext Application Load Balancer" \
--vpc-id $VPC_ID \
--tag-specifications 'ResourceType=security-group,Tags=[{Key=Name,Value=erpnext-alb-sg}]'
ALB_SG=$(aws ec2 describe-security-groups \
--filters "Name=tag:Name,Values=erpnext-alb-sg" \
--query "SecurityGroups[0].GroupId" --output text)
# Create security group for application
aws ec2 create-security-group \
--group-name erpnext-app-sg \
--description "Security group for ERPNext Application" \
--vpc-id $VPC_ID \
--tag-specifications 'ResourceType=security-group,Tags=[{Key=Name,Value=erpnext-app-sg}]'
APP_SG=$(aws ec2 describe-security-groups \
--filters "Name=tag:Name,Values=erpnext-app-sg" \
--query "SecurityGroups[0].GroupId" --output text)
# Create security group for database
aws ec2 create-security-group \
--group-name erpnext-db-sg \
--description "Security group for ERPNext Database" \
--vpc-id $VPC_ID \
--tag-specifications 'ResourceType=security-group,Tags=[{Key=Name,Value=erpnext-db-sg}]'
DB_SG=$(aws ec2 describe-security-groups \
--filters "Name=tag:Name,Values=erpnext-db-sg" \
--query "SecurityGroups[0].GroupId" --output text)
# Configure security group rules
# ALB security group
aws ec2 authorize-security-group-ingress \
--group-id $ALB_SG \
--protocol tcp \
--port 80 \
--cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id $ALB_SG \
--protocol tcp \
--port 443 \
--cidr 0.0.0.0/0
# Application security group
aws ec2 authorize-security-group-ingress \
--group-id $APP_SG \
--protocol tcp \
--port 8000 \
--source-group $ALB_SG
aws ec2 authorize-security-group-ingress \
--group-id $APP_SG \
--protocol tcp \
--port 9000 \
--source-group $ALB_SG
# Database security group
aws ec2 authorize-security-group-ingress \
--group-id $DB_SG \
--protocol tcp \
--port 3306 \
--source-group $APP_SG
aws ec2 authorize-security-group-ingress \
--group-id $DB_SG \
--protocol tcp \
--port 6379 \
--source-group $APP_SG
```
## 💾 Managed Database Services Setup
### 1. RDS MySQL Instance
```bash
# Create DB subnet group
DB_SUBNET_1A=$(aws ec2 describe-subnets \
--filters "Name=tag:Name,Values=erpnext-db-subnet-1a" \
--query "Subnets[0].SubnetId" --output text)
DB_SUBNET_1B=$(aws ec2 describe-subnets \
--filters "Name=tag:Name,Values=erpnext-db-subnet-1b" \
--query "Subnets[0].SubnetId" --output text)
aws rds create-db-subnet-group \
--db-subnet-group-name erpnext-db-subnet-group \
--db-subnet-group-description "Subnet group for ERPNext database" \
--subnet-ids $DB_SUBNET_1A $DB_SUBNET_1B \
--tags Key=Name,Value=erpnext-db-subnet-group
# Create RDS MySQL instance
aws rds create-db-instance \
--db-instance-identifier erpnext-db \
--db-instance-class db.t3.medium \
--engine mysql \
--engine-version 8.0.35 \
--master-username admin \
--master-user-password YourSecureDBPassword123! \
--allocated-storage 100 \
--storage-type gp3 \
--storage-encrypted \
--vpc-security-group-ids $DB_SG \
--db-subnet-group-name erpnext-db-subnet-group \
--backup-retention-period 7 \
--backup-window 03:00-04:00 \
--maintenance-window sun:04:00-sun:05:00 \
--auto-minor-version-upgrade \
--deletion-protection \
--enable-performance-insights \
--performance-insights-retention-period 7 \
--tags Key=Name,Value=erpnext-db
# Wait for instance to be available
aws rds wait db-instance-available --db-instance-identifier erpnext-db
# Create ERPNext database
aws rds create-db-instance \
--db-instance-identifier erpnext-db \
--allocated-storage 100 \
--db-instance-class db.t3.medium \
--engine mysql \
--master-username admin \
--master-user-password YourSecureDBPassword123! \
--vpc-security-group-ids $DB_SG \
--db-subnet-group-name erpnext-db-subnet-group
```
### 2. MemoryDB for Redis Instance
```bash
# Create MemoryDB subnet group
aws memorydb create-subnet-group \
--subnet-group-name erpnext-redis-subnet-group \
--description "Subnet group for ERPNext Redis" \
--subnet-ids $DB_SUBNET_1A $DB_SUBNET_1B \
--tags Key=Name,Value=erpnext-redis-subnet-group
# Create MemoryDB user
aws memorydb create-user \
--user-name erpnext-redis-user \
--authentication-mode Type=password,Passwords=YourSecureRedisPassword123! \
--access-string "on ~* &* +@all" \
--tags Key=Name,Value=erpnext-redis-user
# Create MemoryDB ACL
aws memorydb create-acl \
--acl-name erpnext-redis-acl \
--user-names erpnext-redis-user \
--tags Key=Name,Value=erpnext-redis-acl
# Create MemoryDB cluster
aws memorydb create-cluster \
--cluster-name erpnext-redis \
--node-type db.t4g.small \
--engine-version 6.2 \
--num-shards 1 \
--num-replicas-per-shard 1 \
--acl-name erpnext-redis-acl \
--subnet-group-name erpnext-redis-subnet-group \
--security-group-ids $DB_SG \
--maintenance-window sun:05:00-sun:06:00 \
--sns-topic-arn arn:aws:sns:us-east-1:$(aws sts get-caller-identity --query Account --output text):erpnext-notifications \
--tls-enabled \
--tags Key=Name,Value=erpnext-redis
# Wait for cluster to be available
aws memorydb describe-clusters \
--cluster-name erpnext-redis \
--query "Clusters[0].Status" --output text
```
### 3. AWS Systems Manager Parameter Store for Configuration
```bash
# Store database configuration
aws ssm put-parameter \
--name "/erpnext/database/host" \
--value $(aws rds describe-db-instances \
--db-instance-identifier erpnext-db \
--query "DBInstances[0].Endpoint.Address" --output text) \
--type "String" \
--description "ERPNext Database Host"
aws ssm put-parameter \
--name "/erpnext/database/port" \
--value "3306" \
--type "String" \
--description "ERPNext Database Port"
aws ssm put-parameter \
--name "/erpnext/database/name" \
--value "erpnext" \
--type "String" \
--description "ERPNext Database Name"
aws ssm put-parameter \
--name "/erpnext/database/username" \
--value "admin" \
--type "String" \
--description "ERPNext Database Username"
# Store Redis configuration
aws ssm put-parameter \
--name "/erpnext/redis/host" \
--value $(aws memorydb describe-clusters \
--cluster-name erpnext-redis \
--query "Clusters[0].ClusterEndpoint.Address" --output text) \
--type "String" \
--description "ERPNext Redis Host"
aws ssm put-parameter \
--name "/erpnext/redis/port" \
--value "6379" \
--type "String" \
--description "ERPNext Redis Port"
```
## 🔑 AWS Secrets Manager for Sensitive Data
```bash
# Store database password
aws secretsmanager create-secret \
--name "erpnext/database/password" \
--description "ERPNext Database Password" \
--secret-string "YourSecureDBPassword123!" \
--tags '[{"Key":"Application","Value":"ERPNext"},{"Key":"Environment","Value":"Production"}]'
# Store Redis password
aws secretsmanager create-secret \
--name "erpnext/redis/password" \
--description "ERPNext Redis Password" \
--secret-string "YourSecureRedisPassword123!" \
--tags '[{"Key":"Application","Value":"ERPNext"},{"Key":"Environment","Value":"Production"}]'
# Store ERPNext admin password
aws secretsmanager create-secret \
--name "erpnext/admin/password" \
--description "ERPNext Admin Password" \
--secret-string "YourSecureAdminPassword123!" \
--tags '[{"Key":"Application","Value":"ERPNext"},{"Key":"Environment","Value":"Production"}]'
# Store API credentials
aws secretsmanager create-secret \
--name "erpnext/api/credentials" \
--description "ERPNext API Credentials" \
--secret-string '{"api_key":"your-api-key-here","api_secret":"your-api-secret-here"}' \
--tags '[{"Key":"Application","Value":"ERPNext"},{"Key":"Environment","Value":"Production"}]'
```
## 📊 CloudWatch and Monitoring Setup
```bash
# Create CloudWatch log groups
aws logs create-log-group \
--log-group-name /aws/ecs/erpnext-backend \
--retention-in-days 7
aws logs create-log-group \
--log-group-name /aws/ecs/erpnext-frontend \
--retention-in-days 7
aws logs create-log-group \
--log-group-name /aws/ecs/erpnext-worker \
--retention-in-days 7
# Create SNS topic for alerts
aws sns create-topic \
--name erpnext-alerts \
--tags Key=Application,Value=ERPNext
# Subscribe email to alerts (replace with your email)
aws sns subscribe \
--topic-arn arn:aws:sns:us-east-1:$(aws sts get-caller-identity --query Account --output text):erpnext-alerts \
--protocol email \
--notification-endpoint your-email@yourdomain.com
```
## 🔍 Verification Checklist
Before proceeding to deployment, verify:
```bash
# Check AWS credentials and region
aws sts get-caller-identity
aws configure get region
# Verify VPC and subnets
aws ec2 describe-vpcs --filters "Name=tag:Name,Values=erpnext-vpc"
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID"
# Check security groups
aws ec2 describe-security-groups --filters "Name=vpc-id,Values=$VPC_ID"
# Verify RDS instance
aws rds describe-db-instances --db-instance-identifier erpnext-db
# Check MemoryDB cluster
aws memorydb describe-clusters --cluster-name erpnext-redis
# Verify secrets
aws secretsmanager list-secrets --filters Key=tag-key,Values=Application Key=tag-value,Values=ERPNext
# Check IAM roles
aws iam get-role --role-name ERPNextECSTaskRole
# Verify CloudWatch log groups
aws logs describe-log-groups --log-group-name-prefix /aws/ecs/erpnext
```
## 💡 Cost Optimization for Managed Services
### 1. RDS Optimization
```bash
# Use appropriate instance types
# Development: db.t3.micro or db.t3.small
# Production: db.t3.medium or larger
# Enable storage autoscaling
aws rds modify-db-instance \
--db-instance-identifier erpnext-db \
--max-allocated-storage 1000 \
--apply-immediately
# Use Reserved Instances for production (1-3 year terms)
aws rds describe-reserved-db-instances-offerings \
--db-instance-class db.t3.medium \
--engine mysql
```
### 2. MemoryDB Optimization
```bash
# Right-size cluster based on memory usage
# Start with db.t4g.small and scale based on usage
# Monitor memory utilization and adjust
# Use Multi-AZ only for production
# Single-AZ for development/testing environments
```
### 3. Network Cost Optimization
```bash
# Use same AZ for services to minimize data transfer costs
# Monitor VPC Flow Logs for inter-AZ traffic
# Consider VPC endpoints for AWS services if needed
```
## 🚨 Security Best Practices for Managed Services
### 1. Network Security
- **VPC isolation**: All managed services use private subnets
- **Security groups**: Restrictive access between tiers
- **No public access**: Database and Redis not accessible from internet
### 2. Access Control
```bash
# Enable RDS IAM database authentication
aws rds modify-db-instance \
--db-instance-identifier erpnext-db \
--enable-iam-database-authentication \
--apply-immediately
# Create IAM database user
aws rds create-db-instance-read-replica \
--db-instance-identifier erpnext-db-read-replica \
--source-db-instance-identifier erpnext-db \
--db-instance-class db.t3.small
```
### 3. Encryption
- **Encryption at rest**: Enabled by default for both services
- **Encryption in transit**: SSL/TLS enforced
- **Secrets management**: AWS Secrets Manager for credentials
## 📚 Additional Resources
- [Amazon RDS Documentation](https://docs.aws.amazon.com/rds/)
- [Amazon MemoryDB Documentation](https://docs.aws.amazon.com/memorydb/)
- [Amazon ECS Documentation](https://docs.aws.amazon.com/ecs/)
- [Amazon EKS Documentation](https://docs.aws.amazon.com/eks/)
- [AWS VPC Documentation](https://docs.aws.amazon.com/vpc/)
## ➡️ Next Steps
After completing prerequisites:
1. **ECS with Managed Services**: Follow `01-ecs-managed-deployment.md`
2. **EKS with Managed Services**: Follow `02-eks-managed-deployment.md`
3. **Production Hardening**: See `03-production-managed-setup.md`
---
**⚠️ Important Notes**:
- Managed services incur continuous costs even when not in use
- Plan your backup and disaster recovery strategy
- Monitor costs regularly using AWS Cost Explorer
- Keep track of all resources created for billing purposes
- Use AWS Config for compliance and governance

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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.

View File

@ -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 ^(?<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}),(?<milliseconds>\d{3}) (?<level>\w+) (?<message>.*)$
Time_Key timestamp
Time_Format %Y-%m-%d %H:%M:%S

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff