Phase 6: Install Ceph

Goal: turn the unused storage in each Dell T5500 into a shared storage cluster that Proxmox, OpenStack, Kubernetes, and Slurm can later consume.

Target design:

pve01 ─ Ceph MON + MGR + OSD
pve02 ─ Ceph MON + MGR + OSD
pve03 ─ Ceph MON + MGR + OSD

Use Ceph for:

VM disks        → Ceph RBD
OpenStack Cinder → Ceph RBD
Glance images → Ceph RBD
Kubernetes PVs → Ceph CSI later
Shared files → CephFS later
Object storage → RGW later

1. Pre-check every node

Run on all three Proxmox nodes:

hostname
ip -br addr
lsblk
pveversion
timedatectl

Check that:

pve01 / pve02 / pve03 can resolve each other
time is synced
cluster quorum is healthy
the intended Ceph disks are unused

Check cluster:

pvecm status

You want:

Quorum: Yes
Nodes: 3

2. Decide the Ceph network

Because you only have one NIC per node, keep it simple first.

Use your existing Proxmox management network initially.

Example:

pve01 192.168.1.10
pve02 192.168.1.11
pve03 192.168.1.12

Later, if you add VLANs or a second NIC, you can separate:

public_network  = client / VM / OpenStack access
cluster_network = OSD replication / recovery traffic

For now:

Ceph public network = 192.168.1.0/24
Ceph cluster network = same network

Not ideal for production, but fine for learning.


3. Install Ceph from Proxmox UI or CLI

Option A — Proxmox UI

On each node:

Datacenter
→ Node
→ Ceph
→ Install Ceph

Use the same version on all nodes.

Then initialise Ceph on the first node.


Option B — CLI

On each node:

pveceph install

Then initialise Ceph on pve01:

pveceph init --network 192.168.1.0/24

Then create MONs:

pveceph mon create

Run this on each node:

pveceph mon create

Then create managers:

pveceph mgr create

Again, run on each node.

Verify:

ceph -s
ceph mon stat
ceph mgr stat

4. Prepare disks for OSDs

List disks:

lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT

Example:

sda  150G  Proxmox OS
sdb 2.5T empty disk for Ceph OSD

Important: do not use the Proxmox OS disk as an OSD unless you deliberately partitioned it for that.

Wipe the intended Ceph disk:

wipefs -a /dev/sdb
sgdisk --zap-all /dev/sdb

Then create one OSD per node:

pveceph osd create /dev/sdb

Repeat on pve01, pve02, and pve03.

Verify:

ceph osd tree
ceph -s

You want to see:

3 osds: 3 up, 3 in

5. Create a Ceph pool for VM disks

Create an RBD pool:

ceph osd pool create vm-rbd 32
ceph osd pool application enable vm-rbd rbd
rbd pool init vm-rbd

For a small 3-node lab, 32 PGs is fine to start.

Add it to Proxmox storage:

pvesm add rbd ceph-vm-rbd \
--pool vm-rbd \
--content images,rootdir \
--krbd 0

Check:

pvesm status

You should see ceph-vm-rbd.


6. Test VM storage on Ceph

Create or clone a small VM and place its disk on:

ceph-vm-rbd

Then test live migration:

qm migrate <vmid> pve02 --online

If the VM disk is on Ceph, migration should not require copying the disk.

That is one of the main reasons Ceph is valuable.


7. Create CephFS later, not immediately

Do RBD first.

After RBD works, add CephFS.

Create metadata servers:

pveceph mds create

Create CephFS:

pveceph fs create cephfs

Add to Proxmox storage:

pvesm add cephfs cephfs \
--content iso,backup,snippets

Use CephFS for:

ISO images
container templates
backups
shared files
snippets

Use RBD for:

VM disks
OpenStack Cinder volumes
Glance images

8. Learn the important Ceph commands

Run these repeatedly until you understand them:

ceph -s
ceph health detail
ceph osd tree
ceph osd df
ceph df
ceph mon stat
ceph mgr stat
ceph pg stat
ceph pg dump_stuck

For OSD detail:

ceph osd metadata
ceph osd perf

For pools:

ceph osd pool ls detail
rbd ls vm-rbd
rbd du vm-rbd

