Les meilleures pratiques pour déployer des applications Web Java avec un temps d’arrêt minimal

Lors du déploiement d’une application Web Java volumineuse (> 100 Mo .war), j’utilise actuellement le processus de déploiement suivant:

  • Le fichier d’application .war est développé localement sur la machine de développement.
  • L’application développée est synchronisée entre la machine de développement et l’environnement en direct.
  • Le serveur d’applications dans l’environnement en direct est redémarré après la synchronisation. Cette étape n’est pas ssortingctement nécessaire, mais j’ai constaté que le redémarrage du serveur d’applications lors du déploiement évite “java.lang.OutOfMemoryError: PermGen space” en raison du chargement fréquent des classes.

Bonnes choses à propos de cette approche:

  • Le rsync minimise la quantité de données envoyées de la machine de développement à l’environnement en direct. Le téléchargement du fichier .war complet prend plus de dix minutes, alors que la synchronisation prend quelques secondes.

Mauvaises choses à propos de cette approche:

  • Pendant que rsync s’exécute, le contexte d’application est redémarré car les fichiers sont mis à jour. Idéalement, le redémarrage devrait avoir lieu après la fin de la synchronisation, et non pas quand il est encore en cours d’exécution.
  • Le redémarrage du serveur d’applications entraîne environ deux minutes d’arrêt.

J’aimerais trouver un processus de déploiement avec les propriétés suivantes:

  • Temps d’arrêt minimal pendant le processus de déploiement.
  • Temps minimum passé à télécharger les données.
  • Si le processus de déploiement est spécifique au serveur d’applications, le serveur d’applications doit être open-source.

Question:

  • Compte tenu des exigences énoncées, quel est le processus de déploiement optimal?

Il a été noté que rsync ne fonctionne pas correctement lors de la modification de fichiers WAR. La raison en est que les fichiers WAR sont essentiellement des fichiers ZIP et sont créés par défaut avec des fichiers membres compressés. Les petites modifications apscopes aux fichiers membres (avant la compression) entraînent des différences à grande échelle dans le fichier ZIP, ce qui rend l’algorithme de transfert delta de rsync inefficace.

Une solution possible consiste à utiliser jar -0 ... pour créer le fichier WAR original. L’option -0 indique à la commande jar de ne pas compresser les fichiers membres lors de la création du fichier WAR. Ensuite, lorsque rsync compare les anciennes et les nouvelles versions du fichier WAR, l’algorithme de transfert delta devrait pouvoir créer de petits diffs. Ensuite, arrangez-vous pour que rsync envoie les diffs (ou fichiers originaux) sous forme compressée; Par exemple, utilisez rsync -z ... ou un stream de données / transport compressé en dessous.

EDIT: en fonction de la structure du fichier WAR, il peut également être nécessaire d’utiliser jar -0 ... pour créer des fichiers JAR de composants. Cela s’appliquerait aux fichiers JAR qui sont fréquemment sujets à modification (ou qui sont simplement reconstruits), plutôt qu’à des fichiers JAR tiers stables.

En théorie, cette procédure devrait apporter une amélioration significative par rapport à l’envoi de fichiers WAR réguliers. En pratique, je n’ai pas essayé cela, donc je ne peux pas promettre que cela fonctionnera.

L’inconvénient est que le fichier WAR déployé sera considérablement plus gros. Cela peut entraîner des temps de démarrage plus longs pour les applications Web, même si je soupçonne que l’effet serait marginal.


Une approche différente consisterait à examiner votre fichier WAR pour voir si vous pouvez identifier les fichiers JAR de bibliothèque susceptibles de (presque) ne jamais changer. Retirez ces fichiers JAR du fichier WAR et déployez-les séparément dans le répertoire common/lib du serveur Tomcat. par exemple en utilisant rsync .

Mettre à jour:

Depuis que cette réponse a été écrite pour la première fois, un meilleur moyen de déployer des fichiers de guerre sur tomcat sans temps d’arrêt est apparu. Dans les versions récentes de tomcat, vous pouvez inclure des numéros de version dans vos noms de fichiers de guerre. Ainsi, par exemple, vous pouvez déployer les fichiers ROOT##001.war et ROOT##002.war dans le même contexte simultanément. Tout ce qui suit le ## est interprété comme un numéro de version par tomcat et ne fait pas partie du chemin de contexte. Tomcat gardera toutes les versions de votre application en cours d’exécution et servira de nouvelles requêtes et sessions à la version la plus récente, tout en complétant les anciennes requêtes et sessions de la version avec laquelle elles ont démarré. La spécification des numéros de version peut également être effectuée via le gestionnaire tomcat et même les tâches catalina ant. Plus d’infos ici .

