How to Deploy Object Storage on Red Hat Ceph Storage Cluster

Red Hat Ceph Storage Object Storage

Introduction

In a Red Hat Ceph Storage cluster, object storage is provided by the RADOS Gateway (RGW). It provides an S3-compatible interface that translates RESTful API requests into the cluster’s RADOS format.

A diagram showcasing object storage data-flow with a Red Hat Ceph Storage cluster
Diagram of the end-to-end data flow-path when using object storage on Ceph.

The client sends the data via the loadbalancer, which is configured to choose one of the nodes with an RGW on them (in our case, all 3 storage nodes have one). Then, the RGW performs the write using ‘librados’ to a primary OSD calculated by the CRUSH algorithm. The primary OSD simultaneously replicates the data to n other OSDs in the PG (depends on configuration, default is 3).

NOTE: The node/OSDs mumbers are arbitrary, and in reality, the loadbalancer points to ALL configured RGW nodes, not just one.

The following section will show you how to deploy object storage in a Red Hat Ceph Storage cluster.

This guide assumes you have a Red Hat Ceph Storage cluster set up. If not, check out our other guide on How to Deploy a Red Hat Ceph Storage Cluster in an Air-gapped Environment.

Procedure

Deploy RGW on the nodes

Label the nodes:

[ceph: root@node1 /]# for node in node1 node2 node3; \
do ceph orch host label add $node rgw; done

Deploy the RGW daemon on the nodes:

[ceph: root@node1 /]# ceph orch apply rgw rgw-octopuscs \
--placement="label:rgw"

NOTE: This command might take a few minutes to complete.

Verify that the deployment worked:

[ceph: root@node1 /]# ceph orch ls 
NAME                       PORTS  RUNNING  REFRESHED  AGE  PLACEMENT                                                                                          
...                                                                                                
rgw.rgw-octopuscs            ?:80       3/3  7s ago     9s   label:rgw                                                                                          

[ceph: root@node1 /]# ceph orch ps --daemon_type=rgw
NAME                           HOST  PORTS  STATUS  
rgw.rgw-octopuscs.node1.aqbnrg  node1  *:80   running (49s)  
rgw.rgw-octopuscs.node2.hyjmrr  node2  *:80   running (42s)  
rgw.rgw-octopuscs.node3.nynnyr  node3  *:80   running (36s)

Install the LoadBalancer

On bastion, download the HAProxy package and any required dependencies. Then, copy it to the loadbalancer

[root@bastion ~]# mkdir -p ~/ceph-cluster-packages/loadbalancer-packages/haproxy && cd ~/ceph-cluster-packages/loadbalancer-packages/haproxy
[root@bastion haproxy]# dnf download --resolve haproxy
[root@bastion haproxy]# scp haproxy-<version>.rpm root@loadbalancer:/root
Install the HAProxy package on the loadbalancer node.
[root@bastion haproxy]# ssh loadbalancer
[root@loadbalancer ~]# ll
-rw-r--r--. 1 root root 2615238 Jan 22 11:13 haproxy-<version>.rpm

[root@loadbalancer ~]# dnf install -y ./haproxy-<version>.rpm
Configure HAProxy to serve the OSD nodes

Backup the original haproxy.cfg and replace the contents with the below configuration
(replace <value> with your values).

[root@loadbalancer ~]# cp haproxy.cfg haproxy.cfg.backup
[root@loadbalancer ~]# cat haproxy.cfg
# HAProxy configuration
# ---------------------

defaults
  timeout connect 10s
  timeout client 5m
  timeout server 5m
  timeout http-request 10s
  timeout http-keep-alive 2m

listen stats
  bind *:8080
  mode http
  stats enable
  stats uri /haproxy_stats
  stats auth admin:<secure-password> # Replace with your password
# ---------------------------------------------------------------------

# S3 Object Storage Frontend (Public Facing)
# ------------------------------------------

frontend rgw_frontend
    bind *:80
    mode http
    option httplog
    default_backend rgw_nodes
# ---------------------------------------------------------------------

# RGW Backend Nodes (The Storage Nodes)
# ---------------------------------------------------------------------

backend rgw_nodes
    mode http
    balance leastconn
    option httpchk GET /
    # The 'check' parameter tells HAProxy to monitor if the node is alive
    server node1 <node1-ip>:80 check
    server node2 <node2-ip>:80 check
    server node3 <node3-ip>:80 check
Start HAProxy service
[root@loadbalancer ~]# systemctl enable --now haproxy

Check the HAproxy service

You can check connectivity to the cluster via the loadbalancer. You can use bastion for this.

[root@bastion ~]# curl http://loadbalancer
<?xml version="1.0" encoding="UTF-8"?><ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Owner><ID>anonymous</ID><DisplayName></DisplayName></Owner><Buckets></Buckets></ListAllMyBucketsResult>

