<?xml version="1.0"?>
<rss version="2.0"><channel><title><![CDATA[Virtualization & Cloud Latest Topics]]></title><link>https://wtmsp.com/forum/11-virtualization-cloud/</link><description><![CDATA[Virtualization & Cloud Latest Topics]]></description><language>en</language><item><title>Our Proxmox backup setup, and why we restore something every month</title><link>https://wtmsp.com/topic/21-our-proxmox-backup-setup-and-why-we-restore-something-every-month/</link><description><![CDATA[<p>When we inherit a Proxmox cluster, backups are one of the first things I look at. Proxmox makes them easy to turn on, which also makes it easy to assume you're covered when you aren't. This is what we run for our own cluster and for clients: which vzdump mode we pick, how we schedule and prune, why I think Proxmox Backup Server is worth the extra box, and the restore testing that tells you whether any of it works.</p><div class="ipsRichTextBox" data-i-background-color="blue"><div class="ipsRichTextBox__title"><p><strong>Have these ready</strong></p></div><ul><li>A Proxmox VE node or cluster</li><li>Backup storage that is <strong>not</strong> on the same disks or pool your VMs run on. An NFS share or a separate disk works, and a Proxmox Backup Server (PBS) is better.</li><li>A spare VM ID range for restore tests. We use 9000 and up.</li></ul></div><h2>A snapshot isn't a backup</h2><p>A Proxmox snapshot is a point-in-time state stored on the same storage as the VM. It's perfect for "I'm about to upgrade this and want to roll back in five minutes if it goes badly." It does nothing for you if the storage fails, the pool fills, someone deletes the VM, or ransomware reaches the host. Long-lived snapshots also slow down some storage types and eat space as the disk changes. Take them before a change and delete them after. Backups are separate copies on separate storage.</p><h2>Picking a vzdump mode</h2><ul><li>Snapshot mode keeps the guest running while it's backed up. For VMs, Proxmox uses QEMU's live backup mechanism, and with the QEMU guest agent installed and enabled it briefly freezes the guest filesystems for consistency. For containers it needs storage that supports snapshots, or it falls back to suspend mode. This is our default.</li><li>Suspend mode, for containers, copies data while running and then suspends the container briefly for a final sync. For VMs it offers little over snapshot mode and causes a pause.</li><li>Stop mode shuts the guest down, backs it up and starts it again. It's the most consistent, at the cost of downtime. We use it for a few legacy systems whose databases don't tolerate live backup well.</li></ul><p>Install and enable the QEMU guest agent on every VM: <code>qemu-guest-agent</code> on Linux, the VirtIO guest tools on Windows, and the Guest Agent option in the VM settings. Without it, snapshot-mode backups are crash-consistent, not filesystem-consistent.</p><p>A one-off manual backup looks like this:</p><pre class="ipsCode" data-language="bash">vzdump 101 --mode snapshot --storage backup-nfs --compress zstd --notes-template '{{guestname}} manual'</pre><h2>Scheduling, and why hand-picked lists go wrong</h2><p>Create jobs under Datacenter, Backup in the GUI. They're stored cluster-wide in <code>/etc/pve/jobs.cfg</code> and run on whichever node holds each guest. We usually run one nightly job for everything, outside business hours, selecting by pool or by "all" with exclusions instead of a hand-picked list of VM IDs. Hand-picked lists are how a new VM ends up never backed up.</p><p>Point the job's failure notifications at someone who actually reads that inbox. A backup job that's been failing silently for three weeks is the most common problem we find on inherited clusters.</p><h2>How long to keep things</h2><p>Retention is set with prune options on the job or on the storage. For most small environments we start here:</p><pre class="ipsCode" data-language="plaintext">keep-last=3, keep-daily=7, keep-weekly=4, keep-monthly=6</pre><p>On the command line the same thing is:</p><pre class="ipsCode" data-language="bash">vzdump 101 --storage backup-nfs --prune-backups keep-daily=7,keep-weekly=4,keep-monthly=6</pre><p>That gives you a week of dailies, a month of weeklies and half a year of monthlies. The monthlies are what save you when someone notices that data went missing back in the spring.</p><h2>Proxmox Backup Server is worth the extra box</h2><p>Backing up to a plain NFS share works, but every backup is a full compressed archive. PBS changes that in a few ways:</p><ul><li>Data is stored in chunks and identical chunks are stored once, so nightly backups of mostly unchanged VMs take a small fraction of the space.</li><li>With dirty bitmaps, a running VM only sends changed blocks after the first backup, so jobs finish much faster.</li><li>Verify jobs re-read chunks and check their checksums on a schedule, which catches silent corruption before you need the backup.</li><li>Sync jobs to a second PBS offsite give you the offsite copy in a 3-2-1 setup.</li><li>You also get file-level restore from the GUI, encryption, and per-user permissions.</li></ul><p>On PBS, schedule prune, garbage collection and verify jobs per datastore. Prune only marks old snapshots for removal. Garbage collection is what actually frees the space.</p><h2>Restore to a new ID and actually boot it</h2><p>A backup you've never restored is a hope, not a backup. Restore to a new VM ID so the production guest is left alone:</p><pre class="ipsCode" data-language="bash"># VM from a vzdump archive on file storage
qmrestore /mnt/pve/backup-nfs/dump/vzdump-qemu-101-2026_10_01-02_00_03.vma.zst 9101 --storage local-lvm --unique

