Compartir a través de


Aprovechamiento de contenedores y orquestadores

Sugerencia

Este contenido es un extracto del libro electrónico “Architecting Cloud Native .NET Applications for Azure” (Diseño de la arquitectura de aplicaciones .NET nativas en la nube para Azure), disponible en Documentos de .NET o como un PDF descargable y gratuito que se puede leer sin conexión.

Cloud Native .NET apps for Azure eBook cover thumbnail.

Los contenedores y orquestadores están diseñados para resolver problemas comunes a los enfoques de implementación monolíticos.

Retos de las implementaciones monolíticas

Tradicionalmente, la mayoría de las aplicaciones se han implementado como una única unidad. Estas aplicaciones se conocen como monolíticas. Este enfoque general de implementación de aplicaciones como unidades únicas, aun cuando se compongan de varios módulos o ensamblados, se conoce como arquitectura monolítica, como se muestra en la figura 3-1.

Monolithic architecture.

Figura 3-1. Arquitectura monolítica

Aunque tienen la ventaja de ser sencillas, las arquitecturas monolíticas deben afrontar un sinfín de retos:

Implementación

Además, requieren un reinicio de la aplicación, lo que puede afectar temporalmente a la disponibilidad si no se aplican técnicas de tiempo de inactividad cero durante la implementación.

Ampliación

Una aplicación monolítica se hospeda completamente en una única instancia de equipo, lo que a menudo requiere hardware de alta funcionalidad. Si alguna parte de la arquitectura monolítica requiere escalado, hay que implementar otra copia de toda la aplicación en otro equipo. En una arquitectura monolítica, los componentes de la aplicación no pueden escalar individualmente: o todos, o ninguno. El escalado de componentes que no requieren escalado da como resultado un uso de recursos ineficaz y costoso.

Entorno

Normalmente, las aplicaciones monolíticas se implementan en un entorno de hospedaje con un sistema operativo, un tiempo de ejecución y unas dependencias de biblioteca ya preinstalados. Es posible que este entorno no coincida con el que se desarrolló o probó la aplicación. Las incoherencias entre entornos de aplicación son un origen común de problemas de las implementaciones monolíticas.

Acoplamiento

Las aplicaciones monolíticas suelen experimentar un acoplamiento elevado entre sus componentes funcionales. Al no haber límites físicos, los cambios en el sistema suelen derivar en efectos secundarios no deseados y costosos. Incorporar nuevas características y correcciones resulta una tarea complicada, lenta y costosa de implementar. Las actualizaciones requieren pruebas exhaustivas. El acoplamiento también dificulta la refactorización de componentes o el intercambio en implementaciones alternativas. Incluso cuando se construye con una separación estricta de cada aspecto, la erosión arquitectónica se hace cada vez más patente a medida que la base de código monolítica se va deteriorando con un sinfín de "casos especiales".

Bloqueo de plataformas

Una aplicación monolítica se construye con una sola pila tecnológica. Aunque ofrece uniformidad, esta decisión puede convertirse en un obstáculo para la innovación. Las características y componentes nuevos se crearán con la pila actual de la aplicación, incluso cuando las tecnologías más modernas puedan constituir una mejor opción. Un problema a largo plazo es que la pila tecnológica se vuelve obsoleta. Rediseñar una aplicación completa en una plataforma nueva y más moderna es, en el mejor de los casos, costoso y arriesgado.

¿Cuáles son las ventajas de los contenedores y orquestadores?

Ya vimos los contenedores en el capítulo 1, donde destacamos cómo Cloud Native Computing Foundation (CNCF) clasifica la contenedorización como el primer paso de su mapa de seguimiento nativo de nube, una guía dirigida a empresas que están iniciando su periplo nativo de nube. En esta sección desgranaremos las ventajas de los contenedores.

