Théorie et pratique de la normalisation des services Docker

Les informations sur le thÚme de l'architecture des applications de microservices, qui ont déjà réussi à combler leur retard, suffisent aujourd'hui pour décider si elles conviennent ou non à votre produit. Et ce n'est pas du tout un secret que les entreprises qui ont décidé de choisir cette voie doivent faire face à de nombreux défis d'ingénierie et de culture. L'une des sources de problÚmes est les frais généraux qui se multiplient partout, et cela s'applique également à la routine associée aux processus de production.



Source de l'image:


Comme vous pouvez le deviner, l'Anti-plagiat est exactement une telle entreprise, oĂč la comprĂ©hension est progressivement venue que nous sommes avec des microservices en cours de route. Mais avant de commencer Ă  manger le cactus, nous avons dĂ©cidĂ© de le nettoyer et de le faire cuire. Et puisque toutes les seules solutions vraies et correctes pour chacune sont uniques, au lieu de diapositives DevOps universelles avec de belles flĂšches, nous avons dĂ©cidĂ© de partager notre propre expĂ©rience et de dire comment nous avons dĂ©jĂ  couvert une partie considĂ©rable de notre chemin spĂ©cial vers, je l'espĂšre, le succĂšs.


Si vous fabriquez un produit vraiment unique, largement constitué de savoir-faire, il n'y a presque aucune chance d'esquiver ce chemin spécial, car il est formé de nombreux chemins privés: à partir de la culture et des données historiques qui se sont développées dans l'entreprise, se terminant par sa propre spécificité et la pile technologique utilisée .


L'une des tĂąches de toute entreprise et Ă©quipe est de trouver l'Ă©quilibre optimal entre les libertĂ©s et les rĂšgles, et les microservices portent ce problĂšme Ă  un nouveau niveau. Cela peut sembler contredire l'idĂ©e mĂȘme de microservices, qui implique une grande libertĂ© dans le choix des technologies, mais si vous ne vous concentrez pas directement sur les problĂšmes architecturaux et technologiques, mais regardez les problĂšmes de production en gĂ©nĂ©ral, alors le risque d'ĂȘtre quelque part dans l' intrigue du «Jardin des dĂ©lices terrestres» est tout Ă  fait tangible. .


Cependant, dans l'ouvrage "CrĂ©ation de microservices" Sam Newman propose une solution Ă  ce problĂšme, oĂč littĂ©ralement dĂšs les premiĂšres pages il Ă©voque la nĂ©cessitĂ© de limiter la crĂ©ativitĂ© des Ă©quipes dans le cadre d'accords techniques. Ainsi, l'une des clĂ©s du succĂšs, en particulier dans le contexte d'une ressource Ă  main levĂ©e limitĂ©e, est la standardisation de tout ce qui ne peut ĂȘtre nĂ©gociĂ© et que personne ne voudrait vraiment faire tout le temps. En Ă©laborant des accords, nous crĂ©ons des rĂšgles du jeu claires pour tous les participants Ă  la production et au fonctionnement des composants du systĂšme. Et connaissant les rĂšgles du jeu, vous devez ĂȘtre d'accord, jouer devrait ĂȘtre plus facile et plus agrĂ©able. Cependant, suivre ces rĂšgles elles-mĂȘmes peut devenir une routine et causer de l'inconfort aux participants, ce qui entraĂźne directement toutes sortes de dĂ©viations par rapport Ă  elles et, par consĂ©quent, l'Ă©chec de l'idĂ©e dans son ensemble. Et le moyen le plus Ă©vident est de mettre tous les accords dans le code, car aucune rĂ©glementation ne peut faire ce que l'automatisation et les outils pratiques peuvent utiliser, dont l'utilisation est intuitive et naturelle.


En allant dans cette direction, nous avons pu automatiser de plus en plus, et plus notre processus est devenu comme un convoyeur de bout en bout pour la production de bibliothĂšques et de micro (ou pas) services.


Clause de non-responsabilité
Ce texte n'est pas une tentative d'indiquer «comme il se doit», il n'y a pas de solutions universelles, seulement une description de notre position sur le chemin évolutif et la direction choisie. Tout ce qui précÚde peut ne pas convenir à tout le monde, mais dans notre cas, cela a du sens principalement parce que:

- Le développement dans l'entreprise dans 90% des cas se fait en C #;
- Il n'était pas nécessaire de repartir de zéro, une partie des normes, approches et technologies acceptées - c'est le résultat d'une expérience ou simplement d'un héritage historique;
- Des référentiels avec des projets .NET, contrairement aux équipes, des dizaines (et il y en aura plus);
- Nous aimons utiliser un pipeline CI trĂšs simple, en Ă©vitant autant que possible le blocage des fournisseurs;
- Pour un développeur .NET ordinaire, les mots "conteneur", "docker" et "Linux" peuvent toujours provoquer des épisodes de légÚre horreur existentielle, mais je ne veux pas briser qui que ce soit par le genou.