Using the Object Storage on a Red Hat Ceph Storage Cluster

In this section, we will showcase how to store files in our Red Hat Ceph Storage cluster using the AWS S3 API. For that, we need to create an S3 entity in our Red Hat Ceph Storage cluster to make authorized requests, and an S3-compatible API tool for read/write operations. We will use the s3cmd tool.

Create a New radosgw Entity

[root@node1 ~]# cephadm shell
[ceph: root@node1 /]# radosgw-admin user create --uid=s3admin \
  --display-name="S3 Administrator" \
  --access-key=<access-key> --secret=<secret>

Install and Configure the s3cmd Tool

On bastion, download the s3cmd repository.

[root@bastion~]# cd /etc/yum.repos.d/
[root@bastion~]# wget http://s3tools.org/repo/RHEL_6/s3tools.repo

Install the s3cmd tool:

[root@bastion~]# dnf install -y s3cmd
[root@bastion~]# s3cmd --version
s3cmd version 2.4.0

Configure s3cmd to point to your local S3 object storage:
(replace <value> as needed)

[root@bastion ~]# s3cmd --configure

Enter new values or accept defaults in brackets with Enter.
Refer to user manual for detailed description of all options.

Access key and Secret key are your identifiers for Amazon S3. Leave them empty for using the env variables.
Access Key: <access-key>
Secret Key: <secret>
Default Region [US]: default

Use "s3.amazonaws.com" for S3 Endpoint and not modify it to the target Amazon S3.
S3 Endpoint [s3.amazonaws.com]: loadbalancer

Use "%(bucket)s.s3.amazonaws.com" to the target Amazon S3. "%(bucket)s" and "%(location)s" vars can be used
if the target S3 system supports dns based buckets.
DNS-style bucket+hostname:port template for accessing a bucket [%(bucket)s.s3.amazonaws.com]: loadbalancer/%(bucket)s

Encryption password is used to protect your files from reading
by unauthorized persons while in transfer to S3
Encryption password: 
Path to GPG program [/bin/gpg]: <Enter>

When using secure HTTPS protocol all communication with Amazon S3
servers is protected from 3rd party eavesdropping. This method is
slower than plain HTTP, and can only be proxied with Python 2.7 or newer
Use HTTPS protocol [Yes]: No

On some networks all internet access must go through a HTTP proxy.
Try setting it here if you can't connect to S3 directly
HTTP Proxy server name: <Enter>

New settings:
  Access Key: <access-key>
  Secret Key: <secret>
  Default Region: default
  S3 Endpoint: loadbalancer
  DNS-style bucket+hostname:port template for accessing a bucket: %(bucket)s.s3.amazonaws.com
  Encryption password: 
  Path to GPG program: /bin/gpg
  Use HTTPS protocol: False
  HTTP Proxy server name: 
  HTTP Proxy server port: 0

Test access with supplied credentials? [Y/n] Y
Please wait, attempting to list all buckets...
Success. Your access key and secret key worked fine :-)

Now verifying that encryption works...
Not configured. Never mind.

Save settings? [y/N] y
Configuration saved to '/root/.s3cfg'

Modify the newly created s3cmd file (this change is necessary because s3cmd defaults to look for buckets in Amazon’s AWS, and not locally):

[root@bastion ~]# sed -i 's|host_bucket = .*|host_bucket = loadbalancer|g' ~/.s3cfg

Create an S3 bucket

[root@bastion ~]# s3cmd ls
[root@bastion ~]# s3cmd mb s3://octopuscs-storage
Bucket 's3://octopuscs-storage/' created
[root@bastion ~]# s3cmd ls
2026-01-25 15:15  s3://octopuscs-storage

Verify that the bucket can store/retrieve data

For this, we are going to create a 10 GiB ’empty’ file on our bastion. Make sure your bastion has enough storage to accommodate this file. Notice the OSD’s utilization before and after.

[ceph: root@node1 /]# ceph osd df     
ID  CLASS  RAW USE  DATA     META     AVAIL    %USE 
 2    hdd  104 MiB  3.7 MiB  101 MiB  50 GiB  0.20 
 5    hdd  108 MiB  3.7 MiB  105 MiB  50 GiB  0.21
 8    hdd  101 MiB  4.2 MiB  97 MiB   50 GiB  0.20
 0    hdd  104 MiB  3.7 MiB  101 MiB  50 GiB  0.20
 3    hdd  105 MiB  4.3 MiB  101 MiB  50 GiB  0.21
 6    hdd  108 MiB  3.7 MiB  105 MiB  50 GiB  0.21
 1    hdd  100 MiB  3.7 MiB  97 MiB   50 GiB  0.20
 4    hdd  104 MiB  3.7 MiB  101 MiB  50 GiB  0.20
 7    hdd  109 MiB  4.3 MiB  105 MiB  50 GiB  0.21
 TOTAL     946 MiB  35 MiB   911 MiB  449 GiB 0.21

