A finales de 2019, Para OpenShift Ingeniero de Confiabilidad del Sitio (SRE) El equipo de SRE-Platform (SREP) tuvo problemas antes de lanzar una nueva característica: Kubernetes controles de autenticación basados en roles (RBAC) no funcionó. O, mejor dicho, no nos funciona. RBAC quiere trabajar en una amplia gama de situaciones: Los usuarios con este rol pueden realizar este tipo de operaciones en este tipo de objetos. No hay una manera fácil de decir «excepto estos objetos específicos», necesitamos una forma de hacerlo. ¿Por qué RBAC es lo suficientemente bueno para la mayoría de las personas pero no para nosotros?
El equipo de SREP opera la plataforma para el producto OpenShift Dedicated de Red Hat. Esto incluye colocar barandillas alrededor de los componentes principales de OpenShift para que los usuarios no interfieran accidentalmente con el funcionamiento del clúster. Si bien nuestros usuarios pueden esperar crear sus propios espacios de nombres, no queremos que se interfiera con los espacios de nombres centrales (como kube-system). Las reglas de RBAC dicen que los usuarios pueden crear y eliminar sus propios espacios de nombres, pero otorgar ese acceso permitiría eliminar el acceso al sistema kube, lo que sería problemático.
El problema se extiende desde los espacios de nombres a otros objetos, como las configuraciones de usuarios, grupos y proveedores de identidad que los ingenieros usan para responder a los problemas de los clústeres. Es fundamental mantener estos tres objetos a salvo de manipulaciones. Pero también queremos que los usuarios administren su propia configuración de usuario, grupo y proveedor de identidad. Kubernetes RBAC nos deja con un gran vacío. Necesitamos respaldar un producto que tenga medidas de protección alrededor de los objetos confidenciales en el clúster, al mismo tiempo que permite a nuestros usuarios administrar sus propios objetos.
Afortunadamente, Kubernetes nos permite tener dos formas: RBAC para trazos gruesos y una llamada Controlador de Admisión Dinámica Para un control detallado que es difícil o engorroso de expresar mediante las reglas RBAC tradicionales. Estos controladores de admisión son manejados por webhooks que deciden cómo manejar la solicitud.
Tabla de Contenidos
Crear expedientes
Mi tarea es crear un webhook para evitar que los clientes accedan a grupos en clúster administrados por SRE. Esperamos que esta solución provisional nos dé tiempo para evaluar una solución más duradera.
Afortunadamente, Kubernetes permite a los administradores proporcionar sus propios controladores de admisión dinámicos personalizados para esto. Una vez que RBAC permite que el usuario realice la solicitud, el servidor API llama al webhook especificado para decidir si permite la solicitud. La llamada de webhook incluye una carga útil que contiene información del usuario (pertenencia de usuarios y grupos) y representaciones de objetos relacionados. De los dos controladores de admisión dinámicos disponibles, confiamos en gran medida en el tipo ValidatingWebhookConfiguration.
Después de un breve período de desarrollo, creé un marco Flask en Python para respaldar los casos de uso del grupo y otros webhooks. Sin embargo, pronto agregamos más webhooks, que hicieron que este marco simple pasara de ser fácil de usar a un esfuerzo de mantenimiento doloroso y propenso a errores. Las primeras opciones de diseño que funcionaron para uno o dos webhooks no demostraron ser escalables.
sentir dolor
Si bien el marco de trabajo de Python funciona bien, interactuar con él es una molestia. La creación de un nuevo webhook significa que los desarrolladores tienen que crear varios archivos YAML, modificar los archivos de Python existentes y agregar nuevos archivos de webhook de Python. La biblioteca original de «ayuda genérica» era poco común y tuvo que ser cambiada.
En la superficie, nada de esto es difícil, pero está en la naturaleza humana basar nuevos trabajos en otros trabajos similares. Los ingenieros copiaron y pegaron los archivos YAML y el código fuente existentes, olvidándose de validar los cambios importantes. La información que describe los webhooks (sus nombres, lógica comercial y configuración) se divide en código YAML y Python. Olvidarse de actualizar la lista de webhooks activos es una fuente de errores.
Los webhooks también deben ejecutarse lo más rápido posible, ya que evitan que el servidor API acepte los cambios del usuario. Incluso se puede sentir un ligero retraso, especialmente con cambios automáticos frecuentes. Esto se debe a que los cambios en los tipos de objetos comunes (como los espacios de nombres) desencadenan webhooks cada vez, y cualquier ralentización puede causar problemas graves de rendimiento del servidor API.
Las pruebas no estaban en el diseño original. Más tarde decidimos agregar pruebas para ganar confianza en nuestro webhook. La prueba fue difícil porque todos los webhooks posteriores heredaron patrones de diseño iniciales deficientes.
buscar alivio
A fines de la primavera de 2020, frustrado con el desorden del código base de Python y YAML, transfirí todo a Golang, decidido a tomar una mejor decisión esta vez. Al hacer una reescritura, no quiero que nadie tenga que escribir o modificar archivos YAML. Quiero crear un marco que resuelva el problema general de cómo SREP escribe y administra los webhooks ValidatingWebhookConfiguration mediante la automatización tanto como sea posible. Por estas razones, diseñé una interfaz de Golang para proporcionar a los autores de webhooks una manera fácil de escribir sus webhooks y registrarlos en el marco.
El registro del webhook se realiza a través de un único archivo por webhook (este webhook de espacio de nombres es un ejemplo), aprovechando el comportamiento de la función init de Golang.
La migración inicial fue bastante sencilla, copiando cada archivo de Python más o menos directamente. Debido a que los webhooks de Python manejan las solicitudes HTTP, también lo hace el nuevo webhook de Golang.El «punto de entrada» inicial de cada webhook es una forma desafortunada de acoplar cada webhook a net/http
(HandleRequest(http.ResponseWriter, *http.Request))
Aún así, fue una decisión consciente aplazar la refactorización de los paquetes de Python para obtener una ventaja en Golang.
El «punto de entrada» ahora permite que el webhook se centre más en las implicaciones del manejo de las solicitudes del servidor API. De esta forma, los webhooks no necesitan preocuparse por net/http, siempre que se les asigne un objetomissionctl.Request. Después de refactorizar para eliminar HandleRequest:
import admissionctl "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
Authorized(request admissionctl.Request) admissionctl.Response
La interfaz de Golang permite que el webhook se registre principalmente, por lo que el código del marco puede ejecutar el servidor web y aceptar solicitudes entrantes del servidor API. Sin embargo, este no es el único valor proporcionado por la interfaz.
Mi experiencia en programación tiene sus raíces en Ruby, y uno de los conceptos centrales de Ruby es que puede «hacer» una pregunta a un objeto (por ejemplo, obj.nil?
si devuelve verdadero o falso obj
está teniendo un nil
value) enviando un «mensaje» al objeto. Encuentro natural pensar en el mismo patrón. Entonces, cuando construí la interfaz, pensé en lo que el marco podría querer «preguntar» sobre los webhooks. En otras palabras, un webhook debe tener la capacidad de responder preguntas sobre sí mismo para que otros componentes del marco puedan interactuar de maneras diferentes y significativas.
Di adiós a YAML manual
La nueva interfaz de Golang está en el corazón de la reescritura de Golang, lo que permite eliminar la necesidad de que los ingenieros escriban YAML manualmente. Todavía necesitamos YAML, pero sería mejor si el marco pudiera escribirlo por nosotros.
En Kubernetes y OpenShift, cada webhook necesita un objeto ValidatingWebhookConfiguration para indicar al servidor de la API cómo y cuándo acceder al webhook.
La interfaz de Golang requiere que cada webhook sepa su nombre, la solicitud para enviar el identificador uniforme de recursos (URI), lo que debe manejar (como espacios de nombres y grupos), qué sucede si el servidor API no puede acceder al webhook y otros hechos útiles.
escribí un subprogramas Está asociado con el mismo registro de webhook para «preguntar» la «respuesta» necesaria para crear un objeto ValidatingWebhookConfiguration que se usará en el clúster. No es necesario editar manualmente YAML, simplemente ejecute un programa.
[ Get started with containers in 30 days with the Containers, Kubernetes, and Red Hat OpenShift technical overview course. ]
aumentar el valor de la interfaz
La misma interfaz que usamos para evitar escribir YAML se usa dentro del marco para otro propósito: verificación de duplicados. Supongamos que alguien agrega accidentalmente dos webhooks que requieren el mismo URI de solicitud. En este caso, el marco entra en pánico y lo prohíbe. Estos problemas aún existen; la naturaleza humana no evita copiar y pegar solo porque usamos Golang en lugar de Python.
A medida que aprendimos más sobre la nueva implementación de Golang, descubrimos que queríamos exigir más a nuestros webhooks. ¿Qué pasaría si pudiéramos ayudar a proporcionar documentación sobre el propósito de cada webhook?Después de consultar con el equipo de documentación, amplié la interfaz para incluir un Doc() string
método, que a su vez es otro subprograma Escribir documentación. Está escrito en JSON, por lo que se puede incluir en otros procesos para proporcionar un formato más legible por humanos.
Mejorar las pruebas
El equipo de SREP es principalmente un equipo centrado en Golang, y la mayor parte de nuestra experiencia en pruebas está dentro del dominio de Golang. Por esta razón, migrar un webhook basado en Python a Golang representa un avance en las opciones de prueba. El estado actual de las pruebas unitarias dentro del marco es funcional y la cobertura es buena, pero hay espacio para una mejor experiencia del desarrollador para limpiar algunos patrones repetitivos.
limpiar webhooks
Obligar a cada webhook a ser independiente facilita la eliminación de webhooks obsoletos. SREP solía tener tres webhooks adicionales para asegurar usuarios específicos, grupos y objetos relacionados con la identidad de SRE para que los SRE pudieran acceder y administrar clústeres de usuarios de manera confiable. Cada SRE tiene un usuario en el clúster con pertenencia a un grupo asociado con una función RBAC específica. Los SRE que inician sesión en clústeres de usuarios usan proveedores de identidad solo de SRE. Estos objetos en el clúster deben protegerse para garantizar la cadena de inicio de sesión.
Mucho después de que se crearan y trasladaran los webhooks a Golang, hubo un cambio fundamental en la forma en que los SRE administraban los clústeres, alejándose de los usuarios, grupos y proveedores de identidad dentro del clúster. El patrón de diseño inicial de hacer que los webhooks fueran lo más autónomos posible significa que eliminar el trío de webhooks obsoletos no es más desafiante que eliminar el directorio de webhooks y sus archivos de registro.Use nuestra utilidad para regenerar el YAML, una vez que se complete la regeneración, el webhook ya no existirá, gracias a nuestro Gestión de clústeres método.
Reconocer opciones y limitaciones.
Nuestro nuevo enfoque no es perfecto. Todavía hay algunas limitaciones.
Webhook de mutación
Todos los webhooks de SREP son tipos ValidatingWebhookConfiguration y son los únicos tipos de webhook admitidos por el marco descrito en este artículo. No se admite MutatingWebhookConfiguration. Cambiar el webhook cambia la representación del objeto enviado al servidor API, que no es el flujo de trabajo que necesita el equipo.
Control de autenticación
No hay controles de autenticación en el marco de webhook, como pueden tener otros controladores implementados en el clúster. Cuando diseñamos esto, no parece que se requiera autenticación en el servidor API.Entonces, por ejemplo, cualquiera puede llamar al servicio y enviar un objeto AdmissionReview curl(1)
.
Sin embargo, la comunicación entre el servidor API y el servicio de webhook está encriptada para ayudar a evitar material confidencial en la carga útil.
otras opciones
Este marco de webhook ciertamente no es la única forma de lograr el objetivo del control de admisión dinámico. Miramos otras opciones disponibles.
- Agente de política abierta: Una de las otras opciones que consideramos fue Agente de políticas abierto, que puede realizar este control de admisión. En última instancia, no lo adoptamos por razones que no recordamos.
- Otros marcos: Puede usar otros marcos similares a este para la integración. Desafortunadamente, otras soluciones no funcionaron para nosotros porque requerían más trabajo para integrarse y algunas requerían autenticación.
Han pasado más de dos años desde que concebimos originalmente este marco, y han pasado casi dos años desde que cambiamos a Golang. Eso significa que, sin duda, hay otras opciones ahora. Ninguno es malo y ninguno es mejor que lo que diseñamos.
respirar nueva vida
Hacia fines de 2019, SREP enfrentó desafíos y necesitábamos una solución rápida. Python cumple su promesa como un lenguaje de desarrollo rápido, lo que nos permite enfrentar los desafíos rápidamente y nos da tiempo para trabajar en soluciones más duraderas.
La migración a Golang le da nueva vida a la inmanejable maraña de Python y YAML. Con las funciones nativas de Golang, los ingenieros pueden concentrarse en el negocio de escribir webhooks en lugar de preocuparse por YAML y manejar solicitudes HTTP.