⏺ 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:
parent
696ce0670c
commit
8894bbf56e
@ -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
418
documentation/deployment-guides/aws-managed/README.md
Normal file
418
documentation/deployment-guides/aws-managed/README.md
Normal 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.
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
1182
documentation/deployment-guides/aws-managed/scripts/deploy-ecs.sh
Executable file
1182
documentation/deployment-guides/aws-managed/scripts/deploy-ecs.sh
Executable file
File diff suppressed because it is too large
Load Diff
1278
documentation/deployment-guides/aws-managed/scripts/deploy-eks.sh
Executable file
1278
documentation/deployment-guides/aws-managed/scripts/deploy-eks.sh
Executable file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user