Réponse originale:

Rsync a tendance à être inefficace sur les fichiers compressés car son algorithme de transfert delta recherche les modifications dans les fichiers et une petite modification dans un fichier non compressé peut modifier radicalement la version compressée résultante. Pour cette raison, il peut être judicieux de synchroniser un fichier de guerre non compressé plutôt qu’une version compressée, si la bande passante du réseau s’avère être un goulot d’étranglement.

Qu’est-ce qui ne va pas avec l’utilisation de l’application gestionnaire Tomcat pour effectuer vos déploiements? Si vous ne souhaitez pas télécharger le fichier de guerre entier directement vers l’application Tomcat Manager depuis un emplacement distant, vous pouvez le synchroniser (non compressé pour les raisons mentionnées ci-dessus) vers un emplacement réservé sur la boîte de production, le reconditionner en une guerre et puis remettez-le au responsable local. Il existe une belle tâche ant qui est livrée avec Tomcat vous permettant de créer des scripts de déploiement à l’aide de l’application Gestionnaire Tomcat.

Il y a une faille supplémentaire dans votre approche que vous n’avez pas mentionnée: bien que votre application soit partiellement déployée (lors d’une opération rsync), votre application peut être dans un état incohérent où les interfaces modifiées peuvent être désynchronisées, de nouvelles dépendances être indisponible, etc. En outre, selon la durée de votre travail rsync, votre application peut redémarrer plusieurs fois. Êtes-vous conscient que vous pouvez et devez désactiver le comportement d’écoute pour les fichiers modifiés et de redémarrage dans Tomcat? Il n’est en fait pas recommandé pour les systèmes de production. Vous pouvez toujours effectuer un redémarrage manuel ou scripté de votre application à l’aide de l’application Gestionnaire Tomcat.

Votre application ne sera évidemment pas accessible aux utilisateurs lors d’un redémarrage. Mais si la disponibilité vous préoccupe, vous avez sûrement des serveurs Web redondants derrière un équilibreur de charge. Lors du déploiement d’un fichier de guerre mis à jour, vous pouvez demander à l’équilibreur de charge d’envoyer temporairement toutes les demandes aux autres serveurs Web jusqu’à la fin du déploiement. Rincez et répétez pour vos autres serveurs Web.

Dans tous les environnements où le temps d’arrêt est une considération, vous exécutez sûrement une sorte de cluster de serveurs pour augmenter la fiabilité via la redondance. Je retirerais un hôte du cluster, le mettrais à jour et le renverrais ensuite dans le cluster. Si vous avez une mise à jour qui ne peut pas s’exécuter dans un environnement mixte (modification de schéma incompatible requirejse sur la firebase database, par exemple), vous devrez supprimer tout le site, au moins pour un moment. L’astuce consiste à mettre en place des processus de remplacement avant de déposer les originaux.

En utilisant tomcat comme exemple, vous pouvez utiliser CATALINA_BASE pour définir un répertoire où tous les répertoires de travail de tomcat seront trouvés, séparément du code exécutable. Chaque fois que je déploie un logiciel, je le déploie dans un nouveau répertoire de base afin que je puisse installer un nouveau code sur le disque à côté de l’ancien code. Je peux alors démarrer une autre instance de tomcat qui pointe vers le nouveau répertoire de base, démarrer et démarrer tout le processus, puis échanger l’ancien processus (numéro de port) avec le nouveau dans l’équilibreur de charge.

Si je m’inquiète de la préservation des données de session sur le commutateur, je peux configurer mon système de sorte que chaque hôte dispose d’un partenaire vers lequel il réplique les données de session. Je peux supprimer l’un de ces hôtes, le mettre à jour, le restaurer pour qu’il sélectionne les données de session, puis basculer les deux hôtes. Si j’ai plusieurs paires dans le cluster, je peux supprimer la moitié de toutes les paires, puis faire un changement de masse, ou je peux les faire une paire à la fois, en fonction des exigences de la version, des exigences de l’entreprise, etc. Personnellement, cependant, je préfère simplement permettre aux utilisateurs finaux de subir la perte très occasionnelle d’une session active plutôt que d’essayer de mettre à niveau avec des sessions intactes.

