Note: Starting with RHOSO 19, the split API layout is deprecated. The security vulnerability described in OSSN-0090 has been resolved with Glance's new location API, which is now supported by Nova and Cinder. For new deployments, use the default single layout. For migration guidance, see Location API Migration Guide.
As mentioned in OSSN-0090, when deploying Glance in a popular configuration where Glance shares a common storage backend with Nova and/or Cinder, it was possible to open some known attack vectors by which malicious data modification could occur. The split API deployment was implemented as a security workaround before the location API was available.
- A
user facingglance-api service, accessible via the Public and Admin keystone endpoints. - An
internal facing onlyservice, accessible via the Internal keystone endpoint.
The user facing service is configured to not expose image locations, namely by setting the following options in glance-api.conf:
[DEFAULT]
show_image_direct_url = False
show_multiple_locations = FalseThe internal service, operating on a different port (e.g. 9293), is configured identically to the public facing service, except for the following:
[DEFAULT]
show_image_direct_url = True
show_multiple_locations = TrueOpenStack services that use glance (cinder and nova) is configured to access it via the new internal service. That way both cinder and nova will have access to the image location data.
Glance supports resource consumption quotas on tenants through the use of
Keystone’s unified limits functionality. In order to enable this feature, as
per the official
documentation, the
Glance CRD exposes the quotas structure, where the limits defined in the
documentation can be defined. As the full Glance example
CR
shows, when the following is added in the top level definition, all the
GlanceAPI Pods will enable per tenant quotas via use_keystone_limits = True in their config, that will be automatically generated by the Glance
operator.
apiVersion: glance.openstack.org/v1beta1
kind: Glance
metadata:
name: glance
spec:
...
...
quotas:
imageSizeTotal: 1000
imageStageTotal: 1000
imageCountUpload: 100
imageCountTotal: 100
As a follow up work, tha Glance operator might rely on the existing webhooks
to enforce the Quota Limits values.
When using the Cinder backend the Glance containers need to run with higher privileges than with other backends, this is due to the usage of os-brick to connect to volumes.
Some of the things that the operator does when using Cinder as a backend are:
- Run containers as privileged
- Mount for r/w more directories
- Pod shares the PID namespace with the host
- Run CLI commands with the binaries present in the host
OpenShift deployments are different from TripleO deployments. For example, in TripleO we run iscsid and multipathd daemons in containers and their client side commands are in sync (version-wise) in the service containers. It is also required that there are no other daemons running in the host or other containers.
In the OpenShift world this is different, iscsid and multipathd run on the host and therefore their version won't be in sync with the one in the openstack service containers, which means that they won't work correctly.
The reason why OpenShift runs iscsid and multipathd in the host is so that they can be shared between the different things that may need it: OpenShift itself, CSI plugins, and in this case also OpenStack services.
The way to use the host iscsi and multipath services is by exiting the pod
namespace and running the commands on the host namespace using nsenter. Even
if this may look nasty/weird, this is the recommended way by the storage
OpenShift team and what CSI plugins do.
We are using nsenter through the templates/glance/bin/run-on-host script.
This run-on-host script supports 2 ways of replacing commands: being copied
or symlinked. In our case we instruct kolla to use the copying mechanism in
templates/glance/config/glance-api-config.json.
To be able to use nsenter the pod needs to share the PID namespace with the
host.
It's possible to deploy multiple GlanceAPI to serve different workloads. When a
Glance CR is created, if not explicitly specified in the top level CR, the mutate
webhook invocation creates an instance of a glanceAPI object called "default",
and it results in two GlanceAPI deployments.
apiVersion: glance.openstack.org/v1beta1
kind: Glance
...
spec:
serviceUser: glance
databaseInstance: openstack
databaseUser: glance
glanceAPIs:
..
secret: osp-secret
storageClass: ""
...
...
Both GlanceAPI internal and external sub-CR(s) are generated, resulting in
two different k8s StatefuSet objects.
The Status and the definition of the deployed GlanceAPI services can be
retrieved with the following commands:
$ oc get Glance
NAME STATUS MESSAGE
glance True Setup complete
$ oc get GlanceAPI
NAME NETWORKATTACHMENTS STATUS MESSAGE
glance-default-external True Setup complete
glance-default-internal True Setup complete
The resulting CRs can be inspected as regular k8s objects. The following diagram describes how k8s resources/object are connected.
[statefulset(s)]
+-------------------------+
+------------+ +---------------------+ |---> | glance-default-internal |
| glance CR | |---> | glanceAPI-default CR| +-------------------------+
+------------+ +---------------------+ |---> | glance-default-external |
+-------------------------------+
(headless SVC to allow pod2pod communication) | glance-default-internal-api |
(it must match with the StatefulSet ServiceName) +-------------------------------+
| glance-default-external-api |
+-------------------------------+
By default, if no option is specified, a glanceAPI deployment is split
between internal and external.
It's possible to not split the deployment and reduce everything to a single
API instance.
To do that, a Glance CR spec like the following should be created:
apiVersion: glance.openstack.org/v1beta1
kind: Glance
...
spec:
serviceUser: glance
databaseInstance: openstack
databaseUser: glance
glanceAPIs:
api0:
replicas=1
type: "single"
secret: osp-secret
storageClass: ""
...
...
As per the example above, by passing type: single to the api0 instance,
defined in the glanceAPIs section, let the main Glance controller to only
generate a single api0 StatefulSet that is not split between internal
and external.
[k8s Service(s)]
[statefulset(s)] +---------------------+
+------------+ +------------------+ +-------------------+ | glance-api-internal |
| glance CR | |---> | glance-default CR| |---> | glance-api-single | |--> | --------------------+
+------------+ +------------------+ +-------------------+ | glance-api-public |
+-----------------------+
(headless Svc) |--> | glance-api-single-api |
+-----------------------+
Both internal and public k8s Services are created, and they point to the
single StatefuSet generated by the glanceAPI controller.
$ oc get glanceapi
NAME NETWORKATTACHMENTS STATUS MESSAGE
glance-api-single True Setup complete
The generic GlanceAPI StatefuSet, generated by the process above, deploys three
containers within the same Pod:
+-------------------------------------+
| GlanceAPI::api0:Pod |
| |
| +-----------------------+ |
| | +------------+ | |
| | | glance-log | -------------------> It streams the glance-api0 logs
| | +------------+ | | in /var/log/glance
| +-----------------------+ |
| +-----------------------+ |
| | +--------------+ | |
| | | glance-httpd | ---|-------------> It intercepts requests on 9292 and
| | +-+------------+ | | do the ProxyPass to the process
| | | | | behind on 127.0.0.1:9293
| | +-+----------+ | |
| | | glance-api | -----|-------------> Runs the glance-api process on 9293
| | +------------+ | |
| +-----+-----------------+ |
+---------|-----------------------------+
|
+-> GlanceAPI:api0 = (httpd + glance-api)
The reason about having an additional httpd layer relies on the fact that
TLS is not natively supported in Glance.
Even though Glance it's not supported in production as regular wsgi process
run by httpd, this scenario sees httpd configured in a way that executes
ProxyPass for the requests coming from the associated endpoint to the
glance-api service.
More details about this can be found in the upstream documentation.
The previous section describes the deployment flow and how many layouts are
supported by a glanceAPI: having single or split might change according
to the configured backend.
An additional feature provided by the glance-operator is the ability to deploy
multiple GlanceAPI that are supposed to serve different workloads.
A real use case scenario where it makes sense to have multiple GlanceAPI is an
Edge deployment (known as DCN).
This is a scenario where multiple glanceAPI instances are orchestrated by the
same operator, but they're connected with different backends.
spec:
customServiceConfig: |
[DEFAULT]
enabled_backends = default_backend:rbd
[glance_store]
default_backend = default_backend
[default_backend]
rbd_store_ceph_conf = /etc/ceph/ceph.conf
store_description = "RBD backend"
rbd_store_pool = images
rbd_store_user = openstack
databaseInstance: openstack
databaseUser: glance
keystoneEndpoint: central
glanceAPIs:
central:
replicas: 1
edge0:
replicas: 1
edge1:
replicas: 1
...
...
...
extraMounts:
- name: central
region: r1
extraVol:
- propagation:
- central
volumes:
- name: ceph0
secret:
secretName: ceph-conf-files-0
mounts:
- name: ceph0
mountPath: "/etc/ceph"
readOnly: true
- name: edge0
region: r1
extraVol:
- propagation:
- edge0
volumes:
- name: ceph1
secret:
secretName: ceph-conf-files-1
mounts:
- name: ceph1
mountPath: "/etc/ceph"
readOnly: true
- name: edge1
region: r1
extraVol:
- propagation:
- edge1
volumes:
- name: ceph2
secret:
secretName: ceph-conf-files-2
mounts:
- name: ceph2
mountPath: "/etc/ceph"
readOnly: true
In the example above, all the GlanceAPI instances share the same configuration,
which is inherited by the main customServiceConfig, whileextraMounts are
added to connect each instance to a different Ceph cluster.
For each instance, you can configure the layout parameter (either split or
single) based on the specific backend requirements. However, keep in mind that
webhooks will prevent any modifications to the layout after it's been set.
It's also possible to deploy multiple glanceAPI services within the same availability zone (AZ) to handle different workloads. Here's an example configuration:
keystoneEndpoint: api0
glanceAPIs:
api0:
customServiceConfig: |
[DEFAULT]
enabled_backends = default_backend:rbd
replicas: 1
type: split
api1:
customServiceConfig: |
[DEFAULT]
enabled_backends = default_backend:swift
replicas: 1
type: split
In this setup:
- api0 is registered in the Keystone catalog and used as the default endpoint for CLI operations.
- api1, while not the default, remains an active API that can still be utilized
for image uploads by specifying the
--os-image-urlparameter.
Switching the Keystone catalog's active API is straightforward and can be done
by updating the keystoneEndpoint parameter.
Even though multiple instances can be deployed and be reachable through the generated
k8s Services, only one of them can be registered in the keystone catalog at a given
time.
There's a 1:1 relation between the image service in keystone, associated to Glance,
and the Endpoint (internal and public) that exist in Keystone.
For this reason, in the top-level Glance CR, a keystoneEndpoint parameter is
defined and exposed.
Unless a single instance is deployed, the human operator is supposed to choose,
before the main OpenStackControlPlane CR is applied, which instance should be
registered in keystone.
As an example, let's consider a generic Glance spec like the following:
spec:
customServiceConfig: |
...
...
databaseInstance: openstack
databaseUser: glance
keystoneEndpoint: api0
glanceAPIs:
api0:
replicas: 1
api1:
replicas: 1
api2:
replicas: 1
...
...
The keystoneEndpoint parameter points to api0: it means that api0 will be
registered in the Keystone catalog.
An OpenStack administrator will be able to reach that instance via openstack-cli
and perform any normal image related operation.
At the same time, api1 and api2 are not reachable via cli, and they will only
be available through their k8s Services by applications that are able to
discover their Endpoints.
Once a GlanceAPI is deployed, its Endpoints are reflected in both the generated
sub-CR, as well as in the top level CR.
For each deployed API instance, an entry is added in the Status.Endpoints
map, and each endpoint is prefixed with the name of the API it refers to.
apiVersion: glance.openstack.org/v1beta1
kind: Glance
...
status:
apiEndpoint:
api0-internal: http://glance-api0-internal.openstack.svc:9292
api0-public: https://glance-api0-public.apps.crc.openstack.dev:9292
api1-internal: http://glance-api1-internal.openstack.svc:9292
api1-public: https://glance-api1-public.apps.crc.openstack.dev:9292
...
...
When an API is decommissioned, the associated Endpoint is removed from the
top-level apiEndpoint map.
Other than decommissioning an instance (delete from the main CR), it's possible,
at any point in time, to update the keystoneEndpoint value and select a different
instance that should be registered in Keystone.
An update to the keystoneEndpoint parameter results in a reconciliation execution
that switches the two affected instances (the one registered in keystone and the
one proposed by the new update).
Adding an additional GlanceAPI to OpenStack serves various purposes beyond
just supporting multiple workloads. For instance, to maintain the lifecycle of
a GlanceAPI and its backend services, switching between a split layout and a
single layout is generally not feasible.
Consider a scenario where maintenance requires switching from a backend with a
split layout (e.g., Ceph, Swift, Cinder, S3) to one that uses a single
layout (e.g., File, NFS). The glance-operator webhooks prevent updates to
the layout, as such changes impact critical configuration elements like the
number of PersistentVolumeClaims (PVCs), their size, the image cache setup,
and other components. For further details, refer to the storage requirements
documentation.
Adding a new GlanceAPI assumes an OpenStackControlPlane is already
configured and can be done with the following steps:
- Create a
glance-api.yamlspec file. In this example, there are twoGlanceAPIinstances in theOpenStackControlPlane: the existing API usesSwiftas its backend, while the new API usesFilewith a single layout and one replica.
spec:
glance:
template:
databaseInstance: openstack
keystoneEndpoint: default
glanceAPIs:
default:
customServiceConfig: |
[DEFAULT]
enabled_backends = default_backend:swift
[glance_store]
default_backend = default_backend
[default_backend]
swift_store_create_container_on_put = True
swift_store_auth_version = 3
swift_store_auth_address = {{ .KeystoneInternalURL }}
swift_store_endpoint_type = internalURL
swift_store_user = service:glance
swift_store_key = {{ .ServicePassword }}
swift_store_region = {{ .Region }}
preserveJobs: false
replicas: 3
default1:
type: single
replicas: 1
storage:
storageRequest: 10G
Note:
Retrieve the initial Glance spec from an existing control plane using the
following command:
$ oc get oscp openstack-galera-network-isolation -o yaml | yq '.spec.glance'
Clean up the output to include only relevant details for updating the
OpenStackControlPlane.
- Apply the new spec file:
$ oc -n $NAMESPACE patch openstackcontrolplane $(oc get oscp -o custom-columns=NAME:.metadata.name --no-headers) --type=merge --patch-file=glance_spec.yaml
- Verify the new
GlanceAPIdeployment:
oc -n $NAMESPACE get pods -l service=glance
- Replace
$NAMESPACEwith the actual namespace where theOpenStackControlPlaneis deployed.
You can perform sequential updates to patch customServiceConfig or adjust keystoneEndpoint if you want a new API to serve as the main OpenStack CLI entry point.
For simpler requirements, skip Steps 1 and 2 and deploy an additional API instance directly with:
$ oc -n $NAMESPACE patch openstackcontrolplane $(oc get oscp -o custom-columns=NAME:.metadata.name --no-headers) --type=merge -p='{"spec": {"glance": {"template": {"keystoneEndpoint":"default1", "glanceAPIs": {"default1": {"type": "single", "networkAttachments":["storage"]}}}}}}'
This command adds the following structure to the control plane:
spec:
glance:
template:
keystoneEndpoint: default
glanceAPIs:
default1:
type: single
replicas: 1
Decommissioning a GlanceAPI requires patching the OpenStackControlPlane and
perform at least two actions:
- Delete the
GlanceAPICR and the kubernetes associated objects (Pods,StatefulSets) - Update the
keystoneEndpointto point to an activeAPI
Note:
You cannot delete a GlanceAPI if it is the only API in the
OpenStackControlPlane, nor can you point keystoneEndpoint to a non-existent
API under the glanceAPIs section.
Removing an existing GlanceAPI can be achieved with the following procedure:
- Verify that more that one
GlanceAPIis deployed in theOpenStackControlPlane:
oc -n $NAMESPACE get oscp $(oc get oscp -o custom-columns=NAME:.metadata.name --no-headers) -o jsonpath='{.spec.glance.template.glanceAPIs}' | jq
{
"default": {
"apiTimeout": 60,
"imageCache": {
"cleanerScheduler": "*/30 * * * *",
"prunerScheduler": "1 0 * * *",
"size": ""
},
"networkAttachments": [
"storage"
],
"override": {},
"replicas": 1,
"resources": {},
"storage": {},
"tls": {
"api": {
"internal": {},
"public": {}
}
},
"type": "split"
},
"default1": {
"apiTimeout": 60,
"imageCache": {
"cleanerScheduler": "*/30 * * * *",
"prunerScheduler": "1 0 * * *",
"size": ""
},
"override": {},
"replicas": 1,
"resources": {},
"storage": {},
"tls": {
"api": {
"internal": {},
"public": {}
}
},
"type": "split"
}
}
- Identify the current
keystonecatalogGlanceAPI:
$ oc -n $NAMESPACE get oscp $(oc get oscp -o custom-columns=NAME:.metadata.name --no-headers) -o jsonpath='{.spec.glance.template.keystoneEndpoint}'
default
- (Optional): Update the Keystone endpoint. If the API to be removed is
currently associated with the keystone catalog, switch the
KeystoneEndpointto another API:
$ oc -n $NAMESPACE patch openstackcontrolplane $(oc get oscp -o custom-columns=NAME:.metadata.name --no-headers) --type=merge -p='{"spec": {"glance": {"template": {"keystoneEndpoint":"default1"}}}}'
- Verify the new API has an associated keystone endpoint:
$ oc exec -it openstackclient bash -- openstack endpoint list | grep image
| a32dfa92fafd40588f3fef699744511f | regionOne | glance | image | True | internal | https://glance-default1-internal.openstack.svc:9292 |
| c4804456ee37464ea48e5258e7f29d7a | regionOne | glance | image | True | public | https://glance-default1-public-openstack.apps-crc.testing |
- Remove the
GlanceAPIcalleddefault:
$ oc -n $NAMESPACE patch openstackcontrolplane $(oc get oscp -o custom-columns=NAME:.metadata.name --no-headers) --type=json -p="[{'op': 'remove', 'path': '/spec/glance/template/glanceAPIs/default'}]"
and observe the Pods and the associated resources are deleted.
Note: The above process does not delete any PersistentVolumeClaims (PVCs) associated with the decommissioned API. This is by design, allowing re-adding the API with previous settings without data loss.
- Replace
$NAMESPACEwith the actual namespace where theOpenStackControlPlaneis deployed.
Conditions represent a critical aspect for reconciliation because they allow:
- to evaluate the status of a particular component validating a set of conditions
- give a feedback to the end user about the current state of the Deployment
- identify the status of the underlying GlanceAPIs and report their healhy to the upper level CR
The document
docs/conditions
describes the meaning of the k8s-operators shared Conditions.
Conditions are re-evaluated when a new Reconcile loop starts, and if one of
them is different from what was previously registered, an update is performed,
and it allows to give immediate feedback to the user.
Conditions are initially marked as Unknown, while the general ReadyCondition
is marked as False because we assume that the Deployment is in progress but
not Ready at the same time, until the entire set of conditions are evaluated.
Conditions can be seen as a checklist or steps within the reconcile loop, and
while the loop proceed to the end, each of them is evaluated.
If a particular component with an associated condition is not Ready, an error
is returned from the operator, and its failing condition is marked as False
with an appropriate error message.
If the end of the loop is reached, it means we passed through all the steps and
the Conditions are marked to True: it is possible, at this point, to mark the
overall ReadyCondition to Status=True as well and Mirror the result to the
top-level CR.
Conditions are always Mirrored using the defer function, so that we always
have an information to the ReadyCondition that reflects the point where the
loop is exiting/failing.
The general IsReady function is used as a wrapper for the ReadyCondition
boolean, and it can be used to get the status of the instance. In general
in the glance-operator we make the following assumptions:
- A Glance/GlanceAPI is considered
Readyif all the subconditions are verified - A Deployment (intended as the
StatefulSetthat represents the GlanceAPIs) is consideredReadyif the number ofReplicasspecified in theGlanceCR spec is equal to the number of available instances (ReadyCount).
Glance requires a staging area to manipulate data during an import operation.
It is possible to copy image data into multiple stores using image import
workflow, and all the import methods and plugins are based on this flow,
hence some persistence is required for the resulting Glance Pod.
PVCs represent the main Storage interface for the Glance service, and it's
important to have a clear picture of how this interface is implemented and how
it can be used in this context.
In general, a Storage struct is exposed by the glance-operator API, and it
helps to choose the best layout for a particular glanceAPI. If the storage
layout is not defined for a single glanceAPI, it will be inherited by the
top-level definition. When defining the storage requirements for a GlanceAPI,
it is possible to choose between two different models:
- External
- PVC
If External is chosen, no PVCs are created, and from a Storage point of
view the GlanceAPI acts like a stateless instance, where no persistence is
provided. In this case, it's expected that the human operator provides some
persistence via extraMounts.
For this particular scenario, the most popular choice is NFS, and it can be
mapped to /var/lib/glance/os_glance_staging_store
...
default:
storage:
external: true
...
...
extraMounts:
- extraVol:
- extraVolType: NFS
mounts:
- mountPath: /var/lib/glance/os_glance_staging_store
name: nfs
volumes:
- name: nfs
nfs:
path: <NFS PATH>
server: <NFS IP ADDRESS>It's important to note that the configuration described in the sample above
conflicts with the distributed image import feature that uses glance-direct
import method.
distributed image import requires a RWO storage plugged into a particular
instance: it owns the data and receives requests in case its staged data are
required for an upload operation.
When the External model is adopted, if Ceph is used as a backend and an
image conversion operation is run in one of the existing replicas, the
glance-operator does not have to make any assumption about the underlying
storage that is tied to the staging area, and the conversion operation that
uses the os_glance_staging_store directory (within the Pod) interacts with
the RWX NFS backend provided via extraMounts.
With this scenario, an image-cache PVC can still be requested and mounted to
a subPath, unless the human administrator would like to manage it externally
via ExtraMounts (where mounts are realized using SubPath to avoid directory
overlapping).
External is not the default option though, and when a GlanceAPI instance
is deployed, a PVC is created and bound to /var/lib/glance according to the
StorageClass and StorageRequest passed as input.
...
default:
replicas: 3
storage:
storageClass: local-storage
storageRequest: 10G
...In this case, if Ceph is set as a backend, no dedicated image conversion
PVC is created, and the human operator must think about the PVC sizing in
advance: the size of the PVC should be at least up to the largest converted
image size.
Concurrent conversions within the same Pod might be problematic in terms of
PVC size: a conversion will fail or can't take place if the PVC is full and
there's no enough space, and the upload should be retried after the previous
conversion is over and the staging area space is released.
However, concurrent conversion operations might happen in different Pods:
it's recommended to deploy at least 3 replicas for a particular glanceAPI,
and this helps to handle heavy operations like image conversion.
While multiple replicas help to handle this problem better, if a PVC layout
is chosen it's important to highlight that the Storage requirements in terms of
sizing linearly increase with the number of glanceAPI instances and replicas.
num pvc = ∑([(local + image_cache) * num_replicas]) * layout
storage size = ∑([(storageRequest + imageCacheSize) * num_replicas]) * layout
where:
- local: Local
PVCbound to/var/lib/glance - image_cache: Cache
PVCbound to/var/lib/glance/image-cache - ∑ - API: we sum the resulting value for each API
- num_replicas: number of replicas for each glanceAPI (default is 3)
- layout:
split=2Pods ,single=1Pod,edge=1Pod
For a PVC based layout, the scale out of a glanceAPI in terms of replicas is
limited by the available Storage provided by the storageClass, and depends
on the StorageRequest.
The StorageRequest is a critical parameter, it can be globally defined for all
the glanceAPI, or defined with a different value for each API, and it will
influence scale out operations for each of them.
Other than a local PVC required to for the staging area, it is possible to
enable image cache, which is translated into an additional PVC bound to each
glanceAPI instance.
A glance-cache PVC is bound to /var/lib/glance/image-cache, and the
operator is responsible to configure the instance accordingly, setting both
image_cache_max_size and the image_cache_dir parameters.
The number of image cache PVCs follows the same rules described for the local
PVC, and the number of requested PVCs is proportional to the number of
replicas. In addition, it contributes to set the scale out limits discussed
above.
As per the assumptions described above, here's a few examples of GlanceAPIs
requirements in a common scenario based on the PVC model.
- GlanceAPI = 1
- num_replicas = 3
- no image cache
- storageRequest = 30G
- layout: single=1
num_pvc = (1 + 0) * 3 = 3 * 1 = 3
storage_size = 1 * [(30 + 0) * 3] = 90G
We can conclude that 90G is required for a single glanceAPI with 3 replicas.
- GlanceAPI: 3
- num_replicas = 3
- image_cache_size = 20G
- storageRequest = 30G
- layout: edge (edge = 1)
api0
-> replica1 (20 + 30 = 50G)
-> replica2 (20 + 30 = 50G) => 150G (6 PVCs)
-> replica3 (20 + 30 = 50G)
api1
-> replica1 (20 + 30 = 50G)
-> replica2 (20 + 30 = 50G) => 150G (6 PVCs)
-> replica3 (20 + 30 = 50G)
api2
-> replica1 (20 + 30 = 50G)
-> replica2 (20 + 30 = 50G) => 150G (6 PVCs)
-> replica3 (20 + 30 = 50G)
num_pvc = [(3 + 3) * 3] * 1 = 18
storage_size = 3 * [(30 + 20) * 3] = 450G
We can conclude that 150G is required for each API: given we have 3 APIs the total number is 450G.
Assume we have the following Glance CR
...
glanceAPIs:
api0:
type: single
replicas: 3
api1:
type: single
replicas: 3
storage:
external: true
api2:
type: single
replicas: 3
imageCache:
size: 5G
storage:
storageRequest: 30G
storageClass: local-storage
api3:
type: single
replicas: 3
storage:
external: true
secret: osp-secret
storage:
storageClass: local-storage
storageRequest: 10G
...
As per the CR above, we can gather the following information.
api0
---
- num_replicas = 3
- no image Cache
- storageRequest = 10G (inherited by the top-level definition)
num_pvc = 3
storage_size = 10 *3 = 30G
api1
---
- num_replicas = 3
- no image cache (external = true)
- no storageRequest (external = true)
num_pvc = 0
storage_size = 0
The storage should be externally provisioned
api2
---
- num_replicas = 3
- image_cache_size = 5G
- storageRequest = 30G
- layout = single
num_pvc = 2 * 3 = 6
storage_size = (30 + 5) * 3 = 105G
api3
---
- num_replicas = 3
- image_cache_size = 2G
- no storageRequest (external = true)
The storage should be externally provisioned
num pvc = ∑([(local + image_cache) * num_replicas]) * layout
num_pvc = Sum(3 + 0 + 6 + 0) = 9
storage_size = Sum(30 + 0 + 105 + 0) = 135G
Note:
we assume a local-storage storageClass exists and it's the only one used to
keep the example simple, and only single layout is used. In case of split
layout, the same API will double the required size.