diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cb44980 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Terraform files +*.tfstate +*.tfstate.* +.terraform/ +.terraform.lock.hcl +terraform.tfvars +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Kubernetes secrets +*-secret.yaml +secrets/ + +# Environment files +.env +.env.local + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Temporary files +tmp/ +temp/ +*.tmp diff --git a/README.md b/README.md index ab7021c..b06225e 100644 --- a/README.md +++ b/README.md @@ -1 +1,308 @@ -# aks-nextcloud \ No newline at end of file +# Nextcloud on Azure Kubernetes Service (AKS) + +This repository contains infrastructure as code (IaC) for deploying Nextcloud on Azure Kubernetes Service (AKS) using Terraform and Kubernetes manifests. + +## Architecture + +The infrastructure includes: + +- **Azure Kubernetes Service (AKS)**: Container orchestration platform +- **MySQL (containerized)**: Database running as StatefulSet within AKS cluster +- **Azure Storage Account**: Persistent storage for Nextcloud user files and config using Azure Files +- **Azure Virtual Network**: Network isolation and security +- **Redis**: In-memory cache for improved performance +- **Kubernetes Resources**: + - Nextcloud application deployment + - MySQL StatefulSet with persistent storage + - Redis deployment for caching + - Persistent Volume Claims for data storage + - Services and Ingress for external access + +### Storage Architecture + +Nextcloud uses a **split storage approach** for optimal performance: + +1. **Application Code** (container local filesystem): + - Nextcloud application files remain in the container + - Fast startup and execution + - No rsync overhead during initialization + +2. **User Data** (Azure Files - 100GB): + - Mounted at `/var/www/html/data` + - Stores user-uploaded files + - ReadWriteMany access mode for multi-pod support + +3. **Configuration** (Azure Files - 1GB): + - Mounted at `/var/www/html/config` + - Stores config.php and settings + - Shared across all Nextcloud pods for consistency + +4. **Database** (Managed-CSI - 20GB): + - MySQL uses block storage for optimal performance + - Fast database operations + +## Prerequisites + +- [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) (>= 2.30) +- [Terraform](https://www.terraform.io/downloads.html) (>= 1.0) +- [kubectl](https://kubernetes.io/docs/tasks/tools/) (>= 1.24) +- [kustomize](https://kustomize.io/) (>= 4.0) - optional, for Kustomize-based deployments +- Azure subscription with appropriate permissions + +## Quick Start + +### 1. Deploy Infrastructure with Terraform + +```bash +# Login to Azure +az login + +# Navigate to terraform directory +cd terraform + +# Copy example variables file +cp terraform.tfvars.example terraform.tfvars + +# Edit terraform.tfvars with your desired configuration +vim terraform.tfvars + +# Initialize Terraform +terraform init + +# Review the planned changes +terraform plan + +# Apply the configuration +terraform apply +``` + +### 2. Configure kubectl + +```bash +# Get AKS credentials +az aks get-credentials --resource-group --name + +# Verify connection +kubectl get nodes +``` + +### 3. Update Kubernetes Secrets + +After Terraform completes, update the secrets with actual values: + +```bash +# Get Terraform outputs +terraform output -json > outputs.json + +# Extract values (example using jq) +POSTGRES_FQDN=$(terraform output -raw mysql_fqdn) +POSTGRES_PASSWORD=$(terraform output -raw mysql_admin_password) +STORAGE_ACCOUNT_NAME=$(terraform output -raw storage_account_name) +STORAGE_ACCOUNT_KEY=$(terraform output -raw storage_account_key) + +# Update secrets file +cd ../kubernetes/base +# Edit secrets.yaml and replace placeholder values +``` + +Or use a script to generate secrets: + +```bash +# Example: Create secrets from Terraform outputs +kubectl create secret generic nextcloud-db \ + --from-literal=db-host="$POSTGRES_FQDN" \ + --from-literal=db-name="nextcloud" \ + --from-literal=db-username="nextcloudadmin" \ + --from-literal=db-password="$POSTGRES_PASSWORD" \ + --namespace=nextcloud --dry-run=client -o yaml > secrets-db.yaml + +kubectl create secret generic azure-storage \ + --from-literal=azurestorageaccountname="$STORAGE_ACCOUNT_NAME" \ + --from-literal=azurestorageaccountkey="$STORAGE_ACCOUNT_KEY" \ + --namespace=nextcloud --dry-run=client -o yaml > secrets-storage.yaml +``` + +### 4. Deploy Nextcloud to Kubernetes + +#### Option A: Using kubectl + +```bash +cd kubernetes/base + +# Create namespace +kubectl apply -f namespace.yaml + +# Apply all resources +kubectl apply -f . +``` + +#### Option B: Using Kustomize (Recommended) + +For development environment: +```bash +cd kubernetes/overlays/dev +kubectl apply -k . +``` + +For production environment: +```bash +cd kubernetes/overlays/prod +kubectl apply -k . +``` + +### 5. Access Nextcloud + +```bash +# Get the external IP address +kubectl get service nextcloud -n nextcloud + +# Wait for EXTERNAL-IP to be assigned +# Access Nextcloud at http:// +``` + +**Important:** After first deployment, wait for Nextcloud initialization to complete (5-10 minutes), then scale to multiple replicas if needed: + +```bash +# Check if Nextcloud is initialized +kubectl logs -n nextcloud -l app=nextcloud --tail=20 +# Look for "Nextcloud is already installed" + +# Scale to 2 replicas (after initialization completes) +kubectl scale deployment nextcloud -n nextcloud --replicas=2 +``` + +For production with Ingress: +1. Install an Ingress controller (e.g., NGINX Ingress Controller) +2. Install cert-manager for TLS certificates +3. **Important:** Update the Ingress resource in `kubernetes/base/ingress.yaml` with your actual domain name (replace `nextcloud.example.com`) +4. Access Nextcloud at https://your-domain.com + +## Configuration + +### Terraform Variables + +Key variables in `terraform/variables.tf`: + +- `resource_group_name`: Azure resource group name +- `location`: Azure region (e.g., westeurope, eastus) - works in any region with containerized MySQL +- `prefix`: Prefix for resource names +- `node_count`: Initial number of AKS nodes +- `vm_size`: VM size for AKS nodes + +Note: MySQL is deployed as a containerized StatefulSet within AKS, so no database-specific Terraform variables are needed. + +### Kubernetes Configuration + +Key configurations in `kubernetes/base/configmap.yaml`: + +- `NEXTCLOUD_TRUSTED_DOMAINS`: Domains allowed to access Nextcloud +- `PHP_MEMORY_LIMIT`: PHP memory limit +- `PHP_UPLOAD_LIMIT`: Maximum file upload size +- `REDIS_HOST`: Redis hostname for caching + +## Scaling + +### Horizontal Pod Autoscaling + +To enable HPA for Nextcloud: + +```bash +kubectl autoscale deployment nextcloud \ + --cpu-percent=70 \ + --min=2 \ + --max=10 \ + -n nextcloud +``` + +### AKS Node Autoscaling + +The AKS cluster is configured with autoscaling enabled. Adjust `min_node_count` and `max_node_count` in Terraform variables. + +## Monitoring + +Consider installing: + +- **Prometheus & Grafana**: For metrics and monitoring +- **Azure Monitor**: For AKS and resource monitoring +- **Log Analytics**: For centralized logging + +## Backup and Disaster Recovery + +1. **Database Backups**: MySQL data is stored in Kubernetes persistent volumes. Use Velero or Azure Backup for Kubernetes for backup/restore +2. **File Backups**: Use Azure Storage snapshots or backup solutions +3. **Kubernetes Resources**: Store manifests in version control (this repository) + +## Security Considerations + +1. **Secrets Management**: + - Use Azure Key Vault for production secrets + - Consider External Secrets Operator or Sealed Secrets + +2. **Network Security**: + - Configure Network Security Groups (NSGs) + - MySQL is internal to the cluster (not publicly accessible) + - Enable Pod Security Standards + +3. **TLS/SSL**: + - Use cert-manager with Let's Encrypt for automatic certificate management + - Configure HTTPS-only access + +4. **Database Security**: + - Use strong passwords (auto-generated in Terraform) + - Restrict firewall rules + - Enable SSL connections + +## Troubleshooting + +### Terraform deployment errors + +Note: This deployment uses containerized MySQL which works in any Azure region without subscription restrictions. + +### Check pod status +```bash +kubectl get pods -n nextcloud +kubectl describe pod -n nextcloud +kubectl logs -n nextcloud +``` + +### Check persistent volumes +```bash +kubectl get pv,pvc -n nextcloud +``` + +### Database connection issues +```bash +# Test from a debug pod +kubectl run -it --rm debug --image=mysql:8.0 --restart=Never -n nextcloud -- mysql -h mysql -u nextcloud -p +``` + +## Cleanup + +To destroy all resources: + +```bash +# Delete Kubernetes resources +kubectl delete namespace nextcloud + +# Destroy Terraform infrastructure +cd terraform +terraform destroy +``` + +## Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Submit a pull request + +## License + +MIT License - See LICENSE file for details + +## References + +- [Nextcloud Documentation](https://docs.nextcloud.com/) +- [Azure AKS Documentation](https://docs.microsoft.com/en-us/azure/aks/) +- [Terraform Azure Provider](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs) +- [Kubernetes Documentation](https://kubernetes.io/docs/home/) \ No newline at end of file diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..c5b63da --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,65 @@ +# Architecture Overview + +This document describes the architecture of the Nextcloud deployment on Azure Kubernetes Service. + +## Components + +### Azure Infrastructure + +1. **Resource Group**: Container for all resources +2. **Virtual Network**: Isolated network (10.0.0.0/16) + - AKS Subnet (10.0.1.0/24) +3. **AKS Cluster**: Kubernetes orchestration + - Autoscaling: 1-5 nodes + - VM Size: Standard_D2s_v3 +4. **Storage Account**: Azure Files for persistent user data and config + - User data file share: 100GB (for uploaded files) + - Config file share: 1GB (for configuration files) + - Application code remains in container (no rsync overhead) + +### Kubernetes Resources + +1. **Namespace**: nextcloud +2. **StatefulSets**: + - MySQL (1 replica) with 20GB persistent volume +3. **Deployments**: + - Nextcloud (2 replicas) + - Redis (1 replica) +4. **Services**: + - LoadBalancer for external access + - ClusterIP for Redis + - Headless service for MySQL StatefulSet +5. **Storage**: + - PVC with Azure Files for Nextcloud user data (100GB) + - PVC with Azure Files for Nextcloud config (1GB) + - PVC with managed-csi for MySQL data (20GB) + - Application code remains in container filesystem (fast initialization) +6. **Configuration**: ConfigMaps and Secrets + +## Data Flow + +1. User → LoadBalancer → Nextcloud Pod +2. Nextcloud → Redis (cache) +3. Nextcloud → MySQL (metadata and database) +4. Nextcloud → Azure Files (user uploaded files + config) + +## Security + +- VNet isolation +- Managed identities +- Kubernetes secrets +- Database firewall rules +- Encryption at rest and in transit + +## Scaling + +- Horizontal pod autoscaling +- AKS node autoscaling +- Database vertical scaling + +## High Availability + +- Multiple Nextcloud pod replicas +- MySQL StatefulSet with persistent storage +- Use Velero for backup/restore +- Zone-redundant storage option for Azure Files diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 0000000..6d85804 --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,365 @@ +# Deployment Guide + +This guide provides step-by-step instructions for deploying Nextcloud on Azure Kubernetes Service. + +## Storage Architecture + +This deployment uses an **optimized split storage approach** for fast initialization and reliable multi-pod operations: + +### What Gets Mounted Where + +1. **Application Code** (`/var/www/html/*` except data and config): + - **Remains in container** filesystem + - **Why**: Fast initialization (no rsync overhead) + - Includes: PHP files, apps, themes, assets + +2. **User Data** (`/var/www/html/data`): + - **Mounted from Azure Files** (ReadWriteMany) + - **Why**: User-uploaded files need to be shared across all pods + - Size: 100GB + +3. **Configuration** (`/var/www/html/config`): + - **Mounted from Azure Files** (ReadWriteMany) + - **Why**: Config.php and settings must be consistent across pods + - Size: 1GB + +### Why Not Mount Entire /var/www/html? + +Mounting the entire Nextcloud directory on Azure Files causes: +- **Extremely slow initialization** (15-30 minutes for rsync operations) +- Connection timeouts and pod restarts +- Startup probe failures + +With split storage: +- ✅ **Fast initialization** (2-5 minutes instead of 30+ minutes) +- ✅ **Reliable startup** (no timeouts) +- ✅ **Multi-pod support** (data and config shared via Azure Files) +- ✅ **Best practice** Nextcloud architecture + +## Prerequisites + +Before starting, ensure you have: + +1. **Azure Account** with an active subscription +2. **Azure CLI** installed and logged in (`az login`) +3. **Terraform** (>= 1.0) installed +4. **kubectl** (>= 1.24) installed +5. Appropriate Azure permissions to create resources + +## Automated Deployment + +The quickest way to deploy is using the provided script: + +```bash +cd scripts +./deploy.sh +``` + +This script will: +- Deploy the Terraform infrastructure +- Configure kubectl +- Create all necessary secrets +- Deploy Nextcloud to Kubernetes +- Wait for the service to be ready +- Display the access URL + +## Manual Deployment + +For more control over the deployment process, follow these steps: + +### Step 1: Configure Terraform + +```bash +cd terraform +cp terraform.tfvars.example terraform.tfvars +# Edit terraform.tfvars with your settings +``` + +### Step 2: Deploy Infrastructure + +```bash +terraform init +terraform plan +terraform apply +``` + +### Step 3: Configure Kubernetes + +```bash +az aks get-credentials --resource-group --name +kubectl get nodes +``` + +### Step 4: Create Secrets + +```bash +kubectl create secret generic nextcloud-db --namespace=nextcloud \ + --from-literal=db-host="$(terraform output -raw mysql_fqdn)" \ + --from-literal=db-name="nextcloud" \ + --from-literal=db-username="nextcloudadmin" \ + --from-literal=db-password="$(terraform output -raw mysql_admin_password)" +``` + +### Step 5: Deploy Application + +```bash +cd ../kubernetes/overlays/prod +kubectl apply -k . +``` + +## Scaling Nextcloud + +**Important:** The deployment starts with **1 replica** to avoid concurrent database initialization conflicts. + +### After Initial Deployment + +Once Nextcloud is fully initialized and running (all pods show READY 1/1), you can scale to multiple replicas: + +```bash +# Scale to 2 replicas (default for base) +kubectl scale deployment nextcloud -n nextcloud --replicas=2 + +# Or scale to 3 replicas (recommended for prod) +kubectl scale deployment nextcloud -n nextcloud --replicas=3 +``` + +### Why Start with 1 Replica? + +Nextcloud's initialization process: +- Creates database schema and tables on first run +- Uses file locking to prevent concurrent initialization +- Multiple pods starting simultaneously causes "flock: Permission denied" errors +- After initialization, Nextcloud supports multiple replicas without issues + +### Trusted Domains Configuration + +The deployment includes an init container that automatically configures trusted domains: +- **During initial installation**: Uses `NEXTCLOUD_TRUSTED_DOMAINS` environment variable +- **For existing installations**: Init container uses `occ` command to update config.php +- Configured domains: `*.azurewebsites.net`, `*.westeurope.cloudapp.azure.com`, `nextcloud.example.com` +- Init container only runs when config.php exists (after first installation completes) +- Allows access via Azure public IP DNS names and custom domains + +### Verifying Initialization + +Check if Nextcloud has completed initialization: + +```bash +# Check pod status - should show READY 1/1 +kubectl get pods -n nextcloud -l app=nextcloud + +# Check logs - should show "Nextcloud is already installed" +kubectl logs -n nextcloud -l app=nextcloud --tail=20 + +# Once you see "Nextcloud is already installed", it's safe to scale up +kubectl scale deployment nextcloud -n nextcloud --replicas=2 +``` + +## Cleanup + +To remove all deployed resources: + +```bash +cd scripts +./cleanup.sh +``` + +Or manually: + +```bash +# Delete Kubernetes resources +kubectl delete namespace nextcloud + +# Delete Azure infrastructure +cd terraform +terraform destroy +``` + +## Troubleshooting + +Note: This deployment uses containerized MySQL which works in all Azure regions without subscription restrictions. + +### Common Issues + +#### Concurrent Initialization Conflicts + +If you see errors like "flock: Permission denied" or "Another process is initializing Nextcloud": + +**Cause:** Multiple Nextcloud pods trying to initialize the database simultaneously. + +**Solution:** +1. **Delete all Nextcloud pods to stop conflicting initialization:** +```bash +kubectl delete pods -n nextcloud -l app=nextcloud +``` + +2. **Ensure only 1 replica is configured during first deployment:** +```bash +kubectl scale deployment nextcloud -n nextcloud --replicas=1 +``` + +3. **Wait for initialization to complete** (5-10 minutes): +```bash +kubectl logs -f -n nextcloud -l app=nextcloud +# Wait until you see "Nextcloud is already installed" +``` + +4. **Once initialized, scale to desired replicas:** +```bash +kubectl scale deployment nextcloud -n nextcloud --replicas=2 +``` + +**Prevention:** The base deployment now starts with 1 replica by default to avoid this issue. + +### Nextcloud Pods Crashing + +If Nextcloud pods are in CrashLoopBackOff state: + +1. **Check if MySQL is running:** +```bash +kubectl get pods -n nextcloud -l app=mysql +kubectl get statefulset -n nextcloud +``` + +2. **If MySQL pod doesn't exist, deploy it:** +```bash +kubectl apply -f kubernetes/base/mysql-statefulset.yaml +``` + +3. **Wait for MySQL to be ready:** +```bash +kubectl wait --for=condition=ready pod -l app=mysql -n nextcloud --timeout=300s +``` + +4. **Check MySQL logs:** +```bash +kubectl logs -n nextcloud -l app=mysql +``` + +5. **Restart Nextcloud after MySQL is ready:** +```bash +kubectl rollout restart deployment/nextcloud -n nextcloud +``` + +### Nextcloud Pods Not Becoming Ready + +If Nextcloud pods show as Running but READY is 0/1 for extended periods: + +1. **Check Nextcloud logs for initialization progress:** +```bash +kubectl logs -n nextcloud -l app=nextcloud --tail=100 +``` + +2. **Nextcloud first-time initialization takes 2-5 minutes with optimized storage.** The startup probe allows up to 10 minutes (60 failures × 10 sec period = 600 sec). + +3. **If logs show "Initializing nextcloud..." with no further output:** + - Initialization is running silently in the background + - Database schema creation typically takes 2-5 minutes on first run + - Wait and continue monitoring logs with `kubectl logs -f -n nextcloud -l app=nextcloud` + +4. **Check pod events and restart count:** +```bash +kubectl get pods -n nextcloud -l app=nextcloud # Check RESTARTS column +kubectl get events -n nextcloud --field-selector involvedObject.name= --sort-by='.lastTimestamp' +``` + +5. **If pod is restarting before initialization completes (rare with optimized storage):** + - Events will show "Container nextcloud failed startup probe" + - The startup probe allows 10 minutes (600 seconds) + - With optimized storage, initialization typically completes in 2-5 minutes + - If initialization takes longer, you can increase `failureThreshold` in `nextcloud-deployment.yaml` (e.g., from 60 to 90 for 15 minutes) + - Re-apply after editing: `kubectl apply -f kubernetes/base/nextcloud-deployment.yaml` + - Delete pods to pick up changes: `kubectl delete pods -n nextcloud -l app=nextcloud` + +6. **Verify Apache is running inside the container:** +```bash +# Get the pod name first +POD_NAME=$(kubectl get pods -n nextcloud -l app=nextcloud -o jsonpath='{.items[0].metadata.name}') +kubectl exec -n nextcloud $POD_NAME -- ps aux | grep apache +``` + +7. **If initialization is stuck, check database connectivity from a test pod:** +```bash +kubectl run -it --rm debug --image=mysql:8.0 --restart=Never -n nextcloud -- mysql -h mysql -u nextcloud -p +# Enter password from: kubectl get secret nextcloud-db -n nextcloud -o jsonpath='{.data.db-password}' | base64 -d +``` + +**Note:** The `mysql` command is not available in Nextcloud pods. Use a separate MySQL debug pod as shown above. + +### Database Connection Issues + +If Nextcloud can't connect to MySQL: + +1. **Verify secrets are created (not using placeholders):** +```bash +kubectl get secret nextcloud-db -n nextcloud -o jsonpath='{.data.db-password}' | base64 -d +# Should show a random password, not "REPLACE_WITH_MYSQL_PASSWORD" +``` + +2. **Verify MySQL services are running:** +```bash +kubectl get svc -n nextcloud +# Should show two MySQL services: +# - mysql: ClusterIP with an IP address (for client connections) +# - mysql-headless: ClusterIP None (for StatefulSet pod management) +``` + +3. **Test database connectivity from a debug pod:** +```bash +kubectl run -it --rm debug --image=mysql:8.0 --restart=Never -n nextcloud -- mysql -h mysql -u nextcloud -p +``` + +### MySQL StatefulSet Issues + +If MySQL pod won't start: + +1. **Check persistent volume claim:** +```bash +kubectl get pvc -n nextcloud +``` + +2. **Check MySQL logs:** +```bash +kubectl logs -n nextcloud mysql-0 +``` + +3. **Describe the MySQL pod:** +```bash +kubectl describe pod mysql-0 -n nextcloud +``` + +### Pods Stuck in Pending State + +If pods remain in Pending state: + +1. **Check node availability:** +```bash +kubectl get nodes +``` + +2. **Describe the pending pod:** +```bash +kubectl describe pod -n nextcloud +``` + +3. **Check for resource constraints or PVC binding issues** + +### Re-deployment + +If you need to completely redeploy: + +1. **Delete all Kubernetes resources:** +```bash +kubectl delete namespace nextcloud +``` + +2. **Recreate namespace:** +```bash +kubectl create namespace nextcloud +``` + +3. **Run deployment script again:** +```bash +cd scripts +./deploy.sh +``` diff --git a/kubernetes/base/configmap.yaml b/kubernetes/base/configmap.yaml new file mode 100644 index 0000000..f0eecdc --- /dev/null +++ b/kubernetes/base/configmap.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: nextcloud-config + namespace: nextcloud +data: + MYSQL_DATABASE: "nextcloud" + NEXTCLOUD_TRUSTED_DOMAINS: "*.westeurope.cloudapp.azure.com" + REDIS_HOST: "redis" + REDIS_HOST_PORT: "6379" + PHP_MEMORY_LIMIT: "512M" + PHP_UPLOAD_LIMIT: "10G" diff --git a/kubernetes/base/ingress.yaml b/kubernetes/base/ingress.yaml new file mode 100644 index 0000000..d942207 --- /dev/null +++ b/kubernetes/base/ingress.yaml @@ -0,0 +1,29 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: nextcloud + namespace: nextcloud + annotations: + kubernetes.io/ingress.class: nginx + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/proxy-body-size: "10G" + nginx.ingress.kubernetes.io/proxy-buffering: "off" + nginx.ingress.kubernetes.io/proxy-request-buffering: "off" + nginx.ingress.kubernetes.io/server-snippet: | + client_max_body_size 10G; +spec: + tls: + - hosts: + - nextcloud.example.com # CHANGE THIS: Replace with your actual domain name + secretName: nextcloud-tls + rules: + - host: nextcloud.example.com # CHANGE THIS: Replace with your actual domain name + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: nextcloud + port: + number: 80 diff --git a/kubernetes/base/kustomization.yaml b/kubernetes/base/kustomization.yaml new file mode 100644 index 0000000..631301c --- /dev/null +++ b/kubernetes/base/kustomization.yaml @@ -0,0 +1,17 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - namespace.yaml + - configmap.yaml + - secrets.yaml + - pvc.yaml + - mysql-statefulset.yaml + - redis.yaml + - nextcloud-deployment.yaml + - nextcloud-service.yaml + # Note: ingress.yaml is excluded from base - add it in overlays with environment-specific domain + +commonLabels: + app.kubernetes.io/name: nextcloud + app.kubernetes.io/managed-by: kustomize diff --git a/kubernetes/base/mysql-statefulset.yaml b/kubernetes/base/mysql-statefulset.yaml new file mode 100644 index 0000000..1d6edb7 --- /dev/null +++ b/kubernetes/base/mysql-statefulset.yaml @@ -0,0 +1,107 @@ +apiVersion: v1 +kind: Service +metadata: + name: mysql-headless + namespace: nextcloud +spec: + ports: + - port: 3306 + name: mysql + clusterIP: None # Headless service for StatefulSet + selector: + app: mysql +--- +apiVersion: v1 +kind: Service +metadata: + name: mysql + namespace: nextcloud +spec: + ports: + - port: 3306 + name: mysql + targetPort: 3306 + selector: + app: mysql + type: ClusterIP # Regular service for client connections +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: mysql + namespace: nextcloud +spec: + selector: + matchLabels: + app: mysql + serviceName: mysql-headless # Must reference the headless service + replicas: 1 + template: + metadata: + labels: + app: mysql + spec: + containers: + - name: mysql + image: mysql:8.0 + env: + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: nextcloud-db + key: db-password + - name: MYSQL_DATABASE + valueFrom: + secretKeyRef: + name: nextcloud-db + key: db-name + - name: MYSQL_USER + valueFrom: + secretKeyRef: + name: nextcloud-db + key: db-username + - name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + name: nextcloud-db + key: db-password + ports: + - containerPort: 3306 + name: mysql + volumeMounts: + - name: mysql-persistent-storage + mountPath: /var/lib/mysql + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "500m" + livenessProbe: + exec: + command: + - /bin/sh + - -c + - mysqladmin ping -u root -p$MYSQL_ROOT_PASSWORD + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - /bin/sh + - -c + - mysqladmin ping -u root -p$MYSQL_ROOT_PASSWORD + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 2 + volumeClaimTemplates: + - metadata: + name: mysql-persistent-storage + spec: + accessModes: [ "ReadWriteOnce" ] + storageClassName: managed-csi + resources: + requests: + storage: 20Gi diff --git a/kubernetes/base/namespace.yaml b/kubernetes/base/namespace.yaml new file mode 100644 index 0000000..479a5a7 --- /dev/null +++ b/kubernetes/base/namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: nextcloud + labels: + name: nextcloud diff --git a/kubernetes/base/nextcloud-deployment.yaml b/kubernetes/base/nextcloud-deployment.yaml new file mode 100644 index 0000000..743498b --- /dev/null +++ b/kubernetes/base/nextcloud-deployment.yaml @@ -0,0 +1,132 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nextcloud + namespace: nextcloud +spec: + replicas: 1 # Start with 1 replica to avoid concurrent initialization conflicts + selector: + matchLabels: + app: nextcloud + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + template: + metadata: + labels: + app: nextcloud + spec: + containers: + - name: nextcloud + image: nextcloud:28-apache + ports: + - containerPort: 80 + name: http + env: + - name: MYSQL_HOST + valueFrom: + secretKeyRef: + name: nextcloud-db + key: db-host + - name: MYSQL_DATABASE + valueFrom: + secretKeyRef: + name: nextcloud-db + key: db-name + - name: MYSQL_USER + valueFrom: + secretKeyRef: + name: nextcloud-db + key: db-username + - name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + name: nextcloud-db + key: db-password + - name: NEXTCLOUD_ADMIN_USER + valueFrom: + secretKeyRef: + name: nextcloud-admin + key: admin-username + - name: NEXTCLOUD_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: nextcloud-admin + key: admin-password + - name: REDIS_HOST + valueFrom: + configMapKeyRef: + name: nextcloud-config + key: REDIS_HOST + - name: REDIS_HOST_PORT + valueFrom: + configMapKeyRef: + name: nextcloud-config + key: REDIS_HOST_PORT + - name: NEXTCLOUD_TRUSTED_DOMAINS + valueFrom: + configMapKeyRef: + name: nextcloud-config + key: NEXTCLOUD_TRUSTED_DOMAINS + - name: PHP_MEMORY_LIMIT + valueFrom: + configMapKeyRef: + name: nextcloud-config + key: PHP_MEMORY_LIMIT + - name: PHP_UPLOAD_LIMIT + valueFrom: + configMapKeyRef: + name: nextcloud-config + key: PHP_UPLOAD_LIMIT + volumeMounts: + - name: nextcloud-data + mountPath: /var/www/html/data # Only mount user data directory, not entire app + - name: nextcloud-config + mountPath: /var/www/html/config # Mount config directory for multi-pod consistency + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "2Gi" + cpu: "1000m" + startupProbe: + httpGet: + path: /status.php + port: 80 + httpHeaders: + - name: Host + value: localhost + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 60 # 60 failures × 10 sec = 600 sec (10 minutes) - sufficient with optimized storage + livenessProbe: + httpGet: + path: /status.php + port: 80 + httpHeaders: + - name: Host + value: localhost + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /status.php + port: 80 + httpHeaders: + - name: Host + value: localhost + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + volumes: + - name: nextcloud-data + persistentVolumeClaim: + claimName: nextcloud-data + - name: nextcloud-config + persistentVolumeClaim: + claimName: nextcloud-config diff --git a/kubernetes/base/nextcloud-service.yaml b/kubernetes/base/nextcloud-service.yaml new file mode 100644 index 0000000..1cfb5c0 --- /dev/null +++ b/kubernetes/base/nextcloud-service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: nextcloud + namespace: nextcloud +spec: + selector: + app: nextcloud + ports: + - port: 80 + targetPort: 80 + name: http + type: LoadBalancer diff --git a/kubernetes/base/pvc.yaml b/kubernetes/base/pvc.yaml new file mode 100644 index 0000000..375b288 --- /dev/null +++ b/kubernetes/base/pvc.yaml @@ -0,0 +1,79 @@ +# PersistentVolume for Nextcloud user data +# Only stores user files, not application code (for fast initialization) +apiVersion: v1 +kind: PersistentVolume +metadata: + name: nextcloud-data-pv +spec: + capacity: + storage: 100Gi + accessModes: + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + storageClassName: azurefile + azureFile: + secretName: azure-storage + shareName: nextcloud-data + readOnly: false + mountOptions: + - dir_mode=0770 + - file_mode=0660 + - uid=33 # www-data user + - gid=33 # www-data group + - mfsymlinks + - cache=strict + - actimeo=30 +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: nextcloud-data + namespace: nextcloud +spec: + accessModes: + - ReadWriteMany + storageClassName: azurefile + resources: + requests: + storage: 100Gi + volumeName: nextcloud-data-pv +--- +# PersistentVolume for Nextcloud config +# Stores config.php and other configuration files for multi-pod consistency +apiVersion: v1 +kind: PersistentVolume +metadata: + name: nextcloud-config-pv +spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + storageClassName: azurefile + azureFile: + secretName: azure-storage + shareName: nextcloud-config + readOnly: false + mountOptions: + - dir_mode=0770 + - file_mode=0660 + - uid=33 # www-data user + - gid=33 # www-data group + - mfsymlinks + - cache=strict + - actimeo=30 +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: nextcloud-config + namespace: nextcloud +spec: + accessModes: + - ReadWriteMany + storageClassName: azurefile + resources: + requests: + storage: 1Gi + volumeName: nextcloud-config-pv diff --git a/kubernetes/base/redis.yaml b/kubernetes/base/redis.yaml new file mode 100644 index 0000000..631b4c4 --- /dev/null +++ b/kubernetes/base/redis.yaml @@ -0,0 +1,52 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: nextcloud +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: redis:7-alpine + ports: + - containerPort: 6379 + name: redis + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + tcpSocket: + port: 6379 + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + tcpSocket: + port: 6379 + initialDelaySeconds: 5 + periodSeconds: 5 +--- +apiVersion: v1 +kind: Service +metadata: + name: redis + namespace: nextcloud +spec: + selector: + app: redis + ports: + - port: 6379 + targetPort: 6379 + name: redis + type: ClusterIP diff --git a/kubernetes/base/secrets.yaml b/kubernetes/base/secrets.yaml new file mode 100644 index 0000000..3397e8b --- /dev/null +++ b/kubernetes/base/secrets.yaml @@ -0,0 +1,45 @@ +# WARNING: This file contains PLACEHOLDER values that MUST be replaced before deployment! +# DO NOT deploy this file directly. Use the deployment script or manually create secrets. +# +# Recommended approach: +# 1. Use the automated deployment script (scripts/deploy.sh) +# 2. Or use kubectl create secret with values from Terraform outputs +# +# Example: +# kubectl create secret generic nextcloud-db --namespace=nextcloud \ +# --from-literal=db-host="" \ +# --from-literal=db-name="nextcloud" \ +# --from-literal=db-username="nextcloudadmin" \ +# --from-literal=db-password="" + +apiVersion: v1 +kind: Secret +metadata: + name: nextcloud-db + namespace: nextcloud +type: Opaque +stringData: + db-host: "mysql" # MySQL StatefulSet service name + db-name: "nextcloud" + db-username: "nextcloud" + db-password: "REPLACE_WITH_MYSQL_PASSWORD" +--- +apiVersion: v1 +kind: Secret +metadata: + name: nextcloud-admin + namespace: nextcloud +type: Opaque +stringData: + admin-username: "admin" + admin-password: "REPLACE_WITH_SECURE_PASSWORD" # Generate with: openssl rand -base64 32 +--- +apiVersion: v1 +kind: Secret +metadata: + name: azure-storage + namespace: nextcloud +type: Opaque +stringData: + azurestorageaccountname: "REPLACE_WITH_STORAGE_ACCOUNT_NAME" + azurestorageaccountkey: "REPLACE_WITH_STORAGE_ACCOUNT_KEY" diff --git a/kubernetes/overlays/dev/deployment-patch.yaml b/kubernetes/overlays/dev/deployment-patch.yaml new file mode 100644 index 0000000..d44ab86 --- /dev/null +++ b/kubernetes/overlays/dev/deployment-patch.yaml @@ -0,0 +1,17 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nextcloud + namespace: nextcloud +spec: + template: + spec: + containers: + - name: nextcloud + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "500m" diff --git a/kubernetes/overlays/dev/kustomization.yaml b/kubernetes/overlays/dev/kustomization.yaml new file mode 100644 index 0000000..3e6fc38 --- /dev/null +++ b/kubernetes/overlays/dev/kustomization.yaml @@ -0,0 +1,17 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: nextcloud + +resources: + - ../../base + +commonLabels: + environment: dev + +patchesStrategicMerge: + - deployment-patch.yaml + +replicas: + - name: nextcloud + count: 1 diff --git a/kubernetes/overlays/prod/deployment-patch.yaml b/kubernetes/overlays/prod/deployment-patch.yaml new file mode 100644 index 0000000..29e6305 --- /dev/null +++ b/kubernetes/overlays/prod/deployment-patch.yaml @@ -0,0 +1,17 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nextcloud + namespace: nextcloud +spec: + template: + spec: + containers: + - name: nextcloud + resources: + requests: + memory: "1Gi" + cpu: "500m" + limits: + memory: "4Gi" + cpu: "2000m" diff --git a/kubernetes/overlays/prod/kustomization.yaml b/kubernetes/overlays/prod/kustomization.yaml new file mode 100644 index 0000000..071e14f --- /dev/null +++ b/kubernetes/overlays/prod/kustomization.yaml @@ -0,0 +1,17 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: nextcloud + +resources: + - ../../base + +commonLabels: + environment: prod + +patchesStrategicMerge: + - deployment-patch.yaml + +replicas: + - name: nextcloud + count: 3 diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh new file mode 100755 index 0000000..9649be1 --- /dev/null +++ b/scripts/cleanup.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Cleanup script for Nextcloud AKS deployment +set -e + +echo "================================" +echo "Nextcloud AKS Cleanup Script" +echo "================================" + +read -p "Are you sure you want to delete all resources? (yes/no): " confirm +if [ "$confirm" != "yes" ]; then + echo "Cleanup cancelled." + exit 0 +fi + +# Delete Kubernetes resources +echo "" +echo "Deleting Kubernetes resources..." +kubectl delete namespace nextcloud --ignore-not-found=true + +# Wait for namespace deletion +echo "Waiting for namespace deletion..." +kubectl wait --for=delete namespace/nextcloud --timeout=300s || true + +# Destroy Terraform infrastructure +echo "" +echo "Destroying Azure infrastructure..." +cd terraform + +if [ -f "terraform.tfstate" ]; then + terraform destroy -auto-approve +else + echo "No Terraform state found. Skipping infrastructure cleanup." +fi + +echo "" +echo "================================" +echo "Cleanup completed!" +echo "================================" diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..25503a4 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,140 @@ +#!/bin/bash +# Deploy script for Nextcloud on AKS +set -e + +echo "================================" +echo "Nextcloud AKS Deployment Script" +echo "================================" + +# Check prerequisites +command -v az >/dev/null 2>&1 || { echo "Azure CLI is required but not installed. Aborting." >&2; exit 1; } +command -v terraform >/dev/null 2>&1 || { echo "Terraform is required but not installed. Aborting." >&2; exit 1; } +command -v kubectl >/dev/null 2>&1 || { echo "kubectl is required but not installed. Aborting." >&2; exit 1; } + +# Step 1: Deploy infrastructure with Terraform +echo "" +echo "Step 1: Deploying Azure infrastructure..." +cd terraform + +if [ ! -f "terraform.tfvars" ]; then + echo "Error: terraform.tfvars not found. Please create it from terraform.tfvars.example" + exit 1 +fi + +terraform init +terraform plan -out=tfplan +terraform apply tfplan + +# Extract outputs +RESOURCE_GROUP=$(terraform output -raw resource_group_name) +AKS_CLUSTER=$(terraform output -raw aks_cluster_name) +MYSQL_PASSWORD=$(terraform output -raw mysql_password) +STORAGE_ACCOUNT_NAME=$(terraform output -raw storage_account_name) +STORAGE_ACCOUNT_KEY=$(terraform output -raw storage_account_key) + +echo "Infrastructure deployed successfully!" + +# Step 2: Configure kubectl +echo "" +echo "Step 2: Configuring kubectl..." +az aks get-credentials --resource-group "$RESOURCE_GROUP" --name "$AKS_CLUSTER" --overwrite-existing + +# Step 3: Create namespace +echo "" +echo "Step 3: Creating namespace..." +kubectl apply -f ../kubernetes/base/namespace.yaml + +# Step 4: Create secrets +echo "" +echo "Step 4: Creating secrets..." +kubectl create secret generic nextcloud-db \ + --from-literal=db-host="mysql" \ + --from-literal=db-name="nextcloud" \ + --from-literal=db-username="nextcloud" \ + --from-literal=db-password="$MYSQL_PASSWORD" \ + --namespace=nextcloud --dry-run=client -o yaml | kubectl apply -f - + +kubectl create secret generic azure-storage \ + --from-literal=azurestorageaccountname="$STORAGE_ACCOUNT_NAME" \ + --from-literal=azurestorageaccountkey="$STORAGE_ACCOUNT_KEY" \ + --namespace=nextcloud --dry-run=client -o yaml | kubectl apply -f - + +# Generate a random admin password (32 chars for stronger security) +ADMIN_PASSWORD=$(openssl rand -base64 32 | cut -c1-32) + +kubectl create secret generic nextcloud-admin \ + --from-literal=admin-username="admin" \ + --from-literal=admin-password="$ADMIN_PASSWORD" \ + --namespace=nextcloud --dry-run=client -o yaml | kubectl apply -f - + +# Save credentials securely +CREDS_DIR="$HOME/.nextcloud" +mkdir -p "$CREDS_DIR" +chmod 700 "$CREDS_DIR" +echo "Admin password: $ADMIN_PASSWORD" > "$CREDS_DIR/credentials.txt" +chmod 600 "$CREDS_DIR/credentials.txt" + +echo "Admin credentials saved securely to: $CREDS_DIR/credentials.txt" + +echo "Secrets created successfully!" + +# Step 5: Deploy Kubernetes resources +echo "" +echo "Step 5: Deploying Nextcloud to Kubernetes..." +cd ../kubernetes/base +kubectl apply -f configmap.yaml +kubectl apply -f pvc.yaml +kubectl apply -f mysql-statefulset.yaml +kubectl apply -f redis.yaml + +# Wait for MySQL to be ready before deploying Nextcloud +echo "" +echo "Waiting for MySQL StatefulSet to be ready..." +# First check if the pod exists +echo "Checking MySQL pod status..." +kubectl get pods -n nextcloud -l app=mysql + +# Wait for the StatefulSet to be ready with better error handling +if ! kubectl wait --for=condition=ready pod -l app=mysql -n nextcloud --timeout=600s; then + echo "ERROR: MySQL pod did not become ready within 10 minutes." + echo "Checking MySQL pod status and logs..." + kubectl get pods -n nextcloud -l app=mysql + kubectl describe pod -n nextcloud -l app=mysql + echo "" + echo "MySQL pod logs:" + kubectl logs -n nextcloud -l app=mysql --tail=50 || true + echo "" + echo "Please investigate the MySQL pod issue before continuing." + exit 1 +fi + +echo "MySQL is ready!" + +kubectl apply -f nextcloud-deployment.yaml +kubectl apply -f nextcloud-service.yaml + +echo "" +echo "================================" +echo "Deployment completed!" +echo "================================" +echo "" +echo "Waiting for Nextcloud pods to be ready..." +kubectl wait --for=condition=ready pod -l app=nextcloud -n nextcloud --timeout=300s || true + +EXTERNAL_IP="" +while [ -z "$EXTERNAL_IP" ]; do + echo "Waiting for external IP..." + EXTERNAL_IP=$(kubectl get svc nextcloud -n nextcloud -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null) + [ -z "$EXTERNAL_IP" ] && sleep 10 +done + +echo "" +echo "Nextcloud is accessible at: http://$EXTERNAL_IP" +echo "Default admin credentials:" +echo " Username: admin" +echo " Password: See $HOME/.nextcloud/credentials.txt" +echo "" +echo "IMPORTANT: Save your admin password from $HOME/.nextcloud/credentials.txt" +echo "" +echo "To check status: kubectl get all -n nextcloud" +echo "To view logs: kubectl logs -f deployment/nextcloud -n nextcloud" diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..54e3525 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,116 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.0" + } + random = { + source = "hashicorp/random" + version = "~> 3.0" + } + } +} + +provider "azurerm" { + features {} +} + +# Resource Group +resource "azurerm_resource_group" "nextcloud" { + name = var.resource_group_name + location = var.location + tags = var.tags +} + +# Virtual Network +resource "azurerm_virtual_network" "nextcloud" { + name = "${var.prefix}-vnet" + location = azurerm_resource_group.nextcloud.location + resource_group_name = azurerm_resource_group.nextcloud.name + address_space = ["10.0.0.0/16"] + tags = var.tags +} + +# Subnet for AKS +resource "azurerm_subnet" "aks" { + name = "${var.prefix}-aks-subnet" + resource_group_name = azurerm_resource_group.nextcloud.name + virtual_network_name = azurerm_virtual_network.nextcloud.name + address_prefixes = ["10.0.1.0/24"] +} + +# Note: MySQL will be deployed as a StatefulSet within the AKS cluster +# No dedicated subnet needed for containerized MySQL + +# AKS Cluster +resource "azurerm_kubernetes_cluster" "nextcloud" { + name = "${var.prefix}-aks" + location = azurerm_resource_group.nextcloud.location + resource_group_name = azurerm_resource_group.nextcloud.name + dns_prefix = "${var.prefix}-aks" + + default_node_pool { + name = "default" + node_count = var.node_count + vm_size = var.vm_size + vnet_subnet_id = azurerm_subnet.aks.id + enable_auto_scaling = true + min_count = var.min_node_count + max_count = var.max_node_count + } + + identity { + type = "SystemAssigned" + } + + network_profile { + network_plugin = "azure" + load_balancer_sku = "standard" + service_cidr = "10.1.0.0/16" + dns_service_ip = "10.1.0.10" + } + + tags = var.tags +} + +# Storage Account for Nextcloud data +# Note: Storage account names must be 3-24 characters, lowercase letters and numbers only +# Adding random suffix to ensure global uniqueness +resource "random_id" "storage" { + byte_length = 4 +} + +resource "azurerm_storage_account" "nextcloud" { + name = lower(substr("${replace(var.prefix, "-", "")}${random_id.storage.hex}", 0, 24)) + resource_group_name = azurerm_resource_group.nextcloud.name + location = azurerm_resource_group.nextcloud.location + account_tier = "Standard" + account_replication_type = "LRS" + + tags = var.tags +} + +resource "azurerm_storage_share" "nextcloud_data" { + name = "nextcloud-data" + storage_account_name = azurerm_storage_account.nextcloud.name + quota = 100 +} + +# File share for Nextcloud config directory +resource "azurerm_storage_share" "nextcloud_config" { + name = "nextcloud-config" + storage_account_name = azurerm_storage_account.nextcloud.name + quota = 1 # Config files are small, 1GB is sufficient +} + +# MySQL password for containerized MySQL deployment +# This will be used in Kubernetes secrets +resource "random_password" "mysql" { + length = 24 + special = true +} + +# Note: MySQL is deployed as a StatefulSet within Kubernetes +# No Azure MySQL Flexible Server resources needed diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000..14b66ef --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1,47 @@ +output "aks_cluster_name" { + description = "Name of the AKS cluster" + value = azurerm_kubernetes_cluster.nextcloud.name +} + +output "aks_cluster_id" { + description = "ID of the AKS cluster" + value = azurerm_kubernetes_cluster.nextcloud.id +} + +output "resource_group_name" { + description = "Name of the resource group" + value = azurerm_resource_group.nextcloud.name +} + +output "mysql_password" { + description = "MySQL root password for containerized deployment" + value = random_password.mysql.result + sensitive = true +} + +output "storage_account_name" { + description = "Name of the storage account" + value = azurerm_storage_account.nextcloud.name +} + +output "storage_account_key" { + description = "Primary access key for the storage account" + value = azurerm_storage_account.nextcloud.primary_access_key + sensitive = true +} + +output "storage_share_name" { + description = "Name of the file share for Nextcloud data" + value = azurerm_storage_share.nextcloud_data.name +} + +output "storage_config_share_name" { + description = "Name of the file share for Nextcloud config" + value = azurerm_storage_share.nextcloud_config.name +} + +output "kube_config" { + description = "Kubernetes configuration" + value = azurerm_kubernetes_cluster.nextcloud.kube_config_raw + sensitive = true +} diff --git a/terraform/terraform.tfvars.example b/terraform/terraform.tfvars.example new file mode 100644 index 0000000..1e74d67 --- /dev/null +++ b/terraform/terraform.tfvars.example @@ -0,0 +1,23 @@ +# Example terraform.tfvars file +# Copy this file to terraform.tfvars and customize the values + +resource_group_name = "nextcloud-rg" + +# Azure region - MySQL is deployed as a container, works in any region +location = "westeurope" + +prefix = "nextcloud" + +# AKS Configuration +node_count = 2 +min_node_count = 1 +max_node_count = 5 +vm_size = "Standard_D2s_v3" + +# Tags +tags = { + Environment = "production" + ManagedBy = "terraform" + Project = "nextcloud" + CostCenter = "IT" +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..b448450 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,51 @@ +variable "resource_group_name" { + description = "Name of the resource group" + type = string + default = "nextcloud-rg" +} + +variable "location" { + description = "Azure region for resources" + type = string + default = "westeurope" +} + +variable "prefix" { + description = "Prefix for resource names" + type = string + default = "nextcloud" +} + +variable "node_count" { + description = "Initial number of nodes in the AKS cluster" + type = number + default = 2 +} + +variable "min_node_count" { + description = "Minimum number of nodes for autoscaling" + type = number + default = 1 +} + +variable "max_node_count" { + description = "Maximum number of nodes for autoscaling" + type = number + default = 5 +} + +variable "vm_size" { + description = "VM size for AKS nodes" + type = string + default = "Standard_D2s_v3" +} + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) + default = { + Environment = "production" + ManagedBy = "terraform" + Project = "nextcloud" + } +}