Un peu de fond


Au printemps 2017, Microsoft a présenté au monde un aperçu de .NET Core 2.0, et cette année, les astrologues C # se sont immédiatement précipités pour déclarer l'année Linux, alors ...



Source de l'image:


Pendant un certain temps, nous, ne faisant pas confiance à la magie, avons tout collecté et testé sur Windows et Linux, publié des artefacts avec certains scripts SSH, essayé de configurer les anciens pipelines CI / CD en mode couteau suisse. Mais aprÚs un certain temps, ils ont réalisé que nous faisions quelque chose de mal. De plus, les références aux microservices et aux conteneurs résonnaient de plus en plus souvent. Nous avons donc également décidé de surfer sur la vague de battage médiatique et d'explorer ces directions.


DĂ©jĂ  au stade de la rĂ©flexion sur notre futur possible de microservices, un certain nombre de questions se sont posĂ©es, ignorant lesquelles, nous avons risquĂ© dans ce tout futur de nouveaux problĂšmes que nous aurions nous-mĂȘmes crĂ©Ă©s en Ă©change de leur rĂ©solution.


PremiĂšrement, lorsque nous regardons le cĂŽtĂ© opĂ©rationnel du monde thĂ©orique des microservices sans rĂšgles, nous avons Ă©tĂ© effrayĂ©s par la perspective du chaos avec toutes les consĂ©quences qui en dĂ©coulent, y compris non seulement la qualitĂ© imprĂ©visible du rĂ©sultat, mais aussi les conflits entre Ă©quipes ou dĂ©veloppeurs et ingĂ©nieurs. Et essayer de faire des recommandations, ne pas ĂȘtre en mesure de les respecter, semblait immĂ©diatement une entreprise vide de sens.


DeuxiĂšmement, personne ne savait vraiment comment crĂ©er correctement des conteneurs et Ă©crire des fichiers dockerfile, qui, nĂ©anmoins, ont dĂ©jĂ  commencĂ© Ă  vivre dans nos rĂ©fĂ©rentiels. De plus, beaucoup «lisent quelque part» que tout n'y est pas si simple. Donc, quelqu'un a dĂ» plonger plus profondĂ©ment et comprendre, puis revenir avec les meilleures pratiques d'assemblage de conteneurs. Mais la perspective de jouer le rĂŽle d'un docker packer Ă  plein temps, laissĂ© seul avec des piles de fichiers docker, pour une raison quelconque, n'a inspirĂ© personne dans l'entreprise. En outre, comme il s'est avĂ©rĂ©, plonger une fois n'est clairement pas suffisant, et mĂȘme Ă  premiĂšre vue, il peut s'avĂ©rer faux ou tout simplement pas trĂšs bon.


Et troisiĂšmement, je voulais ĂȘtre sĂ»r que les images obtenues avec les services seraient non seulement correctes du point de vue des pratiques de conteneur, mais seraient Ă©galement prĂ©visibles dans leur comportement et auraient toutes les propriĂ©tĂ©s et attributs nĂ©cessaires pour simplifier le contrĂŽle des conteneurs lancĂ©s. En d'autres termes, je voulais obtenir des images avec des applications qui sont Ă©galement configurĂ©es et Ă©crire des journaux, fournir une interface unique pour obtenir des mesures, avoir un ensemble cohĂ©rent d'Ă©tiquettes, etc. Il Ă©tait Ă©galement important que l'assemblage sur l'ordinateur du dĂ©veloppeur donne le mĂȘme rĂ©sultat que l'assemblage sur n'importe quel systĂšme CI, y compris la rĂ©ussite des tests et la gĂ©nĂ©ration d'artefacts.


Ainsi, une comprĂ©hension est nĂ©e qu'un certain processus serait nĂ©cessaire pour gĂ©rer et centraliser de nouvelles connaissances, pratiques et normes, et le chemin depuis le premier commit vers une image docker complĂštement prĂȘte pour l'infrastructure du produit devrait ĂȘtre unifiĂ© et aussi automatisĂ© que possible, sans aller au-delĂ  des termes commençant avec le mot continu.


CLI vs GUI


Le point de dĂ©part d'un nouveau composant, qu'il s'agisse d'un service ou d'une bibliothĂšque, est de crĂ©er un rĂ©fĂ©rentiel. Cette Ă©tape peut ĂȘtre divisĂ©e en deux parties: crĂ©er et configurer le rĂ©fĂ©rentiel sur l'hĂ©bergement du systĂšme de contrĂŽle de version (nous avons Bitbucket) et l'initialiser avec la crĂ©ation d'une structure de fichiers. Heureusement, un certain nombre d'exigences existaient dĂ©jĂ  pour les deux. Par consĂ©quent, leur formalisation dans le code Ă©tait une tĂąche logique.


