La primera vez que oí hablar de contenedores fue en 2018, cuando un compañero de trabajo nos mostró una presentación sobre qué eran y cómo funcionaban. Lo único que pensé fue "guay, eso no son más que máquinas virtuales (VM) sobrecomplicadas. En fin..." Es vergonzoso lo ingenuo y equivocado que estaba.
Yo aún era nuevo en el desarrollo de software y este tema estaba fuera de lo que me interesaba, es decir, la corrección de errores y algunas características menores del producto en las que estaba trabajando en ese momento. Quién iba a decir que 4 años más tarde oiríamos susurros en el viento acerca de los contenedores, y que yo lideraría la carga para que eso sucediera.
Pero Joe, ¿por qué gastar todo este tiempo y esfuerzo en contenerizar un monolito que ya es estable y fiable?
Bien, querido lector, puede que nuestras razones no sean las mismas que las tuyas, pero decidimos hacerlo por modernización, escalabilidad y reducción de costes de infraestructura. Nuestra aplicación que se ejecutaba en .NET Framework y dependía de Active Directory (AD) no estaba a la altura de las necesidades de los clientes, que querían OAuth, soporte multiplataforma y más escalabilidad. Las aplicaciones web en contenedores pueden escalar según sea necesario, y los costes de infraestructura de añadir más contenedores son una fracción de lo que cuesta configurar más máquinas virtuales de Windows y mantener todo un entorno AD.