# VM from PBS (volume ID as shown by pvesm list)
pvesm list pbs01 --vmid 101
qmrestore pbs01:backup/vm/101/2026-10-01T09:00:03Z 9101 --storage local-lvm --unique

# Container
pct restore 9201 /mnt/pve/backup-nfs/dump/vzdump-lxc-201-2026_10_01-02_10_44.tar.zst --storage local-lvm</pre><p>Before you boot the copy, take it off the network. Otherwise it can fight the production guest for its IP or start sending email.</p><pre class="ipsCode" data-language="bash">qm set 9101 --net0 virtio,bridge=vmbr0,link_down=1
qm start 9101</pre><p>When you rewrite <code>net0</code>, keep the NIC model and bridge that <code>qm config 9101</code> shows. Then open the console and confirm the OS boots, services start and the data is current. Delete the test guest when you're done.</p><div class="ipsRichTextBox" data-i-background-color="green"><div class="ipsRichTextBox__title"><p><strong>What a healthy setup looks like</strong></p></div><ul><li>The backup job's task log shows every expected guest with status OK.</li><li>The storage view lists backups at the retention points you expect.</li><li>On PBS, the last verify job is green.</li><li>You have a dated note of the last successful restore test for each important system.</li></ul></div><div class="ipsRichTextBox" data-i-background-color="orange"><div class="ipsRichTextBox__title"><p><strong>Mistakes we keep running into</strong></p></div><ul><li>Backups sitting on the same pool as the VMs. If Ceph or the RAID dies, both go together. Backup storage has to be independent.</li><li>Restoring over the original by accident. Restoring to the existing VM ID overwrites it, so always use a new ID unless you mean to replace production.</li><li>Duplicate MAC and IP. Without <code>--unique</code> and a disconnected NIC, a restored VM can knock the original off the network.</li><li>Trusting snapshot mode for databases. A filesystem freeze isn't the same as an application-consistent database backup, so run native dumps for critical databases as well.</li><li>A full PBS datastore. If garbage collection isn't running, pruned backups still take up space until the datastore fills.</li></ul></div><p>We restore something every month. If the dated note for an important system is more than a month old, that restore is the next thing on my list.</p>]]></description><guid isPermaLink="false">21</guid><pubDate>Sun, 18 Oct 2026 20:31:43 +0000</pubDate></item><item><title>We moved our whole cloud VPS fleet onto our own Proxmox cluster, and nobody noticed</title><link>https://wtmsp.com/topic/20-we-moved-our-whole-cloud-vps-fleet-onto-our-own-proxmox-cluster-and-nobody-noticed/</link><description><![CDATA[<p>Over a few years we'd piled up a fleet of cloud VPS instances: a monitoring box, a ticketing box, a couple of web hosts, a mail relay, and a scattering of one-off servers. Each was cheap on its own, but together they cost us about $334 a month. We consolidated everything onto our own Proxmox cluster in a colocation rack, and the hosting bill dropped to about $112 a month. That's roughly two thirds less, and it keeps heading toward near zero as the last stragglers move. Honestly, the bigger win for us was having one place to back up, monitor and patch. Here's the method that let us do it without the people we support seeing any downtime.</p><div class="ipsRichTextBox" data-i-background-color="blue"><div class="ipsRichTextBox__title"><p><strong>What we had in place first</strong></p></div><ul><li>A Proxmox cluster with enough capacity and a backup target</li><li>Routable public addresses at the new location. We use a small public block.</li><li>A router or firewall VM in front of the cluster. We use VyOS, and OPNsense or pfSense work too.</li><li>Control of DNS for every hostname being moved</li><li>A spreadsheet. Seriously.</li></ul></div><h2>Write down what every box really runs</h2><p>Before moving anything, log into every VPS and write down what it actually runs, not what you remember it running. For each box we captured:</p><ul><li>Listening ports and the processes behind them (<code>ss -tlnup</code>)</li><li>Every hostname pointing at it, from DNS and from web server configs</li><li>Cron jobs and systemd timers for every user</li><li>Databases, where their data lives, and how big they are</li><li>Inbound dependencies like webhooks, firewall allowlists at third parties, and API clients that connect by IP</li><li>Outbound dependencies like SMTP relays and API keys bound to source IPs</li></ul><p>The cron jobs and IP allowlists are what get you. A forgotten nightly report, or a vendor that only accepts connections from your old IP, won't show up until a week after cutover when someone asks where their report went.</p><h2>One container per service</h2><p>I get the urge to rebuild the old all-in-one boxes as they were. Resist it. We gave each service its own LXC container (or a VM where it needed one) with its own IP on an internal services VLAN, for example <code>10.10.20.0/24</code>. Now each service gets backed up, restored, upgraded and moved on its own, and the next migration becomes moving one small thing at a time.</p><h2>You own the firewall now</h2><p>Your VPS provider was giving you a firewall for free, even if you never thought about it. Now it's your job. We run a VyOS VM with the public uplink on one side and the internal VLANs on the other. The policy is simple. Inbound is dropped by default, established and related return traffic is allowed, and only the specific ports each published service needs are opened. Internal VLANs can reach the internet, but the internet reaches nothing unless a rule says so.</p><h2>A single reverse proxy for the web stuff</h2><p>Most of what we moved was HTTP. Instead of giving every web app a public IP, we run one reverse proxy container and forward ports 80 and 443 to it. Ours is Nginx Proxy Manager, though Caddy, Traefik or plain Nginx are just as good. It terminates TLS, holds the certificates, and routes by hostname to the right internal container, so one public IP can serve dozens of sites.</p><h2>1:1 NAT for the few that need their own IP</h2><p>Some things don't fit behind a shared HTTP proxy. Think mail servers that need matching reverse DNS, VPN endpoints, game or voice servers on fixed ports, and anything a third party allowlists by IP. For those we use 1:1 NAT on the router, mapping a dedicated public address to one internal container, with firewall rules limited to the ports that service uses. Keep this list short, because every 1:1 mapping is one more exposed surface to watch.</p><h2>Test the new side while DNS still points at the old one</h2><p>This is the step that made our cutovers boring, which is exactly what you want. Copy the data, bring the service up on the new side, and test it while DNS still points at the old server. Lower the DNS TTL to 300 seconds a day or two ahead so the eventual flip propagates quickly.</p><p>From inside the network, hit the backend directly with a Host header:</p><pre class="ipsCode" data-language="bash">curl -sI -H 'Host: app.contoso.com' http://10.10.20.15/</pre><p>Then test the full public path, including the router, reverse proxy and TLS certificate, without changing DNS. <code>--resolve</code> pins the hostname to the new public IP:</p><pre class="ipsCode" data-language="bash">curl -sv --resolve app.contoso.com:443:192.0.2.10 https://app.contoso.com/ -o /dev/null 2&gt;&amp;1 | grep -E 'HTTP/|subject:|expire'</pre><p>You can do the same thing in a browser by temporarily adding the name to your hosts file. Log in, click around, submit a form, and check that background jobs run. Only once it works end to end do we touch DNS.</p><h2>Flip DNS, but keep a way back</h2><p>For services with a database, do a final sync in a quiet window. Stop the app on the old side, take a last dump, restore it on the new side, then flip DNS. After that:</p><ul><li>Stop the old VPS. Don't delete it. A stopped instance can be started again in a minute if something was missed.</li><li>Take a provider snapshot of the old box as a second rollback and keep it until you're confident. Snapshots usually cost a fraction of a running instance.</li><li>If you can, watch the old box's logs for a few days before stopping it. Any traffic still arriving points you at a hard-coded IP or a forgotten client.</li></ul><div class="ipsRichTextBox" data-i-background-color="green"><div class="ipsRichTextBox__title"><p><strong>Signs the move actually stuck</strong></p></div><ul><li><code>dig +short app.contoso.com</code> returns the new address from several resolvers.</li><li>The certificate on the new proxy is valid and auto-renewing.</li><li>Cron jobs, scheduled reports and outbound email are firing from the new location.</li><li>Each new container shows up in your backup jobs and monitoring.</li></ul></div><div class="ipsRichTextBox" data-i-background-color="orange"><div class="ipsRichTextBox__title"><p><strong>Things that surprised us</strong></p></div><ul><li>Several integrations called our old server by IP rather than hostname. The access logs on the box we kept running a little longer are how we found them.</li><li>Apps behind the proxy suddenly see every request coming from the proxy's IP, which breaks rate limiting and fail2ban. Configure trusted proxies and <code>X-Forwarded-For</code> handling in each app.</li><li>A new public IP has no sending history and probably no matching PTR record. Fix reverse DNS and SPF before moving anything that sends mail.</li><li>Don't cancel the VPS account first. Cancel the instances, keep the snapshots, and close the account only once you no longer need them.</li></ul></div><p>Moving one small service at a time felt slow while we were doing it. Looking back, it's the reason nobody had to call us about it.</p>]]></description><guid isPermaLink="false">20</guid><pubDate>Sun, 18 Oct 2026 20:30:43 +0000</pubDate></item><item><title>Docker inside an unprivileged LXC on Proxmox, plus a TUN device for Tailscale</title><link>https://wtmsp.com/topic/19-docker-inside-an-unprivileged-lxc-on-proxmox-plus-a-tun-device-for-tailscale/</link><description><![CDATA[<p>Someone wants to try a self-hosted tool, and of course it ships as a Docker image. A whole VM for it feels like overkill, so most of the time we run Docker inside an LXC container on Proxmox instead. Containers start in seconds, share the host kernel and use far less RAM. It works well once the right features are turned on. Here's how we set it up, how we give a container a TUN device so Tailscale or WireGuard can run inside it, and when I give up and build a VM.</p><div class="ipsRichTextBox" data-i-background-color="blue"><div class="ipsRichTextBox__title"><p><strong>Before you start</strong></p></div><ul><li>A Proxmox VE host. We're on the 8.x and 9.x series.</li><li>A Debian or Ubuntu container template downloaded to local storage</li><li>Root shell access to the Proxmox host</li></ul></div><h2>Stay unprivileged</h2><p>In an unprivileged container, root inside the container maps to a high, unprivileged UID on the host (UID 100000 by default). If something breaks out, it lands on the host as a nobody user. In a privileged container, root inside is root on the host, so a container escape is a host compromise.</p><p>Privileged mode does avoid some friction, like bind-mounted host directories with odd ownership, certain device access, or NFS mounts inside the container. We still treat it as a last resort. If an app only works in privileged mode, that usually tells me it belongs in a VM.</p><h2>When I build a VM instead</h2><ul><li>The workload needs its own kernel, kernel modules or a different kernel version.</li><li>You need live migration without downtime. Containers restart when they migrate.</li><li>The service is internet-facing and untrusted, and you want the stronger isolation boundary.</li><li>The stack leans on Docker features that fight with nesting, or it's Kubernetes.</li><li>It's Windows, or anything else that isn't Linux.</li></ul><p>For a handful of internal services, Docker in LXC is fine, and we run plenty of them.</p><h2>Creating the container</h2><p>Two features matter. <code>nesting=1</code> lets the container create its own nested namespaces and cgroups, which Docker needs. <code>keyctl=1</code> allows the keyctl system call in unprivileged containers. Docker and runc use it, and without it you get failures that are confusing to track down.</p><pre class="ipsCode" data-language="bash">pveam update
pveam available --section system | grep debian-12
pveam download local debian-12-standard_12.7-1_amd64.tar.zst

