This document explains how to migrate your admission policies from
Policy Controller to reduce the risk of gaps in policy enforcement.
Policy Controller is deprecated as of November 12, 2026, and reaches end
of support on December 31, 2027. In version 1.39 and later,
Google Kubernetes Engine (GKE) clusters don't support Policy Controller.

You can migrate the admission policies that you enforce by using
Policy Controller to Kubernetes ValidatingAdmissionPolicies or to self-managed
Open Policy Agent (OPA) Gatekeeper. Migrating doesn't affect workloads that are
already running. Policy Controller evaluates requests to create or update
resources, so removing it doesn't restart or change running workloads.


This document is for operators and
security specialists who manage Policy Controller across a fleet. To
learn more about common roles and example tasks that we reference in
Google Cloud content, see
[Common GKE user roles and tasks](https://docs.cloud.google.com/kubernetes-engine/enterprise/docs/concepts/roles-tasks.md.txt).

## Blocked upgrades to GKE version 1.39

GKE blocks upgrades to version 1.39 and later for clusters that
have Policy Controller installed. Before you upgrade a cluster to version 1.39,
migrate your policies and remove Policy Controller from that cluster. Don't
remove Policy Controller just to unblock the upgrade before you migrate your
policies, because doing so leaves the cluster without policy enforcement.

<br />

## Choose a replacement

Evaluate each admission policy in Policy Controller and choose a replacement
option for that policy from the following table. You don't have to choose one
replacement for an entire cluster. You can combine options on the same cluster
based on what each policy requires:

| Option | When to use | Operation and support |
| ValidatingAdmissionPolicy (recommended) | Policies that check a single resource when it's created or updated. Most templates in the Policy Controller template library work this way. | ValidatingAdmissionPolicies are built into Kubernetes and available in GKE version 1.30 and later at no additional cost. Cloud Customer Care supports ValidatingAdmissionPolicies as part of GKE. |
| Pod Security Admission (recommended for Pod security bundles) | Predefined Pod security controls from the `pss-baseline-v2022`, `pss-restricted-v2022`, or `psp-v2022` bundle. For more information, see [Use Pod Security Admission for Pod security bundles](https://docs.cloud.google.com/kubernetes-engine/docs/deprecations/policy-controller.md.txt#use-psa). | Pod Security Admission is built into Kubernetes and enabled by default in GKE at no additional cost. Customer Care supports Pod Security Admission as part of GKE. |
| Self-managed OPA Gatekeeper | Policies that ValidatingAdmissionPolicies can't express, such as referential policies that read other resources in the cluster, policies that you want to keep in Rego, and audits of existing resources. You can also add Common Expression Language (CEL) rules to Gatekeeper ConstraintTemplates so that Gatekeeper generates ValidatingAdmissionPolicies for API server enforcement, and its audit controller checks existing resources. | You install, upgrade, and operate Gatekeeper. Customer Care doesn't support open source Gatekeeper. The Gatekeeper community provides support through GitHub. |
|---|---|---|
[Comparison of Policy Controller replacement options]

## Inventory and classify your policies

By default, the Policy Controller template library installs every template, and
policy bundles install their constraints with the `dryrun` enforcement action.
As a result, the templates and constraints on a cluster don't necessarily match
the policies that you enforce. To find out what you enforce, follow these
steps:

1. Identify the clusters in your fleet that run Policy Controller:

   ```bash
   gcloud container fleet policycontroller describe \
       --project=PROJECT_ID
   ```

   Replace `PROJECT_ID` with the ID of your fleet host
   project.

   > [!NOTE]
   > **Note:** You might have clusters outside of a fleet running Policy Controller, such as clusters where you installed Policy Controller by using the ConfigManagement Operator or clusters that you unregistered from a fleet without uninstalling Policy Controller. These clusters don't appear in the output of the preceding command. To check whether a cluster runs Policy Controller, see [View the Policy Controller version](https://docs.cloud.google.com/kubernetes-engine/policy-controller/docs/how-to/installing-policy-controller.md.txt#version). To remove Policy Controller from these clusters, see [Remove Policy Controller from clusters outside a fleet](https://docs.cloud.google.com/kubernetes-engine/docs/deprecations/policy-controller.md.txt#outside-fleet).

2. On each cluster that you identified, list your constraints:

   ```bash
   kubectl get constraints \
       -o custom-columns='KIND:.kind,NAME:.metadata.name,'\
   'ACTION:.spec.enforcementAction,VIOLATIONS:.status.totalViolations,'\
   'BUNDLE:.metadata.labels.policycontroller\.gke\.io/bundleName'
   ```

   The output shows each constraint's enforcement action, its violation
   count from the most recent audit, and any bundle that installed it. A
   constraint that has no value in the `ACTION` column uses the default `deny`
   enforcement action.
3. Back up your ConstraintTemplates and constraints to separate files:

   ```bash
   kubectl get constrainttemplates -o yaml \
       > policy-controller-templates-backup.yaml
   kubectl get constraints -o yaml \
       > policy-controller-constraints-backup.yaml
   ```
4. Optional: Perform the following additional backup operations:

   - If you enabled referential rules in Policy Controller, then back up
     your sync configuration:

     ```bash
     kubectl get config.config.gatekeeper.sh -n gatekeeper-system -o yaml \
         > policy-controller-config-backup.yaml
     ```
   - If you enabled mutation in Policy Controller, then back up your
     mutators:

     ```bash
     kubectl get assign,assignmetadata,assignimage,modifyset -o yaml \
         > policy-controller-mutators-backup.yaml
     ```

Keep these backup files in a safe place. Removing Policy Controller deletes
every ConstraintTemplate, constraint, sync configuration, and mutator on the
cluster, including the ones that you created. You need the backups to translate
or re-create your policies.

After you back up your policies, determine your migration path for each
constraint by checking the constraint type in the following table:

| Constraint type | Migration path |
| Single-resource constraint | If a constraint evaluates fields on only the incoming resource without reading `data.inventory` or belonging to a Pod security bundle, then use a ValidatingAdmissionPolicy. |
| Unenforced `dryrun` constraint | If a constraint uses the `dryrun` enforcement action and you don't plan to enforce it, delete the constraint. |
| Pod security bundle constraint | If a constraint comes from the `pss-baseline-v2022`, `pss-restricted-v2022`, or `psp-v2022` bundle, then use Pod Security Admission. Use ValidatingAdmissionPolicies for any controls that Pod Security Admission doesn't cover. |
| Referential constraint | If a constraint uses a referential template that reads other resources in the cluster, then use self-managed Gatekeeper. To find referential templates, search your backup file for the `data.inventory` reference. |
| Cloud Service Mesh, Istio, or Google Cloud constraint | Review the constraint individually to check whether a built-in GKE feature replaces the policy, or whether the ConstraintTemplate in your backup file reads `data.inventory`. Most of these templates check only the incoming resource and work in ValidatingAdmissionPolicies, except the referential `AsmAuthzPolicyDefaultDeny` and `AsmPeerAuthnMeshStrictMtls` templates, which require self-managed Gatekeeper. For built-in replacements, enable Binary Authorization instead of using the `K8sRequireBinAuthZ` ConstraintTemplate, and configure the node image of your node pools instead of using the `K8sRequireCosNodeImage` ConstraintTemplate. |
|---|---|
[Migration paths by constraint type]

## Migrate to ValidatingAdmissionPolicy

A ValidatingAdmissionPolicy contains the policy logic, like a
ConstraintTemplate. A ValidatingAdmissionPolicyBinding applies the policy, like
a constraint. A ValidatingAdmissionPolicy uses CEL expressions instead of Rego,
and the API server evaluates the expressions directly, without a webhook.

### Example: allowed image repositories

The following Policy Controller constraint requires Pods to use images from one
Artifact Registry repository. It excludes the `kube-system` namespace:

```yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
  name: allowed-repos
spec:
  enforcementAction: deny
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
    excludedNamespaces: ["kube-system"]
  parameters:
    repos:
    - "us-docker.pkg.dev/example-project/example-repo/"
```

The following ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding
enforce the same rule. The ValidatingAdmissionPolicyBinding uses the `Warn` and
`Audit` actions so that you can compare results before you enforce the
ValidatingAdmissionPolicy:

```yaml
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: allowed-repos
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups: [""]
      apiVersions: ["v1"]
      operations: ["CREATE", "UPDATE"]
      resources: ["pods", "pods/ephemeralcontainers"]
  variables:
  - name: repos
    expression: "['us-docker.pkg.dev/example-project/example-repo/']"
  - name: images
    expression: >-
      object.spec.containers.map(c, c.image) +
      (has(object.spec.initContainers) ?
        object.spec.initContainers.map(c, c.image) : []) +
      (has(object.spec.ephemeralContainers) ?
        object.spec.ephemeralContainers.map(c, c.image) : [])
  validations:
  - expression: >-
      variables.images.all(image,
        variables.repos.exists(repo, image.startsWith(repo)))
    messageExpression: >-
      'container images must come from one of these repositories: ' +
      variables.repos.join(', ')
    reason: Forbidden
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: allowed-repos
spec:
  policyName: allowed-repos
  validationActions: ["Warn", "Audit"]
  matchResources:
    namespaceSelector:
      matchExpressions:
      - key: kubernetes.io/metadata.name
        operator: NotIn
        values: ["kube-system", "gatekeeper-system"]
```

In this example, the `repos` variable holds the value from the constraint's
`parameters` field. The validation expression returns a `true` value for Pods
that comply with the ValidatingAdmissionPolicy, which is the opposite of a Rego
violation rule. To reuse one ValidatingAdmissionPolicy across multiple
ValidatingAdmissionPolicyBindings that supply different values, use a parameter
resource instead of a variable. For more information about parameter resources,
see
[Parameter resources](https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/#parameter-resources)
in the Kubernetes documentation.

The following table shows how constraint fields map to
ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding fields:

| Constraint field | ValidatingAdmissionPolicy or ValidatingAdmissionPolicyBinding field |
| `spec.enforcementAction` | The `validationActions` field in the ValidatingAdmissionPolicyBinding, which maps the `deny`, `warn`, and `dryrun` actions to the `["Deny"]`, `["Warn"]`, and `["Audit"]` values |
| `spec.parameters` | The `variables` field in the ValidatingAdmissionPolicy, or a parameter resource that the ValidatingAdmissionPolicyBinding references in the `paramRef` field |
| `spec.match.kinds` | The `matchConstraints.resourceRules` field in the ValidatingAdmissionPolicy, which uses API groups and resource names, such as the `pods` resource, instead of kinds |
| `spec.match.namespaces` and `spec.match.excludedNamespaces` | The `matchResources.namespaceSelector` field in the ValidatingAdmissionPolicyBinding, using the `In` or `NotIn` operator on the `kubernetes.io/metadata.name` label |
| `spec.match.labelSelector` | The `matchResources.objectSelector` field in the ValidatingAdmissionPolicyBinding |
| `spec.match.namespaceSelector` | The `matchResources.namespaceSelector` field in the ValidatingAdmissionPolicyBinding |
| `spec.match.scope` | The `scope` field of each resource rule |
|---|---|
[Mapping of Policy Controller constraint fields to
ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding
fields]

For more information about how to write expressions, see
[CEL in Kubernetes](https://kubernetes.io/docs/reference/using-api/cel/)
in the Kubernetes documentation.

### Test policies alongside Policy Controller

To replace a constraint without a gap in enforcement, run the
ValidatingAdmissionPolicy next to the constraint before you enforce the
ValidatingAdmissionPolicy:

1. Apply the ValidatingAdmissionPolicy and a ValidatingAdmissionPolicyBinding that has the `validationActions: ["Warn", "Audit"]` setting. Leave the Policy Controller constraint in place.
2. Compare the results by using the audit log query in [Replace violation reporting](https://docs.cloud.google.com/kubernetes-engine/docs/deprecations/policy-controller.md.txt#replace-violation-reporting). If the ValidatingAdmissionPolicy flags requests that Policy Controller allows, or the reverse, then fix the ValidatingAdmissionPolicy. For ValidatingAdmissionPolicies that match Pods, don't rely on warnings. Controllers create the Pods for Deployments and other workloads, so the controller receives the warning.
3. After the results match, change the ValidatingAdmissionPolicyBinding to use the `validationActions: ["Deny"]` setting.
4. Delete the Policy Controller constraint.

### Plan for behavior differences

ValidatingAdmissionPolicies behave differently from Policy Controller in the
following ways, which can block requests or leave gaps in enforcement:

- **Failure policy** : the Policy Controller webhook fails open, which means that the API server admits a request if Policy Controller doesn't respond. A ValidatingAdmissionPolicy that has the `failurePolicy: Fail` setting denies a request if an expression returns an error or exceeds the CEL cost limit. The ValidatingAdmissionPolicy also denies a request if a referenced parameter resource doesn't exist and the value of the `paramRef.parameterNotFoundAction` field in the ValidatingAdmissionPolicyBinding is `Deny`. If the value is `Allow`, then the API server skips a missing parameter resource regardless of the `failurePolicy` setting. Test each ValidatingAdmissionPolicy and choose values for both fields.
- **Missing fields** : Rego treats a missing field as no violation, but CEL returns an error. Before you read an optional field, use the `has()` macro to check whether the field exists.
- **Exemptions** : a ValidatingAdmissionPolicy doesn't exempt namespaces by default or use the `admission.gatekeeper.sh/ignore` label. On GKE Standard clusters, a ValidatingAdmissionPolicyBinding applies to every namespace in the cluster, including system namespaces such as the `kube-system` namespace, unless you exclude them in the `matchResources.namespaceSelector` field on the ValidatingAdmissionPolicyBinding or in the `matchConstraints.namespaceSelector` or `matchConditions` fields on the ValidatingAdmissionPolicy. On Autopilot clusters, GKE excludes managed namespaces automatically and rejects ValidatingAdmissionPolicies that match the `nodes`, `persistentVolumes`, `certificatesigningrequests`, or `tokenreviews` resources. For more information, see [GKE Autopilot security measures](https://docs.cloud.google.com/kubernetes-engine/docs/concepts/autopilot-security.md.txt#validatingadmissionpolicy).
- **Subresources** : Policy Controller checks Pod updates that add ephemeral containers. To keep that coverage, include the `pods/ephemeralcontainers` subresource in your resource rules, as the example does.
- **No audit of existing resources**: a ValidatingAdmissionPolicy evaluates only incoming API server requests. It doesn't report existing resources that violate a ValidatingAdmissionPolicy.
- **One message for each request**: a ValidatingAdmissionPolicy reports the first validation that fails, not every violation.

## Use Pod Security Admission for Pod security bundles

If you use the `pss-baseline-v2022` or `pss-restricted-v2022` bundles, then
replace the bundle with Pod Security Admission instead of translating each
constraint. Pod Security Admission uses the built-in Kubernetes `PodSecurity`
admission controller to enforce predefined rules from the
[Kubernetes Pod Security Standards](https://kubernetes.io/docs/concepts/security/pod-security-standards/)
based on namespace labels. The `pss-baseline-v2022` bundle corresponds to the
`baseline` level, and the `pss-restricted-v2022` bundle corresponds to the
`restricted` level.

Pod Security Admission doesn't cover every control in the `psp-v2022` bundle,
such as allowlists for host paths, users, capabilities, and sysctls. Use
ValidatingAdmissionPolicies for those controls. Pod Security Admission also
can't exempt specific images, like Policy Controller constraints that use the
`exemptImages` parameter. GKE doesn't support configuring Pod
Security Admission exemptions. If you need exceptions, then use
ValidatingAdmissionPolicies for the affected policies.

For more information about how to configure Pod Security Admission, see
[Apply predefined Pod-level security policies using PodSecurity](https://docs.cloud.google.com/kubernetes-engine/docs/how-to/podsecurityadmission.md.txt).

## Migrate to self-managed OPA Gatekeeper

Choose Gatekeeper for the policies that ValidatingAdmissionPolicies can't
express. Policy Controller is based on Gatekeeper, so your ConstraintTemplates
and constraints use the same APIs in Gatekeeper. All templates in the
Policy Controller template library work in Gatekeeper without changes to their
logic. However, Gatekeeper uses a newer Rego engine and stricter validation
that can reject a custom ConstraintTemplate.

When you use self-managed Gatekeeper, you become responsible for the tasks that
Google Cloud performs for Policy Controller, such as installing,
upgrading, scaling, and monitoring Gatekeeper on each cluster.
Customer Care doesn't support open source Gatekeeper. For help, use
the
[Gatekeeper GitHub issue tracker](https://github.com/open-policy-agent/gatekeeper/issues).

Gatekeeper doesn't include the Policy Controller template library or policy
bundles. Your backup files contain the ConstraintTemplates and constraints from
your clusters. For templates that originated in the open source Gatekeeper
library, you can use the templates in the
[Gatekeeper policy library](https://open-policy-agent.github.io/gatekeeper-library/website/).
Template names stay the same, but parameters and messages can differ between
versions.

<br />

To move policies to Gatekeeper, follow these steps:

1. Optional: If you can't accept a gap in enforcement while you replace Policy Controller, then migrate your most important policies to ValidatingAdmissionPolicies first. Make sure that those ValidatingAdmissionPolicyBindings exclude the `gatekeeper-system` namespace so that they don't block the Gatekeeper installation Jobs. After you remove Policy Controller, your cluster stops enforcing Gatekeeper policies until you apply the corresponding Gatekeeper constraints. ValidatingAdmissionPolicies keep enforcing because they don't depend on Policy Controller or Gatekeeper.
2. If you use custom ConstraintTemplates, then test each custom template
   manifest by using the
   [`gator` command-line tool](https://open-policy-agent.github.io/gatekeeper/website/docs/gator/)
   from the Gatekeeper version that you plan to install:

   ```bash
   gator test --filename=TEMPLATE_FILE
   ```

   Replace `TEMPLATE_FILE` with the path to your
   ConstraintTemplate manifest file. Before you remove Policy Controller, fix
   any custom ConstraintTemplate that Gatekeeper rejects.
3. If you deploy constraints from a Git repository by using a GitOps tool,
   then prepare the ConstraintTemplates that your constraints use and add them
   to the repository when you install Gatekeeper. Otherwise, your constraints
   fail to apply after you remove Policy Controller because their
   ConstraintTemplates no longer exist.

4. Remove Policy Controller by following the steps in
   [Remove Policy Controller](https://docs.cloud.google.com/kubernetes-engine/docs/deprecations/policy-controller.md.txt#remove-policy-controller). Don't install
   Gatekeeper on a cluster that still runs Policy Controller. They both use
   the `gatekeeper-system` namespace, the same CustomResourceDefinitions, and
   the same webhook names.

5. Install Gatekeeper by using Helm, using the settings that you choose from
   the table in the next section,
   [Gatekeeper settings that differ from Policy Controller](https://docs.cloud.google.com/kubernetes-engine/docs/deprecations/policy-controller.md.txt#settings). For
   more information about how to install Gatekeeper, see
   [Installation](https://open-policy-agent.github.io/gatekeeper/website/docs/install/)
   in the Gatekeeper documentation.

   On Autopilot clusters, the default Helm chart installation fails
   because GKE rejects wildcard rules in mutating webhooks. Set
   the `disableMutation: true` Helm value, or set the
   `mutatingWebhookCustomRules` Helm value to list only the specific resources
   that your mutators target. Don't include the `nodes`, `persistentvolumes`,
   `certificatesigningrequests`, or `tokenreviews` resources.
6. In your backup files, remove the following fields, labels, and annotations
   from each resource that you plan to reapply:

   - The `status` field.
   - The `resourceVersion`, `uid`, `creationTimestamp`, `generation`, `managedFields`, and `ownerReferences` fields in the `metadata` section.
   - The `kubectl.kubernetes.io/last-applied-configuration` annotation.
   - The `app.kubernetes.io/managed-by: anthospolicycontroller.googleapis.com` label. If Policy Controller is enabled on the cluster again, such as by a fleet-level default configuration, then it manages resources that have this label and can modify or delete them.
   - Labels and annotations whose keys have the `policycontroller.` prefix.
7. Apply the ConstraintTemplates that your constraints use, and then check
   that Gatekeeper created a CustomResourceDefinition for each template:

   ```bash
   kubectl get constrainttemplates \
       -o custom-columns='NAME:.metadata.name,CREATED:.status.created'
   ```

   When every template shows a `true` value in the `CREATED` column, apply
   your constraints.

### Gatekeeper settings that differ from Policy Controller

Before you install Gatekeeper, review the settings in the following table where
the Gatekeeper Helm chart defaults differ from Policy Controller:

| Setting | Policy Controller default | Gatekeeper Helm chart default | Recommendation |
| Mutation | Off unless you turned it on | On | If you don't use mutation, then set the `disableMutation: true` Helm value. On Autopilot clusters, you must either set the `disableMutation: true` Helm value or configure the `mutatingWebhookCustomRules` Helm value without wildcards or restricted resources. If you don't specify one of these values, then GKE rejects the installation. |
| Exempt namespaces | The `gatekeeper-system` namespace and the namespaces in your `exemptableNamespaces` list | The namespace where Gatekeeper is installed | Add your exempt namespaces to the `controllerManager.exemptNamespaces` Helm value. Gatekeeper rejects changes to namespaces that have the `admission.gatekeeper.sh/ignore` label unless those namespaces are exempt. |
| Referential data | Synced automatically for referential bundles | Not synced | Create a Gatekeeper Config or SyncSet that lists the kinds that your referential templates read. Until those kinds sync into the `data.inventory` object, conflict checks allow violating requests, and existence checks, such as the `K8sRequireNamespaceNetworkPolicies` ConstraintTemplate, deny every matching request even when the required resource exists. |
| ValidatingAdmissionPolicy generation | Not available | On for templates that contain CEL code | Policy Controller templates contain only Rego, so Gatekeeper generates ValidatingAdmissionPolicies only if you add CEL to your ConstraintTemplates. To keep enforcement in the webhook, set the `defaultCreateVAPForTemplates: false` and `defaultCreateVAPBindingForConstraints: false` Helm values. For more information, see [Integration with Kubernetes Validating Admission Policy](https://open-policy-agent.github.io/gatekeeper/website/docs/validating-admission-policy/) in the Gatekeeper documentation. |
| Replicas and resources | One replica for each component, with a 1,024 MiB memory limit | Three webhook replicas, with different resource limits | Configure the replica count and resource limits based on your current usage. |
| Images | Pulled from a Google-managed registry | Pulled from Docker Hub (the `openpolicyagent/gatekeeper`, `openpolicyagent/gatekeeper-crds`, and `curlimages/curl` images) | For private clusters or clusters that enforce Binary Authorization, mirror all three images to Artifact Registry and override their image values in the chart. If you don't mirror the `curlimages/curl` image, then disable the webhook probe Job. |
|---|---|---|---|
[Comparison of Policy Controller defaults and Gatekeeper Helm chart
defaults]

By default, Gatekeeper exposes only Prometheus metrics, and its metric names
differ from the names that Policy Controller sends to Cloud Monitoring.
Update any dashboards and alerts that use Policy Controller metrics.

## Migrate mutation policies

If you enabled mutation in Policy Controller, then list your mutators:

```bash
kubectl get assign,assignmetadata,assignimage,modifyset
```

For each mutator, choose one of the following options:

- **MutatingAdmissionPolicy** : you can use a MutatingAdmissionPolicy in GKE version 1.36 and later. Like a ValidatingAdmissionPolicy, a MutatingAdmissionPolicy uses CEL expressions and runs in the API server without a webhook. Kubernetes runs all MutatingAdmissionPolicies before ValidatingAdmissionPolicies, so test them together to make sure that a mutation doesn't cause a ValidatingAdmissionPolicy to reject a valid manifest. On Autopilot clusters, GKE excludes managed namespaces from MutatingAdmissionPolicies. For more information about mutating policies, see [Mutating Admission Policy](https://kubernetes.io/docs/reference/access-authn-authz/mutating-admission-policy/) in the Kubernetes documentation.
- **Self-managed Gatekeeper** : on Standard clusters, your mutators work without changes because Gatekeeper turns on mutation by default. On Autopilot clusters, you must set the `mutatingWebhookCustomRules` Helm value in the Gatekeeper chart to list the specific resources that your mutators target instead of wildcard rules. For more information, see [GKE Autopilot security measures](https://docs.cloud.google.com/kubernetes-engine/docs/concepts/autopilot-security.md.txt#webhooks). After you install Gatekeeper, remove the Policy Controller metadata from your mutator backup file and reapply your mutators, as you do for constraints.

## Update policy checks in build pipelines

Your build pipeline might check manifests against your Policy Controller
constraints before deployment. For example, the pipeline might use the
`gatekeeper` function in `kpt` or the `gator` command-line tool. For more
information, see
[Validate apps against company policies in a CI pipeline](https://docs.cloud.google.com/kubernetes-engine/policy-controller/docs/tutorials/app-policy-validation-ci-pipeline.md.txt).

If your pipeline checks manifests, then update it for your replacement:

- If you use Gatekeeper, then you can keep your pipeline. Update the file or directory paths in your `gator` or `kpt` command to reference the repository or directory that contains your cleaned Gatekeeper ConstraintTemplate and constraint manifests.
- If you use ValidatingAdmissionPolicies, then those tools don't evaluate
  your policies. Instead, run a server-side dry run against a test cluster
  that has your ValidatingAdmissionPolicies and
  ValidatingAdmissionPolicyBindings that use the `Deny` action:

  ```bash
  kubectl apply --dry-run=server -f MANIFEST_DIRECTORY
  ```

  Replace `MANIFEST_DIRECTORY` with the directory that
  contains your manifests. The command fails if a ValidatingAdmissionPolicy
  rejects a manifest.

## Replace violation reporting

A ValidatingAdmissionPolicy doesn't include a dashboard, and it doesn't audit
resources that already exist in your cluster. Use the following methods
instead:

- **Warnings** : if a ValidatingAdmissionPolicyBinding uses the `Warn` action, then the API server returns a warning to the client that sent the request, such as the `kubectl` command-line tool. For Pods that a controller creates, such as the Pods in a Deployment, the controller receives the warning.
- **Audit logs** : if a ValidatingAdmissionPolicyBinding uses the `Audit`
  action, then the API server annotates the audit log entry for each failed
  validation. GKE writes create, update, and delete requests
  to Admin Activity audit logs in Cloud Logging. To find failed
  validations, run the following query in the Logs Explorer:

      resource.type="k8s_cluster"
      logName:"cloudaudit.googleapis.com%2Factivity"
      labels."validation.policy.admission.k8s.io/validation_failure":*

If you use constraints that have the `dryrun` enforcement action as a
compliance report on existing resources, then ValidatingAdmissionPolicies can't
replace that report. Keep those policies on self-managed Gatekeeper, whose
audit controller checks existing resources on a schedule.

## Remove Policy Controller

Before you remove Policy Controller from a cluster, complete the following
tasks:

- Confirm that any policies that you migrated to ValidatingAdmissionPolicies or Pod Security Admission are enforced and that you have a backup of your ConstraintTemplates, constraints, and mutators. Removing Policy Controller deletes its CustomResourceDefinitions, which deletes every ConstraintTemplate, constraint, and mutator on the cluster, including the ones that you created.
- If you deploy constraints from a Git repository by using a GitOps tool such as Config Sync or Argo CD, then remove the replaced constraints from the repository. Confirm that the constraints are removed from your clusters.
- Optional: To confirm that nothing depends on the Policy Controller webhook, [suspend Policy Controller](https://docs.cloud.google.com/kubernetes-engine/policy-controller/docs/how-to/stopping-policy-controller.md.txt) first. Suspending removes only the webhook configurations, and you can restore them.

To remove Policy Controller, follow these steps:

1. Uninstall Policy Controller from your clusters:

   ```bash
   gcloud container fleet policycontroller disable \
       --memberships=MEMBERSHIP_NAME \
       --project=PROJECT_ID
   ```

   Replace the following:
   - `MEMBERSHIP_NAME`: the membership name of the registered cluster that you want to uninstall Policy Controller from. To uninstall Policy Controller from multiple clusters, specify a comma-separated list of membership names, or replace the `--memberships=MEMBERSHIP_NAME` flag with the `--all-memberships` flag.
   - `PROJECT_ID`: the ID of your fleet host project.
2. Confirm that Policy Controller has finished uninstalling from your
   clusters:

   ```bash
   gcloud container fleet policycontroller describe \
       --memberships=MEMBERSHIP_NAME \
       --project=PROJECT_ID \
       --format="yaml(membershipStates)"
   ```

   Wait until the value of the top-level `policycontroller.state` field for
   each membership is `NOT_INSTALLED`, not `DECOMMISSIONING`. Individual
   `componentStates` entries reach the `NOT_INSTALLED` state before
   CustomResourceDefinition cleanup finishes.

   If you plan to install self-managed Gatekeeper, then confirm that the
   `gatekeeper-system` namespace no longer exists:

   ```bash
   kubectl get namespace gatekeeper-system
   ```

   Wait until the output reports that the `gatekeeper-system` namespace isn't
   found.
3. Optional: To prevent clusters that you register later from getting
   Policy Controller, remove the fleet-level default configuration:

   ```bash
   gcloud container fleet policycontroller enable \
       --no-fleet-default-member-config \
       --project=PROJECT_ID
   ```

   This command removes only the default configuration. It doesn't install
   Policy Controller on any cluster.

> [!CAUTION]
> **Caution:** Don't delete the Policy Controller fleet feature or unregister a cluster until Policy Controller finishes uninstalling. Otherwise, Policy Controller keeps running on the cluster without management. Don't use the `gcloud container fleet policycontroller detach` command to remove Policy Controller, because that command stops management and leaves Policy Controller running.

### Remove Policy Controller from clusters outside a fleet

The `gcloud container fleet policycontroller disable` command removes
Policy Controller only from clusters that the fleet manages. A cluster can run
Policy Controller without fleet management in the following cases:

- You installed Policy Controller by using the ConfigManagement Operator.
- You unregistered the cluster from its fleet, or ran the `gcloud container fleet policycontroller detach` command, before Policy Controller finished uninstalling.

To remove Policy Controller from these clusters, bring the cluster under fleet
management, and then uninstall Policy Controller:

1. Back up your ConstraintTemplates, constraints, sync configuration, and mutators, as described in [Inventory and classify your policies](https://docs.cloud.google.com/kubernetes-engine/docs/deprecations/policy-controller.md.txt#inventory-and-classify). When you clean metadata from backups taken from a ConfigManagement Operator installation, also remove the `configmanagement.gke.io/*` and `k8s-app: kubernetes-config-management` labels in addition to the `ownerReferences` field and `policycontroller.*` metadata.
2. If you installed Policy Controller by using the ConfigManagement Operator,
   then remove the `policyController` field from the ConfigManagement custom
   resource on the cluster. Wait until reconciliation completes.
   Reconciliation can take about two minutes. Then, check whether the
   `gatekeeper-system` namespace still exists:

   ```bash
   kubectl get namespace gatekeeper-system
   ```

   In ConfigManagement Operator version 1.17 and earlier, removing the
   `policyController` field uninstalls Policy Controller automatically. If the
   `gatekeeper-system` namespace isn't found, then skip the remaining steps in
   this section. In ConfigManagement Operator version 1.18 and later, the
   `gatekeeper-system` namespace remains on the cluster, so continue to the
   next step.
3. If the cluster isn't registered to a fleet, then register it. For more
   information, see
   [Register a cluster on Google Cloud to your fleet](https://docs.cloud.google.com/kubernetes-engine/fleet-management/docs/register/gke.md.txt).

4. Check whether mutation is enabled on the cluster:

   ```bash
   kubectl get mutatingwebhookconfiguration \
       gatekeeper-mutating-webhook-configuration
   ```
5. Enable Policy Controller on the cluster's membership so that the fleet
   takes over management of the existing installation. If the preceding
   command returned the `gatekeeper-mutating-webhook-configuration` resource,
   then include the `--mutation` flag so that the fleet adopts and later
   removes cluster-scoped mutation resources:

   ```bash
   gcloud container fleet policycontroller enable \
       --memberships=MEMBERSHIP_NAME \
       --project=PROJECT_ID
   ```

   Replace the following:
   - `MEMBERSHIP_NAME`: the membership name of your registered cluster.
   - `PROJECT_ID`: the ID of your fleet host project.
6. Wait until the value of the `policycontroller.state` field for the
   membership is `ACTIVE`:

   ```bash
   gcloud container fleet policycontroller describe \
       --memberships=MEMBERSHIP_NAME \
       --project=PROJECT_ID \
       --format="yaml(membershipStates)"
   ```
7. Uninstall Policy Controller by following the steps in
   [Remove Policy Controller](https://docs.cloud.google.com/kubernetes-engine/docs/deprecations/policy-controller.md.txt#remove-policy-controller). If you aren't
   installing self-managed Gatekeeper, then delete any remaining constraint
   CustomResourceDefinitions after the `gatekeeper-system` namespace is
   removed:

   ```bash
   kubectl get crd -o name \
       | grep '\.constraints\.gatekeeper\.sh$' \
       | xargs -r kubectl delete
   ```
8. Optional: If you registered the cluster only to complete this procedure,
   then unregister it after the `describe` command shows the `NOT_INSTALLED`
   state for the membership. For clusters that you registered by using the
   `--enable-fleet` flag, run the `gcloud container clusters update` command
   with the `--clear-fleet-project` flag instead of the
   `gcloud container fleet memberships unregister` command. For more
   information, see
   [Unregistering a cluster](https://docs.cloud.google.com/kubernetes-engine/fleet-management/docs/unregister.md.txt).

## What's next

- Read about [Validating Admission Policy](https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/) in the Kubernetes documentation.
- Learn how to [apply predefined Pod-level security policies using PodSecurity](https://docs.cloud.google.com/kubernetes-engine/docs/how-to/podsecurityadmission.md.txt).
- Read the [Gatekeeper documentation](https://open-policy-agent.github.io/gatekeeper/website/docs/).