9. Break it deliberately

This is where the real learning happens.

Safely test:

systemctl stop ceph-osd@0
ceph -s
systemctl start ceph-osd@0

Observe:

HEALTH_WARN
OSD down
PG degraded
recovery starts
HEALTH_OK returns

Then test node-level failure:

Shutdown pve03
Watch ceph -s
Restart pve03
Watch recovery

Do not panic when Ceph shows warnings. Learn what they mean.


10. What healthy looks like

A healthy 3-node lab should show roughly:

cluster:
health: HEALTH_OK

services:
mon: 3 daemons, quorum pve01,pve02,pve03
mgr: pve01(active), standbys: pve02,pve03
osd: 3 osds: 3 up, 3 in

data:
pools: 1 pools
pgs: active+clean

11. Key concepts to understand

Focus on these:

MON    = cluster map and quorum
MGR = metrics, dashboard, management modules
OSD = stores the actual data
Pool = logical storage namespace
PG = placement group
CRUSH = decides where data lives
RBD = block devices for VMs
CephFS = shared filesystem
RGW = S3-compatible object storage

The biggest one is CRUSH.

CRUSH determines where replicas are placed. In your 3-node lab, you want data spread across different hosts, not multiple copies on the same host.


Recommended order

Do not try to configure everything at once.

Use this sequence:

1. Install Ceph packages
2. Create MONs
3. Create MGRs
4. Add one OSD per node
5. Confirm HEALTH_OK
6. Create RBD pool
7. Add RBD to Proxmox
8. Put VM disk on Ceph
9. Test live migration
10. Break and recover one OSD
11. Add CephFS
12. Later integrate with OpenStack

For your homelab, the first real success milestone is:

A VM running on pve01 with its disk on Ceph RBD,
live migrated to pve02 without copying the disk.

That proves the Proxmox + Ceph foundation is working.

Reality and Actions

Current hardware

HostHDD1HDD2Total Raw
pve03 TB3 TB6 TB
pve13 TB3 TB6 TB
pve23 TB3 TB6 TB

Total:

  • 6 OSDs
  • 18 TB raw
  • Approximately 6 TB usable with a replication factor of 3.

That may sound wasteful, but it is exactly what you want to learn with. Three-way replication means the cluster can survive the loss of an entire node while keeping data available.


Recommended layout

I would dedicate all six HDDs exclusively to Ceph.

Do not put ZFS on them.

Keep:

  • Proxmox OS on your boot SSD
  • Ceph on the six HDDs
pve0
├── OS SSD
├── sdb → OSD 0
└── sdc → OSD 1

pve1
├── OS SSD
├── sdb → OSD 2
└── sdc → OSD 3

pve2
├── OS SSD
├── sdb → OSD 4
└── sdc → OSD 5

Phase 1

Install Ceph everywhere

Since you’ve already invested in Ansible, make this another role:

roles/
ceph_install/
ceph_cluster/
ceph_osd/
ceph_pools/

Tasks:

Install Ceph


Create monitors



Create manager



Deploy OSDs



Create pool



Add storage to Proxmox

Phase 2

Create MONs

One monitor per node.

pve0  MON
pve1 MON
pve2 MON

Never run only one monitor.


Phase 3

Managers

pve0 mgr active

pve1 mgr standby

pve2 mgr standby

Automatic failover.


Phase 4

OSDs

Each HDD becomes an OSD.

pve0

sdb → osd.0

sdc → osd.1

--------------------

pve1

sdb → osd.2

sdc → osd.3

--------------------

pve2

sdb → osd.4

sdc → osd.5

Total

6 OSDs

Phase 5

Failure domains

Very important.

Choose

host

not

osd

This means replicas are stored on different servers.

Replica 1

pve0

Replica 2

pve1

Replica 3

pve2

instead of

Replica 1

sdb

Replica 2

sdc

Same machine

Phase 6

Replication

Use

size = 3

min_size = 2

This is the standard recommendation for a three-node cluster.


Phase 7

Pools

Initially I’d create only three pools.

rbd-vm

RBD images

-------------------

rbd-ct

Container images

-------------------

backup

Optional later

Phase 8

Storage

After Ceph is healthy

