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.