Implementación eficaz de imágenes de Docker para escenarios de conectividad intermitente de ancho de banda bajo

Azure DevOps
Functions
Container Registry
Storage
Key Vault
SQL Database
Monitor

A medida que Internet de las cosas (IoT) se vuelve más ubicuo, mover el procesamiento al perímetro sirve como modelo clave para satisfacer necesidades como la conectividad de baja latencia y la conservación del ancho de banda. Esto es especialmente cierto en escenarios que implican movilidad, que normalmente se caracterizan por una conectividad intermitente y con poco ancho de banda. Dado que normalmente aprovisionará dispositivos perimetrales mediante la implementación de imágenes de contenedor de software, las interrupciones en los procesos de implementación pueden producir errores en escenarios móviles. Como resultado, es posible que necesite una funcionalidad de implementación confiable y resistente para situaciones en las que tenga un ancho de banda limitado, intermitente o bajo.

En este artículo se resumen el proceso y los componentes que el equipo de ingeniería de software comercial (CSE) de Microsoft usó para compilar una solución para abordar este escenario. Esta solución habilitó la implementación de contenedores de Docker en dispositivos IoT remotos heterogéneos a través de conexiones a Internet intermitentes de ancho de banda bajo, como a través de satélite, desde la plataforma IoT Edge del cliente. El equipo de CSE lo logró minimizando el tamaño de la implementación en cada dispositivo y habilitando la supervisión confiable de estas implementaciones.

Posibles casos de uso

Esta solución es adecuada para cualquier escenario en el que se usan contenedores de software como parte de la solución y la conectividad es intermitente con un ancho de banda bajo. Entre otros, se incluyen los siguientes ejemplos:

  • Escenarios móviles

  • Actualizaciones de automoción por vía inalámbrica

  • Petróleo y gas y minería

  • Minoristas

  • En cualquier lugar en el que no se garantice una conexión segura

Architecture

En escenarios de ancho de banda alto, Azure IoT Edge extrae imágenes directamente de un registro de Docker accesible desde Internet, ya sea un centro de Docker o uno privado como Azure Container Registry (ACR). Esta es la misma funcionalidad que la ejecución del comando "docker pull <image_name>" desde un equipo local.

Sin embargo, cuando se ve obligado a trabajar con un acceso de red potencialmente intermitente, como una conexión a Internet satélite, el método de extracción de Docker deja de ser confiable. En concreto, el progreso no se almacenará en caché si la conexión a Internet se interrumpe mientras Docker extrae la imagen. Por lo tanto, cuando se reanuda la conexión a Internet, Docker debe empezar a extraer la imagen desde el principio.

En las secciones siguientes se describe un mecanismo de implementación alternativo que compensa los límites impuestos por la conectividad intermitente.

En el diagrama siguiente se muestra la arquitectura de alto nivel de esta solución.

Azure DevOps y arquitectura de soluciones de alto nivel de Azure

Requisitos de diseño

El cliente necesitaba una solución que permitiese la conectividad de nube intermitente de ancho de banda bajo, lo que permitía que las aplicaciones implementadas continuaran ejecutándose localmente y que el personal local usara la funcionalidad sin un retraso de ida y vuelta en la nube y sin conexión. Cuando estaba conectada a la nube, la solución necesitaba usar la conexión en la nube de forma eficaz para priorizar el envío de datos según las reglas de negocio que se definen de forma coherente entre productos.

El equipo de CSE identificó los siguientes requisitos detallados para la aplicación de revisiones binarias de archivos de imagen:

  • Los archivos de imagen se deben transferir a través de una conexión satélite de conectividad intermitente de ancho de banda bajo (1 Mbit/s).

  • Los datos que se transfieren deben minimizarse tanto como sea posible.

  • El cliente prefiere seguir usando una aplicación de terceros para transferir archivos a dispositivos.

  • Las cargas de trabajo de un dispositivo se ejecutarán en IoT Edge mediante imágenes de Docker.

  • El tamaño de la imagen será de entre decenas de MB y varios GB (las imágenes base de Windows son de ~5,5 GB).

  • Los módulos de IoT Edge se escribirán en .NET Core 2.2.

