Cambiar recursos

En esta página se explica cómo mutar recursos con Policy Controller. Esto resulta útil para hacer cosas como definir valores predeterminados. Por ejemplo, puede que quieras insertar una etiqueta para todos los recursos de un espacio de nombres específico o asignar el valor Always a imagePullPolicy de un pod si aún no se ha definido.

Habilitar mutación

Consola

Para habilitar la mutación, sigue estos pasos:

  1. En la consola, vaya a la página Política de la sección Gestión de postura. Google Cloud

    Ir a la política

  2. En la pestaña Configuración, en la tabla de clústeres, seleccione Editar en la columna Editar configuración.
  3. Despliega el menú Editar configuración de Policy Controller.
  4. Selecciona la casilla Habilitar webhook de mutación.
  5. Selecciona Guardar cambios.

gcloud

Para habilitar la mutación, ejecuta el siguiente comando:

gcloud container fleet policycontroller update \
    --memberships=MEMBERSHIP_NAME \
    --mutation

Sustituye MEMBERSHIP_NAME por el nombre de miembro del clúster registrado para habilitar la mutación. Puedes especificar varias suscripciones separadas por comas.

Definiciones

  • Mutador: recurso de Kubernetes que ayuda a configurar el comportamiento de mutación de Policy Controller.
  • Sistema: una disposición de varios mutadores

Ejemplo de mutación

En el siguiente ejemplo se muestra un mutador que asigna el valor Always a imagePullPolicy de todos los contenedores de todos los pods:

# set-image-pull-policy.yaml

apiVersion: mutations.gatekeeper.sh/v1alpha1
kind: Assign
metadata:
  name: always-pull-image
spec:
  applyTo:
  - groups: [""]
    kinds: ["Pod"]
    versions: ["v1"]
  location: "spec.containers[name: *].imagePullPolicy"
  parameters:
    assign:
      value: "Always"

En este ejemplo, se incluyen los campos de metadatos estándar de Kubernetes (apiVersion, kind y metadata.name), pero spec es donde se configura el comportamiento del mutador.

spec.applyTo vincula el mutador a los recursos especificados. Como vamos a cambiar campos específicos de un objeto, estamos definiendo implícitamente el esquema de ese objeto. Por ejemplo, el mutador actual no tendría sentido si se aplicara a un recurso Namespace. Por este motivo, este campo es obligatorio para que Policy Controller sepa para qué esquemas es relevante este mutador. GroupVersionKinds que falten en esta lista no se mutan.

spec.location nos indica qué campo se va a modificar. En este caso, usamos un glob (*) para indicar que queremos modificar todas las entradas de la lista de contenedores. Ten en cuenta que los únicos tipos de listas que se pueden recorrer con el campo location son las listas de tipo mapa, y se debe especificar el campo de clave del mapa. Las listas de tipo mapa son una estructura de Kubernetes. Puedes consultar más detalles sobre ellos en la documentación de Kubernetes.

spec.parameters.assign.value es el valor que se asignará a location. Este campo no tiene tipo y puede adoptar cualquier valor. Sin embargo, ten en cuenta que Kubernetes sigue validando la solicitud después de la mutación, por lo que, si insertas valores con un esquema incorrecto para el objeto que se está modificando, la solicitud se rechazará.

Usar mutadores para Jobs

Si vas a configurar un trabajo o un CronJob, debes especificar la versión y el grupo por separado, tal como se muestra en el siguiente ejemplo:

applyTo:
- groups: ["batch"]
  kind: ["Job"]
  versions: ["v1"]

Cuando usas un mutador para Jobs, los Jobs no se mutan a menos que se modifiquen. Si se modifica un trabajo, se activa una solicitud al webhook de mutación y se muta.

Flujo de ejecución

Quizá el concepto más importante que debes conocer sobre los webhooks de mutación de Kubernetes es su política de reinvocación, ya que el resultado de un mutador puede cambiar el comportamiento de otro. Por ejemplo, si añades un nuevo contenedor sidecar, un mutador que defina la política de extracción de imágenes para todos los contenedores tendrá un nuevo contenedor que mutar.

