⏺ Perfect! I've successfully created the complete Azure equivalent of the GCP managed deployment
guides. Here's what has been created: 📁 Azure Managed Deployment Structure documentation/deployment-guides/azure-managed/ ├── README.md # Overview and architecture ├── 00-prerequisites-managed.md # Azure prerequisites setup ├── 01-aks-managed-deployment.md # AKS deployment guide ├── 02-container-instances-deployment.md # Container Instances guide ├── 03-production-managed-setup.md # Production hardening ├── kubernetes-manifests/ # Kubernetes YAML files │ ├── namespace.yaml # Namespace and quotas │ ├── storage.yaml # Azure Files storage │ ├── configmap.yaml # Configuration │ ├── secrets.yaml # Key Vault integration │ ├── erpnext-backend.yaml # Backend deployment │ ├── erpnext-frontend.yaml # Frontend deployment │ ├── erpnext-workers.yaml # Workers and scheduler │ ├── ingress.yaml # Ingress configuration │ └── jobs.yaml # Init and backup jobs └── scripts/ # Automation scripts ├── deploy-managed.sh # AKS deployment script └── container-instances-deploy.sh # ACI deployment script 🔄 Key Azure Service Mappings | GCP Service | Azure Equivalent | Notes | |----------------------|--------------------------------------|------------------------------- ---------------------------| | Cloud SQL (MySQL) | Azure Database for PostgreSQL | Using PostgreSQL instead of MySQL for better performance | | Memorystore (Redis) | Azure Cache for Redis | Full compatibility with SSL/TLS | | Cloud Storage | Azure Storage (Blob & Files) | Using Azure Files for shared storage | | GKE | AKS (Azure Kubernetes Service) | Full Kubernetes compatibility | | Cloud Run | Azure Container Instances | Serverless container platform | | Secret Manager | Azure Key Vault | Integrated secrets management | | Cloud IAM | Azure AD & Managed Identity | Enterprise-grade identity | | Stackdriver | Azure Monitor & Application Insights | Comprehensive monitoring | | Cloud CDN | Azure CDN | Global content delivery | | Cloud Load Balancing | Azure Application Gateway | L7 load balancing with WAF | ✨ Key Features 1. Managed Services - Azure Database for PostgreSQL with high availability - Azure Cache for Redis with SSL/TLS encryption - Azure Storage for file management - Azure Key Vault for secrets 2. Deployment Options - AKS: Full Kubernetes control with auto-scaling - Container Instances: Simpler serverless deployment 3. Security - Managed Identity for passwordless authentication - Private endpoints for database and Redis - Network security groups and firewall rules - SSL/TLS encryption everywhere 4. Production Ready - Automated backups with geo-redundancy - Monitoring with Azure Monitor - Auto-scaling configurations - Disaster recovery setup 5. Automation - One-command deployment scripts - Environment validation - Health checks and diagnostics - Backup automation 🚀 Quick Start # Prerequisites cd documentation/deployment-guides/azure-managed/ # Follow 00-prerequisites-managed.md # Option 1: Deploy to AKS ./scripts/deploy-managed.sh deploy # Option 2: Deploy to Container Instances ./scripts/container-instances-deploy.sh deploy 💰 Cost Comparison | Deployment Size | Azure (Monthly) | GCP (Monthly) | |-------------------|-----------------|---------------| | Small (<50 users) | ~ | ~ | | Medium (50-200) | ~ | ~ | | Large (200+) | ~,823 | ~,794 | The Azure deployment uses PostgreSQL instead of MySQL, which provides better performance and features, and includes Azure-specific optimizations for the cloud-native environment.
This commit is contained in:
parent
8894bbf56e
commit
6301b2ef4b
@ -0,0 +1,677 @@
|
|||||||
|
# Azure Prerequisites for ERPNext with Managed Services
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This guide covers the prerequisites and initial setup required for deploying ERPNext on Microsoft Azure using managed database services: Azure Database for PostgreSQL and Azure Cache for Redis.
|
||||||
|
|
||||||
|
## 🔧 Required Tools
|
||||||
|
|
||||||
|
### 1. Azure CLI
|
||||||
|
```bash
|
||||||
|
# Install Azure CLI on macOS
|
||||||
|
brew update && brew install azure-cli
|
||||||
|
|
||||||
|
# Install on Linux
|
||||||
|
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
|
||||||
|
|
||||||
|
# Install on Windows (PowerShell as Administrator)
|
||||||
|
Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi; Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'
|
||||||
|
|
||||||
|
# Login to Azure
|
||||||
|
az login
|
||||||
|
az account set --subscription "Your Subscription Name"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. kubectl (Kubernetes CLI) - For AKS Option
|
||||||
|
```bash
|
||||||
|
# Install kubectl via Azure CLI
|
||||||
|
az aks install-cli
|
||||||
|
|
||||||
|
# Verify installation
|
||||||
|
kubectl version --client
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Docker (for local testing and Container Instances)
|
||||||
|
```bash
|
||||||
|
# Install Docker
|
||||||
|
curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
|
||||||
|
|
||||||
|
# Enable Docker BuildKit
|
||||||
|
export DOCKER_BUILDKIT=1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Helm (for AKS Kubernetes package management)
|
||||||
|
```bash
|
||||||
|
# Install Helm
|
||||||
|
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
||||||
|
|
||||||
|
# Verify installation
|
||||||
|
helm version
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🏗️ Azure Subscription Setup
|
||||||
|
|
||||||
|
### 1. Create Resource Group
|
||||||
|
```bash
|
||||||
|
# Set variables
|
||||||
|
export RESOURCE_GROUP="erpnext-rg"
|
||||||
|
export LOCATION="eastus"
|
||||||
|
|
||||||
|
# Create resource group
|
||||||
|
az group create \
|
||||||
|
--name $RESOURCE_GROUP \
|
||||||
|
--location $LOCATION
|
||||||
|
|
||||||
|
# Verify creation
|
||||||
|
az group show --name $RESOURCE_GROUP
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Register Required Providers
|
||||||
|
```bash
|
||||||
|
# Register necessary Azure providers
|
||||||
|
az provider register --namespace Microsoft.ContainerService
|
||||||
|
az provider register --namespace Microsoft.ContainerInstance
|
||||||
|
az provider register --namespace Microsoft.DBforPostgreSQL
|
||||||
|
az provider register --namespace Microsoft.Cache
|
||||||
|
az provider register --namespace Microsoft.Storage
|
||||||
|
az provider register --namespace Microsoft.Network
|
||||||
|
az provider register --namespace Microsoft.KeyVault
|
||||||
|
az provider register --namespace Microsoft.ManagedIdentity
|
||||||
|
|
||||||
|
# Check registration status
|
||||||
|
az provider list --query "[?namespace=='Microsoft.ContainerService'].registrationState" -o tsv
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Set Default Resource Group and Location
|
||||||
|
```bash
|
||||||
|
# Set defaults for Azure CLI
|
||||||
|
az configure --defaults group=$RESOURCE_GROUP location=$LOCATION
|
||||||
|
|
||||||
|
# Verify configuration
|
||||||
|
az configure --list-defaults
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔐 Security Setup
|
||||||
|
|
||||||
|
### 1. Managed Identity Creation
|
||||||
|
```bash
|
||||||
|
# Create User Assigned Managed Identity
|
||||||
|
az identity create \
|
||||||
|
--name erpnext-identity \
|
||||||
|
--resource-group $RESOURCE_GROUP
|
||||||
|
|
||||||
|
# Get identity details
|
||||||
|
export IDENTITY_ID=$(az identity show --name erpnext-identity --resource-group $RESOURCE_GROUP --query id -o tsv)
|
||||||
|
export CLIENT_ID=$(az identity show --name erpnext-identity --resource-group $RESOURCE_GROUP --query clientId -o tsv)
|
||||||
|
export PRINCIPAL_ID=$(az identity show --name erpnext-identity --resource-group $RESOURCE_GROUP --query principalId -o tsv)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Key Vault Setup
|
||||||
|
```bash
|
||||||
|
# Create Key Vault
|
||||||
|
export KEYVAULT_NAME="erpnext-kv-$(openssl rand -hex 4)"
|
||||||
|
az keyvault create \
|
||||||
|
--name $KEYVAULT_NAME \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--location $LOCATION \
|
||||||
|
--enable-rbac-authorization
|
||||||
|
|
||||||
|
# Grant access to managed identity
|
||||||
|
az role assignment create \
|
||||||
|
--role "Key Vault Secrets User" \
|
||||||
|
--assignee $PRINCIPAL_ID \
|
||||||
|
--scope /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.KeyVault/vaults/$KEYVAULT_NAME
|
||||||
|
|
||||||
|
# Create secrets
|
||||||
|
az keyvault secret set \
|
||||||
|
--vault-name $KEYVAULT_NAME \
|
||||||
|
--name erpnext-admin-password \
|
||||||
|
--value "YourSecurePassword123!"
|
||||||
|
|
||||||
|
az keyvault secret set \
|
||||||
|
--vault-name $KEYVAULT_NAME \
|
||||||
|
--name erpnext-db-password \
|
||||||
|
--value "YourDBPassword123!"
|
||||||
|
|
||||||
|
az keyvault secret set \
|
||||||
|
--vault-name $KEYVAULT_NAME \
|
||||||
|
--name erpnext-redis-key \
|
||||||
|
--value "$(openssl rand -base64 32)"
|
||||||
|
|
||||||
|
az keyvault secret set \
|
||||||
|
--vault-name $KEYVAULT_NAME \
|
||||||
|
--name erpnext-api-key \
|
||||||
|
--value "your-api-key-here"
|
||||||
|
|
||||||
|
az keyvault secret set \
|
||||||
|
--vault-name $KEYVAULT_NAME \
|
||||||
|
--name erpnext-api-secret \
|
||||||
|
--value "your-api-secret-here"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Service Principal Creation (Alternative to Managed Identity)
|
||||||
|
```bash
|
||||||
|
# Create service principal for automation
|
||||||
|
az ad sp create-for-rbac \
|
||||||
|
--name erpnext-sp \
|
||||||
|
--role Contributor \
|
||||||
|
--scopes /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP \
|
||||||
|
--sdk-auth > ~/erpnext-sp-credentials.json
|
||||||
|
|
||||||
|
# Store service principal credentials securely
|
||||||
|
chmod 600 ~/erpnext-sp-credentials.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌐 Networking Setup
|
||||||
|
|
||||||
|
### 1. Virtual Network Creation
|
||||||
|
```bash
|
||||||
|
# Create virtual network
|
||||||
|
az network vnet create \
|
||||||
|
--name erpnext-vnet \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--address-prefix 10.0.0.0/16
|
||||||
|
|
||||||
|
# Create subnets for different services
|
||||||
|
# Subnet for AKS nodes
|
||||||
|
az network vnet subnet create \
|
||||||
|
--name aks-subnet \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--vnet-name erpnext-vnet \
|
||||||
|
--address-prefix 10.0.1.0/24
|
||||||
|
|
||||||
|
# Subnet for Container Instances
|
||||||
|
az network vnet subnet create \
|
||||||
|
--name aci-subnet \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--vnet-name erpnext-vnet \
|
||||||
|
--address-prefix 10.0.2.0/24 \
|
||||||
|
--delegation Microsoft.ContainerInstance/containerGroups
|
||||||
|
|
||||||
|
# Subnet for database services
|
||||||
|
az network vnet subnet create \
|
||||||
|
--name db-subnet \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--vnet-name erpnext-vnet \
|
||||||
|
--address-prefix 10.0.3.0/24
|
||||||
|
|
||||||
|
# Subnet for Redis cache
|
||||||
|
az network vnet subnet create \
|
||||||
|
--name redis-subnet \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--vnet-name erpnext-vnet \
|
||||||
|
--address-prefix 10.0.4.0/24
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Network Security Groups
|
||||||
|
```bash
|
||||||
|
# Create NSG for AKS
|
||||||
|
az network nsg create \
|
||||||
|
--name aks-nsg \
|
||||||
|
--resource-group $RESOURCE_GROUP
|
||||||
|
|
||||||
|
# Allow HTTP/HTTPS traffic
|
||||||
|
az network nsg rule create \
|
||||||
|
--name AllowHTTP \
|
||||||
|
--nsg-name aks-nsg \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--priority 100 \
|
||||||
|
--access Allow \
|
||||||
|
--protocol Tcp \
|
||||||
|
--direction Inbound \
|
||||||
|
--source-address-prefixes Internet \
|
||||||
|
--source-port-ranges '*' \
|
||||||
|
--destination-address-prefixes '*' \
|
||||||
|
--destination-port-ranges 80 443 8080
|
||||||
|
|
||||||
|
# Create NSG for databases
|
||||||
|
az network nsg create \
|
||||||
|
--name db-nsg \
|
||||||
|
--resource-group $RESOURCE_GROUP
|
||||||
|
|
||||||
|
# Allow PostgreSQL traffic from app subnets
|
||||||
|
az network nsg rule create \
|
||||||
|
--name AllowPostgreSQL \
|
||||||
|
--nsg-name db-nsg \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--priority 100 \
|
||||||
|
--access Allow \
|
||||||
|
--protocol Tcp \
|
||||||
|
--direction Inbound \
|
||||||
|
--source-address-prefixes 10.0.1.0/24 10.0.2.0/24 \
|
||||||
|
--source-port-ranges '*' \
|
||||||
|
--destination-address-prefixes '*' \
|
||||||
|
--destination-port-ranges 5432
|
||||||
|
|
||||||
|
# Associate NSGs with subnets
|
||||||
|
az network vnet subnet update \
|
||||||
|
--name aks-subnet \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--vnet-name erpnext-vnet \
|
||||||
|
--network-security-group aks-nsg
|
||||||
|
|
||||||
|
az network vnet subnet update \
|
||||||
|
--name db-subnet \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--vnet-name erpnext-vnet \
|
||||||
|
--network-security-group db-nsg
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Private DNS Zone (for Private Endpoints)
|
||||||
|
```bash
|
||||||
|
# Create private DNS zones for managed services
|
||||||
|
az network private-dns zone create \
|
||||||
|
--name privatelink.postgres.database.azure.com \
|
||||||
|
--resource-group $RESOURCE_GROUP
|
||||||
|
|
||||||
|
az network private-dns zone create \
|
||||||
|
--name privatelink.redis.cache.windows.net \
|
||||||
|
--resource-group $RESOURCE_GROUP
|
||||||
|
|
||||||
|
# Link DNS zones to VNet
|
||||||
|
az network private-dns link vnet create \
|
||||||
|
--name erpnext-postgres-link \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--zone-name privatelink.postgres.database.azure.com \
|
||||||
|
--virtual-network erpnext-vnet \
|
||||||
|
--registration-enabled false
|
||||||
|
|
||||||
|
az network private-dns link vnet create \
|
||||||
|
--name erpnext-redis-link \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--zone-name privatelink.redis.cache.windows.net \
|
||||||
|
--virtual-network erpnext-vnet \
|
||||||
|
--registration-enabled false
|
||||||
|
```
|
||||||
|
|
||||||
|
## 💾 Managed Database Services Setup
|
||||||
|
|
||||||
|
### 1. Azure Database for PostgreSQL
|
||||||
|
```bash
|
||||||
|
# Create PostgreSQL server
|
||||||
|
export DB_SERVER_NAME="erpnext-db-$(openssl rand -hex 4)"
|
||||||
|
export DB_ADMIN_USER="erpnext"
|
||||||
|
export DB_ADMIN_PASSWORD="YourDBPassword123!"
|
||||||
|
|
||||||
|
az postgres flexible-server create \
|
||||||
|
--name $DB_SERVER_NAME \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--location $LOCATION \
|
||||||
|
--admin-user $DB_ADMIN_USER \
|
||||||
|
--admin-password $DB_ADMIN_PASSWORD \
|
||||||
|
--sku-name Standard_D2s_v3 \
|
||||||
|
--storage-size 128 \
|
||||||
|
--version 13 \
|
||||||
|
--vnet erpnext-vnet \
|
||||||
|
--subnet db-subnet \
|
||||||
|
--backup-retention 7 \
|
||||||
|
--geo-redundant-backup Enabled \
|
||||||
|
--high-availability Enabled \
|
||||||
|
--zone 1 \
|
||||||
|
--standby-zone 2
|
||||||
|
|
||||||
|
# Create database
|
||||||
|
az postgres flexible-server db create \
|
||||||
|
--server-name $DB_SERVER_NAME \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--database-name erpnext
|
||||||
|
|
||||||
|
# Configure PostgreSQL parameters for ERPNext
|
||||||
|
az postgres flexible-server parameter set \
|
||||||
|
--server-name $DB_SERVER_NAME \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name max_connections \
|
||||||
|
--value 200
|
||||||
|
|
||||||
|
az postgres flexible-server parameter set \
|
||||||
|
--server-name $DB_SERVER_NAME \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name shared_buffers \
|
||||||
|
--value 65536 # 256MB for Standard_D2s_v3
|
||||||
|
|
||||||
|
# Enable extensions required by ERPNext
|
||||||
|
az postgres flexible-server parameter set \
|
||||||
|
--server-name $DB_SERVER_NAME \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name azure.extensions \
|
||||||
|
--value "uuid-ossp,pg_trgm,btree_gin"
|
||||||
|
|
||||||
|
# Get connection string
|
||||||
|
export DB_CONNECTION_STRING=$(az postgres flexible-server show-connection-string \
|
||||||
|
--server-name $DB_SERVER_NAME \
|
||||||
|
--database-name erpnext \
|
||||||
|
--admin-user $DB_ADMIN_USER \
|
||||||
|
--admin-password $DB_ADMIN_PASSWORD \
|
||||||
|
--query connectionStrings.psql -o tsv)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Azure Cache for Redis
|
||||||
|
```bash
|
||||||
|
# Create Redis cache
|
||||||
|
export REDIS_NAME="erpnext-redis-$(openssl rand -hex 4)"
|
||||||
|
|
||||||
|
az redis create \
|
||||||
|
--name $REDIS_NAME \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--location $LOCATION \
|
||||||
|
--sku Standard \
|
||||||
|
--vm-size c1 \
|
||||||
|
--enable-non-ssl-port false \
|
||||||
|
--minimum-tls-version 1.2 \
|
||||||
|
--redis-configuration maxmemory-policy="allkeys-lru"
|
||||||
|
|
||||||
|
# Get Redis access key
|
||||||
|
export REDIS_KEY=$(az redis list-keys \
|
||||||
|
--name $REDIS_NAME \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--query primaryKey -o tsv)
|
||||||
|
|
||||||
|
# Get Redis hostname
|
||||||
|
export REDIS_HOST=$(az redis show \
|
||||||
|
--name $REDIS_NAME \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--query hostName -o tsv)
|
||||||
|
|
||||||
|
# Create private endpoint for Redis
|
||||||
|
az network private-endpoint create \
|
||||||
|
--name redis-private-endpoint \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--vnet-name erpnext-vnet \
|
||||||
|
--subnet redis-subnet \
|
||||||
|
--private-connection-resource-id $(az redis show --name $REDIS_NAME --resource-group $RESOURCE_GROUP --query id -o tsv) \
|
||||||
|
--group-id redisCache \
|
||||||
|
--connection-name redis-connection
|
||||||
|
|
||||||
|
# Configure DNS for private endpoint
|
||||||
|
az network private-endpoint dns-zone-group create \
|
||||||
|
--name redis-dns-group \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--endpoint-name redis-private-endpoint \
|
||||||
|
--private-dns-zone privatelink.redis.cache.windows.net \
|
||||||
|
--zone-name redis
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Database Initialization
|
||||||
|
```bash
|
||||||
|
# Create initialization script
|
||||||
|
cat > /tmp/init_erpnext_db.sql <<EOF
|
||||||
|
-- Create ERPNext database with proper encoding
|
||||||
|
CREATE DATABASE IF NOT EXISTS erpnext WITH ENCODING 'UTF8' LC_COLLATE='en_US.utf8' LC_CTYPE='en_US.utf8';
|
||||||
|
|
||||||
|
-- Connect to erpnext database
|
||||||
|
\c erpnext;
|
||||||
|
|
||||||
|
-- Create required extensions
|
||||||
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||||
|
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
|
||||||
|
CREATE EXTENSION IF NOT EXISTS "btree_gin";
|
||||||
|
|
||||||
|
-- Set database parameters
|
||||||
|
ALTER DATABASE erpnext SET statement_timeout = 0;
|
||||||
|
ALTER DATABASE erpnext SET lock_timeout = 0;
|
||||||
|
ALTER DATABASE erpnext SET idle_in_transaction_session_timeout = 0;
|
||||||
|
ALTER DATABASE erpnext SET client_encoding = 'UTF8';
|
||||||
|
ALTER DATABASE erpnext SET standard_conforming_strings = on;
|
||||||
|
ALTER DATABASE erpnext SET check_function_bodies = false;
|
||||||
|
ALTER DATABASE erpnext SET xmloption = content;
|
||||||
|
ALTER DATABASE erpnext SET client_min_messages = warning;
|
||||||
|
ALTER DATABASE erpnext SET row_security = off;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Connect to PostgreSQL and run initialization
|
||||||
|
PGPASSWORD=$DB_ADMIN_PASSWORD psql \
|
||||||
|
-h $DB_SERVER_NAME.postgres.database.azure.com \
|
||||||
|
-U $DB_ADMIN_USER@$DB_SERVER_NAME \
|
||||||
|
-d postgres \
|
||||||
|
-f /tmp/init_erpnext_db.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📦 Storage Setup
|
||||||
|
|
||||||
|
### 1. Azure Storage Account
|
||||||
|
```bash
|
||||||
|
# Create storage account for file uploads
|
||||||
|
export STORAGE_ACCOUNT="erpnext$(openssl rand -hex 4)"
|
||||||
|
|
||||||
|
az storage account create \
|
||||||
|
--name $STORAGE_ACCOUNT \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--location $LOCATION \
|
||||||
|
--sku Standard_LRS \
|
||||||
|
--kind StorageV2 \
|
||||||
|
--https-only true \
|
||||||
|
--min-tls-version TLS1_2 \
|
||||||
|
--allow-blob-public-access false
|
||||||
|
|
||||||
|
# Create blob containers
|
||||||
|
az storage container create \
|
||||||
|
--name erpnext-files \
|
||||||
|
--account-name $STORAGE_ACCOUNT \
|
||||||
|
--auth-mode login
|
||||||
|
|
||||||
|
az storage container create \
|
||||||
|
--name erpnext-backups \
|
||||||
|
--account-name $STORAGE_ACCOUNT \
|
||||||
|
--auth-mode login
|
||||||
|
|
||||||
|
# Get storage connection string
|
||||||
|
export STORAGE_CONNECTION_STRING=$(az storage account show-connection-string \
|
||||||
|
--name $STORAGE_ACCOUNT \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--query connectionString -o tsv)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configure Managed Identity Access
|
||||||
|
```bash
|
||||||
|
# Grant storage access to managed identity
|
||||||
|
az role assignment create \
|
||||||
|
--role "Storage Blob Data Contributor" \
|
||||||
|
--assignee $PRINCIPAL_ID \
|
||||||
|
--scope /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Storage/storageAccounts/$STORAGE_ACCOUNT
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Monitoring and Logging
|
||||||
|
|
||||||
|
### 1. Log Analytics Workspace
|
||||||
|
```bash
|
||||||
|
# Create Log Analytics workspace
|
||||||
|
az monitor log-analytics workspace create \
|
||||||
|
--workspace-name erpnext-logs \
|
||||||
|
--resource-group $RESOURCE_GROUP
|
||||||
|
|
||||||
|
# Get workspace ID and key
|
||||||
|
export WORKSPACE_ID=$(az monitor log-analytics workspace show \
|
||||||
|
--workspace-name erpnext-logs \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--query customerId -o tsv)
|
||||||
|
|
||||||
|
export WORKSPACE_KEY=$(az monitor log-analytics workspace get-shared-keys \
|
||||||
|
--workspace-name erpnext-logs \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--query primarySharedKey -o tsv)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Application Insights
|
||||||
|
```bash
|
||||||
|
# Create Application Insights
|
||||||
|
az monitor app-insights component create \
|
||||||
|
--app erpnext-insights \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--location $LOCATION \
|
||||||
|
--workspace erpnext-logs
|
||||||
|
|
||||||
|
# Get instrumentation key
|
||||||
|
export INSTRUMENTATION_KEY=$(az monitor app-insights component show \
|
||||||
|
--app erpnext-insights \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--query instrumentationKey -o tsv)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Enable Database Monitoring
|
||||||
|
```bash
|
||||||
|
# Enable monitoring for PostgreSQL
|
||||||
|
az postgres flexible-server update \
|
||||||
|
--name $DB_SERVER_NAME \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--maintenance-window "Sun:02:00"
|
||||||
|
|
||||||
|
# Enable Redis monitoring (enabled by default)
|
||||||
|
# Metrics are automatically collected for Azure Cache for Redis
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 Verification Checklist
|
||||||
|
|
||||||
|
Before proceeding to deployment, verify:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check resource group exists
|
||||||
|
az group show --name $RESOURCE_GROUP
|
||||||
|
|
||||||
|
# Verify managed identity
|
||||||
|
az identity show --name erpnext-identity --resource-group $RESOURCE_GROUP
|
||||||
|
|
||||||
|
# Check Key Vault
|
||||||
|
az keyvault show --name $KEYVAULT_NAME
|
||||||
|
|
||||||
|
# List secrets
|
||||||
|
az keyvault secret list --vault-name $KEYVAULT_NAME
|
||||||
|
|
||||||
|
# Verify VNet and subnets
|
||||||
|
az network vnet show --name erpnext-vnet --resource-group $RESOURCE_GROUP
|
||||||
|
az network vnet subnet list --vnet-name erpnext-vnet --resource-group $RESOURCE_GROUP
|
||||||
|
|
||||||
|
# Check PostgreSQL server
|
||||||
|
az postgres flexible-server show \
|
||||||
|
--name $DB_SERVER_NAME \
|
||||||
|
--resource-group $RESOURCE_GROUP
|
||||||
|
|
||||||
|
# Verify Redis cache
|
||||||
|
az redis show --name $REDIS_NAME --resource-group $RESOURCE_GROUP
|
||||||
|
|
||||||
|
# Check storage account
|
||||||
|
az storage account show --name $STORAGE_ACCOUNT --resource-group $RESOURCE_GROUP
|
||||||
|
|
||||||
|
# Verify Log Analytics workspace
|
||||||
|
az monitor log-analytics workspace show \
|
||||||
|
--workspace-name erpnext-logs \
|
||||||
|
--resource-group $RESOURCE_GROUP
|
||||||
|
```
|
||||||
|
|
||||||
|
## 💡 Cost Optimization for Managed Services
|
||||||
|
|
||||||
|
### 1. PostgreSQL Optimization
|
||||||
|
```bash
|
||||||
|
# Use appropriate SKUs
|
||||||
|
# Development: Burstable B1ms or B2s
|
||||||
|
# Production: General Purpose D2s_v3 or higher
|
||||||
|
|
||||||
|
# Enable automatic storage growth
|
||||||
|
az postgres flexible-server update \
|
||||||
|
--name $DB_SERVER_NAME \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--storage-auto-grow Enabled
|
||||||
|
|
||||||
|
# Use zone-redundant HA only for production
|
||||||
|
# Save ~50% by using single zone for dev/test
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Redis Cache Optimization
|
||||||
|
```bash
|
||||||
|
# Right-size Redis instance
|
||||||
|
# Basic tier for dev/test (no SLA)
|
||||||
|
# Standard tier for production (99.9% SLA)
|
||||||
|
# Premium tier only if clustering/geo-replication needed
|
||||||
|
|
||||||
|
# Monitor memory usage and scale accordingly
|
||||||
|
az redis show --name $REDIS_NAME --resource-group $RESOURCE_GROUP \
|
||||||
|
--query "redisConfiguration.maxmemory-policy"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Reserved Capacity
|
||||||
|
```bash
|
||||||
|
# Purchase reserved capacity for predictable workloads
|
||||||
|
# Up to 65% savings for 3-year commitments
|
||||||
|
# Available for PostgreSQL, Redis, and VMs
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚨 Security Best Practices
|
||||||
|
|
||||||
|
### 1. Network Security
|
||||||
|
- **Private endpoints**: All managed services use private endpoints
|
||||||
|
- **VNet integration**: Secure communication within VNet
|
||||||
|
- **No public access**: Database and Redis not accessible from internet
|
||||||
|
|
||||||
|
### 2. Access Control
|
||||||
|
```bash
|
||||||
|
# Use Azure AD authentication for PostgreSQL
|
||||||
|
az postgres flexible-server ad-admin create \
|
||||||
|
--server-name $DB_SERVER_NAME \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--display-name "ERPNext Admins" \
|
||||||
|
--object-id $(az ad group show --group "ERPNext Admins" --query objectId -o tsv)
|
||||||
|
|
||||||
|
# Enable Azure AD auth only mode
|
||||||
|
az postgres flexible-server update \
|
||||||
|
--name $DB_SERVER_NAME \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--password-auth Disabled
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Encryption
|
||||||
|
- **Encryption at rest**: Enabled by default with Microsoft-managed keys
|
||||||
|
- **Encryption in transit**: TLS 1.2+ enforced
|
||||||
|
- **Customer-managed keys**: Optional for additional control
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Enable customer-managed keys (optional)
|
||||||
|
az postgres flexible-server update \
|
||||||
|
--name $DB_SERVER_NAME \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--key-vault-key-uri https://$KEYVAULT_NAME.vault.azure.net/keys/cmk/version
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Export Environment Variables
|
||||||
|
|
||||||
|
Save these for deployment scripts:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create environment file
|
||||||
|
cat > ~/erpnext-azure-env.sh <<EOF
|
||||||
|
export RESOURCE_GROUP="$RESOURCE_GROUP"
|
||||||
|
export LOCATION="$LOCATION"
|
||||||
|
export KEYVAULT_NAME="$KEYVAULT_NAME"
|
||||||
|
export IDENTITY_ID="$IDENTITY_ID"
|
||||||
|
export CLIENT_ID="$CLIENT_ID"
|
||||||
|
export PRINCIPAL_ID="$PRINCIPAL_ID"
|
||||||
|
export DB_SERVER_NAME="$DB_SERVER_NAME"
|
||||||
|
export DB_ADMIN_USER="$DB_ADMIN_USER"
|
||||||
|
export DB_ADMIN_PASSWORD="$DB_ADMIN_PASSWORD"
|
||||||
|
export REDIS_NAME="$REDIS_NAME"
|
||||||
|
export REDIS_HOST="$REDIS_HOST"
|
||||||
|
export REDIS_KEY="$REDIS_KEY"
|
||||||
|
export STORAGE_ACCOUNT="$STORAGE_ACCOUNT"
|
||||||
|
export STORAGE_CONNECTION_STRING="$STORAGE_CONNECTION_STRING"
|
||||||
|
export WORKSPACE_ID="$WORKSPACE_ID"
|
||||||
|
export WORKSPACE_KEY="$WORKSPACE_KEY"
|
||||||
|
export INSTRUMENTATION_KEY="$INSTRUMENTATION_KEY"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Source for future use
|
||||||
|
source ~/erpnext-azure-env.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## ➡️ Next Steps
|
||||||
|
|
||||||
|
After completing prerequisites:
|
||||||
|
1. **AKS with Managed Services**: Follow `01-aks-managed-deployment.md`
|
||||||
|
2. **Container Instances**: Follow `02-container-instances-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 Azure Cost Management
|
||||||
|
- Keep track of all resources created for billing purposes
|
||||||
|
- Use tags for resource organization and cost tracking
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,761 @@
|
|||||||
|
# Azure Container Instances Deployment with Managed Services
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This guide covers deploying ERPNext on Azure Container Instances (ACI) using Azure Database for PostgreSQL and Azure Cache for Redis. ACI provides a serverless container platform ideal for simpler deployments without Kubernetes complexity.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Completed all steps in `00-prerequisites-managed.md`
|
||||||
|
- Azure CLI installed and configured
|
||||||
|
- Docker installed locally for image building
|
||||||
|
- Environment variables from prerequisites exported
|
||||||
|
|
||||||
|
## 🚀 Container Registry Setup
|
||||||
|
|
||||||
|
### 1. Create Azure Container Registry
|
||||||
|
```bash
|
||||||
|
# Source environment variables
|
||||||
|
source ~/erpnext-azure-env.sh
|
||||||
|
|
||||||
|
# Create container registry
|
||||||
|
export ACR_NAME="erpnextacr$(openssl rand -hex 4)"
|
||||||
|
az acr create \
|
||||||
|
--name $ACR_NAME \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--location $LOCATION \
|
||||||
|
--sku Standard \
|
||||||
|
--admin-enabled true
|
||||||
|
|
||||||
|
# Get registry credentials
|
||||||
|
export ACR_LOGIN_SERVER=$(az acr show --name $ACR_NAME --query loginServer -o tsv)
|
||||||
|
export ACR_USERNAME=$(az acr credential show --name $ACR_NAME --query username -o tsv)
|
||||||
|
export ACR_PASSWORD=$(az acr credential show --name $ACR_NAME --query passwords[0].value -o tsv)
|
||||||
|
|
||||||
|
# Login to registry
|
||||||
|
az acr login --name $ACR_NAME
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Build and Push Custom Images
|
||||||
|
```bash
|
||||||
|
# Create Dockerfile for ERPNext with Azure integrations
|
||||||
|
cat > Dockerfile.azure <<EOF
|
||||||
|
FROM frappe/erpnext-worker:v14
|
||||||
|
|
||||||
|
# Install Azure Storage SDK for Python
|
||||||
|
RUN pip install azure-storage-blob azure-identity
|
||||||
|
|
||||||
|
# Add custom entrypoint for Azure configurations
|
||||||
|
COPY entrypoint.azure.sh /entrypoint.azure.sh
|
||||||
|
RUN chmod +x /entrypoint.azure.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["/entrypoint.azure.sh"]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create entrypoint script
|
||||||
|
cat > entrypoint.azure.sh <<EOF
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Configure database connection for PostgreSQL
|
||||||
|
export DB_TYPE="postgres"
|
||||||
|
export DB_HOST="\${DB_HOST}"
|
||||||
|
export DB_PORT="5432"
|
||||||
|
export DB_NAME="erpnext"
|
||||||
|
|
||||||
|
# Configure Redis with authentication
|
||||||
|
export REDIS_CACHE="redis://:\${REDIS_PASSWORD}@\${REDIS_HOST}:6380/0?ssl_cert_reqs=required"
|
||||||
|
export REDIS_QUEUE="redis://:\${REDIS_PASSWORD}@\${REDIS_HOST}:6380/1?ssl_cert_reqs=required"
|
||||||
|
export REDIS_SOCKETIO="redis://:\${REDIS_PASSWORD}@\${REDIS_HOST}:6380/2?ssl_cert_reqs=required"
|
||||||
|
|
||||||
|
# Execute original command
|
||||||
|
exec "\$@"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Build and push image
|
||||||
|
docker build -f Dockerfile.azure -t $ACR_LOGIN_SERVER/erpnext-azure:v14 .
|
||||||
|
docker push $ACR_LOGIN_SERVER/erpnext-azure:v14
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📦 Deploy Container Instances
|
||||||
|
|
||||||
|
### 1. Create File Share for Persistent Storage
|
||||||
|
```bash
|
||||||
|
# Create file share in storage account
|
||||||
|
az storage share create \
|
||||||
|
--name erpnext-sites \
|
||||||
|
--account-name $STORAGE_ACCOUNT \
|
||||||
|
--quota 100
|
||||||
|
|
||||||
|
az storage share create \
|
||||||
|
--name erpnext-assets \
|
||||||
|
--account-name $STORAGE_ACCOUNT \
|
||||||
|
--quota 50
|
||||||
|
|
||||||
|
# Get storage account key
|
||||||
|
export STORAGE_KEY=$(az storage account keys list \
|
||||||
|
--account-name $STORAGE_ACCOUNT \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--query "[0].value" -o tsv)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Deploy Backend Container Group
|
||||||
|
```bash
|
||||||
|
# Create backend container group with multiple containers
|
||||||
|
cat > aci-backend-deployment.yaml <<EOF
|
||||||
|
apiVersion: 2021-10-01
|
||||||
|
location: $LOCATION
|
||||||
|
name: erpnext-backend
|
||||||
|
properties:
|
||||||
|
containers:
|
||||||
|
- name: backend
|
||||||
|
properties:
|
||||||
|
image: $ACR_LOGIN_SERVER/erpnext-azure:v14
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 2
|
||||||
|
memoryInGb: 4
|
||||||
|
ports:
|
||||||
|
- port: 8000
|
||||||
|
protocol: TCP
|
||||||
|
environmentVariables:
|
||||||
|
- name: DB_HOST
|
||||||
|
value: $DB_SERVER_NAME.postgres.database.azure.com
|
||||||
|
- name: DB_USER
|
||||||
|
value: $DB_ADMIN_USER
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
secureValue: $DB_ADMIN_PASSWORD
|
||||||
|
- name: REDIS_HOST
|
||||||
|
value: $REDIS_HOST
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
secureValue: $REDIS_KEY
|
||||||
|
- name: ADMIN_PASSWORD
|
||||||
|
secureValue: YourSecurePassword123!
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
- name: assets
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites/assets
|
||||||
|
- name: scheduler
|
||||||
|
properties:
|
||||||
|
image: $ACR_LOGIN_SERVER/erpnext-azure:v14
|
||||||
|
command: ["bench", "schedule"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 0.5
|
||||||
|
memoryInGb: 1
|
||||||
|
environmentVariables:
|
||||||
|
- name: DB_HOST
|
||||||
|
value: $DB_SERVER_NAME.postgres.database.azure.com
|
||||||
|
- name: DB_USER
|
||||||
|
value: $DB_ADMIN_USER
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
secureValue: $DB_ADMIN_PASSWORD
|
||||||
|
- name: REDIS_HOST
|
||||||
|
value: $REDIS_HOST
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
secureValue: $REDIS_KEY
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
- name: worker-default
|
||||||
|
properties:
|
||||||
|
image: $ACR_LOGIN_SERVER/erpnext-azure:v14
|
||||||
|
command: ["bench", "worker", "--queue", "default"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 1
|
||||||
|
memoryInGb: 2
|
||||||
|
environmentVariables:
|
||||||
|
- name: DB_HOST
|
||||||
|
value: $DB_SERVER_NAME.postgres.database.azure.com
|
||||||
|
- name: DB_USER
|
||||||
|
value: $DB_ADMIN_USER
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
secureValue: $DB_ADMIN_PASSWORD
|
||||||
|
- name: REDIS_HOST
|
||||||
|
value: $REDIS_HOST
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
secureValue: $REDIS_KEY
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
- name: worker-long
|
||||||
|
properties:
|
||||||
|
image: $ACR_LOGIN_SERVER/erpnext-azure:v14
|
||||||
|
command: ["bench", "worker", "--queue", "long"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 1
|
||||||
|
memoryInGb: 2
|
||||||
|
environmentVariables:
|
||||||
|
- name: DB_HOST
|
||||||
|
value: $DB_SERVER_NAME.postgres.database.azure.com
|
||||||
|
- name: DB_USER
|
||||||
|
value: $DB_ADMIN_USER
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
secureValue: $DB_ADMIN_PASSWORD
|
||||||
|
- name: REDIS_HOST
|
||||||
|
value: $REDIS_HOST
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
secureValue: $REDIS_KEY
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
imageRegistryCredentials:
|
||||||
|
- server: $ACR_LOGIN_SERVER
|
||||||
|
username: $ACR_USERNAME
|
||||||
|
password: $ACR_PASSWORD
|
||||||
|
volumes:
|
||||||
|
- name: sites
|
||||||
|
azureFile:
|
||||||
|
shareName: erpnext-sites
|
||||||
|
storageAccountName: $STORAGE_ACCOUNT
|
||||||
|
storageAccountKey: $STORAGE_KEY
|
||||||
|
- name: assets
|
||||||
|
azureFile:
|
||||||
|
shareName: erpnext-assets
|
||||||
|
storageAccountName: $STORAGE_ACCOUNT
|
||||||
|
storageAccountKey: $STORAGE_KEY
|
||||||
|
osType: Linux
|
||||||
|
restartPolicy: Always
|
||||||
|
ipAddress:
|
||||||
|
type: Private
|
||||||
|
ports:
|
||||||
|
- port: 8000
|
||||||
|
protocol: TCP
|
||||||
|
subnetIds:
|
||||||
|
- id: /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Network/virtualNetworks/erpnext-vnet/subnets/aci-subnet
|
||||||
|
type: Microsoft.ContainerInstance/containerGroups
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Deploy backend container group
|
||||||
|
az container create \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--file aci-backend-deployment.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Deploy Frontend Container Group
|
||||||
|
```bash
|
||||||
|
# Create frontend container group
|
||||||
|
cat > aci-frontend-deployment.yaml <<EOF
|
||||||
|
apiVersion: 2021-10-01
|
||||||
|
location: $LOCATION
|
||||||
|
name: erpnext-frontend
|
||||||
|
properties:
|
||||||
|
containers:
|
||||||
|
- name: frontend
|
||||||
|
properties:
|
||||||
|
image: frappe/erpnext-nginx:v14
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 1
|
||||||
|
memoryInGb: 1
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
protocol: TCP
|
||||||
|
environmentVariables:
|
||||||
|
- name: BACKEND
|
||||||
|
value: erpnext-backend.internal:8000
|
||||||
|
- name: FRAPPE_SITE_NAME_HEADER
|
||||||
|
value: frontend
|
||||||
|
- name: SOCKETIO
|
||||||
|
value: erpnext-websocket.internal:9000
|
||||||
|
- name: UPSTREAM_REAL_IP_ADDRESS
|
||||||
|
value: 127.0.0.1
|
||||||
|
- name: UPSTREAM_REAL_IP_HEADER
|
||||||
|
value: X-Forwarded-For
|
||||||
|
- name: PROXY_READ_TIMEOUT
|
||||||
|
value: "120"
|
||||||
|
- name: CLIENT_MAX_BODY_SIZE
|
||||||
|
value: 50m
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
- name: assets
|
||||||
|
mountPath: /usr/share/nginx/html/assets
|
||||||
|
- name: websocket
|
||||||
|
properties:
|
||||||
|
image: frappe/frappe-socketio:v14
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 0.5
|
||||||
|
memoryInGb: 0.5
|
||||||
|
ports:
|
||||||
|
- port: 9000
|
||||||
|
protocol: TCP
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
imageRegistryCredentials:
|
||||||
|
- server: $ACR_LOGIN_SERVER
|
||||||
|
username: $ACR_USERNAME
|
||||||
|
password: $ACR_PASSWORD
|
||||||
|
volumes:
|
||||||
|
- name: sites
|
||||||
|
azureFile:
|
||||||
|
shareName: erpnext-sites
|
||||||
|
storageAccountName: $STORAGE_ACCOUNT
|
||||||
|
storageAccountKey: $STORAGE_KEY
|
||||||
|
- name: assets
|
||||||
|
azureFile:
|
||||||
|
shareName: erpnext-assets
|
||||||
|
storageAccountName: $STORAGE_ACCOUNT
|
||||||
|
storageAccountKey: $STORAGE_KEY
|
||||||
|
osType: Linux
|
||||||
|
restartPolicy: Always
|
||||||
|
ipAddress:
|
||||||
|
type: Public
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
protocol: TCP
|
||||||
|
dnsNameLabel: erpnext-$RESOURCE_GROUP
|
||||||
|
type: Microsoft.ContainerInstance/containerGroups
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Deploy frontend container group
|
||||||
|
az container create \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--file aci-frontend-deployment.yaml
|
||||||
|
|
||||||
|
# Get public IP/FQDN
|
||||||
|
export FRONTEND_FQDN=$(az container show \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-frontend \
|
||||||
|
--query ipAddress.fqdn -o tsv)
|
||||||
|
|
||||||
|
echo "Frontend accessible at: http://$FRONTEND_FQDN:8080"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 Initialize ERPNext Site
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run site initialization as a one-time container
|
||||||
|
az container create \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-init \
|
||||||
|
--image $ACR_LOGIN_SERVER/erpnext-azure:v14 \
|
||||||
|
--cpu 2 \
|
||||||
|
--memory 4 \
|
||||||
|
--restart-policy Never \
|
||||||
|
--environment-variables \
|
||||||
|
DB_HOST=$DB_SERVER_NAME.postgres.database.azure.com \
|
||||||
|
DB_USER=$DB_ADMIN_USER \
|
||||||
|
REDIS_HOST=$REDIS_HOST \
|
||||||
|
--secure-environment-variables \
|
||||||
|
DB_PASSWORD=$DB_ADMIN_PASSWORD \
|
||||||
|
REDIS_PASSWORD=$REDIS_KEY \
|
||||||
|
ADMIN_PASSWORD="YourSecurePassword123!" \
|
||||||
|
--azure-file-volume-account-name $STORAGE_ACCOUNT \
|
||||||
|
--azure-file-volume-account-key $STORAGE_KEY \
|
||||||
|
--azure-file-volume-share-name erpnext-sites \
|
||||||
|
--azure-file-volume-mount-path /home/frappe/frappe-bench/sites \
|
||||||
|
--command-line "/bin/bash -c 'bench new-site frontend --db-host \$DB_HOST --db-port 5432 --db-name erpnext --db-password \$DB_PASSWORD --admin-password \$ADMIN_PASSWORD --install-app erpnext && bench --site frontend migrate'" \
|
||||||
|
--subnet /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Network/virtualNetworks/erpnext-vnet/subnets/aci-subnet \
|
||||||
|
--registry-login-server $ACR_LOGIN_SERVER \
|
||||||
|
--registry-username $ACR_USERNAME \
|
||||||
|
--registry-password $ACR_PASSWORD
|
||||||
|
|
||||||
|
# Wait for initialization to complete
|
||||||
|
az container show \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-init \
|
||||||
|
--query containers[0].instanceView.currentState.state
|
||||||
|
|
||||||
|
# View initialization logs
|
||||||
|
az container logs \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-init
|
||||||
|
|
||||||
|
# Delete init container after completion
|
||||||
|
az container delete \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-init \
|
||||||
|
--yes
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌐 Configure Application Gateway
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create public IP for Application Gateway
|
||||||
|
az network public-ip create \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-ag-pip \
|
||||||
|
--allocation-method Static \
|
||||||
|
--sku Standard
|
||||||
|
|
||||||
|
# Create Application Gateway
|
||||||
|
az network application-gateway create \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-ag \
|
||||||
|
--location $LOCATION \
|
||||||
|
--vnet-name erpnext-vnet \
|
||||||
|
--subnet aks-subnet \
|
||||||
|
--public-ip-address erpnext-ag-pip \
|
||||||
|
--sku Standard_v2 \
|
||||||
|
--capacity 2 \
|
||||||
|
--http-settings-port 8080 \
|
||||||
|
--http-settings-protocol Http \
|
||||||
|
--frontend-port 80 \
|
||||||
|
--routing-rule-type Basic
|
||||||
|
|
||||||
|
# Configure backend pool with container instances
|
||||||
|
az network application-gateway address-pool create \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--gateway-name erpnext-ag \
|
||||||
|
--name erpnext-backend-pool \
|
||||||
|
--servers $FRONTEND_FQDN
|
||||||
|
|
||||||
|
# Create health probe
|
||||||
|
az network application-gateway probe create \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--gateway-name erpnext-ag \
|
||||||
|
--name erpnext-health \
|
||||||
|
--protocol Http \
|
||||||
|
--path / \
|
||||||
|
--interval 30 \
|
||||||
|
--timeout 30 \
|
||||||
|
--threshold 3
|
||||||
|
|
||||||
|
# Configure SSL (optional)
|
||||||
|
# Upload SSL certificate
|
||||||
|
az network application-gateway ssl-cert create \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--gateway-name erpnext-ag \
|
||||||
|
--name erpnext-ssl \
|
||||||
|
--cert-file /path/to/certificate.pfx \
|
||||||
|
--cert-password YourCertPassword
|
||||||
|
|
||||||
|
# Create HTTPS listener
|
||||||
|
az network application-gateway frontend-port create \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--gateway-name erpnext-ag \
|
||||||
|
--name https-port \
|
||||||
|
--port 443
|
||||||
|
|
||||||
|
az network application-gateway http-listener create \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--gateway-name erpnext-ag \
|
||||||
|
--name erpnext-https-listener \
|
||||||
|
--frontend-port https-port \
|
||||||
|
--ssl-cert erpnext-ssl
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Monitoring and Logging
|
||||||
|
|
||||||
|
### 1. Enable Container Insights
|
||||||
|
```bash
|
||||||
|
# Enable diagnostics for container groups
|
||||||
|
az monitor diagnostic-settings create \
|
||||||
|
--resource /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.ContainerInstance/containerGroups/erpnext-backend \
|
||||||
|
--name erpnext-backend-diagnostics \
|
||||||
|
--workspace /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.OperationalInsights/workspaces/erpnext-logs \
|
||||||
|
--logs '[{"category": "ContainerInstanceLog", "enabled": true}]' \
|
||||||
|
--metrics '[{"category": "AllMetrics", "enabled": true}]'
|
||||||
|
|
||||||
|
az monitor diagnostic-settings create \
|
||||||
|
--resource /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.ContainerInstance/containerGroups/erpnext-frontend \
|
||||||
|
--name erpnext-frontend-diagnostics \
|
||||||
|
--workspace /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.OperationalInsights/workspaces/erpnext-logs \
|
||||||
|
--logs '[{"category": "ContainerInstanceLog", "enabled": true}]' \
|
||||||
|
--metrics '[{"category": "AllMetrics", "enabled": true}]'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. View Container Logs
|
||||||
|
```bash
|
||||||
|
# View backend logs
|
||||||
|
az container logs \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-backend \
|
||||||
|
--container-name backend
|
||||||
|
|
||||||
|
# View worker logs
|
||||||
|
az container logs \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-backend \
|
||||||
|
--container-name worker-default
|
||||||
|
|
||||||
|
# Stream logs
|
||||||
|
az container attach \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-backend \
|
||||||
|
--container-name backend
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Create Alerts
|
||||||
|
```bash
|
||||||
|
# Alert for container restart
|
||||||
|
az monitor metrics alert create \
|
||||||
|
--name erpnext-container-restart \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--scopes /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.ContainerInstance/containerGroups/erpnext-backend \
|
||||||
|
--condition "sum RestartCount > 5" \
|
||||||
|
--window-size 15m \
|
||||||
|
--evaluation-frequency 5m
|
||||||
|
|
||||||
|
# Alert for high CPU usage
|
||||||
|
az monitor metrics alert create \
|
||||||
|
--name erpnext-high-cpu \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--scopes /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.ContainerInstance/containerGroups/erpnext-backend \
|
||||||
|
--condition "avg CpuUsage > 80" \
|
||||||
|
--window-size 5m \
|
||||||
|
--evaluation-frequency 1m
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Scaling Container Instances
|
||||||
|
|
||||||
|
### Manual Scaling
|
||||||
|
```bash
|
||||||
|
# Scale by creating additional container groups
|
||||||
|
for i in {2..3}; do
|
||||||
|
sed "s/erpnext-backend/erpnext-backend-$i/g" aci-backend-deployment.yaml > aci-backend-deployment-$i.yaml
|
||||||
|
az container create \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--file aci-backend-deployment-$i.yaml
|
||||||
|
done
|
||||||
|
|
||||||
|
# Update Application Gateway backend pool
|
||||||
|
az network application-gateway address-pool update \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--gateway-name erpnext-ag \
|
||||||
|
--name erpnext-backend-pool \
|
||||||
|
--servers erpnext-backend-1.internal erpnext-backend-2.internal erpnext-backend-3.internal
|
||||||
|
```
|
||||||
|
|
||||||
|
### Auto-scaling with Logic Apps
|
||||||
|
```bash
|
||||||
|
# Create Logic App for auto-scaling
|
||||||
|
az logic workflow create \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-autoscale \
|
||||||
|
--definition '{
|
||||||
|
"triggers": {
|
||||||
|
"Recurrence": {
|
||||||
|
"recurrence": {
|
||||||
|
"frequency": "Minute",
|
||||||
|
"interval": 5
|
||||||
|
},
|
||||||
|
"type": "Recurrence"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"CheckMetrics": {
|
||||||
|
"type": "Http",
|
||||||
|
"inputs": {
|
||||||
|
"method": "GET",
|
||||||
|
"uri": "https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/'$RESOURCE_GROUP'/providers/Microsoft.ContainerInstance/containerGroups/erpnext-backend/providers/Microsoft.Insights/metrics?api-version=2018-01-01&metricnames=CpuUsage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ScaleDecision": {
|
||||||
|
"type": "If",
|
||||||
|
"expression": "@greater(body('"'"'CheckMetrics'"'"').value[0].timeseries[0].data[0].average, 70)",
|
||||||
|
"actions": {
|
||||||
|
"ScaleUp": {
|
||||||
|
"type": "Http",
|
||||||
|
"inputs": {
|
||||||
|
"method": "PUT",
|
||||||
|
"uri": "https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/'$RESOURCE_GROUP'/providers/Microsoft.ContainerInstance/containerGroups/erpnext-backend-new?api-version=2021-10-01"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 Backup and Recovery
|
||||||
|
|
||||||
|
### 1. Database Backup
|
||||||
|
```bash
|
||||||
|
# Manual backup
|
||||||
|
az postgres flexible-server backup create \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--server-name $DB_SERVER_NAME \
|
||||||
|
--backup-name erpnext-manual-backup-$(date +%Y%m%d)
|
||||||
|
|
||||||
|
# List backups
|
||||||
|
az postgres flexible-server backup list \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--server-name $DB_SERVER_NAME
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. File Storage Backup
|
||||||
|
```bash
|
||||||
|
# Create backup container
|
||||||
|
az storage container create \
|
||||||
|
--name erpnext-backups \
|
||||||
|
--account-name $STORAGE_ACCOUNT
|
||||||
|
|
||||||
|
# Backup file shares using AzCopy
|
||||||
|
azcopy copy \
|
||||||
|
"https://$STORAGE_ACCOUNT.file.core.windows.net/erpnext-sites?$STORAGE_KEY" \
|
||||||
|
"https://$STORAGE_ACCOUNT.blob.core.windows.net/erpnext-backups/$(date +%Y%m%d)/" \
|
||||||
|
--recursive
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Application Backup Job
|
||||||
|
```bash
|
||||||
|
# Create backup container instance
|
||||||
|
az container create \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-backup \
|
||||||
|
--image $ACR_LOGIN_SERVER/erpnext-azure:v14 \
|
||||||
|
--cpu 1 \
|
||||||
|
--memory 2 \
|
||||||
|
--restart-policy OnFailure \
|
||||||
|
--environment-variables \
|
||||||
|
DB_HOST=$DB_SERVER_NAME.postgres.database.azure.com \
|
||||||
|
DB_USER=$DB_ADMIN_USER \
|
||||||
|
STORAGE_ACCOUNT=$STORAGE_ACCOUNT \
|
||||||
|
--secure-environment-variables \
|
||||||
|
DB_PASSWORD=$DB_ADMIN_PASSWORD \
|
||||||
|
STORAGE_KEY=$STORAGE_KEY \
|
||||||
|
--azure-file-volume-account-name $STORAGE_ACCOUNT \
|
||||||
|
--azure-file-volume-account-key $STORAGE_KEY \
|
||||||
|
--azure-file-volume-share-name erpnext-sites \
|
||||||
|
--azure-file-volume-mount-path /home/frappe/frappe-bench/sites \
|
||||||
|
--command-line "/bin/bash -c 'bench --site frontend backup && az storage blob upload --account-name \$STORAGE_ACCOUNT --account-key \$STORAGE_KEY --container-name erpnext-backups --file /home/frappe/frappe-bench/sites/frontend/private/backups/*.sql.gz --name backup-$(date +%Y%m%d).sql.gz'" \
|
||||||
|
--subnet /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Network/virtualNetworks/erpnext-vnet/subnets/aci-subnet
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 Troubleshooting
|
||||||
|
|
||||||
|
### Container Health Issues
|
||||||
|
```bash
|
||||||
|
# Check container status
|
||||||
|
az container show \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-backend \
|
||||||
|
--query containers[].instanceView.currentState
|
||||||
|
|
||||||
|
# Restart container group
|
||||||
|
az container restart \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-backend
|
||||||
|
|
||||||
|
# Execute commands in container
|
||||||
|
az container exec \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-backend \
|
||||||
|
--container-name backend \
|
||||||
|
--exec-command "/bin/bash"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Network Connectivity Issues
|
||||||
|
```bash
|
||||||
|
# Test database connectivity from container
|
||||||
|
az container exec \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-backend \
|
||||||
|
--container-name backend \
|
||||||
|
--exec-command "pg_isready -h $DB_SERVER_NAME.postgres.database.azure.com -U $DB_ADMIN_USER"
|
||||||
|
|
||||||
|
# Test Redis connectivity
|
||||||
|
az container exec \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-backend \
|
||||||
|
--container-name backend \
|
||||||
|
--exec-command "redis-cli -h $REDIS_HOST -a $REDIS_KEY ping"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Storage Issues
|
||||||
|
```bash
|
||||||
|
# Check file share usage
|
||||||
|
az storage share show \
|
||||||
|
--name erpnext-sites \
|
||||||
|
--account-name $STORAGE_ACCOUNT \
|
||||||
|
--query "properties.quota"
|
||||||
|
|
||||||
|
# List files in share
|
||||||
|
az storage file list \
|
||||||
|
--share-name erpnext-sites \
|
||||||
|
--account-name $STORAGE_ACCOUNT \
|
||||||
|
--account-key $STORAGE_KEY
|
||||||
|
```
|
||||||
|
|
||||||
|
## 💰 Cost Optimization
|
||||||
|
|
||||||
|
### 1. Use Spot Instances (Preview)
|
||||||
|
```bash
|
||||||
|
# Deploy with spot instances for non-critical workloads
|
||||||
|
az container create \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name erpnext-worker-spot \
|
||||||
|
--image $ACR_LOGIN_SERVER/erpnext-azure:v14 \
|
||||||
|
--cpu 1 \
|
||||||
|
--memory 2 \
|
||||||
|
--priority Spot \
|
||||||
|
--eviction-policy Delete
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Optimize Container Sizes
|
||||||
|
```bash
|
||||||
|
# Monitor actual resource usage
|
||||||
|
az monitor metrics list \
|
||||||
|
--resource /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.ContainerInstance/containerGroups/erpnext-backend \
|
||||||
|
--metric-names CpuUsage MemoryUsage \
|
||||||
|
--aggregation Average \
|
||||||
|
--interval PT1H
|
||||||
|
|
||||||
|
# Adjust container sizes based on usage
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Schedule Scaling
|
||||||
|
```bash
|
||||||
|
# Use Azure Functions to scale down during off-hours
|
||||||
|
# Create timer-triggered function to stop/start containers
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Production Considerations
|
||||||
|
|
||||||
|
1. **High Availability**: Deploy multiple container groups across availability zones
|
||||||
|
2. **Disaster Recovery**: Set up geo-replication for database and storage
|
||||||
|
3. **Security**: Use managed identities instead of keys where possible
|
||||||
|
4. **Monitoring**: Set up comprehensive dashboards in Azure Monitor
|
||||||
|
5. **Compliance**: Enable Azure Policy for compliance enforcement
|
||||||
|
|
||||||
|
## 📋 Verification Checklist
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check all container groups are running
|
||||||
|
az container list --resource-group $RESOURCE_GROUP --output table
|
||||||
|
|
||||||
|
# Verify application is accessible
|
||||||
|
curl -I http://$FRONTEND_FQDN:8080
|
||||||
|
|
||||||
|
# Check database connectivity
|
||||||
|
az postgres flexible-server show \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name $DB_SERVER_NAME \
|
||||||
|
--query state
|
||||||
|
|
||||||
|
# Verify Redis is accessible
|
||||||
|
az redis show \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name $REDIS_NAME \
|
||||||
|
--query provisioningState
|
||||||
|
|
||||||
|
# Check storage account
|
||||||
|
az storage account show \
|
||||||
|
--resource-group $RESOURCE_GROUP \
|
||||||
|
--name $STORAGE_ACCOUNT \
|
||||||
|
--query provisioningState
|
||||||
|
```
|
||||||
|
|
||||||
|
## ➡️ Next Steps
|
||||||
|
|
||||||
|
1. Configure custom domain and SSL certificate
|
||||||
|
2. Set up continuous deployment from Azure DevOps/GitHub
|
||||||
|
3. Implement comprehensive monitoring and alerting
|
||||||
|
4. Configure backup and disaster recovery procedures
|
||||||
|
5. Review and implement production hardening (see `03-production-managed-setup.md`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**⚠️ Important Notes**:
|
||||||
|
- Container Instances have a maximum of 4 vCPUs and 16GB RAM per container
|
||||||
|
- For larger deployments, consider using AKS instead
|
||||||
|
- Monitor costs as Container Instances are billed per second
|
||||||
|
- Implement proper secret management using Key Vault
|
||||||
|
- Regular security updates for container images are essential
|
||||||
File diff suppressed because it is too large
Load Diff
335
documentation/deployment-guides/azure-managed/README.md
Normal file
335
documentation/deployment-guides/azure-managed/README.md
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
# ERPNext Azure Deployment with Managed Services
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This directory contains comprehensive guides and resources for deploying ERPNext on Microsoft Azure using **managed database services**: Azure Database for MySQL/PostgreSQL and Azure Cache for Redis. This approach provides better reliability, security, and operational efficiency compared to self-hosted databases.
|
||||||
|
|
||||||
|
## 🏗️ Architecture Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Microsoft Azure │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||||||
|
│ │ Container │ │ AKS │ │
|
||||||
|
│ │ Instances │ │ (Kubernetes) │ │
|
||||||
|
│ │ (Serverless) │ │ │ │
|
||||||
|
│ │ │ │ ┌─────────────┐ │ │
|
||||||
|
│ │ ┌─────────────┐ │ │ │ Pods │ │ │
|
||||||
|
│ │ │ Frontend │ │ │ │ - Frontend │ │ │
|
||||||
|
│ │ │ Backend │ │ │ │ - Backend │ │ │
|
||||||
|
│ │ │ Workers │ │ │ │ - Workers │ │ │
|
||||||
|
│ │ └─────────────┘ │ │ └─────────────┘ │ │
|
||||||
|
│ └─────────────────┘ └─────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌─────────────────────────────┼─────────────────────────────┐ │
|
||||||
|
│ │ Managed Services │ │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐ │ │
|
||||||
|
│ │ │ Azure DB for │ │ Azure Cache │ │Azure Storage │ │ │
|
||||||
|
│ │ │ PostgreSQL │ │ for Redis │ │ (Blobs) │ │ │
|
||||||
|
│ │ └──────────────┘ └─────────────┘ └──────────────┘ │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📁 Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
azure-managed/
|
||||||
|
├── README.md # This file
|
||||||
|
├── 00-prerequisites-managed.md # Prerequisites for managed services
|
||||||
|
├── 01-aks-managed-deployment.md # AKS with managed databases
|
||||||
|
├── 02-container-instances-deployment.md # Container Instances deployment
|
||||||
|
├── 03-production-managed-setup.md # Production hardening
|
||||||
|
├── kubernetes-manifests/ # K8s manifests for managed services
|
||||||
|
│ ├── namespace.yaml # Namespace with resource quotas
|
||||||
|
│ ├── storage.yaml # Application file storage
|
||||||
|
│ ├── configmap.yaml # Config for managed services
|
||||||
|
│ ├── secrets.yaml # Key Vault integration
|
||||||
|
│ ├── erpnext-backend.yaml # Backend deployment
|
||||||
|
│ ├── erpnext-frontend.yaml # Frontend deployment
|
||||||
|
│ ├── erpnext-workers.yaml # Workers deployment
|
||||||
|
│ ├── ingress.yaml # Application Gateway Ingress
|
||||||
|
│ └── jobs.yaml # Site creation and backup jobs
|
||||||
|
└── scripts/ # Automation scripts
|
||||||
|
├── deploy-managed.sh # AKS deployment script
|
||||||
|
└── container-instances-deploy.sh # Container Instances script
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
### Option 1: AKS with Managed Services (Recommended for Production)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Complete prerequisites
|
||||||
|
cd azure-managed/
|
||||||
|
# Follow 00-prerequisites-managed.md
|
||||||
|
|
||||||
|
# 2. Deploy to AKS
|
||||||
|
cd scripts/
|
||||||
|
export RESOURCE_GROUP="erpnext-rg"
|
||||||
|
export DOMAIN="erpnext.yourdomain.com"
|
||||||
|
export EMAIL="admin@yourdomain.com"
|
||||||
|
./deploy-managed.sh deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Azure Container Instances Deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Complete prerequisites
|
||||||
|
cd azure-managed/
|
||||||
|
# Follow 00-prerequisites-managed.md
|
||||||
|
|
||||||
|
# 2. Deploy to Container Instances
|
||||||
|
cd scripts/
|
||||||
|
export RESOURCE_GROUP="erpnext-rg"
|
||||||
|
export DOMAIN="erpnext.yourdomain.com"
|
||||||
|
./container-instances-deploy.sh deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Key Benefits of Managed Services
|
||||||
|
|
||||||
|
### 🛡️ Enhanced Reliability
|
||||||
|
- **99.99% SLA** for Azure Database and Azure Cache
|
||||||
|
- **Automatic failover** and disaster recovery
|
||||||
|
- **Point-in-time restore** for databases (up to 35 days)
|
||||||
|
- **Automated backups** with geo-redundant storage
|
||||||
|
|
||||||
|
### 🔧 Operational Efficiency
|
||||||
|
- **Zero database administration** overhead
|
||||||
|
- **Automatic security patches** and updates
|
||||||
|
- **Performance recommendations** via Azure Advisor
|
||||||
|
- **Built-in monitoring** with Azure Monitor
|
||||||
|
|
||||||
|
### 🔒 Enterprise Security
|
||||||
|
- **Private endpoints** within VNet
|
||||||
|
- **Encryption at rest and in transit** by default
|
||||||
|
- **Azure AD integration** for authentication
|
||||||
|
- **Audit logging** and threat detection
|
||||||
|
|
||||||
|
### 💰 Cost Optimization
|
||||||
|
- **Reserved capacity** discounts (up to 65% savings)
|
||||||
|
- **Automatic scaling** for compute and storage
|
||||||
|
- **Serverless options** for variable workloads
|
||||||
|
- **Cost management** recommendations
|
||||||
|
|
||||||
|
## 📊 Deployment Options Comparison
|
||||||
|
|
||||||
|
| Feature | AKS + Managed DB | Container Instances + Managed DB | Self-Hosted DB |
|
||||||
|
|---------|------------------|----------------------------------|-----------------|
|
||||||
|
| **Scalability** | Manual/Auto HPA | Manual (1-100 instances) | Manual |
|
||||||
|
| **Operational Overhead** | Medium | Very Low | High |
|
||||||
|
| **Database Reliability** | 99.99% SLA | 99.99% SLA | Depends on setup |
|
||||||
|
| **Cost (Small)** | ~$400/month | ~$180/month | ~$280/month |
|
||||||
|
| **Cost (Large)** | ~$750/month | ~$380/month | ~$550/month |
|
||||||
|
| **Cold Start** | None | 5-10 seconds | None |
|
||||||
|
| **Customization** | High | Medium | Very High |
|
||||||
|
| **Multi-tenancy** | Supported | Limited | Supported |
|
||||||
|
|
||||||
|
## 🛠️ Managed Services Configuration
|
||||||
|
|
||||||
|
### Azure Database for PostgreSQL
|
||||||
|
- **Tiers**: Basic, General Purpose, Memory Optimized
|
||||||
|
- **Compute**: 1-64 vCores, Burstable options available
|
||||||
|
- **Storage**: 5GB to 16TB, automatic growth
|
||||||
|
- **Backup**: Automated daily backups with 7-35 day retention
|
||||||
|
- **High Availability**: Zone redundant deployment
|
||||||
|
- **Security**: Private endpoints, Azure AD auth, TLS 1.2+
|
||||||
|
|
||||||
|
### Azure Cache for Redis
|
||||||
|
- **Tiers**: Basic (1GB-53GB), Standard (250MB-53GB), Premium (6GB-1.2TB)
|
||||||
|
- **Features**: Persistence, clustering, geo-replication
|
||||||
|
- **Performance**: Up to 2,000,000 requests/second
|
||||||
|
- **Monitoring**: Azure Monitor integration
|
||||||
|
|
||||||
|
### Additional Services
|
||||||
|
- **Azure Storage**: Blob storage for files and static assets
|
||||||
|
- **Azure Key Vault**: Secure credential management
|
||||||
|
- **Virtual Network**: Private networking with service endpoints
|
||||||
|
- **Azure Logic Apps**: Workflow automation
|
||||||
|
- **Azure Functions**: Serverless compute for background jobs
|
||||||
|
|
||||||
|
## 🔧 Advanced Features
|
||||||
|
|
||||||
|
### Auto-scaling Configuration
|
||||||
|
- **AKS**: Horizontal Pod Autoscaler and Cluster Autoscaler
|
||||||
|
- **Container Instances**: Manual scaling with container groups
|
||||||
|
- **Database**: Automatic storage scaling, manual compute scaling
|
||||||
|
- **Redis**: Manual scaling with zero downtime
|
||||||
|
|
||||||
|
### Security Hardening
|
||||||
|
- **Network isolation** with VNet and NSGs
|
||||||
|
- **Managed Identity** for secure Azure API access
|
||||||
|
- **Key Vault integration** for secrets management
|
||||||
|
- **Network policies** for pod-to-pod communication
|
||||||
|
- **Azure Policy** for compliance enforcement
|
||||||
|
|
||||||
|
### Monitoring & Observability
|
||||||
|
- **Azure Monitor** integration for logs and metrics
|
||||||
|
- **Application Insights** for application performance
|
||||||
|
- **Log Analytics** workspace for centralized logging
|
||||||
|
- **Azure Dashboards** for visualization
|
||||||
|
- **Alert rules** for proactive monitoring
|
||||||
|
|
||||||
|
### Backup & Disaster Recovery
|
||||||
|
- **Database**: Automated backups with geo-redundancy
|
||||||
|
- **Application files**: Automated backup to Azure Storage
|
||||||
|
- **Cross-region replication** for disaster recovery
|
||||||
|
- **Azure Site Recovery** for full DR solution
|
||||||
|
|
||||||
|
## 💰 Cost Estimation & Optimization
|
||||||
|
|
||||||
|
### Typical Monthly Costs (East US)
|
||||||
|
|
||||||
|
#### Small Deployment (< 50 users)
|
||||||
|
```
|
||||||
|
Azure Database (B_Gen5_2): $73
|
||||||
|
Azure Cache Redis (C1): $61
|
||||||
|
Container Instances (2 vCPU): $73
|
||||||
|
Azure Storage (50GB): $1
|
||||||
|
Application Gateway: $18
|
||||||
|
Total: ~$226/month
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Medium Deployment (50-200 users)
|
||||||
|
```
|
||||||
|
Azure Database (GP_Gen5_4): $292
|
||||||
|
Azure Cache Redis (C3): $244
|
||||||
|
AKS (3 D4s_v3 nodes): $384
|
||||||
|
Azure Storage (200GB): $4
|
||||||
|
Application Gateway: $18
|
||||||
|
Total: ~$942/month
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Large Deployment (200+ users)
|
||||||
|
```
|
||||||
|
Azure Database (GP_Gen5_8): $584
|
||||||
|
Azure Cache Redis (P1): $443
|
||||||
|
AKS (6 D4s_v3 nodes): $768
|
||||||
|
Azure Storage (500GB): $10
|
||||||
|
Application Gateway: $18
|
||||||
|
Total: ~$1,823/month
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cost Optimization Strategies
|
||||||
|
1. **Use reserved instances** (up to 65% savings)
|
||||||
|
2. **Right-size resources** based on monitoring data
|
||||||
|
3. **Use spot instances** for non-critical workloads
|
||||||
|
4. **Implement lifecycle management** for blob storage
|
||||||
|
5. **Use Azure Hybrid Benefit** if you have existing licenses
|
||||||
|
|
||||||
|
## 🚨 Migration Path from Self-Hosted
|
||||||
|
|
||||||
|
### Phase 1: Assessment (Week 1)
|
||||||
|
- [ ] Audit current database size and performance
|
||||||
|
- [ ] Identify custom configurations and extensions
|
||||||
|
- [ ] Plan migration windows and rollback procedures
|
||||||
|
- [ ] Set up managed services in parallel
|
||||||
|
|
||||||
|
### Phase 2: Data Migration (Week 2)
|
||||||
|
- [ ] Export data from existing MySQL/Redis
|
||||||
|
- [ ] Use Azure Database Migration Service
|
||||||
|
- [ ] Validate data integrity and performance
|
||||||
|
- [ ] Update connection strings and test
|
||||||
|
|
||||||
|
### Phase 3: Application Migration (Week 3)
|
||||||
|
- [ ] Deploy ERPNext with managed services
|
||||||
|
- [ ] Migrate file storage to Azure Storage
|
||||||
|
- [ ] Update backup procedures
|
||||||
|
- [ ] Conduct full testing
|
||||||
|
|
||||||
|
### Phase 4: Cutover and Optimization (Week 4)
|
||||||
|
- [ ] DNS cutover to new deployment
|
||||||
|
- [ ] Monitor performance and costs
|
||||||
|
- [ ] Optimize resource allocation
|
||||||
|
- [ ] Decommission old infrastructure
|
||||||
|
|
||||||
|
## 🔍 Troubleshooting Common Issues
|
||||||
|
|
||||||
|
### Database Connection Issues
|
||||||
|
```bash
|
||||||
|
# Test connectivity from AKS
|
||||||
|
kubectl run pg-test --rm -i --tty --image=postgres:13 -- psql -h your-db.postgres.database.azure.com -U erpnext@your-db -d erpnext
|
||||||
|
|
||||||
|
# Check connection from Container Instances
|
||||||
|
az container exec --resource-group erpnext-rg --name erpnext-backend --exec-command "psql -h your-db.postgres.database.azure.com -U erpnext@your-db -d erpnext"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Redis Connection Issues
|
||||||
|
```bash
|
||||||
|
# Test Redis connectivity
|
||||||
|
kubectl run redis-test --rm -i --tty --image=redis:alpine -- redis-cli -h your-cache.redis.cache.windows.net -a your-access-key ping
|
||||||
|
|
||||||
|
# Check Redis metrics
|
||||||
|
az redis show --name your-cache --resource-group erpnext-rg
|
||||||
|
```
|
||||||
|
|
||||||
|
### Performance Issues
|
||||||
|
```bash
|
||||||
|
# Check database performance
|
||||||
|
az postgres server show --resource-group erpnext-rg --name your-db
|
||||||
|
|
||||||
|
# Monitor Redis memory usage
|
||||||
|
az redis show --name your-cache --resource-group erpnext-rg --query "redisConfiguration"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Additional Resources
|
||||||
|
|
||||||
|
### Azure Documentation
|
||||||
|
- [Azure Database for PostgreSQL Best Practices](https://docs.microsoft.com/azure/postgresql/concepts-best-practices)
|
||||||
|
- [Azure Cache for Redis Best Practices](https://docs.microsoft.com/azure/azure-cache-for-redis/cache-best-practices)
|
||||||
|
- [AKS Best Practices](https://docs.microsoft.com/azure/aks/best-practices)
|
||||||
|
- [Container Instances Overview](https://docs.microsoft.com/azure/container-instances/container-instances-overview)
|
||||||
|
|
||||||
|
### ERPNext Specific
|
||||||
|
- [ERPNext Database Configuration](https://docs.erpnext.com/docs/user/manual/en/setting-up/database-setup)
|
||||||
|
- [Performance Optimization](https://docs.erpnext.com/docs/user/manual/en/setting-up/performance)
|
||||||
|
- [Backup Strategies](https://docs.erpnext.com/docs/user/manual/en/setting-up/backup)
|
||||||
|
|
||||||
|
### Monitoring & Operations
|
||||||
|
- [Azure Monitor Documentation](https://docs.microsoft.com/azure/azure-monitor/)
|
||||||
|
- [Application Insights](https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview)
|
||||||
|
- [Log Analytics](https://docs.microsoft.com/azure/azure-monitor/logs/log-analytics-overview)
|
||||||
|
|
||||||
|
## 🎯 Decision Matrix
|
||||||
|
|
||||||
|
### Choose AKS + Managed Services if:
|
||||||
|
- ✅ Need full control over application deployment
|
||||||
|
- ✅ Require complex networking or multi-tenancy
|
||||||
|
- ✅ Have existing Kubernetes expertise
|
||||||
|
- ✅ Need consistent performance with no cold starts
|
||||||
|
- ✅ Plan to run multiple applications in same cluster
|
||||||
|
|
||||||
|
### Choose Container Instances + Managed Services if:
|
||||||
|
- ✅ Want minimal operational overhead
|
||||||
|
- ✅ Have predictable traffic patterns
|
||||||
|
- ✅ Need simple deployment model
|
||||||
|
- ✅ Want to minimize costs for smaller deployments
|
||||||
|
- ✅ Prefer serverless-like simplicity
|
||||||
|
|
||||||
|
## 📞 Support & Contributing
|
||||||
|
|
||||||
|
### Getting Help
|
||||||
|
- **Documentation Issues**: Create issues in the repository
|
||||||
|
- **Deployment Support**: Follow troubleshooting guides
|
||||||
|
- **Performance Issues**: Check Azure Monitor dashboards
|
||||||
|
- **Cost Optimization**: Use Azure Cost Management
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
- **Documentation improvements**: Submit pull requests
|
||||||
|
- **Script enhancements**: Share automation improvements
|
||||||
|
- **Best practices**: Contribute lessons learned
|
||||||
|
- **Cost optimizations**: Share optimization strategies
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**⚠️ Important Notes**:
|
||||||
|
- Managed services incur continuous costs even when not in use
|
||||||
|
- Always test deployments in staging before production
|
||||||
|
- Monitor costs and usage regularly with Azure Cost Management
|
||||||
|
- Keep credentials secure in Azure Key Vault
|
||||||
|
- Follow Azure security best practices
|
||||||
|
|
||||||
|
**🎯 Recommendation**: For most production deployments, AKS with managed services provides the best balance of control, reliability, and operational efficiency.
|
||||||
@ -0,0 +1,190 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: erpnext-config
|
||||||
|
namespace: erpnext
|
||||||
|
data:
|
||||||
|
# Database configuration for Azure Database for PostgreSQL
|
||||||
|
DB_TYPE: "postgres"
|
||||||
|
DB_HOST: "${DB_SERVER_NAME}.postgres.database.azure.com"
|
||||||
|
DB_PORT: "5432"
|
||||||
|
DB_NAME: "erpnext"
|
||||||
|
DB_USER: "${DB_ADMIN_USER}"
|
||||||
|
|
||||||
|
# Redis configuration for Azure Cache for Redis
|
||||||
|
REDIS_HOST: "${REDIS_HOST}"
|
||||||
|
REDIS_PORT: "6380"
|
||||||
|
REDIS_USE_SSL: "true"
|
||||||
|
|
||||||
|
# Azure Storage configuration
|
||||||
|
STORAGE_ACCOUNT: "${STORAGE_ACCOUNT}"
|
||||||
|
STORAGE_CONTAINER: "erpnext-files"
|
||||||
|
STORAGE_BACKUP_CONTAINER: "erpnext-backups"
|
||||||
|
|
||||||
|
# Application configuration
|
||||||
|
FRAPPE_SITE_NAME_HEADER: "frontend"
|
||||||
|
WORKER_CLASS: "gthread"
|
||||||
|
GUNICORN_WORKERS: "4"
|
||||||
|
GUNICORN_THREADS: "4"
|
||||||
|
GUNICORN_TIMEOUT: "120"
|
||||||
|
GUNICORN_LOGLEVEL: "info"
|
||||||
|
|
||||||
|
# Environment settings
|
||||||
|
ENVIRONMENT: "production"
|
||||||
|
DEVELOPER_MODE: "0"
|
||||||
|
ALLOW_TESTS: "0"
|
||||||
|
|
||||||
|
# Performance settings
|
||||||
|
REDIS_CACHE_EXPIRES_IN_SEC: "3600"
|
||||||
|
REDIS_QUEUE_EXPIRES_IN_SEC: "604800"
|
||||||
|
|
||||||
|
# Security settings
|
||||||
|
ENCRYPTION_KEY: "${ENCRYPTION_KEY}"
|
||||||
|
FORCE_HTTPS: "1"
|
||||||
|
SESSION_COOKIE_SECURE: "1"
|
||||||
|
|
||||||
|
# Monitoring
|
||||||
|
ENABLE_MONITORING: "1"
|
||||||
|
APPLICATIONINSIGHTS_CONNECTION_STRING: "InstrumentationKey=${INSTRUMENTATION_KEY}"
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: nginx-config
|
||||||
|
namespace: erpnext
|
||||||
|
data:
|
||||||
|
nginx.conf: |
|
||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 4096;
|
||||||
|
use epoll;
|
||||||
|
multi_accept on;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
tcp_nopush on;
|
||||||
|
tcp_nodelay on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
types_hash_max_size 2048;
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
# Compression
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
gzip_types text/plain text/css text/xml text/javascript
|
||||||
|
application/json application/javascript application/xml+rss;
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
|
||||||
|
# Rate limiting
|
||||||
|
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
|
||||||
|
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
|
||||||
|
|
||||||
|
upstream backend {
|
||||||
|
least_conn;
|
||||||
|
server erpnext-backend:8000 max_fails=3 fail_timeout=30s;
|
||||||
|
keepalive 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream websocket {
|
||||||
|
server erpnext-websocket:9000;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
|
||||||
|
client_max_body_size 50M;
|
||||||
|
client_body_buffer_size 16K;
|
||||||
|
client_header_buffer_size 1K;
|
||||||
|
large_client_header_buffers 4 16K;
|
||||||
|
|
||||||
|
# Security
|
||||||
|
location ~ /\. {
|
||||||
|
deny all;
|
||||||
|
access_log off;
|
||||||
|
log_not_found off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Static files
|
||||||
|
location /assets {
|
||||||
|
alias /usr/share/nginx/html/assets;
|
||||||
|
expires 7d;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
|
location /files {
|
||||||
|
alias /usr/share/nginx/html/files;
|
||||||
|
expires 7d;
|
||||||
|
}
|
||||||
|
|
||||||
|
# WebSocket
|
||||||
|
location /socket.io {
|
||||||
|
proxy_pass http://websocket;
|
||||||
|
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_read_timeout 86400;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Application
|
||||||
|
location / {
|
||||||
|
limit_req zone=general burst=20 nodelay;
|
||||||
|
|
||||||
|
proxy_pass http://backend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
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_set_header X-Frappe-Site-Name frontend;
|
||||||
|
|
||||||
|
proxy_connect_timeout 120s;
|
||||||
|
proxy_send_timeout 120s;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
|
||||||
|
proxy_buffer_size 4K;
|
||||||
|
proxy_buffers 8 4K;
|
||||||
|
proxy_busy_buffers_size 8K;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Login rate limiting
|
||||||
|
location ~ ^/(api/method/login|api/method/logout) {
|
||||||
|
limit_req zone=login burst=5 nodelay;
|
||||||
|
proxy_pass http://backend;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
location /health {
|
||||||
|
access_log off;
|
||||||
|
return 200 "healthy\n";
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,291 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: erpnext-backend
|
||||||
|
namespace: erpnext
|
||||||
|
labels:
|
||||||
|
app: erpnext-backend
|
||||||
|
component: backend
|
||||||
|
version: v14
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
revisionHistoryLimit: 5
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 1
|
||||||
|
maxUnavailable: 0
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: erpnext-backend
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: erpnext-backend
|
||||||
|
component: backend
|
||||||
|
azure.workload.identity/use: "true"
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: "8000"
|
||||||
|
prometheus.io/path: "/metrics"
|
||||||
|
spec:
|
||||||
|
serviceAccountName: erpnext-sa
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- weight: 100
|
||||||
|
podAffinityTerm:
|
||||||
|
labelSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- key: app
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- erpnext-backend
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
volumes:
|
||||||
|
- name: sites
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: erpnext-sites
|
||||||
|
- name: assets
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: erpnext-assets
|
||||||
|
- name: logs
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: erpnext-logs
|
||||||
|
- name: secrets-store
|
||||||
|
csi:
|
||||||
|
driver: secrets-store.csi.k8s.io
|
||||||
|
readOnly: true
|
||||||
|
volumeAttributes:
|
||||||
|
secretProviderClass: erpnext-secrets
|
||||||
|
initContainers:
|
||||||
|
- name: wait-for-db
|
||||||
|
image: postgres:13-alpine
|
||||||
|
command: ['sh', '-c', 'until pg_isready -h ${DB_HOST} -p 5432; do echo waiting for database; sleep 2; done;']
|
||||||
|
env:
|
||||||
|
- name: DB_HOST
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_HOST
|
||||||
|
containers:
|
||||||
|
- name: backend
|
||||||
|
image: frappe/erpnext-worker:v14
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8000
|
||||||
|
name: http
|
||||||
|
protocol: TCP
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
- name: assets
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites/assets
|
||||||
|
- name: logs
|
||||||
|
mountPath: /home/frappe/frappe-bench/logs
|
||||||
|
- name: secrets-store
|
||||||
|
mountPath: /mnt/secrets-store
|
||||||
|
readOnly: true
|
||||||
|
env:
|
||||||
|
- name: DB_TYPE
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_TYPE
|
||||||
|
- name: DB_HOST
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_HOST
|
||||||
|
- name: DB_PORT
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_PORT
|
||||||
|
- name: DB_NAME
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_NAME
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_USER
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: erpnext-secrets
|
||||||
|
key: db-password
|
||||||
|
- name: REDIS_HOST
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: REDIS_HOST
|
||||||
|
- name: REDIS_PORT
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: REDIS_PORT
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: erpnext-secrets
|
||||||
|
key: redis-key
|
||||||
|
- name: REDIS_CACHE
|
||||||
|
value: "rediss://:$(REDIS_PASSWORD)@$(REDIS_HOST):$(REDIS_PORT)/0?ssl_cert_reqs=required"
|
||||||
|
- name: REDIS_QUEUE
|
||||||
|
value: "rediss://:$(REDIS_PASSWORD)@$(REDIS_HOST):$(REDIS_PORT)/1?ssl_cert_reqs=required"
|
||||||
|
- name: REDIS_SOCKETIO
|
||||||
|
value: "rediss://:$(REDIS_PASSWORD)@$(REDIS_HOST):$(REDIS_PORT)/2?ssl_cert_reqs=required"
|
||||||
|
- name: ADMIN_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: erpnext-secrets
|
||||||
|
key: admin-password
|
||||||
|
- name: ENCRYPTION_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: erpnext-secrets
|
||||||
|
key: encryption-key
|
||||||
|
- name: WORKER_CLASS
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: WORKER_CLASS
|
||||||
|
- name: GUNICORN_WORKERS
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: GUNICORN_WORKERS
|
||||||
|
- name: GUNICORN_THREADS
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: GUNICORN_THREADS
|
||||||
|
- name: GUNICORN_TIMEOUT
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: GUNICORN_TIMEOUT
|
||||||
|
- name: APPLICATIONINSIGHTS_CONNECTION_STRING
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: APPLICATIONINSIGHTS_CONNECTION_STRING
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 1Gi
|
||||||
|
limits:
|
||||||
|
cpu: 2000m
|
||||||
|
memory: 4Gi
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/method/ping
|
||||||
|
port: 8000
|
||||||
|
httpHeaders:
|
||||||
|
- name: X-Frappe-Site-Name
|
||||||
|
value: frontend
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/method/ping
|
||||||
|
port: 8000
|
||||||
|
httpHeaders:
|
||||||
|
- name: X-Frappe-Site-Name
|
||||||
|
value: frontend
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
lifecycle:
|
||||||
|
preStop:
|
||||||
|
exec:
|
||||||
|
command: ["/bin/sh", "-c", "sleep 15"]
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: acr-secret
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: erpnext-backend
|
||||||
|
namespace: erpnext
|
||||||
|
labels:
|
||||||
|
app: erpnext-backend
|
||||||
|
annotations:
|
||||||
|
service.beta.kubernetes.io/azure-load-balancer-internal: "true"
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
sessionAffinity: ClientIP
|
||||||
|
sessionAffinityConfig:
|
||||||
|
clientIP:
|
||||||
|
timeoutSeconds: 10800
|
||||||
|
ports:
|
||||||
|
- port: 8000
|
||||||
|
targetPort: 8000
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
app: erpnext-backend
|
||||||
|
---
|
||||||
|
apiVersion: autoscaling/v2
|
||||||
|
kind: HorizontalPodAutoscaler
|
||||||
|
metadata:
|
||||||
|
name: erpnext-backend-hpa
|
||||||
|
namespace: erpnext
|
||||||
|
spec:
|
||||||
|
scaleTargetRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: erpnext-backend
|
||||||
|
minReplicas: 3
|
||||||
|
maxReplicas: 15
|
||||||
|
metrics:
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: cpu
|
||||||
|
target:
|
||||||
|
type: Utilization
|
||||||
|
averageUtilization: 70
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: memory
|
||||||
|
target:
|
||||||
|
type: Utilization
|
||||||
|
averageUtilization: 80
|
||||||
|
behavior:
|
||||||
|
scaleUp:
|
||||||
|
stabilizationWindowSeconds: 60
|
||||||
|
policies:
|
||||||
|
- type: Percent
|
||||||
|
value: 50
|
||||||
|
periodSeconds: 60
|
||||||
|
- type: Pods
|
||||||
|
value: 2
|
||||||
|
periodSeconds: 60
|
||||||
|
selectPolicy: Max
|
||||||
|
scaleDown:
|
||||||
|
stabilizationWindowSeconds: 300
|
||||||
|
policies:
|
||||||
|
- type: Percent
|
||||||
|
value: 25
|
||||||
|
periodSeconds: 60
|
||||||
|
- type: Pods
|
||||||
|
value: 1
|
||||||
|
periodSeconds: 60
|
||||||
|
selectPolicy: Min
|
||||||
|
---
|
||||||
|
apiVersion: policy/v1
|
||||||
|
kind: PodDisruptionBudget
|
||||||
|
metadata:
|
||||||
|
name: erpnext-backend-pdb
|
||||||
|
namespace: erpnext
|
||||||
|
spec:
|
||||||
|
minAvailable: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: erpnext-backend
|
||||||
@ -0,0 +1,194 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: erpnext-frontend
|
||||||
|
namespace: erpnext
|
||||||
|
labels:
|
||||||
|
app: erpnext-frontend
|
||||||
|
component: frontend
|
||||||
|
version: v14
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
revisionHistoryLimit: 5
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 1
|
||||||
|
maxUnavailable: 0
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: erpnext-frontend
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: erpnext-frontend
|
||||||
|
component: frontend
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: "8080"
|
||||||
|
prometheus.io/path: "/metrics"
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- weight: 100
|
||||||
|
podAffinityTerm:
|
||||||
|
labelSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- key: app
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- erpnext-frontend
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
volumes:
|
||||||
|
- name: sites
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: erpnext-sites
|
||||||
|
- name: assets
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: erpnext-assets
|
||||||
|
- name: nginx-config
|
||||||
|
configMap:
|
||||||
|
name: nginx-config
|
||||||
|
containers:
|
||||||
|
- name: frontend
|
||||||
|
image: frappe/erpnext-nginx:v14
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
name: http
|
||||||
|
protocol: TCP
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
readOnly: true
|
||||||
|
- name: assets
|
||||||
|
mountPath: /usr/share/nginx/html/assets
|
||||||
|
readOnly: true
|
||||||
|
- name: nginx-config
|
||||||
|
mountPath: /etc/nginx/nginx.conf
|
||||||
|
subPath: nginx.conf
|
||||||
|
env:
|
||||||
|
- name: BACKEND
|
||||||
|
value: erpnext-backend:8000
|
||||||
|
- name: FRAPPE_SITE_NAME_HEADER
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: FRAPPE_SITE_NAME_HEADER
|
||||||
|
- name: SOCKETIO
|
||||||
|
value: erpnext-websocket:9000
|
||||||
|
- name: UPSTREAM_REAL_IP_ADDRESS
|
||||||
|
value: "127.0.0.1"
|
||||||
|
- name: UPSTREAM_REAL_IP_HEADER
|
||||||
|
value: "X-Forwarded-For"
|
||||||
|
- name: UPSTREAM_REAL_IP_RECURSIVE
|
||||||
|
value: "on"
|
||||||
|
- name: PROXY_READ_TIMEOUT
|
||||||
|
value: "120"
|
||||||
|
- name: CLIENT_MAX_BODY_SIZE
|
||||||
|
value: "50m"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 200m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
lifecycle:
|
||||||
|
preStop:
|
||||||
|
exec:
|
||||||
|
command: ["/bin/sh", "-c", "sleep 15"]
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: acr-secret
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: erpnext-frontend
|
||||||
|
namespace: erpnext
|
||||||
|
labels:
|
||||||
|
app: erpnext-frontend
|
||||||
|
annotations:
|
||||||
|
service.beta.kubernetes.io/azure-load-balancer-internal: "false"
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
sessionAffinity: ClientIP
|
||||||
|
sessionAffinityConfig:
|
||||||
|
clientIP:
|
||||||
|
timeoutSeconds: 10800
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
targetPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
app: erpnext-frontend
|
||||||
|
---
|
||||||
|
apiVersion: autoscaling/v2
|
||||||
|
kind: HorizontalPodAutoscaler
|
||||||
|
metadata:
|
||||||
|
name: erpnext-frontend-hpa
|
||||||
|
namespace: erpnext
|
||||||
|
spec:
|
||||||
|
scaleTargetRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: erpnext-frontend
|
||||||
|
minReplicas: 2
|
||||||
|
maxReplicas: 10
|
||||||
|
metrics:
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: cpu
|
||||||
|
target:
|
||||||
|
type: Utilization
|
||||||
|
averageUtilization: 70
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: memory
|
||||||
|
target:
|
||||||
|
type: Utilization
|
||||||
|
averageUtilization: 80
|
||||||
|
behavior:
|
||||||
|
scaleUp:
|
||||||
|
stabilizationWindowSeconds: 60
|
||||||
|
policies:
|
||||||
|
- type: Percent
|
||||||
|
value: 50
|
||||||
|
periodSeconds: 60
|
||||||
|
selectPolicy: Max
|
||||||
|
scaleDown:
|
||||||
|
stabilizationWindowSeconds: 300
|
||||||
|
policies:
|
||||||
|
- type: Percent
|
||||||
|
value: 25
|
||||||
|
periodSeconds: 60
|
||||||
|
selectPolicy: Min
|
||||||
|
---
|
||||||
|
apiVersion: policy/v1
|
||||||
|
kind: PodDisruptionBudget
|
||||||
|
metadata:
|
||||||
|
name: erpnext-frontend-pdb
|
||||||
|
namespace: erpnext
|
||||||
|
spec:
|
||||||
|
minAvailable: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: erpnext-frontend
|
||||||
@ -0,0 +1,501 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: erpnext-worker-default
|
||||||
|
namespace: erpnext
|
||||||
|
labels:
|
||||||
|
app: erpnext-worker-default
|
||||||
|
component: worker
|
||||||
|
queue: default
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: erpnext-worker-default
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: erpnext-worker-default
|
||||||
|
component: worker
|
||||||
|
queue: default
|
||||||
|
azure.workload.identity/use: "true"
|
||||||
|
spec:
|
||||||
|
serviceAccountName: erpnext-sa
|
||||||
|
volumes:
|
||||||
|
- name: sites
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: erpnext-sites
|
||||||
|
- name: logs
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: erpnext-logs
|
||||||
|
- name: secrets-store
|
||||||
|
csi:
|
||||||
|
driver: secrets-store.csi.k8s.io
|
||||||
|
readOnly: true
|
||||||
|
volumeAttributes:
|
||||||
|
secretProviderClass: erpnext-secrets
|
||||||
|
containers:
|
||||||
|
- name: worker
|
||||||
|
image: frappe/erpnext-worker:v14
|
||||||
|
imagePullPolicy: Always
|
||||||
|
command: ["bench", "worker", "--queue", "default"]
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
- name: logs
|
||||||
|
mountPath: /home/frappe/frappe-bench/logs
|
||||||
|
- name: secrets-store
|
||||||
|
mountPath: /mnt/secrets-store
|
||||||
|
readOnly: true
|
||||||
|
env:
|
||||||
|
- name: DB_TYPE
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_TYPE
|
||||||
|
- name: DB_HOST
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_HOST
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_USER
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: erpnext-secrets
|
||||||
|
key: db-password
|
||||||
|
- name: REDIS_HOST
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: REDIS_HOST
|
||||||
|
- name: REDIS_PORT
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: REDIS_PORT
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: erpnext-secrets
|
||||||
|
key: redis-key
|
||||||
|
- name: REDIS_CACHE
|
||||||
|
value: "rediss://:$(REDIS_PASSWORD)@$(REDIS_HOST):$(REDIS_PORT)/0?ssl_cert_reqs=required"
|
||||||
|
- name: REDIS_QUEUE
|
||||||
|
value: "rediss://:$(REDIS_PASSWORD)@$(REDIS_HOST):$(REDIS_PORT)/1?ssl_cert_reqs=required"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 300m
|
||||||
|
memory: 512Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1000m
|
||||||
|
memory: 1Gi
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: acr-secret
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: erpnext-worker-long
|
||||||
|
namespace: erpnext
|
||||||
|
labels:
|
||||||
|
app: erpnext-worker-long
|
||||||
|
component: worker
|
||||||
|
queue: long
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: erpnext-worker-long
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: erpnext-worker-long
|
||||||
|
component: worker
|
||||||
|
queue: long
|
||||||
|
azure.workload.identity/use: "true"
|
||||||
|
spec:
|
||||||
|
serviceAccountName: erpnext-sa
|
||||||
|
volumes:
|
||||||
|
- name: sites
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: erpnext-sites
|
||||||
|
- name: logs
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: erpnext-logs
|
||||||
|
- name: secrets-store
|
||||||
|
csi:
|
||||||
|
driver: secrets-store.csi.k8s.io
|
||||||
|
readOnly: true
|
||||||
|
volumeAttributes:
|
||||||
|
secretProviderClass: erpnext-secrets
|
||||||
|
containers:
|
||||||
|
- name: worker
|
||||||
|
image: frappe/erpnext-worker:v14
|
||||||
|
imagePullPolicy: Always
|
||||||
|
command: ["bench", "worker", "--queue", "long"]
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
- name: logs
|
||||||
|
mountPath: /home/frappe/frappe-bench/logs
|
||||||
|
- name: secrets-store
|
||||||
|
mountPath: /mnt/secrets-store
|
||||||
|
readOnly: true
|
||||||
|
env:
|
||||||
|
- name: DB_TYPE
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_TYPE
|
||||||
|
- name: DB_HOST
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_HOST
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_USER
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: erpnext-secrets
|
||||||
|
key: db-password
|
||||||
|
- name: REDIS_HOST
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: REDIS_HOST
|
||||||
|
- name: REDIS_PORT
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: REDIS_PORT
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: erpnext-secrets
|
||||||
|
key: redis-key
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 300m
|
||||||
|
memory: 512Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1000m
|
||||||
|
memory: 2Gi
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: acr-secret
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: erpnext-worker-short
|
||||||
|
namespace: erpnext
|
||||||
|
labels:
|
||||||
|
app: erpnext-worker-short
|
||||||
|
component: worker
|
||||||
|
queue: short
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: erpnext-worker-short
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: erpnext-worker-short
|
||||||
|
component: worker
|
||||||
|
queue: short
|
||||||
|
azure.workload.identity/use: "true"
|
||||||
|
spec:
|
||||||
|
serviceAccountName: erpnext-sa
|
||||||
|
volumes:
|
||||||
|
- name: sites
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: erpnext-sites
|
||||||
|
- name: logs
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: erpnext-logs
|
||||||
|
- name: secrets-store
|
||||||
|
csi:
|
||||||
|
driver: secrets-store.csi.k8s.io
|
||||||
|
readOnly: true
|
||||||
|
volumeAttributes:
|
||||||
|
secretProviderClass: erpnext-secrets
|
||||||
|
containers:
|
||||||
|
- name: worker
|
||||||
|
image: frappe/erpnext-worker:v14
|
||||||
|
imagePullPolicy: Always
|
||||||
|
command: ["bench", "worker", "--queue", "short"]
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
- name: logs
|
||||||
|
mountPath: /home/frappe/frappe-bench/logs
|
||||||
|
- name: secrets-store
|
||||||
|
mountPath: /mnt/secrets-store
|
||||||
|
readOnly: true
|
||||||
|
env:
|
||||||
|
- name: DB_TYPE
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_TYPE
|
||||||
|
- name: DB_HOST
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_HOST
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_USER
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: erpnext-secrets
|
||||||
|
key: db-password
|
||||||
|
- name: REDIS_HOST
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: REDIS_HOST
|
||||||
|
- name: REDIS_PORT
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: REDIS_PORT
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: erpnext-secrets
|
||||||
|
key: redis-key
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 300m
|
||||||
|
memory: 512Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1000m
|
||||||
|
memory: 1Gi
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: acr-secret
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: erpnext-scheduler
|
||||||
|
namespace: erpnext
|
||||||
|
labels:
|
||||||
|
app: erpnext-scheduler
|
||||||
|
component: scheduler
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: erpnext-scheduler
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: erpnext-scheduler
|
||||||
|
component: scheduler
|
||||||
|
azure.workload.identity/use: "true"
|
||||||
|
spec:
|
||||||
|
serviceAccountName: erpnext-sa
|
||||||
|
volumes:
|
||||||
|
- name: sites
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: erpnext-sites
|
||||||
|
- name: logs
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: erpnext-logs
|
||||||
|
- name: secrets-store
|
||||||
|
csi:
|
||||||
|
driver: secrets-store.csi.k8s.io
|
||||||
|
readOnly: true
|
||||||
|
volumeAttributes:
|
||||||
|
secretProviderClass: erpnext-secrets
|
||||||
|
containers:
|
||||||
|
- name: scheduler
|
||||||
|
image: frappe/erpnext-worker:v14
|
||||||
|
imagePullPolicy: Always
|
||||||
|
command: ["bench", "schedule"]
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
- name: logs
|
||||||
|
mountPath: /home/frappe/frappe-bench/logs
|
||||||
|
- name: secrets-store
|
||||||
|
mountPath: /mnt/secrets-store
|
||||||
|
readOnly: true
|
||||||
|
env:
|
||||||
|
- name: DB_TYPE
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_TYPE
|
||||||
|
- name: DB_HOST
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_HOST
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_USER
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: erpnext-secrets
|
||||||
|
key: db-password
|
||||||
|
- name: REDIS_HOST
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: REDIS_HOST
|
||||||
|
- name: REDIS_PORT
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: REDIS_PORT
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: erpnext-secrets
|
||||||
|
key: redis-key
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 200m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: acr-secret
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: erpnext-websocket
|
||||||
|
namespace: erpnext
|
||||||
|
labels:
|
||||||
|
app: erpnext-websocket
|
||||||
|
component: websocket
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: erpnext-websocket
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: erpnext-websocket
|
||||||
|
component: websocket
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- weight: 100
|
||||||
|
podAffinityTerm:
|
||||||
|
labelSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- key: app
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- erpnext-websocket
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
volumes:
|
||||||
|
- name: sites
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: erpnext-sites
|
||||||
|
containers:
|
||||||
|
- name: websocket
|
||||||
|
image: frappe/frappe-socketio:v14
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 9000
|
||||||
|
name: websocket
|
||||||
|
protocol: TCP
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
readOnly: true
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 200m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 9000
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 9000
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: acr-secret
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: erpnext-websocket
|
||||||
|
namespace: erpnext
|
||||||
|
labels:
|
||||||
|
app: erpnext-websocket
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
sessionAffinity: ClientIP
|
||||||
|
ports:
|
||||||
|
- port: 9000
|
||||||
|
targetPort: 9000
|
||||||
|
protocol: TCP
|
||||||
|
name: websocket
|
||||||
|
selector:
|
||||||
|
app: erpnext-websocket
|
||||||
|
---
|
||||||
|
apiVersion: autoscaling/v2
|
||||||
|
kind: HorizontalPodAutoscaler
|
||||||
|
metadata:
|
||||||
|
name: erpnext-worker-default-hpa
|
||||||
|
namespace: erpnext
|
||||||
|
spec:
|
||||||
|
scaleTargetRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: erpnext-worker-default
|
||||||
|
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
|
||||||
@ -0,0 +1,182 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: erpnext-ingress
|
||||||
|
namespace: erpnext
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: nginx
|
||||||
|
nginx.ingress.kubernetes.io/rewrite-target: /
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
|
||||||
|
# SSL/TLS Configuration
|
||||||
|
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||||
|
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||||
|
nginx.ingress.kubernetes.io/ssl-protocols: "TLSv1.2 TLSv1.3"
|
||||||
|
nginx.ingress.kubernetes.io/ssl-ciphers: "TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256"
|
||||||
|
|
||||||
|
# Proxy Configuration
|
||||||
|
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-connect-timeout: "120"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-send-timeout: "120"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-buffer-size: "8k"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-buffers-number: "8"
|
||||||
|
|
||||||
|
# WebSocket Support
|
||||||
|
nginx.ingress.kubernetes.io/websocket-services: "erpnext-websocket"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
|
||||||
|
|
||||||
|
# Security Headers
|
||||||
|
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||||
|
more_set_headers "X-Frame-Options: SAMEORIGIN";
|
||||||
|
more_set_headers "X-Content-Type-Options: nosniff";
|
||||||
|
more_set_headers "X-XSS-Protection: 1; mode=block";
|
||||||
|
more_set_headers "Referrer-Policy: strict-origin-when-cross-origin";
|
||||||
|
more_set_headers "Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval' https: data: blob:";
|
||||||
|
more_set_headers "Permissions-Policy: geolocation=(), microphone=(), camera=()";
|
||||||
|
|
||||||
|
# Rate Limiting
|
||||||
|
nginx.ingress.kubernetes.io/limit-rps: "20"
|
||||||
|
nginx.ingress.kubernetes.io/limit-rpm: "200"
|
||||||
|
nginx.ingress.kubernetes.io/limit-connections: "10"
|
||||||
|
|
||||||
|
# Session Affinity
|
||||||
|
nginx.ingress.kubernetes.io/affinity: "cookie"
|
||||||
|
nginx.ingress.kubernetes.io/affinity-mode: "persistent"
|
||||||
|
nginx.ingress.kubernetes.io/session-cookie-name: "erpnext-session"
|
||||||
|
nginx.ingress.kubernetes.io/session-cookie-max-age: "86400"
|
||||||
|
nginx.ingress.kubernetes.io/session-cookie-change-on-failure: "true"
|
||||||
|
|
||||||
|
# CORS Configuration (if needed)
|
||||||
|
nginx.ingress.kubernetes.io/enable-cors: "true"
|
||||||
|
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"
|
||||||
|
nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Frappe-Site-Name"
|
||||||
|
nginx.ingress.kubernetes.io/cors-allow-origin: "*"
|
||||||
|
nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
|
||||||
|
|
||||||
|
# Azure Application Gateway specific (if using AGIC)
|
||||||
|
# appgw.ingress.kubernetes.io/use-private-ip: "false"
|
||||||
|
# appgw.ingress.kubernetes.io/backend-protocol: "http"
|
||||||
|
# appgw.ingress.kubernetes.io/cookie-based-affinity: "true"
|
||||||
|
# appgw.ingress.kubernetes.io/request-timeout: "120"
|
||||||
|
# appgw.ingress.kubernetes.io/connection-draining: "true"
|
||||||
|
# appgw.ingress.kubernetes.io/connection-draining-timeout: "30"
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- ${DOMAIN}
|
||||||
|
- www.${DOMAIN}
|
||||||
|
secretName: erpnext-tls
|
||||||
|
rules:
|
||||||
|
- host: ${DOMAIN}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
# WebSocket endpoint
|
||||||
|
- path: /socket.io
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: erpnext-websocket
|
||||||
|
port:
|
||||||
|
number: 9000
|
||||||
|
|
||||||
|
# API endpoints (higher priority)
|
||||||
|
- path: /api
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: erpnext-backend
|
||||||
|
port:
|
||||||
|
number: 8000
|
||||||
|
|
||||||
|
# Assets and files
|
||||||
|
- path: /assets
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: erpnext-frontend
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
|
|
||||||
|
- path: /files
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: erpnext-frontend
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
|
|
||||||
|
# Default route
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: erpnext-frontend
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
|
|
||||||
|
# Redirect www to non-www
|
||||||
|
- host: www.${DOMAIN}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: erpnext-frontend
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
|
---
|
||||||
|
# Alternative Ingress for Application Gateway Ingress Controller (AGIC)
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: erpnext-ingress-agic
|
||||||
|
namespace: erpnext
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: azure/application-gateway
|
||||||
|
appgw.ingress.kubernetes.io/ssl-redirect: "true"
|
||||||
|
appgw.ingress.kubernetes.io/use-private-ip: "false"
|
||||||
|
appgw.ingress.kubernetes.io/backend-protocol: "http"
|
||||||
|
appgw.ingress.kubernetes.io/backend-hostname: "erpnext-backend"
|
||||||
|
appgw.ingress.kubernetes.io/cookie-based-affinity: "true"
|
||||||
|
appgw.ingress.kubernetes.io/request-timeout: "120"
|
||||||
|
appgw.ingress.kubernetes.io/connection-draining: "true"
|
||||||
|
appgw.ingress.kubernetes.io/connection-draining-timeout: "30"
|
||||||
|
appgw.ingress.kubernetes.io/health-probe-path: "/api/method/ping"
|
||||||
|
appgw.ingress.kubernetes.io/health-probe-interval: "30"
|
||||||
|
appgw.ingress.kubernetes.io/health-probe-timeout: "10"
|
||||||
|
appgw.ingress.kubernetes.io/health-probe-unhealthy-threshold: "3"
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
spec:
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- ${DOMAIN}
|
||||||
|
secretName: erpnext-tls-agic
|
||||||
|
rules:
|
||||||
|
- host: ${DOMAIN}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /socket.io/*
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: erpnext-websocket
|
||||||
|
port:
|
||||||
|
number: 9000
|
||||||
|
- path: /api/*
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: erpnext-backend
|
||||||
|
port:
|
||||||
|
number: 8000
|
||||||
|
- path: /*
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: erpnext-frontend
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
@ -0,0 +1,357 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: erpnext-site-init
|
||||||
|
namespace: erpnext
|
||||||
|
labels:
|
||||||
|
app: erpnext
|
||||||
|
component: site-init
|
||||||
|
spec:
|
||||||
|
ttlSecondsAfterFinished: 3600
|
||||||
|
backoffLimit: 3
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: erpnext
|
||||||
|
component: site-init
|
||||||
|
azure.workload.identity/use: "true"
|
||||||
|
spec:
|
||||||
|
serviceAccountName: erpnext-sa
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
volumes:
|
||||||
|
- name: sites
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: erpnext-sites
|
||||||
|
- name: secrets-store
|
||||||
|
csi:
|
||||||
|
driver: secrets-store.csi.k8s.io
|
||||||
|
readOnly: true
|
||||||
|
volumeAttributes:
|
||||||
|
secretProviderClass: erpnext-secrets
|
||||||
|
initContainers:
|
||||||
|
- name: wait-for-db
|
||||||
|
image: postgres:13-alpine
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
until pg_isready -h ${DB_HOST} -p 5432 -U ${DB_USER}; do
|
||||||
|
echo "Waiting for database to be ready..."
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
echo "Database is ready!"
|
||||||
|
env:
|
||||||
|
- name: DB_HOST
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_HOST
|
||||||
|
- name: DB_USER
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_USER
|
||||||
|
containers:
|
||||||
|
- name: site-init
|
||||||
|
image: frappe/erpnext-worker:v14
|
||||||
|
imagePullPolicy: Always
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
echo "Starting ERPNext site initialization..."
|
||||||
|
|
||||||
|
# Check if site already exists
|
||||||
|
if [ -d "/home/frappe/frappe-bench/sites/frontend" ]; then
|
||||||
|
echo "Site 'frontend' already exists. Running migrations..."
|
||||||
|
bench --site frontend migrate
|
||||||
|
else
|
||||||
|
echo "Creating new site 'frontend'..."
|
||||||
|
bench new-site frontend \
|
||||||
|
--db-type postgres \
|
||||||
|
--db-host ${DB_HOST} \
|
||||||
|
--db-port 5432 \
|
||||||
|
--db-name erpnext \
|
||||||
|
--db-user ${POSTGRES_USER} \
|
||||||
|
--db-password ${POSTGRES_PASSWORD} \
|
||||||
|
--admin-password ${ADMIN_PASSWORD} \
|
||||||
|
--no-mariadb-socket \
|
||||||
|
--install-app erpnext \
|
||||||
|
--set-default
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set site configuration
|
||||||
|
bench --site frontend set-config db_type postgres
|
||||||
|
bench --site frontend set-config redis_cache "rediss://:${REDIS_PASSWORD}@${REDIS_HOST}:${REDIS_PORT}/0?ssl_cert_reqs=required"
|
||||||
|
bench --site frontend set-config redis_queue "rediss://:${REDIS_PASSWORD}@${REDIS_HOST}:${REDIS_PORT}/1?ssl_cert_reqs=required"
|
||||||
|
bench --site frontend set-config redis_socketio "rediss://:${REDIS_PASSWORD}@${REDIS_HOST}:${REDIS_PORT}/2?ssl_cert_reqs=required"
|
||||||
|
|
||||||
|
# Enable scheduler
|
||||||
|
bench --site frontend scheduler enable
|
||||||
|
|
||||||
|
# Clear cache
|
||||||
|
bench --site frontend clear-cache
|
||||||
|
|
||||||
|
echo "Site initialization completed successfully!"
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
- name: secrets-store
|
||||||
|
mountPath: /mnt/secrets-store
|
||||||
|
readOnly: true
|
||||||
|
env:
|
||||||
|
- name: DB_HOST
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_HOST
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_USER
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: erpnext-secrets
|
||||||
|
key: db-password
|
||||||
|
- name: REDIS_HOST
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: REDIS_HOST
|
||||||
|
- name: REDIS_PORT
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: REDIS_PORT
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: erpnext-secrets
|
||||||
|
key: redis-key
|
||||||
|
- name: ADMIN_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: erpnext-secrets
|
||||||
|
key: admin-password
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 1Gi
|
||||||
|
limits:
|
||||||
|
cpu: 2000m
|
||||||
|
memory: 2Gi
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: acr-secret
|
||||||
|
---
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: erpnext-backup
|
||||||
|
namespace: erpnext
|
||||||
|
labels:
|
||||||
|
app: erpnext
|
||||||
|
component: backup
|
||||||
|
spec:
|
||||||
|
schedule: "0 2 * * *" # Daily at 2 AM
|
||||||
|
concurrencyPolicy: Forbid
|
||||||
|
successfulJobsHistoryLimit: 3
|
||||||
|
failedJobsHistoryLimit: 3
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
ttlSecondsAfterFinished: 86400
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: erpnext
|
||||||
|
component: backup
|
||||||
|
azure.workload.identity/use: "true"
|
||||||
|
spec:
|
||||||
|
serviceAccountName: erpnext-sa
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
volumes:
|
||||||
|
- name: sites
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: erpnext-sites
|
||||||
|
- name: secrets-store
|
||||||
|
csi:
|
||||||
|
driver: secrets-store.csi.k8s.io
|
||||||
|
readOnly: true
|
||||||
|
volumeAttributes:
|
||||||
|
secretProviderClass: erpnext-secrets
|
||||||
|
containers:
|
||||||
|
- name: backup
|
||||||
|
image: frappe/erpnext-worker:v14
|
||||||
|
imagePullPolicy: Always
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
echo "Starting backup at $(date)"
|
||||||
|
|
||||||
|
# Create backup
|
||||||
|
bench --site frontend backup --with-files
|
||||||
|
|
||||||
|
# Upload to Azure Storage
|
||||||
|
BACKUP_PATH="/home/frappe/frappe-bench/sites/frontend/private/backups"
|
||||||
|
LATEST_DB_BACKUP=$(ls -t $BACKUP_PATH/*.sql.gz | head -1)
|
||||||
|
LATEST_FILES_BACKUP=$(ls -t $BACKUP_PATH/*.tar | head -1 || echo "")
|
||||||
|
|
||||||
|
if [ -n "$LATEST_DB_BACKUP" ]; then
|
||||||
|
echo "Uploading database backup: $(basename $LATEST_DB_BACKUP)"
|
||||||
|
# Use Azure CLI or azcopy to upload to blob storage
|
||||||
|
# az storage blob upload \
|
||||||
|
# --account-name ${STORAGE_ACCOUNT} \
|
||||||
|
# --container-name erpnext-backups \
|
||||||
|
# --name "$(date +%Y%m%d)/$(basename $LATEST_DB_BACKUP)" \
|
||||||
|
# --file "$LATEST_DB_BACKUP" \
|
||||||
|
# --auth-mode login
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$LATEST_FILES_BACKUP" ]; then
|
||||||
|
echo "Uploading files backup: $(basename $LATEST_FILES_BACKUP)"
|
||||||
|
# az storage blob upload \
|
||||||
|
# --account-name ${STORAGE_ACCOUNT} \
|
||||||
|
# --container-name erpnext-backups \
|
||||||
|
# --name "$(date +%Y%m%d)/$(basename $LATEST_FILES_BACKUP)" \
|
||||||
|
# --file "$LATEST_FILES_BACKUP" \
|
||||||
|
# --auth-mode login
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up old local backups (keep last 7 days)
|
||||||
|
find $BACKUP_PATH -type f -mtime +7 -delete
|
||||||
|
|
||||||
|
echo "Backup completed at $(date)"
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
- name: secrets-store
|
||||||
|
mountPath: /mnt/secrets-store
|
||||||
|
readOnly: true
|
||||||
|
env:
|
||||||
|
- name: DB_HOST
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_HOST
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_USER
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: erpnext-secrets
|
||||||
|
key: db-password
|
||||||
|
- name: STORAGE_ACCOUNT
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: STORAGE_ACCOUNT
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 1Gi
|
||||||
|
limits:
|
||||||
|
cpu: 1000m
|
||||||
|
memory: 2Gi
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: acr-secret
|
||||||
|
---
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: erpnext-maintenance
|
||||||
|
namespace: erpnext
|
||||||
|
labels:
|
||||||
|
app: erpnext
|
||||||
|
component: maintenance
|
||||||
|
spec:
|
||||||
|
schedule: "0 3 * * 0" # Weekly on Sunday at 3 AM
|
||||||
|
concurrencyPolicy: Forbid
|
||||||
|
successfulJobsHistoryLimit: 1
|
||||||
|
failedJobsHistoryLimit: 2
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
ttlSecondsAfterFinished: 86400
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: erpnext
|
||||||
|
component: maintenance
|
||||||
|
azure.workload.identity/use: "true"
|
||||||
|
spec:
|
||||||
|
serviceAccountName: erpnext-sa
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
volumes:
|
||||||
|
- name: sites
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: erpnext-sites
|
||||||
|
- name: secrets-store
|
||||||
|
csi:
|
||||||
|
driver: secrets-store.csi.k8s.io
|
||||||
|
readOnly: true
|
||||||
|
volumeAttributes:
|
||||||
|
secretProviderClass: erpnext-secrets
|
||||||
|
containers:
|
||||||
|
- name: maintenance
|
||||||
|
image: frappe/erpnext-worker:v14
|
||||||
|
imagePullPolicy: Always
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
echo "Starting maintenance tasks at $(date)"
|
||||||
|
|
||||||
|
# Clear old logs
|
||||||
|
bench --site frontend clear-log-table --days 30
|
||||||
|
|
||||||
|
# Clear old emails
|
||||||
|
bench --site frontend clear-email-queue --days 30
|
||||||
|
|
||||||
|
# Optimize database
|
||||||
|
bench --site frontend optimize
|
||||||
|
|
||||||
|
# Clear cache
|
||||||
|
bench --site frontend clear-cache
|
||||||
|
|
||||||
|
# Run custom maintenance scripts if any
|
||||||
|
# bench --site frontend execute custom_app.maintenance.run
|
||||||
|
|
||||||
|
echo "Maintenance completed at $(date)"
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
- name: secrets-store
|
||||||
|
mountPath: /mnt/secrets-store
|
||||||
|
readOnly: true
|
||||||
|
env:
|
||||||
|
- name: DB_HOST
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_HOST
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: erpnext-config
|
||||||
|
key: DB_USER
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: erpnext-secrets
|
||||||
|
key: db-password
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 200m
|
||||||
|
memory: 512Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 1Gi
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: acr-secret
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: erpnext
|
||||||
|
labels:
|
||||||
|
name: erpnext
|
||||||
|
environment: production
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ResourceQuota
|
||||||
|
metadata:
|
||||||
|
name: erpnext-quota
|
||||||
|
namespace: erpnext
|
||||||
|
spec:
|
||||||
|
hard:
|
||||||
|
requests.cpu: "20"
|
||||||
|
requests.memory: 40Gi
|
||||||
|
limits.cpu: "40"
|
||||||
|
limits.memory: 80Gi
|
||||||
|
persistentvolumeclaims: "10"
|
||||||
|
services.loadbalancers: "2"
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: LimitRange
|
||||||
|
metadata:
|
||||||
|
name: erpnext-limits
|
||||||
|
namespace: erpnext
|
||||||
|
spec:
|
||||||
|
limits:
|
||||||
|
- max:
|
||||||
|
cpu: "4"
|
||||||
|
memory: 8Gi
|
||||||
|
min:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
default:
|
||||||
|
cpu: "1"
|
||||||
|
memory: 2Gi
|
||||||
|
defaultRequest:
|
||||||
|
cpu: 200m
|
||||||
|
memory: 512Mi
|
||||||
|
type: Container
|
||||||
|
- max:
|
||||||
|
storage: 100Gi
|
||||||
|
min:
|
||||||
|
storage: 1Gi
|
||||||
|
type: PersistentVolumeClaim
|
||||||
@ -0,0 +1,98 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: erpnext-sa
|
||||||
|
namespace: erpnext
|
||||||
|
annotations:
|
||||||
|
azure.workload.identity/client-id: "${CLIENT_ID}"
|
||||||
|
---
|
||||||
|
apiVersion: secrets-store.csi.x-k8s.io/v1
|
||||||
|
kind: SecretProviderClass
|
||||||
|
metadata:
|
||||||
|
name: erpnext-secrets
|
||||||
|
namespace: erpnext
|
||||||
|
spec:
|
||||||
|
provider: azure
|
||||||
|
parameters:
|
||||||
|
usePodIdentity: "false"
|
||||||
|
useVMManagedIdentity: "true"
|
||||||
|
userAssignedIdentityID: "${CLIENT_ID}"
|
||||||
|
keyvaultName: "${KEYVAULT_NAME}"
|
||||||
|
cloudName: "AzurePublicCloud"
|
||||||
|
objects: |
|
||||||
|
array:
|
||||||
|
- |
|
||||||
|
objectName: erpnext-admin-password
|
||||||
|
objectType: secret
|
||||||
|
objectAlias: admin-password
|
||||||
|
- |
|
||||||
|
objectName: erpnext-db-password
|
||||||
|
objectType: secret
|
||||||
|
objectAlias: db-password
|
||||||
|
- |
|
||||||
|
objectName: erpnext-redis-key
|
||||||
|
objectType: secret
|
||||||
|
objectAlias: redis-key
|
||||||
|
- |
|
||||||
|
objectName: erpnext-api-key
|
||||||
|
objectType: secret
|
||||||
|
objectAlias: api-key
|
||||||
|
- |
|
||||||
|
objectName: erpnext-api-secret
|
||||||
|
objectType: secret
|
||||||
|
objectAlias: api-secret
|
||||||
|
- |
|
||||||
|
objectName: erpnext-encryption-key
|
||||||
|
objectType: secret
|
||||||
|
objectAlias: encryption-key
|
||||||
|
- |
|
||||||
|
objectName: erpnext-jwt-secret
|
||||||
|
objectType: secret
|
||||||
|
objectAlias: jwt-secret
|
||||||
|
tenantId: "${TENANT_ID}"
|
||||||
|
secretObjects:
|
||||||
|
- secretName: erpnext-secrets
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
- objectName: admin-password
|
||||||
|
key: admin-password
|
||||||
|
- objectName: db-password
|
||||||
|
key: db-password
|
||||||
|
- objectName: redis-key
|
||||||
|
key: redis-key
|
||||||
|
- objectName: api-key
|
||||||
|
key: api-key
|
||||||
|
- objectName: api-secret
|
||||||
|
key: api-secret
|
||||||
|
- objectName: encryption-key
|
||||||
|
key: encryption-key
|
||||||
|
- objectName: jwt-secret
|
||||||
|
key: jwt-secret
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: azure-storage-secret
|
||||||
|
namespace: erpnext
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
azurestorageaccountname: "${STORAGE_ACCOUNT}"
|
||||||
|
azurestorageaccountkey: "${STORAGE_KEY}"
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: acr-secret
|
||||||
|
namespace: erpnext
|
||||||
|
type: kubernetes.io/dockerconfigjson
|
||||||
|
data:
|
||||||
|
.dockerconfigjson: |
|
||||||
|
{
|
||||||
|
"auths": {
|
||||||
|
"${ACR_LOGIN_SERVER}": {
|
||||||
|
"username": "${ACR_USERNAME}",
|
||||||
|
"password": "${ACR_PASSWORD}",
|
||||||
|
"auth": "${ACR_AUTH}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
apiVersion: storage.k8s.io/v1
|
||||||
|
kind: StorageClass
|
||||||
|
metadata:
|
||||||
|
name: azure-files-premium
|
||||||
|
provisioner: file.csi.azure.com
|
||||||
|
allowVolumeExpansion: true
|
||||||
|
parameters:
|
||||||
|
skuName: Premium_LRS
|
||||||
|
protocol: smb
|
||||||
|
reclaimPolicy: Retain
|
||||||
|
volumeBindingMode: Immediate
|
||||||
|
mountOptions:
|
||||||
|
- dir_mode=0777
|
||||||
|
- file_mode=0777
|
||||||
|
- uid=1000
|
||||||
|
- gid=1000
|
||||||
|
- mfsymlinks
|
||||||
|
- cache=strict
|
||||||
|
- nosharesock
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: erpnext-sites
|
||||||
|
namespace: erpnext
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteMany
|
||||||
|
storageClassName: azure-files-premium
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 50Gi
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: erpnext-assets
|
||||||
|
namespace: erpnext
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteMany
|
||||||
|
storageClassName: azure-files-premium
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 20Gi
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: erpnext-logs
|
||||||
|
namespace: erpnext
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteMany
|
||||||
|
storageClassName: azure-files-premium
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
@ -0,0 +1,604 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# ERPNext Azure Container Instances Deployment Script
|
||||||
|
# Usage: ./container-instances-deploy.sh [deploy|update|delete|status|scale]
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Color codes for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ENV_FILE="${HOME}/erpnext-azure-env.sh"
|
||||||
|
|
||||||
|
# Function to print colored output
|
||||||
|
print_color() {
|
||||||
|
local color=$1
|
||||||
|
local message=$2
|
||||||
|
echo -e "${color}${message}${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to check prerequisites
|
||||||
|
check_prerequisites() {
|
||||||
|
print_color "$YELLOW" "Checking prerequisites..."
|
||||||
|
|
||||||
|
# Check Azure CLI
|
||||||
|
if ! command -v az &> /dev/null; then
|
||||||
|
print_color "$RED" "Azure CLI not found. Please install it first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check Docker
|
||||||
|
if ! command -v docker &> /dev/null; then
|
||||||
|
print_color "$RED" "Docker not found. Please install it first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check environment file
|
||||||
|
if [ ! -f "$ENV_FILE" ]; then
|
||||||
|
print_color "$RED" "Environment file not found at $ENV_FILE"
|
||||||
|
print_color "$YELLOW" "Please complete the prerequisites first (00-prerequisites-managed.md)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Source environment variables
|
||||||
|
source "$ENV_FILE"
|
||||||
|
|
||||||
|
# Check required environment variables
|
||||||
|
local required_vars=(
|
||||||
|
"RESOURCE_GROUP"
|
||||||
|
"LOCATION"
|
||||||
|
"DB_SERVER_NAME"
|
||||||
|
"DB_ADMIN_USER"
|
||||||
|
"DB_ADMIN_PASSWORD"
|
||||||
|
"REDIS_NAME"
|
||||||
|
"REDIS_HOST"
|
||||||
|
"REDIS_KEY"
|
||||||
|
"STORAGE_ACCOUNT"
|
||||||
|
"STORAGE_KEY"
|
||||||
|
)
|
||||||
|
|
||||||
|
for var in "${required_vars[@]}"; do
|
||||||
|
if [ -z "${!var}" ]; then
|
||||||
|
print_color "$RED" "Required environment variable $var is not set"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
print_color "$GREEN" "Prerequisites check passed!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to create container registry
|
||||||
|
create_container_registry() {
|
||||||
|
print_color "$YELLOW" "Creating Azure Container Registry..."
|
||||||
|
|
||||||
|
export ACR_NAME="erpnextacr$(openssl rand -hex 4)"
|
||||||
|
|
||||||
|
az acr create \
|
||||||
|
--name "$ACR_NAME" \
|
||||||
|
--resource-group "$RESOURCE_GROUP" \
|
||||||
|
--location "$LOCATION" \
|
||||||
|
--sku Standard \
|
||||||
|
--admin-enabled true
|
||||||
|
|
||||||
|
# Get registry credentials
|
||||||
|
export ACR_LOGIN_SERVER=$(az acr show --name "$ACR_NAME" --query loginServer -o tsv)
|
||||||
|
export ACR_USERNAME=$(az acr credential show --name "$ACR_NAME" --query username -o tsv)
|
||||||
|
export ACR_PASSWORD=$(az acr credential show --name "$ACR_NAME" --query passwords[0].value -o tsv)
|
||||||
|
|
||||||
|
# Login to registry
|
||||||
|
az acr login --name "$ACR_NAME"
|
||||||
|
|
||||||
|
print_color "$GREEN" "Container Registry created: $ACR_LOGIN_SERVER"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to build and push images
|
||||||
|
build_and_push_images() {
|
||||||
|
print_color "$YELLOW" "Building and pushing Docker images..."
|
||||||
|
|
||||||
|
# Create temporary build directory
|
||||||
|
BUILD_DIR=$(mktemp -d)
|
||||||
|
cd "$BUILD_DIR"
|
||||||
|
|
||||||
|
# Create Dockerfile for ERPNext with Azure integrations
|
||||||
|
cat > Dockerfile.azure <<'EOF'
|
||||||
|
FROM frappe/erpnext-worker:v14
|
||||||
|
|
||||||
|
# Install Azure Storage SDK for Python and PostgreSQL client
|
||||||
|
RUN pip install azure-storage-blob azure-identity psycopg2-binary
|
||||||
|
|
||||||
|
# Add custom entrypoint for Azure configurations
|
||||||
|
COPY entrypoint.azure.sh /entrypoint.azure.sh
|
||||||
|
RUN chmod +x /entrypoint.azure.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["/entrypoint.azure.sh"]
|
||||||
|
CMD ["start"]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create entrypoint script
|
||||||
|
cat > entrypoint.azure.sh <<'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Configure database connection for PostgreSQL
|
||||||
|
export DB_TYPE="postgres"
|
||||||
|
export DB_HOST="${DB_HOST}"
|
||||||
|
export DB_PORT="5432"
|
||||||
|
export DB_NAME="erpnext"
|
||||||
|
|
||||||
|
# Configure Redis with SSL
|
||||||
|
export REDIS_CACHE="rediss://:${REDIS_PASSWORD}@${REDIS_HOST}:6380/0?ssl_cert_reqs=required"
|
||||||
|
export REDIS_QUEUE="rediss://:${REDIS_PASSWORD}@${REDIS_HOST}:6380/1?ssl_cert_reqs=required"
|
||||||
|
export REDIS_SOCKETIO="rediss://:${REDIS_PASSWORD}@${REDIS_HOST}:6380/2?ssl_cert_reqs=required"
|
||||||
|
|
||||||
|
# Execute command
|
||||||
|
if [ "$1" = "start" ]; then
|
||||||
|
exec bench start
|
||||||
|
else
|
||||||
|
exec "$@"
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Build and push image
|
||||||
|
docker build -f Dockerfile.azure -t "$ACR_LOGIN_SERVER/erpnext-azure:v14" .
|
||||||
|
docker push "$ACR_LOGIN_SERVER/erpnext-azure:v14"
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
cd -
|
||||||
|
rm -rf "$BUILD_DIR"
|
||||||
|
|
||||||
|
print_color "$GREEN" "Images built and pushed successfully!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to create file shares
|
||||||
|
create_file_shares() {
|
||||||
|
print_color "$YELLOW" "Creating Azure File Shares..."
|
||||||
|
|
||||||
|
# Create file shares
|
||||||
|
az storage share create \
|
||||||
|
--name erpnext-sites \
|
||||||
|
--account-name "$STORAGE_ACCOUNT" \
|
||||||
|
--account-key "$STORAGE_KEY" \
|
||||||
|
--quota 100
|
||||||
|
|
||||||
|
az storage share create \
|
||||||
|
--name erpnext-assets \
|
||||||
|
--account-name "$STORAGE_ACCOUNT" \
|
||||||
|
--account-key "$STORAGE_KEY" \
|
||||||
|
--quota 50
|
||||||
|
|
||||||
|
az storage share create \
|
||||||
|
--name erpnext-logs \
|
||||||
|
--account-name "$STORAGE_ACCOUNT" \
|
||||||
|
--account-key "$STORAGE_KEY" \
|
||||||
|
--quota 10
|
||||||
|
|
||||||
|
print_color "$GREEN" "File shares created successfully!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to deploy backend container group
|
||||||
|
deploy_backend() {
|
||||||
|
print_color "$YELLOW" "Deploying backend container group..."
|
||||||
|
|
||||||
|
# Create YAML deployment file
|
||||||
|
cat > aci-backend.yaml <<EOF
|
||||||
|
apiVersion: 2021-10-01
|
||||||
|
location: $LOCATION
|
||||||
|
name: erpnext-backend
|
||||||
|
properties:
|
||||||
|
containers:
|
||||||
|
- name: backend
|
||||||
|
properties:
|
||||||
|
image: $ACR_LOGIN_SERVER/erpnext-azure:v14
|
||||||
|
command: ["bench", "start"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 2
|
||||||
|
memoryInGb: 4
|
||||||
|
ports:
|
||||||
|
- port: 8000
|
||||||
|
protocol: TCP
|
||||||
|
environmentVariables:
|
||||||
|
- name: DB_HOST
|
||||||
|
value: $DB_SERVER_NAME.postgres.database.azure.com
|
||||||
|
- name: DB_USER
|
||||||
|
value: $DB_ADMIN_USER
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
secureValue: $DB_ADMIN_PASSWORD
|
||||||
|
- name: REDIS_HOST
|
||||||
|
value: $REDIS_HOST
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
secureValue: $REDIS_KEY
|
||||||
|
- name: ADMIN_PASSWORD
|
||||||
|
secureValue: ${ADMIN_PASSWORD:-YourSecurePassword123!}
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
- name: logs
|
||||||
|
mountPath: /home/frappe/frappe-bench/logs
|
||||||
|
- name: scheduler
|
||||||
|
properties:
|
||||||
|
image: $ACR_LOGIN_SERVER/erpnext-azure:v14
|
||||||
|
command: ["bench", "schedule"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 0.5
|
||||||
|
memoryInGb: 1
|
||||||
|
environmentVariables:
|
||||||
|
- name: DB_HOST
|
||||||
|
value: $DB_SERVER_NAME.postgres.database.azure.com
|
||||||
|
- name: DB_USER
|
||||||
|
value: $DB_ADMIN_USER
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
secureValue: $DB_ADMIN_PASSWORD
|
||||||
|
- name: REDIS_HOST
|
||||||
|
value: $REDIS_HOST
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
secureValue: $REDIS_KEY
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
- name: worker-default
|
||||||
|
properties:
|
||||||
|
image: $ACR_LOGIN_SERVER/erpnext-azure:v14
|
||||||
|
command: ["bench", "worker", "--queue", "default"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 1
|
||||||
|
memoryInGb: 2
|
||||||
|
environmentVariables:
|
||||||
|
- name: DB_HOST
|
||||||
|
value: $DB_SERVER_NAME.postgres.database.azure.com
|
||||||
|
- name: DB_USER
|
||||||
|
value: $DB_ADMIN_USER
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
secureValue: $DB_ADMIN_PASSWORD
|
||||||
|
- name: REDIS_HOST
|
||||||
|
value: $REDIS_HOST
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
secureValue: $REDIS_KEY
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
imageRegistryCredentials:
|
||||||
|
- server: $ACR_LOGIN_SERVER
|
||||||
|
username: $ACR_USERNAME
|
||||||
|
password: $ACR_PASSWORD
|
||||||
|
volumes:
|
||||||
|
- name: sites
|
||||||
|
azureFile:
|
||||||
|
shareName: erpnext-sites
|
||||||
|
storageAccountName: $STORAGE_ACCOUNT
|
||||||
|
storageAccountKey: $STORAGE_KEY
|
||||||
|
- name: logs
|
||||||
|
azureFile:
|
||||||
|
shareName: erpnext-logs
|
||||||
|
storageAccountName: $STORAGE_ACCOUNT
|
||||||
|
storageAccountKey: $STORAGE_KEY
|
||||||
|
osType: Linux
|
||||||
|
restartPolicy: Always
|
||||||
|
ipAddress:
|
||||||
|
type: Private
|
||||||
|
ports:
|
||||||
|
- port: 8000
|
||||||
|
protocol: TCP
|
||||||
|
subnetIds:
|
||||||
|
- id: /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Network/virtualNetworks/erpnext-vnet/subnets/aci-subnet
|
||||||
|
type: Microsoft.ContainerInstance/containerGroups
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Deploy container group
|
||||||
|
az container create \
|
||||||
|
--resource-group "$RESOURCE_GROUP" \
|
||||||
|
--file aci-backend.yaml
|
||||||
|
|
||||||
|
print_color "$GREEN" "Backend container group deployed!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to deploy frontend container group
|
||||||
|
deploy_frontend() {
|
||||||
|
print_color "$YELLOW" "Deploying frontend container group..."
|
||||||
|
|
||||||
|
# Create YAML deployment file
|
||||||
|
cat > aci-frontend.yaml <<EOF
|
||||||
|
apiVersion: 2021-10-01
|
||||||
|
location: $LOCATION
|
||||||
|
name: erpnext-frontend
|
||||||
|
properties:
|
||||||
|
containers:
|
||||||
|
- name: frontend
|
||||||
|
properties:
|
||||||
|
image: frappe/erpnext-nginx:v14
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 1
|
||||||
|
memoryInGb: 1
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
protocol: TCP
|
||||||
|
environmentVariables:
|
||||||
|
- name: BACKEND
|
||||||
|
value: 10.0.2.4:8000
|
||||||
|
- name: FRAPPE_SITE_NAME_HEADER
|
||||||
|
value: frontend
|
||||||
|
- name: SOCKETIO
|
||||||
|
value: 10.0.2.4:9000
|
||||||
|
- name: UPSTREAM_REAL_IP_ADDRESS
|
||||||
|
value: 127.0.0.1
|
||||||
|
- name: UPSTREAM_REAL_IP_HEADER
|
||||||
|
value: X-Forwarded-For
|
||||||
|
- name: PROXY_READ_TIMEOUT
|
||||||
|
value: "120"
|
||||||
|
- name: CLIENT_MAX_BODY_SIZE
|
||||||
|
value: 50m
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
- name: assets
|
||||||
|
mountPath: /usr/share/nginx/html/assets
|
||||||
|
- name: websocket
|
||||||
|
properties:
|
||||||
|
image: frappe/frappe-socketio:v14
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 0.5
|
||||||
|
memoryInGb: 0.5
|
||||||
|
ports:
|
||||||
|
- port: 9000
|
||||||
|
protocol: TCP
|
||||||
|
volumeMounts:
|
||||||
|
- name: sites
|
||||||
|
mountPath: /home/frappe/frappe-bench/sites
|
||||||
|
imageRegistryCredentials:
|
||||||
|
- server: docker.io
|
||||||
|
username: _
|
||||||
|
password: _
|
||||||
|
volumes:
|
||||||
|
- name: sites
|
||||||
|
azureFile:
|
||||||
|
shareName: erpnext-sites
|
||||||
|
storageAccountName: $STORAGE_ACCOUNT
|
||||||
|
storageAccountKey: $STORAGE_KEY
|
||||||
|
- name: assets
|
||||||
|
azureFile:
|
||||||
|
shareName: erpnext-assets
|
||||||
|
storageAccountName: $STORAGE_ACCOUNT
|
||||||
|
storageAccountKey: $STORAGE_KEY
|
||||||
|
osType: Linux
|
||||||
|
restartPolicy: Always
|
||||||
|
ipAddress:
|
||||||
|
type: Public
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
protocol: TCP
|
||||||
|
dnsNameLabel: erpnext-${RESOURCE_GROUP}
|
||||||
|
type: Microsoft.ContainerInstance/containerGroups
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Deploy container group
|
||||||
|
az container create \
|
||||||
|
--resource-group "$RESOURCE_GROUP" \
|
||||||
|
--file aci-frontend.yaml
|
||||||
|
|
||||||
|
# Get public FQDN
|
||||||
|
export FRONTEND_FQDN=$(az container show \
|
||||||
|
--resource-group "$RESOURCE_GROUP" \
|
||||||
|
--name erpnext-frontend \
|
||||||
|
--query ipAddress.fqdn -o tsv)
|
||||||
|
|
||||||
|
print_color "$GREEN" "Frontend deployed! URL: http://$FRONTEND_FQDN:8080"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to initialize site
|
||||||
|
initialize_site() {
|
||||||
|
print_color "$YELLOW" "Initializing ERPNext site..."
|
||||||
|
|
||||||
|
# Run site initialization
|
||||||
|
az container create \
|
||||||
|
--resource-group "$RESOURCE_GROUP" \
|
||||||
|
--name erpnext-init \
|
||||||
|
--image "$ACR_LOGIN_SERVER/erpnext-azure:v14" \
|
||||||
|
--cpu 2 \
|
||||||
|
--memory 4 \
|
||||||
|
--restart-policy Never \
|
||||||
|
--environment-variables \
|
||||||
|
DB_HOST="$DB_SERVER_NAME.postgres.database.azure.com" \
|
||||||
|
DB_USER="$DB_ADMIN_USER" \
|
||||||
|
REDIS_HOST="$REDIS_HOST" \
|
||||||
|
--secure-environment-variables \
|
||||||
|
DB_PASSWORD="$DB_ADMIN_PASSWORD" \
|
||||||
|
REDIS_PASSWORD="$REDIS_KEY" \
|
||||||
|
ADMIN_PASSWORD="${ADMIN_PASSWORD:-YourSecurePassword123!}" \
|
||||||
|
--azure-file-volume-account-name "$STORAGE_ACCOUNT" \
|
||||||
|
--azure-file-volume-account-key "$STORAGE_KEY" \
|
||||||
|
--azure-file-volume-share-name erpnext-sites \
|
||||||
|
--azure-file-volume-mount-path /home/frappe/frappe-bench/sites \
|
||||||
|
--command-line "/bin/bash -c 'bench new-site frontend --db-type postgres --db-host \$DB_HOST --db-port 5432 --db-name erpnext --db-user \$DB_USER --db-password \$DB_PASSWORD --admin-password \$ADMIN_PASSWORD --install-app erpnext && bench --site frontend migrate'" \
|
||||||
|
--subnet "/subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Network/virtualNetworks/erpnext-vnet/subnets/aci-subnet" \
|
||||||
|
--registry-login-server "$ACR_LOGIN_SERVER" \
|
||||||
|
--registry-username "$ACR_USERNAME" \
|
||||||
|
--registry-password "$ACR_PASSWORD"
|
||||||
|
|
||||||
|
# Wait for completion
|
||||||
|
print_color "$YELLOW" "Waiting for site initialization to complete..."
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
STATE=$(az container show \
|
||||||
|
--resource-group "$RESOURCE_GROUP" \
|
||||||
|
--name erpnext-init \
|
||||||
|
--query containers[0].instanceView.currentState.state -o tsv)
|
||||||
|
|
||||||
|
if [ "$STATE" = "Terminated" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
az container logs \
|
||||||
|
--resource-group "$RESOURCE_GROUP" \
|
||||||
|
--name erpnext-init
|
||||||
|
|
||||||
|
# Delete init container
|
||||||
|
az container delete \
|
||||||
|
--resource-group "$RESOURCE_GROUP" \
|
||||||
|
--name erpnext-init \
|
||||||
|
--yes
|
||||||
|
|
||||||
|
print_color "$GREEN" "Site initialization completed!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to scale deployment
|
||||||
|
scale_deployment() {
|
||||||
|
local replicas=${1:-3}
|
||||||
|
print_color "$YELLOW" "Scaling deployment to $replicas instances..."
|
||||||
|
|
||||||
|
# Deploy additional backend instances
|
||||||
|
for i in $(seq 2 $replicas); do
|
||||||
|
sed "s/erpnext-backend/erpnext-backend-$i/g" aci-backend.yaml > "aci-backend-$i.yaml"
|
||||||
|
az container create \
|
||||||
|
--resource-group "$RESOURCE_GROUP" \
|
||||||
|
--file "aci-backend-$i.yaml"
|
||||||
|
done
|
||||||
|
|
||||||
|
print_color "$GREEN" "Scaled to $replicas instances!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to show status
|
||||||
|
show_status() {
|
||||||
|
print_color "$YELLOW" "Container Instances Status:"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# List all container groups
|
||||||
|
az container list \
|
||||||
|
--resource-group "$RESOURCE_GROUP" \
|
||||||
|
--output table
|
||||||
|
|
||||||
|
# Show detailed status for main containers
|
||||||
|
for container in erpnext-backend erpnext-frontend; do
|
||||||
|
if az container show --resource-group "$RESOURCE_GROUP" --name "$container" &> /dev/null; then
|
||||||
|
print_color "$GREEN" "\n$container status:"
|
||||||
|
az container show \
|
||||||
|
--resource-group "$RESOURCE_GROUP" \
|
||||||
|
--name "$container" \
|
||||||
|
--query "{Name:name, State:containers[0].instanceView.currentState.state, CPU:containers[0].resources.requests.cpu, Memory:containers[0].resources.requests.memoryInGb, RestartCount:containers[0].instanceView.restartCount}" \
|
||||||
|
--output table
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Show application URL
|
||||||
|
if [ -n "${FRONTEND_FQDN:-}" ]; then
|
||||||
|
print_color "$GREEN" "\nApplication URL: http://$FRONTEND_FQDN:8080"
|
||||||
|
else
|
||||||
|
FRONTEND_FQDN=$(az container show \
|
||||||
|
--resource-group "$RESOURCE_GROUP" \
|
||||||
|
--name erpnext-frontend \
|
||||||
|
--query ipAddress.fqdn -o tsv 2>/dev/null || echo "")
|
||||||
|
if [ -n "$FRONTEND_FQDN" ]; then
|
||||||
|
print_color "$GREEN" "\nApplication URL: http://$FRONTEND_FQDN:8080"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to delete deployment
|
||||||
|
delete_deployment() {
|
||||||
|
print_color "$YELLOW" "Deleting Container Instances deployment..."
|
||||||
|
|
||||||
|
read -p "Are you sure you want to delete the deployment? (yes/no): " confirm
|
||||||
|
if [ "$confirm" != "yes" ]; then
|
||||||
|
print_color "$YELLOW" "Deletion cancelled."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Delete container groups
|
||||||
|
for container in $(az container list --resource-group "$RESOURCE_GROUP" --query "[].name" -o tsv); do
|
||||||
|
print_color "$YELLOW" "Deleting $container..."
|
||||||
|
az container delete \
|
||||||
|
--resource-group "$RESOURCE_GROUP" \
|
||||||
|
--name "$container" \
|
||||||
|
--yes
|
||||||
|
done
|
||||||
|
|
||||||
|
print_color "$GREEN" "Deployment deleted!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to view logs
|
||||||
|
view_logs() {
|
||||||
|
local container=${1:-erpnext-backend}
|
||||||
|
local container_name=${2:-backend}
|
||||||
|
|
||||||
|
print_color "$YELLOW" "Viewing logs for $container/$container_name..."
|
||||||
|
|
||||||
|
az container logs \
|
||||||
|
--resource-group "$RESOURCE_GROUP" \
|
||||||
|
--name "$container" \
|
||||||
|
--container-name "$container_name" \
|
||||||
|
--follow
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main script
|
||||||
|
main() {
|
||||||
|
case "${1:-}" in
|
||||||
|
deploy)
|
||||||
|
check_prerequisites
|
||||||
|
create_container_registry
|
||||||
|
build_and_push_images
|
||||||
|
create_file_shares
|
||||||
|
deploy_backend
|
||||||
|
deploy_frontend
|
||||||
|
initialize_site
|
||||||
|
show_status
|
||||||
|
;;
|
||||||
|
update)
|
||||||
|
check_prerequisites
|
||||||
|
source "$ENV_FILE"
|
||||||
|
build_and_push_images
|
||||||
|
print_color "$YELLOW" "Restarting containers..."
|
||||||
|
az container restart --resource-group "$RESOURCE_GROUP" --name erpnext-backend
|
||||||
|
az container restart --resource-group "$RESOURCE_GROUP" --name erpnext-frontend
|
||||||
|
show_status
|
||||||
|
;;
|
||||||
|
scale)
|
||||||
|
check_prerequisites
|
||||||
|
source "$ENV_FILE"
|
||||||
|
scale_deployment "${2:-3}"
|
||||||
|
;;
|
||||||
|
delete)
|
||||||
|
check_prerequisites
|
||||||
|
delete_deployment
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
check_prerequisites
|
||||||
|
show_status
|
||||||
|
;;
|
||||||
|
logs)
|
||||||
|
check_prerequisites
|
||||||
|
view_logs "${2:-erpnext-backend}" "${3:-backend}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 [deploy|update|scale|delete|status|logs] [options]"
|
||||||
|
echo ""
|
||||||
|
echo "Commands:"
|
||||||
|
echo " deploy - Deploy ERPNext on Container Instances"
|
||||||
|
echo " update - Update existing deployment"
|
||||||
|
echo " scale <count> - Scale backend instances (default: 3)"
|
||||||
|
echo " delete - Delete deployment"
|
||||||
|
echo " status - Show deployment status"
|
||||||
|
echo " logs [container] [name] - View container logs"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 deploy"
|
||||||
|
echo " $0 scale 5"
|
||||||
|
echo " $0 logs erpnext-backend worker-default"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main function
|
||||||
|
main "$@"
|
||||||
411
documentation/deployment-guides/azure-managed/scripts/deploy-managed.sh
Executable file
411
documentation/deployment-guides/azure-managed/scripts/deploy-managed.sh
Executable file
@ -0,0 +1,411 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# ERPNext AKS Deployment Script with Azure Managed Services
|
||||||
|
# Usage: ./deploy-managed.sh [deploy|update|delete|status]
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Color codes for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
MANIFEST_DIR="${SCRIPT_DIR}/../kubernetes-manifests"
|
||||||
|
ENV_FILE="${HOME}/erpnext-azure-env.sh"
|
||||||
|
|
||||||
|
# Function to print colored output
|
||||||
|
print_color() {
|
||||||
|
local color=$1
|
||||||
|
local message=$2
|
||||||
|
echo -e "${color}${message}${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to check prerequisites
|
||||||
|
check_prerequisites() {
|
||||||
|
print_color "$YELLOW" "Checking prerequisites..."
|
||||||
|
|
||||||
|
# Check Azure CLI
|
||||||
|
if ! command -v az &> /dev/null; then
|
||||||
|
print_color "$RED" "Azure CLI not found. Please install it first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check kubectl
|
||||||
|
if ! command -v kubectl &> /dev/null; then
|
||||||
|
print_color "$RED" "kubectl not found. Please install it first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check Helm
|
||||||
|
if ! command -v helm &> /dev/null; then
|
||||||
|
print_color "$RED" "Helm not found. Please install it first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check environment file
|
||||||
|
if [ ! -f "$ENV_FILE" ]; then
|
||||||
|
print_color "$RED" "Environment file not found at $ENV_FILE"
|
||||||
|
print_color "$YELLOW" "Please complete the prerequisites first (00-prerequisites-managed.md)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Source environment variables
|
||||||
|
source "$ENV_FILE"
|
||||||
|
|
||||||
|
# Check required environment variables
|
||||||
|
local required_vars=(
|
||||||
|
"RESOURCE_GROUP"
|
||||||
|
"LOCATION"
|
||||||
|
"DB_SERVER_NAME"
|
||||||
|
"REDIS_NAME"
|
||||||
|
"STORAGE_ACCOUNT"
|
||||||
|
"KEYVAULT_NAME"
|
||||||
|
"CLIENT_ID"
|
||||||
|
)
|
||||||
|
|
||||||
|
for var in "${required_vars[@]}"; do
|
||||||
|
if [ -z "${!var}" ]; then
|
||||||
|
print_color "$RED" "Required environment variable $var is not set"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
print_color "$GREEN" "Prerequisites check passed!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to create AKS cluster
|
||||||
|
create_aks_cluster() {
|
||||||
|
print_color "$YELLOW" "Creating AKS cluster..."
|
||||||
|
|
||||||
|
az aks create \
|
||||||
|
--name erpnext-aks \
|
||||||
|
--resource-group "$RESOURCE_GROUP" \
|
||||||
|
--location "$LOCATION" \
|
||||||
|
--node-count 3 \
|
||||||
|
--node-vm-size Standard_D4s_v3 \
|
||||||
|
--enable-managed-identity \
|
||||||
|
--assign-identity "$IDENTITY_ID" \
|
||||||
|
--network-plugin azure \
|
||||||
|
--vnet-subnet-id "/subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Network/virtualNetworks/erpnext-vnet/subnets/aks-subnet" \
|
||||||
|
--docker-bridge-address 172.17.0.1/16 \
|
||||||
|
--dns-service-ip 10.0.10.10 \
|
||||||
|
--service-cidr 10.0.10.0/24 \
|
||||||
|
--enable-cluster-autoscaler \
|
||||||
|
--min-count 3 \
|
||||||
|
--max-count 10 \
|
||||||
|
--enable-addons monitoring,azure-keyvault-secrets-provider \
|
||||||
|
--workspace-resource-id "/subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.OperationalInsights/workspaces/erpnext-logs" \
|
||||||
|
--generate-ssh-keys \
|
||||||
|
--yes
|
||||||
|
|
||||||
|
print_color "$GREEN" "AKS cluster created successfully!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to get AKS credentials
|
||||||
|
get_aks_credentials() {
|
||||||
|
print_color "$YELLOW" "Getting AKS credentials..."
|
||||||
|
|
||||||
|
az aks get-credentials \
|
||||||
|
--name erpnext-aks \
|
||||||
|
--resource-group "$RESOURCE_GROUP" \
|
||||||
|
--overwrite-existing
|
||||||
|
|
||||||
|
# Verify connection
|
||||||
|
kubectl get nodes
|
||||||
|
|
||||||
|
print_color "$GREEN" "Connected to AKS cluster!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to install NGINX Ingress
|
||||||
|
install_nginx_ingress() {
|
||||||
|
print_color "$YELLOW" "Installing NGINX Ingress Controller..."
|
||||||
|
|
||||||
|
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
|
||||||
|
helm repo update
|
||||||
|
|
||||||
|
helm upgrade --install nginx-ingress ingress-nginx/ingress-nginx \
|
||||||
|
--namespace ingress-nginx \
|
||||||
|
--create-namespace \
|
||||||
|
--set controller.service.type=LoadBalancer \
|
||||||
|
--set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz \
|
||||||
|
--set controller.service.externalTrafficPolicy=Local \
|
||||||
|
--wait
|
||||||
|
|
||||||
|
# Wait for external IP
|
||||||
|
print_color "$YELLOW" "Waiting for LoadBalancer IP..."
|
||||||
|
kubectl wait --namespace ingress-nginx \
|
||||||
|
--for=condition=ready pod \
|
||||||
|
--selector=app.kubernetes.io/component=controller \
|
||||||
|
--timeout=120s
|
||||||
|
|
||||||
|
INGRESS_IP=$(kubectl get service -n ingress-nginx nginx-ingress-ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
|
||||||
|
print_color "$GREEN" "NGINX Ingress installed! External IP: $INGRESS_IP"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to install cert-manager
|
||||||
|
install_cert_manager() {
|
||||||
|
print_color "$YELLOW" "Installing cert-manager..."
|
||||||
|
|
||||||
|
helm repo add jetstack https://charts.jetstack.io
|
||||||
|
helm repo update
|
||||||
|
|
||||||
|
helm upgrade --install cert-manager jetstack/cert-manager \
|
||||||
|
--namespace cert-manager \
|
||||||
|
--create-namespace \
|
||||||
|
--version v1.12.0 \
|
||||||
|
--set installCRDs=true \
|
||||||
|
--wait
|
||||||
|
|
||||||
|
# Create ClusterIssuer
|
||||||
|
cat <<EOF | kubectl apply -f -
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt-prod
|
||||||
|
spec:
|
||||||
|
acme:
|
||||||
|
server: https://acme-v02.api.letsencrypt.org/directory
|
||||||
|
email: ${EMAIL:-admin@example.com}
|
||||||
|
privateKeySecretRef:
|
||||||
|
name: letsencrypt-prod
|
||||||
|
solvers:
|
||||||
|
- http01:
|
||||||
|
ingress:
|
||||||
|
class: nginx
|
||||||
|
EOF
|
||||||
|
|
||||||
|
print_color "$GREEN" "cert-manager installed successfully!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to prepare manifests
|
||||||
|
prepare_manifests() {
|
||||||
|
print_color "$YELLOW" "Preparing Kubernetes manifests..."
|
||||||
|
|
||||||
|
# Export additional variables
|
||||||
|
export TENANT_ID=$(az account show --query tenantId -o tsv)
|
||||||
|
export ACR_AUTH=$(echo -n "${ACR_USERNAME}:${ACR_PASSWORD}" | base64)
|
||||||
|
export DOMAIN=${DOMAIN:-erpnext.example.com}
|
||||||
|
|
||||||
|
# Create temporary directory for processed manifests
|
||||||
|
TEMP_DIR=$(mktemp -d)
|
||||||
|
|
||||||
|
# Process each manifest file
|
||||||
|
for manifest in "$MANIFEST_DIR"/*.yaml; do
|
||||||
|
filename=$(basename "$manifest")
|
||||||
|
envsubst < "$manifest" > "$TEMP_DIR/$filename"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$TEMP_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to deploy ERPNext
|
||||||
|
deploy_erpnext() {
|
||||||
|
print_color "$YELLOW" "Deploying ERPNext to AKS..."
|
||||||
|
|
||||||
|
# Prepare manifests
|
||||||
|
TEMP_DIR=$(prepare_manifests)
|
||||||
|
|
||||||
|
# Apply manifests in order
|
||||||
|
kubectl apply -f "$TEMP_DIR/namespace.yaml"
|
||||||
|
kubectl apply -f "$TEMP_DIR/storage.yaml"
|
||||||
|
kubectl apply -f "$TEMP_DIR/configmap.yaml"
|
||||||
|
kubectl apply -f "$TEMP_DIR/secrets.yaml"
|
||||||
|
|
||||||
|
# Wait for PVCs to be bound
|
||||||
|
print_color "$YELLOW" "Waiting for storage provisioning..."
|
||||||
|
kubectl wait --for=condition=Bound pvc/erpnext-sites -n erpnext --timeout=120s
|
||||||
|
kubectl wait --for=condition=Bound pvc/erpnext-assets -n erpnext --timeout=120s
|
||||||
|
|
||||||
|
# Deploy applications
|
||||||
|
kubectl apply -f "$TEMP_DIR/erpnext-backend.yaml"
|
||||||
|
kubectl apply -f "$TEMP_DIR/erpnext-frontend.yaml"
|
||||||
|
kubectl apply -f "$TEMP_DIR/erpnext-workers.yaml"
|
||||||
|
|
||||||
|
# Wait for deployments
|
||||||
|
print_color "$YELLOW" "Waiting for deployments to be ready..."
|
||||||
|
kubectl wait --for=condition=available --timeout=300s deployment/erpnext-backend -n erpnext
|
||||||
|
kubectl wait --for=condition=available --timeout=300s deployment/erpnext-frontend -n erpnext
|
||||||
|
|
||||||
|
# Apply ingress
|
||||||
|
kubectl apply -f "$TEMP_DIR/ingress.yaml"
|
||||||
|
|
||||||
|
# Run site initialization
|
||||||
|
kubectl apply -f "$TEMP_DIR/jobs.yaml"
|
||||||
|
|
||||||
|
# Wait for site initialization
|
||||||
|
print_color "$YELLOW" "Waiting for site initialization..."
|
||||||
|
kubectl wait --for=condition=complete --timeout=600s job/erpnext-site-init -n erpnext
|
||||||
|
|
||||||
|
# Clean up temp directory
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
|
||||||
|
print_color "$GREEN" "ERPNext deployed successfully!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to update deployment
|
||||||
|
update_deployment() {
|
||||||
|
print_color "$YELLOW" "Updating ERPNext deployment..."
|
||||||
|
|
||||||
|
# Prepare manifests
|
||||||
|
TEMP_DIR=$(prepare_manifests)
|
||||||
|
|
||||||
|
# Apply updates
|
||||||
|
kubectl apply -f "$TEMP_DIR/configmap.yaml"
|
||||||
|
kubectl apply -f "$TEMP_DIR/erpnext-backend.yaml"
|
||||||
|
kubectl apply -f "$TEMP_DIR/erpnext-frontend.yaml"
|
||||||
|
kubectl apply -f "$TEMP_DIR/erpnext-workers.yaml"
|
||||||
|
|
||||||
|
# Restart deployments to pick up changes
|
||||||
|
kubectl rollout restart deployment -n erpnext
|
||||||
|
|
||||||
|
# Wait for rollout
|
||||||
|
kubectl rollout status deployment/erpnext-backend -n erpnext
|
||||||
|
kubectl rollout status deployment/erpnext-frontend -n erpnext
|
||||||
|
|
||||||
|
# Clean up temp directory
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
|
||||||
|
print_color "$GREEN" "Deployment updated successfully!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to delete deployment
|
||||||
|
delete_deployment() {
|
||||||
|
print_color "$YELLOW" "Deleting ERPNext deployment..."
|
||||||
|
|
||||||
|
read -p "Are you sure you want to delete the deployment? This will delete all data! (yes/no): " confirm
|
||||||
|
if [ "$confirm" != "yes" ]; then
|
||||||
|
print_color "$YELLOW" "Deletion cancelled."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Delete namespace (this will delete all resources in it)
|
||||||
|
kubectl delete namespace erpnext --ignore-not-found
|
||||||
|
|
||||||
|
# Delete cert-manager resources
|
||||||
|
kubectl delete clusterissuer letsencrypt-prod --ignore-not-found
|
||||||
|
|
||||||
|
print_color "$GREEN" "Deployment deleted!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to show deployment status
|
||||||
|
show_status() {
|
||||||
|
print_color "$YELLOW" "ERPNext Deployment Status:"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if namespace exists
|
||||||
|
if ! kubectl get namespace erpnext &> /dev/null; then
|
||||||
|
print_color "$RED" "ERPNext namespace not found. Deployment may not exist."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show deployments
|
||||||
|
print_color "$GREEN" "Deployments:"
|
||||||
|
kubectl get deployments -n erpnext
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Show pods
|
||||||
|
print_color "$GREEN" "Pods:"
|
||||||
|
kubectl get pods -n erpnext
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Show services
|
||||||
|
print_color "$GREEN" "Services:"
|
||||||
|
kubectl get services -n erpnext
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Show ingress
|
||||||
|
print_color "$GREEN" "Ingress:"
|
||||||
|
kubectl get ingress -n erpnext
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Show PVCs
|
||||||
|
print_color "$GREEN" "Storage:"
|
||||||
|
kubectl get pvc -n erpnext
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Show HPA
|
||||||
|
print_color "$GREEN" "Autoscaling:"
|
||||||
|
kubectl get hpa -n erpnext
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Get application URL
|
||||||
|
if kubectl get ingress erpnext-ingress -n erpnext &> /dev/null; then
|
||||||
|
INGRESS_IP=$(kubectl get service -n ingress-nginx nginx-ingress-ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
|
||||||
|
print_color "$GREEN" "Application URL: http://$INGRESS_IP"
|
||||||
|
print_color "$YELLOW" "Configure your DNS to point ${DOMAIN:-erpnext.example.com} to $INGRESS_IP"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to run diagnostics
|
||||||
|
run_diagnostics() {
|
||||||
|
print_color "$YELLOW" "Running diagnostics..."
|
||||||
|
|
||||||
|
# Check database connectivity
|
||||||
|
print_color "$YELLOW" "Testing database connectivity..."
|
||||||
|
kubectl run pg-test --rm -i --image=postgres:13 -n erpnext --restart=Never -- \
|
||||||
|
psql -h "$DB_SERVER_NAME.postgres.database.azure.com" -U "$DB_ADMIN_USER" -d erpnext -c "SELECT 1" || true
|
||||||
|
|
||||||
|
# Check Redis connectivity
|
||||||
|
print_color "$YELLOW" "Testing Redis connectivity..."
|
||||||
|
kubectl run redis-test --rm -i --image=redis:alpine -n erpnext --restart=Never -- \
|
||||||
|
redis-cli -h "$REDIS_HOST" -a "$REDIS_KEY" ping || true
|
||||||
|
|
||||||
|
# Check pod logs
|
||||||
|
print_color "$YELLOW" "Recent pod events:"
|
||||||
|
kubectl get events -n erpnext --sort-by='.lastTimestamp' | tail -20
|
||||||
|
|
||||||
|
print_color "$GREEN" "Diagnostics complete!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main script
|
||||||
|
main() {
|
||||||
|
case "${1:-}" in
|
||||||
|
deploy)
|
||||||
|
check_prerequisites
|
||||||
|
create_aks_cluster
|
||||||
|
get_aks_credentials
|
||||||
|
install_nginx_ingress
|
||||||
|
install_cert_manager
|
||||||
|
deploy_erpnext
|
||||||
|
show_status
|
||||||
|
;;
|
||||||
|
update)
|
||||||
|
check_prerequisites
|
||||||
|
get_aks_credentials
|
||||||
|
update_deployment
|
||||||
|
show_status
|
||||||
|
;;
|
||||||
|
delete)
|
||||||
|
check_prerequisites
|
||||||
|
get_aks_credentials
|
||||||
|
delete_deployment
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
check_prerequisites
|
||||||
|
get_aks_credentials
|
||||||
|
show_status
|
||||||
|
;;
|
||||||
|
diagnose)
|
||||||
|
check_prerequisites
|
||||||
|
get_aks_credentials
|
||||||
|
run_diagnostics
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 [deploy|update|delete|status|diagnose]"
|
||||||
|
echo ""
|
||||||
|
echo "Commands:"
|
||||||
|
echo " deploy - Create AKS cluster and deploy ERPNext"
|
||||||
|
echo " update - Update existing deployment"
|
||||||
|
echo " delete - Delete deployment and all resources"
|
||||||
|
echo " status - Show deployment status"
|
||||||
|
echo " diagnose - Run diagnostic checks"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main function
|
||||||
|
main "$@"
|
||||||
Loading…
Reference in New Issue
Block a user