Il s’agit d’un compromis entre l’infrastructure informatique, la complexité des processus de publication et les efforts des développeurs. Si votre cluster est suffisamment grand et que votre désir est suffisamment fort, il est assez facile de concevoir un système qui peut être échangé sans aucun temps d’arrêt pour la plupart des mises à jour. Les grandes modifications de schéma entraînent souvent des temps d’arrêt réels, car les logiciels mis à jour ne peuvent généralement pas accepter l’ancien schéma, et vous ne pouvez probablement pas copier les données sur une nouvelle instance, effectuer la mise à jour des schémas Vous avez manqué des données écrites sur l’ancien après le clonage de la nouvelle firebase database. Bien sûr, si vous avez des ressources, vous pouvez demander aux développeurs de modifier la nouvelle application pour utiliser de nouveaux noms de tables pour toutes les tables mises à jour. Vous pouvez également mettre des sortingggers en place sur la firebase database live. il est écrit dans les anciennes tables par la version précédente (ou peut-être utiliser des vues pour émuler un schéma de l’autre). Affichez vos nouveaux serveurs d’applications et échangez-les dans le cluster. Il y a une tonne de jeux que vous pouvez jouer afin de minimiser les temps d’arrêt si vous avez les ressources de développement pour les construire.

Le mécanisme le plus utile pour réduire les temps d’arrêt pendant les mises à niveau logicielles est peut-être de s’assurer que votre application peut fonctionner en mode lecture seule. Cela fournira certaines fonctionnalités nécessaires à vos utilisateurs, mais vous permettra d’effectuer des modifications à l’échelle du système qui nécessitent des modifications de la firebase database, etc. Placez votre application en mode lecture seule, puis clonez les données, mettez à jour le schéma, affichez de nouveaux serveurs d’applications avec la nouvelle firebase database, puis basculez l’équilibreur de charge pour utiliser les nouveaux serveurs d’applications. Votre seul temps d’arrêt est le temps nécessaire pour passer en mode lecture seule et le temps nécessaire pour modifier la configuration de votre équilibreur de charge (la plupart pouvant le gérer sans aucun temps d’arrêt).

Mon conseil est d’utiliser rsync avec des versions éclatées mais déployer un fichier war.

  1. Créez un dossier temporaire dans l’environnement en direct où vous aurez une version éclatée de l’application Web.
  2. Rsync a explosé des versions.
  3. Après succès, rsync crée un fichier war dans un dossier temporaire dans la machine de l’environnement live.
  4. Remplacez l’ancienne guerre dans le répertoire de déploiement du serveur par une nouvelle du dossier temporaire.

Le remplacement de l’ancienne guerre par une nouvelle est recommandé dans le conteneur JBoss (basé sur Tomcat), car c’est une opération atomique et rapide et il est certain que lorsque le déploiement démarrera, l’ensemble de l’application sera déployé.

Ne pouvez-vous pas faire une copie locale de l’application Web actuelle sur le serveur Web, rsync sur ce répertoire et peut-être même utiliser des liens symboliques, en un seul coup, diriger Tomcat vers un nouveau déploiement sans trop de temps d’arrêt?

Votre approche de rsync la guerre extraite est plutôt bonne, y compris le redémarrage car je pense qu’un serveur de production ne doit pas avoir le déploiement à chaud activé. Donc, le seul inconvénient est le temps d’arrêt lorsque vous devez redémarrer le serveur, non?

Je suppose que tout l’état de votre application est contenu dans la firebase database. Vous ne rencontrez donc aucun problème lorsque certains utilisateurs travaillent sur une instance de serveur d’applications alors que d’autres utilisateurs se trouvent sur une autre instance de serveur d’applications. Si c’est le cas,

Exécutez deux serveurs d’applications : démarrez le second serveur d’applications (qui écoute les autres ports TCP) et déployez votre application à cet endroit. Après le déploiement, mettez à jour la configuration d’Apache httpd (mod_jk ou mod_proxy) pour qu’elle pointe vers le second serveur d’applications. Redémarrer gracieusement le processus Apache httpd. De cette façon, vous n’aurez aucun temps d’arrêt et les nouveaux utilisateurs et demandes seront automatiquement redirigés vers le nouveau serveur d’application.

Si vous pouvez utiliser la prise en charge du clustering et de la réplication de session du serveur d’applications, celle-ci sera encore plus fluide pour les utilisateurs actuellement connectés, car le second serveur d’applications sera resynchronisé dès son démarrage. Ensuite, quand il n’y a pas d’access au premier serveur, fermez-le.

Cela dépend de l’architecture de votre application.

