5 min read

How to enable Ceph multitenancy for object storage in OpenStack?

How to enable Ceph multitenancy for object storage in OpenStack?

How to manage object storage multitenancy with an integrated Ceph cluster into an OpenStack deployment?

Note

If the manipulation is performed after a deployment, changes will only work for new users and new tenants.

Bonus

Integrate Ceph RGW multitenancy and S3 authentication with kolla-ansible

Standard behaviour

Regarding bucket operation, Ceph as some constraints (see here).

The part we're interested in is this: Bucket names must be unique.

Why? Because with OpenStack you have the concept of multitenancy. In a vanilla Swift deployment each tenants can overlap the bucket name between them.
The "classic" object-store service API endpoint are created like this:

openstack endpoint create --region RegionOne \
  object-store public http://endpoint:8080/v1/AUTH_%\(project_id\)s

This specific part AUTH_%\(project_id\)s is very important: each tenant have it's own namespace and can do anything without any impact on other tenant. If you create a container ($CONTAINER), the URL will be:

http://endpoint:8080/v1/AUTH_%\(project_id\)s/$CONTAINER

On the other side the default endpoint with an integrated Ceph is:

http://radosgw-endpoint-url:6780/v1/swift

It means that when you create a container $CONTAINER, the URL will be /v1/swift/$CONTAINER and that's a big problem if you want to have a multitenancy.

You'll have this regardless if you use Horizon/Swift API or S3 endpoint.

How it looks in Ceph

The key is to understand how Ceph works in this situation.

For example if I create (from Swift CLI) 2 containers named demo in 2 differents tenants from Swift endpoint, this is how it'll work:

On the first tenant I can create the container demo:

(openstack) [openstack@centos ~] $ swift post demo --debug
...
INFO:swiftclient:RESP STATUS: 404 Not Found
INFO:swiftclient:RESP BODY: NoSuchKey
DEBUG:urllib3.connectionpool:http://172.16.10.10:6780 "PUT /swift/v1/demo HTTP/1.1" 201 0
DEBUG:swiftclient:RESP STATUS: 201 Created
...

But on the second tenant:

(openstack) [openstack@centos ~] $ swift post demo
DEBUG:urllib3.connectionpool:http://172.16.10.10:6780 "POST /swift/v1/demo HTTP/1.1" 403 12
INFO:swiftclient:RESP STATUS: 403 Forbidden
INFO:swiftclient:RESP BODY: AccessDenied
ClientException: Container POST failed: http://172.16.10.10:6780/swift/v1/demo 403 Forbidden   AccessDenied
Container POST failed: http://172.16.10.10:6780/swift/v1/demo 403 Forbidden   AccessDenied
Failed Transaction ID: tx000000000000000000070-005e962c65-86cc-default

I'll not be allowed to create this container because the name already exists.

If we look on Ceph side we see clearly why we cannot overlap 2 containers:

[openstack@controller-1 ~] $ radosgw-admin bucket list
[
    "demo"
]

This behaviour might be fine for a variety of use cases, but if you want to take advantage of multitenancy, it's really problematic.

Enable multitenancy capabilities

We need to specify to Ceph that he needs to use specific bucket name and have an identifier of the tenant name on each bucket.

You have to add 2 options in your ceph.conf:

[client.radosgw.gateway]
...
rgw_keystone_implicit_tenants = true
rgw_swift_account_in_url = true

The first one will tell Ceph to put the bucket under the tenant id for each new user. The second one will allow you to use the endpoint with the tenant id identifier.

You also have to change your swift endpoint with adding %\(tenant_id\)s add the end of each endpoint. Get the IDs:

(openstack) [openstack@centos ~] $ openstack endpoint list | grep swift
| 1be42ae9c8ee43edbb7e4bf73eb9f958 | RegionOne | swift        | object-store   | True    | public    | http://172.16.10.10:6780/swift/v1         |
| 3cfcfe9eed924d7a9acf4d86761be6cb | RegionOne | swift        | object-store   | True    | internal  | http://172.16.11.10:6780/swift/v1         |
| c279c8bda5694eb19d4e6c0924a0e6fd | RegionOne | swift        | object-store   | True    | admin     | http://172.16.11.10:6780/swift/v1         |

Then edit them (remplace the id at the end by your endpoints id):

openstack endpoint set --region RegionOne --service swift --interface public --url http://172.16.10.10:6780/swift/v1/%\(tenant_id\)s 1be42ae9c8ee43edbb7e4bf73eb9f958
openstack endpoint set --region RegionOne --service swift --interface internal --url http://172.16.10.10:6780/swift/v1/%\(tenant_id\)s 3cfcfe9eed924d7a9acf4d86761be6cb
openstack endpoint set --region RegionOne --service swift --interface admin --url http://172.16.10.10:6780/swift/v1/%\(tenant_id\)s c279c8bda5694eb19d4e6c0924a0e6fd

