- -
- 100%
- +
La ventaja del enfoque del clúster por usuario es su simplicidad: cada desarrollador puede gestionar su propio clúster y, debido a su aislamiento, es mucho más difícil para los diferentes desarrolladores interferirse entre ellos.
Por otra parte, un único clúster de desarrollo será mucho más eficiente. Es probable que podamos mantener el mismo número de desarrolladores en un clúster compartido por la tercera parte del precio (o menos). Además, es mucho más fácil para nosotros instalar servicios en clústeres compartidos, por ejemplo la monitorización y el registro, lo que facilita considerablemente producir un clúster amigable con el desarrollador. La desventaja de un clúster de desarrollo compartido es el proceso de gestión de usuarios y la posible interferencia entre desarrolladores. Debido a que actualmente el proceso de añadir nuevos usuarios y namespaces (espacios de nombres) al clúster de Kubernetes no es ágil, deberemos activar un proceso para incorporar nuevos desarrolladores. Aunque la gestión de recursos de Kubernetes y el Role-Based Access Control (control de acceso basado en roles) (RBAC) pueden reducir la probabilidad de que dos desarrolladores entren en conflicto, siempre es posible que un usuario pueda bloquear el clúster de desarrollo al consumir demasiados recursos —de manera que otras aplicaciones y desarrolladores no los pueden planificar—. Además, deberemos asegurarnos de que los desarrolladores no pierdan ni olviden los recursos que han creado. Esto es algo más fácil, sin embargo, que el enfoque en el que los desarrolladores crean sus propios clústeres.
Ambos enfoques son factibles, pero nuestra recomendación es tener un único clúster grande para todos los desarrolladores. Aunque puedan existir problemas de interferencia entre ellos, estos se pueden gestionar y, en última instancia, la rentabilidad y la posibilidad de agregar fácilmente capacidades al clúster a nivel de toda la organización compensan los riesgos que supone la interferencia. Pero tendremos que invertir en los procesos de incorporación de nuevos desarrolladores, de gestión de recursos y de recolección de basura. Nuestra recomendación es intentarlo con un único clúster grande como primera opción. A medida que la organización crezca (o si ya es una organización grande), podríamos considerar tener un clúster por equipo de trabajo o grupo (de 10 a 20 personas), en lugar de un clúster gigante para cientos de usuarios. Esto puede simplificar tanto la facturación como la gestión.
Configuración de un clúster compartido por varios desarrolladores
Cuando se configura un clúster grande, el objetivo principal es asegurar que lo puedan utilizar varios usuarios simultáneamente sin que interfieran entre ellos. La manera obvia de separar a los diferentes desarrolladores es con los espacios de nombres de Kubernetes. Los espacios de nombres pueden servir como marco para el despliegue de servicios, de manera que el servicio de frontend de un usuario no interfiera con el servicio de frontend de otro usuario. Los espacios de nombres son también el marco para RBAC, con lo que se asegura que un desarrollador no pueda borrar accidentalmente el trabajo realizado por otro desarrollador. Por lo tanto, en un clúster compartido tiene sentido utilizar un espacio de nombres como el espacio de trabajo de cada desarrollador. Los procesos para la incorporación de usuarios y la creación y protección de un espacio de nombres se describen en las secciones siguientes.
Registro de usuarios
Antes de que podamos asignar un usuario a un espacio de nombres, debemos registrarlo en el propio clúster de Kubernetes. Para hacerlo hay dos opciones. Podemos utilizar la autenticación basada en certificados, crear un nuevo certificado para el usuario y proporcionarle un archivo kubeconfig que puede usar para iniciar sesión. O podemos configurar nuestro clúster para que utilice un sistema de identidad externo (por ejemplo, Microsoft Azure Active Directory o AWS Identity and Access Management [IAM]) para el acceso al clúster.
En general, el uso de un sistema de identidad externo es una buena práctica porque no requiere mantener dos fuentes de identidad diferentes, pero en algunos casos esto no es posible y necesitamos usar certificados. Afortunadamente, podemos usar la API de certificados de Kubernetes para la creación y gestión de dichos certificados. A continuación, veremos el proceso para añadir un nuevo usuario a un clúster existente. En primer lugar, necesitamos crear una solicitud de firma de certificado para generar un nuevo certificado. A continuación, veremos un sencillo programa en Go que permite realizarlo:
package main import ( "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/pem" "os" ) func main() { name := os.Args[1] user := os.Args[2] key, err := rsa.GenerateKey(rand.Reader, 1024) if err != nil { panic(err) } keyDer := x509.MarshalPKCS1PrivateKey(key) keyBlock := pem.Block{ Type: "RSA PRIVATE KEY", Bytes: keyDer, } keyFile, err := os.Create(name + "-key.pem") if err != nil { panic(err) } pem.Encode(keyFile, &keyBlock) keyFile.Close() commonName := user // You may want to update these too emailAddress := "someone@myco.com" org := "My Co, Inc." orgUnit := "Widget Farmers" city := "Seattle" state := "WA" country := "US" subject := pkix.Name{ CommonName: commonName, Country: []string{country}, Locality: []string{city}, Organization: []string{org}, OrganizationalUnit: []string{orgUnit}, Province: []string{state}, } asn1, err := asn1.Marshal(subject.ToRDNSequence()) if err != nil { panic(err) } csr := x509.CertificateRequest{ RawSubject: asn1, EmailAddresses: []string{emailAddress}, SignatureAlgorithm: x509.SHA256WithRSA, } bytes, err := x509.CreateCertificateRequest(rand.Reader, &csr, key) if err != nil { panic(err) } csrFile, err := os.Create(name + ".csr") if err != nil { panic(err) } pem.Encode(csrFile, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: bytes}) csrFile.Close() }
Podemos ejecutarlo de la siguiente manera:
go run csr-gen.go client
Con esto creamos los archivos llamados client-key.pem y client.csr. A continuación, podemos ejecutar el siguiente script para crear y descargar un nuevo certificado:
#!/bin/bash csr_name="my-client-csr" name="${1:-my-user}" csr="${2}" cat <
Este script imprime la información final que podemos añadir al archivo kubeconfig para habilitar al usuario. Por supuesto, el usuario no tiene privilegios de acceso, por lo que tendremos que aplicar RBAC de Kubernetes al usuario con el fin de dotar de privilegios al espacio de nombres.
Creación y dotación de seguridad a un espacio de nombres
El primer paso para proporcionar un espacio de nombres es crearlo. Podemos hacerlo usando kubectl create namespace my-namespace.
Pero la verdad es que cuando creamos un espacio de nombres, necesitamos adjuntar un montón de metadatos, como por ejemplo la información de contacto del equipo de trabajo que crea el componente que se ha implementado en el espacio de nombres. Generalmente, esto se hace en forma de anotaciones. Podemos generar el archivo YAML usando alguna plantilla, como por ejemplo Jinja u otras, o bien podemos crear el espacio de nombres y, a continuación, hacer la anotación. Veamos un sencillo script para hacer esto:
ns='my-namespace' kubectl create namespace ${ns} kubectl annotate namespace ${ns} annotation_key= annotation_value
Cuando se crea el espacio de nombres, necesitamos dotarlo de seguridad para tener la garantía de que podemos conceder acceso a un usuario específico. Para ello, podemos vincular un rol a un usuario en el contexto de ese espacio de nombres. Esto lo conseguimos creando el objeto RoleBinding dentro del propio espacio de nombres. RoleBindig podría tener este aspecto:
apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: example namespace: my-namespace roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: edit subjects: - apiGroup: rbac.authorization.k8s.io kind: User name: myuser
Para crearlo, lo único que tenemos que hacer es ejecutar kubectl create -f role-binding.yaml. Hay que tener en cuenta que podemos reutilizar este vínculo tanto como deseemos, siempre y cuando actualicemos el espacio de nombres en el vínculo para apuntar al espacio de nombres correcto. Si tenemos la seguridad de que el usuario no tiene ningún otro vínculo de rol, podemos estar seguros de que este espacio de nombres es la única parte del clúster a la que el usuario tiene acceso. Otra práctica razonable es conceder acceso a los lectores a todo el clúster; de esta manera, los desarrolladores pueden ver lo que otros están haciendo en caso de que interfieran con su trabajo. Sin embargo, debemos tener cuidado al conceder dicho acceso de lectura porque este incluirá el acceso a recursos secretos en el clúster. Generalmente, en un clúster de desarrollo esto está bien porque todo el mundo está en la misma organización y los datos secretos se utilizan solo en desarrollo. Sin embargo, si es motivo de preocupación, podemos crear un rol más detallado en el que se elimine la posibilidad de leer datos secretos.
Si deseamos limitar la cantidad de recursos que consume un espacio de nombres determinado, podemos utilizar el recurso ResourceQuota para fijar un límite al número total de recursos que consume ese espacio de nombres en particular. Por ejemplo, la siguiente cuota limita el espacio de nombres a 10 núcleos y 100 GB de memoria, para Request y Limit, para las cápsulas en ese espacio de nombres:
apiVersion: v1 kind: ResourceQuota metadata: name: limit-compute namespace: my-namespace spec: hard: requests.cpu: "10" requests.memory: 100Gi limits.cpu: "10" limits.memory: 100Gi
Administración de espacios de nombres
Ahora que hemos visto cómo incorporar a un nuevo usuario y cómo crear un espacio de nombres para utilizarlo como espacio de trabajo, la pregunta sigue siendo cómo asignar un desarrollador a un espacio de nombres. Como con muchas otras cosas, no hay una sola respuesta; en este caso hay dos enfoques. El primero es dar a cada usuario su propio espacio de nombres como parte del proceso de integración. Esto es útil porque después de que un usuario se haya registrado, siempre tiene un espacio de trabajo donde puede desarrollar y gestionar sus aplicaciones. Sin embargo, hacer que el espacio de nombres del desarrollador se extienda demasiado en el tiempo anima a este a dejar las cosas en el espacio de nombres después de haber terminado de usarlas, y la recogida de basura y la contabilidad de los recursos individuales se complican. Un enfoque alternativo consiste en crear y asignar temporalmente un espacio de nombres con un time to live (tiempo de vida) (TTL) limitado. Esta opción asegura que el desarrollador tenga en cuenta que los recursos en el clúster son transitorios y que es fácil automatizar la eliminación de espacios de nombres completos cuando su TTL expire.
En este modelo, cuando el desarrollador quiere iniciar un nuevo proyecto, utiliza una herramienta para asignar un nuevo espacio de nombres al proyecto. Cuando creamos el espacio de nombres, este tiene una selección de metadatos asociados a él para la gestión y la contabilidad. Obviamente, estos metadatos incluyen el TTL para el espacio de nombres, pero también incluyen al desarrollador al que está asignado, los recursos que deben asignarse al espacio de nombres (por ejemplo, CPU y memoria), el equipo de trabajo y el propósito del espacio de nombres. Estos metadatos garantizan que podamos realizar un seguimiento del uso de los recursos y eliminar el espacio de nombres en el momento adecuado.
El desarrollo de herramientas para asignar espacios de nombres a petición puede parecer un reto, pero las herramientas sencillas son relativamente fáciles de desarrollar. Por ejemplo, se puede lograr la asignación de un nuevo espacio de nombres con un sencillo script que crea el espacio de nombres y solicita los metadatos relevantes para adjuntarlos al mismo.
Si queremos tener una mayor integración con Kubernetes, podemos usar las custom resources definitions (definiciones de recursos personalizados) (CRD), que permiten a los usuarios crear y asignar dinámicamente nuevos espacios de nombres mediante la herramienta kubectl. Si tienes tiempo y ganas, esta es definitivamente una buena opción porque hace que la gestión de los espacios de nombres sea declarativa y también permite el uso de RBAC de Kubernetes.
Una vez disponemos de las herramientas para habilitar la asignación de espacios de nombres, necesitamos añadir algunas herramientas para reutilizarlos cuando su TTL haya expirado. Una vez más, podemos conseguirlo con un sencillo script que examina los espacios de nombres y borra aquellos que tienen una TTL caducada.
Podemos crear este script en un contenedor y usar ScheduledJob para ejecutarlo con una frecuencia de una hora. Combinadas entre sí, estas herramientas pueden garantizar que los desarrolladores puedan asignar fácilmente recursos independientes para su proyecto en la medida en que lo necesiten. Pero esos recursos también se obtendrán en el intervalo adecuado para tener la seguridad de que no estamos malgastando recursos y de que los recursos antiguos no se interponen en el camino del nuevo desarrollo.
Servicios a nivel de clúster
Además de las herramientas para asignar y gestionar espacios de nombres, también hay servicios útiles a nivel de clúster, y es una buena idea habilitarlos en nuestro clúster de desarrollo. El primero es la agregación de registros a un sistema central de Logging as a Service (administración de registros como servicio) (LaaS). Una de las cosas más fáciles de hacer para que los desarrolladores entiendan el funcionamiento de sus aplicaciones es escribir algo en STDOUT. Aunque podemos acceder a estos registros a través de kubectl logs, estos tienen una longitud limitada y no son localizables particularmente. Si, en cambio, enviamos automáticamente esos registros a un sistema LaaS —como puede ser un servicio en la nube o un clúster de búsqueda elástica—, los desarrolladores pueden buscar fácilmente información relevante en los registros, así como información de registros añadida a través de varios contenedores a su servicio.
Habilitación de flujos de trabajo para desarrolladores
Ahora que hemos tenido éxito en la configuración de un clúster compartido y que podemos registrar nuevos desarrolladores en el clúster, necesitamos conseguir que estos desarrollen sus aplicaciones. Recordemos que uno de los KPI clave que medimos es el tiempo que transcurre desde la incorporación hasta la aplicación inicial que se ejecuta en el clúster. Está claro que, a través de lo que acabamos de describir, podemos autenticar con rapidez a un usuario en un clúster y asignarle un espacio de nombres. Pero, ¿qué hay de empezar con la aplicación? Desafortunadamente, aunque hay algunas técnicas que ayudan en este proceso, por lo general poner en marcha la aplicación inicial requiere más de convención que de automatización. En las siguientes secciones, describimos un enfoque para lograrlo. De ninguna manera se trata de un único enfoque o una única solución. Opcionalmente, podemos aplicar el enfoque tal cual o inspirarnos en ideas para llegar a nuestra propia solución.
Instalación inicial
Uno de los principales retos para implementar una aplicación es la instalación de todas las dependencias. En muchos casos, especialmente en las arquitecturas modernas de microservicios, incluso para empezar la labor de desarrollo en uno de los microservicios se requiere el despliegue de múltiples dependencias, ya sean bases de datos u otros microservicios. Aunque el despliegue de la aplicación en sí es relativamente sencillo, la tarea de identificar e implementar todas las dependencias para crear la aplicación completa es a menudo una tarea frustrante de prueba y error combinada con instrucciones incompletas o desactualizadas.
Para abordar este tema, a menudo es útil introducir una convención para describir e instalar dependencias. Esto se puede ver como el equivalente de algo como npm install, que instala todas las dependencias JavaScript necesarias. Con el tiempo, es probable que exista una herramienta similar a npm que proporcione este servicio para los usuarios de Kubernetes, pero hasta entonces la mejor práctica es confiar en la convención dentro de nuestro equipo de trabajo.
Una de las opciones a adoptar como convención es la creación del script setup.sh dentro del directorio raíz de todos los repositorios del proyecto. Es responsabilidad de este script crear todas las dependencias dentro de un espacio de nombres en particular para asegurar que todas las dependencias de la aplicación se crean correctamente. Por ejemplo, un script de instalación puede parecerse a lo siguiente:
kubectl create my-service/database-stateful-set-yaml kubectl create my-service/middle-tier.yaml kubectl create my-service/configs.yaml
A continuación, podríamos integrar este script con npm añadiendo lo siguiente a nuestro package.json:
{ ... "scripts": { "setup": "./setup.sh", ... } }
Con esta configuración, un desarrollador nuevo tiene simplemente que ejecutar npm run setup y se instalarán las dependencias en el clúster. Obviamente, esta integración particular es específica de Node.js/npm. Con otros lenguajes de programación, tendrá más sentido integrar las herramientas específicas del correspondiente lenguaje. Por ejemplo, en Java podemos integrar en su lugar el archivo Maven pom.xml.
Preparación de la fase de desarrollo activo
Una vez configurado el espacio de trabajo del desarrollador con las dependencias necesarias, la siguiente tarea es permitirle que pueda iterar de forma inmediata su aplicación. La primera condición previa para ello es que exista la posibilidad de crear y transmitir una imagen del contenedor. Supongamos que ya la tenemos configurada. Si no es así, puedes leer cómo hacerlo en otros libros y recursos en línea.
Después de que hayamos hecho built (crear) y push (subir) una imagen del contenedor, la tarea es extenderla al clúster. A diferencia de los despliegues tradicionales, en el caso de las iteraciones de los desarrolladores mantener la disponibilidad no es realmente una preocupación. Por lo tanto, la manera más fácil de desplegar un nuevo código es simplemente eliminar el objeto Deployment asociado con el Deployment anterior y, luego, crear un nuevo despliegue que apunte a la imagen recién creada. También es posible actualizar un Deployment existente, pero esto desencadenará la lógica de despliegue en el recurso Deployment. Aunque es posible configurar un Deployment para poner en marcha el código de forma inmediata, hacerlo así introduce una diferencia entre el entorno de desarrollo y el entorno de producción, lo cual puede ser peligroso o desestabilizador. Imaginemos, por ejemplo, que accidentalmente hacemos push (subir) sobre la configuración de desarrollo de Deployment a producción. De repente, y de forma accidental, desplegaremos nuevas versiones en producción sin las pruebas y retardos adecuados entre las fases de despliegue. Debido a este riesgo, y como hay una alternativa, la mejor práctica es eliminar y volver a crear el Deployment.
Al igual que sucede en la instalación de dependencias, aquí también es una buena práctica crear un script para ejecutar este despliegue. Un ejemplo de script deploy.sh podría ser el siguiente:
kubectl delete -f ./my-service/deployment.yaml perl -pi -e 's/${old_version}/${new_version}/' ./my-service/deployment.yaml kubectl create -f ./my-service/deployment.yaml
Como antes, podemos integrarlo con las herramientas del lenguaje de programación para que, por ejemplo, un desarrollador pueda simplemente ejecutar npm run deploy para desplegar su nuevo código en el clúster.
Preparación de pruebas y depuración
Después de que un usuario haya implementado con éxito la versión de desarrollo de su aplicación, necesita probarla y, si hay problemas, depurar cualquier inconveniente que aparezca con la aplicación. Esto también puede ser un obstáculo a la hora del desarrollo en Kubernetes, porque no siempre está claro cómo interactuamos con el clúster. La línea de comandos de kubectl, como herramienta para conseguirlo, es una verdadera navaja suiza, desde kubectl logs a kubectl exe y kubectl port-forward. Pero aprender a usar las diferentes opciones y conseguir familiarizarse con la herramienta puede requerir una considerable experiencia. Además, debido a que la herramienta se ejecuta en el terminal, a menudo requiere la composición de varias ventanas para examinar simultáneamente el código fuente de la aplicación y la propia aplicación en ejecución.
Para agilizar la experiencia de pruebas y depuración, las herramientas de Kubernetes se integran cada vez más en entornos de desarrollo, como por ejemplo la extensión de código abierto de Visual Studio (VS) Code en Kubernetes. La extensión se instala fácilmente de forma gratuita desde el marketplace de VS Code. Cuando se instala, descubre automáticamente cualquier clúster que ya tengamos en nuestro archivo kubeconfig, y proporciona un panel de navegación en forma de árbol para que podamos ver el contenido de nuestro clúster de un vistazo.
Además de poder ver rápidamente el estado de nuestro clúster, la integración permite al desarrollador utilizar las herramientas disponibles mediante kubectl de una manera intuitiva y reconocible. Desde la vista en forma de árbol, si hacemos clic con el botón derecho del ratón en una cápsula de Kubernetes, podemos utilizar de forma inmediata el enrutamiento de puertos para establecer una conexión de red de la cápsula directamente con la máquina local. Del mismo modo, podemos acceder a los registros de la cápsula o incluso obtener un terminal en el contenedor en ejecución.
La integración de estos comandos con las expectativas de la prototípica interfaz de usuario (por ejemplo, si hacemos clic con el botón derecho del ratón, se muestra un menú contextual), así como la integración de estas experiencias además del código de la propia aplicación, permiten a los desarrolladores con una mínima experiencia en Kubernetes ser productivos con el clúster de desarrollo en un tiempo record.
Por supuesto, esta extensión de VS Code no es la única integración entre Kubernetes y un entorno de desarrollo. Hay otras que podemos instalar dependiendo de la elección del entorno y del estilo de programación (vi, emacs, etc.).
Mejores prácticas en el establecimiento de un entorno de desarrollo
Establecer flujos de trabajo que tengan éxito es clave para ser productivos y estar satisfechos. Seguir estas mejores prácticas ayudará a asegurar que los desarrolladores estén operativos de forma inmediata:
• Podemos pensar en la experiencia del desarrollador en tres fases: incorporación, desarrollo y pruebas. Debemos tener la seguridad de que el entorno de desarrollo que creamos es compatible con estas tres fases.




