|
| 1 | +--- |
| 2 | +title: "Vision for Namespaces, Horizontal Scalability" |
| 3 | +description: "Description of Alex's vision for how Namespaces and Horizontal Scalability can improve OpenBao" |
| 4 | +slug: vision-for-namespaces |
| 5 | +authors: cipherboy |
| 6 | +tags: [vision, community, technical] |
| 7 | +--- |
| 8 | + |
| 9 | +As the OpenBao community [starts development on Namespaces](https://github.com/openbao/openbao/issues/787) |
| 10 | +and the Horizontal Scalability Working Group has its kickoff, I wanted to take |
| 11 | +the opportunity to put forward a blog post describing how these two groups' |
| 12 | +work can compliment each other and provide an alternative path forward other |
| 13 | +than Vault Enterprise's Performance Secondary and Disaster Recovery clustering |
| 14 | +modes. |
| 15 | + |
| 16 | +<!-- truncate --> |
| 17 | + |
| 18 | +## Scaling Read Operations |
| 19 | + |
| 20 | +Most obviously, the first step for the Horizontal Scalability working group |
| 21 | +will be to [allow standby nodes to service requests](https://github.com/openbao/openbao/issues/569) |
| 22 | +as described in our project direction & roadmap: |
| 23 | + |
| 24 | +> 4. Allow HA standby nodes to service read-only (from a storage modification PoV) requests. (scalability) |
| 25 | +> - Currently HA mode standby nodes forward all requests up to the active node, preventing horizontal scalability of OpenBao. Due to limitations in Raft (only the active node can perform storage writes), we can't immediately scale writes. Thus, start by bringing these nodes "online" (loading the mount table, plugins, &c) and allowing them to service read-only requests, returning `ErrReadOnly` on storage write operations to trigger automatic request forwarding. |
| 26 | +
|
| 27 | +The shortcoming of this is that writes are not scalable: there is still only |
| 28 | +a single active writer node. Note that a read operation in this context refers |
| 29 | +to any API request (of any operation type) that results only in `get` or `list` |
| 30 | +("read") storage operations. Any operations involving a storage write (`put` |
| 31 | +or `delete`) would be forwarded to the active node for processing. |
| 32 | + |
| 33 | +This behaves similarly to Performance Secondary Standby nodes. |
| 34 | + |
| 35 | +## Scaling Write Operations |
| 36 | + |
| 37 | +In our RFC for namespaces, there is the following future work item: |
| 38 | + |
| 39 | +> ### Per-Namespace Storage Segments |
| 40 | +> |
| 41 | +> At the physical storage level, a namespace could be implemented as a new database schema in PostgreSQL (with each plugin being a new database table) or a new disk directory in Raft/BoltDB. This can likely build on top of storage views, using the existing JSON namespace data to translate source path to destination. This would help with scaling OpenBao: each tenant would have its own data storage location and so could impact other tenants less. Theoretically this could even lead to per-segment unique storage backend depending on workload characteristics assuming no cross-segment consistency is required. |
| 42 | +> |
| 43 | +> However, this work is strictly disjoint from implementing namespaces as a feature and so will be done later. |
| 44 | +
|
| 45 | +In conjunction with additional changes to horizontal scalability, this change |
| 46 | +could allow each namespace to have a different active node dictated by its |
| 47 | +storage backend, distributing writes across the cluster. Notably, very few |
| 48 | +operations are truly cross-namespace; this is mostly limited to the creation |
| 49 | +and deletion of namespaces and mount move operations, which affects the |
| 50 | +namespace store in the parent context in addition to the actual target |
| 51 | +namespace. |
| 52 | + |
| 53 | +The benefit of this approach is that it doesn't affect plugins, token stores, |
| 54 | +ACL stores, or any other namespace-specific functionality. This is because |
| 55 | +our namespace design opted to place all namespace-specific data within the |
| 56 | +namespace's path (`/namespaces/<uuid>`) rather than mixing it within the |
| 57 | +root of storage (`/core` or `/sys`) across all namespaces. |
| 58 | + |
| 59 | +Additionally, this gives a natural place for segmenting storage, allowing |
| 60 | +smaller databases when used with PostgreSQL or Raft. This allows greater |
| 61 | +scalability as many have scalability problems when lots of data is written |
| 62 | +to a single backend instance. By supporting different storage types (e.g., |
| 63 | +mixing and matching PostgreSQL and Raft), namespaces with different SLAs |
| 64 | +or workload write/read ratios can be supported. |
| 65 | + |
| 66 | +One shortcoming with this approach is it doesn't allow scaling all types of |
| 67 | +workloads. For instance, a single PKI mount which stores certificates will |
| 68 | +not be horizontally scalable as writes will not be able to be distributed. |
| 69 | +However, the majority of large, diverse workloads will be able to be |
| 70 | +distributed because of this change. |
| 71 | + |
| 72 | +Another shortcoming is the complexity this entails for an operator. Some |
| 73 | +of this may be mitigated by allowing namespace-native configuration, without |
| 74 | +requiring full configuration file changes. |
| 75 | + |
| 76 | +## Disaster Recovery |
| 77 | + |
| 78 | +OpenBao recently added support for [Raft non-voter nodes](https://github.com/openbao/openbao/issues/578). |
| 79 | +This can likely form the basis of [disaster recovery](https://github.com/openbao/openbao/issues/38), |
| 80 | +especially if enablement for other storage backends (like PostgreSQL) is |
| 81 | +added. |
| 82 | + |
| 83 | +Notably in Raft, non-voter status means that these nodes do not contribute to |
| 84 | +quorum requirements, allowing writes to commit faster but still allowing the |
| 85 | +use of Raft to distribute updates. This means that adding non-voter nodes |
| 86 | +results in additional traffic from the leader but doesn't otherwise impact |
| 87 | +write speeds. By putting these non-voters in secondary data centers, with |
| 88 | +[the ability to promote non-voter nodes](https://github.com/openbao/openbao/pull/996), |
| 89 | +we can subsequently initiate a failover operation in the event the primary |
| 90 | +cluster goes down, with minimal data loss. |
| 91 | + |
| 92 | +PostgreSQL can similarly be extended to support a non-voter setup, wherein |
| 93 | +certain nodes will not attempt to become leaders unless updated later. This |
| 94 | +will behave similarly to Raft, allowing more read scalability via the use of |
| 95 | +read-only PostgreSQL replicas in other secondary data centers. |
| 96 | + |
| 97 | +By broadening [the use of transactions](https://github.com/openbao/openbao/issues/607), |
| 98 | +we can further guarantee that these other nodes are consistent, independent |
| 99 | +of what storage backend is used. |
| 100 | + |
| 101 | +Additionally, we could allow standby or non-voter nodes to serve read requests |
| 102 | +without an active leader. This could potentially be extended to support |
| 103 | +generating offline authentication tokens using JWTs or similar signature-based |
| 104 | +schemes for use with non-lease paths. This would allow for greater high |
| 105 | +availability in the event of a leadership outage. |
| 106 | + |
| 107 | +## Offline Recovery Mode |
| 108 | + |
| 109 | +Sometimes a hybrid replication mode would be preferred; Vault Enterprise |
| 110 | +supports this via a [path filtering operation](https://developer.hashicorp.com/vault/api-docs/system/replication/replication-performance#create-paths-filter). |
| 111 | + |
| 112 | +When we combine the [earlier work for per-namespace storage backends](#scaling-write-operations) |
| 113 | +with an additional Namespaces future work item: |
| 114 | + |
| 115 | +> ### Per-Namespace Seal Mechanisms |
| 116 | +> |
| 117 | +> The existing Vault Enterprise Namespace feature supports locking and unlocking namespaces by an operator with access to the parent namespace. While potentially useful to limit requests to a namespace without impacting other users, we could add a similar mechanism using the Barrier + Keyring functionality to also give us per-tenant encryption. This also lets the tenant control their own encryption keys. With lazy loading of namespaces (preventing inbound requests unless the namespace is unsealed), this behaves similarly to locking and unlocking the namespace though doesn’t conflict with it. |
| 118 | +> |
| 119 | +> However, this work is strictly disjoint from implementing namespaces as a feature and so will be done later. |
| 120 | +
|
| 121 | +If we implement non-hierarchical namespaces, which do not chaining to `root` |
| 122 | +as a parent for tokens, ACL policies, or layered sealing, this allows us to |
| 123 | +have cluster-independent namespaces. We would additionally need the Shamir |
| 124 | +fallback [from the parallel unseal RFCs](https://github.com/openbao/openbao/issues/1021). |
| 125 | + |
| 126 | +This gives two nice properties: |
| 127 | + |
| 128 | +1. We can have a stronger path filtering, allowing replication of namespaces |
| 129 | + in disjoint cluster topologies. For instance, a secondary site may have only |
| 130 | + a subset of namespaces, which it may be non-voter on until promoted to |
| 131 | + leader during an outage. |
| 132 | +2. A namespace could potentially be shared by two different organizations as |
| 133 | + a form of secret sharing with some peer establishment protocol. |
| 134 | + |
| 135 | +Notably, the parallel unseal allows either Shamir's based initial |
| 136 | +establishment or the use of public keys explicitly provisioned for local-only |
| 137 | +seal material usable by the remote cluster. This allows local disaster nodes |
| 138 | +to survive the outage of the primary seal mechanism. |
| 139 | + |
| 140 | +## Lazy Loading |
| 141 | + |
| 142 | +By making Namespaces have their own seal mechanism, we'll be forcing OpenBao |
| 143 | +to handle the scenario when a namespace cannot be loaded. We have two options: |
| 144 | + |
| 145 | +1. To hard fail, requiring operators to rectify the problem immediately. With |
| 146 | + things like parallel unseal, this should be doable unless a storage engine |
| 147 | + itself is down. |
| 148 | +2. To soft fail, refusing to load the namespace (and any children) but |
| 149 | + continuing regular operation on all other namespaces. |
| 150 | + |
| 151 | +This sits on different sides of a security/availability problem: we could be |
| 152 | +available, but not processing key revocations because a single namespace is |
| 153 | +offline, or we could be in a similar place, not processing all revocations |
| 154 | +because a single offline namespace resulted in all namespaces being |
| 155 | +unavailable. |
| 156 | + |
| 157 | +I'd like to see namespaces, and eventually mounts, be lazily loadable. This |
| 158 | +will likely require new interfaces for backends to indicate when they'd next |
| 159 | +like their Rollback function to be issued, if that occurs too infrequently. |
| 160 | +And mounts will need to be audited for lease expiry, with some window for |
| 161 | +mount reuse without unmounting refreshed on last request. |
| 162 | + |
| 163 | +But, this will allow us to scale OpenBao to workloads with lots of |
| 164 | +infrequently-accessed mounts without requiring all nodes in a cluster have |
| 165 | +the capability of loading the entire system at once. |
0 commit comments