Explanation

Storage Architecture and Tiers

This document explains kup6s’s multi-tier storage architecture - why we use different storage technologies for different workloads, and how to choose the right storage tier for your data.

Overview: Why Multiple Storage Tiers?

Kubernetes workloads have vastly different storage requirements:

Data Type

Access Pattern

Needs

Best Fit

Databases

Random I/O, high IOPS

Performance, HA

Replicated block storage

Git repositories

Sequential, append-mostly

Reliability, backups

Simple block storage

Logs/Metrics

Write-heavy, time-series

Long retention, cost

Object storage

Artifacts/Uploads

Large files, read-heavy

Scale, low cost/GB

Object storage

Single storage solution = suboptimal for all use cases

kup6s uses a two-tier architecture where each tier is optimized for specific workloads:

  1. Longhorn - Self-managed replicated block storage (flexible, HA), on the dedicated EX NVMe disks

  2. Hetzner S3 Object Storage - Scalable object storage (unlimited, cost-effective)

Note: All block storage is Longhorn on the EX NVMe disks. The Hetzner Cloud CSI driver is disabled (disable_hetzner_csi = true), so there is no hcloud-volumes StorageClass. Shared/file storage uses the SMB CSI driver (Hetzner Storage Box).

Tier 1: Longhorn Distributed Block Storage

What It Is

Self-managed distributed block storage running on the dedicated EX worker nodes.

  • Runs on the 2 EX bare-metal workers in Helsinki (NVMe disks, ~2 TB usable total)

  • Replicates data across the 2 hosts with hard anti-affinity for high availability

  • Provides snapshot, backup, and restore capabilities

  • Backs up to Hetzner Storage Box (CIFS)

Longhorn Resilience Configuration

Since 2025-11-09, Longhorn manager pods use custom health probe configuration to prevent CrashLoopBackOff during cluster operations.

The Problem (Default Helm Chart):

  • Default readiness probe has only 30-second startup window (too short for network initialization)

  • No startup probe for slow webhook initialization during K3s upgrades

  • No liveness probe means pods never auto-recover from stuck states

  • Pods can enter permanent CrashLoopBackOff during network disruptions

Custom Probe Solution:

  • startupProbe: 5-minute grace period for webhook initialization during cluster disruptions

  • livenessProbe: Automatic recovery from stuck states (90-second tolerance)

  • readinessProbe: Enhanced timeout (5s) to handle network latency

Why This Matters: During K3s upgrades or network disruptions, Longhorn manager pods can experience a “chicken-and-egg” problem where the pod’s readiness probe checks its own admission webhook endpoint, but network timing issues prevent the endpoint from becoming available within the default 30-second window. This permanently marks the pod as NotReady, preventing it from joining the Service that would make it reachable.

Implementation:

Three Storage Classes

Longhorn provides three storage classes with different replica strategies:

1. longhorn-redundant-app (1 replica)

When to use:

  • Applications with built-in replication (PostgreSQL clusters, Redis clusters, Kafka)

  • App-level redundancy eliminates need for storage redundancy

  • Minimize cost for already-replicated data

Example: PostgreSQL with CNPG (3 replicas)

PostgreSQL: 3 database replicas = 3x data redundancy
Storage: 1 Longhorn replica = 1x storage
Total: 3 copies of data (efficient!)

Without this optimization:

PostgreSQL: 3 database replicas
Storage: 2 Longhorn replicas per DB
Total: 6 copies of data (wasteful!)

Characteristics:

  • Data locality: best-effort (replica on same node as pod for performance)

  • Cost: Lowest (1x storage per PVC)

  • Protection: App-level replication provides redundancy

2. longhorn (2 replicas) - DEFAULT

When to use:

  • General purpose storage for single-instance applications

  • Good balance of cost vs reliability

  • Most common use case

Example: Prometheus (no built-in replication)

Prometheus: 1 instance (or 2 for HA, but separate data)
Storage: 2 Longhorn replicas
Protection: Survives 1 node failure

Characteristics:

  • Data locality: best-effort (one replica local, one remote)

  • Cost: Medium (2x storage per PVC)

  • Protection: Survives 1 node failure

  • Default: Used when no storageClassName specified

3. longhorn-ha (3 replicas)

When to use:

  • Mission-critical data requiring maximum availability

  • Data that absolutely cannot be lost

  • Willing to pay 3x storage cost