Datacenter

Storage



Add



RBD

Then migrate VMs onto Ceph.


Phase 9

Networking

This is the biggest decision.

Option A

Single network

192.168.1.x

VM traffic

Cluster traffic

Ceph traffic

Migration

Perfectly acceptable for a homelab.


Option B

Two networks

vmbr0

LAN

192.168.1.x

---------------------

vmbr1

Ceph backend

10.10.10.x

Better performance.


Option C (my recommendation)

Since you’re building this to learn OpenStack and AI infrastructure, use:

vmbr0

Management

192.168.1.x

------------------

vmbr1

Storage

10.10.10.x

------------------

vmbr2

Migration

10.20.20.x

Exactly how many production clusters are designed.


Phase 10

CRUSH

Later we’ll customise CRUSH.

Initially

host



osd

is sufficient.

Later you can experiment with

rack



host



osd

Performance expectations

With spinning disks:

  • Around 150–200 MB/s per disk sequential throughput.
  • Aggregate cluster throughput can reach roughly 800–1000 MB/s for large sequential reads/writes.
  • Random I/O and latency will be the limiting factors, especially for VM boot disks.

If you later add a small enterprise NVMe SSD (for example, one per node), you can use it for RocksDB/WAL or metadata acceleration in BlueStore, which significantly improves random performance while keeping the bulk data on HDDs.


Suggested Ansible roles

I would structure the Ceph automation as follows:

roles/
├── ceph_install
│ ├── tasks
│ ├── handlers
│ └── defaults

├── ceph_bootstrap

├── ceph_mon

├── ceph_mgr

├── ceph_osd

├── ceph_crush

├── ceph_pools

├── ceph_storage

├── ceph_dashboard

├── ceph_health

└── ceph_cleanup

This mirrors how you’d automate a real production deployment and makes each stage independently repeatable and testable.

My recommendation

Rather than using the Proxmox GUI to create Ceph, I’d automate the entire deployment with Ansible. That approach aligns well with your goal of becoming proficient with OpenStack and modern infrastructure automation, and it gives you an idempotent build process that can recreate the cluster from scratch whenever you need to test or recover.

Ansible playbooks and roles generated, downloaded, and run in as recommended:

Recommended command sequence

Use --ask-vault-pass for all runs:

ansible-playbook -i inventory.ini ceph-site.yml --tags ceph_preflight --ask-vault-pass

Then:

ansible-playbook -i inventory.ini ceph-site.yml --tags ceph_install --ask-vault-pass
ansible-playbook -i inventory.ini ceph-site.yml --tags ceph_init,ceph_services --ask-vault-pass
ansible-playbook -i inventory.ini ceph-site.yml --tags ceph_osd --ask-vault-pass
ansible-playbook -i inventory.ini ceph-site.yml --tags ceph_pools,ceph_storage --ask-vault-pass
ansible-playbook -i inventory.ini ceph-verify.yml --ask-vault-pass

Actions

Cluster target

You built a 3-node Proxmox/Ceph cluster:

pve0  192.168.1.10  2 x 3 TB HDDs
pve1 192.168.1.11 2 x 3 TB HDDs
pve2 192.168.1.12 2 x 3 TB HDDs

Ceph result:

MONs: pve0, pve1, pve2
MGRs: pve0 active, pve1/pve2 standby
OSDs: 6 total, 6 up, 6 in
Health: HEALTH_OK
Raw capacity: ~16 TiB shown by Ceph
Usable replicated capacity: roughly one third, about 5.4 TiB with size=3

1. Confirm the 3 TB HDDs existed on each host

You checked the kernel logs on each Proxmox node:

dmesg | grep 2.73

Example output:

[sdb] 5860533168 512-byte logical blocks: (3.00 TB/2.73 TiB)
[sdc] 5860533168 512-byte logical blocks: (3.00 TB/2.73 TiB)

Purpose:

Confirmed each Proxmox host had two 3 TB HDDs.
Identified them as /dev/sdb and /dev/sdc.
Established these would become Ceph OSD disks.

Important result:

pve0: /dev/sdb, /dev/sdc
pve1: /dev/sdb, /dev/sdc
pve2: /dev/sdb, /dev/sdc