En la práctica, esto significa que, para cualquier solicitud, es posible que se llame al webhook de mutación de Policy Controller más de una vez.

Para reducir la latencia, Policy Controller vuelve a invocarse a sí mismo para evitar solicitudes HTTP adicionales. Esto significa que la inyección de sidecar y la política de extracción de imágenes tienen el resultado esperado.

La rutina de mutación de Policy Controller seguirá invocándose a sí misma hasta que el recurso "converja", lo que significa que las iteraciones adicionales no tendrán ningún efecto.

Sintaxis de ubicación

La sintaxis de ubicación usa el descriptor de acceso de punto (.) para desplazarse por los campos. En el caso de las listas con claves, los usuarios pueden hacer referencia a objetos concretos de una lista mediante la sintaxis [<key>: <value>] o a todos los objetos de la lista mediante [<key>: *].

Los valores y los campos se pueden entrecomillar con comillas simples (') o dobles ("). Esto es necesario cuando tienen caracteres especiales, como puntos o espacios.

En los valores entre comillas, los caracteres especiales se pueden escapar añadiendo el prefijo \. "Use \" to escape and \\\" to escape" se convierte en Use " to escape and \" to escape.

Algunos ejemplos del recurso v1/Pod:

  • spec.priority referencias spec.priority
  • spec.containers[name: "foo"].volumeMounts[mountPath: "/my/mount"].readOnly hace referencia al campo readOnly del montaje /my/mount del contenedor foo.
  • spec.containers[name: *].volumeMounts[mountPath: "/my/mount"].readOnly hace referencia al campo readOnly del /my/mount de todos los contenedores.

Si haces referencia a una ubicación que no existe en un recurso, se creará de forma predeterminada. Este comportamiento se puede configurar mediante pruebas de ruta.

Pruebas de rutas

¿Cómo podemos realizar la asignación de valores predeterminados sin modificar un valor que ya existe? Quizá queramos definir /secure-mount como de solo lectura para todos los contenedores, pero no queremos crear un /secure-mount si aún no existe. Podemos hacer cualquiera de estas dos cosas mediante pruebas de ruta.

Aquí tienes un ejemplo que evita mutar imagePullPolicy si ya se ha definido:

# set-image-pull-policy.yaml

apiVersion: mutations.gatekeeper.sh/v1alpha1
kind: Assign
metadata:
  name: always-pull-image
spec:
  applyTo:
  - groups: [""]
    kinds: ["Pod"]
    versions: ["v1"]
  location: "spec.containers[name: *].imagePullPolicy"
  parameters:
    assign:
      value: "Always"
    pathTests:
    - subPath: "spec.containers[name: *].imagePullPolicy"
      condition: "MustNotExist"

Aquí tienes otro ejemplo en el que se evita crear un contenedor sidecar vacío si aún no existe:

# set-image-pull-policy.yaml

apiVersion: mutations.gatekeeper.sh/v1alpha1
kind: Assign
metadata:
  name: always-pull-image
spec:
  applyTo:
  - groups: [""]
    kinds: ["Pod"]
    versions: ["v1"]
  location: 'spec.containers[name: "sidecar"].imagePullPolicy'
  parameters:
    assign:
      value: "Always"
    pathTests:
    - subPath: 'spec.containers[name: "sidecar"]'
      condition: "MustExist"

Si es necesario, se pueden especificar varias pruebas de ruta.

subPath debe ser un prefijo de location (o igual a él).

Los únicos valores válidos para condition son MustExist y MustNotExist.

Concordancia

Los mutadores también permiten la coincidencia, utilizando los mismos criterios que las restricciones.

Mutadores

Actualmente hay dos tipos de mutadores: Assign y AssignMetadata.

Asignad

Assign puede cambiar cualquier valor fuera del campo metadata de un recurso. Como todos los GroupVersionKinds tienen un esquema único, deben estar vinculados a un conjunto de GroupVersionKinds concretos.

Tiene el siguiente esquema:

apiVersion: mutations.gatekeeper.sh/v1alpha1
kind: Assign
metadata:
  name: always-pull-image
spec:
  applyTo:
  - groups: [""]
    kinds: ["Pod"]
    versions: ["v1"]
  match:
    kinds: # redundant because of `applyTo`, but left in for consistency
      - apiGroups: ["*"]
        kinds: ["*"]
    namespaces: ["my-namespace"]
    scope: "Namespaced" # or "Cluster"
    excludedNamespaces: ["some-other-ns"]
    labelSelector:
      matchLabels:
        mutate: "yes"
      matchExpressions:
      - key: "my-label"
        operator: "In" # or, "NotIn", "Exists", or "DoesNotExist"
        values: ["my-value"]
    namespaceSelector:
      matchLabels:
        mutate: "yes"
      matchExpressions:
      - key: "my-label"
        operator: "In" # or, "NotIn", "Exists", or "DoesNotExist"
        values: ["my-value"]
  location: "spec.containers[name: *].imagePullPolicy"
  parameters:
    pathTests:
    - subPath: 'spec.containers[name: "sidecar"]' # must be a prefix of `location`
      condition: "MustExist" # or "MustNotExist"
    - subPath: "spec.containers[name: *].imagePullPolicy"
      condition: "MustNotExist"
    assign:
      value: "Always" # any type can go here, not just a string

AssignMetadata

AssignMetadata puede añadir nuevas etiquetas de metadatos. No puede cambiar el valor de las etiquetas de metadatos que ya existen. De lo contrario, sería posible escribir un sistema de mutadores que se repitiera indefinidamente, lo que provocaría que las solicitudes se agotasen.

Como todos los recursos comparten el mismo esquema metadata, no es necesario especificar a qué recurso se aplica un AssignMetadata.

Además, como AssignMetadata no puede hacer tantas cosas, su esquema es un poco más sencillo.

apiVersion: mutations.gatekeeper.sh/v1alpha1
kind: AssignMetadata
metadata:
  name: set-team-name
spec:
  match:
    kinds:
      - apiGroups: ["*"]
        kinds: ["*"]
    namespaces: ["my-namespace"]
    scope: "Namespaced" # or "Cluster"
    excludedNamespaces: ["some-other-ns"]
    labelSelector:
      matchLabels:
        mutate: "yes"
      matchExpressions:
      - key: "my-label"
        operator: "In" # or, "NotIn", "Exists", or "DoesNotExist"
        values: ["my-value"]
    namespaceSelector:
      matchLabels:
        mutate: "yes"
      matchExpressions:
      - key: "my-label"
        operator: "In" # or, "NotIn", "Exists", or "DoesNotExist"
        values: ["my-value"]
  location: "metadata.labels.team" # must start with `metadata.labels`
  parameters:
    assign:
      value: "Always" # any type can go here, not just a string

Prácticas recomendadas

Advertencias de Kubernetes

En la documentación de Kubernetes se indican algunas consideraciones importantes sobre el uso de webhooks de mutación. Como Policy Controller funciona como un webhook de admisión de Kubernetes, estos consejos también se aplican en este caso.

La sintaxis de mutación de Policy Controller se ha diseñado para que sea más fácil cumplir los requisitos operativos de los webhooks de mutación, incluida la idempotencia.

Escribir mutadores

Atomicidad

Es recomendable que cada mutador sea lo más autosuficiente posible. Como Kubernetes es coherente en última instancia, un mutador no debe depender de que se haya reconocido un segundo mutador para hacer su trabajo correctamente. Por ejemplo, al añadir un sidecar, añada todo el sidecar, no lo construya poco a poco usando varios mutadores.

Validación

Si quieres aplicar una condición, es recomendable que el mutador tenga una restricción coincidente. De esta forma, nos aseguramos de que se rechacen las solicitudes infractoras y de que se detecten las infracciones preexistentes en la auditoría.

Recuperación de emergencia

La mutación se implementa como un webhook de mutación de Kubernetes. Se puede detener de la misma forma que el webhook de validación, pero el recurso pertinente es un MutatingWebhookConfiguration llamado gatekeeper-mutating-webhook-configuration.

Siguientes pasos