Docker es la plataforma de administración de contenedores más popular. Funciona con contenedores tanto en Linux como en Windows. Los contenedores proporcionan entornos de aplicación independientes pero reproducibles que se ejecutan de la misma manera en cualquier sistema. Esto hace que sean perfectos para desarrollar y hospedar servicios nativos de nube. Los contenedores están completamente aislados entre sí. Dos contenedores en el mismo hardware de host pueden tener diferentes versiones de software sin que ello provoque conflictos.

Los contenedores se definen mediante archivos de texto sencillos que se convierten en artefactos del proyecto y que se controlan en el código fuente. Mientras que los servidores y las máquinas virtuales requieren un esfuerzo manual para actualizarlos por completo, los contenedores se controlan fácilmente con versiones. Las aplicaciones creadas para ejecutarse en contenedores se pueden desarrollar, probar e implementar mediante herramientas automatizadas como parte de una canalización de compilación.

Los contenedores son inmutables. Una vez definido un contenedor, se puede volver a crear y a ejecutar exactamente de la misma manera. Esta inmutabilidad se presta al diseño basado en componentes. Si algunas partes de una aplicación evolucionan de forma diferente a otras, ¿por qué volver a implementar toda la aplicación cuando solo hace falta implementar las partes que cambian con más frecuencia? Las diferentes características y cuestiones transversales de una aplicación se pueden dividir en unidades independientes. En la figura 3-2 se muestra cómo una aplicación monolítica puede sacar partido de los contenedores y los microservicios al delegar determinadas características o funcionalidades. La funcionalidad restante de la propia aplicación también se ha contenedorizado.

Breaking up a monolithic app to use microservices in the back end.

Figura 3-2. Descomposición de una aplicación monolítica para usar microservicios

Cada servicio nativo de nube se crea e implementa en un contenedor aparte. Cada uno se puede actualizar según sea necesario. Se pueden hospedar servicios individuales en nodos con los recursos adecuados para cada servicio. El entorno en el que se ejecuta cada servicio es inmutable, se puede compartir entre los entornos de desarrollo, pruebas y producción y se es posible controlarlo con versiones de forma sencilla. El acoplamiento entre diferentes áreas de la aplicación se produce explícitamente como llamadas o mensajes entre servicios, y no como dependencias en tiempo de compilación dentro de la aplicación monolítica. También se puede elegir la tecnología que mejor se adapte a una funcionalidad determinada sin necesidad de realizar cambios en el resto de la aplicación.

Los servicios contenerizados requieren una administración automatizada. Un conjunto grande de contenedores implementados de manera independiente no podría administrarse manualmente. Por ejemplo, planteémonos las siguientes preguntas:

  • ¿Cómo se aprovisionarán las instancias de contenedor en un clúster con muchas máquinas?
  • Una vez implementados, ¿cómo se detectarán y comunicarán los contenedores entre sí?
  • ¿Cómo se puede aumentar o reducir el número de contenedores a demanda?
  • ¿Cómo se supervisa el estado de cada contenedor?
  • ¿Cómo se protege un contenedor de errores de hardware y software?
  • ¿Cómo se actualizan los contenedores de una aplicación en vivo sin que haya un tiempo de inactividad?

Los orquestadores de contenedores abordan y automatizan estas y otras cuestiones.

En el ecosistema nativo de nube, Kubernetes se ha convertido en el orquestador de contenedores de facto. Se trata de una plataforma de código abierto administrada por Cloud Native Computing Foundation (CNCF). Kubernetes automatiza la implementación, el escalado y las cuestiones operativas de las cargas de trabajo contenedorizadas en un clúster de maquinas completo. Pero es sabido que instalar y administrar Kubernetes es bastante complejo.

Un enfoque mucho mejor es usar Kubernetes como un servicio administrado de un proveedor de nube. La nube de Azure incluye una plataforma de Kubernetes totalmente administrada llamada Azure Kubernetes Service (AKS). AKS reduce la complejidad y la sobrecarga operativa de la administración de Kubernetes. El usuario utiliza Kubernetes como servicio en la nube, mientras que Microsoft se encarga de administrarlo y dar el correspondiente soporte. AKS también se integra estrechamente con otros servicios y herramientas de desarrollo de Azure.

