github.com/operator-framework/operator-lifecycle-manager@v0.30.0/doc/design/operatorgroups.md (about) 1 # Operator Multitenancy with OperatorGroups 2 3 An `OperatorGroup` is an OLM resource that provides rudimentary multitenant configuration to OLM installed operators. 4 5 ## OperatorGroup Overview 6 7 * An `OperatorGroup` selects a set of target namespaces in which to generate required RBAC access for its member operators. 8 * The set of target namespaces is provided via a comma-delimited string stored in the `olm.targetNamespaces` annotation. This annotation is applied to member operator's `ClusterServiceVersion` (CSV) instances and is projected into their deployments. It is accessible to operator containers using [The Downward API](https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/#the-downward-api) 9 * An operator is said to be a [member of an `OperatorGroup`](#operatorgroup-membership) if its CSV exists in the same namespace as the `OperatorGroup` and its CSV's [`InstallModes` support the set of namespaces targeted by the `OperatorGroup`](#installmodes-and-supported-operatorgroups) 10 * In order to transition, a CSV must be an active member of an `OperatorGroup` that has no [provided API conflicts with intersecting `OperatorGroups`](#operatorgroup-intersection) 11 12 ## OperatorGroup Membership 13 14 An operator defined by CSV `csv-a` is said to be a _member_ of `OperatorGroup` `op-a` in namespace `ns-a` if both of the following hold: 15 * `op-a` is the only `OperatorGroup` in `ns-a` 16 * `csv-a`'s `InstallMode`s support `op-a`'s target namespace set 17 18 ### TooManyOperatorGroups 19 20 If there exists more than one `OperatorGroup` in a single namespace, any CSV created in that namespace will transition to a failure state with reason `TooManyOperatorGroups`. CSVs in a failed state for this reason will transition to pending once the number of `OperatorGroup`s in their namespaces reaches one. 21 22 ### InstallModes and Supported OperatorGroups 23 24 An `InstallMode` consists of an `InstallModeType` field and a boolean `Supported` field. A CSV's spec can contain a set of `InstallModes` of four distinct `InstallModeTypes`: 25 * `OwnNamespace`: If supported, the operator can be a member of an `OperatorGroup` that selects its own namespace 26 * `SingleNamespace`: If supported, the operator can be a member of an `OperatorGroup` that selects one namespace 27 * `MultiNamespace`: If supported, the operator can be a member of an `OperatorGroup` that selects more than one namespace 28 * `AllNamespaces`: If supported, the operator can be a member of an `OperatorGroup` that selects all namespaces (target namespace set is the empty string "") 29 30 > Note: If a CSV's spec omits an entry of `InstallModeType`, that type is considered unsupported unless support can be inferred by an existing entry that implicitly supports it. 31 32 ### UnsupportedOperatorGroup 33 34 If a CSV's `InstallMode`s do not support the target namespace selection of the `OperatorGroup` in its namespace, the CSV will transition to a failure state with reason `UnsupportedOperatorGroup`. CSVs in a failed state for this reason will transition to pending once either the `OperatorGroups`'s target namespace selection changes to a supported configuration, or the CSV's `InstallMode`s are modified to support the `OperatorGroup`'s target namespace selection. 35 36 ## Target Namespace Selection 37 38 Select the set of namespaces by specifying a label selector with the `spec.selector` field: 39 40 ```yaml 41 apiVersion: operators.coreos.com/v1alpha2 42 kind: OperatorGroup 43 metadata: 44 name: my-group 45 namespace: my-namespace 46 spec: 47 selector: 48 matchLabels: 49 cool.io/prod: "true" 50 ``` 51 52 or by explicitly naming target namespaces with the `spec.targetNamespaces` field: 53 54 ```yaml 55 apiVersion: operators.coreos.com/v1alpha2 56 kind: OperatorGroup 57 metadata: 58 name: my-group 59 namespace: my-namespace 60 spec: 61 targetNamespaces: 62 - my-namespace 63 - my-other-namespace 64 - my-other-other-namespace 65 ``` 66 67 > Note: If both `spec.targetNamespaces` and `spec.selector` are defined, `spec.selector` is ignored. 68 69 Additionally, a _global_ `OperatorGroup` (which selects all namespaces) is specified by omitting both `spec.selector` and `spec.targetNamespaces`: 70 71 ```yaml 72 apiVersion: operators.coreos.com/v1alpha2 73 kind: OperatorGroup 74 metadata: 75 name: my-group 76 namespace: my-namespace 77 ``` 78 79 The resolved set of selected namespaces is surfaced in an `OperatorGroup`'s `status.namespaces` field. A global `OperatorGroup`'s `status.namespace` is of length 1 and contains the empty string, `""`, which signals a consuming operator that it should watch all namespaces. 80 81 > Note: The consuming operator must know to treat `""` as an all namespace configuration. 82 83 ## OperatorGroup CSV Annotations 84 85 Member CSVs of an `OperatorGroup` get the following annotations: 86 * `olm.operatorGroup=<group-name>` 87 * Contains the name of the `OperatorGroup` 88 * `olm.operatorGroupNamespace=<group-namespace>` 89 * Contains the namespace of the `OperatorGroup` 90 * `olm.targetNamespaces=<target-namespaces>` 91 * Contains a comma-delimited string listing the `OperatorGroup`'s target namespace selection. This annotation is projected onto the pod template of a CSV's deployments and can be consumed by a pod instance via [The Downward API](https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/#the-downward-api) 92 93 > Note: All annotations except `olm.targetNamespaces` are included with [copied CSVs](#copied-csvs). Omitting the `olm.targetNamespaces` annotation on copied CSVs prevents the names of target namespaces from being leaked between tenants. 94 95 ## Provided APIs Annotation 96 97 Information about what `GroupVersionKinds`s (GVK) are provided by an `OperatorGroup` are surfaced in an `olm.providedAPIs` annotation. The annotation's value is a string consisting of a set of `<Kind>.<version>.<group>`s delimited with commas. The GVKs of CRDs and APIServices provided by all active member CSVs of an `OperatorGroup` are included. 98 99 Here's an example of an `OperatorGroup` with a single active member CSV providing the PackageManifests resource: 100 101 ```yaml 102 apiVersion: operators.coreos.com/v1alpha2 103 kind: OperatorGroup 104 metadata: 105 annotations: 106 olm.providedAPIs: PackageManifest.v1alpha1.packages.apps.redhat.com 107 name: olm-operators 108 namespace: local 109 ... 110 spec: 111 selector: {} 112 serviceAccount: 113 metadata: 114 creationTimestamp: null 115 targetNamespaces: 116 - local 117 status: 118 lastUpdated: 2019-02-19T16:18:28Z 119 namespaces: 120 - local 121 ``` 122 123 ## RBAC 124 125 When an `OperatorGroup` is created, 3 ClusterRoles each containing a single AggregationRule are generated: 126 * `<operatorgroup-name>-admin` 127 * ClusterRoleSelector set to match the `olm.opgroup.permissions/aggregate-to-admin: <operatorgroup-name>` label 128 129 * `<operatorgroup-name>-edit` 130 * ClusterRoleSelector set to match the `olm.opgroup.permissions/aggregate-to-edit: <operatorgroup-name>` label 131 132 * `<operatorgroup-name>-view` 133 * ClusterRoleSelector set to match the `olm.opgroup.permissions/aggregate-to-view: <operatorgroup-name>` label 134 135 When a CSV becomes an active member of an `OperatorGroup` and is not in a failed state with reason InterOperatorGroupOwnerConflict, the following RBAC resources are generated: 136 * For each provided API resource from a CRD: 137 * A `<kind.group-version-admin>` ClusterRole is generated with the `*` verb on `<group>` `<kind>` with aggregation labels `rbac.authorization.k8s.io/aggregate-to-admin: true` and `olm.opgroup.permissions/aggregate-to-admin: <operatorgroup-name>` 138 * A `<kind.group-version-edit>` ClusterRole is generated with the `create, update, patch, delete` verbs on `<group>` `<kind>` with aggregation labels `rbac.authorization.k8s.io/aggregate-to-edit: true` and `olm.opgroup.permissions/aggregate-to-edit: <operatorgroup-name>` 139 * A `<kind.group-version-view>` ClusterRole is generated with the `get, list, watch` verbs on `<group>` `<kind>` with aggregation labels `rbac.authorization.k8s.io/aggregate-to-view: true` and `olm.opgroup.permissions/aggregate-to-view: <operatorgroup-name>` 140 * A `<kind.group-version-view-crd>` ClusterRole is generated with the `get` verb on `apiextensions.k8s.io` `customresourcedefinitions` `<crd-name>` with aggregation labels `rbac.authorization.k8s.io/aggregate-to-view: true` and `olm.opgroup.permissions/aggregate-to-view: <operatorgroup-name>` 141 142 * For each provided API resource from an APIService: 143 * A `<kind.group-version-admin>` ClusterRole is generated with the `*` verb on `<group>` `<kind>` with aggregation labels `rbac.authorization.k8s.io/aggregate-to-admin: true` and `olm.opgroup.permissions/aggregate-to-admin: <operatorgroup-name>` 144 * A `<kind.group-version-edit>` ClusterRole is generated with the `create, update, patch, delete` verbs on `<group>` `<kind>` with aggregation labels `rbac.authorization.k8s.io/aggregate-to-edit: true` and `olm.opgroup.permissions/aggregate-to-edit: <operatorgroup-name>` 145 * A `<kind.group-version-view>` ClusterRole is generated with the `get, list, watch` verbs on `<group>` `<kind>` with aggregation labels `rbac.authorization.k8s.io/aggregate-to-view: true` and `olm.opgroup.permissions/aggregate-to-view: <operatorgroup-name>` 146 147 * For CSV in the _global_ `OperatorGroup`: 148 * A ClusterRole and corresponding ClusterRoleBinding are generated for each permission defined in the CSV's permissions field. All resources generated are given the `olm.owner: <csv-name>` and `olm.owner.namespace: <csv-namespace>` labels 149 * Else for each target namespace: 150 * All Roles and RoleBindings in the operator namespace with the `olm.owner: <csv-name>` and `olm.owner.namespace: <csv-namespace>` labels are copied into the target namespace. 151 152 ## Copied CSVs 153 154 OLM will create copies of all active member CSVs of an `OperatorGroup` in each of that `OperatorGroup`'s target namespaces. The purpose of a Copied CSV is to tell users of a target namespace that a specific operator is configured to watch resources created there. Copied CSVs have a status reason _Copied_ and are updated to match the status of their source CSV. The `olm.targetNamespaces` annotation is stripped from copied CSVs before they are created on the cluster. Omitting the target namespace selection avoids an unnecessary information leak. Copied CSVs are deleted when their source CSV no longer exists or the operator group their source CSV belongs to no longer targets the copied CSV's namespace. 155 156 ## Static OperatorGroups 157 158 An `OperatorGroup` is _static_ if it's `spec.staticProvidedAPIs` field is set to __true__. As a result, OLM does not modify the OperatorGroups's `olm.providedAPIs` annotation, which means that it can be set in advance. This is useful when a user wishes to use an `OperatorGroup` to prevent [resource contention](#what-can-go-wrong) in a set of namespaces, but does not have active member CSVs that provide the APIs for those resources. 159 160 Here's an example of an `OperatorGroup` that "protects" prometheus resources in all namespaces with the `something.cool.io/cluster-monitoring: "true"` annotation: 161 162 ```yaml 163 apiVersion: operators.coreos.com/v1alpha2 164 kind: OperatorGroup 165 metadata: 166 name: cluster-monitoring 167 namespace: cluster-monitoring 168 annotations: 169 olm.providedAPIs: Alertmanager.v1.monitoring.coreos.com,Prometheus.v1.monitoring.coreos.com,PrometheusRule.v1.monitoring.coreos.com,ServiceMonitor.v1.monitoring.coreos.com 170 spec: 171 staticProvidedAPIs: true 172 selector: 173 matchLabels: 174 something.cool.io/cluster-monitoring: "true" 175 ``` 176 177 ## OperatorGroup Intersection 178 179 ### OperatorGroup Intersection Terminology 180 181 * Two `OperatorGroup`s are said to be _intersecting_ if the intersection of their target namespace sets __is not the empty set__ 182 * Two `OperatorGroup`s are said to have _intersecting provided APIs_ if they are __intersecting__ and the intersection of their provided API sets (defined by `olm.providedAPIs` annotations) __is not the empty set__ 183 184 ### What Can Go Wrong? 185 186 `OperatorGroup`s with _intersecting provided APIs_ can compete for the same resources in the set of intersecting namespaces. 187 188 ### Rules for Intersection 189 190 Each time an active member CSV syncs, OLM queries the cluster for the set of _intersecting provided APIs_ between the CSV's `OperatorGroup` and all other `OperatorGroup`s. OLM then checks if that set __is the empty set__: 191 * If __true__ and the CSV's provided APIs __are a subset__ of the `OperatorGroup`'s: 192 * Continue transitioning 193 * If __true__ and the CSV's provided APIs __are not a subset__ of the `OperatorGroup`'s: 194 * If the `OperatorGroup` [__is static__](#static-operatorgroups): 195 * Clean up any deployments that belong to the CSV 196 * Transition the CSV to a failed state with status reason CannotModifyStaticOperatorGroupProvidedAPIs 197 * Else: 198 * Replace the `OperatorGroup`'s `olm.providedAPIs` annotation with the union of itself and the CSV's provided APIs 199 * If __false__ and the CSV's provided APIs __are not a subset__ of the `OperatorGroup`'s: 200 * Clean up any deployments that belong to the CSV 201 * Transition the CSV to a failed state with status reason InterOperatorGroupOwnerConflict 202 * If __false__ and the CSV's provided APIs __are a subset__ of the `OperatorGroup`'s: 203 * If the `OperatorGroup` [__is static__](#static-operatorgroups): 204 * Clean up any deployments that belong to the CSV 205 * Transition the CSV to a failed state with status reason CannotModifyStaticOperatorGroupProvidedAPIs 206 * Else: 207 * Replace the `OperatorGroup`'s `olm.providedAPIs` annotation with the difference between itself and the CSV's provided APIs 208 209 > Note: Failure states caused by `OperatorGroup`s are non-terminal. 210 211 > Note: When checking intersection rules, an `OperatorGroup`'s namespace is always included as part of its selected target namespaces. 212 213 Each time an `OperatorGroup` syncs: 214 * The set of provided APIs from active member CSV's is calculated from the cluster (ignoring [copied CSVs](#copied-csvs)) 215 * The cluster set is compared to `olm.providedAPIs`: 216 * If `olm.providedAPIs` contains any extraneous provided APIs: 217 * `olm.providedAPIs` is pruned of any extraneous provided APIs (not provided on cluster) 218 * All CSVs that provide the same APIs across all namespaces (including those removed) are requeued. This notifies conflicting CSVs in intersecting groups that their conflict has possibly been resolved, either through resizing or through deletion of the conflicting CSV.