CephCIV · Multi-Tenancy in PracticeMulti-Tenancy in Practice
Tenant onboarding as a repeatable procedure
What you'll learn
- Define what onboarding creates
- Establish naming conventions that survive scale
- Script the procedure
- Verify a tenant before handing it over
Prerequisites
None — start here.
Verified against Ceph Tentacle 20.2.x · Ceph Squid 19.2.x (supported previous) · cephadm matches the verified Ceph release · podman 4.x · csi-rbd and csi-cephfs current · RBD / CephFS / RGW current (matches Ceph release) · Linux kernel 5.15+ (5.10 minimum) · Ubuntu 24.04 LTS (Ceph host baseline) · Debian 12 (Bookworm) (Ceph host baseline) · Rocky Linux / RHEL / AlmaLinux 9.x (Ceph host baseline) · Proxmox VE 9.x (cross-course integration) · Kubernetes 1.31+ (cross-course integration) · 2026-08-18
Why this matters in production
Tenants provisioned by hand differ from one another in ways that surface months later as inconsistent quotas, orphaned entities, and pools nobody can attribute.
What onboarding creates
| Object | Per tenant |
|---|---|
| A pool, or a namespace within a shared pool | one |
| A cephx entity scoped to it | one, or one per environment |
| A quota | on the pool or namespace |
| An application tag | so ceph df and health checks attribute correctly |
| A register entry | owner, purpose, review date |
| Monitoring labels | so per-tenant dashboards exist from day one |
ceph osd pool create tenant-acme 128
ceph osd pool application enable tenant-acme rbd
ceph osd pool set-quota tenant-acme max_bytes $((10 * 1024**4))
ceph auth get-or-create client.tenant-acme \
mon 'profile rbd' \
osd 'profile rbd pool=tenant-acme' \
mgr 'profile rbd pool=tenant-acme'
Naming conventions
A convention that survives scale encodes:
the tenant
the environment
the service type
| Pattern | Example |
|---|---|
<tenant>-<env>-<type> | acme-prod-rbd |
| Entity matching the pool | client.acme-prod-rbd |
| Namespace form | pool shared-rbd, namespace acme-prod |
ceph osd pool ls | sort
ceph auth ls --format json | python3 -c '
import sys,json
for e in json.load(sys.stdin)["auth_dump"]:
if e["entity"].startswith("client."): print(e["entity"])' | sort
A convention lets a script answer "which entities belong to acme" and
"which pools have no owner", which is what makes offboarding possible.
Scripting it
#!/bin/bash
# onboard-tenant.sh <tenant> <env> <quota-tb> <pg-num>
set -euo pipefail
T=$1; E=$2; Q=$3; PG=$4
POOL="${T}-${E}-rbd"
ENT="client.${T}-${E}-rbd"
ceph osd pool create "$POOL" "$PG"
ceph osd pool application enable "$POOL" rbd
ceph osd pool set-quota "$POOL" max_bytes $(( Q * 1024**4 ))
rbd pool init "$POOL"
ceph auth get-or-create "$ENT" \
mon 'profile rbd' \
osd "profile rbd pool=$POOL" \
mgr "profile rbd pool=$POOL"
echo "$ENT | $POOL | quota ${Q}TiB | created $(date -I)" \
>> /secure/tenant-register.txt
bash onboard-tenant.sh acme prod 10 128
Scripting is not about saving time. It is about every tenant being
identical, so a later change can be applied to all of them.
Verifying before handover
POOL=acme-prod-rbd; ENT=client.acme-prod-rbd
ceph osd pool get "$POOL" all | head
ceph osd pool get-quota "$POOL"
ceph auth get "$ENT"
# the tenant key works for its own pool
ceph -n "$ENT" --keyring /tmp/t.keyring -s >/dev/null && echo "auth OK"
rbd -n "$ENT" --keyring /tmp/t.keyring -p "$POOL" ls && echo "pool access OK"
# and does not work for another tenant's pool
rbd -n "$ENT" --keyring /tmp/t.keyring -p other-prod-rbd ls 2>&1 | \
grep -q -i 'denied\|permission' && echo "isolation OK" || echo "ISOLATION FAILURE"
Quiz
Knowledge check · 4 questions
Q1. What is the main benefit of scripting tenant onboarding?
Q2. A missing pool application tag costs something even on a cluster where every pool is working correctly.
Q3. Design a tenant onboarding procedure.
A cluster will host around forty tenants. Provisioning is currently done by hand from a wiki page.
Q4. What should a tenant verification include beyond confirming access works?
Passing score: 75%. Answers are checked in this browser.
Production discipline
Script tenant onboarding so every tenant has an identical shape — the value is not the provisioning time but every later change across all tenants becoming a loop. Enable the pool application tag at creation, and include a negative isolation test in the handover check.
Cross-course references
- Kubernetes: namespace provisioning templates exist for the identical consistency reason
- Linux: identical configuration is what makes fleet-wide change tractable