Examples:

  • Critical backups

  • Audit logs

  • Compliance data

Characteristics:

  • Data locality: disabled (maximize scheduling flexibility)

  • Cost: Highest (3x storage per PVC)

  • Protection: Survives 2 simultaneous node failures

  • Use sparingly: Most apps don’t need this level

Data Locality Explained

best-effort (used by 1 and 2 replica classes):

  • Longhorn tries to place one replica on the same node as the pod

  • Faster reads/writes (local disk access)

  • Falls back to remote if local placement impossible

disabled (used by 3 replica class):

  • Replicas distributed purely by available space

  • Maximum scheduling flexibility

  • Better for HA scenarios (replicas spread across more nodes)

Choosing the Right Storage Class

Does your app replicate data internally?
├─ Yes (DB cluster, Redis cluster, Kafka)
│  └─ Use: longhorn-redundant-app (1 replica)
│     Reason: App replication = redundancy already
└─ No (single instance or stateless)
   ├─ Is data mission-critical? Cannot lose under any circumstance?
   │  └─ Yes → Use: longhorn-ha (3 replicas)
   │     Reason: Maximum protection
   └─ No → Use: longhorn (2 replicas, default)
      Reason: Good balance of cost vs reliability

Storage Cost Calculation

Example: App requests 10Gi PVC

Storage Class

Replicas

Actual Storage Used

Cost Factor

longhorn-redundant-app

1

10Gi

1x

longhorn (default)

2

20Gi

2x

longhorn-ha

3

30Gi

3x

Cluster-wide impact:

  • 10 PVCs × 10Gi each = 100Gi requested

  • With longhorn (default): 200Gi actual storage used

  • With longhorn-redundant-app where appropriate: 100-150Gi (savings!)

Example PVCs

# PostgreSQL with CNPG (3 replicas = built-in redundancy)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data-myapp
  namespace: myapp
spec:
  storageClassName: longhorn-redundant-app  # 1 replica
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

---
# Prometheus (single instance, needs storage HA)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: prometheus-data
  namespace: monitoring
spec:
  storageClassName: longhorn  # 2 replicas (default)
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi

---
# Critical audit logs (cannot lose)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: audit-logs
  namespace: compliance
spec:
  storageClassName: longhorn-ha  # 3 replicas
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi

Longhorn Backup Strategy

Backup Target: Hetzner Storage Box (CIFS/SMB)

  • Daily automatic backups via RecurringJob

  • Backup target: cifs://u233601-sub2.your-storagebox.de/u233601-sub2

  • Credentials stored in TF_VAR_longhorn_cifs_* environment variables

CRITICAL Configuration Notes:

  • Backup target URL MUST include cifs:// protocol prefix

  • Example: cifs://u233601-sub2.your-storagebox.de/u233601-sub2

  • Without cifs://: Longhorn manager CrashLoopBackOff

Tier 2: Hetzner S3 Object Storage

What It Is

Managed S3-compatible object storage provided by Hetzner.

  • S3-compatible API (works with AWS SDK tools)

  • Regional endpoints (fsn1, nbg1, hel1)

  • Lifecycle policies for automatic expiration

  • Cost-effective for large-scale data

Region

All object storage is in fsn1 (Falkenstein):

  • Purpose: etcd backups, application data (Loki logs, Thanos metrics, GitLab artifacts)

  • etcd backup bucket: backup-etcd-fsn1-kup6s

  • Endpoint: https://fsn1.your-objectstorage.com

etcd backups (backup-etcd-fsn1-kup6s) and all other object storage live in fsn1.

When to Use S3

Perfect for:

  • Large files - Artifacts, uploads, container images

  • Long-term data - Logs, metrics, backups

  • Unlimited scale - No capacity planning needed

  • Infrequent access - Cheaper per GB than block storage

Examples:

  • GitLab artifacts, uploads, LFS, pages, registry, backups

  • Prometheus/Thanos long-term metrics (730-day retention)

  • Loki log chunks (90-day retention)

  • PostgreSQL Barman backups

Shared Credentials Model

CRITICAL: Hetzner S3 credentials are project-wide

  • All S3 services use the SAME credentials

  • Hetzner S3 credentials grant access to all buckets in project

  • Not bucket-specific like AWS IAM

  • Credentials: TF_VAR_hetzner_s3_access_key and TF_VAR_hetzner_s3_secret_key

Services using S3:

  • K3S etcd backups

  • Loki log storage

  • Thanos metrics storage

  • Crossplane-managed application buckets

