Préparation opérationnelle
Écrire du code peut être amusant, mais exécuter du code de manière sécurisée, évolutive et fiable est un ensemble de compétences totalement différent.
Read or listen to this story on Medium.
Agile et DevOps sont arrivés à une ère de développement logiciel rapide et dynamique, comme indiqué dans Construire une culture Agile .
Le prototypage rapide peut désormais être réalisé à moindre coût et facilement, mais la préparation opérationnelle et l'étape consistant à rendre ce prototype "prêt pour la production" sont souvent manquées. Il est souvent beaucoup trop facile de prendre le prototype en cours d'exécution que les parties prenantes aiment et de l'appeler production, le projet est clos et les développeurs passent au projet suivant. Attendez, qui va prendre en charge cette nouvelle solution ?
Le premier défi peut, espérons-le, être résolu par le concept d'équipes de service DevOps et de propriété du produit. Si une application ou un service est en cours d'exécution pour votre organisation, il doit être important pour quelqu'un et il doit avoir un propriétaire averti. Oui, l'exécution de nombreuses applications nécessite des ressources humaines, alors dotez-les correctement. Les équipes de service peuvent absolument prendre en charge plus d'un service, mais gardez à l'esprit que progressivement leur charge de travail augmentera au-delà de ce qu'elles peuvent correctement supporter.
La préparation opérationnelle est un processus structuré visant à garantir que l'équipe des opérations acquiert les outils, les compétences et la documentation nécessaires pour exploiter et maintenir une solution nouvellement achevée et l'infrastructure de support . Par conséquent, il est crucial de commencer le processus de préparation opérationnelle au début de la phase de planification et d'exécution du projet.
Gardez à l'esprit que des correctifs seront toujours nécessaires. Même avec des solutions sans serveur, les anciennes versions d'interpréteur seront toujours obsolètes et nécessiteront des modifications et des tests. Soyons réalistes, ce n'est pas de la magie, ce n'est pas vraiment sans serveur, ça tourne juste sur le serveur de quelqu'un d'autre. La barre du modèle de responsabilité partagée peut bouger, mais en fin de compte, la responsabilité de la conformité de la solution en matière de cybersécurité vous appartient.
Pratiques DevOps
Il y a quelques années, j'étais chargé de la maintenance de l'infrastructure Tomcat d'une entreprise et j'ai commencé à remarquer que les fichiers .war qu'un développeur me donnait à déployer étaient systématiquement environ deux fois plus volumineux que ce que les autres développeurs m'envoyaient. Il s'est avéré que les paramètres du compilateur sur son poste de travail n'étaient pas configurés pour compresser, contrairement aux autres développeurs. Quoi d'autre était différent dans ses paramètres?
L'essentiel est le suivant : ne compilez pas le code localement, si vous ne pouvez pas utiliser un pipeline de construction, à tout le moins, demandez à vos développeurs de compiler le code sur un système partagé.
Règle n°1 : ne compilez pas le code de production sur votre poste de travail local
Dans le même ordre d'idées, peu m'importe si vous n'avez pas modifié une seule ligne de code, si vous recompilez un nouveau binaire, il s'agit maintenant d'une toute nouvelle version qui doit être testée à nouveau. À cette fin, compiler du code pour un environnement spécifique (par exemple, une version Dev étant différente de la version Prod) est définitivement un anti-modèle qui doit être évité. Compilez un package générique et transmettez des paramètres spécifiques à l'environnement lors du déploiement. Le même package doit être déployé dans tous les environnements.
Règle n°2 : ne compilez pas de code pour un environnement spécifique
Même si vous déployez le même package, la dérive du serveur est un autre problème courant. Au fil du temps, quelle que soit la qualité et le dévouement de votre équipe OPS, les serveurs Dev ne seront pas exactement comme les serveurs Prod. Ils sont corrigés à des moments différents, peut-être par des personnes différentes, la configuration de l'infrastructure est différente, etc. Ces légères différences peuvent affecter la stabilité et le comportement de la solution de manière parfois inexplicable.
Le meilleur outil pour lutter contre la dérive des serveurs consiste en fait à "Dockeriser" votre déploiement. La création d'images Docker (ou toute solution équivalente) est en fait l'un des meilleurs moyens d'inclure des bibliothèques et des dépendances dans votre déploiement. Peu importe si le serveur Dev a une version Java différente de celle du serveur Prod, la version Java dont vous avez besoin est empaquetée à l'intérieur de votre image de conteneur, avec votre code. Cela en fait vraiment un déploiement portable, et à cette fin, les serveurs importent beaucoup moins. Vous ne devez jamais vous fier à un serveur nommé (si le nom du serveur commence par 'prd_' alors ...), les serveurs virtuels sont dynamiques et peuvent être reconstruits ou mis à l'échelle selon les besoins dans le cadre d'un ASG. Les microservices sont dynamiques, les paramètres de déploiement sont le ciment qui maintient l'environnement ensemble.
Règle #3 : Les serveurs sont du bétail, pas des animaux de compagnie, vous ne pouvez pas les nommer
J'ai déjà travaillé avec un développeur qui a mis une "mise à jour apt-get" au début de son fichier Docker-compose. Bien que j'applaudis ses efforts du point de vue de la cybersécurité, cet acte m'a terrifié du point de vue DevOps, car il va à l'encontre du principe fondamental de "construire une fois, déployer partout". Je peux vous garantir que la même image Docker, construite une fois et stockée dans un registre Docker, n'aura pas les mêmes bibliothèques lorsqu'elle sera déployée en Dev aujourd'hui et en Production le mois prochain. Bien que cela puisse sembler tiré par les cheveux, la prévisibilité et la répétabilité sont deux des pierres angulaires de la préparation opérationnelle. Peu importe qui a compilé le code ce jour-là et qui l'a déployé, le résultat doit être le même. Si vous le pouvez, automatisez vos builds, tests et déploiements dans le cadre d'un pipeline d'intégration et de déploiement continus (CI/CD).
Règle n° 4 : recherchez la prévisibilité et la répétabilité dans votre processus de création et de déploiement
Ne blâmez pas Murphy
La "loi de Murphy" stipule que "tout ce qui peut mal tourner tournera mal et au pire moment possible". Cet adage, qui peut s'appliquer à de nombreuses choses différentes, s'applique sans aucun doute à l'exécution de solutions informatiques dans un environnement de production.
"L'espoir n'est pas une stratégie"
Attendez-vous à ce que les choses se cassent et que des erreurs soient commises. Avant d'appeler votre solution « production », les parties prenantes, ainsi que les équipes Dev et Ops (ou DevOps) doivent organiser une « révision de l'état de préparation opérationnelle » formelle de la solution. Plusieurs listes de contrôle sont disponibles sur Internet, et AWS a également mis sa liste de contrôle à la disposition des clients . En un mot, les équipes doivent prendre des décisions concernant la haute disponibilité de la solution proposée, ainsi que les options de reprise après sinistre. Il n'y a pas de réponses "taille unique" à ces questions, l'importance de cette solution pour votre organisation et le coût des temps d'arrêt sont quelque peu subjectifs et propres à votre culture. Des serveurs tombent en panne, des centres de données entiers peuvent être inondés, des régions entières peuvent être détruites par une frappe nucléaire et des continents entiers peuvent se déconnecter après une frappe de météore... vous devez trouver l'équilibre entre votre tolérance au risque et le coût et complexité de la solution de contournement, sachant qu'une complexité accrue se fait au prix d'un risque accru. La réponse n'est pas facile à obtenir, et cela peut nécessiter une discussion récurrente, mais la conversation en vaut la peine.
La haute disponibilité est l'art de concevoir des solutions qui basculent ou évoluent instantanément face à l'adversité, la reprise après sinistre est l'art de mettre en œuvre des sauvegardes et de pouvoir restaurer la solution d'une manière ou d'une autre dans un délai acceptable. Les deux ne s'excluent pas mutuellement, une solution hautement disponible ne vous protégera pas d'une suppression de base de données malveillante ou accidentelle, la modification se répliquerait simplement sur tous les nœuds ; auquel cas une restauration à partir d'une sauvegarde ou d'un site de décalage serait nécessaire. Combien de données pouvez-vous vous permettre de perdre entre les sauvegardes ? Combien de temps d'arrêt affecterait votre marque et enverrait les clients vers la concurrence ?
Avoir un pipeline de déploiement stable et bien défini, ainsi qu'une solution bien documentée (voir Créer des diagrammes à partir de texte ) et une structure de support bien dotée en personnel sont tous des facteurs extrêmement importants dans cette équation.
Les tests fonctionnels et de régression doivent toujours faire partie de votre processus de déploiement, et les tests de charge et les tests d'endurance doivent également être présents. J'ai vu des machines virtuelles déployées à partir du même modèle se comporter très différemment des autres, ce n'est malheureusement pas une science exacte, implémentez des contrôles de santé appropriés avant de permettre à tout nouveau système d'accepter une charge de travail réelle. Testez tôt, testez souvent, mais générez des alertes significatives. Attention aux effets en cascade des dépendances en aval, votre équipe doit-elle être alertée si elle tombe en panne à cause d'une panne causée par une autre équipe ?
Je vous recommande fortement de penser à mettre en œuvre deux niveaux différents de surveillance et d'alerte, afin de ne pas submerger vos personnes de garde. La surveillance de chaque instance de chaque composant est utile, mais surtout avec les systèmes distribués où plusieurs instances d'un composant sont en cours d'exécution, la perte d'une instance peut ne pas valoir la peine de réveiller quelqu'un au milieu de la nuit, peut-être un rapport nocturne qui peut être adressé dans le matin suffira. Cependant, si le test de bout en bout échoue, le test qui imite de près les actions qu'un utilisateur aléatoire prendrait régulièrement, alors l'alerte est appropriée.
Règle #5 : Attendez-vous à l'inattendu
"La simplicité est la sophistication ultime."
Vous avez décidé du degré de disponibilité de votre solution, en évoluant sur plusieurs zones de disponibilité et centres de données, avec un équilibrage de charge régional et des répliques de base de données en lecture seule avec mise en cache. Les choses vont encore mal tourner.
Vous devez également mettre en œuvre des métriques et une surveillance pour obtenir une visibilité sur votre solution, son comportement et ses performances. Surveillez les composants clés, ainsi que l'expérience utilisateur de bout en bout. Obtenez des rapports quotidiens sur le nombre de codes de retour 200, 400 et 500 que votre service renvoie chaque jour et examinez toute anomalie.
Une fois, j'ai déployé une nouvelle version d'un service de traitement qui a immédiatement commencé à générer des erreurs. Nous avons annulé la nouvelle version et examiné les journaux d'erreurs. Deux des conteneurs de traitement émettaient des ID de transaction uniques en double, ce qui confondait un autre service dépendant en aval. Comment était-ce possible ? Le développeur avait utilisé un algorithme pour émettre des identifiants uniques qui reposaient sur le temps de déploiement du conteneur... et deux conteneurs ont été déployés d'une manière ou d'une autre dans la même milliseconde. La solution était simple, utilisez l'ID du conteneur ainsi que l'horodatage du déploiement pour émettre des ID uniques, mais quelles sont les chances que cela se produise ?
Lors de la conception de microservices, vous devez concevoir pour l'échec. Attendez-vous toujours à ce que les services dont vous dépendez puissent échouer. Pire encore qu'un échec, les services peuvent ne renvoyer une réponse invalide ou confuse qu'une partie du temps, peut-être qu'une seule instance sur 100 donne de mauvaises réponses. Attendez-vous à ce que les choses échouent, implémentez de nouvelles tentatives et s'il vous plaît implémentez un backoff exponentiel, j'ai vu trop de problèmes avec des tentatives rapides submergeant un service déjà en difficulté. Réessayez pendant une longue période, et les pannes sur les services dépendants peuvent potentiellement durer des heures, voire des jours. Vous devez prendre des décisions, sous stress, vaut-il mieux mal servir toutes les requêtes ou vaut-il mieux ne servir correctement qu'un nombre limité de requêtes et envoyer un message d'erreur aux autres utilisateurs ?
Règle n°6 : Construire pour l'échec
Les choses vont se casser, la suppression d'un goulot d'étranglement dans votre infrastructure ne fera que révéler le suivant, et votre équipe apprendra et s'améliorera. Mais cela ne peut se produire que si votre culture permet que cet échec se produise et devienne une opportunité d'apprentissage. Vous devez construire une culture irréprochable, où les gens peuvent se heurter à un mur et surmonter cet obstacle, apprendre et grandir. Si chaque erreur, évitable ou non, est considérée comme un échec individuel, alors une culture du "cela ne s'est jamais produit" s'installera et la suspicion prendra le pas sur tout. Soyez ouvert sur vos échecs et vos erreurs, soyez vulnérable et admettez vos lacunes, et vous grandirez tous ensemble en tant qu'organisation. La transparence est essentielle.
Bien sûr, chaque panne doit faire l'objet d'une enquête et une analyse des causes profondes (RCA) appropriée doit être effectuée, mais à moins qu'une erreur évidente et délibérée ait été commise par un humain, l'accent ne devrait jamais être mis sur "qui a fait cela ?" mais plutôt sur "comment le processus a-t-il laissé un humain faire cette erreur?". Le véritable objectif du RCA est de s'assurer que la même erreur ou une erreur similaire ne puisse pas être commise à l'avenir, cela fait partie du processus d'amélioration continue. Et bien sûr, si la panne a été causée par un bogue logiciel ou d'infrastructure, les commentaires à l'équipe de développement sont indispensables.
Règle #7 : Construire une culture irréprochable
Si vous ne faites pas d'erreurs, vous ne travaillez pas sur des problèmes assez difficiles. Et c'est une grosse erreur.
Sachant que les choses vont se briser, vous devez également accepter l'inattendu. Netflix a été le pionnier de l'ingénierie du chaos , une méthode de test de logiciels distribués qui introduit délibérément des scénarios d'échec et défectueux pour vérifier sa résilience face à des perturbations aléatoires. Tout le monde ne peut pas "diriger les singes" en production comme Netflix le fait, l'impact pour eux peut être de servir quelques images d'un film à une résolution inférieure, et l'impact sur un système financier en temps réel pourrait être dévastateur, alors s'il vous plaît adaptez-le à votre propre environnement.
Bien qu'il ne mentionne pas spécifiquement les systèmes informatiques, le livre Antifragile: Things That Gain from Disorder, de Nassim Nicholas Taleb vaut la peine d'être lu et peut très certainement s'appliquer à la création de microservices résilients.
Règle n°8 : Acceptez l'inattendu
« L'antifragilité va au-delà de la résilience ou de la robustesse. Le résilient résiste aux chocs et reste le même ; l'antifragile s'améliore.
L'atténuation de la fragilité n'est pas une option mais une exigence. Cela peut sembler évident, mais le point semble être manqué. Car la fragilité est très punitive, comme une maladie en phase terminale. Un paquet ne se brise pas dans des conditions défavorables, puis parvient à se réparer lorsque les conditions appropriées sont rétablies. La fragilité a une propriété semblable à un cliquet, l'irréversibilité des dommages.