AKS es una tecnología basada en clústeres. Un grupo de máquinas virtuales federadas, o nodos, se implementa en la nube de Azure. Juntos forman un entorno de alta disponibilidad, o un clúster. El clúster parece una única entidad sin complicaciones en la aplicación nativa de nube, cuando en realidad AKS implementa los servicios contenedorizados en estos nodos siguiendo de una estrategia perfectamente trazada que distribuye la carga uniformemente.

¿Cuáles son las ventajas del escalado?

Los servicios basados en contenedores pueden aprovechar las ventajas de escalado proporcionadas por herramientas de orquestación como Kubernetes. Por diseño, los contenedores solo tienen información sobre sí mismos. Cuando haya varios contenedores que necesiten funcionar juntos, deberán organizarse en un nivel superior. Organizar un gran número de contenedores y sus dependencias compartidas, como la configuración de una red... ¡ahí es donde las herramientas de orquestación entran en juego para salvarnos los muebles! Kubernetes crea una capa de abstracción en grupos de contenedores y los organiza en pods. Los pods se ejecutan en máquinas de trabajo denominadas nodos. Esta estructura organizada se conoce como clúster. En la figura 3-3 se muestran los distintos componentes de un clúster de Kubernetes.

Kubernetes cluster components.Figura 3-3. Componentes de un clúster de Kubernetes

El escalado de cargas de trabajo contenedorizadas es una característica clave de los orquestadores de contenedores. AKS admite el escalado automático en dos dimensiones: instancias de contenedor y nodos de proceso. Ambas proporcionan a AKS la capacidad de responder de forma rápida y eficaz a los picos de demanda e incorporar más recursos. Analizaremos el escalado en AKS más adelante en este capítulo.

Declarativo frente a imperativo

Kubernetes admite la configuración declarativa e imperativa. El enfoque imperativo consiste en ejecutar varios comandos que indican a Kubernetes qué hacer a cada paso del camino. Ejecuta esta imagen. Elimina este pod. Expón este puerto. Con el enfoque declarativo, se crea un archivo de configuración, denominado manifiesto, para describir lo que se quiere hacer en lugar de lo que se debe hacer. Kubernetes lee el manifiesto y transforma el estado final deseado en el estado final de facto.

Los comandos imperativos son excelentes para el aprendizaje y la experimentación interactiva, pero lo más conveniente es crear archivos de manifiesto de Kubernetes de forma declarativa para adoptar el uso de una infraestructura como enfoque de código, lo que dará lugar a implementaciones confiables y repetibles. El archivo de manifiesto se convierte en un artefacto del proyecto y se usa en la canalización de CI/CD para automatizar las implementaciones de Kubernetes.

Si ya ha configurado el clúster con comandos imperativos, puede exportar un manifiesto declarativo mediante kubectl get svc SERVICENAME -o yaml > service.yaml. Este comando genera un manifiesto similar al que se muestra aquí:

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: "2019-09-13T13:58:47Z"
  labels:
    component: apiserver
    provider: kubernetes
  name: kubernetes
  namespace: default
  resourceVersion: "153"
  selfLink: /api/v1/namespaces/default/services/kubernetes
  uid: 9b1fac62-d62e-11e9-8968-00155d38010d
spec:
  clusterIP: 10.96.0.1
  ports:
  - name: https
    port: 443
    protocol: TCP
    targetPort: 6443
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}

Cuando se usa la configuración declarativa, se puede obtener una vista previa de los cambios que se realizarán antes de confirmarlos usando kubectl diff -f FOLDERNAME en la carpeta donde se encuentran los archivos de configuración. Una vez que no tenga duda de que quiere aplicar los cambios, ejecute kubectl apply -f FOLDERNAME. Agregue -R para procesar una jerarquía de carpetas de forma recursiva.