L’une de mes applications se trouve derrière un proxy d’équilibrage de charge, où j’effectue un déploiement échelonné, éliminant efficacement les temps d’arrêt.

Déploiement à chaud d’un Java EAR pour minimiser ou éliminer les temps d’arrêt d’une application sur un serveur ou Déploiement «à chaud» d’une dépendance à la guerre dans Jboss à l’aide des outils Jboss Eclipse Le plugin Eclipse peut vous offrir quelques options.

Le déploiement sur un cluster sans temps d’arrêt est également intéressant.

JavaRebel a aussi un déploiement de code à chaud .

Si les fichiers statiques constituent une grande partie de votre gros WAR (100Mo est assez important), les placer en dehors de WAR et les déployer sur un serveur Web (par exemple Apache) devant votre serveur d’applications peut accélérer les choses. En plus de cela, Apache fait généralement un meilleur travail pour servir des fichiers statiques qu’un moteur de servlet (même si la plupart d’entre eux ont fait des progrès significatifs dans ce domaine).

Donc, au lieu de produire une grosse WAR, mettez-la au régime et produisez:

  • un gros ZIP avec des fichiers statiques pour Apache
  • une WAR moins volumineuse pour le moteur de servlet.

Si vous le souhaitez, allez plus loin dans le processus de création de WAR: si possible, déployez Grails et les autres JAR qui ne changent pas fréquemment (ce qui est probablement le cas de la plupart d’entre eux) au niveau du serveur d’applications.

Si vous réussissez à produire un WAR plus léger, je ne prendrais pas la peine de synchroniser des répertoires plutôt que des archives.

Points forts de cette approche:

  1. Les fichiers statiques peuvent être “déployés” à chaud sur Apache (par exemple, utilisez un lien symbolique pointant vers le répertoire en cours, décompressez les nouveaux fichiers, mettez à jour le lien symbolique et voilà).
  2. Le WAR sera plus mince et il faudra moins de temps pour le déployer.

Faiblesse de cette approche:

  1. Il y a un serveur supplémentaire (le serveur Web), ce qui ajoute (un peu) plus de complexité.
  2. Vous aurez besoin de changer les scripts de construction (pas une grosse affaire IMO).
  3. Vous devrez changer la logique rsync.

Je ne suis pas sûr que cela réponde à votre question, mais je vais simplement partager le processus de déploiement que j’utilise ou rencontre dans les quelques projets que j’ai réalisés.

Semblable à vous, je ne me rappelle jamais avoir effectué un redéploiement ou une mise à jour de guerre complète. La plupart du temps, mes mises à jour sont limitées à quelques fichiers jsp, peut-être une bibliothèque, certains fichiers de classe. Je suis capable de gérer et de déterminer quels sont les artefacts affectés, et généralement, nous avons empaqueté ces mises à jour dans un fichier zip, avec un script de mise à jour. Je vais exécuter le script de mise à jour. Le script effectue les opérations suivantes:

  • Sauvegardez les fichiers qui seront écrasés, peut-être dans un dossier avec la date et l’heure d’aujourd’hui.
  • Déballer mes fichiers
  • Arrêtez le serveur d’applications
  • Déplacer les fichiers sur
  • Démarrer le serveur d’applications

Si les temps d’arrêt sont un problème, et ils le sont généralement, mes projets sont généralement HA, même s’ils ne partagent pas l’état, mais utilisent un routeur qui assure un routage des sessions persistantes.

Une autre chose que je suis curieux serait, pourquoi le besoin de synchroniser? Vous devez être en mesure de savoir quels sont les changements requirejs, en les déterminant dans votre environnement de stockage intermédiaire / de développement, sans effectuer de contrôles delta avec live. Dans la plupart des cas, il vous faudrait tout de même régler votre rsync pour ignorer les fichiers, comme certains fichiers de propriétés définissant les ressources utilisées par un serveur de production, comme la connexion à la firebase database, le serveur smtp, etc.

J’espère que ceci est utile.

À quoi est-ce que votre ensemble PermSpace? Je m’attendrais à voir cette croissance aussi, mais devrait descendre après la collecte des anciennes classes? (ou le ClassLoader est-il toujours assis?)

En pensant à rien, vous pouvez synchroniser vers un répertoire distinct nommé par version ou par date. Si le conteneur prend en charge les liens symboliques, pourriez-vous SIGSTOP le processus racine, basculer la racine du système de fichiers du contexte via le lien symbolique, puis SIGCONT?