pct create 210 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
  --hostname docker01 \
  --unprivileged 1 \
  --features nesting=1,keyctl=1 \
  --cores 2 --memory 2048 --swap 512 \
  --rootfs local-lvm:16 \
  --net0 name=eth0,bridge=vmbr0,ip=10.10.20.21/24,gw=10.10.20.1 \
  --onboot 1

pct start 210</pre><p>Use the template filename that <code>pveam available</code> actually lists, because the version suffix changes over time. For a container you already have, turn the features on under Options, Features in the GUI, or run <code>pct set 210 --features nesting=1,keyctl=1</code> and restart it.</p><h2>Installing Docker</h2><p>Get in with <code>pct enter 210</code> and install Docker from Docker's official repository using their documented Debian steps, or use the distribution's <code>docker.io</code> package if you prefer. Then run a test container:</p><pre class="ipsCode" data-language="bash">docker run --rm hello-world
docker info | grep -i 'storage driver'</pre><p>You want the <code>overlay2</code> storage driver. If you see <code>vfs</code>, every image layer is a full copy and disk use balloons. That used to be common on ZFS-backed container storage. Newer ZFS releases support overlayfs, so check what your host is running.</p><h2>Giving it /dev/net/tun</h2><p>Tailscale and WireGuard userspace implementations need <code>/dev/net/tun</code>, and unprivileged containers don't get it by default. Stop the container and add two lines to its config file on the host:</p><pre class="ipsCode" data-language="bash">pct stop 210
nano /etc/pve/lxc/210.conf</pre><pre class="ipsCode" data-language="ini">lxc.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file</pre><p>The first line allows access to the character device with major 10, minor 200, which is TUN. The second bind-mounts the host's device node into the container and creates the file if it doesn't exist. Start the container again and install Tailscale the normal way:</p><pre class="ipsCode" data-language="bash">pct start 210
pct enter 210
ls -l /dev/net/tun
curl -fsSL https://tailscale.com/install.sh | sh
tailscale up</pre><p>If the container is going to be a subnet router or exit node, also enable IP forwarding inside it (<code>net.ipv4.ip_forward=1</code> in a sysctl file) and approve the routes in the Tailscale admin console. That last bit is the part I always forget.</p><div class="ipsRichTextBox" data-i-background-color="green"><div class="ipsRichTextBox__title"><p><strong>Quick sanity checks</strong></p></div><ul><li><code>docker run --rm hello-world</code> finishes without permission errors.</li><li><code>docker info</code> shows <code>overlay2</code>.</li><li>Inside the container, <code>ls -l /dev/net/tun</code> shows a character device, and <code>tailscale status</code> shows the node online.</li><li>On the host, <code>pct config 210</code> shows <code>unprivileged: 1</code> and the features line.</li></ul></div><div class="ipsRichTextBox" data-i-background-color="orange"><div class="ipsRichTextBox__title"><p><strong>Where this has bitten us</strong></p></div><ul><li>Bind-mounted host folders show up as nobody:nogroup. That's UID mapping doing its job. Either chown the host directory to the mapped UID (container UID plus 100000) or set up a custom idmap. Please don't switch to privileged just to fix permissions.</li><li>Updates can break nesting. We've seen Docker, containerd or runc updates inside containers start failing with AppArmor or permission errors until the host side caught up. Take a <code>pct snapshot</code> before upgrading Docker in any container that matters.</li><li>vzdump snapshot mode doesn't quiesce databases running in Docker. For anything with a database, run its native dump before the backup or use stop mode.</li><li>Moving the container to another node restarts it, along with every Docker service inside.</li></ul></div><p>I try to keep it to one service per container. Once a single container is running twenty stacks, you've rebuilt a VM with weaker isolation, and that's usually my cue to move it to an actual VM.</p>]]></description><guid isPermaLink="false">19</guid><pubDate>Sun, 18 Oct 2026 20:29:43 +0000</pubDate></item><item><title>Giving an off-contract Nutanix block a second life as a Proxmox and Ceph cluster</title><link>https://wtmsp.com/topic/18-giving-an-off-contract-nutanix-block-a-second-life-as-a-proxmox-and-ceph-cluster/</link><description><![CDATA[<p>When the support contract ran out on a 4-node Nutanix block, the easy move would have been to recycle it. I'm glad we didn't. Those chassis are close to ideal Proxmox VE and Ceph hardware: identical nodes, SSD and HDD in every sled, dual 10GbE, and IPMI on each one. Ours now runs a good chunk of our internal workload. This is the order we built it in, plus what I'd plan differently next time.</p><div class="ipsRichTextBox" data-i-background-color="blue"><div class="ipsRichTextBox__title"><p><strong>Have these lined up first</strong></p></div><ul><li>The block itself. We used four nodes, and three is the minimum for a sane Ceph cluster.</li><li>IPMI access to each node for console and virtual media</li><li>The Proxmox VE ISO, on USB or mounted over IPMI</li><li>A switch with VLAN support, ideally 10GbE for Ceph</li><li>An IP plan for each network below, and working NTP</li></ul></div><h2>Networks first, because moving Ceph later hurts</h2><p>Changing Ceph networks after the fact is painful, so we settled them before installing anything. We use four logical networks, each on its own VLAN:</p><ul><li>Management (for example <code>10.10.10.0/24</code>) carries the Proxmox web UI, SSH and corosync. Corosync is latency-sensitive, so keep it off any link Ceph is saturating. A second corosync link on another network is cheap insurance.</li><li>Ceph public (<code>10.10.30.0/24</code>) is where monitors and clients talk to OSDs.</li><li>Ceph cluster (<code>10.10.40.0/24</code>) carries OSD-to-OSD replication and recovery. This is the link that gets hammered when a disk fails.</li><li>VM traffic rides a VLAN-aware bridge carrying the guest networks.</li></ul><p>On a two-port 10GbE node, we put Ceph public and cluster on one port, management and VM traffic on the other, and used the 1GbE ports as a corosync backup.</p><h2>Disks: Proxmox on the boot device, everything else to Ceph</h2><p>Nutanix nodes usually boot from a small SATADOM or M.2 device and keep the data drives behind an HBA. Install Proxmox on the boot device and leave every data disk for Ceph. Give each data disk its own OSD and don't put RAID underneath it, since Ceph is your redundancy layer.</p><p>Keep SSDs and HDDs in separate pools with separate CRUSH rules. If you mix them in one pool, the slowest disk sets the pace for everything. And set expectations with whoever is planning capacity: with the default 3-way replication, usable space is roughly a third of raw.</p><h2>Install every node, then ping everything</h2><p>Install from the ISO on each node with a unique hostname and a static management IP. After first boot, set up your repositories, update, and build the bridges and VLAN interfaces in <code>/etc/network/interfaces</code> or the GUI. Before you go further, make sure every node can ping every other node on the management, Ceph public and Ceph cluster networks. A VLAN typo is a lot easier to find now than after Ceph is running on top of it.</p><h2>Building the cluster</h2><p>On the first node:</p><pre class="ipsCode" data-language="bash">pvecm create hx-cluster</pre><p>Then join the rest using the first node's management IP:</p><pre class="ipsCode" data-language="bash">pvecm add 10.10.10.11
pvecm status</pre><p><code>pvecm status</code> should list every node and show <code>Quorate: Yes</code>. I join nodes one at a time and let each one settle before adding the next.</p><h2>Ceph packages, monitors and OSDs</h2><p>Install the Ceph packages on every node, then initialize the config once with both networks:</p><pre class="ipsCode" data-language="bash"># on every node
pveceph install --repository no-subscription

