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>Sandbox.SampleService</Product> <BaseVersion>0.0.0</BaseVersion> <EntryProject>Sandbox.SampleService.Bootstrap</EntryProject> <ExposedPort>4000/tcp</ExposedPort> <GlobalizationInvariant>false</GlobalizationInvariant> <RepositoryUrl>https://bitbucket.contoso.com/projects/SND/repos/sandbox.sampleservice/</RepositoryUrl> <DocumentationUrl>https://bitbucket.contoso.com/projects/SND/repos/sandbox.sampleservice/browse/README.md</DocumentationUrl> <Authors>User Name <username@contoso.com></Authors> <Description>The sample service for demo purposes.</Description> <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> <GenerateDocumentationFile>true</GenerateDocumentationFile> <LangVersion>latest</LangVersion> <BaseVersion Condition="'$(BaseVersion)' == ''">0.0.0</BaseVersion> <BuildNumber Condition="'$(BuildNumber)' == ''">0</BuildNumber> <BuildNumber>$([System.String]::Format('{0:0000}',$(BuildNumber)))</BuildNumber> <VersionSuffix Condition="'$(VersionSuffix)' == ''">local</VersionSuffix> <VersionSuffix Condition="'$(VersionSuffix)' == 'prod'"></VersionSuffix> <VersionPrefix>$(BaseVersion).$(BuildNumber)</VersionPrefix> <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:
Ă 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:
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Ă©:
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 .