Componentes

Azure

Aplicaciones de terceros

Código Abierto

Ejemplo de caso de uso

Una importante empresa de logística quería mejorar el seguimiento de sus envíos de productos en todo el mundo, incluso a través de áreas geográficas con conectividad de nube intermitente con un ancho de banda bajo. Los envíos de productos tenían una variedad de dispositivos IoT instalados con fines de seguros, seguridad o seguimiento, en función del tipo de bienes que se enviaba. Las funcionalidades de estos dispositivos, como los rastreadores GPS, los sensores de temperatura y las herramientas para capturar otros puntos de datos pertinentes, varían de un dispositivo a otro. Los bienes se podían enviar mediante distintos métodos de transporte (por ejemplo, tierra, aire y mar) y a una amplia variedad de regiones remotas.

La solución ha aumentado considerablemente la confiabilidad y la resistencia del proceso de aprovisionamiento en entornos con conectividad limitada. En la explicación siguiente se describe el proceso de evaluación de opciones, los detalles de la solución que el equipo de CSE desarrolló para este cliente y otros escenarios en los que esta solución podría ser útil.

Escenario de cliente

El cliente tenía problemas para actualizar sus dispositivos a través de la plataforma de IoT Edge desarrollada recientemente. CSE colaboró con el cliente para abordar los siguientes problemas principales:

  • Consumo elevado de ancho de banda al implementar software actualizado en dispositivos.

  • Ninguna implementación automatizada estandarizada en todos los dispositivos y ninguna manera de saber qué dispositivos tenían determinadas actualizaciones.

  • Flexibilidad limitada en la selección de tecnología.

Evaluación de la solución

De las cuatro posibles soluciones evaluadas por CSE, más de una era viable y estaba disponible. CSE determinó que una transferencia de datos de imagen de Docker completa era la solución ideal.

Criterios de evaluación

CSE consideró lo siguiente:

  • Capacidad de la solución para cumplir los requisitos

    • Sí/No
  • Complejidad del dispositivo IoT (cuánta lógica era necesario implementar en los dispositivos)

    • Bajo, medio, alto
  • Complejidad de Azure (cuánta lógica era necesario implementar en Azure)

    • Bajo, medio, alto
  • Eficiencia del ancho de banda (proporción de los datos transferidos al tamaño total de la imagen) para transferir una imagen cuando:

    • No existe ninguna imagen en el dispositivo.

    • Existe una imagen con la misma imagen base en el dispositivo.

    • Una imagen que representa una versión anterior de una aplicación se encuentra en el dispositivo.

    • Una imagen para la aplicación compilada en una versión anterior de la imagen base se encuentra en el dispositivo.

Para las evaluaciones de eficiencia del ancho de banda, CSE usó imágenes para representar los siguientes conjuntos.

Escenario Descripción
Transferir nueva imagen: capa base presente en el dispositivo Transferencia de una nueva imagen mientras ya hay otra imagen en el dispositivo que comparte una imagen base. Representa la implementación de una nueva aplicación por primera vez, mientras que existe otra aplicación en el mismo sistema operativo y marco.
Actualización de la capa de la aplicación Cambiar solo el código de la imagen de una aplicación existente. Representa un cambio típico cuando un usuario confirma una nueva característica.
Actualización de la imagen base Cambiar la versión de la imagen base en la que se basa la aplicación.

A continuación se muestra una breve descripción de las cuatro opciones y sus ventajas e inconvenientes.

Opciones de evaluación que se consideran para la solución

Transferencia de capas de Docker

Una imagen es un montaje del sistema de archivos de unión de varias diferencias del sistema de archivos, que son de solo lectura, con una capa grabable adicional para los cambios realizados mientras se ejecuta el contenedor. Estos distintos sistemas de archivos se conocen como capas, que básicamente son carpetas y archivos. Las capas se apilan para formar la base del sistema de archivos raíz de un contenedor. Puesto que las capas son de solo lectura, varias imágenes pueden compartir la misma capa si la tienen en común.

En esta solución, se reutilizan las capas entre imágenes, por lo que solo es necesario transferir las nuevas capas al dispositivo. Esto sería más habitual en imágenes que comparten la misma capa base (normalmente el sistema operativo, como Ubuntu o Alpine) o en imágenes que son versiones actualizadas de imágenes existentes.