# once, on the first node
pveceph init --network 10.10.30.0/24 --cluster-network 10.10.40.0/24</pre><p>Monitors need a majority to keep quorum, so run an odd number of them. On four nodes that means three. Run these on three different nodes:</p><pre class="ipsCode" data-language="bash">pveceph mon create
pveceph mgr create</pre><p>Then create one OSD per data disk on each node. Ceph assigns the device class (<code>hdd</code>, <code>ssd</code> or <code>nvme</code>) by itself, and you can check it later with <code>ceph osd tree</code>.</p><pre class="ipsCode" data-language="bash">lsblk -o NAME,SIZE,ROTA,MODEL
pveceph osd create /dev/sdb
pveceph osd create /dev/sdc
# ...repeat for every data disk on every node</pre><div class="ipsRichTextBox" data-i-background-color="orange"><div class="ipsRichTextBox__title"><p><strong>If a disk refuses to become an OSD</strong></p></div><p>Disks from the previous platform still carry its metadata, and old partitions block OSD creation. Wipe them first with <code>ceph-volume lvm zap /dev/sdX --destroy</code>. Triple-check the device name before you hit enter.</p></div><h2>Pools split by disk type</h2><p>Create one replicated rule per device class, then a pool for each. The <code>--add_storages</code> flag registers the pool as Proxmox storage across the cluster.</p><pre class="ipsCode" data-language="bash">ceph osd crush rule create-replicated rule-ssd default host ssd
ceph osd crush rule create-replicated rule-hdd default host hdd