On bastion, create test directories. Then, create an a 10 GiB file. The command might take a few minutes to complete.

[root@bastion ~]# mkdir -p /tmp/test/{put,get} && cd /tmp/test/put
[root@bastion put]# dd if=/dev/zero of=/tmp/test/put/big-file bs=4M count=2500
...
10485760000 bytes (10 GB, 9.8 GiB) copied, 8.43405 s, 1.2 GB/s

Upload the file to the Ceph S3 bucket.

[root@bastion put]# s3cmd --acl-public put /tmp/test/put/big-file s3://octopuscs-storage/
...
upload: 'big-file' -> 's3://octopuscs-storage/big-file'  [part 667 of 667, 10MB] [1 of 1]
 10485760 of 10485760   100% in    0s    18.25 MB/s  done
Public URL of the object is: http://loadbalancer/octopuscs-storage/big-file

[root@bastion put]# s3cmd ls s3://octopuscs-storage
2026-01-25 15:31  10485760000  s3://octopuscs-storage/big-file

Check OSD’s utilization now.

Notice that even though the file is 10 GiB, the ‘DATA’ column shows ~30 GiB usage. That is because Ceph’s default configuration is to replicate any data in any pool 3 times.

[ceph: root@node1 /]# ceph osd df
ID  CLASS  RAW USE   DATA      META      AVAIL    %USE 
 2   hdd   2.6 GiB   2.5 GiB   118 MiB   47 GiB   5.29 
 5   hdd   4.0 GiB   3.9 GiB   132 MiB   46 GiB   7.97 
 8   hdd   3.5 GiB   3.4 GiB   136 MiB   46 GiB   7.04 
 0   hdd   3.9 GiB   3.7 GiB   132 MiB   46 GiB   7.73 
 3   hdd   3.5 GiB   3.4 GiB   158 MiB   46 GiB   7.04 
 6   hdd   2.8 GiB   2.7 GiB   130 MiB   47 GiB   5.62 
 1   hdd   2.3 GiB   2.2 GiB   126 MiB   48 GiB   4.64 
 4   hdd   6.3 GiB   6.1 GiB   175 MiB   44 GiB   12.60
 7   hdd   1.6 GiB   1.5 GiB   114 MiB   48 GiB   3.14 
TOTAL      31 GiB    29 GiB    1.2 GiB   419 GiB  6.79

Next, pull the file from the S3 bucket back to the bastion node.

[root@bastion put]# rm /tmp/test/put/big-file
[root@bastion put]# cd /tmp/test/get
[root@bastion get]# wget http://loadbalancer/octopuscs-storage/big-file
--2026-01-22 13:21:36--  http://loadbalancer/octopuscs-storage/big-file
...
HTTP request sent, awaiting response... 200 OK
Length: 10485760000 (9.8G) [binary/octet-stream]
Saving to: ‘big-file’

big-file                              100%[==============================================>] 9.77G  85.3MB/s    in 2m 6s   

2026-01-22 13:23:42 (79.4 MB/s) - ‘big-file’ saved [10485760000/10485760000]

[root@bastion get]# ll
total 10240000
-rw-r--r--. 1 root root 10485760000 Jan 22 13:05 big-file

Remove the file from bastion and from the S3 bucket

[root@bastion get]# rm big-file
[root@bastion get]# s3cmd ls s3://octopuscs-storage
2026-01-25 15:31  10485760000  s3://octopuscs-storage/big-file

[root@bastion get]# s3cmd rm s3://octopuscs-storage/big-file
[root@bastion get]# s3cmd ls s3://octopuscs-storage
(no output)

Clean the bucket with Garbage Collector

On node1, manually trigger Ceph’s garbage collector.

[root@node1 ~]# cephadm shell
[ceph: root@node1 /]# radosgw-admin gc process --include-all

NOTE: Be careful running this on a production cluster. Running gc process manually will cause a performance hit.

Summery

In this guide, we learned how to deploy object storage solution on a Red Hat Storage Ceph cluster. To advance the cluster’s capabilities further, check out our other guides on How to deploy RBD storage on a Red Hat Ceph Storage Cluster, How to Deploy a Multi-Site Ceph Object Gateway, and How To Use REST API with RADOS Gateway.

Make sure to check out the Red Hat Docs for the complete configuration options for your Ceph cluster.

Noam Akselbant

Noam Akselbant is a DevOps Engineer with hands-on experience in Linux, Ansible, OpenShift, and Ceph, specializing in automation, cloud-native & on-prem technologies, and scalable infrastructure.

Leave a Reply

Your email address will not be published. Required fields are marked *