Las ventajas e inconvenientes de este método incluyen:

  • La información sobre las capas existentes en cada dispositivo se debe mantener en el orquestador.

  • Los cambios en la capa base provocan cambios en todos los códigos hash de todas las capas posteriores.

  • Se necesitan códigos hash de capa coherentes para comparar.

  • Podría introducir dependencias en docker save y docker load.

Cliente de Docker modificado

Esta solución se centra en modificar o encapsular el cliente de Docker para que reanude la descarga de capas cuando se interrumpa la conexión a Internet. De forma predeterminada, docker pull reanudará una descarga si la conexión a Internet se restaura en un plazo de ~30 minutos a partir de la interrupción. De lo contrario, el cliente se cierra y se pierde todo el progreso de la descarga.

Aunque se trata de una solución viable, presentaba complicaciones, entre las que se incluyen:

  • Todas las imágenes del dispositivo debían registrarse con el demonio de Docker y extraer las imágenes para maximizar la eficacia del ancho de banda.

  • El proyecto de código abierto de Docker debería modificarse para admitir esta funcionalidad modificada, lo que presenta un riesgo para el proyecto si los responsables de mantener el código abierto rechazaran la propuesta.

  • Los datos se transferirían a través de HTTP en lugar de usar la solución de transferencia rápida de archivos elegida por el cliente, lo que requeriría el desarrollo de lógica de reintento personalizada.

  • Todas las capas debían retransmitirse al cambiar una imagen base.

En Edge

Este enfoque mueve el entorno de compilación de imagen a cada dispositivo. En este escenario, se envían los datos siguientes al dispositivo:

  • Código fuente de la aplicación que se está compilando

  • Copia de todos los paquetes NuGet de los que depende el código

  • Las imágenes base de Docker para el entorno de compilación y el entorno de ejecución de .NET Core

  • Los metadatos sobre el aspecto que debería tener la imagen final

A continuación, un agente de compilación en el dispositivo compila la imagen y la registra con el Administrador de dispositivos de Docker.

Esta solución se rechazó porque:

  • Seguiría siendo necesaria una manera de mover imágenes de Docker de gran tamaño al dispositivo. Las imágenes para compilar la aplicación de .NET son más grandes que las que se usan para ejecutarla.

  • Este método solo funcionará para aplicaciones de .NET Core en las que el equipo esté en posesión del código fuente, y no se lograría ninguna ventaja al usar imágenes de terceros.

  • Crea la necesidad de empaquetar y realizar un seguimiento del movimiento de paquetes NuGet al dispositivo.

Si una imagen no se puede compilar en el dispositivo, el equipo tendrá que depurar de forma remota el entorno de compilación y la imagen creada, lo que implica pasar una gran cantidad de telemetría a través de una conexión a Internet potencialmente limitada.

Transferencia diferencial de imagen completa

Este enfoque trata una imagen de Docker como un único archivo binario. Esto se logra usando el comando docker save para exportar la imagen como un archivo .tar. Al exportar dos imágenes de Docker, podemos calcular la diferencia binaria que, cuando se aplica, transforma una imagen en la otra.

En esta solución, se realiza un seguimiento de las imágenes de Docker existentes en los dispositivos y se crean diferencias binarias para transformar una imagen existente en la nueva imagen que se va a implementar. De este modo, solo se transfiere la diferencia a través de la conexión a Internet de ancho de banda bajo.

Conclusión de la evaluación

En la tabla siguiente se muestran las soluciones anteriores medidas con respecto a los criterios de evaluación de la primera sección.

Solución ¿Cumple los requisitos? Complejidad del dispositivo Complejidad de Azure Transporte Primera imagen Base existente en el dispositivo Actualización de la capa de la aplicación Actualización de la imagen base
Transferencia de capas de Docker Bajo Media FileCatalyst 100% 10,5 % 22,4 % 100%
Cliente de Docker modificado Media Bajo HTTP 100% 10,5 % 22,4 % 100%
En Edge No Alto Media FileCatalyst N/D N/D N/D N/D
Transferencia diferencial de imagen completa Bajo Alto FileCatalyst 100% 3,2 % 0,01 % 16,1 %

