Kubernetes Longhorn Storage Node Placement: Enterprise Configuration and Resource Management Guide
Enterprise Kubernetes deployments require sophisticated storage node placement strategies that optimize performance, ensure data locality, and maintain operational efficiency across diverse infrastructure configurations. Longhorn storage systems, when properly configured with node placement controls, enable organizations to build highly available storage architectures that meet specific performance, compliance, and resource utilization requirements.
Understanding how to effectively restrict and manage Longhorn storage nodes through node selectors, taints and tolerations, and advanced configuration patterns is crucial for platform engineering teams responsible for large-scale production deployments. This comprehensive guide explores enterprise-grade approaches to storage node management that balance performance optimization with operational simplicity.
Executive Summary
Kubernetes Longhorn storage node placement requires strategic configuration of node selectors, resource constraints, and deployment patterns that align with enterprise infrastructure requirements. This guide provides comprehensive strategies for implementing advanced node placement controls, optimizing storage performance through intelligent resource allocation, and building production-ready storage architectures that scale efficiently across complex Kubernetes environments.
Longhorn Storage Architecture and Node Management
Understanding Storage Node Roles
Longhorn storage architecture involves multiple node types with specific roles and requirements:
# Comprehensive node role configuration for Longhorn storage
apiVersion: v1
kind: ConfigMap
metadata:
name: longhorn-node-architecture
namespace: longhorn-system
data:
node-roles.yaml: |
storage_nodes:
purpose: "Primary storage nodes hosting volume replicas"
requirements:
cpu: "4 cores minimum"
memory: "8Gi minimum"
storage: "High-performance SSD or NVMe"
network: "10Gbps+ recommended"
labels:
node_role: "storage"
storage_tier: "high-performance"
longhorn_storage: "enabled"
compute_nodes:
purpose: "Application workload nodes with storage access"
requirements:
cpu: "2 cores minimum"
memory: "4Gi minimum"
network: "1Gbps minimum"
labels:
node_role: "compute"
longhorn_client: "enabled"
management_nodes:
purpose: "Longhorn manager and UI components"
requirements:
cpu: "2 cores minimum"
memory: "2Gi minimum"
high_availability: "required"
labels:
node_role: "management"
longhorn_manager: "enabled"
edge_nodes:
purpose: "Edge storage nodes for local data access"
requirements:
local_storage: "required"
network_latency: "low"
labels:
node_role: "edge"
storage_tier: "edge"
longhorn_edge: "enabled"
---
apiVersion: v1
kind: ConfigMap
metadata:
name: longhorn-deployment-patterns
namespace: longhorn-system
data:
patterns.yaml: |
dedicated_storage_cluster:
description: "Separate storage and compute nodes"
benefits:
- "Optimized resource allocation"
- "Better performance isolation"
- "Simplified capacity planning"
use_cases:
- "Large-scale production environments"
- "High-performance workloads"
- "Strict resource governance"
converged_infrastructure:
description: "Combined storage and compute on same nodes"
benefits:
- "Reduced infrastructure costs"
- "Simplified deployment"
- "Better resource utilization"
use_cases:
- "Small to medium deployments"
- "Development environments"
- "Edge computing scenarios"
hybrid_deployment:
description: "Mixed dedicated and converged nodes"
benefits:
- "Flexible resource allocation"
- "Gradual scaling options"
- "Workload-specific optimization"
use_cases:
- "Growing organizations"
- "Multi-tenant environments"
- "Diverse workload requirements"
Advanced Node Selection Strategies
Implement sophisticated node selection mechanisms for optimal storage placement:
# Advanced Longhorn node selection configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: longhorn-node-selection-config
namespace: longhorn-system
data:
node-selectors.yaml: |
# Primary storage nodes configuration
storage_nodes:
nodeSelector:
storage.company.com/tier: "high-performance"
node.company.com/storage-enabled: "true"
kubernetes.io/arch: "amd64"
tolerations:
- key: "storage.company.com/dedicated"
operator: "Equal"
value: "longhorn"
effect: "NoSchedule"
- key: "node.company.com/storage-only"
operator: "Exists"
effect: "NoExecute"
# Management component nodes
management_nodes:
nodeSelector:
node.company.com/role: "management"
node.company.com/zone: "control-plane"
tolerations:
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
# Edge storage nodes
edge_nodes:
nodeSelector:
node.company.com/location: "edge"
storage.company.com/local-storage: "available"
tolerations:
- key: "node.company.com/edge"
operator: "Equal"
value: "true"
effect: "NoSchedule"
---
# Comprehensive Longhorn Helm values for node placement
apiVersion: v1
kind: ConfigMap
metadata:
name: longhorn-helm-values
namespace: longhorn-system
data:
values.yaml: |
# Image configuration
image:
longhorn:
engine:
repository: longhornio/longhorn-engine
tag: v1.5.3
manager:
repository: longhornio/longhorn-manager
tag: v1.5.3
ui:
repository: longhornio/longhorn-ui
tag: v1.5.3
instanceManager:
repository: longhornio/longhorn-instance-manager
tag: v1.5.3
# Service configuration
service:
ui:
type: ClusterIP
nodePort: null
manager:
type: ClusterIP
# Persistence configuration for management components
persistence:
defaultClass: true
defaultClassReplicaCount: 3
reclaimPolicy: Retain
recurringJobs:
enable: true
# CSI driver configuration
csi:
kubeletRootDir: /var/lib/kubelet
attacherReplicaCount: 3
provisionerReplicaCount: 3
resizerReplicaCount: 3
snapshotterReplicaCount: 3
# Longhorn Manager node placement
longhornManager:
priorityClass: "system-node-critical"
tolerations:
- key: "storage.company.com/dedicated"
operator: "Equal"
value: "longhorn"
effect: "NoSchedule"
- key: "node.company.com/storage-only"
operator: "Exists"
effect: "NoExecute"
nodeSelector:
storage.company.com/longhorn-manager: "enabled"
node.company.com/zone: "storage"
resources:
limits:
cpu: "1000m"
memory: "2Gi"
requests:
cpu: "500m"
memory: "1Gi"
# Longhorn Driver node placement
longhornDriver:
priorityClass: "system-node-critical"
tolerations:
- key: "storage.company.com/dedicated"
operator: "Equal"
value: "longhorn"
effect: "NoSchedule"
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
- key: "node.company.com/compute-only"
operator: "Equal"
value: "true"
effect: "NoSchedule"
nodeSelector:
storage.company.com/csi-driver: "enabled"
# Longhorn UI placement
longhornUI:
replicas: 2
priorityClass: "system-cluster-critical"
nodeSelector:
node.company.com/role: "management"
node.company.com/ui-enabled: "true"
tolerations:
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
resources:
limits:
cpu: "500m"
memory: "1Gi"
requests:
cpu: "100m"
memory: "256Mi"
# Default storage class configuration
defaultSettings:
backupstorePollInterval: 300
backupTarget: "s3://longhorn-backups@us-east-1/"
backupTargetCredentialSecret: "longhorn-backup-credentials"
createDefaultDiskLabeledNodes: false
defaultDataPath: "/var/lib/longhorn/"
defaultDataLocality: "best-effort"
replicaReplenishmentWaitInterval: 600
concurrentReplicaRebuildPerNodeLimit: 3
systemManagedPodsImagePullPolicy: "IfNotPresent"
autoSalvage: true
autoDeletePodWhenVolumeDetachedUnexpectedly: true
disableSchedulingOnCordonedNode: true
replicaZoneSoftAntiAffinity: true
volumeAttachmentRecoveryPolicy: "wait"
nodeDownPodDeletionPolicy: "delete-both-statefulset-and-deployment-pod"
allowVolumeCreationWithDegradedAvailability: false
mkfsExt4Parameters: "-O ^64bit,^metadata_csum"
---
# Node preparation automation
apiVersion: batch/v1
kind: Job
metadata:
name: longhorn-node-preparation
namespace: longhorn-system
spec:
template:
spec:
restartPolicy: OnFailure
hostNetwork: true
hostPID: true
serviceAccount: longhorn-node-preparation
containers:
- name: node-prep
image: alpine:latest
securityContext:
privileged: true
command: ["/bin/sh"]
args:
- -c
- |
set -euo pipefail
echo "🔧 Preparing nodes for Longhorn storage"
# Install required packages
apk add --no-cache curl jq
# Get node information
NODE_NAME=${NODE_NAME:-$(hostname)}
echo "Preparing node: $NODE_NAME"
# Check for required kernel modules
echo "📋 Checking kernel modules..."
for module in iscsi_tcp target_core_mod; do
if lsmod | grep -q "$module"; then
echo "✅ Module $module is loaded"
else
echo "⚠️ Module $module is not loaded, attempting to load..."
modprobe "$module" || echo "❌ Failed to load $module"
fi
done
# Check disk configuration
echo "💾 Analyzing disk configuration..."
lsblk -f
df -h
# Validate network connectivity
echo "🌐 Testing network connectivity..."
ping -c 3 8.8.8.8 || echo "⚠️ Network connectivity issues detected"
echo "✅ Node preparation completed for $NODE_NAME"
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
volumeMounts:
- name: host-root
mountPath: /host
mountPropagation: HostToContainer
- name: host-sys
mountPath: /sys
- name: host-dev
mountPath: /dev
volumes:
- name: host-root
hostPath:
path: /
- name: host-sys
hostPath:
path: /sys
- name: host-dev
hostPath:
path: /dev
nodeSelector:
storage.company.com/longhorn-candidate: "true"
Node Labeling and Preparation Automation
Comprehensive Node Management Scripts
Implement automated node preparation and labeling systems:
#!/bin/bash
# Script: longhorn-node-manager.sh
# Purpose: Comprehensive node management for Longhorn storage
set -euo pipefail
# Configuration
LABEL_PREFIX="storage.company.com"
NODE_CONFIG_FILE="/etc/longhorn/node-config.yaml"
LOG_FILE="/var/log/longhorn-node-management.log"
function log_message() {
local level="$1"
local message="$2"
local timestamp=$(date -u +"%Y-%m-%d %H:%M:%S UTC")
echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}
function detect_node_capabilities() {
local node_name="$1"
log_message "INFO" "Detecting capabilities for node: $node_name"
# Get node information
local node_info=$(kubectl get node "$node_name" -o json)
local cpu_capacity=$(echo "$node_info" | jq -r '.status.capacity.cpu')
local memory_capacity=$(echo "$node_info" | jq -r '.status.capacity.memory' | numfmt --from=iec)
local storage_capacity=$(echo "$node_info" | jq -r '.status.capacity."ephemeral-storage"' | numfmt --from=iec)
# Convert memory to GB
local memory_gb=$((memory_capacity / 1024 / 1024 / 1024))
# Detect node role based on resources
local node_role="unknown"
local storage_tier="standard"
if [[ $memory_gb -ge 32 && "$cpu_capacity" -ge 8 ]]; then
node_role="storage-high-performance"
storage_tier="high-performance"
elif [[ $memory_gb -ge 16 && "$cpu_capacity" -ge 4 ]]; then
node_role="storage-standard"
storage_tier="standard"
elif [[ $memory_gb -ge 8 && "$cpu_capacity" -ge 2 ]]; then
node_role="compute"
storage_tier="none"
else
node_role="edge"
storage_tier="edge"
fi
# Detect storage devices
local storage_devices=$(kubectl debug node/"$node_name" -it --image=alpine -- sh -c 'lsblk -d -o NAME,SIZE,TYPE | grep disk' 2>/dev/null | wc -l || echo "0")
# Check for SSD/NVMe storage
local has_ssd="false"
if kubectl debug node/"$node_name" -it --image=alpine -- sh -c 'lsblk -d -o NAME,ROTA | grep -E "nvme|0$"' 2>/dev/null | grep -q .; then
has_ssd="true"
fi
# Output capabilities
cat <<EOF
{
"node_name": "$node_name",
"capabilities": {
"cpu_cores": $cpu_capacity,
"memory_gb": $memory_gb,
"storage_gb": $((storage_capacity / 1024 / 1024 / 1024)),
"storage_devices": $storage_devices,
"has_ssd": $has_ssd,
"recommended_role": "$node_role",
"storage_tier": "$storage_tier"
}
}
EOF
}
function prepare_storage_node() {
local node_name="$1"
local node_role="$2"
log_message "INFO" "Preparing storage node: $node_name (role: $node_role)"
# Check if node is ready
local node_ready=$(kubectl get node "$node_name" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}')
if [[ "$node_ready" != "True" ]]; then
log_message "ERROR" "Node $node_name is not ready"
return 1
fi
# Install required packages via daemonset
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: longhorn-node-preparation-$node_name
namespace: longhorn-system
spec:
selector:
matchLabels:
app: longhorn-node-preparation
template:
metadata:
labels:
app: longhorn-node-preparation
spec:
hostNetwork: true
hostPID: true
containers:
- name: node-prep
image: alpine:latest
securityContext:
privileged: true
command: ["/bin/sh"]
args:
- -c
- |
set -euo pipefail
echo "Preparing Longhorn storage node"
# Load kernel modules
modprobe iscsi_tcp || echo "Failed to load iscsi_tcp"
modprobe target_core_mod || echo "Failed to load target_core_mod"
# Create longhorn directory
mkdir -p /var/lib/longhorn
# Set appropriate permissions
chmod 755 /var/lib/longhorn
echo "Node preparation completed"
sleep infinity
volumeMounts:
- name: host-root
mountPath: /host
mountPropagation: HostToContainer
volumes:
- name: host-root
hostPath:
path: /
nodeSelector:
kubernetes.io/hostname: $node_name
tolerations:
- operator: Exists
EOF
# Wait for preparation to complete
sleep 30
# Clean up preparation daemonset
kubectl delete daemonset "longhorn-node-preparation-$node_name" -n longhorn-system || true
log_message "INFO" "Node preparation completed for $node_name"
}
function apply_node_labels() {
local node_name="$1"
local capabilities="$2"
log_message "INFO" "Applying labels to node: $node_name"
# Parse capabilities
local node_role=$(echo "$capabilities" | jq -r '.capabilities.recommended_role')
local storage_tier=$(echo "$capabilities" | jq -r '.capabilities.storage_tier')
local has_ssd=$(echo "$capabilities" | jq -r '.capabilities.has_ssd')
local cpu_cores=$(echo "$capabilities" | jq -r '.capabilities.cpu_cores')
local memory_gb=$(echo "$capabilities" | jq -r '.capabilities.memory_gb')
# Apply base labels
kubectl label node "$node_name" \
"${LABEL_PREFIX}/node-role=$node_role" \
"${LABEL_PREFIX}/storage-tier=$storage_tier" \
"${LABEL_PREFIX}/has-ssd=$has_ssd" \
"${LABEL_PREFIX}/cpu-cores=$cpu_cores" \
"${LABEL_PREFIX}/memory-gb=$memory_gb" \
--overwrite
# Apply role-specific labels
case "$node_role" in
"storage-high-performance")
kubectl label node "$node_name" \
"${LABEL_PREFIX}/longhorn-storage=enabled" \
"${LABEL_PREFIX}/longhorn-manager=enabled" \
"${LABEL_PREFIX}/high-performance=true" \
--overwrite
;;
"storage-standard")
kubectl label node "$node_name" \
"${LABEL_PREFIX}/longhorn-storage=enabled" \
"${LABEL_PREFIX}/longhorn-manager=enabled" \
--overwrite
;;
"compute")
kubectl label node "$node_name" \
"${LABEL_PREFIX}/longhorn-client=enabled" \
"${LABEL_PREFIX}/csi-driver=enabled" \
--overwrite
;;
"edge")
kubectl label node "$node_name" \
"${LABEL_PREFIX}/longhorn-edge=enabled" \
"${LABEL_PREFIX}/local-storage=preferred" \
--overwrite
;;
esac
log_message "INFO" "Labels applied successfully to $node_name"
}
function apply_node_taints() {
local node_name="$1"
local node_role="$2"
log_message "INFO" "Applying taints to node: $node_name (role: $node_role)"
case "$node_role" in
"storage-high-performance"|"storage-standard")
# Taint storage nodes to prevent non-storage workloads
kubectl taint node "$node_name" \
"${LABEL_PREFIX}/dedicated=storage:NoSchedule" \
--overwrite || true
kubectl taint node "$node_name" \
"${LABEL_PREFIX}/storage-only=true:NoExecute" \
--overwrite || true
;;
"edge")
# Taint edge nodes for edge-specific workloads
kubectl taint node "$node_name" \
"${LABEL_PREFIX}/edge=true:NoSchedule" \
--overwrite || true
;;
esac
log_message "INFO" "Taints applied successfully to $node_name"
}
function validate_node_configuration() {
local node_name="$1"
log_message "INFO" "Validating configuration for node: $node_name"
# Check labels
local labels=$(kubectl get node "$node_name" --show-labels | grep "$LABEL_PREFIX")
if [[ -n "$labels" ]]; then
log_message "INFO" "Labels validated for $node_name"
else
log_message "WARN" "No Longhorn labels found on $node_name"
return 1
fi
# Check node readiness
local ready=$(kubectl get node "$node_name" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}')
if [[ "$ready" == "True" ]]; then
log_message "INFO" "Node $node_name is ready"
else
log_message "ERROR" "Node $node_name is not ready"
return 1
fi
# Test scheduling capability
local test_pod_name="node-validation-$node_name-$(date +%s)"
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: $test_pod_name
namespace: default
spec:
restartPolicy: Never
nodeSelector:
kubernetes.io/hostname: $node_name
containers:
- name: test
image: alpine:latest
command: ["sleep", "30"]
tolerations:
- operator: Exists
EOF
# Wait for pod to schedule
local timeout=60
local count=0
while [[ $count -lt $timeout ]]; do
local pod_phase=$(kubectl get pod "$test_pod_name" -n default -o jsonpath='{.status.phase}' 2>/dev/null || echo "NotFound")
if [[ "$pod_phase" == "Running" || "$pod_phase" == "Succeeded" ]]; then
log_message "INFO" "Test pod scheduled successfully on $node_name"
kubectl delete pod "$test_pod_name" -n default || true
return 0
fi
sleep 2
count=$((count + 2))
done
log_message "WARN" "Test pod failed to schedule on $node_name within timeout"
kubectl delete pod "$test_pod_name" -n default || true
return 1
}
function configure_all_nodes() {
log_message "INFO" "Starting comprehensive node configuration"
# Get all nodes
local nodes=$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}')
for node in $nodes; do
log_message "INFO" "Processing node: $node"
# Detect capabilities
local capabilities=$(detect_node_capabilities "$node")
local node_role=$(echo "$capabilities" | jq -r '.capabilities.recommended_role')
log_message "INFO" "Detected role for $node: $node_role"
# Prepare node
prepare_storage_node "$node" "$node_role"
# Apply labels
apply_node_labels "$node" "$capabilities"
# Apply taints if needed
apply_node_taints "$node" "$node_role"
# Validate configuration
validate_node_configuration "$node"
log_message "INFO" "Configuration completed for node: $node"
done
log_message "INFO" "All nodes configured successfully"
}
function generate_node_report() {
log_message "INFO" "Generating node configuration report"
local report_file="/tmp/longhorn-node-report-$(date +%Y%m%d_%H%M%S).json"
cat > "$report_file" <<EOF
{
"report_timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"cluster_info": {
"total_nodes": $(kubectl get nodes --no-headers | wc -l),
"kubernetes_version": "$(kubectl version -o json | jq -r '.serverVersion.gitVersion')"
},
"nodes": [
EOF
local nodes=$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}')
local first_node=true
for node in $nodes; do
if [[ "$first_node" == "true" ]]; then
first_node=false
else
echo "," >> "$report_file"
fi
local capabilities=$(detect_node_capabilities "$node")
echo "$capabilities" >> "$report_file"
done
cat >> "$report_file" <<EOF
],
"summary": {
"storage_nodes": $(kubectl get nodes -l "${LABEL_PREFIX}/longhorn-storage=enabled" --no-headers 2>/dev/null | wc -l),
"compute_nodes": $(kubectl get nodes -l "${LABEL_PREFIX}/longhorn-client=enabled" --no-headers 2>/dev/null | wc -l),
"edge_nodes": $(kubectl get nodes -l "${LABEL_PREFIX}/longhorn-edge=enabled" --no-headers 2>/dev/null | wc -l)
}
}
EOF
log_message "INFO" "Node report generated: $report_file"
echo "📊 Node Configuration Report: $report_file"
cat "$report_file" | jq .
}
# Main execution
case "${1:-help}" in
"configure")
configure_all_nodes
;;
"detect")
if [[ -n "${2:-}" ]]; then
detect_node_capabilities "$2"
else
echo "Usage: $0 detect <node-name>"
fi
;;
"prepare")
if [[ -n "${2:-}" && -n "${3:-}" ]]; then
prepare_storage_node "$2" "$3"
else
echo "Usage: $0 prepare <node-name> <role>"
fi
;;
"label")
if [[ -n "${2:-}" ]]; then
local capabilities=$(detect_node_capabilities "$2")
apply_node_labels "$2" "$capabilities"
else
echo "Usage: $0 label <node-name>"
fi
;;
"validate")
if [[ -n "${2:-}" ]]; then
validate_node_configuration "$2"
else
echo "Usage: $0 validate <node-name>"
fi
;;
"report")
generate_node_report
;;
*)
echo "Longhorn Node Manager"
echo "===================="
echo ""
echo "Usage: $0 {configure|detect|prepare|label|validate|report}"
echo ""
echo "Commands:"
echo " configure - Configure all nodes automatically"
echo " detect <node> - Detect node capabilities"
echo " prepare <node> - Prepare node for Longhorn storage"
echo " label <node> - Apply appropriate labels"
echo " validate <node> - Validate node configuration"
echo " report - Generate comprehensive node report"
;;
esac
Advanced Storage Tier Management
Multi-Tier Storage Architecture
Implement comprehensive storage tier management for different performance requirements:
# Multi-tier storage configuration for Longhorn
apiVersion: v1
kind: ConfigMap
metadata:
name: longhorn-storage-tiers
namespace: longhorn-system
data:
storage-tiers.yaml: |
tier_definitions:
platinum:
description: "Highest performance NVMe storage"
node_requirements:
cpu_cores: 16
memory_gb: 64
storage_type: "nvme"
network_speed: "25Gbps"
volume_characteristics:
replicas: 3
data_locality: "strict-local"
performance_class: "ultra-high"
use_cases:
- "Database primary storage"
- "High-frequency trading systems"
- "Real-time analytics"
gold:
description: "High-performance SSD storage"
node_requirements:
cpu_cores: 8
memory_gb: 32
storage_type: "ssd"
network_speed: "10Gbps"
volume_characteristics:
replicas: 3
data_locality: "best-effort"
performance_class: "high"
use_cases:
- "Application databases"
- "Cache layers"
- "Log aggregation"
silver:
description: "Standard performance storage"
node_requirements:
cpu_cores: 4
memory_gb: 16
storage_type: "ssd_hdd_mixed"
network_speed: "1Gbps"
volume_characteristics:
replicas: 2
data_locality: "disabled"
performance_class: "standard"
use_cases:
- "General application storage"
- "Development environments"
- "Backup storage"
bronze:
description: "Cost-optimized storage"
node_requirements:
cpu_cores: 2
memory_gb: 8
storage_type: "hdd"
network_speed: "1Gbps"
volume_characteristics:
replicas: 2
data_locality: "disabled"
performance_class: "basic"
use_cases:
- "Archive storage"
- "Long-term backups"
- "Infrequently accessed data"
---
# Storage class definitions for different tiers
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: longhorn-platinum
annotations:
storageclass.kubernetes.io/is-default-class: "false"
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
parameters:
numberOfReplicas: "3"
staleReplicaTimeout: "2880"
fromBackup: ""
fsType: "ext4"
dataLocality: "strict-local"
replicaAutoBalance: "least-effort"
diskSelector: "storage-tier,platinum"
nodeSelector: "storage-tier,platinum"
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: longhorn-gold
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
parameters:
numberOfReplicas: "3"
staleReplicaTimeout: "2880"
fromBackup: ""
fsType: "ext4"
dataLocality: "best-effort"
replicaAutoBalance: "least-effort"
diskSelector: "storage-tier,gold"
nodeSelector: "storage-tier,gold"
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: longhorn-silver
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: Immediate
parameters:
numberOfReplicas: "2"
staleReplicaTimeout: "2880"
fromBackup: ""
fsType: "ext4"
dataLocality: "disabled"
replicaAutoBalance: "least-effort"
diskSelector: "storage-tier,silver"
nodeSelector: "storage-tier,silver"
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: longhorn-bronze
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: Immediate
parameters:
numberOfReplicas: "2"
staleReplicaTimeout: "2880"
fromBackup: ""
fsType: "ext4"
dataLocality: "disabled"
replicaAutoBalance: "disabled"
diskSelector: "storage-tier,bronze"
nodeSelector: "storage-tier,bronze"
---
# Automated storage tier assignment
apiVersion: batch/v1
kind: CronJob
metadata:
name: storage-tier-optimizer
namespace: longhorn-system
spec:
schedule: "0 2 * * *" # Run daily at 2 AM
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
serviceAccount: storage-tier-optimizer
containers:
- name: optimizer
image: bitnami/kubectl:latest
command: ["/bin/bash"]
args:
- -c
- |
set -euo pipefail
echo "🔄 Starting storage tier optimization"
# Get all nodes with storage capabilities
nodes=$(kubectl get nodes -l "storage.company.com/longhorn-storage=enabled" -o jsonpath='{.items[*].metadata.name}')
for node in $nodes; do
echo "Analyzing node: $node"
# Get node specifications
cpu_cores=$(kubectl get node "$node" -o jsonpath='{.status.capacity.cpu}')
memory_bytes=$(kubectl get node "$node" -o jsonpath='{.status.capacity.memory}' | numfmt --from=iec)
memory_gb=$((memory_bytes / 1024 / 1024 / 1024))
# Check for SSD/NVMe
has_nvme="false"
has_ssd="false"
# Use node feature discovery if available
storage_type=$(kubectl get node "$node" -o jsonpath='{.metadata.labels.feature\.node\.kubernetes\.io/storage-nonrotationaldisk}' 2>/dev/null || echo "unknown")
if [[ "$storage_type" == "true" ]]; then
has_ssd="true"
# Check for NVMe specifically
nvme_info=$(kubectl get node "$node" -o jsonpath='{.metadata.labels.feature\.node\.kubernetes\.io/pci-0200_144d\.present}' 2>/dev/null || echo "false")
if [[ "$nvme_info" == "true" ]]; then
has_nvme="true"
fi
fi
# Determine tier based on specifications
tier="bronze"
if [[ $memory_gb -ge 64 && $cpu_cores -ge 16 && "$has_nvme" == "true" ]]; then
tier="platinum"
elif [[ $memory_gb -ge 32 && $cpu_cores -ge 8 && "$has_ssd" == "true" ]]; then
tier="gold"
elif [[ $memory_gb -ge 16 && $cpu_cores -ge 4 ]]; then
tier="silver"
fi
echo "Assigning tier '$tier' to node '$node'"
# Apply tier labels
kubectl label node "$node" \
"storage.company.com/storage-tier=$tier" \
"storage.company.com/tier-last-updated=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--overwrite
# Apply tier-specific disk labels
kubectl annotate node "$node" \
"storage.company.com/tier-specifications={\"cpu\":$cpu_cores,\"memory_gb\":$memory_gb,\"has_ssd\":$has_ssd,\"has_nvme\":$has_nvme}" \
--overwrite
done
echo "✅ Storage tier optimization completed"
---
# Resource monitoring for storage tiers
apiVersion: v1
kind: ConfigMap
metadata:
name: storage-tier-monitoring
namespace: longhorn-system
data:
monitoring-rules.yaml: |
groups:
- name: longhorn.storage.tiers
rules:
- alert: StorageTierPerformanceDegradation
expr: |
(
rate(longhorn_volume_read_latency_seconds[5m]) > 0.1
or
rate(longhorn_volume_write_latency_seconds[5m]) > 0.1
)
and
on(volume) label_replace(
kube_persistentvolume_labels{label_storage_tier!=""},
"volume", "$1", "persistentvolume", "(.+)"
)
for: 10m
labels:
severity: warning
tier: "{{ $labels.label_storage_tier }}"
annotations:
summary: "Storage tier performance degradation"
description: "Volume {{ $labels.volume }} in {{ $labels.label_storage_tier }} tier is experiencing high latency"
- alert: StorageTierCapacityHigh
expr: |
(
(longhorn_disk_capacity_bytes - longhorn_disk_usage_bytes) / longhorn_disk_capacity_bytes * 100 < 20
)
and
on(node) label_replace(
kube_node_labels{label_storage_company_com_storage_tier!=""},
"node", "$1", "node", "(.+)"
)
for: 5m
labels:
severity: warning
tier: "{{ $labels.label_storage_company_com_storage_tier }}"
annotations:
summary: "Storage tier capacity running low"
description: "{{ $labels.label_storage_company_com_storage_tier }} tier node {{ $labels.node }} has less than 20% free space"
- alert: StorageTierNodeUnavailable
expr: |
up{job="longhorn-manager"} == 0
and
on(node) label_replace(
kube_node_labels{label_storage_company_com_storage_tier!=""},
"node", "$1", "node", "(.+)"
)
for: 2m
labels:
severity: critical
tier: "{{ $labels.label_storage_company_com_storage_tier }}"
annotations:
summary: "Storage tier node unavailable"
description: "{{ $labels.label_storage_company_com_storage_tier }} tier node {{ $labels.node }} is unavailable"
Production Deployment Strategies
Rolling Deployment and Maintenance
Implement comprehensive deployment and maintenance procedures:
#!/bin/bash
# Script: longhorn-production-deployment.sh
# Purpose: Production deployment and maintenance procedures
set -euo pipefail
# Configuration
NAMESPACE="longhorn-system"
BACKUP_DIR="/var/backups/longhorn-production"
MAINTENANCE_LOG="/var/log/longhorn-maintenance-$(date +%Y%m%d_%H%M%S).log"
function log_deployment_step() {
local level="$1"
local message="$2"
local timestamp=$(date -u +"%Y-%m-%d %H:%M:%S UTC")
echo "[$timestamp] [$level] $message" | tee -a "$MAINTENANCE_LOG"
}
function create_pre_deployment_backup() {
log_deployment_step "INFO" "Creating pre-deployment backup"
local backup_timestamp=$(date +%Y%m%d_%H%M%S)
local backup_path="$BACKUP_DIR/pre-deployment-$backup_timestamp"
mkdir -p "$backup_path"
# Backup Kubernetes resources
kubectl get all,pv,pvc,sc,nodes.longhorn.io,volumes.longhorn.io,settings.longhorn.io -o yaml > "$backup_path/kubernetes-resources.yaml"
# Backup Helm release information
if command -v helm >/dev/null 2>&1; then
helm get values longhorn -n "$NAMESPACE" > "$backup_path/helm-values.yaml"
helm get manifest longhorn -n "$NAMESPACE" > "$backup_path/helm-manifest.yaml"
fi
# Create volume snapshot manifests
kubectl get volumesnapshots --all-namespaces -o yaml > "$backup_path/volume-snapshots.yaml" 2>/dev/null || true
log_deployment_step "INFO" "Pre-deployment backup completed: $backup_path"
}
function validate_cluster_readiness() {
log_deployment_step "INFO" "Validating cluster readiness for deployment"
# Check node health
local unhealthy_nodes=$(kubectl get nodes -o json | jq '[.items[] | select(.status.conditions[] | select(.type=="Ready" and .status!="True"))] | length')
if [[ $unhealthy_nodes -gt 0 ]]; then
log_deployment_step "ERROR" "$unhealthy_nodes unhealthy nodes detected"
return 1
fi
# Check existing Longhorn health
local unhealthy_pods=$(kubectl get pods -n "$NAMESPACE" --no-headers | grep -v "Running\|Completed" | wc -l)
if [[ $unhealthy_pods -gt 0 ]]; then
log_deployment_step "WARN" "$unhealthy_pods unhealthy Longhorn pods detected"
kubectl get pods -n "$NAMESPACE" | grep -v "Running\|Completed"
fi
# Check volume health
local unhealthy_volumes=$(kubectl get volumes.longhorn.io -n "$NAMESPACE" -o json | jq '[.items[] | select(.status.state != "attached" and .status.state != "detached")] | length')
if [[ $unhealthy_volumes -gt 0 ]]; then
log_deployment_step "ERROR" "$unhealthy_volumes unhealthy volumes detected"
return 1
fi
# Check resource availability
local cpu_usage=$(kubectl top nodes --no-headers | awk '{print $3}' | sed 's/%//' | awk '{sum+=$1} END {print sum/NR}' || echo "0")
local memory_usage=$(kubectl top nodes --no-headers | awk '{print $5}' | sed 's/%//' | awk '{sum+=$1} END {print sum/NR}' || echo "0")
if (( $(echo "$cpu_usage > 80" | bc -l 2>/dev/null || echo "0") )); then
log_deployment_step "WARN" "High CPU usage detected: ${cpu_usage}%"
fi
if (( $(echo "$memory_usage > 80" | bc -l 2>/dev/null || echo "0") )); then
log_deployment_step "WARN" "High memory usage detected: ${memory_usage}%"
fi
log_deployment_step "INFO" "Cluster readiness validation completed"
}
function perform_rolling_deployment() {
local deployment_strategy="$1"
log_deployment_step "INFO" "Starting rolling deployment with strategy: $deployment_strategy"
case "$deployment_strategy" in
"conservative")
perform_conservative_deployment
;;
"balanced")
perform_balanced_deployment
;;
"aggressive")
perform_aggressive_deployment
;;
*)
log_deployment_step "ERROR" "Unknown deployment strategy: $deployment_strategy"
return 1
;;
esac
log_deployment_step "INFO" "Rolling deployment completed"
}
function perform_conservative_deployment() {
log_deployment_step "INFO" "Performing conservative deployment"
# Scale down non-critical workloads
log_deployment_step "INFO" "Scaling down non-critical workloads"
local non_critical_deployments=$(kubectl get deployments --all-namespaces -l "criticality!=critical" -o jsonpath='{range .items[*]}{.metadata.namespace}{","}{.metadata.name}{","}{.spec.replicas}{"\n"}{end}' 2>/dev/null || echo "")
while IFS=',' read -r namespace name replicas; do
if [[ -n "$namespace" && -n "$name" && -n "$replicas" ]]; then
log_deployment_step "INFO" "Scaling down $namespace/$name from $replicas to 0"
kubectl scale deployment "$name" --replicas=0 -n "$namespace" || true
# Store original replica count
kubectl annotate deployment "$name" -n "$namespace" \
"longhorn.company.com/original-replicas=$replicas" \
--overwrite || true
fi
done <<< "$non_critical_deployments"
# Wait for workload termination
sleep 60
# Perform Helm upgrade with conservative settings
log_deployment_step "INFO" "Performing Helm upgrade"
helm upgrade longhorn longhorn/longhorn \
--namespace "$NAMESPACE" \
--wait \
--timeout=30m \
--set longhornManager.priorityClass=system-node-critical \
--set longhornDriver.priorityClass=system-node-critical \
--set defaultSettings.guaranteedEngineManagerCPU=5 \
--set defaultSettings.guaranteedReplicaManagerCPU=5
# Wait for system stabilization
sleep 120
# Restore non-critical workloads
log_deployment_step "INFO" "Restoring non-critical workloads"
while IFS=',' read -r namespace name replicas; do
if [[ -n "$namespace" && -n "$name" ]]; then
local original_replicas=$(kubectl get deployment "$name" -n "$namespace" -o jsonpath='{.metadata.annotations.longhorn\.company\.com/original-replicas}' 2>/dev/null || echo "$replicas")
log_deployment_step "INFO" "Scaling up $namespace/$name to $original_replicas"
kubectl scale deployment "$name" --replicas="$original_replicas" -n "$namespace" || true
# Remove annotation
kubectl annotate deployment "$name" -n "$namespace" \
"longhorn.company.com/original-replicas-" || true
fi
done <<< "$non_critical_deployments"
}
function perform_balanced_deployment() {
log_deployment_step "INFO" "Performing balanced deployment"
# Perform Helm upgrade with standard settings
helm upgrade longhorn longhorn/longhorn \
--namespace "$NAMESPACE" \
--wait \
--timeout=20m \
--set longhornManager.priorityClass=system-node-critical \
--set longhornDriver.priorityClass=system-node-critical
# Monitor deployment progress
local timeout=1200
local count=0
while [[ $count -lt $timeout ]]; do
local ready_pods=$(kubectl get pods -n "$NAMESPACE" --no-headers | grep "Running" | wc -l)
local total_pods=$(kubectl get pods -n "$NAMESPACE" --no-headers | wc -l)
log_deployment_step "INFO" "Deployment progress: $ready_pods/$total_pods pods running"
if [[ $ready_pods -eq $total_pods ]]; then
log_deployment_step "INFO" "All pods are running"
break
fi
sleep 30
count=$((count + 30))
done
if [[ $count -ge $timeout ]]; then
log_deployment_step "WARN" "Deployment timeout reached, some pods may still be starting"
fi
}
function perform_aggressive_deployment() {
log_deployment_step "INFO" "Performing aggressive deployment"
# Perform rapid Helm upgrade
helm upgrade longhorn longhorn/longhorn \
--namespace "$NAMESPACE" \
--wait \
--timeout=10m \
--force
# Quick validation
sleep 60
local running_pods=$(kubectl get pods -n "$NAMESPACE" --no-headers | grep "Running" | wc -l)
log_deployment_step "INFO" "Rapid deployment completed with $running_pods running pods"
}
function validate_deployment_success() {
log_deployment_step "INFO" "Validating deployment success"
# Check pod health
local unhealthy_pods=$(kubectl get pods -n "$NAMESPACE" --no-headers | grep -v "Running\|Completed" | wc -l)
if [[ $unhealthy_pods -gt 0 ]]; then
log_deployment_step "ERROR" "$unhealthy_pods unhealthy pods after deployment"
kubectl get pods -n "$NAMESPACE" | grep -v "Running\|Completed"
return 1
fi
# Test volume operations
log_deployment_step "INFO" "Testing volume operations"
local test_pvc_name="deployment-validation-$(date +%s)"
kubectl apply -f - <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: $test_pvc_name
namespace: default
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 1Gi
EOF
# Wait for PVC to be bound
local timeout=300
local count=0
while [[ $count -lt $timeout ]]; do
local pvc_status=$(kubectl get pvc "$test_pvc_name" -n default -o jsonpath='{.status.phase}' 2>/dev/null || echo "NotFound")
if [[ "$pvc_status" == "Bound" ]]; then
log_deployment_step "INFO" "Volume operation test successful"
kubectl delete pvc "$test_pvc_name" -n default || true
break
fi
sleep 5
count=$((count + 5))
done
if [[ $count -ge $timeout ]]; then
log_deployment_step "ERROR" "Volume operation test failed"
kubectl delete pvc "$test_pvc_name" -n default || true
return 1
fi
# Check system responsiveness
log_deployment_step "INFO" "Checking system responsiveness"
local api_response_time=$(time kubectl get nodes >/dev/null 2>&1 | grep real | awk '{print $2}' || echo "unknown")
log_deployment_step "INFO" "API response time: $api_response_time"
log_deployment_step "INFO" "Deployment validation completed successfully"
}
function generate_deployment_report() {
log_deployment_step "INFO" "Generating deployment report"
local report_file="/tmp/longhorn-deployment-report-$(date +%Y%m%d_%H%M%S).json"
cat > "$report_file" <<EOF
{
"deployment_info": {
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"cluster_context": "$(kubectl config current-context)",
"longhorn_version": "$(kubectl get deployment longhorn-ui -n $NAMESPACE -o jsonpath='{.spec.template.spec.containers[0].image}' | cut -d':' -f2)",
"kubernetes_version": "$(kubectl version -o json | jq -r '.serverVersion.gitVersion')"
},
"system_status": {
"total_nodes": $(kubectl get nodes --no-headers | wc -l),
"storage_nodes": $(kubectl get nodes -l "storage.company.com/longhorn-storage=enabled" --no-headers 2>/dev/null | wc -l || echo "0"),
"running_pods": $(kubectl get pods -n $NAMESPACE --no-headers | grep "Running" | wc -l),
"total_pods": $(kubectl get pods -n $NAMESPACE --no-headers | wc -l),
"healthy_volumes": $(kubectl get volumes.longhorn.io -n $NAMESPACE -o json | jq '[.items[] | select(.status.state == "attached" or .status.state == "detached")] | length')
},
"performance_metrics": {
"deployment_duration": "Measured during deployment",
"system_responsiveness": "$(time kubectl get nodes >/dev/null 2>&1 | grep real | awk '{print $2}' || echo 'unknown')"
}
}
EOF
log_deployment_step "INFO" "Deployment report generated: $report_file"
cat "$report_file" | jq .
}
function orchestrate_production_deployment() {
local strategy="${1:-balanced}"
log_deployment_step "INFO" "Starting production Longhorn deployment orchestration"
# Pre-deployment activities
create_pre_deployment_backup
validate_cluster_readiness
# Perform deployment
perform_rolling_deployment "$strategy"
# Post-deployment validation
validate_deployment_success
# Generate report
generate_deployment_report
log_deployment_step "INFO" "Production deployment orchestration completed"
echo ""
echo "🚀 Deployment Summary"
echo "===================="
echo "📄 Deployment log: $MAINTENANCE_LOG"
echo "💾 Backup directory: $BACKUP_DIR"
echo "📊 Deployment report: Generated above"
echo ""
echo "Next steps:"
echo "1. Monitor system performance"
echo "2. Update monitoring and alerting"
echo "3. Document deployment outcomes"
echo "4. Plan next maintenance window"
}
# Execution
case "${1:-help}" in
"deploy")
orchestrate_production_deployment "${2:-balanced}"
;;
"conservative")
orchestrate_production_deployment "conservative"
;;
"balanced")
orchestrate_production_deployment "balanced"
;;
"aggressive")
orchestrate_production_deployment "aggressive"
;;
"validate")
validate_cluster_readiness
validate_deployment_success
;;
"backup")
create_pre_deployment_backup
;;
"report")
generate_deployment_report
;;
*)
echo "Longhorn Production Deployment Manager"
echo "====================================="
echo ""
echo "Usage: $0 {deploy|conservative|balanced|aggressive|validate|backup|report}"
echo ""
echo "Commands:"
echo " deploy [strategy] - Full deployment orchestration (default: balanced)"
echo " conservative - Conservative deployment strategy"
echo " balanced - Balanced deployment strategy"
echo " aggressive - Aggressive deployment strategy"
echo " validate - Validate system readiness and health"
echo " backup - Create pre-deployment backup"
echo " report - Generate deployment report"
;;
esac
Conclusion
Kubernetes Longhorn storage node placement requires sophisticated configuration strategies that optimize performance, ensure proper resource allocation, and maintain operational efficiency across complex enterprise environments. By implementing advanced node selection patterns, multi-tier storage architectures, and comprehensive deployment procedures, platform engineering teams can build highly available storage systems that meet diverse performance and compliance requirements.
The key to successful Longhorn node placement lies in understanding your infrastructure capabilities, implementing intelligent resource allocation strategies, and maintaining comprehensive monitoring and automation systems that ensure optimal storage performance at scale. As your Kubernetes infrastructure grows, these patterns provide a solid foundation for building enterprise-grade storage systems that adapt to evolving business requirements while maintaining reliability and performance.
Regular assessment, automated optimization, and continuous improvement based on performance metrics and operational experience ensure your Longhorn storage deployment remains efficient, scalable, and aligned with organizational objectives.