pveceph pool create vm-ssd --crush_rule rule-ssd --add_storages
pveceph pool create vm-hdd --crush_rule rule-hdd --add_storages</pre><p>Leave the defaults of <code>size=3</code> and <code>min_size=2</code>. We put VM boot disks and databases on the SSD pool and bulk data on the HDD pool.</p><p>Watch the order here. If you create a pool before your class-specific rules exist, it uses the default rule that spans every disk. You can change it later with <code>ceph osd pool set POOL crush_rule rule-ssd</code>, but that triggers data movement, so it's easier to set the rule when you create the pool.</p><h2>CephFS for the ISO pile</h2><p>RBD pools hold disk images, but ISOs, container templates and backups-in-transit need a filesystem. Put a metadata server on at least two nodes for failover, then create the filesystem:</p><pre class="ipsCode" data-language="bash">pveceph mds create   # on two or more nodes
pveceph fs create --name cephfs --add-storage</pre><p>Every node now sees the same ISO and template library. It's a small thing, but it's one of my favorite quality-of-life wins over local storage.</p><div class="ipsRichTextBox" data-i-background-color="green"><div class="ipsRichTextBox__title"><p><strong>Before you trust it with real VMs</strong></p></div><pre class="ipsCode" data-language="bash">ceph -s
ceph osd tree
ceph osd df
pvesm status</pre><p>You want <code>HEALTH_OK</code>, all monitors in quorum, every OSD <code>up</code> and <code>in</code>, and placement groups <code>active+clean</code>. Then break it on purpose. Migrate a VM between nodes, then reboot one node and watch <code>ceph -s</code> go degraded and recover on its own.</p></div><h2>A few more things I'd tell the next person</h2><ul><li>Update BMC and BIOS firmware while the nodes are still empty, and confirm the HBA presents disks directly to the OS.</li><li>Point chrony at a reliable NTP source on every node. Monitors complain about clock skew if the node clocks drift.</li><li>Don't fill it up. Ceph gets unhappy as OSDs approach the nearfull ratio. We plan to stay well under 80 percent so a node failure can re-replicate without hitting full.</li></ul><p>If I were doing it again, I'd spend even longer on the network plan, since that's the one decision here that's genuinely painful to undo.</p>]]></description><guid isPermaLink="false">18</guid><pubDate>Sun, 18 Oct 2026 20:28:43 +0000</pubDate></item></channel></rss>
