Comment créer un pipeline de déploiement initial efficace
Publié: 2022-03-11J'adore construire des choses - quel développeur ne le fait pas ? J'aime trouver des solutions à des problèmes intéressants, écrire des implémentations et créer du beau code. Cependant, ce que je n'aime pas, ce sont les opérations . Les opérations sont tout ce qui n'est pas impliqué dans la création de logiciels de qualité, de la configuration des serveurs à la livraison de votre code en production.
C'est intéressant, car en tant que développeur Ruby on Rails indépendant, je dois fréquemment créer de nouvelles applications Web et répéter le processus de détermination du côté DevOps des choses. Heureusement, après avoir créé des dizaines d'applications, j'ai finalement opté pour un pipeline de déploiement initial parfait. Malheureusement, tout le monde ne l'a pas compris comme moi - finalement, cette connaissance m'a amené à franchir le pas et à documenter mon processus.
Dans cet article, je vais vous guider à travers mon pipeline parfait à utiliser au début de votre projet. Avec mon pipeline, chaque poussée est testée, la branche principale est déployée vers la mise en scène avec un nouveau vidage de base de données de la production, et les balises versionnées sont déployées vers la production avec des sauvegardes et des migrations automatiques.
Notez que puisque c'est mon pipeline, il est également opiniâtre et adapté à mes besoins ; cependant, vous pouvez vous sentir libre d'échanger tout ce que vous n'aimez pas et de le remplacer par ce qui vous plaît. Pour mon pipeline, nous utiliserons :
- GitLab pour héberger le code.
- Pourquoi : Mes clients préfèrent que leur code reste secret, et le niveau gratuit de GitLab est merveilleux. De plus, le CI gratuit intégré est génial. Merci GitLab !
- Alternatives : GitHub, BitBucket, AWS CodeCommit et bien d'autres.
- GitLab CI pour construire, tester et déployer notre code.
- Pourquoi : Il s'intègre à GitLab et est gratuit !
- Alternatives : TravisCI, Codeship, CircleCI, DIY avec Fabric8, et bien d'autres.
- Heroku pour héberger notre application.
- Pourquoi : Il fonctionne prêt à l'emploi et constitue la plate-forme idéale pour commencer. Vous pouvez modifier cela à l'avenir, mais toutes les nouvelles applications n'ont pas besoin de s'exécuter sur un cluster Kubernetes spécialement conçu. Même Coinbase a commencé sur Heroku.
- Alternatives : AWS, DigitalOcean, Vultr, DIY avec Kubernetes et bien d'autres.
À l'ancienne : créez une application de base et déployez-la sur Heroku
Tout d'abord, recréons une application typique pour quelqu'un qui n'utilise pas de pipelines CI/CD sophistiqués et souhaite simplement déployer son application.
Peu importe le type d'application que vous créez, mais vous aurez besoin de Yarn ou de npm. Pour mon exemple, je crée une application Ruby on Rails car elle est livrée avec des migrations et une CLI, et j'ai déjà écrit la configuration pour cela. Vous pouvez utiliser n'importe quel framework ou langage que vous préférez, mais vous aurez besoin de Yarn pour faire la version que je ferai plus tard. Je crée une application CRUD simple en utilisant seulement quelques commandes et aucune authentification.
Et testons si notre application fonctionne comme prévu. Je suis allé de l'avant et j'ai créé quelques messages, juste pour être sûr.
Et déployons-le sur Heroku en poussant notre code et en exécutant des migrations
$ heroku create toptal-pipeline Creating ⬢ toptal-pipeline... done https://toptal-pipeline.herokuapp.com/ | https://git.heroku.com/toptal-pipeline.git $ git push heroku master Counting objects: 132, done. ... To https://git.heroku.com/toptal-pipeline.git * [new branch] master -> master $ heroku run rails db:migrate Running rails db:migrate on ⬢ toptal-pipeline... up, run.9653 (Free) ...
Enfin, testons-le en production
Et c'est tout! En règle générale, c'est là que la plupart des développeurs quittent leurs opérations. À l'avenir, si vous apportez des modifications, vous devrez répéter les étapes de déploiement et de migration ci-dessus. Vous pouvez même faire des tests si vous n'êtes pas en retard pour le dîner. C'est très bien comme point de départ, mais réfléchissons un peu plus à cette méthode.
Avantages
- Rapide à mettre en place.
- Les déploiements sont faciles.
Les inconvénients
- Pas DRY : Nécessite de répéter les mêmes étapes à chaque changement.
- Pas de version : "Je ramène le déploiement d'hier à celui de la semaine dernière" n'est pas très précis dans trois semaines.
- Pas mal à l'épreuve du code : vous savez que vous êtes censé exécuter des tests, mais personne ne regarde, vous pouvez donc le pousser malgré les tests cassés occasionnels.
- Pas à l'épreuve des mauvais acteurs : que se passe-t-il si un développeur mécontent décide de casser votre application en envoyant du code avec un message indiquant que vous ne commandez pas assez de pizzas pour votre équipe ?
- N'évolue pas : permettre à chaque développeur de se déployer lui donnerait un accès au niveau de la production à l'application, en violation du principe du moindre privilège.
- Pas d'environnement intermédiaire : les erreurs spécifiques à l'environnement de production ne s'afficheront pas avant la production.
Le pipeline de déploiement initial parfait
Je vais essayer quelque chose de différent aujourd'hui : tenons une conversation hypothétique. Je vais vous donner une voix, et nous parlerons de la façon dont nous pouvons améliorer ce flux de courant. Allez-y, dites quelque chose.
Tu peux répéter s'il te plait? Attendez, je peux parler ?
Oui, c'est ce que je voulais dire en te donnant une voix. Comment vas-tu?
Je vais bien. C'est bizarre
Je comprends, mais continuez avec ça. Parlons maintenant de notre pipeline. Quelle est la partie la plus ennuyeuse de l'exécution des déploiements ?
C'est facile. Le temps que je perds. Avez-vous déjà essayé de pousser vers Heroku ?
Ouais, regarder vos dépendances télécharger et créer des applications dans le cadre du git push
est horrible !
N'est-ce pas? C'est fou. J'aimerais ne pas avoir à faire ça. Il y a aussi le fait que je dois exécuter des migrations * après * le déploiement, donc je dois regarder l'émission et vérifier pour m'assurer que mon déploiement se déroule
D'accord, vous pouvez en fait résoudre ce dernier problème en enchaînant les deux commandes avec &&
, comme git push heroku master && heroku run rails db:migrate
, ou simplement en créant un script bash et en le mettant dans votre code, mais toujours, bonne réponse, le le temps et la répétition est une vraie douleur.
Ouais, c'est vraiment nul
Et si je vous disais que vous pourriez résoudre ce problème immédiatement avec un pipeline CI/CD ?
A quoi maintenant? Qu'est-ce que c'est?
CI/CD signifie intégration continue (CI) et livraison/déploiement continu (CD). Il était assez difficile pour moi de comprendre exactement ce que c'était quand j'ai commencé parce que tout le monde utilisait des termes vagues comme "fusion du développement et des opérations", mais en termes simples :
- Intégration continue : Assurez-vous que tout votre code est fusionné en un seul endroit. Demandez à votre équipe d'utiliser Git et vous utiliserez CI.
- Livraison continue : assurez-vous que votre code est prêt à être expédié en permanence. Cela signifie produire rapidement une version prête à être distribuée de votre produit.
- Déploiement continu : prendre en toute transparence le produit de la livraison continue et le déployer simplement sur vos serveurs.
Oh, je comprends maintenant. Il s'agit de faire en sorte que mon application se déploie comme par magie dans le monde !
Mon article préféré expliquant CI/CD est par Atlassian ici. Cela devrait éclaircir toutes les questions que vous avez. Quoi qu'il en soit, revenons au problème.
Ouais, revenons à ça. Comment éviter les déploiements manuels ?
Configuration d'un pipeline CI/CD à déployer sur Push to master
Et si je vous disais que vous pouviez réparer ce problème immédiatement avec un CI/CD ? Vous pouvez pousser vers votre télécommande GitLab ( origin
) et un ordinateur sera généré pour pousser directement votre code vers Heroku.
Certainement pas!
Ouais façon! Revenons à nouveau dans le code.
Créez un .gitlab-ci.yml
avec le contenu suivant, en toptal-pipeline
par le nom de votre application Heroku :
image: ruby:2.4 before_script: - > : "${HEROKU_EMAIL:?Please set HEROKU_EMAIL in your CI/CD config vars}" - > : "${HEROKU_AUTH_TOKEN:?Please set HEROKU_AUTH_TOKEN in your CI/CD config vars}" - curl https://cli-assets.heroku.com/install-standalone.sh | sh - | cat >~/.netrc <<EOF machine api.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN machine git.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN EOF - chmod 600 ~/.netrc - git config --global user.email "[email protected]" - git config --global user.name "CI/CD" variables: APPNAME_PRODUCTION: toptal-pipeline deploy_to_production: stage: deploy environment: name: production url: https://$APPNAME_PRODUCTION.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_PRODUCTION.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku run rails db:migrate --app $APPNAME_PRODUCTION only: - master
Poussez ceci vers le haut et regardez-le échouer dans la page Pipelines de votre projet. C'est parce qu'il manque les clés d'authentification pour votre compte Heroku. La réparation est assez simple, cependant. Vous aurez d'abord besoin de votre clé API Heroku. Obtenez-le à partir de la page Gérer le compte, puis ajoutez les variables secrètes suivantes dans les paramètres CI/CD de votre dépôt GitLab :
-
HEROKU_EMAIL
: L'adresse e-mail que vous utilisez pour vous connecter à Heroku -
HEROKU_AUTH_KEY
: La clé que vous avez obtenue de Heroku
Cela devrait se traduire par un GitLab fonctionnel vers Heroku se déployant à chaque push. Quant à ce qui se passe :
- En poussant pour maîtriser
- La CLI Heroku est installée et authentifiée dans un conteneur.
- Votre code est transmis à Heroku.
- Une sauvegarde de votre base de données est capturée dans Heroku.
- Les migrations sont exécutées.
Déjà, vous pouvez voir que non seulement vous gagnez du temps en automatisant tout sur un git push
, mais vous créez également une sauvegarde de votre base de données à chaque déploiement ! En cas de problème, vous aurez une copie de votre base de données sur laquelle revenir.
Création d'un environnement de mise en scène
Mais attendez, petite question, qu'advient-il de vos problèmes spécifiques à la production ? Que se passe-t-il si vous rencontrez un bogue étrange parce que votre environnement de développement est trop différent de la production ? Une fois, j'ai rencontré des problèmes étranges avec SQLite 3 et PostgreSQL lorsque j'ai exécuté une migration. Les détails m'échappent, mais c'est tout à fait possible.