Teníamos un reto ante nosotros: Nuestra solución estrella de automatización del ciclo de vida de los certificados, Keyfactor Command , era un monolito de .NET Framework en aquel momento, que dependía en gran medida de AD e Internet Information Services (IIS). También utilizaba un instalador de Microsoft Software (MSI) y una interfaz gráfica de usuario exclusiva de Windows para la instalación/configuración. Tomar todo eso, envolverlo en un contenedor de Windows y darlo por terminado no era una opción realista.
Aunque los contenedores de Windows existen, nunca han sido un estándar de la industria, y no queríamos incrustarnos aún más en el ecosistema de Windows. Los contenedores de Windows habrían funcionado como un paso temporal en el camino, pero como un compañero de trabajo dijo una vez tan sabiamente: "No hay nada más permanente que una solución temporal". Era hora de tomarse en serio la multiplataforma.
Nos fijamos unos objetivos ambiciosos:
- La plataforma Keyfactor Command puede arrancarse y ejecutarse en un entorno de contenedores Kubernetes.
- Los componentes relacionados (las "abejas obreras"), incluidos Orchestrator y CA Gateways, también se incluirían en contenedores, por lo que todo el conjunto de productos puede ejecutarse en el mismo entorno.
- Mantener la compatibilidad con versiones anteriores de todas las funciones del producto.
- La autenticación AD sigue funcionando para los clientes que la utilizan actualmente
- Las instalaciones basadas en Windows siguen funcionando sin interrupciones
Estupendo. Pero, ¿cómo lo conseguimos? A medida que emprendíamos nuestro viaje hacia la contenedorización, nuestra lista de tareas pendientes seguía creciendo: .....
En primer lugar, la aplicación debe poder ejecutarse en varias plataformas, por lo que necesitamos pasar de .NET Framework a .NET 8. Parte de esa migración implicaría dejar de depender de IIS y AD para la autenticación. Parte de esa migración implicaba dejar de depender de IIS y AD para la autenticación, por lo que había que añadir a la lista la compatibilidad con OAuth y OIDC. También tuvimos que implementar la conversión del tiempo de ejecución de .NET y OAuth en todos los componentes del producto, no sólo en la plataforma en sí. ¿He mencionado que hay que sustituir todo lo que depende de Windows por algo completamente distinto? En resumen, esto es lo más parecido a una reescritura que se puede conseguir sin hacer una reescritura. No obstante, le dimos el nombre en clave de "Songbird" y nos pusimos manos a la obra.
Requisitos previos
En el momento en que empezamos Songbird, ya estábamos en camino con otro esfuerzo que estábamos llamando "AD Independence" internamente. Esto cubría romper nuestro estrecho acoplamiento con AD y eventualmente añadir soporte OAuth al producto como objetivo final. Genial, eso está en marcha, y por casualidad la conversión a .NET terminó siendo un requisito para completar la Independencia de AD. Así que, lo primero es lo primero, es hora de abordar la conversión .NET.
Para quienes no estén tan familiarizados con .NET, la conversión de Framework a .NET es una montaña rusa de trivialidades y reescrituras completas. Mientras que el 95% de su código base sobrevivirá a la transición 1 por 1, el código de inicio de la aplicación en particular necesita una reescritura completa. Luego deberás hacerlo para cada aplicación. La plataforma Command es una combinación de 7 aplicaciones que funcionan todas juntas: Cinco son sitios web ASP.NET, una es un servicio Windows y otra es la aplicación de configuración de la consola.
La conversión de los sitios web ASP.NET fue sin duda la parte más complicada, ya que todo el proceso ASP.NET, desde la recepción de una solicitud hasta el punto en que recibe nuestra capa de controladores, es totalmente diferente. No solo hubo que rehacer el código de inicio de la aplicación, sino también partes enteras de las aplicaciones web básicas, como la autorización, el registro de solicitudes y la gestión de errores, para adaptarlas mejor al nuevo molde de ASP.NET.
Otro obstáculo para la conversión a .NET es nuestro Asistente de Configuración. Hoy en día esta es una aplicación basada en WPF, que no es multiplataforma, incluso con un tiempo de ejecución .NET. Tendríamos que convertir a otro marco de interfaz de usuario, como MAUI o Avalonia, pero ¿qué obtendríamos del trato? Ser capaz de instalar Command en un equipo Linux y el uso de algo como NGINX como un reemplazo de IIS no estaba en la lista de objetivos finales, así que terminamos desechando esta idea.
En su lugar, trasladamos la mayor parte de nuestra configuración central a una biblioteca común multiobjetivo y creamos una nueva aplicación de consola que imita las capacidades de configuración de un asistente. Esta nueva aplicación, la herramienta de actualización de la base de datos, sería el bootstrapper para la base de datos en el caso de despliegue de contenedores. De todos modos, no se puede utilizar una GUI desde un contenedor :).
Ahora que todo se ha convertido a .NET, pasemos al problema de la autenticación (AuthN) y la autorización (AuthZ). Anteriormente, nos basamos en IIS para realizar la autenticación a través de Basic o Kerberos auth, y luego ASP.NET proporciona una identidad de usuario a utilizar. Este tema merece su propio post para profundizar en él, pero en esencia tuvimos que utilizar el middleware ASP.NET para permitir que la autenticación basada en AD de IIS siguiera funcionando y añadir código de autenticación OAuth y flujos de credenciales de cliente. Además, en lugar de asignar roles y permisos de aplicación a los usuarios y grupos de AD, el modelo de seguridad tuvo que ser reelaborado para ser basado en reclamaciones.
Es hora de hacer recipientes
Nos ha llevado mucho tiempo, pero por fin hemos llegado y estamos listos para empezar a encajar las piezas. Para no complicar en exceso nuestras construcciones, optamos por hacer nuestros dockerfiles lo más delgado posible, y copiar los binarios ya construidos en las imágenes, en lugar de construir la totalidad del código fuente de nuevo en las imágenes de contenedor.
Una vez que teníamos las imágenes, había que ejecutarlas en algún sitio. Al principio del desarrollo, se trataba de instancias locales de Docker Compose con un montón de configuraciones personalizadas, junto con un proxy inverso. Rápidamente nos dimos cuenta de que esto no iba a escalar o ser muy soportable en producción, ya que simplemente había demasiados ajustes de configuración y contenedores que necesitaban alinearse para que todo funcionara como se esperaba. También habíamos fijado previamente el objetivo de que la aplicación se ejecutara en Kubernetes (K8s).
Decidimos tomar otro enfoque y subir una capa en el ecosistema de contenedores para orquestar con Kubernetes (K8s) y Helm. Usando Helm, podríamos definir una Carta con un conjunto común de valores de configuración que hace todo el trabajo de conectar todo correctamente para usted. Consideramos brevemente escribir un operador K8s, que efectivamente hace lo mismo, pero decidimos no hacerlo ya que esto requería más código que necesitaba ser mantenido, mientras que Helm es plantillas de configuración planas que reflejan fielmente lo que se despliega en K8s.
Hemos dedicado mucho tiempo a hacer que este gráfico sea lo más fácil de usar posible. Dado que uno de nuestros requisitos era mantener la compatibilidad con versiones anteriores, Keyfactor Command todavía tenía que poder instalarse a través de nuestro MSI en Windows, y queríamos que la curva de aprendizaje fuera baja para utilizar este método de instalación alternativo. A pesar de que el gráfico de Helm probablemente se consumirá en la producción por tuberías automatizadas, era importante para nosotros que los usuarios internos que no eran muy conocedores de contenedores pudieran ser apuntados a un clúster, se les diera un archivo de valores que necesitan para llenar los bits importantes, y luego ejecutar una `helm install`, y 2 minutos más tarde pueden iniciar sesión en su instancia Command y empezar a usarlo. Al mismo tiempo, aunque el caso por defecto en el gráfico es lo más simple posible, hemos proporcionado suficiente configuración para permitir que la aplicación se adapte a cualquier necesidad.