También puede usar la configuración declarativa con otras características de Kubernetes, siendo una de ellas las implementaciones. Las implementaciones declarativas ayudan a administrar las versiones, las actualizaciones y el escalado. Indican al controlador de implementación de Kubernetes cómo implementar nuevos cambios, escalar horizontalmente o revertir a una revisión anterior. Si un clúster es inestable, una implementación declarativa devolverá automáticamente el clúster a un estado deseado. Por ejemplo, si un nodo debe bloquearse, el mecanismo de implementación volverá a implementar un reemplazo para lograr el estado deseado.

El uso de la configuración declarativa permite representar la infraestructura como un código que se puede comprobar y controlar con versiones junto con el código de la aplicación. Proporciona un mejor control de los cambios y una mejor compatibilidad con la implementación continua mediante una canalización de tipo "crear e implementar".

¿Qué escenarios son ideales para usar contenedores y orquestadores?

Los siguientes escenarios son ideales para usar contenedores y orquestadores.

Aplicaciones que requieren un tiempo de actividad y una escalabilidad elevados

Las aplicaciones individuales que tienen unos requisitos de tiempo de actividad y escalabilidad elevados son el candidato ideal para las arquitecturas nativas de nube que usan microservicios, contenedores y orquestadores. Se pueden desarrollar en contenedores, probar en entornos con control de versiones e implementar en producción sin tiempos de inactividad. El uso de clústeres de Kubernetes garantiza que estas aplicaciones también se pueden escalar a demanda y a recuperarse automáticamente de los errores de nodo.

Gran cantidad de aplicaciones

Las organizaciones que implementan y mantienen una gran cantidad de aplicaciones se benefician del uso de contenedores y orquestadores. El esfuerzo inicial de configurar entornos contenedorizados y clústeres de Kubernetes es fundamentalmente un coste fijo. La implementación, mantenimiento y actualización de aplicaciones individuales tiene un coste que varía con el número de aplicaciones. Más allá de algunas aplicaciones, la complejidad de mantener aplicaciones personalizadas supera manualmente el coste de implementar una solución mediante contenedores y orquestadores.

¿Cuándo debe evitarse el uso de contenedores y orquestadores?

Si no puede crear la aplicación siguiendo los principios de las aplicaciones Twelve-Factor, probablemente lo mejor sea abstenerse de usar contenedores y orquestadores. En estos casos, decántese por una plataforma de hospedaje basada en máquinas virtuales o quizá algún sistema híbrido. Así, siempre podrá poner en marcha ciertos aspectos funcionales en contenedores independientes o incluso en funciones sin servidor.

Recursos de desarrollo

En esta sección se incluye una breve lista de recursos de desarrollo que pueden ayudarle a empezar a usar contenedores y orquestadores en su próxima aplicación. Si busca instrucciones sobre cómo diseñar una aplicación de arquitectura de microservicios nativos de nube, lea el complemento de este libro, Microservicios de .NET: arquitectura para aplicaciones .NET en contenedor.

Desarrollo de Kubernetes local

Las implementaciones de Kubernetes aportan un gran valor a los entornos de producción, pero también se pueden ejecutar localmente en una máquina de desarrollo. Aunque podemos trabajar con microservicios individuales de forma independiente, puede haber ocasiones en las que tengamos que ejecutar todo el sistema localmente, como si lo hiciéramos al implementarlo en producción. Hay varias herramientas que nos pueden ser de ayuda: Minikube y Docker Desktop. Visual Studio también proporciona herramientas para el desarrollo de Docker.

Minikube

¿Qué es Minikube? En el proyecto Minikube se señala que "Minikube implementa un clúster de Kubernetes local en macOS, Linux y Windows". Sus objetivos principales son "ser la mejor herramienta para el desarrollo de aplicaciones locales de Kubernetes y admitir todas las características de Kubernetes que encajen". La instalación de Minikube es independiente de Docker, aunque Minikube admite unos hipervisores distintos de los que admite Docker Desktop. Las siguientes características de Kubernetes son compatibles actualmente con Minikube:

  • DNS
  • NodePorts
  • ConfigMaps y secretos
  • Paneles
  • Entornos de ejecución de contenedor: Docker, rkt, CRI-O y containerd
  • Activación de la interfaz de red de contenedor (CNI)
  • Entrada

