* another * are * availability * available * bootstrap * boundaries * ceph * certificate * class * codifies * consuming * corrupted * createor * csi * deployments * exceeded * execute * filesystem * healthiness * heuristics * immediately * insecure * installed * isolated * maintained * maximum * minute * monitor * new * nginx * nonexistent * not * occurs * omitempty * operator * orchestration * persistentvolumes * placement * preexisting * prometheus * protecting * provisioner * purposes * reconcile * regex * related * requests * returns * rubbish * running * schedulable * schedule * serviceaccount * simulating * snapshots * statement * static * tenants * the * unavailable * volumeattachment * waiting * with * wrapper * zonegroup Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
172 lines
9.6 KiB
Markdown
172 lines
9.6 KiB
Markdown
# Updating OSDs in parallel
|
|
**Targeted for v1.6**
|
|
|
|
## Background
|
|
In clusters with large numbers of OSDs, it can take a very long time to update all of the OSDs. This
|
|
occurs on updates of Rook and Ceph both for major as well as the most minor updates. To better
|
|
support large clusters, Rook should be able to update (and upgrade) multiple OSDs in parallel.
|
|
|
|
|
|
## High-level requirements
|
|
|
|
### Ability to set a maximum number of OSDs to be updated simultaneously
|
|
In the worst (but unlikely) case, all OSDs which are updated for a given parallel update operation
|
|
might fail to come back online after they are updated. Users may wish to limit the number of OSDs
|
|
updated in parallel in order to avoid too many OSDs failing in this way.
|
|
|
|
### Cluster growth takes precedence over updates
|
|
Adding new OSDs to a cluster should occur as quickly as possible. This allows users to make use of
|
|
newly added storage as quickly as possible, which they may need for critical applications using the
|
|
underlying Ceph storage. In some degraded cases, adding new storage may be necessary in order to
|
|
allow currently-running Ceph OSDs to be updated without experiencing storage cluster downtime.
|
|
|
|
This does not necessarily mean that adding new OSDs needs to happen before updates.
|
|
|
|
This prioritization might delay updates significantly since adding OSDs not only adds capacity to
|
|
the Ceph cluster but also necessitates data rebalancing. Rebalancing generates data movement which
|
|
needs to settle for updates to be able to proceed.
|
|
|
|
### OSD updates should not starve other resources of updates
|
|
For Ceph cluster with huge numbers of OSDs, Rook's process to update OSDs should not starve other
|
|
resources out of the opportunity to get configuration updates.
|
|
|
|
|
|
## Technical implementation details
|
|
|
|
### Changes to Ceph
|
|
The Ceph manager (mgr) will add functionality to allow querying the maximum number of OSDs that are
|
|
okay to stop safely. The command will take an initial OSD ID to include in the results. It should
|
|
return error if the initial OSD cannot be stopped safely. Otherwise it returns a list of 1 or more
|
|
OSDs that can be stopped safely in parallel. It should take a `--max=<int>` parameter that limits
|
|
the number of OSDs returned.
|
|
|
|
It will look similar to this on the command line `ceph osd ok-to-stop $id --max $int`.
|
|
|
|
The command will have an internal algorithm that follows the flow below:
|
|
1. Query `ok-to-stop` for the "seed" OSD ID. This represents the CRUSH hierarchy bucket at the "osd"
|
|
(or "device") level.
|
|
2. If the previous operation reports that it safe to update, batch query `ok-to-stop` for all OSDs
|
|
that fall under the CRUSH bucket one level up from the current level.
|
|
3. Repeat step 3 moving up the CRUSH hierarchy until one of the following two conditions:
|
|
1. The number of OSDs in the batch query is greater than or equal to the `max` parameter, OR
|
|
2. It is no longer `ok-to-stop` all OSDs in the CRUSH bucket.
|
|
4. Update OSD Deployments in parallel for the last CRUSH bucket where it was `ok-to-stop` the OSDs.
|
|
- If there are more OSDs in the CRUSH bucket than allowed by the user that are okay to stop,
|
|
return only the `max` number of OSD IDs from the CRUSH bucket.
|
|
|
|
The pull request for this feature in the Ceph project can be found at
|
|
https://github.com/ceph/ceph/pull/39455.
|
|
|
|
### Rook Operator Workflow
|
|
1. Build an "existence list" of OSDs which already have Deployments created for them.
|
|
1. Build an "update queue" of OSD Deployments which need updated.
|
|
1. Start OSD prepare Jobs as needed for OSDs on PVC and OSDs on nodes.
|
|
1. Note which prepare Jobs are started
|
|
1. Provision Loop
|
|
1. If all prepare Jobs have been completed and the update queue is empty, stop Provision Loop.
|
|
1. If there is a `CephCluster` update/delete, stop Provision Loop with a special error.
|
|
1. Create OSDs: if a prepare Job has completed, read the results.
|
|
1. If any OSDs reported by prepare Job do not exist in the "existence list", create them.
|
|
1. Mark the prepare Job as completed.
|
|
1. Restart Provision Loop.
|
|
1. Update OSDs: if the update queue is not empty, update a batch of OSD Deployments.
|
|
1. Query `ceph osd ok-to-stop <osd-id> --max=<int>` for each OSD in the update queue until
|
|
a list of OSD IDs is returned.
|
|
- If no OSDs in the update queue are okay to stop, Restart Provision Loop.
|
|
1. Update all of the OSD Deployments in parallel.
|
|
1. Record any failures.
|
|
1. Remove all OSDs from the batch from the update queue (even failures).
|
|
1. Restart Provision Loop.
|
|
1. If there are any recorded errors/failures, return with an error. Otherwise return success.
|
|
|
|
### How to make sure Ceph cluster updates are not starved by OSD updates
|
|
Because [cluster growth takes precedence over updates](#cluster-growth-takes-precedence-over-updates),
|
|
it could take a long time for all OSDs in a cluster to be updated. In order for Rook to have
|
|
opportunity to reconcile other components of a Ceph cluster's `CephCluster` resource, Rook should
|
|
ensure that the OSD update reconciliation does not create a scenario where the `CephCluster` cannot
|
|
be modified in other ways.
|
|
|
|
https://github.com/rook/rook/pull/6693 introduced a means of interrupting the current OSD
|
|
orchestration to handle newer `CephCluster` resource changes. This functionality should remain so
|
|
that user changes to the `CephCluster` can begin reconciliation quickly. The Rook Operator should
|
|
stop OSD orchestration on any updates to the `CephCluster` spec and be able to resume OSD
|
|
orchestration with the next reconcile.
|
|
|
|
### How to build the existence list
|
|
List all OSD Deployments belonging to the Rook cluster. Build a list of OSD IDs matching the OSD
|
|
Deployments. Record this in a data structure that allows O(1) lookup.
|
|
|
|
### How to build the update queue
|
|
List all OSD Deployments belonging to the Rook cluster to use as the update queue. All OSDs should
|
|
be updated in case there are changes to the CephCluster resource that result in OSD deployments
|
|
being updated.
|
|
|
|
The minimal information each item in the queue needs is only the OSD ID. The OSD Deployment managed
|
|
by Rook can easily be inferred from the OSD ID.
|
|
|
|
Note: A previous version of this design planned to ignore OSD Deployments which are already updated.
|
|
The plan was to identify OSD Deployments which need updated by looking at the OSD Deployments for:
|
|
(1) a `rook-version` label that does not match the current version of the Rook operator AND/OR
|
|
(2) a `ceph-version` label that does not match the current Ceph version being deployed. This is an
|
|
invalid optimization that does not account for OSD Deployments changing due to CephCluster resource
|
|
updates. Instead of trying to optimize, it is better to always update OSD Deployments and rely on the
|
|
lower level update calls to finish quickly when there is no update to apply.
|
|
|
|
### User configuration
|
|
|
|
#### Modifications to `CephCluster` CRD
|
|
Establish a new `updatePolicy` section in the `CephCluster` `spec`. In this section, users can
|
|
set options for how OSDs should be updated in parallel. Additionally, we can move some existing
|
|
one-off configs related to updates to this section for better coherence. This also allows for
|
|
a natural location where future update options can be added.
|
|
|
|
```yaml
|
|
apiVersion: ceph.rook.io/v1
|
|
kind: CephCluster
|
|
# ...
|
|
spec:
|
|
# ...
|
|
# Move these to the new updatePolicy but keep them here for backwards compatibility.
|
|
# These can be marked deprecated, but do not remove them until CephCluster CRD v2.
|
|
skipUpgradeChecks:
|
|
continueUpgradeAfterChecksEvenIfNotHealthy:
|
|
removeOSDsIfOutAndSafeToDestroy:
|
|
|
|
# Specify policies related to updating the Ceph cluster and its components. This applies to
|
|
# minor updates as well as upgrades.
|
|
updatePolicy:
|
|
# skipUpgradeChecks is merely relocated from spec
|
|
skipUpgradeChecks: <bool, default=false>
|
|
|
|
# continueUpgradeAfterChecksEvenIfNotHealthy is merely relocated from spec
|
|
continueUpgradeAfterChecksEvenIfNotHealthy: <bool, default=false, relocated from spec>
|
|
|
|
# allow for future additions to updatePolicy like healthErrorsToIgnore
|
|
|
|
# Update policy for OSDs.
|
|
osds:
|
|
# removeIfOutAndSafeToDestroy is merely relocated from spec (removeOSDsIfOutAndSafeToRemove)
|
|
removeIfOutAndSafeToDestroy: <bool, default=false>
|
|
|
|
# Max number of OSDs in the cluster to update at once. Rook will try to update this many OSDs
|
|
# at once if it is safe to do so. It will update fewer OSDs at once if it would be unsafe to
|
|
# update maxInParallelPerCluster at once. This can be a discrete number or a percentage of
|
|
# total OSDs in the Ceph cluster.
|
|
# Rook defaults to updating 15% of OSDs in the cluster simultaneously if this value is unset.
|
|
# Inspired by Kubernetes apps/v1 RollingUpdateDeployment.MaxUnavailable.
|
|
# Note: I think we can hide the information about CRUSH from the user since it is not
|
|
# necessary for them to understand that complexity.
|
|
maxInParallelPerCluster: <k8s.io/apimachinery/pkg/util/intstr.intOrString, default=15%>
|
|
```
|
|
|
|
Default `maxInParallelPerCluster`: Ceph defaults to keeping 3 replicas of an item or 2+1 erasure
|
|
coding. It should be impossible to update more than one-third (33.3%) of a default Ceph cluster at
|
|
any given time. It should be safe and fairly easy to update slightly less than half of one-third at
|
|
once, which rounds down to 16%. 15% is a more round number, so that is chosen instead.
|
|
|
|
|
|
## Future considerations
|
|
Some users may wish to update OSDs in a particular failure domain or zone completely before moving
|
|
onto updates in another zone to minimize risk from updates to a single failure domain. This is out
|
|
of scope for this initial design, but we should consider how to allow space to more easily implement
|
|
this change when it is needed. |