En ce qui concerne le contexte initial redémarre. Tous les conteneurs ont des options de configuration pour désactiver le redéploiement automatique sur le fichier de classe ou les modifications de ressources statiques. Vous ne pouvez probablement pas désactiver les redéploiements automatiques sur les modifications web.xml pour que ce fichier soit le dernier à mettre à jour. Ainsi, si vous désactivez le redéploiement automatique et la mise à jour du fichier web.xml en dernier, vous verrez le contexte redémarrer après toute la mise à jour.

Nous téléchargeons la nouvelle version de l’application Web dans un répertoire distinct, puis nous déplaçons pour la remplacer par celle en cours d’exécution, ou nous utilisons des liens symboliques. Par exemple, nous avons un lien symbolique dans le répertoire webapps tomcat nommé “myapp”, qui pointe vers l’application Web actuelle nommée “myapp-1.23”. Nous téléchargeons la nouvelle application Web sur “myapp-1.24”. Lorsque tout est prêt, arrêtez le serveur, supprimez le lien symbolique et créez-en un nouveau pointant vers la nouvelle version, puis redémarrez le serveur.

Nous désactivons le rechargement automatique sur les serveurs de production pour les performances, mais malgré tout, le fait de modifier des fichiers de manière non atomique peut entraîner des problèmes, car des fichiers statiques ou même des pages JSP risquent d’être endommagés.

En pratique, les applications Web sont en fait situées sur un périphérique de stockage partagé, de sorte que les serveurs en cluster, à équilibrage de charge et de basculement disposent tous du même code.

Le principal inconvénient de votre situation est que le téléchargement prendra plus de temps, car votre méthode permet à rsync de ne transférer que les fichiers modifiés ou ajoutés. Vous pouvez d’abord copier l’ancien dossier Webapp sur le nouveau et le synchroniser si cela fait une différence significative et si c’est vraiment un problème.

Tomcat 7 a une fonctionnalité intéressante appelée ” déploiement parallèle ” conçue pour ce cas d’utilisation.

L’essentiel est que vous développiez le fichier .war dans un répertoire, directement sous Webapps / ou Symlinked. Les versions successives de l’application se trouvent dans les répertoires nommés app##version , par exemple myapp##001 et myapp##002 . Tomcat traitera les sessions existantes allant à l’ancienne version et les nouvelles sessions à la nouvelle version.

Le problème est que vous devez faire très attention aux fuites de PermGen. Cela est particulièrement vrai avec Grails qui utilise beaucoup de PermGen. VisualVM est votre ami.

Utilisez simplement 2 serveurs Tomcat ou plus avec un proxy par-dessus. Ce proxy peut être apache / nignix / haproxy.

Maintenant, dans chacun des serveurs proxy, les URL “in” et “out” avec les ports sont configurés.

Copiez d’abord votre guerre dans le matou sans arrêter le service. Une fois la guerre déployée, elle est automatiquement ouverte par le moteur Tomcat.

Remarque cross check unpackWARs = “true” et autoDeploy = “true” dans le noeud “Host” dans server.xml

Ça a l’air de ça

   

Maintenant, consultez les journaux de Tomcat. S’il n’y a pas d’erreur, cela signifie qu’il est correctement installé.

Frappez maintenant toutes les API pour les tester

Maintenant, venez sur votre serveur proxy.

Modifiez simplement le mappage de l’URL d’arrière-plan avec le nom de la nouvelle guerre. Étant donné que l’enregistrement avec les serveurs proxy tels qu’apache / nignix / haProxy a pris moins de temps, vous vous sentirez comme un temps d’arrêt minimal

Voir – https://developers.google.com/speed/pagespeed/module/domains pour le mappage des URL

Vous utilisez Resin, Resin prend en charge la gestion des versions des applications Web.

http://www.caucho.com/resin-4.0/admin/deploy.xtp#VersioningandGracefulUpgrades

Mise à jour: son processus de surveillance peut également aider à résoudre les problèmes d’espace libre.

Pas une “meilleure pratique” mais quelque chose à laquelle je pensais.

Que diriez-vous de déployer l’application Web via un DVCS tel que git?

De cette façon, vous pouvez laisser git déterminer quels fichiers transférer sur le serveur. Vous avez également une belle façon de revenir en arrière si cela s’avère être cassé, il suffit de faire un retour!

J’ai écrit un script bash qui prend quelques parameters et synchronise le fichier entre les serveurs. Accélère le transfert de rsync pour les archives plus grandes:

https://gist.github.com/3985742