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.
357 lines
11 KiB
YAML
357 lines
11 KiB
YAML
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 |