It'll work both with and without AUTH_ prefix. More informations here.

How it looks now in Ceph

I've created 2 tenants:

(openstack) [openstack@centos ~] $ openstack project list | grep 'demo2\|demo3'
| 2ae80d2a85ce446bae46595611033a94 | demo2   |
| cdc03141fb1f4090a7de4cbaee55a667 | demo3   |

Then created a demo container with each of these tenant.

Now if we look at Ceph:

[openstack@controller-1 ~] $ radosgw-admin bucket list
[
    "cdc03141fb1f4090a7de4cbaee55a667/demo",
    "2ae80d2a85ce446bae46595611033a94/demo"
]

I see that each container is in the tenant namespace and I haven't any impact by overlapping the container names accross the tenants.

In a simpler way, here's what used to happen before:

Creation of a container named "demo"
Tenant A id: 2ae80d2a85ce446bae46595611033a94
Tenant B id: cdc03141fb1f4090a7de4cbaee55a667

    +-------------------+   OK
    | user A > tenant A +--------->  /demo 
    +-------------------+

    +-------------------+   KO
    | user B > tenant B +--------->  /demo already exist
    +-------------------+

and now:

Creation of a container named "demo"
Tenant A id: 2ae80d2a85ce446bae46595611033a94
Tenant B id: cdc03141fb1f4090a7de4cbaee55a667

    +-------------------+   OK
    | user A > tenant A +--------->  2ae80d2a85ce446bae46595611033a94/demo
    +-------------------+

    +-------------------+   OK
    | user B > tenant B +--------->  cdc03141fb1f4090a7de4cbaee55a667/demo
    +-------------------+

Swift public access

The endpoint now will be:

http://ceph_endpoint:6780/swift/v1/$tenant_id/$container

Example with the container demo of demo3 tenant:

(openstack) [openstack@centos ~] $ swift stat demo -v
                          URL: http://172.16.10.10:6780/swift/v1/cdc03141fb1f4090a7de4cbaee55a667/demo
                   Auth Token: gAAAAABeljlU0SW_cEqFomGOq0L33N45uDFAHsKBWzPg4w2WxguR4aD1WlexGD6mB3FQK4HDnpms8y-z3_O-KP5rc-DMyzkj6XoHqZagRyTiUFc9twyyg17CUyk5uTl8PENrtLUZN_K3vZRRqiubT__imIGMEqmrl0fivO6Q_CUwxi8obmmeTlQ
                      Account: cdc03141fb1f4090a7de4cbaee55a667
                    Container: demo
                      Objects: 0
                        Bytes: 0
                     Read ACL:
                    Write ACL:
                      Sync To:
                     Sync Key:
              X-Storage-Class: STANDARD
                Accept-Ranges: bytes
             X-Storage-Policy: default-placement
X-Container-Bytes-Used-Actual: 0
                Last-Modified: Tue, 14 Apr 2020 22:29:29 GMT
                  X-Timestamp: 1586903369.52510
                   X-Trans-Id: tx00000000000000000005f-005e963954-121c1-default
                 Content-Type: text/plain; charset=utf-8
       X-Openstack-Request-Id: tx00000000000000000005f-005e963954-121c1-default

S3 public access

The S3 endpoint will be now look like that:

http://ceph_endpoint:6780/$tenant_id:$container

Example with the container demo of demo3 tenant:

(openstack) [openstack@centos ~] $ curl -s http://172.16.10.10:6780/cdc03141fb1f4090a7de4cbaee55a667:demo | tidy -xml -i -
No warnings or errors were found.

<?xml version="1.0" encoding="utf-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Tenant>cdc03141fb1f4090a7de4cbaee55a667</Tenant>
  <Name>demo</Name>
  <Prefix></Prefix>
  <Marker></Marker>
  <MaxKeys>1000</MaxKeys>
  <IsTruncated>false</IsTruncated>
</ListBucketResult>

That's all good 😀

Resources

https://docs.ceph.com/docs/master/radosgw/
https://docs.ceph.com/docs/luminous/radosgw/multitenancy/
https://docs.ceph.com/docs/master/radosgw/keystone/
https://docs.ceph.com/docs/master/radosgw/swift/tempurl/
https://docs.ceph.com/docs/master/radosgw/config-ref/
https://opendev.org/openstack/openstack-ansible/commit/9dbdf71de04425473143bdf36412c5278830e993
https://github.com/ceph/ceph/commit/e9259486decab52a362443d3fd3dec33b0ec654f

Picture : Fredy Jacob