Nuestro último reto tenía que ver con la limitación de que nuestra base de datos debe crearse y configurarse antes de que las propias aplicaciones puedan iniciarse correctamente. En los antiguos entornos Windows, la configuración de la base de datos se realiza como parte del proceso de instalación, junto con los servicios web asociados y la configuración de IIS. K8s es muy declarativo, le dices que arranque algunas aplicaciones y lo hace. ¿Ves el problema? Si ponemos en marcha los servicios web y un trabajo para configurar la base de datos al mismo tiempo, algo va a fallar. Dado que estamos tratando de hacer esto tan fácil de usar como sea posible, nuestra única opción era hornear esto en el Gráfico que es más difícil de lo que parece.
Helm, como K8s, también intenta funcionar de la forma más declarativa posible. Pero tenemos algunas opciones para lograr este flujo de arranque:
- Despliegue todo junto, utilice contenedores Init para sondear la base de datos y espere a que se complete un trabajo de configuración, luego inicie los servicios web.
- Utiliza los hooks de Helm para ponderar los despliegues de forma que el trabajo de configuración se ejecute antes de que existan los webservices, y una vez completado, despliega los webservices.
Dedicamos mucho tiempo a considerar estas opciones, ya que cada una tenía sus pros y sus contras y no había una opción claramente mejor. La opción dos acabó ganando debido a la preocupación por las condiciones de carrera que podrían darse con la opción uno. Los ganchos en sí tienen algunas desventajas, a saber, si hay un fallo en la instalación Helm no limpiará los recursos del gancho y usted debe hacerlo a mano. No es realmente un problema en producción, pero durante las pruebas de desarrollo este fue un punto de dolor constante que casi nos ha llevado de vuelta a la opción de contenedores init muchas veces.
El producto final
Y ya está. Ahora que ya teníamos el libro de jugadas, sólo teníamos que aplicar un concepto similar a los demás componentes. Di hola a la nube moderna y adiós a los viejos servidores Windows. Con esto completado, ahora tenemos Keyfactor Command versión 24.4 con containerización disponible para los clientes y nuestros nobles objetivos establecidos anteriormente finalmente completados.
Al final, pudimos lograr nuestro objetivo de crear un sólido despliegue en contenedores de Command utilizando Kubernetes y Helm, manteniendo al mismo tiempo una instalación basada en Windows MSI. Aunque fue mucho trabajo que implicó numerosos pasos complejos, el resultado cumple con éxito todos nuestros requisitos. Aprendimos mucho por el camino y ahora vemos un océano de posibilidades para hacer que nuestra aplicación sea más nativa de la nube y aún mejor para los clientes".
Este ha sido nuestro primer blog de desarrollo en Keyfactor y, si te ha gustado, nos encantaría conocer tu opinión para poder mostrarte los entresijos de otros proyectos en el futuro. Conéctate conmigo en LinkedIn y envíame un mensaje si te ha gustado lo que has leído y permanece atento a blogs de desarrollo similares en el futuro.