J'utilise strictement PostgreSQL dans le développement, je ne confonds jamais les moteurs de base de données comme ça, et je surveille avec diligence ma pile pour les incompatibilités potentielles.
Eh bien, c'est un travail fastidieux et j'applaudis votre discipline. Personnellement, je suis beaucoup trop paresseux pour faire ça. Cependant, pouvez-vous garantir ce niveau de diligence pour tous les futurs développeurs, collaborateurs ou contributeurs potentiels ?
Euh - Ouais, non. Vous m'avez bien eu. D'autres personnes vont tout gâcher. Quel est votre point, cependant?
Ce que je veux dire, c'est que vous avez besoin d'un environnement de mise en scène. C'est comme la production, mais ce n'est pas le cas. Un environnement intermédiaire est l'endroit où vous vous entraînez à déployer en production et détectez toutes vos erreurs en amont. Mes environnements de mise en scène reflètent généralement la production, et je vide une copie de la base de données de production lors du déploiement de la mise en scène pour m'assurer qu'aucun cas de coin gênant ne gâche mes migrations. Avec un environnement de staging, vous pouvez arrêter de traiter vos utilisateurs comme des cobayes.
C'est logique! Alors, comment puis-je faire cela?
C'est là que ça devient intéressant. J'aime déployer master
directement sur la mise en scène.
Attendez, n'est-ce pas là que nous déployons la production en ce moment ?
Oui, mais maintenant nous allons plutôt déployer vers la mise en scène.
Mais si master
se déploie en staging, comment déployons-nous en production ?
En utilisant quelque chose que vous auriez dû faire il y a des années : versionner notre code et pousser les balises Git.
Balises Git ? Qui utilise les balises Git ? ! Cela commence à ressembler à beaucoup de travail.
C'était sûr, mais heureusement, j'ai déjà fait tout ce travail et vous pouvez simplement vider mon code et cela fonctionnera.
Tout d'abord, ajoutez un bloc sur le déploiement intermédiaire à votre fichier .gitlab-ci.yml
, j'ai créé une nouvelle application Heroku appelée toptal-pipeline-staging
:
… variables: APPNAME_PRODUCTION: toptal-pipeline APPNAME_STAGING: toptal-pipeline-staging deploy_to_staging: stage: deploy environment: name: staging url: https://$APPNAME_STAGING.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_STAGING.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku pg:backups:restore `heroku pg:backups:url --app $APPNAME_PRODUCTION` --app $APPNAME_STAGING --confirm $APPNAME_STAGING - heroku run rails db:migrate --app $APPNAME_STAGING only: - master - tags ...
Modifiez ensuite la dernière ligne de votre bloc de production pour qu'il s'exécute sur des balises Git sémantiquement versionnées au lieu de la branche master :
deploy_to_production: ... only: - /^v(?'MAJOR'(?:0|(?:[1-9]\d*)))\.(?'MINOR'(?:0|(?:[1-9]\d*)))\.(?'PATCH'(?:0|(?:[1-9]\d*)))(?:-(?'prerelease'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?(?:\+(?'build'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$/ # semver pattern above is adapted from https://github.com/semver/semver.org/issues/59#issuecomment-57884619
L'exécuter maintenant échouera car GitLab est suffisamment intelligent pour autoriser uniquement les branches "protégées" à accéder à nos variables secrètes. Pour ajouter des balises de version, accédez à la page des paramètres du référentiel de votre projet GitLab et ajoutez v*
aux balises protégées.
Récapitulons ce qui se passe maintenant :
- En poussant vers master, ou en poussant un commit étiqueté
- La CLI Heroku est installée et authentifiée dans un conteneur.
- Votre code est transmis à Heroku.
- Une sauvegarde de la production de votre base de données est capturée dans Heroku.
- La sauvegarde est vidée dans votre environnement de staging.
- Les migrations sont exécutées sur la base de données intermédiaire.
- En poussant un commit étiqueté sémantiquement version
- La CLI Heroku est installée et authentifiée dans un conteneur.
- Votre code est transmis à Heroku.
- Une sauvegarde de la production de votre base de données est capturée dans Heroku.
- Les migrations sont exécutées sur la base de données de production.
Vous sentez-vous puissant maintenant ? Je me sens puissant. Je me souviens, la première fois que je suis venu aussi loin, j'ai appelé ma femme et lui ai expliqué tout ce pipeline avec des détails atroces. Et elle n'est même pas technique. J'étais super impressionné par moi-même, et vous devriez l'être aussi ! Excellent travail à venir jusqu'ici !
Tester chaque poussée
Mais il y a plus, puisqu'un ordinateur fait des choses pour vous de toute façon, il peut également exécuter toutes les choses que vous êtes trop paresseux pour faire : des tests, des erreurs de peluche, à peu près tout ce que vous voulez faire, et si l'un d'entre eux échoue, il a gagné pas passer au déploiement.
J'adore avoir cela dans mon pipeline, cela rend mes revues de code amusantes. Si une demande de fusion réussit toutes mes vérifications de code, elle mérite d'être examinée.
Ajoutez un bloc de test
:
test: stage: test variables: POSTGRES_USER: test POSTGRES_PASSSWORD: test-password POSTGRES_DB: test DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSSWORD}@postgres/${POSTGRES_DB} RAILS_ENV: test services: - postgres:alpine before_script: - curl -sL https://deb.nodesource.com/setup_8.x | bash - apt-get update -qq && apt-get install -yqq nodejs libpq-dev - curl -o- -L https://yarnpkg.com/install.sh | bash - source ~/.bashrc - yarn - gem install bundler --no-ri --no-rdoc - bundle install -j $(nproc) --path vendor - bundle exec rake db:setup RAILS_ENV=test script: - bundle exec rake spec - bundle exec rubocop
Récapitulons ce qui se passe maintenant :
- À chaque poussée ou demande de fusion
- Ruby et Node sont installés dans un conteneur.
- Les dépendances sont installées.
- L'application est testée.
- En poussant vers le maître, ou en poussant un commit étiqueté, et seulement si tous les tests réussissent
- La CLI Heroku est installée et authentifiée dans un conteneur.
- Votre code est transmis à Heroku.
- Une sauvegarde de la production de votre base de données est capturée dans Heroku.
- La sauvegarde est vidée dans votre environnement de staging.
- Les migrations sont exécutées sur la base de données intermédiaire.
- En poussant un commit étiqueté sémantiquement version, et seulement si tous les tests réussissent
- La CLI Heroku est installée et authentifiée dans un conteneur.
- Votre code est transmis à Heroku.
- Une sauvegarde de la production de votre base de données est capturée dans Heroku.
- Les migrations sont exécutées sur la base de données de production.
Prenez du recul et émerveillez-vous du niveau d'automatisation que vous avez atteint. A partir de maintenant, tout ce que vous avez à faire est d'écrire du code et de pousser. Testez votre application manuellement dans la mise en scène si vous en avez envie, et lorsque vous vous sentez suffisamment en confiance pour la diffuser dans le monde, étiquetez-la avec la version sémantique !
Versionnement sémantique automatique
Oui, c'est parfait, mais il manque quelque chose. Je n'aime pas rechercher la dernière version de l'application et la marquer explicitement. Cela prend plusieurs commandes et me distrait pendant quelques secondes.
OK, mec, arrête ! C'est assez. Vous êtes juste en train de le sur-concevoir maintenant. Ça marche, c'est génial, ne gâchez pas une bonne chose en allant trop loin.
D'accord, j'ai une bonne raison de faire ce que je m'apprête à faire.
Priez, éclairez-moi.
J'étais comme toi. J'étais content de cette configuration, mais ensuite j'ai foiré. git tag
répertorie les tags par ordre alphabétique, v0.0.11
est supérieure v0.0.2
. Une fois, j'ai accidentellement marqué une version et j'ai continué à le faire pendant environ une demi-douzaine de versions jusqu'à ce que je voie mon erreur. C'est alors que j'ai décidé d'automatiser cela aussi.
On y va encore une fois
D'accord, heureusement, nous avons la puissance de npm à notre disposition, j'ai donc trouvé un package approprié : yarn add --dev standard-version
et ajoutez ce qui suit à votre fichier package.json
:
"scripts": { "release": "standard-version", "major": "yarn release --release-as major", "minor": "yarn release --release-as minor", "patch": "yarn release --release-as patch" },
Maintenant, vous devez faire une dernière chose, configurer Git pour pousser les balises par défaut. Pour le moment, vous devez exécuter git push --tags
pour pousser une balise vers le haut, mais le faire automatiquement sur git push
normal est aussi simple que d'exécuter git config --global push.followTags true
.
Pour utiliser votre nouveau pipeline, chaque fois que vous souhaitez créer une exécution de release :
-
yarn patch
pour les versions de patch -
yarn minor
pour les versions mineures -
yarn major
pour les versions majeures
Si vous n'êtes pas sûr de la signification des mots « majeur », « mineur » et « correctif », consultez le site de version sémantique pour en savoir plus.
Maintenant que vous avez enfin terminé votre pipeline, récapitulons comment l'utiliser !
- Écrire du code.
- Validez et poussez-le pour le tester et le déployer en staging.
- Utilisez
yarn patch
pour étiqueter une version de patch. -
git push
pour le mettre en production.
Résumé et autres étapes
Je viens juste d'effleurer la surface de ce qui est possible avec les pipelines CI/CD. C'est un exemple assez simpliste. Vous pouvez faire bien plus en remplaçant Heroku par Kubernetes. Si vous décidez d'utiliser GitLab CI, lisez la documentation yaml car vous pouvez faire bien plus en mettant en cache des fichiers entre les déploiements ou en sauvegardant des artefacts !
Un autre changement énorme que vous pourriez apporter à ce pipeline est d'introduire des déclencheurs externes pour exécuter la version et la publication sémantiques. Actuellement, ChatOps fait partie de leur plan payant, et j'espère qu'ils le publieront dans des plans gratuits. Mais imaginez pouvoir déclencher l'image suivante via une seule commande Slack !
Finalement, à mesure que votre application commence à devenir complexe et nécessite des dépendances au niveau du système, vous devrez peut-être utiliser un conteneur. Lorsque cela se produit, consultez notre guide : Premiers pas avec Docker : Simplifier Devops .
Cet exemple d'application est vraiment en ligne et vous pouvez en trouver le code source ici.