Ahora tengo un proyecto de tamaño mediano que se está acercando al final de la fase de "prototipos descuidados con cafeína para demostraciones de clientes" y la transición a la fase de "pensar en el futuro". El proyecto consiste en dispositivos basados en Linux con software y firmware, y un servidor web administrativo central. Actualmente existen 10 prototipos, se espera que la producción sea del orden de 1000 bajos.
Sin estar bien versado en el arte de las actualizaciones automáticas, y teniendo poco tiempo, rápidamente desarrollé mi propia estrategia de implementación / actualización automática de software y, francamente, es una mierda. Actualmente consta de lo siguiente:
- Un repositorio git alojado (GitLab) con una rama de lanzamiento de producción (tenga en cuenta que la fuente del servidor web también está en este mismo repositorio, así como algunas otras cosas).
- Un botón "implementar actualización" en la interfaz web que:
- Extrae la última versión de la rama de lanzamiento de producción en un área de repositorio local y también la copia en un área de preparación de paquetes temporal.
- Ejecuta un script de desinfección (almacenado en el repositorio) en el área de preparación para eliminar archivos fuente no relacionados (por ejemplo, fuente del servidor, fuente de firmware, etc.) y archivos .git.
- Escribe el hash git actual en un archivo en el paquete de actualización (el propósito quedará claro a continuación).
- Si todo salió bien, lo descomprime y lo prepara para servir sobrescribiendo el paquete comprimido anterior con un archivo del mismo nombre, luego elimina el área de preparación.
- Tenga en cuenta que ahora hay dos copias del software del dispositivo actual en el servidor, que se espera que estén sincronizadas: un repositorio git local completo en la última rama de producción y un paquete comprimido listo para usar que ahora se supone que representa eso misma versión
- El software en el dispositivo es autónomo en un directorio llamado
/opt/example/current
, que es un enlace simbólico a la versión actual del software. - Una función de actualización automática en el dispositivo que, en el arranque:
- Comprueba la presencia de un
do_not_update
archivo y no realiza ninguna otra acción si existe (para dispositivos de desarrollo, ver más abajo). - Lee el hash de confirmación actual del archivo de texto mencionado anteriormente.
- Realiza una solicitud HTTP al servidor con ese hash como parámetro de consulta. El servidor responderá con un 304 (el hash es la versión actual) o servirá el paquete de actualización comprimido.
- Instala el paquete de actualización, si lo recibió, en
/opt/example
:- Extrayendo la información actualizada del software una carpeta llamada
stage
. - Ejecutar un script posterior a la instalación desde el paquete de actualización que hace cosas como hacer los cambios locales necesarios para esa actualización, etc.
- Copiar la carpeta raíz del software actual en
previous
(elimina los existentesprevious
primero, si hay uno). - Copiando la
stage
carpeta alatest
(elimina la existentelatest
primero, si hay una). - Asegurar el
current
enlace simbólico al que apuntarlatest
. - Reiniciar el dispositivo (las actualizaciones de firmware, si están presentes, se aplican al reiniciar).
- Extrayendo la información actualizada del software una carpeta llamada
- Comprueba la presencia de un
También está el problema de la implementación inicial en dispositivos recién construidos. Los dispositivos están actualmente basados en tarjetas SD (tiene su propio conjunto de problemas, fuera de alcance aquí), por lo que este proceso consiste en:
- Existe una imagen SD que tiene alguna versión anterior estable del software.
- Se crea una tarjeta SD a partir de esta imagen.
- En el primer arranque, se lleva a cabo una primera inicialización específica del dispositivo (basada en el número de serie) y luego el actualizador automático toma e instala la última versión de producción del software como de costumbre.
Además, necesitaba soporte para dispositivos de desarrollo. Para dispositivos de desarrollo:
- Se mantiene un repositorio git local completo en el dispositivo.
- El
current
enlace simbólico apunta al directorio de desarrollo. - Existe un
do_not_update
archivo local que impide que el actualizador automático elimine el código de desarrollo con una actualización de producción.
Ahora, el proceso de despliegue estaba teóricamente destinado a ser:
- Una vez que el código esté listo para la implementación, empújelo a la rama de lanzamiento.
- Presione el botón "implementar actualización" en el servidor.
- La actualización ahora está activa y los dispositivos se actualizarán automáticamente la próxima vez que verifiquen.
Sin embargo, hay muchos problemas en la práctica:
- El código del servidor web está en el mismo repositorio que el código del dispositivo, y el servidor tiene un repositorio git local que ejecuto. El último código del servidor web no está en la misma rama que el último código del dispositivo. La estructura del directorio es problemática. Cuando el botón "implementar actualización" extrae la última versión de la rama de producción, la extrae en un subdirectorio del código del servidor. Esto significa que cuando despliegue en un servidor desde cero, tengo que "sembrar" manualmente este subdirectorio agarrando la rama de producción del dispositivo, porque, probablemente, por error de usuario git de mi parte, si no lo hago, la implementación intenta extraiga el código del dispositivo de la rama del servidor web del directorio principal . Creo que esto se puede resolver haciendo que el área de preparación no sea un subdirectorio del repositorio git local del servidor.
- El servidor web actualmente no mantiene el git hash del software del dispositivo de forma persistente. Al iniciar el servidor, realiza un
git rev-parse HEAD
repositorio de software en su dispositivo local para recuperar el hash actual. Por razones que no puedo entender, esto también está causando un montón de errores lógicos que no describiré aquí, basta con decir que a veces reiniciar el servidor arruina las cosas, especialmente si el servidor es nuevo y no hay producción el repositorio de sucursales ha sido retirado todavía. Me encantaría compartir la fuente de esa lógica si así lo solicita, pero esta publicación se está alargando. - Si el script de desinfección (del lado del servidor) falla por alguna razón, entonces el servidor se queda con un repositorio actualizado pero un paquete de actualización no sincronizado / faltante, por
git rev-parse HEAD
lo que devolverá un hash que no coincide con lo que realmente está siendo servido a los dispositivos, y los problemas aquí deben corregirse manualmente en la línea de comando del servidor. Es decir, el servidor no sabe que el paquete de actualización no es correcto, simplemente lo asume por pura fe. Esto, combinado con los puntos anteriores, hace que el servidor sea extremadamente frágil en la práctica. - Uno de los mayores problemas es : actualmente no hay ningún demonio actualizador separado ejecutándose en el dispositivo. Debido a las complicaciones que esperan el acceso a internet wifi y a la piratería informática de última hora, es el software principal de control del dispositivo el que verifica y actualiza el dispositivo. Esto significa que si de alguna manera una versión mal probada llega a producción, y el software de control no puede iniciarse, todos los dispositivos que existen están esencialmente bloqueados, ya que ya no pueden actualizarse. Esta sería una pesadilla absoluta en la producción. El mismo trato para un solo dispositivo si pierde energía en un momento desafortunado.
- El otro problema importante es : no hay soporte para actualizaciones incrementales. Si un dispositivo, por ejemplo, no se enciende por un tiempo, la próxima vez que se actualice se salte un montón de versiones de lanzamiento, debe poder realizar una actualización directa de omisión de versión. La consecuencia de esto es que la implementación actualizada es una pesadilla de asegurarse de que cualquier actualización se pueda aplicar sobre cualquier versión anterior. Además, dado que los hash git se utilizan para identificar versiones en lugar de números de versión, la comparación lexicográfica de versiones para facilitar actualizaciones incrementales actualmente no es posible.
- Un nuevo requisito que no soporto actualmente es que existan algunas opciones de configuración por dispositivo (pares clave / valor) que deben configurarse en el lado del servidor administrativo. No me importaría de alguna manera servir estas opciones por dispositivo al dispositivo en la misma solicitud HTTP que la actualización de software (tal vez podría encapsularlo en encabezados / cookies HTTP) aunque no estoy demasiado preocupado por esto, ya que puedo siempre haga que sea una solicitud HTTP separada.
- Hay una ligera complicación debido al hecho de que existen dos (y más en el futuro) versiones del hardware. La versión actual del hardware se almacena en realidad como una variable de entorno en su imagen SD inicial (no pueden autoidentificarse) y todo el software está diseñado para ser compatible con todas las versiones de los dispositivos. Las actualizaciones de firmware se eligen en función de esta variable de entorno y el paquete de actualización contiene firmware para todas las versiones del hardware. Puedo vivir con esto, aunque es un poco torpe.
- Actualmente no hay forma de cargar manualmente una actualización en el dispositivo (en resumen, estos dispositivos tienen dos adaptadores wifi en ellos, uno para conectarse a Internet y otro en modo AP que el usuario usa para configurar el dispositivo; en el futuro Tengo la intención de agregar una función de "actualización de software" a la interfaz web local del dispositivo). Esto no es un gran problema, pero tiene algún impacto en el método de instalación de la actualización.
- Un montón de otras frustraciones e inseguridad general.
Entonces ... eso fue largo. Pero mi pregunta se reduce a esto:
¿Cómo hago esto de manera adecuada y segura? ¿Hay pequeños ajustes que puedo hacer a mi proceso actual? ¿Existe una estrategia probada en el tiempo / sistema existente que pueda aprovechar para no tener que rodar mi propio sistema de actualización de mierda ? O si tengo que rodar el mío, ¿cuáles son las cosas que deben ser ciertas para que un proceso de implementación / actualización sea seguro y exitoso? También tengo que poder incluir dispositivos de desarrollo en la mezcla.
Espero que la pregunta sea clara. Me doy cuenta de que es un poco confuso, pero estoy 100% seguro de que este es un problema que se ha abordado antes y se ha resuelto con éxito, pero no sé cuáles son las estrategias aceptadas actualmente.
Respuestas:
¿Podría proporcionar más información sobre la distribución, el gestor de arranque y la arquitectura de Linux (x86, ARM, MIPS?) Que están en uso?
Trataré de adivinar de cualquier manera y espero dirigirlo hacia la dirección correcta.
Si esa es una distribución basada en Yocto con U-Boot, recomendaría echar un vistazo a mender.io o swupdate . Estos proyectos parecen ajustarse bien a los criterios. Su objetivo principal es garantizar actualizaciones atómicas.
Mender proporciona un montón de herramientas que incluyen un demonio (y un montón de scripts de systemd) escritos en Go que eliminarán esta carga de tus hombros. Este proyecto es bastante fácil de usar con Yocto (proporcionan una metacapa para muchos dispositivos que debería ser fácil de adaptar para su caso específico y diseño de partición. También tienen muchas soluciones listas para usar para SOC populares) . En caso de que no use Yocto, puede consultar esta publicación que explica exactamente qué pasos debe seguir para usarla con distribuciones no basadas en Yocto.
swupdate también es bastante impresionante, pero parece un esfuerzo de un hombre de DENX (una organización detrás de U-Boot). Parece bastante maduro también.
También hay Ubuntu Snappy con el que no tengo experiencia y no puedo comentar de manera competente sobre este (tal vez alguien intervendrá). La idea es enviar sus aplicaciones en "snaps" independientes. Por lo que he entendido, esta es apenas una solución a su problema, aunque no es todo el sistema.
De hecho, parece que la tendencia actual es usar Docker (incluso en sistemas integrados) y amigos sobre APT / YUM. Esta última puede hacer que sea muy difícil garantizar la consistencia.
fuente