cargo.ayedo.cloud/ayedo/k8s/rook
Polycrate block for deploying and managing Rook-Ceph on Kubernetes. Provides S3-compatible object storage (RGW), block storage (RBD via CSI), and shared filesystems (CephFS via CSI). Includes monitoring integration for VictoriaMetrics and an RGW metrics exporter.
Helm Charts: rook-ceph + ceph-csi-drivers + rook-ceph-cluster from https://charts.rook.io/release / https://ceph.github.io/ceph-csi-operator
Chart version: v1.20.9 / ceph-csi-drivers 1.0.4 (Ceph v20.2.4 / Tentacle default — pin cluster.ceph_image.tag in production; see "Upgrade Path")
Kubernetes minimum: v1.31
Ceph minimum: v19.2.0
Block version: 0.8.1
Architecture
The block is split into three independent instances in workspace.poly — operator, CSI drivers, cluster. This mirrors how Rook itself separates operator, CSI-driver and cluster lifecycle since chart 1.20.0:
rook-operator → installs rook-ceph Helm chart (operator + ceph-csi-operator subchart)
rook-csi-drivers → installs ceph-csi-drivers Helm chart (RBD/CephFS Driver CRs — REQUIRED since 1.20.0)
rook-cluster → installs rook-ceph-cluster Helm chart (CephCluster CRD + pools + objectstore)
All three instances use the same block (cargo.ayedo.cloud/ayedo/k8s/rook) but activate different parts via operator.enabled, csi_drivers.enabled and cluster.enabled. Install order matters and is Rook-mandated: rook-operator → rook-csi-drivers → rook-cluster (declare them in this order in workspace.poly).
Helm release naming: the operator and cluster Helm releases are named after the instance (
block.name, e.g.rook-operator,rook-cluster). Theceph-csi-driversrelease is always named<instance-name>-csi-driversinstead —operator,csi_driversandclusterall default to the same namespace (rook-ceph), so a bareblock.namerelease forcsi_driverswould collide with (and overwrite) the operator's Helm release if both were ever enabled on one instance instead of split as shown above.
Actions
| Action | Description |
|---|---|
install |
Install/upgrade operator and/or cluster including all monitoring and exporter resources |
install-exporter |
Deploy only the RGW metrics exporter (standalone) |
uninstall |
Remove Helm releases (cluster first, then operator) |
status |
Show Helm release status, CephCluster health, and OSD summary |
render |
Render K8s manifests to disk for ArgoCD (see below) |
polycrate run rook-operator install
polycrate run rook-csi-drivers install
polycrate run rook-cluster install
polycrate run rook-cluster status
polycrate run rook-cluster render
render action
Renders all non-Helm Kubernetes manifests (VMServiceScrape, VMPodScrape, Ingress, Exporter, Buckets) to:
<workspace>/<artifactsroot>/secrets/<block-name>/manifests/
The secrets/ path is intentional — rendered manifests may contain sensitive values. This directory must be excluded from git (add to .gitignore). The render action is primarily used for ArgoCD integration where manifests are generated ephemerally and applied directly.
Never commit the rendered manifests to git. Helm values and cluster config are managed via workspace.poly — the rendered output is ephemeral.
Minimal Setup (workspace.poly)
- name: rook-operator
from: cargo.ayedo.cloud/ayedo/k8s/rook:0.8.0
kubeconfig:
from: k8s
config:
operator:
enabled: true
- name: rook-csi-drivers
from: cargo.ayedo.cloud/ayedo/k8s/rook:0.8.0
kubeconfig:
from: k8s
config:
csi_drivers:
enabled: true
- name: rook-cluster
from: cargo.ayedo.cloud/ayedo/k8s/rook:0.8.0
kubeconfig:
from: k8s
config:
object_store:
hosting:
dns_names:
- s3.example.com
cluster:
enabled: true
spec:
storage:
use_all_nodes: true
use_all_devices: true
Configuration Reference
operator.*
| Key | Default | Description |
|---|---|---|
operator.enabled |
false |
Deploy the Rook operator Helm chart |
operator.namespace |
rook-ceph |
Namespace for operator deployment |
operator.chart.version |
1.20.1 |
Helm chart version |
operator.node_selector |
{} |
Node selector for the operator pod |
operator.discovery_daemon.enabled |
false |
Enable the rook-discover DaemonSet (OSD device discovery). Unrelated to CSI drivers — renamed from operator.csi.discovery_daemon.enabled in 0.7.0, see CHANGELOG |
operator.monitoring.vmpodscrape.enabled |
true |
Deploy VMPodScrape for operator metrics |
operator.obc_allow_additional_config_fields |
"maxObjects,maxSize" |
Comma-separated list of allowed OBC config fields |
csi_drivers.* (since 0.7.0 / chart 1.20.0)
Since Rook 1.20.0, CSI drivers (RBD/CephFS) are no longer deployed by the rook-ceph
chart itself — a separate, required ceph-csi-drivers chart configures them via
Driver/OperatorConfig CRs, reconciled by the ceph-csi-operator (bundled as a
subchart of operator). Must be installed after operator, before cluster.
| Key | Default | Description |
|---|---|---|
csi_drivers.enabled |
false |
Deploy the ceph-csi-drivers Helm chart |
csi_drivers.namespace |
rook-ceph |
Namespace (must match operator.namespace) |
csi_drivers.chart.version |
1.0.1 |
Pinned to the ceph-csi-operator chart version that rook-ceph 1.20.1 bundles internally as a subchart dependency |
csi_drivers.drivers.rbd.enabled |
true |
Enable the RBD CSI driver (replaces old operator.csi.rdb_driver.disabled) |
csi_drivers.drivers.rbd.name |
rook-ceph.rbd.csi.ceph.com |
RBD provisioner name — must stay prefixed with operator.namespace |
csi_drivers.drivers.cephfs.enabled |
true |
Enable the CephFS CSI driver (replaces old operator.csi.cephfs_driver.disabled) |
csi_drivers.drivers.cephfs.name |
rook-ceph.cephfs.csi.ceph.com |
CephFS provisioner name — must stay prefixed with operator.namespace |
csi_drivers.drivers.nfs.enabled |
false |
Enable the NFS CSI driver |
There is no single "disable all CSI drivers" toggle anymore (old
operator.csi.csi_driver.disabled) — disabledrivers.rbd.enabledanddrivers.cephfs.enabledindividually if S3-only (see Scenario H).
cluster.*
| Key | Default | Description |
|---|---|---|
cluster.enabled |
false |
Deploy the rook-ceph-cluster Helm chart |
cluster.namespace |
rook-ceph |
Namespace for CephCluster |
cluster.chart.version |
1.20.1 |
Helm chart version |
cluster.toolbox.enabled |
false |
Deploy Ceph toolbox pod (useful for debugging) |
cluster.monitoring.enabled |
false |
Enable chart-managed monitoring (creates Service for MGR metrics — required for VMServiceScrape) |
cluster.monitoring.vmservicescrape.enabled |
false |
Deploy VMServiceScrape for MGR and Ceph exporter |
cluster.monitoring.csi.vmpodscrape.enabled |
false |
Deploy VMPodScrape for CSI driver metrics |
cluster.spec.mon.count |
3 |
Number of mon daemons. Set to 1 for single-node clusters |
cluster.spec.mon.allow_multiple_per_node |
false |
Allow multiple mons on the same node (only needed if count > number of nodes) |
cluster.spec.mgr.count |
2 |
Number of active MGR daemons. Set to 1 for single-node clusters (anti-affinity blocks the second pod) |
cluster.spec.mgr.allow_multiple_per_node |
false |
Allow multiple MGRs on the same node |
cluster.spec.mgr.modules.rook.enabled |
false |
Enable the rook Ceph mgr module. Rook recommends leaving this off; disable it before a Tentacle upgrade. |
cluster.spec.security.cephx.daemon.key_rotation_policy |
Disabled |
Disabled or KeyGeneration. KeyGeneration rotates daemon CephX keys to aes256k (Rook >= 1.20.6, Ceph >= 20.2.4, CVE-2025-30156). Restarts all daemons rolling. |
cluster.spec.security.cephx.daemon.key_generation |
0 |
Used with KeyGeneration. Must be greater than status.cephx.*.keyGeneration. Cannot be decreased. |
cluster.spec.security.cephx.csi.key_type |
aes |
CSI client key cipher. Keep aes unless every node runs kernel >= 7.0 (aes256k). |
cluster.spec.dashboard.enabled |
false |
Enable Ceph dashboard |
cluster.spec.dashboard.ssl |
false |
Enable SSL on dashboard |
cluster.spec.dashboard.prometheus_endpoint |
— | Prometheus/VictoriaMetrics endpoint for dashboard graphs |
cluster.spec.network.encryption.enabled |
false |
Enable Ceph messenger v2 encryption |
cluster.spec.network.compression.enabled |
false |
Enable Ceph messenger v2 compression |
cluster.spec.storage.use_all_nodes |
false |
Use all cluster nodes as OSD hosts |
cluster.spec.storage.use_all_devices |
false |
Use all available block devices as OSDs |
cluster.spec.storage.device_filter |
"" |
Regex to select OSD devices (e.g. ^sd[b-z]) |
cluster.spec.storage.metadata_device |
"" |
Device/partition for BlueStore DB+WAL (e.g. /dev/sdb1) |
cluster.pools_default.size |
"" |
Default replication factor for all auto-created Ceph pools (.mgr, device_health_metrics, etc.). Injected into cephConfig.global.osd_pool_default_size. Set to "1" for single-node clusters. Empty = Ceph default (3) |
cluster.pools_default.min_size |
"" |
Minimum number of replicas required to accept writes. Injected into cephConfig.global.osd_pool_default_min_size. Set to "1" for single-node clusters. Empty = Ceph default (2) |
cluster.ceph_image.repository |
chart default | Override Ceph container image repository |
cluster.ceph_image.tag |
chart default (v20.2.4 / Tentacle for chart v1.20.9) |
Override Ceph container image tag. Pin explicitly in production — a chart bump otherwise starts a Ceph major upgrade. Use a timestamped tag such as v20.2.4-20260818. |
cluster.ceph_config |
{} |
Raw Ceph config map — see below |
cluster.block_pools |
[] |
List of RBD block pools with StorageClasses |
cluster.file_systems |
[] |
List of CephFS filesystems with StorageClasses |
cluster.ingress.enabled |
false |
Expose RGW via Ingress |
object_store.*
| Key | Default | Description |
|---|---|---|
object_store.metadata_pool.failure_domain |
host |
Failure domain for the metadata pool (host, osd). Use osd for single-node |
object_store.metadata_pool.replicated.size |
3 |
Replication factor for the metadata pool. Use 1 for single-node |
object_store.data_pool.failure_domain |
host |
Failure domain for the data pool. Use osd for single-node |
object_store.data_pool.type |
erasure_coded |
Pool type: erasure_coded (efficient, needs ≥3 OSDs) or replicated (works with 1+ OSD) |
object_store.data_pool.replicated.size |
3 |
Replication factor when type: replicated. Use 1 for single-node |
object_store.data_pool.erasure_coded.data_chunks |
2 |
Data chunks for erasure coding (used when type: erasure_coded) |
object_store.data_pool.erasure_coded.coding_chunks |
1 |
Coding (parity) chunks for erasure coding. Total OSDs needed = data_chunks + coding_chunks |
object_store.gateway.instances |
1 |
Number of RGW gateway instances (set ≥ 2 for HA) |
object_store.gateway.placement.node_selector |
{} |
Free nodeAffinity map. The block defines no label key. Empty keeps default scheduling |
object_store.gateway.tls.enabled |
false |
Terminate TLS on beast (securePort = service.port, default 443) |
object_store.gateway.tls.issuer |
letsencrypt-production |
cert-manager ClusterIssuer |
object_store.gateway.tls.secret_name |
"" |
TLS secret name. Required when TLS is enabled |
object_store.gateway.tls.dns_names |
[] |
Certificate DNS names. Required when TLS is enabled |
object_store.gateway.tls.wildcard |
false |
Also request *.<name> for each DNS name |
object_store.gateway.service.enabled |
false |
Create a LoadBalancer Service beside the operator ClusterIP |
object_store.gateway.service.port |
443 |
Public port and beast secure port. Keep 443 so existing URLs do not change |
object_store.gateway.service.external_traffic_policy |
Local |
Local preserves the client IP. Every node that can own the address needs an RGW pod |
object_store.gateway.service.external_ips |
[] |
Keepalived / bare-metal addresses. Mutually exclusive with loadbalancer_ip |
object_store.gateway.service.loadbalancer_ip |
"" |
Cilium LB-IPAM, MetalLB or Octavia pin. Sets loadBalancerIP, lbipam.cilium.io/ips and metallb.universe.tf/loadBalancerIPs |
object_store.gateway.service.annotations |
{} |
Extra Service annotations, for a different IPAM key |
object_store.hosting.dns_names |
[] |
Public S3 hostnames for the object store |
exporter.*
The RGW exporter collects per-bucket S3 metrics (request counts, bytes transferred, user quotas).
| Key | Default | Description |
|---|---|---|
exporter.enabled |
true |
Deploy rgw-exporter |
exporter.credentials_wait_retries |
120 |
Max retries for RGW credentials secret (Ceph OSD prepare can take 10+ min) |
exporter.credentials_wait_delay |
5 |
Seconds between retries (total wait = retries × delay) |
exporter.region |
"" |
Region label for metrics |
exporter.cluster_name |
"" |
Cluster label (defaults to workspace.name) |
exporter.pub_endpoint |
"" |
Public S3 endpoint label |
exporter.intervals.usage |
30 |
Scrape interval for usage stats (seconds) |
exporter.intervals.buckets |
300 |
Scrape interval for bucket list |
exporter.intervals.users |
600 |
Scrape interval for user list |
exporter.users_collector.enabled |
true |
Collect per-user metrics |
exporter.skip_without_bucket |
false |
Skip users without buckets |
exporter.insecure |
true |
Skip TLS verification for RGW connection |
exporter.vmservicescrape.enabled |
true |
Deploy VMServiceScrape for exporter |
buckets[]
Pre-create S3 buckets via ObjectBucketClaims:
buckets:
- name: my-bucket
storage_class_name: ceph-bucket
namespace: my-app
additional_config: {}
OSD Device Selection
Rook needs raw, unformatted block devices for OSDs. Devices with existing partitions or filesystems are skipped automatically.
Option 1: All available devices on all nodes
cluster:
spec:
storage:
use_all_nodes: true
use_all_devices: true
Option 2: Regex filter (recommended for production)
Select specific devices by name pattern. The filter is a Go regex applied to the device name (without /dev/).
cluster:
spec:
storage:
use_all_nodes: true
use_all_devices: false
device_filter: "^sd[b-z]" # all sdb, sdc, ... sdz
# device_filter: "^nvme[0-9]n1" # all NVMe drives
# device_filter: "^sd[c-i]" # sdc through sdi
Option 3: BlueStore DB/WAL on a separate device
BlueStore separates the block data (slow HDD) from metadata (DB) and write-ahead-log (WAL). Putting DB+WAL on an SSD or NVMe dramatically improves random write latency even in an HDD-only cluster.
If you have a partition available on a faster device (e.g. carved from an OS disk or a dedicated SSD), configure:
cluster:
spec:
storage:
use_all_nodes: true
use_all_devices: false
device_filter: "^sd[c-z]"
metadata_device: /dev/sdb1 # pre-partitioned, same path on ALL OSD nodes
Prerequisites: The partition must exist on every OSD node before running
install. Rook does not create the partition — use your OS provisioning tooling (Ansible, cloud-init) to create it first.Sizing: Target ~4GB per OSD for the DB partition. A single 128GB partition can serve ~30 OSDs comfortably.
Storage Backends
S3 Object Storage (RGW)
Always deployed. The block creates a single CephObjectStore named ceph-objectstore with:
- Metadata pool: replicated 3x
- Data pool: erasure coded 2+1 (2 data chunks + 1 coding chunk)
- StorageClass: ceph-bucket (for ObjectBucketClaims)
Configure the public endpoint and gateway redundancy:
object_store:
gateway:
instances: 2 # ≥2 for HA
hosting:
dns_names:
- s3.example.com
- objects.example.com
To publish RGW without an Ingress, turn TLS and the LoadBalancer Service on and leave cluster.ingress.enabled off. Use external_ips where Keepalived already holds the address, or loadbalancer_ip where Cilium LB-IPAM (or MetalLB) assigns it. Do not set both. The public port stays 443.
With external_traffic_policy: Local, only nodes that run an RGW pod accept traffic. Pin those pods with placement.node_selector to every node that can own the address. The client TCP session then ends on the RGW pod, so the node sysctls apply: net.ipv4.tcp_wmem should be 4096 1048576 16777216 (1 MB default, 16 MB max) and net.ipv4.tcp_congestion_control should be bbr. The kernel default 4096 16384 4194304 caps a single stream at high RTT. Pods pick up sysctls only when they start. The block does not set them; the host tcp-tuning does. Confirm them inside a fresh RGW pod before shifting the address off the Ingress.
RBD Block Storage (Kubernetes PVCs)
Add one or more block pools. Each pool gets its own StorageClass.
cluster:
block_pools:
- name: ceph-blockpool
failure_domain: host # data replicated across different hosts
replicated:
size: 3 # 3 replicas
storage_class:
enabled: true
name: ceph-block # StorageClass name
is_default: false # set true to make default StorageClass
reclaim_policy: Delete
allow_volume_expansion: true
fstype: ext4 # ext4 or xfs
failure_domain values: host (default), rack, zone, osd
CephFS Shared Filesystem (RWX volumes)
CephFS supports ReadWriteMany — multiple pods can mount the same volume simultaneously.
cluster:
file_systems:
- name: ceph-filesystem
failure_domain: host
metadata_pool:
replicated:
size: 3
data_pools:
- name: data0
failure_domain: host
replicated:
size: 3
mds:
active_count: 1 # active MDS daemons
active_standby: true # keep a standby MDS for fast failover
storage_class:
enabled: true
name: ceph-filesystem
is_default: false
reclaim_policy: Delete
allow_volume_expansion: true
Setup Scenarios
Scenario A: 3-Node HDD Cluster (S3 + Block Storage)
Minimum viable production setup. All three nodes are both OSD hosts and control plane nodes.
Requirements:
- 3 nodes, each with ≥1 dedicated data disk (e.g. /dev/sdb)
- 10G network between nodes recommended
- name: rook-operator
from: cargo.ayedo.cloud/ayedo/k8s/rook:0.8.0
kubeconfig:
from: k8s
config:
operator:
enabled: true
discovery_daemon:
enabled: true
monitoring:
vmpodscrape:
enabled: true
- name: rook-csi-drivers
from: cargo.ayedo.cloud/ayedo/k8s/rook:0.8.0
kubeconfig:
from: k8s
config:
csi_drivers:
enabled: true
- name: rook-cluster
from: cargo.ayedo.cloud/ayedo/k8s/rook:0.8.0
kubeconfig:
from: k8s
config:
object_store:
gateway:
instances: 2
hosting:
dns_names:
- s3.example.com
cluster:
enabled: true
toolbox:
enabled: true
monitoring:
enabled: true
vmservicescrape:
enabled: true
csi:
vmpodscrape:
enabled: true
spec:
storage:
use_all_nodes: true
use_all_devices: false
device_filter: "^sdb"
ceph_config:
global:
osd_recovery_sleep_hdd: "0.1"
osd_scrub_sleep: "0.1"
osd_memory_target: "4294967296"
block_pools:
- name: ceph-blockpool
failure_domain: host
replicated:
size: 3
storage_class:
name: ceph-block
is_default: true
fstype: ext4
Scenario B: 3-Node HDD Cluster with RGW Usage Metrics
Extends Scenario A with per-bucket S3 metrics (requires RGW usage logging enabled in Ceph).
cluster:
ceph_config:
global:
osd_recovery_sleep_hdd: "0.1"
osd_scrub_sleep: "0.1"
osd_memory_target: "4294967296"
"client.rgw":
rgw_enable_usage_log: "true"
rgw_usage_log_tick_interval: "30"
rgw_usage_log_flush_threshold: "1024"
exporter:
region: "fra1"
pub_endpoint: "https://s3.example.com"
vmservicescrape:
enabled: true
Scenario C: 10-Node HDD Cluster (High Availability)
More OSD nodes allow using rack as failure domain (requires nodes spread across ≥3 racks). Increase replication and use erasure coding for data pools.
cluster:
spec:
storage:
use_all_nodes: true
use_all_devices: false
device_filter: "^sd[b-h]" # up to 7 OSDs per node
block_pools:
- name: ceph-blockpool
failure_domain: rack # tolerate full rack loss
replicated:
size: 3
storage_class:
name: ceph-block
fstype: ext4
ceph_config:
global:
osd_recovery_sleep_hdd: "0.1"
osd_scrub_sleep: "0.05"
osd_memory_target: "4294967296"
Scenario D: SSD/NVMe Cluster
For all-flash clusters, disable recovery sleep throttling and use xfs for better NVMe performance.
cluster:
spec:
storage:
use_all_nodes: true
use_all_devices: false
device_filter: "^nvme[0-9]n1"
block_pools:
- name: ceph-blockpool
failure_domain: host
replicated:
size: 3
storage_class:
name: ceph-block
is_default: true
fstype: xfs # xfs performs better on NVMe
ceph_config:
global:
osd_recovery_sleep_ssd: "0.0" # no throttling on SSDs
osd_scrub_sleep: "0.0"
osd_memory_target: "4294967296"
Scenario E: Mixed HDD+SSD Cluster (BlueStore DB/WAL separation)
HDDs for data, SSD partition for BlueStore DB+WAL. This dramatically improves random write latency.
Pre-requisite: Create the DB partition on all OSD nodes before running install:
# On each OSD node: create a 128G partition on the SSD
parted /dev/sdb mklabel gpt
parted /dev/sdb mkpart primary 0% 100%
# Result: /dev/sdb1 available as metadata device
cluster:
spec:
storage:
use_all_nodes: true
use_all_devices: false
device_filter: "^sd[c-z]" # data OSDs on HDDs
metadata_device: /dev/sdb1 # SSD partition for DB+WAL
ceph_config:
global:
osd_recovery_sleep_hdd: "0.1"
osd_scrub_sleep: "0.1"
osd_memory_target: "4294967296"
Scenario F: External Ceph Cluster (Consumer Mode)
Rook can operate as a consumer of an existing, externally managed Ceph cluster (e.g. managed by cephadm). In this mode, Rook provides the CSI drivers and Kubernetes integration but does not manage OSDs or Ceph daemons.
How it works
The block replaces the import-external-cluster.sh script entirely. You provide the credentials from create-external-cluster-resources.py directly in workspace.poly, and the block creates all required Kubernetes resources in the correct order.
Why this matters for CSI (important): The Rook CSI driver uses two different secrets depending on the operation:
- Attach (ControllerPublishVolume): uses the secret referenced in the StorageClass parameters
- Detach (ControllerUnpublishVolume): uses controllerPublishSecretRef from the ceph-csi-config ConfigMap — always the standard short names rook-csi-rbd-provisioner / rook-csi-cephfs-provisioner
If these standard-named secrets don't exist (e.g. because import-external-cluster.sh was run with --restricted-auth-permission, which produces long non-standard names), volume detach fails silently. The block always creates the standard-named secrets and a StorageClass that includes controller-publish-secret-name for correct fencing/detach behaviour.
Step 1: Generate credentials on the external Ceph cluster
Run create-external-cluster-resources.py on the provider Ceph cluster:
python3 create-external-cluster-resources.py \
--format bash \
--rbd-data-pool-name <your-rbd-pool> \
--cephfs-filesystem-name <your-fs> \ # omit if no CephFS
--skip-monitoring-endpoint
Note on
--restricted-auth-permission: You may use this flag. The script-producedCSI_*_SECRET_NAMEvalues (which are the CephX usernames) will be longer (e.g.csi-rbd-provisioner-<cluster>-<pool>). Use those exact values asuser_idin the block config. The Kubernetes secret name is always the standard short form (rook-csi-rbd-provisioner) — the block enforces this regardless ofuser_id.
Step 2: Configure the block
- name: rook-operator
from: cargo.ayedo.cloud/ayedo/k8s/rook:0.8.0
kubeconfig:
from: k8s
config:
operator:
enabled: true
discovery_daemon:
enabled: true
monitoring:
vmpodscrape:
enabled: true
- name: rook-csi-drivers
from: cargo.ayedo.cloud/ayedo/k8s/rook:0.8.0
kubeconfig:
from: k8s
config:
csi_drivers:
enabled: true
- name: rook-cluster
from: cargo.ayedo.cloud/ayedo/k8s/rook:0.8.0
kubeconfig:
from: k8s
config:
cluster:
enabled: true
spec:
external:
enabled: true # consumer mode: no local OSDs
crash_collector:
enabled: false # no local daemons to monitor
storage:
use_all_nodes: false
use_all_devices: false
external_cluster:
fsid: "<ROOK_EXTERNAL_FSID>"
mon_data: "<mon-a=ip1:6789,mon-b=ip2:6789,...>" # ROOK_EXTERNAL_CEPH_MON_DATA
external_username: "<ROOK_EXTERNAL_USERNAME>"
user_secret: "<ROOK_EXTERNAL_USER_SECRET>"
rbd_pool_name: "<RBD_POOL_NAME>"
rgw_pool_prefix: "default" # RGW_POOL_PREFIX
# StorageClass for the external RBD pool (replaces bash script StorageClass creation)
storage_class:
enabled: true
name: ceph-block
is_default: true
reclaim_policy: Delete
allow_volume_expansion: true
fstype: ext4
# Optional: RGW admin ops user (only if S3/RGW is used)
# rgw_admin_ops_user:
# access_key: "<RGW_ADMIN_OPS_USER_ACCESS_KEY>"
# secret_key: "<RGW_ADMIN_OPS_USER_SECRET_KEY>"
csi_secrets:
rbd_provisioner:
user_id: "<CSI_RBD_PROVISIONER_SECRET_NAME>" # CephX username; same value as secret name suffix
user_key: "<CSI_RBD_PROVISIONER_SECRET>"
rbd_node:
user_id: "<CSI_RBD_NODE_SECRET_NAME>"
user_key: "<CSI_RBD_NODE_SECRET>"
cephfs_provisioner:
user_id: "" # CSI_CEPHFS_PROVISIONER_SECRET_NAME — leave empty if no CephFS
user_key: "" # CSI_CEPHFS_PROVISIONER_SECRET
cephfs_node:
user_id: "" # CSI_CEPHFS_NODE_SECRET_NAME — leave empty if no CephFS
user_key: "" # CSI_CEPHFS_NODE_SECRET
Credential mapping: script output → block config
| Script variable | block config field |
|---|---|
ROOK_EXTERNAL_FSID |
external_cluster.fsid |
ROOK_EXTERNAL_CEPH_MON_DATA |
external_cluster.mon_data |
ROOK_EXTERNAL_USERNAME |
external_cluster.external_username |
ROOK_EXTERNAL_USER_SECRET |
external_cluster.user_secret |
RBD_POOL_NAME |
external_cluster.rbd_pool_name |
RGW_POOL_PREFIX |
external_cluster.rgw_pool_prefix |
RGW_ADMIN_OPS_USER_ACCESS_KEY |
external_cluster.rgw_admin_ops_user.access_key |
RGW_ADMIN_OPS_USER_SECRET_KEY |
external_cluster.rgw_admin_ops_user.secret_key |
CSI_RBD_PROVISIONER_SECRET_NAME |
external_cluster.csi_secrets.rbd_provisioner.user_id |
CSI_RBD_PROVISIONER_SECRET |
external_cluster.csi_secrets.rbd_provisioner.user_key |
CSI_RBD_NODE_SECRET_NAME |
external_cluster.csi_secrets.rbd_node.user_id |
CSI_RBD_NODE_SECRET |
external_cluster.csi_secrets.rbd_node.user_key |
CSI_CEPHFS_PROVISIONER_SECRET_NAME |
external_cluster.csi_secrets.cephfs_provisioner.user_id |
CSI_CEPHFS_PROVISIONER_SECRET |
external_cluster.csi_secrets.cephfs_provisioner.user_key |
CSI_CEPHFS_NODE_SECRET_NAME |
external_cluster.csi_secrets.cephfs_node.user_id |
CSI_CEPHFS_NODE_SECRET |
external_cluster.csi_secrets.cephfs_node.user_key |
user_id: The Python script setsCSI_*_SECRET_NAMEas the CephX username and simultaneously uses it as the Kubernetes secret name suffix. The block stores this value as theuserIDfield inside the K8s secret. The K8s secret name is always the standard short form (rook-csi-rbd-provisioner) — enforced by the block regardless of theuser_idvalue.
What the block creates
Before the Helm chart:
rook-ceph-monSecret — operator credentials (fromexternal_username+user_secret)rook-ceph-mon-endpointsConfigMap — monitor addresses (frommon_data)rgw-admin-ops-userSecret — optional, only ifrgw_admin_ops_user.access_keyis set
After the Helm chart:
rook-csi-rbd-provisionerSecret — required for RBD CSI attach and detachrook-csi-rbd-nodeSecret — required for RBD CSI node stage operationsrook-csi-cephfs-provisionerSecret — optional, only ifcephfs_provisioner.user_keyis setrook-csi-cephfs-nodeSecret — optional, only ifcephfs_node.user_keyis set- StorageClass (
external_cluster.storage_class.name) — RBD StorageClass pointing torbd_pool_name, with all CSI secret refs includingcontroller-publish-secret-namefor correct detach
No
block_poolsneeded for external clusters. The StorageClass is created directly fromexternal_cluster.rbd_pool_name+external_cluster.storage_class.block_poolsis for internal clusters only.
Scenario F1: External cluster, RBD only (no CephFS)
external_cluster:
fsid: "<ROOK_EXTERNAL_FSID>"
mon_data: "<ROOK_EXTERNAL_CEPH_MON_DATA>"
external_username: "<ROOK_EXTERNAL_USERNAME>"
user_secret: "<ROOK_EXTERNAL_USER_SECRET>"
rbd_pool_name: "<RBD_POOL_NAME>"
storage_class:
enabled: true
name: ceph-block
is_default: true
fstype: ext4
csi_secrets:
rbd_provisioner:
user_id: "<CSI_RBD_PROVISIONER_SECRET_NAME>"
user_key: "<CSI_RBD_PROVISIONER_SECRET>"
rbd_node:
user_id: "<CSI_RBD_NODE_SECRET_NAME>"
user_key: "<CSI_RBD_NODE_SECRET>"
cephfs_provisioner:
user_id: ""
user_key: ""
cephfs_node:
user_id: ""
user_key: ""
Scenario F2: External cluster with RBD + CephFS + RGW
external_cluster:
fsid: "<ROOK_EXTERNAL_FSID>"
mon_data: "<ROOK_EXTERNAL_CEPH_MON_DATA>"
external_username: "<ROOK_EXTERNAL_USERNAME>"
user_secret: "<ROOK_EXTERNAL_USER_SECRET>"
rbd_pool_name: "<RBD_POOL_NAME>"
rgw_pool_prefix: "default"
storage_class:
enabled: true
name: ceph-block
is_default: true
fstype: ext4
rgw_admin_ops_user:
access_key: "<RGW_ADMIN_OPS_USER_ACCESS_KEY>"
secret_key: "<RGW_ADMIN_OPS_USER_SECRET_KEY>"
csi_secrets:
rbd_provisioner:
user_id: "<CSI_RBD_PROVISIONER_SECRET_NAME>"
user_key: "<CSI_RBD_PROVISIONER_SECRET>"
rbd_node:
user_id: "<CSI_RBD_NODE_SECRET_NAME>"
user_key: "<CSI_RBD_NODE_SECRET>"
cephfs_provisioner:
user_id: "<CSI_CEPHFS_PROVISIONER_SECRET_NAME>"
user_key: "<CSI_CEPHFS_PROVISIONER_SECRET>"
cephfs_node:
user_id: "<CSI_CEPHFS_NODE_SECRET_NAME>"
user_key: "<CSI_CEPHFS_NODE_SECRET>"
Scenario G: Single-Node Cluster
For development, edge, or lab setups where only one Kubernetes node is available. Several Rook defaults are tuned for multi-node HA and must be reduced for a single-node deployment:
| Component | Problem | Fix |
|---|---|---|
| Mon | 3 mons required, only 1 node → scheduling fails | mon.count: 1 |
| MGR | 2 MGRs with anti-affinity → second pod stays Pending | mgr.count: 1 |
| Internal Ceph pools | .mgr, device_health_metrics auto-created with size 3 → degraded PGs |
pools_default.size: "1", pools_default.min_size: "1" |
| Object store metadata pool | replicated.size: 3 needs 3 hosts |
failure_domain: osd, size: 1 |
| Object store data pool | erasure coding (2+1) needs ≥3 OSDs | type: replicated, failure_domain: osd, size: 1 |
| RBD block pool | failure_domain: host, size: 3 needs 3 hosts |
failure_domain: osd, size: 1 |
| RGW gateway | instances: 2 for HA — optional to reduce |
instances: 1 |
Note: A single-node cluster has no redundancy at any level. Any node or daemon failure results in downtime. Suitable for non-production use only.
- name: rook-operator
from: cargo.ayedo.cloud/ayedo/k8s/rook:0.8.0
kubeconfig:
from: k8s
config:
operator:
enabled: true
- name: rook-csi-drivers
from: cargo.ayedo.cloud/ayedo/k8s/rook:0.8.0
kubeconfig:
from: k8s
config:
csi_drivers:
enabled: true
- name: rook-cluster
from: cargo.ayedo.cloud/ayedo/k8s/rook:0.8.0
kubeconfig:
from: k8s
config:
object_store:
metadata_pool:
failure_domain: osd
replicated:
size: 1
data_pool:
failure_domain: osd
type: replicated
replicated:
size: 1
gateway:
instances: 1
hosting:
dns_names:
- s3.example.com
cluster:
enabled: true
pools_default:
size: "1" # sets osd_pool_default_size — affects .mgr, device_health_metrics, etc.
min_size: "1" # sets osd_pool_default_min_size
spec:
mon:
count: 1
allow_multiple_per_node: false
mgr:
count: 1
allow_multiple_per_node: false
storage:
use_all_nodes: true
use_all_devices: false
device_filter: "^sdb"
block_pools:
- name: ceph-blockpool
failure_domain: osd # single node: osd, not host
replicated:
size: 1 # no cross-host replication on single node
storage_class:
name: ceph-block
is_default: true
fstype: ext4
Scenario H: S3-Only Cluster (No Block/CephFS)
Minimal setup for backup storage or S3-compatible object storage only. No RBD or CephFS drivers needed.
- name: rook-operator
from: cargo.ayedo.cloud/ayedo/k8s/rook:0.8.0
kubeconfig:
from: k8s
config:
operator:
enabled: true
- name: rook-csi-drivers
from: cargo.ayedo.cloud/ayedo/k8s/rook:0.8.0
kubeconfig:
from: k8s
config:
csi_drivers:
enabled: true
drivers:
rbd:
enabled: false # not needed for S3-only
cephfs:
enabled: false # not needed for S3-only
- name: rook-cluster
from: cargo.ayedo.cloud/ayedo/k8s/rook:0.8.0
kubeconfig:
from: k8s
config:
object_store:
gateway:
instances: 2
hosting:
dns_names:
- s3.example.com
cluster:
enabled: true
spec:
storage:
use_all_nodes: true
use_all_devices: false
device_filter: "^sdb"
# block_pools: [] (default, no RBD)
# file_systems: [] (default, no CephFS)
ceph_config — Raw Ceph Configuration
cluster.ceph_config is passed directly to cephClusterSpec.cephConfig in the Rook CRD. This gives full control over all Ceph daemon configuration without any abstraction.
Format:
cluster:
ceph_config:
<section>:
<key>: "<value>" # ALL values must be strings
Sections map to Ceph config sections:
| Section | Applies to |
|---|---|
global |
All Ceph daemons |
osd |
All OSD daemons |
mon |
Monitor daemons |
mgr |
Manager daemons |
mds |
Metadata server daemons |
"client.rgw" |
All RGW (S3 gateway) instances |
"client.rgw.<name>" |
Specific RGW instance |
Important: Section names containing dots (like
client.rgw) must be quoted in YAML.
All values must be strings — wrap numbers and booleans in quotes:
ceph_config:
global:
osd_memory_target: "4294967296" # correct: string
osd_memory_target: 4294967296 # wrong: integer
Useful ceph_config Keys
OSD performance (HDD)
| Key | Recommended | Description |
|---|---|---|
osd_recovery_sleep_hdd |
"0.1" |
Sleep between recovery ops on HDD OSDs (seconds) |
osd_scrub_sleep |
"0.1" |
Sleep between scrub ops (reduces client IO impact) |
osd_memory_target |
"4294967296" |
OSD memory cache target per OSD (bytes). 4GB = 4294967296 |
OSD performance (SSD/NVMe)
| Key | Recommended | Description |
|---|---|---|
osd_recovery_sleep_ssd |
"0.0" |
No throttling needed for SSD OSDs |
osd_scrub_sleep |
"0.0" |
No throttling needed for SSD |
RGW (S3) usage logging — required for per-bucket request/throughput metrics
| Key | Recommended | Description |
|---|---|---|
rgw_enable_usage_log |
"true" |
Enable RGW usage logging |
rgw_usage_log_tick_interval |
"30" |
How often (seconds) to flush usage data |
rgw_usage_log_flush_threshold |
"1024" |
Flush after this many entries regardless of interval |
Full list of available keys: https://docs.ceph.com/en/latest/rados/configuration/
Monitoring
The block integrates with VictoriaMetrics Operator and deploys VMServiceScrape and VMPodScrape resources. No Prometheus ServiceMonitor objects are created.
| Component | Type | Flag |
|---|---|---|
| Rook Operator | VMPodScrape |
operator.monitoring.vmpodscrape.enabled |
| Ceph Manager (MGR) | VMServiceScrape |
cluster.monitoring.vmservicescrape.enabled |
| Ceph Exporter | VMServiceScrape |
cluster.monitoring.vmservicescrape.enabled |
| CSI Drivers (RBD + CephFS) | VMPodScrape |
cluster.monitoring.csi.vmpodscrape.enabled |
| RGW Exporter (per-bucket) | VMServiceScrape |
exporter.vmservicescrape.enabled |
cluster.monitoring.enabledmust betruefor the MGR and Ceph Exporter VMServiceScrapes to work — this flag controls whether the chart creates the Kubernetes Service that exposes MGR metrics.
Full monitoring setup:
- name: rook-operator
config:
operator:
monitoring:
vmpodscrape:
enabled: true
- name: rook-cluster
config:
cluster:
monitoring:
enabled: true
vmservicescrape:
enabled: true
csi:
vmpodscrape:
enabled: true # only useful if block_pools or file_systems are configured
exporter:
vmservicescrape:
enabled: true
Ingress (RGW)
Expose the S3 endpoint via nginx Ingress:
- name: rook-cluster
config:
cluster:
ingress:
enabled: true
class: nginx
wildcard:
enabled: true # create wildcard DNS entry
base_host: "s3.example.com"
hosts: []
tls:
enabled: true
issuer: letsencrypt-production
Upgrade Path
Rook-Ceph requires stepwise upgrades — never skip a minor version.
| From | To | Block version |
|---|---|---|
| 1.17.x | 1.18.x | 0.2.0 |
| 1.18.x | 1.19.x | 0.3.0+ |
| 1.19.x | 1.20.x | 0.7.0 (breaking — see below) |
| 1.20.1 | 1.20.9 | 0.8.1 (patch; required before Ceph 20.2.4) |
The install playbook automatically handles StorageClass immutability issues introduced in Rook v1.18: before upgrading, it detects and deletes any CSI StorageClasses that are missing required fencing parameters. These are recreated correctly by the Helm upgrade. Existing PVCs and PVs are not affected.
Upgrading an existing cluster from 0.6.x (chart 1.19.x) to 0.7.0 (chart 1.20.1)
Full analysis: .specs/2-rook-helm-chart-update-1-19-2-1-20-1-breaking-changes-migrationspfad.md.
- Pre-flight: cluster must already be on Rook chart
>= 1.19.5(a critical Helm-ownership fix required for the 1.20 migration shipped there) andHEALTH_OKwith no down/recovering/ undersized/backfilling PGs. - Decouple the Ceph major upgrade: unless you deliberately want to jump from Ceph Squid
(
v19.2.3) to Tentacle (v20.2.2, the new chart default) in the same step, setcluster.ceph_image.tag: v19.2.3explicitly before upgrading the block. Ceph major upgrades end with an irreversibleceph osd require-osd-release tentacle— treat it as its own, separate, deliberate change. - Add the new
rook-csi-driversinstance toworkspace.poly(see "Architecture" above), positioned between the existingrook-operatorandrook-clusterinstances. - Migrate CSI toggles: if you previously set
operator.csi.rdb_driver.disabled/.cephfs_driver.disabled/.csi_driver.disabled, move the (inverted) values tocsi_drivers.drivers.rbd.enabled/.cephfs.enabled(see "Configuration Reference" above). If you setoperator.csi.discovery_daemon.enabled, move it tooperator.discovery_daemon.enabled(unrelated rename, not CSI-specific). - Run
polycrate run rook-operator install, thenpolycrate run rook-csi-drivers install, thenpolycrate run rook-cluster install— in that exact order. Do not skiprook-csi-drivers installeven if that Helm release is alreadydeployed(operator-only catch-up). After the 1.20 CSI operator starts, Driver CRs withoutspec.*.serviceAccountNamedefault to short SAs (rbd-nodeplugin-sa) that theceph-csi-driverschart never creates — ctrl/node plugins fail to schedule. Re-running the drivers chart writes the long names (rook-ceph-rbd-csi-ceph-com-*-sa). The operator playbook adoptsrook-csi-operator-image-set-configmapwhen its Helmrelease-nameis still the upstream defaultrook-ceph(this block releases the operator asrook-operator). Without that adopt, Helm 1.20.1 refuses the upgrade (rook#17696). - Verify CSI metrics did not silently break: the
cluster.monitoring.csi.vmpodscrapeselector changed (new ceph-csi-operator pod labels,<driver-name>-ctrlplugin/-nodeplugin) — confirmkubectl -n rook-ceph get pods -l app=rook-ceph.rbd.csi.ceph.com-nodepluginreturns pods before trusting CSI dashboards again.
Patch upgrade 0.8.0 (chart 1.20.1) to 0.8.1 (chart v1.20.9)
Patch line inside 1.20.x. Reversible with Helm rollback as long as cluster.ceph_image.tag
has not moved to Tentacle. Required before Ceph 20.2.4: Rook 1.20.1 cannot rotate
daemon keys to aes256k (CVE-2025-30156).
- Pull
cargo.ayedo.cloud/ayedo/k8s/rook:0.8.1. Defaults:mgr.modules.rook.enabled: false,cephx.daemon.key_rotation_policy: Disabled, chartsv1.20.9/ csi-drivers1.0.4. - Keep the existing
cluster.ceph_image.tagpin (e.g.v19.2.3). polycrate run rook-operator install, thenrook-csi-drivers install, thenrook-cluster install. Operator + CSI first — the cluster chart then writesmgr.modules.rook.enabled: false.- Gate: every deployment
rook-version=v1.20.9,ceph versionsstill on the pin, HEALTH_OK or WARN only, CSI PVs Bound, RGW reachable.
Ceph major upgrade with this block (Squid → Tentacle)
Irreversible after Rook sets require_osd_release tentacle. Do not combine with the
Rook patch upgrade.
- Rook already on >= 1.20.6 (1.20.9 via this block).
rookmgr module off. Cluster HEALTH_OK/WARN only, all PGsactive+clean. Optional:ceph osd set noautoscale. - Set
cluster.ceph_image.tagto a Tentacle build (v20.2.4-20260818) and runpolycrate run rook-cluster install. Rook rolls mon → mgr → osd → rgw and then pinsrequire_osd_release tentacle. - After
ceph versionsshows only 20.2.4: setcluster.spec.security.cephx.daemon.key_rotation_policy: KeyGenerationandkey_generationto currentstatus.cephx.*.keyGeneration+ 1. Install again. Rook rotates daemon keys toaes256kand rolls daemons once more. Restart the toolbox. Leave CSI keys onaesunless every node is kernel >= 7.0. ceph osd unset noautoscale. Mute remainingAUTH_INSECURE_*warnings that refer to client/CSI keys.
Cluster Health Verification
Kubernetes resources
# Overall pod status — all should be Running or Completed
kubectl -n rook-ceph get pods
# CephCluster CR status — Phase should be "Ready", Health "HEALTH_OK"
kubectl -n rook-ceph get cephcluster
# Detailed CephCluster status (shows mon map, MGR active, OSD count)
kubectl -n rook-ceph get cephcluster -o yaml | grep -A 30 "^status:"
# CephObjectStore status — Phase should be "Ready"
kubectl -n rook-ceph get cephobjectstore
# OSD deployments — count should match number of configured drives
kubectl -n rook-ceph get deployment -l app=rook-ceph-osd
# MGR deployments — count should match cluster.spec.mgr.count
kubectl -n rook-ceph get deployment -l app=rook-ceph-mgr
# Mon pods — count should match cluster.spec.mon.count
kubectl -n rook-ceph get pods -l app=rook-ceph-mon
Ceph CLI (via toolbox)
Enable the toolbox with cluster.toolbox.enabled: true, then:
kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- bash
# Overall cluster health — target: HEALTH_OK
ceph status
# OSD tree — shows all OSDs, their status (up/in) and placement
ceph osd tree
# OSD status summary
ceph osd stat
# Disk usage per pool
ceph df
# Pool list with replication/EC info
ceph osd pool ls detail
# Monitor quorum
ceph mon stat
ceph quorum_status -f json-pretty | jq '.quorum_names'
# MGR status — shows active and standby MGRs
ceph mgr stat
# Object store / RGW status
radosgw-admin zone get
radosgw-admin bucket list
radosgw-admin bucket stats
# PG (placement group) summary — all PGs should be active+clean
ceph pg stat
# Watch cluster events in real time
ceph -w
MGR replica scale-down
After decreasing cluster.spec.mgr.count from 2 to 1, the Rook operator removes
the second MGR Deployment (e.g. mgr-b) asynchronously during the next reconcile cycle.
Seeing both mgr-a and mgr-b immediately after the change is expected — mgr-a will
be active and mgr-b standby. The operator removes mgr-b once the cluster is healthy.
To speed up the removal manually:
# Inside the toolbox: fail the standby MGR — Rook will not recreate it when count=1
ceph mgr fail mgr-b
Check operator logs if mgr-b persists:
kubectl -n rook-ceph logs -l app=rook-ceph-operator --tail=50 | grep -i mgr
Common issue checks
# OSD not starting — check for devices with existing data/partitions
kubectl -n rook-ceph logs -l app=rook-ceph-osd-prepare --tail=50
# Operator reconcile errors
kubectl -n rook-ceph logs -l app=rook-ceph-operator --tail=100 | grep -i error
# RGW not ready — credentials secret not yet created (OSD prepare can take 10+ min)
kubectl -n rook-ceph get secret | grep object-user
# CSI driver not functional — check controller-plugin pods (since chart 1.20.x /
# ceph-csi-operator; driver name defaults to csi_drivers.drivers.{rbd,cephfs}.name)
kubectl -n rook-ceph get pods -l app=rook-ceph.rbd.csi.ceph.com-ctrlplugin
kubectl -n rook-ceph get pods -l app=rook-ceph.cephfs.csi.ceph.com-ctrlplugin
# PVC stuck in Pending — check CSI node plugin
kubectl -n rook-ceph get pods -l app=rook-ceph.rbd.csi.ceph.com-nodeplugin
kubectl describe pvc <pvc-name> -n <namespace>
# ceph-csi-drivers chart not installed — Driver/OperatorConfig CRs missing
kubectl -n rook-ceph get drivers.csi.ceph.io,operatorconfigs.csi.ceph.io
Toolbox
The Ceph toolbox provides a shell with ceph CLI access for debugging and administration:
cluster:
toolbox:
enabled: true
kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- bash
ceph status
ceph osd tree
ceph df
radosgw-admin bucket list
RadosGW Admin API Credential (polycrate-api)
Polycrate-API kann S3 Buckets und User über die RadosGW Admin API verwalten. Dafür braucht der S3-Cluster ein Credential mit Admin-Berechtigungen.
1. CephObjectStoreUser mit Admin-Caps anlegen
Erstelle einen RGW-User mit users=* und buckets=* (Rook verwendet Singular user/bucket):
apiVersion: ceph.rook.io/v1
kind: CephObjectStoreUser
metadata:
name: rgw-admin-polycrate
namespace: rook-ceph
spec:
store: ceph-objectstore
displayName: "Polycrate API Admin"
capabilities:
user: "*"
bucket: "*"
Nach dem Apply erstellt Rook ein Secret: rook-ceph-object-user-ceph-objectstore-rgw-admin-polycrate.
Hinweis: Rook's capabilities-Feld deckt nur users/buckets ab. Die für polycrate-api ebenfalls zwingende metadata-Cap (siehe 2b) kann darüber nicht gesetzt werden und muss immer per radosgw-admin nachgezogen werden.
2. Access Key und Secret Key auslesen
kubectl -n rook-ceph get secret rook-ceph-object-user-ceph-objectstore-rgw-admin-polycrate -o jsonpath='{.data.AccessKey}' | base64 -d
kubectl -n rook-ceph get secret rook-ceph-object-user-ceph-objectstore-rgw-admin-polycrate -o jsonpath='{.data.SecretKey}' | base64 -d
2b. Caps nachträglich setzen (bei „lacks users=* capability“ oder „Authentication failed“ in polycrate-api)
Rook setzt beim CephObjectStoreUser ggf. nicht die Admin-API-Caps. Dann in rook-ceph-tools:
radosgw-admin caps add --uid=rgw-admin-polycrate --caps="users=*" --rgw-realm=ceph-objectstore
radosgw-admin caps add --uid=rgw-admin-polycrate --caps="buckets=*" --rgw-realm=ceph-objectstore
radosgw-admin caps add --uid=rgw-admin-polycrate --caps="metadata=*" --rgw-realm=ceph-objectstore
metadata=* ist PFLICHT: polycrate-api's Connectivity-Check (RadosGWAdminAPI.check_connectivity in src/s3/cluster_integrations/radosgw.py) ruft GET /admin/metadata/user auf. Ohne diese Cap antwortet RGW dort mit 403 AccessDenied und polycrate-api meldet fälschlich „Authentication failed for ... Check credential (api_user/api_key) and admin capabilities (users=, buckets=)“ — obwohl users=*/buckets=* bereits korrekt gesetzt sind. Mit metadata=* antwortet derselbe Request mit 404 NoSuchKey, was polycrate-api als „erreichbar“ wertet. Beobachtet auf aycloud-hel1-satellite-1 am 2026-07-06.
Realm-Pflicht: Rook nutzt Realm ceph-objectstore. user list und caps add benötigen --rgw-realm=ceph-objectstore, sonst user list = [].
3. In polycrate-api eintragen
- Credential (Kind: S3 Credential):
api_user= Access Key,api_key= Secret Key - S3 Cluster (Kind: Rook CEPH):
admin_endpoint=https://<s3-host>(z.B.https://s3.example.com),credentialzuweisen,namespace=rook-ceph,region= z.B.us-east-1
Die RadosGW Admin API nutzt denselben Host/Port wie die S3 API.