En función de los detalles anteriores, CSE seleccionó el método de transferencia diferencial de imagen completa. Esta solución requería cierta lógica personalizada para compilar las revisiones binarias, pero era la opción más eficaz en cuanto a la cantidad de datos que se envían a los dispositivos.

Consideraciones

Detalles de la solución

Los desarrolladores interactuarán con el código fuente de sus módulos en un repositorio de código fuente. La estructura básica del repositorio consta de carpetas que contienen el código de cada módulo, como se muestra a continuación:

\- repository root

    - modulea

    - modulea.csproj

    - module.json

    - Program.cs

    - Dockerfile

\- moduleb

    - moduleb.csproj

    - module.json

    - Program.cs

    - Dockerfile

El número de repositorios de código fuente es una cuestión de preferencia; sin embargo, los dos patrones recomendados, y lo que CSE usó con este cliente, fue lo siguiente:

  • Un repositorio para todos los módulos desarrollados en todas las series de tareas.

  • Un repositorio de código fuente para cada serie de tareas.

Canalizaciones de compilación del repositorio de origen

Hay una canalización de compilación de Azure DevOps para cada módulo. Estas canalizaciones son responsables de:

  • Examen de seguridad del código fuente.

  • Examen de seguridad de la imagen base para compilar la imagen de Docker.

  • Ejecución de pruebas unitarias para el módulo.

  • Compilación del origen en una imagen de Docker. La etiqueta de imagen contiene BUILD_BUILDID para que la imagen siempre se pueda vincular al código fuente que la creó.

  • Inserción de la imagen en una instancia de Azure Container Registry.

  • Creación del archivo delta.

  • Creación de un archivo de firma para la imagen y almacenamiento de dicho archivo en una cuenta de almacenamiento de Azure.

Todas las instancias de la canalización se pueden basar en una única definición de canalización de YAML. Se puede actuar sobre el módulo mediante variables de entorno, con filtros agregados a cada canalización, de modo que solo se desencadenen cuando los cambios se confirmen en una carpeta determinada. Esto evita la compilación de todos los módulos cuando solo se actualiza uno de ellos.

Como se muestra a continuación, para crear y registrar módulos se usa una compilación genérica de Docker que utiliza la canalización de compilación de Azure DevOps.

Azure Container Registry

Azure Container Registry (ACR) se usa para almacenar las imágenes de Docker de cada módulo. Hay dos configuraciones posibles para ACR:

  • Una única instancia de ACR que almacena todas las imágenes

  • Un sistema de dos instancias de ACR: uno almacena las imágenes de desarrollo, pruebas y depuración; el otro solo contiene imágenes comprobadas por pruebas adicionales y marcadas como listas para producción.

Repositorio de manifiestos

El repositorio de manifiestos contiene los manifiestos de implementación para todas las series de tareas. Las plantillas se colocan en carpetas en función de la serie de tareas que representan, como se muestra a continuación. En esta interacción, las dos series de tareas son la infraestructura compartida y la aplicación contenedora (software).

\- repository root

     - Workstream1

         - deployment.template.json

     - Workstream2

         - deployment.template.json

Canalización de imagen a dispositivo del repositorio de manifiestos

Esta canalización es responsable de implementar las imágenes en varios dispositivos de destino, tal como se define en un archivo de manifiesto. Debe desencadenar la canalización manualmente para iniciar una implementación.

La definición de estas canalizaciones especifica que este trabajo de implementación se ejecuta en un contenedor. Azure DevOps admite la ejecución de canalizaciones de compilación en contenedores y admite la entrada de variables de la imagen en la que se basará el contenedor. De este modo, una sola variable puede controlar la imagen en la que se basan todas las canalizaciones.

Esta imagen contiene el código necesario para determinar qué revisiones se compilan, compilar esas revisiones y distribuirlas al lado de Azure de la herramienta de transferencia de archivos.