Bucket Naming Convention

CRITICAL: Hetzner S3 bucket names are globally unique across all Hetzner customers.

Naming Schema: {localpart}-{namespace}-kup6s

Examples:

  • Infrastructure: backup-etcd-fsn1-kup6s, logs-loki-kup6s, metrics-thanos-kup6s

  • GitLab BDA: gitlab-artifacts-gitlabbda-kup6s, gitlab-uploads-gitlabbda-kup6s

Rule: Always suffix bucket names with -kup6s to avoid collisions.

See S3 Bucket Naming for complete patterns.

Bucket Provisioning

Via Crossplane (automated, GitOps-friendly):

apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
  name: data-myapp-kup6s
spec:
  deletionPolicy: Delete
  managementPolicies:
    - Observe
    - Create
    - Delete
    # Skip Update to avoid tagging operations (Hetzner doesn't support)
  forProvider:
    region: fsn1  # Use Hetzner region codes
  providerConfigRef:
    name: hetzner-s3

CRITICAL Management Policies:

  • Always include managementPolicies: [Observe, Create, Delete]

  • Skips Update operations (Hetzner doesn’t support tagging)

  • Without this: buckets show SYNCED=False due to tagging errors

Storage Decision Matrix

Workload Type

Longhorn

S3

Rationale

Git repositories

✅ (2 replicas)

Backups only

On EX NVMe, backed up daily to S3

PostgreSQL (clustered)

✅ (1 replica)

Backups

App has replication

PostgreSQL (single)

✅ (2 replicas)

Backups

Need storage HA

Redis (clustered)

✅ (1 replica)

App has replication

Redis (single)

✅ (2 replicas)

Need storage HA

Prometheus

✅ (2 replicas)

✅ Long-term

Short-term local, long S3

Loki

✅ (2 replicas)

✅ Chunks

WAL local, chunks S3

GitLab artifacts

Large files, unlimited scale

Container registry

Image layers, blob storage

Backups

Long-term, cost-effective

Performance Characteristics

Storage Tier

Read Latency

Write Latency

IOPS

Throughput

Cost/GB/month

Longhorn (local NVMe)

<5ms

<10ms

Thousands

160-320 MB/s

~€0.10 (cluster overhead)

Hetzner S3

50-200ms

100-300ms

Unlimited

1-10 Gbps

€0.005

Takeaway: Use Longhorn block storage for performance, S3 for scale and cost.

Backup Philosophy

Defense in Depth

  1. Application-level backups - GitLab backups, PostgreSQL Barman

  2. Storage-level redundancy - Longhorn replication across nodes

  3. Off-cluster backups - Longhorn to CIFS, PostgreSQL/GitLab to S3

  4. etcd backups - snapshots to S3 (backup-etcd-fsn1-kup6s, fsn1)

Avoid Double Redundancy

Anti-pattern:

  • PostgreSQL: 3 CNPG replicas (3x data)

  • Storage: 2 Longhorn replicas per instance (2x)

  • Total: 6 copies of data (wasteful!)

Better:

  • PostgreSQL: 3 CNPG replicas (3x data)

  • Storage: 1 Longhorn replica per instance (1x)

  • Barman backups to S3 (off-cluster protection)

  • Total: 3 copies + S3 backup (efficient!)

Cost Optimization Strategies

1. Use Appropriate Storage Classes

Example cluster with 5 PostgreSQL databases (10Gi each):

Approach

Storage Class

Total Longhorn

Savings

Default (all 2 replicas)

longhorn

100Gi

Baseline

Optimized (1 replica)

longhorn-redundant-app

50Gi

50% savings

2. Reduce Retention Where Possible

Prometheus example:

  • Before Thanos: 7-day local retention = 6Gi PVC

  • After Thanos: 3-day local retention = 3Gi PVC

  • Long-term data offloaded to S3 (cheaper)

  • Savings: 50% Longhorn storage

3. Use S3 for Large/Infrequent Data

Storage

Cost/GB/month

Use Case

Longhorn

~€0.10

Active databases, hot data

S3

~€0.005

Archives, backups, artifacts

Savings

20x cheaper

Move appropriate data to S3

4. Right-Size PVCs

Don’t overprovision:

  • Monitor actual usage: kubectl top pv

  • Start small, grow as needed

  • Longhorn PVCs can be expanded (but not shrunk)