J'ai maintenant un projet de taille moyenne qui approche de la fin de la phase de "prototypes bâclés à base de caféine pour les démos de clients" et qui passe à la phase "pensez à l'avenir". Le projet se compose d'appareils basés sur Linux avec logiciel et micrologiciel, et d'un serveur Web administratif central. 10 prototypes existent actuellement, la production devrait être de l'ordre de 1000's bas.
N'étant pas familiarisé avec l'art des mises à jour automatiques et étant à court de temps, j'avais rapidement déployé ma propre stratégie de déploiement / mise à jour automatique de logiciels et, franchement, ça craint. Il se compose actuellement des éléments suivants:
- Un référentiel git hébergé (GitLab) avec une branche de version de production (notez que la source du serveur Web est également dans ce même référentiel, ainsi que quelques autres choses).
- Un bouton "déployer la mise à jour" sur l'interface Web qui:
- Extrait la dernière version de la branche des versions de production dans une zone de dépôt local et la copie également dans une zone de préparation provisoire de package.
- Exécute un script de nettoyage (stocké dans le référentiel) dans la zone de transfert pour supprimer les fichiers source non liés (par exemple, la source du serveur, la source du micrologiciel, etc.) et les fichiers .git.
- Écrit le hachage git actuel dans un fichier du package de mise à jour (l'objectif deviendra clair ci-dessous).
- Si tout s'est bien passé, il le compresse et le rend prêt à servir en écrasant le paquet gzip précédent avec un fichier du même nom, puis supprime la zone de transit.
- Notez qu'il existe maintenant deux copies du logiciel de l'appareil actuel sur le serveur, qui devraient être synchronisées: un référentiel git local complet sur la dernière branche de production et un package gzip prêt à l'emploi qui est maintenant supposé représenter cela même version.
- Le logiciel sur l'appareil est autonome dans un répertoire nommé
/opt/example/current
, qui est un lien symbolique vers la version actuelle du logiciel. - Une fonction de mise à jour automatique sur l'appareil qui, au démarrage:
- Vérifie la présence d'un
do_not_update
fichier et ne prend aucune autre mesure s'il existe (pour les périphériques de développement, voir ci-dessous). - Lit le hachage de validation actuel dans le fichier texte mentionné ci-dessus.
- Fait une requête HTTP au serveur avec ce hachage comme paramètre de requête. Le serveur répondra soit par un 304 (le hachage est la version actuelle) soit servira le package de mise à jour gzippé.
- Installe le package de mise à jour, le cas échéant, dans
/opt/example
:- Extraire les informations du logiciel mis à jour un dossier nommé
stage
. - Exécution d'un script de post-installation à partir du package de mise à jour qui fait des choses comme apporter les modifications locales nécessaires pour cette mise à jour, etc.
- Copie du dossier racine du logiciel actuel dans
previous
(supprime leprevious
premier existant , s'il y en a un). - Copie du
stage
dossier danslatest
(supprime d'latest
abord l' existant , s'il y en a un). - Assurer le
current
lien symbolique vers lequel pointerlatest
. - Redémarrage de l'appareil (les mises à jour du micrologiciel, le cas échéant, sont appliquées au redémarrage).
- Extraire les informations du logiciel mis à jour un dossier nommé
- Vérifie la présence d'un
Il y a aussi la question du déploiement initial sur des appareils nouvellement construits. Les appareils sont actuellement basés sur une carte SD (a son propre ensemble de problèmes, hors de portée ici), donc ce processus consiste à:
- Il existe une image SD contenant une version antérieure stable du logiciel.
- Une carte SD est créée à partir de cette image.
- Au premier démarrage, diverses initialisations spécifiques au périphérique (basées sur le numéro de série) ont lieu, puis le programme de mise à jour automatique récupère et installe la dernière version de production du logiciel comme d'habitude.
De plus, j'avais besoin de support pour les appareils de développement. Pour les appareils de développement:
- Un dépôt git local complet est conservé sur l'appareil.
- Le
current
lien symbolique pointe vers le répertoire de développement. - Il
do_not_update
existe un fichier local qui empêche le programme de mise à jour automatique de supprimer le code de développement avec une mise à jour de production.
Maintenant, le processus de déploiement devait théoriquement être:
- Une fois que le code est prêt pour le déploiement, poussez-le vers la branche de publication.
- Appuyez sur le bouton "déployer la mise à jour" sur le serveur.
- La mise à jour est désormais en ligne et les appareils se mettront à jour automatiquement lors de leur prochaine vérification.
Cependant, il y a une tonne de problèmes dans la pratique:
- Le code du serveur Web est dans le même référentiel que le code du périphérique, et le serveur a un référentiel git local que j'exécute. Le dernier code de serveur Web n'est pas sur la même branche que le dernier code de périphérique. La structure du répertoire est problématique. Lorsque le bouton «déployer la mise à jour» extrait la dernière version de la branche de production, il la tire dans un sous-répertoire du code serveur. Cela signifie que lorsque je me déploie sur un serveur à partir de zéro, je dois "semer" manuellement ce sous-répertoire en y saisissant la branche de production de l'appareil, car, probablement à partir d'une erreur utilisateur git de ma part, si je ne le fais pas, le déploiement tente de extraire le code du périphérique de la branche du serveur Web du répertoire parent . Je pense que cela peut être résolu en faisant de la zone de transfert un sous-répertoire du dépôt git local du serveur.
- Actuellement, le serveur Web ne gère pas de manière continue le hachage git du logiciel de l'appareil. Au démarrage du serveur, il effectue un
git rev-parse HEAD
référentiel logiciel dans son appareil local pour récupérer le hachage actuel. Pour des raisons que je ne peux pas comprendre, cela provoque également une tonne d'erreurs logiques que je ne décrirai pas ici, il suffit de dire que parfois le redémarrage du serveur fout les choses, surtout si le serveur est neuf et sans production le dépôt en succursale a été retiré. Je partagerais avec plaisir la source de cette logique si demandé, mais ce message devient long. - Si le script de nettoyage (côté serveur) échoue pour une raison quelconque, le serveur se retrouve avec un dépôt à jour mais un package de mise à jour non synchronisé / manquant,
git rev-parse HEAD
renverra donc un hachage qui ne correspond pas à ce qui est réellement servis aux périphériques, et les problèmes ici doivent être corrigés manuellement sur la ligne de commande du serveur. C'est-à-dire que le serveur ne sait pas que le package de mise à jour n'est pas correct, il suppose simplement toujours ainsi la foi pure. Ceci, combiné avec les points précédents, rend le serveur extrêmement fragile dans la pratique. - L'un des plus gros problèmes est : il n'y a actuellement aucun démon de mise à jour distinct en cours d'exécution sur l'appareil. En raison de complications en attendant l'accès à Internet wifi et de quelques hackers de dernière minute, c'est le principal logiciel de contrôle de l'appareil lui-même qui vérifie et met à jour l'appareil. Cela signifie que si une version mal testée entre en production et que le logiciel de contrôle ne peut pas démarrer, tous les appareils existants sont essentiellement briques, car ils ne peuvent plus se mettre à jour. Ce serait un véritable cauchemar dans la production. Même chose pour un seul appareil s'il perd de la puissance à un moment malchanceux.
- L'autre problème majeur est : il n'y a pas de support pour les mises à jour incrémentielles. Si un appareil, par exemple, n'est pas allumé pendant un certain temps, alors la prochaine fois qu'il sera mis à jour, il ignorera un tas de versions, il doit être en mesure de faire une mise à jour directe avec saut de version. La conséquence de ce déploiement mis à jour est un cauchemar de s'assurer que toute mise à jour donnée peut être appliquée par-dessus n'importe quelle version passée donnée. De plus, comme les hachages git sont utilisés pour identifier les versions plutôt que les numéros de version, la comparaison lexicographique des versions pour faciliter les mises à jour incrémentielles n'est actuellement pas possible.
- Une nouvelle exigence que je ne supporte pas actuellement est qu'il existera certaines options de configuration par périphérique (paires clé / valeur) qui doivent être configurées côté serveur d'administration. Cela ne me dérangerait pas de servir ces options par appareil à l'appareil dans la même demande HTTP que la mise à jour logicielle (je pourrais peut-être l'encapsuler dans les en-têtes / cookies HTTP) bien que je ne sois pas trop préoccupé par cela, car je peux en faire toujours une requête HTTP distincte.
- Il y a une légère complication due au fait qu'il existe deux versions (et plus à l'avenir) du matériel. La version actuelle du matériel est en fait stockée en tant que variable d'environnement sur son image SD initiale (ils ne peuvent pas s'auto-identifier) et tous les logiciels sont conçus pour être compatibles avec toutes les versions des appareils. Les mises à jour du firmware sont choisies en fonction de cette variable d'environnement et le package de mise à jour contient le firmware pour toutes les versions du matériel. Je peux vivre avec ça même si c'est un peu maladroit.
- Il n'existe actuellement aucun moyen de télécharger manuellement une mise à jour sur l'appareil (bref, ces appareils ont deux adaptateurs wifi, un pour se connecter à Internet et un en mode AP que l'utilisateur utilise pour configurer l'appareil; à l'avenir J'ai l'intention d'ajouter une fonction de «mise à jour du logiciel» à l'interface Web locale de l'appareil). Ce n'est pas énorme, mais cela a un impact sur la méthode d'installation de la mise à jour.
- Un tas d'autres frustrations et insécurité générale.
Alors ... c'était long. Mais ma question se résume à ceci:
Comment faire cela correctement et en toute sécurité? Puis-je apporter de petits ajustements à mon processus existant? Y a-t-il une stratégie éprouvée / un système existant que je peux utiliser pour ne pas avoir à lancer mon propre système de mise à jour merdique ? Ou si je dois rouler le mien, quelles sont les choses qui doivent être vraies pour qu'un processus de déploiement / mise à jour soit sûr et réussi? Je dois également pouvoir inclure des appareils de développement dans le mix.
J'espère que la question est claire. Je me rends compte que c'est un peu flou, mais je suis sûr à 100% qu'il s'agit d'un problème qui a été résolu avant et résolu avec succès, je ne sais simplement pas quelles sont les stratégies actuellement acceptées.
Réponses:
Pourriez-vous s'il vous plaît fournir plus d'informations sur la distribution Linux, le chargeur de démarrage et l'architecture (x86, ARM, MIPS?) Qui sont en cours d'utilisation?
J'essaierai de deviner de toute façon et je l'espère, je vous guiderai dans la bonne direction.
S'il s'agit d'une distribution basée sur Yocto avec U-Boot, je recommanderais de jeter un œil à mender.io ou swupdate . Ces projets semblent bien correspondre aux critères. Leur objectif principal est d'assurer les mises à jour atomiques.
Mender fournit un tas d'outils, y compris un démon (et un tas de scripts systemd) écrits en Go qui lèvera ce fardeau de vos épaules. Ce projet est assez facile à utiliser avec Yocto (ils fournissent une méta-couche pour de nombreux appareils qui devraient être faciles à adapter pour votre cas spécifique et la disposition de la partition. Ils ont également beaucoup de solutions prêtes à l'emploi pour les SOC populaires) . Dans le cas où vous n'utilisez pas Yocto, vous pouvez consulter ce post qui explique exactement les étapes à suivre pour l'utiliser avec des distributions non basées sur Yocto.
swupdate est également assez génial, mais cela semble être l'effort d'un homme d'un gars de DENX (une organisation derrière U-Boot). Il semble également assez mature.
Il y a aussi Ubuntu Snappy avec lequel je n'ai aucune expérience et je ne peux pas commenter celui-ci avec compétence (peut-être que quelqu'un interviendra). L'idée est de vous envoyer des applications dans des "snaps" autonomes. D'après ce que j'ai compris, c'est à peine une solution à votre problème, car ce n'est pas à l'échelle du système.
En fait, il semble que la tendance est aujourd'hui d'utiliser Docker (même dans les systèmes embarqués) et ses amis sur APT / YUM. Plus tard, il peut être très difficile d'assurer la cohérence.
la source