Después de instalar Minikube, se puede empezar a usar rápidamente ejecutando el comando minikube start, que descarga una imagen e inicia el clúster de Kubernetes local. Una vez iniciado el clúster, se puede interactuar con él mediante los comandos kubectl estándar de Kubernetes.

Docker Desktop

También se puede trabajar con Kubernetes directamente desde Docker Desktop en Windows. Es la única opción si se usan contenedores de Windows, pero también es una excelente opción con contenedores que no son de Windows. En la figura 3-4 se muestra cómo habilitar la compatibilidad local con Kubernetes al ejecutar Docker Desktop.

Configuring Kubernetes in Docker Desktop

Figura 3-4. Configuración de Kubernetes en Docker Desktop

Docker Desktop es la herramienta más popular para configurar y ejecutar aplicaciones contenedorizadas localmente. Cuando se trabaja con Docker Desktop, se puede desarrollar localmente con el mismo conjunto exacto de imágenes de contenedor de Docker que se implementará en producción. Docker Desktop está diseñado para "compilar, probar y enviar" aplicaciones contenedorizadas localmente. Se admiten contenedores tanto de Linux como de Windows. Una vez que las imágenes estén insertadas en un registro de imágenes, como Azure Container Registry o Docker Hub, AKS puede extraerlas e implementarlas en producción.

Herramientas Docker de Visual Studio

Visual Studio admite el desarrollo Docker de aplicaciones basadas en web. Al crear una nueva aplicación de ASP.NET Core, existe la posibilidad de configurarla con compatibilidad con Docker, como se muestra en la figura 3-5.

Visual Studio Enable Docker Support

Figura 3-5. Habilitar compatibilidad con Docker en Visual Studio

Cuando se selecciona esta opción, el proyecto se crea en su raíz con un Dockerfile, que se puede usar para compilar y hospedar la aplicación en un contenedor Docker. En la figura 3-6 se muestra un Dockerfile de ejemplo.

FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY ["eShopWeb/eShopWeb.csproj", "eShopWeb/"]
RUN dotnet restore "eShopWeb/eShopWeb.csproj"
COPY . .
WORKDIR "/src/eShopWeb"
RUN dotnet build "eShopWeb.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "eShopWeb.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "eShopWeb.dll"]

Figura 3-6. Dockerfile generado en Visual Studio

Una vez agregada la compatibilidad, puede ejecutar la aplicación en un contenedor Docker en Visual Studio. En la figura 3-7 se muestran las distintas opciones de ejecución disponibles en un nuevo proyecto de ASP.NET Core creado con compatibilidad con Docker agregada.

Visual Studio Docker Run Options

Figura 3-7. Opciones de ejecución de Docker en Visual Studio

Además, en cualquier momento se puede agregar compatibilidad con Docker a una aplicación de ASP.NET Core existente. En el Explorador de soluciones de Visual Studio, haga clic con el botón derecho en el proyecto y seleccione Agregar>Compatibilidad con Docker, como se muestra en la figura 3-8.

Visual Studio Add Docker Support

Figura 3-8. Adición de compatibilidad con Docker en Visual Studio

Herramientas Docker de Visual Studio Code

Hay muchas extensiones disponibles para Visual Studio Code que admiten el desarrollo de Docker.

Microsoft proporciona la extensión Docker para Visual Studio Code. Esta extensión simplifica el proceso de adición de compatibilidad con contenedores a las aplicaciones. Aplica scaffolding a los archivos necesarios, compila imágenes de Docker y permite depurar la aplicación dentro de un contenedor. La extensión incluye un explorador visual que facilita la realización de acciones en contenedores e imágenes, como iniciar, detener, inspeccionar, quitar y mucho más. La extensión también admite Docker Compose, le permite administrar varios contenedores en ejecución como una sola unidad.