2. Confirm Ceph was not yet installed

You checked for the Ceph monitor binary:

ansible -i inventory.ini proxmox -b -m shell \
-a 'command -v ceph-mon || echo MISSING' \
--ask-vault-pass

Output:

pve0: MISSING
pve1: MISSING
pve2: MISSING

Purpose:

Verified that the Ceph monitor service binary was not installed.
Confirmed that pveceph mon create could not work yet.

This explained the earlier failure:

binary not installed: /usr/bin/ceph-mon

3. Install Ceph through Proxmox integration

You ran:

pveceph install --repository no-subscription

It prompted:

This will install Ceph 20.2 Tentacle - continue (y/N)?

You answered:

y

Purpose:

Installed the Proxmox-integrated Ceph packages.
Enabled the no-subscription Ceph repository.
Installed Ceph 20.2 Tentacle packages, including ceph-mon, ceph-mgr, ceph-osd, ceph-volume and ceph-common.

In Ansible, the role had to be fixed because pveceph install was interactive. The corrected task used:

- name: Install Ceph through Proxmox integration
ansible.builtin.shell: |
set -o pipefail
printf 'y\n' | pveceph install --repository no-subscription
args:
executable: /bin/bash
register: pveceph_install_result
changed_when: true
when: not ceph_mon_check.stat.exists
environment:
DEBIAN_FRONTEND: noninteractive
APT_LISTCHANGES_FRONTEND: none

Purpose:

Automatically answered the Proxmox Ceph repository confirmation prompt.
Allowed the install stage to run non-interactively from Ansible.

4. Fix the install role’s Ceph binary checks

The first check used:

ansible.builtin.command: command -v ceph-mon

That failed because command -v is a shell builtin, not an executable binary.

The corrected check used stat:

- name: Check whether ceph-mon exists
ansible.builtin.stat:
path: /usr/bin/ceph-mon
register: ceph_mon_check

And later:

- name: Check ceph-mon binary exists after install
ansible.builtin.stat:
path: /usr/bin/ceph-mon
register: ceph_mon_binary

- name: Fail if ceph-mon is still missing
ansible.builtin.fail:
msg: "ceph-mon is still missing. Ceph monitor package was not installed correctly."
when: not ceph_mon_binary.stat.exists

Purpose:

Made the install role idempotent and reliable.
Checked the actual binary path instead of relying on shell builtins.
Prevented the Ceph init stage from running unless Ceph was truly installed.

5. Verify Ceph binaries existed

You used:

ansible -i inventory.ini proxmox -b -m shell \
-a 'command -v ceph-mon && ceph --version' \
--ask-vault-pass

Purpose:

Confirmed that /usr/bin/ceph-mon existed on all three hosts.
Confirmed the installed Ceph version.

Expected output:

/usr/bin/ceph-mon
ceph version 20.2...

6. Initialize the Ceph cluster on pve0

The playbook then ran the init role on pve0.

Core command:

pveceph init

Purpose:

Created the initial Ceph cluster configuration.
Generated /etc/pve/ceph.conf.
Set up the cluster FSID.
Prepared Proxmox’s Ceph integration.

Then the first monitor was created:

pveceph mon create

Purpose:

Created the first Ceph MON on pve0.
Started the initial Ceph quorum with one monitor.
Allowed ceph -s to work for the first time.

Then the first manager was created:

pveceph mgr create

Purpose:

Created the first Ceph Manager daemon on pve0.
Enabled Ceph management services.
Allowed the cluster to report full status, health, and service metadata.

You hit this idempotency issue:

ceph manager directory '/var/lib/ceph/mgr/ceph-pve0' already exists

That meant the manager had already been created. The role was fixed to check:

/var/lib/ceph/mgr/ceph-pve0

before trying to recreate it.


7. Confirm initial Ceph state

You ran:

ansible -i inventory.ini pve0 -b -m shell \
-a 'ceph -s' \
--ask-vault-pass

Output showed:

mon: 1 daemons, quorum pve0
mgr: pve0(active)
osd: 0 osds
health: HEALTH_WARN
OSD count 0 < osd_pool_default_size 3

Purpose:

Confirmed Ceph was alive.
Confirmed pve0 had the first MON and active MGR.
Confirmed the warning was only because no OSD disks existed yet.

At this point, Ceph control-plane initialization was successful.


8. Add MON and MGR services on pve1 and pve2

You ran the Ceph services stage:

ansible-playbook -i inventory.ini ceph-site.yml \
--tags ceph_services \
--ask-vault-pass

The role ran commands equivalent to:

pveceph mon create
pveceph mgr create

on pve1 and pve2.

Purpose:

Added a Ceph Monitor to each Proxmox host.
Added standby Ceph Manager daemons to pve1 and pve2.
Created a resilient 3-MON quorum.
Allowed manager failover if pve0 fails.

9. Verify 3-node MON quorum and MGR standby

You ran:

ansible -i inventory.ini pve0 -b -m shell \
-a 'ceph -s && echo && ceph mon stat && echo && ceph mgr stat' \
--ask-vault-pass

Output:

mon: 3 daemons, quorum pve0,pve1,pve2
mgr: pve0(active), standbys: pve1, pve2
osd: 0 osds

ceph mon stat showed:

pve0=[v2:192.168.1.10:3300/0,v1:192.168.1.10:6789/0]
pve1=[v2:192.168.1.11:3300/0,v1:192.168.1.11:6789/0]
pve2=[v2:192.168.1.12:3300/0,v1:192.168.1.12:6789/0]

ceph mgr stat showed:

{
"available": true,
"active_name": "pve0",
"num_standby": 2
}

Purpose:

Verified the Ceph monitor quorum was healthy.
Verified all three monitor nodes were participating.
Verified one active manager and two standby managers.
Confirmed the control plane was correctly distributed before adding storage.

10. Check candidate OSD disks

Before wiping disks, the safe commands were:

ansible -i inventory.ini proxmox -b -m shell \
-a 'lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS,MODEL,SERIAL' \
--ask-vault-pass

And:

ansible -i inventory.ini proxmox -b -m shell \
-a 'wipefs -n /dev/sdb /dev/sdc || true' \
--ask-vault-pass

Purpose:

Confirmed /dev/sdb and /dev/sdc were the intended 3 TB HDDs.
Checked they were not mounted.
Checked whether they had existing filesystem, LVM, ZFS or partition signatures.
Prevented accidental destruction of OS disks or wrong devices.

This was the critical destructive safety gate.


11. Enable OSD disk destruction safety flag

In your Ceph variables file:

ceph_osd_devices:
- /dev/sdb
- /dev/sdc

ceph_allow_destroy: true

Purpose:

Explicitly allowed the playbook to wipe /dev/sdb and /dev/sdc.
Prevented accidental OSD creation unless you deliberately enabled destruction.

This is important because ceph-volume lvm zap --destroy and pveceph osd create are destructive.


12. Create six Ceph OSDs

You ran:

ansible-playbook -i inventory.ini ceph-site.yml \
--tags ceph_osd \
--ask-vault-pass

The role performed operations equivalent to:

ceph-volume lvm zap /dev/sdb --destroy
ceph-volume lvm zap /dev/sdc --destroy
pveceph osd create /dev/sdb
pveceph osd create /dev/sdc

on each node.

Purpose:

Destroyed old signatures on /dev/sdb and /dev/sdc.
Prepared each disk as a Ceph BlueStore OSD.
Created two OSDs per host.
Added six OSDs into the Ceph CRUSH map.
Started the ceph-osd services.
Marked the OSDs up and in.

Resulting layout:

pve0:
osd.0 /dev/sdb
osd.1 /dev/sdc

pve1:
osd.2 /dev/sdb
osd.3 /dev/sdc

pve2:
osd.4 /dev/sdb
osd.5 /dev/sdc

13. Verify OSD health and CRUSH tree

You ran:

ansible -i inventory.ini pve0 -b -m shell \
-a 'ceph -s && echo && ceph osd tree' \
--ask-vault-pass

Output:

health: HEALTH_OK

mon: 3 daemons, quorum pve0,pve1,pve2
mgr: pve0(active), standbys: pve1, pve2
osd: 6 osds: 6 up, 6 in

OSD tree:

root default
├── host pve0
│ ├── osd.0 hdd 2.72899 up
│ └── osd.1 hdd 2.72899 up
├── host pve1
│ ├── osd.2 hdd 2.72899 up
│ └── osd.3 hdd 2.72899 up
└── host pve2
├── osd.4 hdd 2.72899 up
└── osd.5 hdd 2.72899 up

Purpose:

Confirmed all six OSDs were available.
Confirmed the OSDs were distributed evenly across the three Proxmox hosts.
Confirmed the CRUSH hierarchy uses host-level placement.
Confirmed the cluster was healthy after adding storage.

14. Pool creation state

Your final ceph -s showed:

pools: 1 pools, 1 pgs
objects: 2 objects
usage: 162 MiB used

That means one pool already exists. It may have been created by the playbook or by Proxmox/Ceph integration.

The next inspection commands were:

ansible -i inventory.ini pve0 -b -m shell \
-a 'ceph osd pool ls detail' \
--ask-vault-pass

Purpose:

Show the existing pool name.
Show pool size, min_size, PG count, autoscale mode and application metadata.

And:

ansible -i inventory.ini pve0 -b -m shell \
-a 'pvesm status' \
--ask-vault-pass

Purpose:

Show whether Proxmox already has an RBD storage backend configured.

15. Recommended pool configuration

For this 3-node cluster, the recommended settings are:

size = 3
min_size = 2
failure domain = host
application = rbd

Commands discussed:

ceph config set global osd_pool_default_size 3
ceph config set global osd_pool_default_min_size 2

Purpose:

Set the default replication factor to 3.
Allow degraded operation with 2 replicas available.
Match the three-node failure-domain layout.

For a VM RBD pool:

pveceph pool create ceph-vm --size 3 --min_size 2 --application rbd

Purpose:

Create a Ceph pool suitable for Proxmox VM disks and container root disks.

Then:

ceph osd pool application enable ceph-vm rbd --yes-i-really-mean-it

Purpose:

Ensure Ceph marks the pool as an RBD pool.
Allows RBD clients and Proxmox storage integration to use it correctly.

16. Add Ceph RBD storage to Proxmox

Command discussed:

pvesm add rbd ceph-vm \
--pool ceph-vm \
--content images,rootdir \
--krbd 0

Purpose:

Adds the Ceph pool as Proxmox shared storage.
Allows VM disks to be created on Ceph RBD.
Allows container root disks to be stored on Ceph RBD.
Makes the storage visible cluster-wide in the Proxmox GUI.

Verification:

pvesm status

Purpose:

Confirms the Proxmox storage backend exists and is active.

17. Final health check

The final cluster health command:

ansible -i inventory.ini pve0 -b -m shell \
-a 'ceph -s && echo && ceph osd tree' \
--ask-vault-pass

Confirmed:

HEALTH_OK
3 MONs
1 active MGR + 2 standby MGRs
6 OSDs up
6 OSDs in
16 TiB raw capacity
host-level CRUSH layout

This is the correct end state for the Ceph storage layer.


End-to-end stage summary

Stage 1: Disk discovery
Confirmed /dev/sdb and /dev/sdc were 3 TB HDDs on all nodes.

Stage 2: Ceph package check
Verified ceph-mon was missing.

Stage 3: Ceph package installation
Installed Ceph 20.2 Tentacle through pveceph no-subscription repository.

Stage 4: Init pve0
Created the initial Ceph cluster, first MON, and first MGR.

Stage 5: Control-plane expansion
Added MON and MGR services on pve1 and pve2.

Stage 6: Quorum verification
Confirmed 3-node monitor quorum and manager standby failover.

Stage 7: Disk safety checks
Checked /dev/sdb and /dev/sdc before destructive OSD creation.

Stage 8: OSD creation
Created 6 total OSDs, 2 per host.

Stage 9: CRUSH verification
Confirmed host-level OSD placement across pve0, pve1, and pve2.

Stage 10: Pool/storage configuration
Created or prepared RBD pool and Proxmox storage integration.

Stage 11: Final validation
Confirmed HEALTH_OK with 6 OSDs up/in.

Your Ceph cluster is now correctly built for a 3-node Proxmox homelab: replicated, quorum-safe, and ready for RBD-backed VM storage.