La tercera fase es de la de pruebas. Esta fase se intercala con la de desarrollo y se utiliza para validar el código antes de su envío y fusión. El objetivo de esta fase es doble. En primer lugar, el desarrollador debe ser capaz de realizar todas las pruebas de su entorno antes de presentar la PR. En segundo lugar, todas las pruebas deben ejecutarse automáticamente antes de que el código se fusione en el repositorio. Además de estos objetivos, también debemos establecer un KPI para la duración del tiempo que tardan las pruebas en realizarse. A medida que el proyecto se vuelve más complejo, es natural que un mayor número de pruebas necesite más tiempo. Cuando esto sucede, puede ser rentable identificar un conjunto más reducido de pruebas de humo que el desarrollador puede utilizar para la validación inicial antes de enviar la PR. También debemos tener un KPI muy estricto en cuanto a la debilidad de las pruebas. Una prueba problemática es una prueba que ocasionalmente (o no tan ocasionalmente) falla. En cualquier proyecto activo aceptable, un índice de debilidad de más de un fallo por cada mil ejecuciones hará que el desarrollador tenga problemas. Necesitamos asegurar que el entorno del clúster no conduce a pruebas problemáticas. A veces las pruebas problemáticas ocurren debido a problemas en el código, pero también pueden ocurrir debido a interferencias en el entorno de desarrollo (por ejemplo, quedarnos sin recursos o experimentar vecinos ruidosos). Debemos tener la seguridad de que nuestro entorno de desarrollo está libre de tales problemas midiendo la debilidad y actuando rápidamente para arreglarlo.
Creación de un clúster de desarrollo
Cuando alguien empieza a pensar en un desarrollo en Kubernetes, una de las primeras dudas que aparecen es si crear un único gran clúster de desarrollo o tener un clúster de desarrollo por desarrollador. Hay que tener en cuenta que esta opción solo tiene sentido en un entorno en el que la creación de clústeres dinámicos resulta fácil, como es la nube pública. En entornos físicos, es posible que la única opción sea un clúster grande.
Si tenemos opciones, debemos considerar los pros y los contras de cada opción. Si optamos por tener un clúster de desarrollo por usuario, el principal inconveniente de este enfoque es que será más costoso y menos eficiente, y tendremos que gestionar un gran número de clústeres de desarrollo diferentes. Los costes adicionales provienen del hecho de que es probable que cada clúster esté muy infrautilizado. Además, con los desarrolladores trabajando en diferentes clústeres, se hace más difícil rastrear y recolectar recursos que ya no están en uso.
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.
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() }
Читать дальше