Alors, quel devrait ĂȘtre notre rĂ©fĂ©rentiel:


  • SituĂ© dans l'un des projets, sur lequel le nom, les droits d'accĂšs, les politiques d'acceptation des demandes de tirage, etc.;
  • Contiennent les fichiers et rĂ©pertoires requis, tels que:
    • fichier avec la configuration et les informations sur le rĂ©fĂ©rentiel SolutionInfo.props (plus d'informations ci-dessous);
    • les codes source du projet dans le rĂ©pertoire src ;
    • .gitignore , README.md , etc.;
  • Contient les sous-modules Git nĂ©cessaires;
  • Le projet doit ĂȘtre dĂ©rivĂ© de l'un des modĂšles.

Étant donnĂ© que l'API REST Bitbucket donne un contrĂŽle total sur la configuration des rĂ©fĂ©rentiels, un utilitaire spĂ©cial a Ă©tĂ© crĂ©Ă© pour interagir avec elle - le gĂ©nĂ©rateur de rĂ©fĂ©rentiel. En mode question-rĂ©ponse, elle reçoit de l'utilisateur toutes les donnĂ©es nĂ©cessaires et crĂ©e un rĂ©fĂ©rentiel qui rĂ©pond pleinement Ă  toutes nos exigences, Ă  savoir:


  • DĂ©finit un projet dans Bitbucket Ă  choisir;
  • Valide le nom conformĂ©ment Ă  notre accord;
  • DĂ©finit tous les paramĂštres nĂ©cessaires qui ne peuvent pas ĂȘtre hĂ©ritĂ©s du projet;
  • Met Ă  jour la liste des modĂšles personnalisĂ©s (nous utilisons des modĂšles dotnet ) pour le projet et suggĂšre de choisir parmi ceux-ci;
  • Remplit le minimum d'informations nĂ©cessaires sur le rĂ©fĂ©rentiel dans le fichier de configuration et dans les documents *.md ;
  • Il connecte les sous-modules avec la configuration du pipeline CI / CD (dans notre cas, c'est Bamboo Specs ) et les scripts d'assemblage.

En d'autres termes, le dĂ©veloppeur, en commençant un nouveau projet, lance l'utilitaire, remplit plusieurs champs, sĂ©lectionne le type de projet et reçoit, par exemple, le «Hello world!» ComplĂštement terminĂ©. un service qui est dĂ©jĂ  connectĂ© au systĂšme CI, d'oĂč le service peut mĂȘme ĂȘtre publiĂ© si vous effectuez une validation qui change la version en non nul.


La premiÚre étape a été franchie. Pas de travail manuel et d'erreurs, recherche de documentation, inscriptions et SMS. Passons maintenant à ce qui y a été généré.


La structure


La standardisation de la structure du rĂ©fĂ©rentiel a pris racine avec nous depuis longtemps et Ă©tait nĂ©cessaire pour simplifier l'assemblage, l'intĂ©gration avec le systĂšme CI et l'environnement de dĂ©veloppement. Initialement, nous sommes partis de l'idĂ©e que le pipeline dans CI devrait ĂȘtre aussi simple et, comme vous pouvez le deviner, standard, ce qui garantirait la portabilitĂ© et la reproductibilitĂ© de l'assemblage. Autrement dit, le mĂȘme rĂ©sultat pourrait ĂȘtre facilement obtenu Ă  la fois dans n'importe quel systĂšme CI et sur le lieu de travail du dĂ©veloppeur. Par consĂ©quent, tout ce qui ne se rapporte pas aux fonctionnalitĂ©s d'un environnement d'intĂ©gration continue spĂ©cifique est soumis Ă  un sous-module Git spĂ©cial et est un systĂšme de construction autosuffisant. Plus prĂ©cisĂ©ment, le systĂšme de normalisation de l'assemblage. Le pipeline lui-mĂȘme, dans une approximation minimale, ne doit exĂ©cuter que le script build.sh , rĂ©cupĂ©rer un rapport sur les tests et lancer un dĂ©ploiement, si nĂ©cessaire. Pour plus de clartĂ©, voyons ce qui se passe si vous gĂ©nĂ©rez le rĂ©fĂ©rentiel SampleService dans un projet avec le nom parlant Sandbox .


 . ├── [bamboo-specs] ├── [devops.build] │ ├── build.sh │ └── ... ├── [docs] ├── [.scripts] ├── [src] │ ├── [CodeAnalysis] │ ├── [Sandbox.SampleService] │ ├── [Sandbox.SampleService.Bootstrap] │ ├── [Sandbox.SampleService.Client] │ ├── [Sandbox.SampleService.Tests] │ ├── Directory.Build.props │ ├── NLog.config │ ├── NuGet.Config │ └── Sandbox.SampleService.sln ├── .gitattributes ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── README.md └── SolutionInfo.props 

Les deux premiers rĂ©pertoires sont des sous-modules Git. bamboo-specs est «Pipeline as Code» pour le systĂšme Atlassian Bamboo CI (il pourrait y avoir un fichier Jenkins Ă  sa place), devops.build est notre systĂšme de construction, dont je devops.build plus en dĂ©tail ci-dessous. Le rĂ©pertoire .scripts Ă©galement. Le projet .NET lui-mĂȘme est situĂ© dans src : NuGet.Config contient la configuration du rĂ©fĂ©rentiel privĂ© NuGet , NLog.config configuration en temps rĂ©el de NLog . Comme vous pouvez le deviner, l'utilisation de NLog dans une entreprise est Ă©galement l'une des normes. Le fichier Directory.Build.props est presque magique. Pour une raison quelconque, peu de gens connaissent une telle possibilitĂ© dans les projets .NET, comme la personnalisation de l'assembly . En bref, les fichiers portant les noms Directory.Build.props et Directory.Build.targets automatiquement importĂ©s dans vos projets et vous permettent de configurer des propriĂ©tĂ©s communes pour tous les projets en un seul endroit. Par exemple, c'est ainsi que nous connectons l'analyseur StyleCop.Analyzers et sa configuration Ă  partir du rĂ©pertoire CodeAnalysis Ă  tous les projets de style code, dĂ©finissons des rĂšgles de version et certains attributs communs pour les bibliothĂšques et les packages ( Company , Copyright , etc.), et nous nous connectons Ă©galement via le <Import> fichier SolutionInfo.props , qui est prĂ©cisĂ©ment le mĂȘme fichier de configuration de rĂ©fĂ©rentiel, qui a Ă©tĂ© discutĂ© ci-dessus. Il contient dĂ©jĂ  la version actuelle, des informations sur les auteurs, l'URL du rĂ©fĂ©rentiel et sa description, ainsi que plusieurs propriĂ©tĂ©s qui affectent le comportement du systĂšme d'assemblage et les artefacts rĂ©sultants.


Exemple `SolutionInfo.props`
 <?xml version="1.0"?> <Project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="devops.build/SolutionInfo.xsd"> <PropertyGroup> <!-- Product name --> <Product>Sandbox.SampleService</Product> <!-- Product version. Version 0.0.0 wouldn't be published! --> <BaseVersion>0.0.0</BaseVersion> <!-- Project name which contains Main() --> <EntryProject>Sandbox.SampleService.Bootstrap</EntryProject> <!-- Exposed port --> <ExposedPort>4000/tcp</ExposedPort> <!-- DOTNET_SYSTEM_GLOBALIZATION_INVARIANT value. See https://github.com/dotnet/corefx/blob/master/Documentation/architecture/globalization-invariant-mode.md --> <GlobalizationInvariant>false</GlobalizationInvariant> <!-- Project URL --> <RepositoryUrl>https://bitbucket.contoso.com/projects/SND/repos/sandbox.sampleservice/</RepositoryUrl> <!-- Documentation URL --> <DocumentationUrl>https://bitbucket.contoso.com/projects/SND/repos/sandbox.sampleservice/browse/README.md</DocumentationUrl> <!-- Your name here --> <Authors>User Name &lt;username@contoso.com&gt;</Authors> <!-- Project description --> <Description>The sample service for demo purposes.</Description> <!-- Bamboo plan key (required for Bamboo Specs --> <BambooBlanKey>SMPL</BambooBlanKey> </PropertyGroup> </Project> 

Exemple `Directory.Build.props`
 <Project> <Import Condition="Exists('..\SolutionInfo.props')" Project="..\SolutionInfo.props" /> <ItemGroup> <None Include="$(MSBuildThisFileDirectory)/CodeAnalysis/stylecop.json" Link="stylecop.json" CopyToOutputDirectory="Never"/> <PackageReference Include="StyleCop.Analyzers" Version="1.*" PrivateAssets="all" /> </ItemGroup> <PropertyGroup> <CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/CodeAnalysis/stylecop.ruleset</CodeAnalysisRuleSet> <!-- Enable XML docs generating--> <GenerateDocumentationFile>true</GenerateDocumentationFile> <!-- Enable C# 7.x features --> <LangVersion>latest</LangVersion> <!-- default base version --> <BaseVersion Condition="'$(BaseVersion)' == ''">0.0.0</BaseVersion> <!-- default build number and format --> <BuildNumber Condition="'$(BuildNumber)' == ''">0</BuildNumber> <BuildNumber>$([System.String]::Format('{0:0000}',$(BuildNumber)))</BuildNumber> <!-- default version suffix --> <VersionSuffix Condition="'$(VersionSuffix)' == ''">local</VersionSuffix> <!-- empty version suffix instead of 'prod' --> <VersionSuffix Condition="'$(VersionSuffix)' == 'prod'"></VersionSuffix> <!-- format version prefix --> <VersionPrefix>$(BaseVersion).$(BuildNumber)</VersionPrefix> <!-- disable IsPackable by default --> <IsPackable>false</IsPackable> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> <Company>Contoso</Company> <Copyright>Copyright $([System.DateTime]::Now.Date.Year) Contoso Ltd</Copyright> </PropertyGroup> </Project> 

Assemblage


Il convient de mentionner tout de suite que moi-mĂȘme et mes collĂšgues avions dĂ©jĂ  une expĂ©rience assez rĂ©ussie dans l’utilisation de diffĂ©rents systĂšmes de construction . Et au lieu de pondĂ©rer un outil existant avec des fonctionnalitĂ©s totalement inhabituelles, il a Ă©tĂ© dĂ©cidĂ© d'en crĂ©er un autre, spĂ©cialisĂ© pour notre nouveau processus, et de laisser l'ancien seul pour mener Ă  bien ses tĂąches dans le cadre de projets hĂ©ritĂ©s. L'idĂ©e de correction Ă©tait le dĂ©sir d'obtenir un outil qui transformera le code en une image docker qui rĂ©pond Ă  toutes nos exigences, en utilisant un processus standard unique, tout en Ă©liminant la nĂ©cessitĂ© pour les dĂ©veloppeurs de plonger dans les subtilitĂ©s de l'assemblage, mais en prĂ©servant la possibilitĂ© d'une certaine personnalisation.


La sĂ©lection d'un cadre appropriĂ© a commencĂ©. Sur la base des exigences de reproductibilitĂ© du rĂ©sultat Ă  la fois sur les machines de build avec Linux et sur les machines Windows de tout dĂ©veloppeur, la vĂ©ritable multiplateforme et un minimum de dĂ©pendances prĂ©dĂ©finies sont devenues une condition clĂ©. À diffĂ©rents moments, j'ai rĂ©ussi Ă  bien connaĂźtre certains des cadres d'assemblage pour les dĂ©veloppeurs .NET: de MSBuild et ses configurations XML monstrueuses, qui ont ensuite Ă©tĂ© traduites en Psake (Powershell), en FAKE exotique (F #). Mais cette fois, je voulais quelque chose de frais et lĂ©ger. De plus, il a dĂ©jĂ  Ă©tĂ© dĂ©cidĂ© que l'assemblage et les tests devraient ĂȘtre entiĂšrement effectuĂ©s dans un environnement de conteneur isolĂ©, donc je ne prĂ©voyais pas de fonctionner Ă  l'intĂ©rieur d'autre chose que les commandes Docker CLI et Git, c'est-Ă -dire que la plupart du processus aurait dĂ» ĂȘtre dĂ©crit dans le Dockerfile.
À cette Ă©poque, FAKE 5 et Cake pour .NET Core n'Ă©taient toujours pas prĂȘts, donc avec une plateforme multiplateforme, ces projets Ă©taient comme ça. Mais mon trĂšs cher PowerShell 6 Core a dĂ©jĂ  Ă©tĂ© publiĂ©, et je l'ai utilisĂ© au maximum. Par consĂ©quent, j'ai dĂ©cidĂ© de me tourner Ă  nouveau vers Psake, et pendant que je me tournais, je suis tombĂ© sur un projet Invoke-Build intĂ©ressant, qui est une refonte de Psake et, comme le souligne l'auteur lui-mĂȘme, est le mĂȘme, mais en mieux et plus facilement. Il en est ainsi. Je ne m'y attarderai pas en dĂ©tail dans le cadre de cet article, je noterai seulement que la compacitĂ© me soudoie si toutes les fonctions de base de cette classe de produits sont disponibles:


  • La sĂ©quence d'actions est dĂ©crite par un ensemble de tĂąches interdĂ©pendantes (tĂąches), qui peuvent ĂȘtre contrĂŽlĂ©es en utilisant leurs interdĂ©pendances et leurs conditions supplĂ©mentaires.
  • Il existe plusieurs aides pratiques, par exemple, exec {} pour gĂ©rer correctement les codes de sortie des applications de console.
  • Toute exception ou arrĂȘt d'utilisation de Ctrl + C sera correctement traitĂ©e dans un bloc Exit-Build spĂ©cial intĂ©grĂ©. Par exemple, vous pouvez supprimer tous les fichiers temporaires, un environnement de test ou dessiner un rapport agrĂ©able Ă  l'Ɠil.


Dockerfile générique


Le Dockerfile lui-mĂȘme et l'assemblage utilisant la docker build offrent des capacitĂ©s de paramĂ©trage assez faibles, et la flexibilitĂ© de ces outils est Ă  peine supĂ©rieure Ă  celle d'une poignĂ©e de pelle. En outre, il existe un grand nombre de façons de rendre la «mauvaise» image, trop grande, trop dangereuse, trop peu intuitive ou simplement imprĂ©visible. Heureusement, la documentation de Microsoft propose dĂ©jĂ  plusieurs exemples de Dockerfile , qui vous permettent de comprendre rapidement les concepts de base et de crĂ©er votre premier Dockerfile, en l'amĂ©liorant progressivement plus tard. Il utilise dĂ©jĂ  un modĂšle Ă  plusieurs Ă©tapes et crĂ©e une image spĂ©ciale « Test Runner » pour exĂ©cuter les tests.


ModĂšle et arguments Ă  plusieurs Ă©tapes


La premiÚre étape consiste à diviser les étapes d'assemblage en plus petites et à en ajouter de nouvelles. Il convient donc de souligner le lancement de la dotnet build tant qu'étape distincte, car pour les projets contenant uniquement des bibliothÚques, il est inutile d'exécuter la dotnet publish . Maintenant, à notre discrétion, nous ne pouvons exécuter que les étapes d'assemblage requises en utilisant
dotnet build --target <name>
Par exemple, nous collectons ici un projet contenant uniquement des bibliothÚques. Les artefacts ici ne sont que des packages NuGet, ce qui signifie que cela n'a aucun sens de collecter une image d'exécution.



Ou nous construisons déjà un service, mais à partir de la branche des fonctionnalités. Nous n'avons pas du tout besoin d'artefacts d'un tel assemblage, il est seulement important de passer les tests et le bilan de santé.



La prochaine chose Ă  faire est de paramĂ©trer l'utilisation des images de base. Depuis un certain temps maintenant, dans le Dockerfile, la directive ARG peut ĂȘtre placĂ©e en dehors des Ă©tapes de construction, et les valeurs transfĂ©rĂ©es peuvent ĂȘtre utilisĂ©es dans le nom de l'image de base.


 ARG DOTNETCORE_VERSION=2.2 ARG ALPINE_VERSION= ARG BUILD_BASE=mcr.microsoft.com/dotnet/core/sdk:${DOTNETCORE_VERSION}-alpine${ALPINE_VERSION} ARG RUNTIME_BASE=mcr.microsoft.com/dotnet/core/runtime:${DOTNETCORE_VERSION}-alpine${ALPINE_VERSION} FROM ${BUILD_BASE} AS restore ... FROM ${RUNTIME_BASE} AS runtime ... 

Nous avons donc eu de nouvelles opportunitĂ©s Ă  premiĂšre vue et pas Ă©videntes. PremiĂšrement, si nous voulons crĂ©er une image avec une application ASP.NET Core, alors l'image d'exĂ©cution en aura besoin d'une autre: mcr.microsoft.com/dotnet/core/aspnet . Le paramĂštre avec une image de base non standard doit ĂȘtre enregistrĂ© dans la configuration du rĂ©fĂ©rentiel SolutionInfo.props et passĂ© en argument lors de l'assemblage. Nous avons Ă©galement facilitĂ© l'utilisation par le dĂ©veloppeur d'autres versions des images .NET Core: des aperçus, par exemple, ou mĂȘme des personnalisĂ©es (on ne sait jamais!).


DeuxiÚmement, la possibilité de "développer" le Dockerfile est encore plus intéressante, ayant fait partie des opérations dans un autre assemblage, dont le résultat sera pris comme base lors de la préparation de l'image d'exécution. Par exemple, certains de nos services utilisent JavaScript et Vue.js, dont nous préparerons le code dans une image distincte, en ajoutant simplement un tel Dockerfile «en expansion» au référentiel:


 ARG DOTNETCORE_VERSION=2.2 ARG ALPINE_VERSION= ARG RUNTIME_BASE=mcr.microsoft.com/dotnet/core/aspnet:${DOTNETCORE_VERSION}-alpine${ALPINE_VERSION} FROM node:alpine AS install WORKDIR /build COPY package.json . RUN npm install FROM install AS src COPY [".babelrc", ".eslintrc.js", ".stylelintrc", "./"] COPY ClientApp ./ClientApp FROM src AS publish RUN npm run build-prod FROM ${RUNTIME_BASE} AS appbase COPY --from=publish /build/wwwroot/ /app/wwwroot/ 

Collectons cette image avec la balise, que nous passerons à l'étape d'assemblage de l'image d'exécution du service ASP.NET comme argument à RUNTIME_BASE. Vous pouvez donc étendre l'assemblage autant que vous le souhaitez, y compris, vous pouvez paramétrer ce que vous ne pouvez pas simplement faire dans la docker build . Vous souhaitez paramétrer l'ajout de Volume? Facile:


 ARG DOTNETCORE_VERSION=2.2 ARG ALPINE_VERSION= ARG RUNTIME_BASE=mcr.microsoft.com/dotnet/core/aspnet:${DOTNETCORE_VERSION}-alpine${ALPINE_VERSION} FROM ${RUNTIME_BASE} AS runtime ARG VOLUME VOLUME ${VOLUME} 

Nous commençons l'assemblage de ce Dockerfile autant de fois que nous voulons ajouter de directives VOLUME. Nous utilisons l'image résultante comme base pour le service.


Exécution de tests


Au lieu d'exécuter des tests directement dans les étapes d'assemblage, il est plus correct et plus pratique de le faire dans un conteneur spécial «Test Runner». Présentant briÚvement l'essence de cette approche, je note qu'elle vous permet de:


  • Effectuez tous les lancements planifiĂ©s, mĂȘme si l'un d'eux se bloque;
  • Montez le rĂ©pertoire du systĂšme de fichiers hĂŽte dans le conteneur pour recevoir un rapport de test, ce qui est essentiel lors de la construction dans le systĂšme CI;
  • ExĂ©cutez les tests dans un environnement temporaire en transmettant le nom de son rĂ©seau au docker run --network <test_network_name> .

Le dernier paragraphe signifie que nous pouvons dĂ©sormais exĂ©cuter non seulement des tests unitaires, mais aussi des tests d'intĂ©gration. Nous dĂ©crivons l'environnement, par exemple, dans docker-compose.yaml , et l' docker-compose.yaml pour la build entiĂšre. Vous pouvez maintenant vĂ©rifier l'interaction avec la base de donnĂ©es ou notre autre service, et enregistrer les journaux d'eux au cas oĂč vous en auriez besoin pour l'analyse.


Nous vĂ©rifions toujours l'image d'exĂ©cution rĂ©sultante pour rĂ©ussir le contrĂŽle de santĂ©, qui est Ă©galement une sorte de test. Un environnement de test temporaire peut ĂȘtre utile ici si le service testĂ© a des dĂ©pendances sur son environnement.


Je note également que l'approche avec les conteneurs de dotnet build assemblés au stade de la dotnet build servira alors trÚs bien pour lancer la dotnet publish dotnet pack dotnet nuget push et la dotnet nuget push . Cela nous permettra d'enregistrer les artefacts d'assemblage localement.


DĂ©pendances Healthcheck et OS


Assez rapidement, il est devenu clair que nos services standardisĂ©s seraient toujours uniques Ă  leur maniĂšre. Ils peuvent avoir des exigences diffĂ©rentes pour les packages prĂ©installĂ©s du systĂšme d'exploitation Ă  l'intĂ©rieur de l'image et diffĂ©rentes façons de vĂ©rifier le contrĂŽle de santĂ©. Et si curl convient pour vĂ©rifier l'Ă©tat d'une application Web, alors pour un backend gRPC ou, en outre, un service sans tĂȘte, il sera inutile, et ce sera Ă©galement un package supplĂ©mentaire dans le conteneur.


Pour donner aux dĂ©veloppeurs la possibilitĂ© de personnaliser l'image et d'Ă©tendre sa configuration, nous utilisons l'accord sur plusieurs scripts spĂ©ciaux qui peuvent ĂȘtre redĂ©finis dans le rĂ©fĂ©rentiel:


 .scripts ├── healthcheck.sh ├── run.sh └── runtime-deps.sh 

Le script healthcheck.sh contient les commandes nécessaires pour vérifier l'état:


  • Pour le Web Ă  l'aide de curl:


     #!/bin/ash set –e curl -sIf -o /dev/null -w "%{http_code}\n" 127.0.0.1/health || exit 1 

  • Autres services utilisant notre propre utilitaire cli:


     #!/bin/ash set –e healthcheck || exit 1 


À l'aide de runtime-deps.sh , les dĂ©pendances sont installĂ©es et, si nĂ©cessaire, toutes les autres actions sur le systĂšme d'exploitation de base sont effectuĂ©es qui sont nĂ©cessaires au fonctionnement normal de l'application Ă  l'intĂ©rieur du conteneur. Exemples typiques:


  • Pour une application Web:


     #!/bin/ash apk add --no-cache curl icu-libs 

  • Pour le service gRPC:


     #!/bin/ash apk add --no-cache libc6-compat 


Ainsi, la maniÚre de gérer les dépendances et de vérifier l'état est standardisée, mais il y a de la place pour une certaine flexibilité. Quant à run.sh , c'est plus loin.


Script Entrypoint


Je suis sûr que tous ceux qui ont écrit au moins une fois leur Dockerfile se sont demandé quelle directive utiliser - CMD ou ENTRYPOINT . De plus, ces équipes ont également deux options de syntaxe, qui affectent de la maniÚre la plus dramatique le résultat. Je n'expliquerai pas la différence en détail, en répétant aprÚs ceux qui ont déjà tout clarifié . Je recommande simplement de se rappeler que dans 99% des situations, il est correct d'utiliser ENTRYPOINT et la syntaxe exec:


ENTRYPOINT ["/ chemin / vers / exécutable"]


Sinon, l'application lancée ne pourra pas traiter correctement les commandes du systÚme d'exploitation, telles que SIGTERM, etc., et vous pouvez également avoir des problÚmes sous la forme de processus zombies et de tout ce qui concerne le problÚme PID 1 . Mais que se passe-t-il si vous souhaitez démarrer le conteneur sans lancer l'application? Oui, vous pouvez remplacer le point d'entrée:
docker run --rm -it --entrypoint ash <image_name> <params>
Il n'a pas l'air trop confortable et intuitif, non? Mais il y a une bonne nouvelle: vous pouvez faire mieux! À savoir, utilisez un script de point d'entrĂ©e . Un tel script vous permet de rendre l'initialisation ( exemple ) arbitrairement complexe, le traitement des paramĂštres et tout ce que vous voulez.


Dans notre cas, par dĂ©faut, le scĂ©nario le plus simple, mais en mĂȘme temps fonctionnel, est utilisĂ©:


 #!/bin/sh set -e if [ ! -z "$1" ] && $(command -v $1 >/dev/null 2>&1) then exec $@ else exec /usr/bin/dotnet /app/${ENTRY_PROJECT}.dll $@ fi 

Il vous permet de contrĂŽler le lancement du conteneur de maniĂšre trĂšs intuitive:
docker run <image> env - exécute simplement env dans l'image, montrant les variables d'environnement.
docker run <image> -param1 value1 - démarre le service avec les arguments spécifiés.


Séparément, vous devez faire attention à la commande exec : sa présence avant d'appeler l'application exécutable lui fournira le PID 1 convoité dans votre conteneur.


Quoi d'autre


Bien sûr, sur plus d'un an et demi d'utilisation, le systÚme de build a accumulé de nombreuses fonctionnalités différentes. En plus de gérer les conditions de lancement des différentes étapes, en travaillant avec le stockage des artefacts, la gestion des versions et d'autres fonctionnalités, notre «standard» du conteneur s'est également développé. Il était rempli d'attributs importants qui le rendent plus prévisible et plus pratique sur le plan administratif:


  • Toutes les Ă©tiquettes d'images nĂ©cessaires sont installĂ©es: versions, numĂ©ros de rĂ©vision, liens vers la documentation, auteur et autres.
  • Dans le conteneur d'exĂ©cution, la configuration NLog est redĂ©finie afin qu'aprĂšs la publication, tous les journaux soient immĂ©diatement prĂ©sentĂ©s sous une forme structurĂ©e Ă  l'aide de json, dont la version est versionnĂ©e.
  • Les rĂšgles d'analyse statique et toutes les autres normes sont automatiquement mises Ă  jour.

Un tel outil, bien sĂ»r, peut toujours ĂȘtre amĂ©liorĂ© et dĂ©veloppĂ©. Tout dĂ©pend des besoins et de l'imagination. Par exemple, en plus de tout, il Ă©tait possible de regrouper des utilitaires cli supplĂ©mentaires dans une image. Le dĂ©veloppeur peut facilement les mettre dans l'image, en spĂ©cifiant dans le fichier de configuration uniquement le nom de l'utilitaire requis et le nom du projet .NET Ă  partir duquel il doit ĂȘtre assemblĂ© (par exemple, notre healthcheck ).


Conclusion


DĂ©crit ici n'est qu'une partie d'une approche intĂ©grĂ©e de la normalisation. Les services eux-mĂȘmes, qui sont crĂ©Ă©s Ă  partir de nos modĂšles, sont restĂ©s en arriĂšre-plan, et ils sont donc assez unifiĂ©s par de nombreux critĂšres, tels qu'une approche unique de la configuration, des mĂ©thodes courantes pour accĂ©der aux mĂ©triques, la gĂ©nĂ©ration de code, etc. . , .


, Linux , - . , , . , , , Code Style, , .


, ! , « », , . , Docker .

Source: https://habr.com/ru/post/fr468683/


All Articles