Para funcionar, la herramienta de distribución de imágenes necesita los siguientes fragmentos de información:

  • Imágenes que se implementarán (proporcionadas por el manifiesto en el repositorio)

  • Dispositivos en los que realizará la implementación (proporcionados por el usuario que desencadena la canalización)

  • Imágenes que ya están en los dispositivos de destino (proporcionadas por una base de datos SQL en Azure)

Las salidas de la canalización son:

  • Agrupaciones de revisiones que se envían al lado de Azure de la herramienta de transferencia de archivos para distribuirse a los dispositivos.

  • Una entrada en la base de datos SQL que marca qué imágenes han empezado a transferirse a cada uno de los dispositivos.

  • Entrada de la base de datos SQL que representa un nuevo conjunto de implementación. Esto incluye información sobre quién ordenó la implementación y una dirección de correo electrónico de contacto por si hay algún problema con la implementación.

Este proceso implica los siguientes pasos:

  1. Determine qué imágenes son necesarias en función del manifiesto de implementación.

  2. Consulte SQL para ver qué imágenes ya están en los dispositivos de destino. Si todas están presentes, la canalización finaliza correctamente.

  3. Determine qué agrupaciones de revisiones deben crearse.

    1. El algoritmo que determina la imagen inicial generará la agrupación de revisiones más pequeña.

    2. Entradas: archivo .tar que contiene la nueva imagen que se está implementando y archivos de firma para las imágenes existentes en los dispositivos.

    3. Salida: clasificación de las imágenes existentes para determinar la revisión más pequeña que se creará.

    4. En esta lista clasificada, se puede tomar una determinación sobre qué revisiones se deben compilar para cada dispositivo. Las revisiones similares se compilan una vez y, a continuación, se copian en todos los dispositivos que las necesitan.

  4. Cree los paquetes de revisiones necesarios según lo determinado en el paso anterior.

  5. Distribuya las revisiones a la cuenta de almacenamiento de la herramienta de transferencia de archivos para la implementación.

  6. Actualice SQL para marcar las nuevas imágenes como "en tránsito" en cada uno de los dispositivos de destino.

  7. Agregue la información del conjunto de implementación a SQL junto con el correo electrónico de contacto de la persona que implementa la imagen.

Archivo original del archivo cambiado para el flujo de trabajo de datos resultante

Canalización de manifiesto a dispositivo del repositorio de manifiestos

Esta canalización envía el nuevo manifiesto de implementación al centro de IoT adecuado para el dispositivo que se está actualizando. Se trata de una canalización desencadenada manualmente. La canalización:

  • Determina qué imágenes son necesarias para la implementación.

  • Consulta SQL para asegurarse de que todas las imágenes necesarias ya están en los dispositivos de destino.

    • Si no lo están, la canalización finaliza aquí con un estado de "error".
  • Inserta el nuevo manifiesto de implementación en el centro de IoT adecuado.

La instancia de IoT Hub donde se inserta la implementación es una variable de entorno configurada al crear la canalización.

Solución de transferencia rápida de archivos

Este cliente ya usaba una solución de transferencia de archivos rápida, denominada FileCatalyst, que prefería seguir usando. Esta solución, también conocida como herramienta de transferencia de archivos "con coherencia final", proporciona la conexión entre Azure y sus dispositivos IoT. El concepto "con coherencia final" significa que una transferencia podría tardar semanas en pasar de A a B, pero acabar completándose sin pérdida de información de archivos. CSE usó una cuenta de Azure Storage en el lado de Azure de la conexión y la máquina virtual (VM) de transferencia de archivos existente del cliente en cada uno de los dispositivos para recibir imágenes. Una vez que los paquetes de revisiones llegan al dispositivo, los procesos existentes los transfieren a una VM Linux. Desde aquí, el archivo se mueve a la VM Linux que ejecuta IoT Hub.

Módulo de reconstrucción de imágenes

