Déploiement d'une application Rails sans interruption de service

Déploiement d'une application Rails sans interruption de service

Parce que la pleine disponibilité d'une application est cruciale pour le bon ressenti de ses utilisateurs, et que pouvoir déployer sans se soucier de ce manque de disponibilité est un confort essentiel au bien-être des développeurs.

Présentation faite dans le cadre des apéros RubyNord.
http://ruby-nord.org/

Le code source de cette présentation est disponible sur https://gitlab.com/bobmaerten/presentation-zdd

5e47e75af9d584936f6b4be3520c75d7?s=128

Bob Maerten

April 19, 2016
Tweet

Transcript

  1. Déploiement d'une application Rails sans interruption de service Apéro Ruby

    Nord - Avril 2016 1 / 38
  2. Bob Maerten http://levups.com 2 / 38

  3. Pourquoi le ZDD ? (Zero Downtime Deployment) 3 / 38

  4. Parce que c'est pratique quand il y a des users

    ! 4 / 38
  5. Parce que les devs n'aiment pas deploy aux aurores !

    5 / 38
  6. Parce qu'il n'y a que les petites natures qui ne

    deploy pas plusieurs fois par jour ! 6 / 38
  7. Le ZDD dans l'esprit commun 7 / 38

  8. Blue/Green 8 / 38

  9. 9 / 38

  10. Blue/Green Avantages test avant bascule ; usage en pseudo A/B

    ; rollback "facile" ; architectures multi-éléments 10 / 38
  11. Blue/Green Avantages test avant bascule ; usage en pseudo A/B

    ; rollback "facile" ; architectures multi-éléments Inconvénients doubles ressources ; gestion des sessions lors de la bascule ; delta DB pendant la bascule. 11 / 38
  12. Mais pour des apps à taille "humaine" ? 12 /

    38
  13. Unicorn ! 13 / 38

  14. Unicorn Avantages protection du code non-threadsafe ;-) multi-process = multi-core

    ; isolation des crashes de workers 14 / 38
  15. Unicorn Avantages protection du code non-threadsafe ;-) multi-process = multi-core

    ; isolation des crashes de workers Inconvénients optimisé pour les "fast-clients" (frontal web obligatoire) consommation mémoire 15 / 38
  16. The killer feature! 16 / 38

  17. Rolling restart $ k i l l - U S

    R 2 < u n i c o r n _ p i d > 17 / 38
  18. Unicorn rolling restart en détail envoi du signal USR2 au

    master process ; 18 / 38
  19. Unicorn rolling restart en détail envoi du signal USR2 au

    master process ; unicorn duplique son master ; u n i c o r n m a s t e r ( o l d ) \ _ u n i c o r n w o r k e r [ 0 ] \ _ u n i c o r n w o r k e r [ 1 ] \ _ u n i c o r n w o r k e r [ 2 ] \ _ u n i c o r n m a s t e r \ _ u n i c o r n w o r k e r [ 0 ] \ _ u n i c o r n w o r k e r [ 1 ] \ _ u n i c o r n w o r k e r [ 2 ] 19 / 38
  20. Unicorn rolling restart en détail envoi du signal USR2 au

    master process ; unicorn duplique son master ; u n i c o r n m a s t e r ( o l d ) \ _ u n i c o r n w o r k e r [ 0 ] \ _ u n i c o r n w o r k e r [ 1 ] \ _ u n i c o r n w o r k e r [ 2 ] \ _ u n i c o r n m a s t e r \ _ u n i c o r n w o r k e r [ 0 ] \ _ u n i c o r n w o r k e r [ 1 ] \ _ u n i c o r n w o r k e r [ 2 ] envoi du signal T T O U à l'ancien master : ses workers se terminent après leur requête en cours ; 20 / 38
  21. Unicorn rolling restart en détail envoi du signal USR2 au

    master process ; unicorn duplique son master ; u n i c o r n m a s t e r ( o l d ) \ _ u n i c o r n w o r k e r [ 0 ] \ _ u n i c o r n w o r k e r [ 1 ] \ _ u n i c o r n w o r k e r [ 2 ] \ _ u n i c o r n m a s t e r \ _ u n i c o r n w o r k e r [ 0 ] \ _ u n i c o r n w o r k e r [ 1 ] \ _ u n i c o r n w o r k e r [ 2 ] envoi du signal T T O U à l'ancien master : ses workers se terminent après leur requête en cours ; Plus de workers sur l'ancien master : envoi du signal Q U I T ; 21 / 38
  22. Unicorn rolling restart en détail envoi du signal USR2 au

    master process ; unicorn duplique son master ; u n i c o r n m a s t e r ( o l d ) \ _ u n i c o r n w o r k e r [ 0 ] \ _ u n i c o r n w o r k e r [ 1 ] \ _ u n i c o r n w o r k e r [ 2 ] \ _ u n i c o r n m a s t e r \ _ u n i c o r n w o r k e r [ 0 ] \ _ u n i c o r n w o r k e r [ 1 ] \ _ u n i c o r n w o r k e r [ 2 ] envoi du signal T T O U à l'ancien master : ses workers se terminent après leur requête en cours ; Plus de workers sur l'ancien master : envoi du signal Q U I T ; sinon, envoi de H U P à l'ancien master et Q U I T au nouveau pour retourner à l'état initial. 22 / 38
  23. config/unicorn.rb b e f o r e _ f o

    r k d o | s e r v e r , w o r k e r | d e f i n e d ? ( A c t i v e R e c o r d : : B a s e ) a n d A c t i v e R e c o r d : : B a s e . c o n n e c t i o n . d i s c o n n e c t ! o l d _ p i d = " # { s e r v e r . c o n f i g [ : p i d ] } . o l d b i n " i f o l d _ p i d ! = s e r v e r . p i d b e g i n s i g = ( w o r k e r . n r + 1 ) > = s e r v e r . w o r k e r _ p r o c e s s e s ? : Q U I T : : T T O U P r o c e s s . k i l l ( s i g , F i l e . r e a d ( o l d _ p i d ) . t o _ i ) r e s c u e E r r n o : : E N O E N T , E r r n o : : E S R C H e n d e n d e n d https://github.com/defunkt/unicorn/blob/master/examples/unicorn.conf.rb 23 / 38
  24. config/deploy .rb n a m e s p a c

    e : d e p l o y d o t a s k : r e s t a r t d o i n v o k e ' u n i c o r n : r e s t a r t ' e n d e n d lib/capistrano/tasks/unicorn.cap n a m e s p a c e : u n i c o r n d o t a s k : e n v i r o n m e n t d o s e t : u n i c o r n _ p i d , " # { s h a r e d _ p a t h } / t m p / p i d s / u n i c o r n . p i d " e n d t a s k : r e s t a r t = > : e n v i r o n m e n t d o o n r o l e s ( : a p p ) d o i f t e s t ( " [ - f # { f e t c h ( : u n i c o r n _ p i d ) } ] " ) e x e c u t e : k i l l , " - s U S R 2 $ ( < # { f e t c h ( : u n i c o r n _ p i d ) } ) " e l s e s t a r t _ u n i c o r n e n d e n d e n d e n d 24 / 38
  25. Quid d'un déploiement avec migration ? 25 / 38

  26. Cas simples Creation de tables l'ancien code ne connait pas

    le modèle associé 26 / 38
  27. Cas simples Creation de tables l'ancien code ne connait pas

    le modèle associé Ajout de colonne l'ancien code ne connait pas la colonne sauf si containte/validation SQL sur la colonne 27 / 38
  28. Cas nécessitant un peu d'at tention Suppression de colonne a

    priori OK depuis Rails 4.0 si l'ancien code n'utilise plus l'attribut 28 / 38
  29. Cas nécessitant un peu d'at tention Suppression de colonne a

    priori OK depuis Rails 4.0 si l'ancien code n'utilise plus l'attribut sinon migrer en 2 étapes : préciser à ActiveRecord d'ignorer la colonne c l a s s U s e r d e f s e l f . c o l u m n s s u p e r . r e j e c t { | c o l u m n | c o l u m n . n a m e = = " n o t e s " } e n d e n d 29 / 38
  30. Cas nécessitant un peu d'at tention Suppression de colonne a

    priori OK depuis Rails 4.0 si l'ancien code n'utilise plus l'attribut sinon migrer en 2 étapes : préciser à ActiveRecord d'ignorer la colonne c l a s s U s e r d e f s e l f . c o l u m n s s u p e r . r e j e c t { | c o l u m n | c o l u m n . n a m e = = " n o t e s " } e n d e n d déployer une migration qui supprime la colonne 30 / 38
  31. Renommage de colonne ajouter une nouvelle colonne 31 / 38

  32. Renommage de colonne ajouter une nouvelle colonne s'assurer de l'écriture

    des données sur les 2 colonnes d e f f i r s t _ n a m e s u p e r | | a t t r i b u t e s [ " f n a m e " ] e n d 32 / 38
  33. Renommage de colonne ajouter une nouvelle colonne s'assurer de l'écriture

    des données sur les 2 colonnes d e f f i r s t _ n a m e s u p e r | | a t t r i b u t e s [ " f n a m e " ] e n d migrer les données, mettre à jour les éventuelles requêtes SQL 33 / 38
  34. Renommage de colonne ajouter une nouvelle colonne s'assurer de l'écriture

    des données sur les 2 colonnes d e f f i r s t _ n a m e s u p e r | | a t t r i b u t e s [ " f n a m e " ] e n d migrer les données, mettre à jour les éventuelles requêtes SQL supprimer l'ancienne colonne comme au point précédent 34 / 38
  35. Renommage de colonne ajouter une nouvelle colonne s'assurer de l'écriture

    des données sur les 2 colonnes d e f f i r s t _ n a m e s u p e r | | a t t r i b u t e s [ " f n a m e " ] e n d migrer les données, mettre à jour les éventuelles requêtes SQL supprimer l'ancienne colonne comme au point précédent Dans tous les cas ! testez vos migrations en local sur une version à jour de votre db de production 35 / 38
  36. Démo ? Questions ? 36 / 38

  37. Images campagne "préjugés" de Cofidis http://techblog.betgenius.com/content/images/2014/Nov/Simple-Blue- Green-2.jpg http://levups.com https://github.com/images/error/angry_unicorn.png 37

    / 38
  38. Google-est-mon-ami-graphie https://www.numergy.com/developpeurs-blog/bluegreen-deployment-le- deploiement-continu-facile/ http://blog.octo.com/zero-downtime-deployment/ http://blog.appsignal.com/blog/2016/03/17/ruby-magic-mastering- concurrency.html https://unicorn.bogomips.org/SIGNALS.html https://blog.codeship.com/rails-migrations-zero-downtime/ http://jakeyesbeck.com/2016/02/07/how-to-remove-a-column-with-zero- downtime-in-ruby-on-rails/

    https://www.rainforestqa.com/blog/2014-06-27-zero-downtime-database- migrations/ 38 / 38