<a id="install-cmf-helm"></a>

# Install Confluent Manager for Apache Flink with Helm

Flink applications are deployed in Confluent Platform with Confluent Manager for Apache Flink®, a
central management component that enables users to securely manage a fleet of Flink jobs
across multiple environments.

This topic walks you through how to install Confluent Manager for Apache Flink (CMF) with Helm.

#### SEE ALSO
To manage Flink workloads across multiple Kubernetes clusters from a single CMF
installation, see [Manage Multiple Kubernetes Clusters in Confluent Manager for Apache Flink](../configure/kubernetes-clusters.md#cmf-kubernetes-clusters).

## Step 1: Confirm prerequisites​

1. Confirm you have adequate hardware.

   The underlying processor architecture of your Kubernetes worker nodes must be a supported version for the Confluent Platform for Apache Flink version you plan to deploy.

   Currently, Confluent Platform for Apache Flink supports x86, ARM64, and s390x hardware architectures.

   | Component                          |   Nodes | Storage                           | Memory   |   CPU cores |
   |------------------------------------|---------|-----------------------------------|----------|-------------|
   | Confluent Manager for Apache Flink |       1 | 10 GB (persistent storage as PVC) | 1 GB RAM |           2 |
   | Flink Kubernetes Operator          |       1 | N/A                               | 3 GB RAM |           2 |

Note the following:

- The CPU values use [Kubernetes CPU resource units](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#meaning-of-cpu) (CPU cores), not bare-metal CPUs. Fractional values such as `0.5` or `500m` are supported.
- Storage, memory, and CPU core values for CMF are configurable through the Helm installation.
- The Flink Kubernetes Operator resource requirements are calculated to support the execution of 200 Flink applications.

1. Install the required tools.

   This installation guide assumes you have already installed Helm. CMF supports Helm 3 for installation.
   You should have already configured Helm using the Helm documentation.
   To verify that your environment is prepared, the following commands should complete without error:
   ```none
   kubectl get pods
   helm list
   ```

   Add the Confluent Platform for Apache Flink Helm repository.
   ```none
   helm repo add confluentinc https://packages.confluent.io/helm
   helm repo update
   ```

## Step 2: Install the Confluent Platform for Apache Flink Kubernetes operator

You must install the Confluent Platform for Apache Flink Kubernetes operator *before* you install CMF because
CMF uses the operator to manage the Flink clusters.

1. Install the certificate manager.
   ```none
   kubectl create -f https://github.com/jetstack/cert-manager/releases/download/v1.18.2/cert-manager.yaml
   ```
2. Install the Flink Kubernetes operator.

   Use the `watchNamespaces` configuration to prepare the
   Kubernetes namespaces you want to deploy Flink applications to. You can
   later upgrade the Flink Kubernetes operator to extend the list, by re-running below
   Helm command again, with the additional namespaces. Note that you need to manually restart the
   Flink Kubernetes operator after changing the `watchNamespaces` configuration, for example by
   deleting the operator pod. It will be automatically recreated.
   You must ensure that the Kubernetes operator watches the Kubernetes namespaces you want to
   deploy Flink applications to with CMF.

   Instead of using Helm, you can also manually prepare a Kubernetes namespace for deploying
   Flink clusters, by creating the necessary `flink` service account, role and role binding, as
   documented in the Flink Kubernetes operator [documentation](https://nightlies.apache.org/flink/flink-kubernetes-operator-docs-release-1.12/docs/operations/rbac/#cluster-scoped-flink-operator-with-jobs-running-in-other-namespaces).
   If you omit the `watchNamespaces` flag, the operator will watch all namespaces, but the necessary
   `flink` service account will only by created in the namespace where the operator is installed.
   Additional namespaces must be setup manually.

   For deployment on OpenShift, you must also pass
   `--set podSecurityContext.runAsUser=null --set podSecurityContext.runAsGroup=null`
   to below Helm command.
   ```none
   helm upgrade --install cp-flink-kubernetes-operator --version "~1.150.0" \
     confluentinc/flink-kubernetes-operator \
     --set watchNamespaces="{namespace1,namespace2,...}"
   ```

## Step 3: Deploy CMF from the Confluent Helm repository

Next you will install CMF using the Confluent-provided Helm chart.
This Helm chart is the only supported way to install and update CMF.
Out-of-band updates to the resources that the Helm chart creates are not supported.

#### IMPORTANT
* If you do not specify a license, CMF will generate a trial license.
* You can use some Confluent Platform components with Confluent Cloud brokers with a valid license,
  including with CMF version 2.1 and later, Flink version 2.0 and later, and Kafka source and sink connectors for Flink version 4.0 and later.
  For support with these self-managed Confluent Platform components, you must have a valid Confluent Enterprise License for Customer-managed Confluent Platform for Confluent Cloud subscription license.
  For more details, see [Confluent Platform Enterprise Licensing Requirements](/platform/current/installation/license.html#enterprise-license-for-cmcpcc-subscription).

1. (Optional) Store your Confluent license in a Kubernetes secret.
   ```none
   kubectl create secret generic <license-secret-name> --from-file=license.txt
   ```
2. (Optional) Create a CMF database encryption key into a Kubernetes secret

   CMF is storing sensitive data such as secrets in its internal database. Below instructions are
   for setting up the encryption key for the CMF database.
   CMF has an `encryption.enabled` property. When this property is set to `false`, encryption is disabled. Otherwise,
   an encryption key is required.
   ```none
   # Generate a 256-bit key (recommended for production)
   openssl rand -out cmf.key 32
   # Create a Kubernetes secret with the encryption key
   kubectl create secret generic <secret-name> \
     --from-file=<property-name>=cmf.key
     -n <your-cmf-namespace>
   ```

   During the CMF installation, pass the following Helm parameter to use the encryption key:
   ```none
   --set encryption.key.kubernetesSecretName=<secret-name> \
   --set encryption.key.kubernetesSecretProperty=<property-name>
   ```

   **Example**
   ```none
   openssl rand -out cmf.key 32
   kubectl create secret generic cmf-encryption-key \
     --from-file=encryption-key=cmf.key \
     -n confluent
   helm upgrade --install cmf --version "~2.4.0" \
           confluentinc/confluent-manager-for-apache-flink \
           --namespace confluent \
           --set encryption.key.kubernetesSecretName=cmf-encryption-key \
           --set encryption.key.kubernetesSecretProperty=encryption-key
   ```

   #### WARNING
   You must backup the encryption key, CMF does not keep a backup of it. If the key is lost,
   you will no longer be able to access the encrypted data stored in the database.
3. Install CMF using the default configuration:

   For deployment on OpenShift, you must also pass
   `--set podSecurity.securityContext.fsGroup=null  --set podSecurity.securityContext.runAsUser=null`
   to below Helm command.
   ```none
   helm upgrade --install cmf confluentinc/confluent-manager-for-apache-flink  \
   --version "~2.4.0" \
   --namespace <namespace> \
   --set license.secretRef=<license-secret-name> \
   --set encryption.enabled=false # or pass --set encryption.key.kubernetesSecretName ...
   ```

   #### NOTE
   CMF will create a `PersistentVolumeClaim` (PVC) in Kubernetes. If the PVC remains in status `Pending`,
   check your Kubernetes cluster configuration and make sure a Container Storage Interface (CSI) driver is installed
   and configured correctly. Alternatively, if you want to run CMF without persistent storage, you can disable the
   PVC by setting the `persistence.create` property to `false`. Note that in this case, a restart of the CMF pod
   will lead to a data loss.

   On AWS EKS, the PVC remains in `Pending` status because standard EKS clusters do not configure a default
   storage class. However, the `gp2` storage class is available. To use `gp2`, pass the following value during
   CMF installation: `--set persistence.storageClassName=gp2`.
4. Configure the Chart.
   Helm provides [several options](https://helm.sh/docs/intro/using_helm/#customizing-the-chart-before-installing)
   for setting and overriding values in a chart.
   For CMF, you should customize the chart by passing a values file with the `--values` flag.

   First, use Helm to show the default `values.yaml` file for CMF.
   ```bash
   helm inspect values confluentinc/confluent-manager-for-apache-flink --version "~2.4.0"
   ```

   You should see output similar to the following:
   ```bash
   ## Image pull secret
   imagePullSecretRef:

   ## confluent-manager-for-apache-flink image
   image:
     repository: confluentinc
     name: cp-cmf
     pullPolicy: IfNotPresent
     tag: ~2.4.0

   ## CMF Pod Resources
   resources:
     limits:
       cpu: 2
       memory: 1024Mi
     requests:
       cpu: 1
       memory: 1024Mi

   ## Load license either from K8s secret
   license:
     ##
     ## The license secret reference name is injected through
     ## CONFLUENT_LICENSE environment variable.
     ## The expected key: license.txt. license.txt contains raw license data.
     ## Example:
     ##   secretRef: confluent-license-for-cmf
     secretRef: ""

   ## Pod Security Context
   podSecurity:
     enabled: true
     securityContext:
       fsGroup: 1001
       runAsUser: 1001
       runAsNonRoot: true

   ## Pod Tolerations
   ## Tolerations allow the pod to be scheduled on nodes with matching taints.
   ## See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ for reference.
   ## Example:
   ## tolerations:
   ## - key: "dedicated"
   ##   operator: "Equal"
   ##   value: "cmf"
   ##   effect: "NoSchedule"
   tolerations: []

   ## Pod Node Selector
   ## Node selector allows you to constrain which nodes your pod can be scheduled on based on node labels.
   ## See https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector for reference.
   ## Example:
   ## nodeSelector:
   ##   node-role: worker
   ##   zone: us-west-2a
   nodeSelector: {}

   ## Pod Annotations
   ## Additional annotations to add to the pod template metadata.
   ## These will be merged with the default annotations (like rollme).
   ## Example:
   ## podAnnotations:
   ##   prometheus.io/scrape: "true"
   ##   prometheus.io/port: "8080"
   podAnnotations: {}

   ## Pod Affinity and Anti-affinity
   ## Affinity allows you to constrain which nodes your pod can be scheduled on.
   ## See https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity for reference.
   ## Example:
   ## affinity:
   ##   nodeAffinity:
   ##     requiredDuringSchedulingIgnoredDuringExecution:
   ##       nodeSelectorTerms:
   ##       - matchExpressions:
   ##         - key: node-role
   ##           operator: In
   ##           values:
   ##           - worker
   ##   podAntiAffinity:
   ##     preferredDuringSchedulingIgnoredDuringExecution:
   ##     - weight: 100
   ##       podAffinityTerm:
   ##         labelSelector:
   ##           matchLabels:
   ##             app.kubernetes.io/name: confluent-manager-for-apache-flink
   ##         topologyKey: kubernetes.io/hostname
   affinity: {}

   ### Config to control the encryption of sensitive data in the CMF database.
   # The encryption of Secrets is always enabled and cannot be disabled.
   # This configuration allows you to control the encryption key.
   # The encryption algorithm is AES with GCM mode. Remember this when providing the key.
   # The key must be a base64 encoded string. The key length must be 128 bits (16 bytes) or 256 bits (32 bytes).
   # You should use 256 bits.
   # In addition, you must create the Kubernetes secret in the namespace where CMF is deployed.
   # And CMF's Service Account must have access to the secret.
   # The secret name must be the same as the kubernetesSecretName name, and the key for the encryption key must be the same as the
   # kubernetesSecretProperty.
   # WARNING: The encryption key cannot be changed after it is set.
   # Make sure that you set the correct key.
   encryption:
     # if set to true, encryption of secrets in the CMF database is enabled and enforced.
     enabled: false
     key:
       kubernetesSecretName: ""
       kubernetesSecretProperty: ""

   ## Persistence for CMF
   ## Note: Only used when cmf.database.type is set to "local" (SQLite)
   persistence:
     # If set to false, the database will be on the pod ephemeral storage, meaning gone when the pod stops
     create: true
     dataVolumeCapacity: 10Gi
   ##  storageClassName: # If not set, the default storage class is used.

   ## Volumes to mount for the CMF pod.
   ##
   ## Example with a PVC.
   ## mountedVolumes:
   ##   volumes:
   ##   - name: custom-volume
   ##     persistentVolumeClaim:
   ##       claimName: pvc-test
   ##   volumeMounts:
   ##   - name: custom-volume
   ##     mountPath: /mnt/<path_of_your_choice>
   ##
   mountedVolumes:
     volumes:
     volumeMounts:

   ## Configure the CMF service for example Authn/Authz
   cmf:
     # controls whether CMF logs stack traces in case of an error.
     # Defaults to 'true' to aid debugging. Set to 'false' if access to the logs is not
     # properly controlled and stack traces could leak sensitive information.
     stackTraceLogging: true
     # determines whether cmf sync's all apps from metadata on startup
     k8s:
       metadataSync: true
     # determines max requests per sec to K8s API server during sync
     # max-req-sec: 300
     ## Database Configuration
     ## CMF supports two database types (choose one):
     ## 1. "local" - Uses SQLite database stored in persistent volume (default)
     ##    - Good for: Development, testing, small deployments
     ## 2. "jdbc" - Uses external JDBC database (PostgreSQL)
     ##    - Good for: Production deployments, high availability
     database:
       # Type of database to use. Choose either "local" or "jdbc"
       # WARNING: Do not change this after initial deployment without proper data migration
       type: local
       # JDBC database configuration (only used when type: jdbc)
       jdbc:
         # Database engine type. values: postgresql
         engine: postgresql
         # Database server hostname or IP address
         url: ""
         # Database server port (default: 5432 for PostgreSQL)
         port: 5432
         # Database name (must be created beforehand)
         database: cmf
         # Database username
         user: cmf

         # Database password configuration
         password:
           # Name of Kubernetes secret containing the password
           kubernetesSecretName: ""
           # Key within the secret that contains the password
           kubernetesSecretProperty: ""

     # configures the CP Flink SQL feature
     sql:
       # DEPRECATED: Use 'encryption.enabled' instead.
       # If set to true, production level features are enabled and enforced. One of the primary features is the
       # encryption of secrets in the CMF database.
       production: false
       ## Feature flag for the environment catalog, which exposes UDFs
       ## (CREATE FUNCTION ... USING JAR) and custom connectors and formats
       ## (CREATE TABLE ... WITH ('connector'=...)). Off by default.
       environmentCatalog:
         enabled: false
       ## Classes that SQL-compiled code may load from CMF's classpath, as
       ## full-match regexes over fully-qualified class names. The defaults
       ## below are the minimum that SQL compilation and connector/format
       ## discovery need; add entries to allow more.
       classAllowlist:
         - 'java\..*'
         - 'javax\..*'
         - 'org\.apache\.flink\..*'
         - 'io\.confluent\.flink\..*'
         - 'org\.slf4j\..*'

     ## Artifact management. Opt-in feature for managing Flink job artifacts
     ## (JARs) on blob storage. When enabled, basePath is required.
     artifacts:
       enabled: false
       ## Required when enabled. Examples:
       ##   basePath: s3://my-bucket/cmf
       ##   basePath: gs://my-bucket/cmf
       ##   basePath: abfs://container@account.dfs.core.windows.net/cmf
       # basePath: ""
       ## Maximum upload size. Accepts a unit suffix (B, KB, MB, GB, TB); a
       ## bare number is bytes. Default 100MB.
       # maxUploadSize: 100MB
       ## Filesystem credentials used only by CMF for upload/download/delete
       ## (Flink clusters fetch artifacts with their own credentials).
       # configuration: {}

     # Kafka client configuration
   #  kafka:
   ##     Comma-separated list of URLs that are allowed to be accessed by the OAuth bearer token.
   ##     This is required for CP 8.0+ where Kafka clients need to explicitly allowlist token endpoints.
   ##     Defaults to "" to allow no URLs.
   #     oauthbearerAllowedUrls: ""

     # authentication:
     #   type: mtls

     # application-events:
     #   # max events per application
     #   max-events-per-application: 100

     # application-instances:
     #   # max instances per application
     #   max-instances-per-application: 100

   ## Enable Kubernetes RBAC
   # When set to true, it will create a proper role/rolebinding or cluster/clusterrolebinding based on namespaced field.
   # If a user doesn't have permission to create role/rolebinding then they can disable rbac field and
   # create required resources out of band to be used by the Operator. In this case, follow the
   # templates/clusterrole.yaml and templates/clusterrolebinding.yaml to create proper required resources.
   rbac: true
   ## Namespaces to grant CMF RBAC access to.
   # Empty: cluster-wide access (ClusterRole). Non-empty: a Role/RoleBinding per listed namespace.
   watchNamespaces: []
   ## Controls whether CMF is granted permissions to create and manage
   ## ServiceAccounts, Roles, and RoleBindings for environments.
   ## When true (default), CMF creates these resources automatically
   ## during environment creation and deletes them on environment deletion.
   ## When false, CMF expects pre-existing ServiceAccount, Role, and
   ## RoleBinding resources in the target namespace and reuses them.
   ## The Helm chart will only grant read-only (get/list/watch) permissions
   ## for these resource types. Use this in locked-down environments where
   ## a platform team pre-provisions RBAC resources.
   manageEnvironmentRbac: true
   ## Creates a default service account for the CMF pod if service.account.create is set to true.
   # In order to use a custom service account, set the name field to the desired service account name and set create to false.
   # Also note that the new service account must have the necessary permissions to run the CMF pod, i.e cluster wide permissions.
   # The custom service account must have:
   #
   # rules:
   #  - apiGroups: ["flink.apache.org"]
   #    resources: ["flinkdeployments", "flinkdeployments/status"] # Needed to manage FlinkDeployments CRs
   #    verbs: ["*"]
   #  - apiGroups: [""]
   #    resources: ["services"] # Read-only permissions needed for the flink UI
   #    verbs: ["get", "list", "watch"]
   serviceAccount:
     create: true
     name: ""
   # The jvmArgs parameter allows you to specify custom Java Virtual Machine (JVM) arguments that will be passed to the application container.
   # This can be useful for tuning memory settings, garbage collection, and other JVM-specific options.
   # Example :
   # jvmArgs: "-Xms512m -Xmx1024m -XX:+UseG1GC"

   jvmArgs:

   ## Extra environment variables for the CMF container.
   ## Accepts any valid Kubernetes env entry (value, secretKeyRef, configMapKeyRef, fieldRef).
   ## Values injected this way can be referenced as ${ENV_VAR} in the CMF config.
   ##
   ## Example:
   ##   extraEnv:
   ##   - name: CMF_OAUTH_CLIENT_SECRET
   ##     valueFrom:
   ##       secretKeyRef:
   ##         name: my-oauth-secret
   ##         key: client-secret
   ##   - name: MY_CONFIG_VALUE
   ##     valueFrom:
   ##       configMapKeyRef:
   ##         name: my-configmap
   ##         key: some-key
   ##   - name: MY_PLAIN_VALUE
   ##     value: "some-value"
   extraEnv: []
   ```

   Note the following about CMF default values:
   - CMF uses SQLite to store metadata about your deployments.
     The data is persisted on a persistent volume
     that is created during the installation with a `PersistentVolumeClaim` created by Helm.
   - The persistent volume is created with your Kubernetes cluster’s default storage class.
     Depending on your storage class, your metadata might
     not be retained if you uninstall CMF.
     For example, if your reclaim policy is `Delete`, data is not retained.
     **Make sure to backup the data in the persistent volume regularly**.
   - If you want to set your storage class, you can overwrite `persistence.storageClassName`
     during the installation.
   - By default, the chart uses the image hosted by [Confluent on DockerHub](https://hub.docker.com/r/confluentinc/cp-cmf).
     To specify your own registry, set the following configuration values:
     ```none
     image:
       repository: <image-registry>
       name: cp-cmf
       pullPolicy: IfNotPresent
       tag: <tag>
     ```

     To build your own CMF, Flink, and Kubernetes Operator images from the published
     archives before you push them to your registry, see [Build Confluent Platform for Apache Flink Container Images from Published Artifacts](build-images.md#cpf-build-images).
   - By default, the chart creates a cluster role and [service account](https://kubernetes.io/docs/concepts/security/service-accounts/)
     that CMF can use to create and monitor Flink applications in all namespaces.
     If you want to keep your service account, you set the `serviceAccount.name` property during installation
     to the preferred service account.
   - To run CMF without cluster-wide permissions and confine it to a fixed set of
     namespaces, set `watchNamespaces` to a non-empty list of namespaces. See
     [Restrict CMF to specific namespaces](#cmf-namespace-scoped-rbac).
   - To change the log level, for example to show debug logs, set `cmf.logging.level.root=debug`.
   - Stack traces are logged by default (`cmf.stackTraceLogging: true`) to aid debugging.
     Stack traces can include file paths, credentials, user data, system internals, or
     exception messages that are useful to a curious reader. In environments where access
     to CMF logs is not restricted to trusted operators, or where logs are forwarded to
     less-trusted systems, set `cmf.stackTraceLogging: false` to suppress stack traces in
     log output.

<a id="cmf-helm-extra-env"></a>

## Inject secrets with environment variables

The `extraEnv` Helm value lets you inject additional environment variables into the CMF container.
This is useful for referencing sensitive values — such as OAuth client secrets or truststore passwords —
from Kubernetes Secrets, without putting them directly in your Helm values files.

Values injected through `extraEnv` can be referenced as `${ENV_VAR}` placeholders anywhere
in the CMF configuration. CMF resolves these placeholders at startup.

This mechanism also works with external secret management solutions such as
[HashiCorp Vault](https://developer.hashicorp.com/vault/docs/platform/k8s/injector),
which can sync secrets into Kubernetes Secrets that `extraEnv` then references via `secretKeyRef`.

1. Create a Kubernetes Secret containing the sensitive values:
   ```bash
   kubectl create secret generic cmf-secrets \
     --from-literal=oauth-client-secret=<your-client-secret> \
     --from-literal=truststore-password=<your-truststore-password> \
     -n <your-cmf-namespace>
   ```
2. Reference the secret in your Helm values file using `extraEnv`:
   ```yaml
   extraEnv:
   - name: CMF_OAUTH_CLIENT_SECRET
     valueFrom:
       secretKeyRef:
         name: cmf-secrets
         key: oauth-client-secret
   - name: CMF_SSL_TRUSTSTORE_PASSWORD
     valueFrom:
       secretKeyRef:
         name: cmf-secrets
         key: truststore-password
   ```
3. Use the `${ENV_VAR}` placeholders in the CMF configuration:
   ```yaml
   cmf:
     authentication:
       type: oauth
       config:
         confluent.metadata.oauthbearer.login.client.secret: ${CMF_OAUTH_CLIENT_SECRET}
         confluent.metadata.ssl.truststore.password: ${CMF_SSL_TRUSTSTORE_PASSWORD}
   ```

`extraEnv` supports all standard Kubernetes environment variable sources: `secretKeyRef`,
`configMapKeyRef`, `fieldRef`, and plain `value`.

<a id="cmf-namespace-scoped-rbac"></a>

## Restrict CMF to specific namespaces

By default, the CMF Helm chart grants CMF a cluster-wide `ClusterRole` so that CMF
can manage Flink resources in any namespace. If your cluster policy does not permit
cluster-scoped permissions, you can instead run CMF with namespace-scoped RBAC by listing
the namespaces CMF is allowed to use in the `watchNamespaces` Helm value:

```yaml
watchNamespaces:
  - flink-dev
  - flink-prod
```

The `watchNamespaces` value controls only which Kubernetes RBAC resources the chart creates:

- When `watchNamespaces` is empty (the default), the chart creates a `ClusterRole` and
  `ClusterRoleBinding`, granting CMF access across the whole cluster.
- When `watchNamespaces` lists one or more namespaces, the chart creates a namespaced
  `Role` and `RoleBinding` in each listed namespace instead of a `ClusterRole`. CMF
  then runs without any cluster-scoped permissions.

Despite the name, `watchNamespaces` is not a runtime watch list: it does not tell CMF
which namespaces to monitor for Flink resources. CMF derives that set dynamically from the
environments you create.

#### IMPORTANT
This `watchNamespaces` value belongs to the CMF chart. It is separate from the
identically named `watchNamespaces` flag on the Flink Kubernetes operator chart, a
different chart you install earlier in this guide. Configure both charts for the namespaces
where you deploy Flink applications: the operator flag lets the operator watch those
namespaces, and the CMF value grants CMF RBAC access to them.

When you run CMF with namespace-scoped RBAC, every namespace you deploy a CMF environment into
must be listed in `watchNamespaces`. For each environment, CMF provisions a dedicated
`ServiceAccount`, `Role`, `RoleBinding`, and `Secret` in the environment’s namespace,
so it requires RBAC access there. Creating an environment in a namespace CMF cannot access
fails with an error that names the namespace, environment, and cluster; to resolve it, add the
namespace to `watchNamespaces` and re-run `helm upgrade`.

Namespace-scoped RBAC also works with a multi-cluster deployment; the same behavior applies
to every Kubernetes cluster registered with CMF. For more information about environments and
multi-cluster deployments, see [Configure Environments in Confluent Manager for Apache Flink](../configure/environments.md#cmf-environments) and [Manage Multiple Kubernetes Clusters in Confluent Manager for Apache Flink](../configure/kubernetes-clusters.md#cmf-kubernetes-clusters).

<a id="cmf-manage-environment-rbac"></a>

## Pre-provision environment RBAC

This section applies when CMF is not permitted to manage RBAC resources and a platform team
pre-provisions the per-environment resources instead.

For each Flink environment, CMF provisions a dedicated `ServiceAccount` and the `Roles`
and `RoleBindings` it needs in the environment’s namespace. The environment’s Flink SQL
compute pool JobManagers run as that `ServiceAccount`. The specified roles grant JobManagers
permission to manage their own `Pods`, `ConfigMaps`, and `Deployments` and to read the
environment `Secret`.

The `manageEnvironmentRbac` Helm value sets the RBAC permissions the chart grants CMF for
service accounts, roles, and role bindings. CMF does not read this chart-only setting at
runtime. By default, `manageEnvironmentRbac` is set to `true`, and the chart grants write
access, including create, update, and delete, so CMF manages the resources for you. When
`manageEnvironmentRbac` is set to `false`, the chart grants only read access, limited to
get, list, and watch, so CMF cannot create the resources; you must provision them in advance.

```yaml
manageEnvironmentRbac: false
```

Regardless of this setting, CMF reuses any of these resources that already exist rather than
recreating them, and when you delete an environment it removes only the resources it created.
Resources that you pre-create remain in place.

### Pre-provision the resources

When `manageEnvironmentRbac` is `false`, create the per-environment RBAC resources in the
environment’s namespace before you create the environment. CMF derives their names from the
environment name, so they must match exactly.

#### WARNING
If any of these resources are missing when you create an environment, CMF cannot create
them, and environment creation fails with a permission error that identifies the namespace,
cluster, and environment.

CMF creates and manages the environment `Secret` (`cmf-env-key-<environment>`) itself —
creating it, or adopting an existing one — so it is not part of this manifest. The following
manifest is for an environment named `<environment>` in namespace `<namespace>`:

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: cmf-env-sa-<environment>
  namespace: <namespace>
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: cmf-env-role-<environment>
  namespace: <namespace>
rules:
  - apiGroups: [""]
    resources: ["pods", "configmaps"]
    verbs: ["*"]
  - apiGroups: ["apps"]
    resources: ["deployments", "deployments/finalizers"]
    verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: cmf-env-rolebinding-<environment>
  namespace: <namespace>
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: cmf-env-role-<environment>
subjects:
  - kind: ServiceAccount
    name: cmf-env-sa-<environment>
    namespace: <namespace>
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: cmf-env-secret-role-<environment>
  namespace: <namespace>
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["cmf-env-key-<environment>"]
    verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: cmf-env-secret-rolebinding-<environment>
  namespace: <namespace>
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: cmf-env-secret-role-<environment>
subjects:
  - kind: ServiceAccount
    name: cmf-env-sa-<environment>
    namespace: <namespace>
```

The secret-access `Role` (`cmf-env-secret-role-<environment>`) grants the environment’s
`ServiceAccount` read access to that `Secret`.

## Step 4: Cleanup

For cleanup instructions, see the [cleanup section in the quickstart guide](../get-started/get-started-application.md#cpf-get-started-cleanup).

## Related content

- [Get Started with Applications in Confluent Manager for Apache Flink](../get-started/get-started-application.md#cpf-get-started)
- [Versions and Interoperability for Confluent Manager for Apache Flink](versions-interoperability.md#cmf-interop)
- [Install and Upgrade Confluent Manager for Apache Flink](overview.md#cpf-install)