El módulo de reconstrucción de imágenes de IoT Edge es responsable de aplicar las revisiones recibidas en los dispositivos. Realiza las siguientes acciones:

  1. Recepción del paquete de revisiones en una carpeta montada en el contenedor.

  2. Descompresión del contenido para leer el archivo de configuración.

  3. Extracción de la imagen base del registro de contenedor local (por hash).

  4. Almacenamiento de la imagen base como un archivo .tar.

  5. Aplicación de la revisión a la imagen base.

  6. Carga del nuevo archivo .tar que contiene la nueva imagen en Docker.

  7. Inserción de la nueva imagen en la instancia de Container Registry local. Se incluyen un nombre descriptivo y una etiqueta en el archivo de configuración.

  8. Envío de un mensaje de operación finalizada correctamente a IoT Hub.

Si se produce un error en el proceso en cualquier momento, este módulo es responsable de enviar un mensaje de error a IoT Hub para que se pueda notificar al usuario que ordenó la implementación. La función de Azure que observa el flujo de IoT Hub procesa estas imágenes y toma medidas en la nube una vez que están listas. Revisión de dispositivos de IoT y centro de operaciones para el flujo de trabajo del reconstructor de imágenes

Registro de contenedor local

Cada dispositivo hospeda su propio registro de contenedor local. En esta solución, CSE usaba el registro de código abierto distribuido por Docker. Este proceso se ejecuta en la VM host, que también se usa como la VM de transferencia de archivos existente.

Azure IoT Hub

IoT Hub se utiliza en otros procesos de implementación. El centro de IoT recibe mensajes de estado de los módulos de reconstrucción de imágenes. También establece los manifiestos de implementación para los distintos dispositivos. Posteriormente, estos manifiestos se utilizan en el resto del flujo DevOps.

Azure Functions

Se usa una función de Azure para supervisar el flujo de mensajes procedente del centro de IoT. Esta es responsable de actuar en los mensajes enviados por los módulos de reconstrucción de imágenes en cada dispositivo.

En el caso de un mensaje de operación finalizada correctamente:

  • La función actualiza el estado de la entrada de SQL de la imagen en el dispositivo de "en tránsito" a "correcto".

  • Si esta es la última imagen que llega a un conjunto de implementación:

    • La función notifica al usuario (mediante notificaciones por correo electrónico configuradas en SQL Server) el éxito de la implementación.

    • La función también desencadena la canalización de manifiesto a dispositivo para empezar a usar las nuevas imágenes.

En el caso de un mensaje de error:

  • La entrada de SQL de la imagen sobre el estado del dispositivo se actualiza de "en tránsito" a "error".

  • Se notifica al usuario (mediante notificaciones por correo electrónico configuradas en SQL Server) del error de transferencia de la imagen.

Flujo de trabajo de mensajes del reconstructor de imágenes de dispositivos IoT y de centro de operaciones

Bases de datos SQL

Una base de datos SQL es responsable de realizar el seguimiento del estado de lo que ocurre en los dispositivos de destino y los servicios de implementación basados en Azure durante y después de los procesos de implementación.

Se creó un paquete NuGet privado para interactuar con la base de datos que se usa tanto en Azure Functions como en Azure Pipelines.

Los datos que se almacenan actualmente son:

  • Qué imágenes hay en qué dispositivo.

  • Qué imágenes se van a incluir en qué dispositivo.

  • Qué imágenes que se están implementando pertenecen a un conjunto y quién ordenó esa implementación.

Se puede usar como origen de datos para un panel que muestre lo siguiente:

  • El estado de una implementación.

  • Las imágenes de un dispositivo determinado.

  • Los dispositivos que tienen una imagen.

  • Los datos de serie temporal sobre transferencias correctas y con errores.

  • Las consultas de implementaciones basadas en el usuario.

El objetivo principal durante esta interacción con el cliente era asegurarse de que el sistema generaba los datos que serían necesarios en el futuro. La solución permite a los paneles futuros consultar IoT Hub para obtener estadísticas sobre la ejecución de dispositivos IoT Edge, lo que proporcionará visibilidad sobre el éxito de la canalización del manifiesto al dispositivo.

Resumen de la implementación

La implementación de esta solución redujo drásticamente el ancho de banda consumido por las actualizaciones de los dispositivos IoT. A continuación se muestra un desglose de las diferencias en la eficacia de la transferencia.

Infografía de la eficiencia de la transferencia

Pasos siguientes

Para más información sobre los procesos y las tecnologías que se usaron para crear esta solución, consulte los siguientes artículos: