Nous créons une tâche de déploiement dans GKE sans plugins, SMS ni inscription. Jetons un coup d'œil sous la veste de Jenkins

Tout a commencé lorsque le chef d’équipe d’une de nos équipes de développement nous a demandé de tester sa nouvelle application, conteneurisée la veille. Je l'ai posté. Après environ 20 minutes, une demande de mise à jour de l'application a été reçue, car une chose très nécessaire y avait été ajoutée. J'ai renouvelé. Après encore quelques heures... eh bien, vous pouvez deviner ce qui a commencé à se passer ensuite...

Je dois admettre que je suis assez paresseux (ne l'ai-je pas admis plus tôt ? Non ?), et étant donné que les chefs d'équipe ont accès à Jenkins, dans lequel nous avons tous des CI/CD, j'ai pensé : laissez-le se déployer en tant que autant qu'il veut ! Je me suis souvenu d'une blague : donnez un poisson à un homme et il mangera pendant une journée ; appelez une personne Fed et elle sera Fed toute sa vie. Et est allé jouer des tours au travail, qui serait capable de déployer un conteneur contenant l'application de n'importe quelle version construite avec succès dans Kuber et de lui transférer toutes les valeurs ENV (mon grand-père, philologue, ancien professeur d'anglais, tournait maintenant son doigt vers sa tempe et me regardait de manière très expressive après avoir lu cette phrase).

Donc, dans cette note, je vais vous raconter comment j'ai appris :

  1. Mettre à jour dynamiquement les tâches dans Jenkins à partir du travail lui-même ou d'autres travaux ;
  2. Connectez-vous à la console cloud (Cloud shell) à partir d'un nœud sur lequel l'agent Jenkins est installé ;
  3. Déployez la charge de travail sur Google Kubernetes Engine.


En fait, je suis bien sûr quelque peu fallacieux. On suppose que vous disposez d'au moins une partie de l'infrastructure dans le cloud de Google et que, par conséquent, vous en êtes l'utilisateur et, bien sûr, vous disposez d'un compte GCP. Mais ce n’est pas le sujet de cette note.

Ceci est ma prochaine aide-mémoire. Je ne veux écrire de telles notes que dans un cas : j'ai été confronté à un problème, je ne savais pas au départ comment le résoudre, la solution n'était pas recherchée sur Google toute faite, alors je l'ai recherchée sur Google en partie et j'ai finalement résolu le problème. Et pour qu'à l'avenir, quand j'oublierai comment je l'ai fait, je n'aie plus besoin de tout rechercher sur Google pièce par pièce et de le compiler ensemble, j'écris moi-même de telles aide-mémoire.

Avertissement: 1. La note a été écrite « pour moi », pour le rôle meilleures pratiques Ne s'applique pas. Je suis heureux de lire les options « il aurait été préférable de procéder de cette façon » dans les commentaires.
2. Si la partie appliquée de la note est considérée comme du sel, alors, comme toutes mes notes précédentes, celle-ci est une solution saline faible.

Mise à jour dynamique des paramètres de travail dans Jenkins

Je prévois votre question : qu'est-ce que la mise à jour dynamique des tâches a à voir avec cela ? Entrez manuellement la valeur du paramètre string et c’est parti !

Je réponds : je suis vraiment paresseux, je n'aime pas quand ils se plaignent : Misha, le déploiement plante, tout est parti ! Vous commencez à chercher et il y a une faute de frappe dans la valeur d'un paramètre de lancement de tâche. Par conséquent, je préfère tout faire aussi efficacement que possible. S'il est possible d'empêcher l'utilisateur de saisir directement des données en lui donnant à la place une liste de valeurs parmi lesquelles choisir, alors j'organise la sélection.

Le plan est le suivant : nous créons un travail dans Jenkins, dans lequel, avant de lancer, nous pourrions sélectionner une version dans la liste, spécifier les valeurs des paramètres transmis au conteneur via ENV, puis il collecte le conteneur et le place dans Container Registry. Ensuite, à partir de là, le conteneur est lancé dans cuber comme charge de travail avec les paramètres spécifiés dans le travail.

Nous ne considérerons pas le processus de création et de mise en place d'un emploi chez Jenkins, c'est hors sujet. Nous supposerons que la tâche est prête. Pour implémenter une liste mise à jour avec des versions, nous avons besoin de deux choses : une liste de sources existante avec des numéros de version a priori valides et une variable comme Paramètre de choix dans la tâche. Dans notre exemple, nommons la variable CONSTRUCTION_VERSION, nous ne nous y attarderons pas en détail. Mais regardons de plus près la liste des sources.

Il n'y a pas beaucoup d'options. Deux choses me sont immédiatement venues à l’esprit :

  • Utilisez l'API d'accès à distance que Jenkins propose à ses utilisateurs ;
  • Demandez le contenu du dossier du référentiel distant (dans notre cas, il s'agit de JFrog Artifactory, ce qui n'est pas important).

API d'accès à distance Jenkins

Selon l’excellente tradition établie, je préférerais éviter les longues explications.
Je me permettrai seulement une traduction gratuite d'un morceau du premier paragraphe première page de la documentation de l'API:

Jenkins fournit une API pour un accès à distance lisible par machine à ses fonctionnalités. <…> L'accès à distance est proposé dans un style de type REST. Cela signifie qu'il n'y a pas de point d'entrée unique à toutes les fonctionnalités, mais plutôt une URL du type ".../api/", Où "..." désigne l'objet auquel les fonctionnalités de l'API sont appliquées.

En d'autres termes, si la tâche de déploiement dont nous parlons actuellement est disponible sur http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build, les sifflets de l'API pour cette tâche sont disponibles sur http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/

Ensuite, nous avons le choix sous quelle forme recevoir le résultat. Concentrons-nous sur XML, puisque l'API permet uniquement le filtrage dans ce cas.

Essayons simplement d'obtenir une liste de toutes les exécutions de tâches. Nous ne nous intéressons qu'au nom de l'assembly (Afficher un nom) et son résultat (résultat):

http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]

Got it?

Filtrons maintenant uniquement les exécutions qui aboutissent au résultat SUCCÈS. Utilisons l'argument &exclure et en paramètre nous lui passerons le chemin vers une valeur non égale à SUCCÈS. Oui oui. Une double négation est une déclaration. Nous excluons tout ce qui ne nous intéresse pas :

http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result!='SUCCESS']

Capture d'écran de la liste des succès
Nous créons une tâche de déploiement dans GKE sans plugins, SMS ni inscription. Jetons un coup d'œil sous la veste de Jenkins

Bon, juste pour s'amuser, assurons-nous que le filtre ne nous a pas trompé (les filtres ne mentent jamais !) et affichons une liste de ceux « qui n'ont pas réussi » :

http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result='SUCCESS']

Capture d'écran de la liste des échecs
Nous créons une tâche de déploiement dans GKE sans plugins, SMS ni inscription. Jetons un coup d'œil sous la veste de Jenkins

Liste des versions d'un dossier sur un serveur distant

Il existe une deuxième façon d'obtenir une liste de versions. Je l'aime encore plus que d'accéder à l'API Jenkins. Eh bien, parce que si l'application a été créée avec succès, cela signifie qu'elle a été empaquetée et placée dans le référentiel dans le dossier approprié. Par exemple, un référentiel est le stockage par défaut des versions de travail des applications. Comme. Eh bien, demandons-lui quelles versions sont stockées. Nous allons curl, grep et awk le dossier distant. Si quelqu'un est intéressé par le oneliner, alors il est sous le spoiler.

Commande sur une seule ligne
Attention à deux choses : je passe les détails de connexion dans l'en-tête et je n'ai pas besoin de toutes les versions du dossier, et je sélectionne uniquement celles qui ont été créées dans le mois. Modifiez la commande en fonction de vos réalités et de vos besoins :

curl -H "X-JFrog-Art-Api:VeryLongAPIKey" -s http://arts.myre.po/artifactory/awesomeapp/ | sed 's/a href=//' | grep "$(date +%b)-$(date +%Y)|$(date +%b --date='-1 month')-$(date +%Y)" | awk '{print $1}' | grep -oP '>K[^/]+' )

Configuration des tâches et du fichier de configuration des tâches dans Jenkins

Nous avons trouvé la source de la liste des versions. Incorporons maintenant la liste résultante dans la tâche. Pour moi, la solution évidente était d’ajouter une étape dans la tâche de création d’application. L'étape qui serait exécutée si le résultat était « succès ».

Ouvrez les paramètres de la tâche d'assemblage et faites défiler jusqu'en bas. Cliquez sur les boutons : Ajouter une étape de construction -> Étape conditionnelle (unique). Dans les paramètres de l'étape, sélectionnez la condition État de construction actuel, définissez la valeur SUCCÈS, l'action à effectuer en cas de succès Exécuter la commande shell.

Et maintenant la partie amusante. Jenkins stocke les configurations de tâches dans des fichiers. Au format XML. Le long du chemin http://путь-до-задания/config.xml En conséquence, vous pouvez télécharger le fichier de configuration, le modifier si nécessaire et le remettre là où vous l'avez obtenu.

N'oubliez pas que nous avons convenu ci-dessus que nous créerions un paramètre pour la liste des versions CONSTRUCTION_VERSION?

Téléchargeons le fichier de configuration et jetons un coup d'œil à l'intérieur. Juste pour s'assurer que le paramètre est en place et du type souhaité.

Capture d'écran sous spoiler.

Votre fragment config.xml devrait avoir le même aspect. Sauf que le contenu de l'élément choix manque encore
Nous créons une tâche de déploiement dans GKE sans plugins, SMS ni inscription. Jetons un coup d'œil sous la veste de Jenkins

Es-tu sûr? Voilà, écrivons un script qui sera exécuté si la construction réussit.
Le script recevra une liste de versions, téléchargera le fichier de configuration, y écrira la liste des versions à l'endroit dont nous avons besoin, puis le remettra. Oui. C'est exact. Écrivez une liste de versions en XML à l'endroit où se trouve déjà une liste de versions (ce sera dans le futur, après le premier lancement du script). Je sais qu'il y a encore de fervents fans des expressions régulières dans le monde. Je ne leur appartient pas. Se il vous plaît installer xmlstarler à la machine sur laquelle la configuration sera modifiée. Il me semble que ce n'est pas un si gros prix à payer pour éviter d'éditer du XML à l'aide de sed.

Sous le spoiler, je présente le code qui exécute la séquence ci-dessus dans son intégralité.

Écrivez une liste de versions d'un dossier sur le serveur distant vers la configuration

#!/bin/bash
############## Скачиваем конфиг
curl -X GET -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml -o appConfig.xml

############## Удаляем и заново создаем xml-элемент для списка версий
xmlstarlet ed --inplace -d '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' appConfig.xml

xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]' --type elem -n a appConfig.xml

xmlstarlet ed --inplace --insert '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a' --type attr -n class -v string-array appConfig.xml

############## Читаем в массив список версий из репозитория
readarray -t vers < <( curl -H "X-JFrog-Art-Api:Api:VeryLongAPIKey" -s http://arts.myre.po/artifactory/awesomeapp/ | sed 's/a href=//' | grep "$(date +%b)-$(date +%Y)|$(date +%b --date='-1 month')-$(date +%Y)" | awk '{print $1}' | grep -oP '>K[^/]+' )

############## Пишем массив элемент за элементом в конфиг
printf '%sn' "${vers[@]}" | sort -r | 
                while IFS= read -r line
                do
                    xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' --type elem -n string -v "$line" appConfig.xml
                done

############## Кладем конфиг взад
curl -X POST -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml --data-binary @appConfig.xml

############## Приводим рабочее место в порядок
rm -f appConfig.xml

Si vous préférez l'option d'obtenir des versions de Jenkins et que vous êtes aussi paresseux que moi, alors sous le spoiler se trouve le même code, mais une liste de Jenkins :

Écrivez une liste de versions de Jenkins dans la configuration
Gardez ceci à l’esprit : le nom de mon assembly se compose d’un numéro de séquence et d’un numéro de version, séparés par deux points. En conséquence, awk supprime la partie inutile. Pour vous-même, modifiez cette ligne en fonction de vos besoins.

#!/bin/bash
############## Скачиваем конфиг
curl -X GET -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml -o appConfig.xml

############## Удаляем и заново создаем xml-элемент для списка версий
xmlstarlet ed --inplace -d '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' appConfig.xml

xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]' --type elem -n a appConfig.xml

xmlstarlet ed --inplace --insert '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a' --type attr -n class -v string-array appConfig.xml

############## Пишем в файл список версий из Jenkins
curl -g -X GET -u username:apiKey 'http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result!=%22SUCCESS%22]&pretty=true' -o builds.xml

############## Читаем в массив список версий из XML
readarray vers < <(xmlstarlet sel -t -v "freeStyleProject/allBuild/displayName" builds.xml | awk -F":" '{print $2}')

############## Пишем массив элемент за элементом в конфиг
printf '%sn' "${vers[@]}" | sort -r | 
                while IFS= read -r line
                do
                    xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' --type elem -n string -v "$line" appConfig.xml
                done

############## Кладем конфиг взад
curl -X POST -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml --data-binary @appConfig.xml

############## Приводим рабочее место в порядок
rm -f appConfig.xml

En théorie, si vous avez testé le code écrit sur la base des exemples ci-dessus, alors dans la tâche de déploiement, vous devriez déjà avoir une liste déroulante avec les versions. C'est comme dans la capture d'écran sous le spoiler.

Liste des versions correctement complétée
Nous créons une tâche de déploiement dans GKE sans plugins, SMS ni inscription. Jetons un coup d'œil sous la veste de Jenkins

Si tout a fonctionné, copiez-collez le script dans Exécuter la commande shell et enregistrez les modifications.

Connexion à Cloud Shell

Nous avons des collecteurs dans des conteneurs. Nous utilisons Ansible comme outil de livraison d'applications et gestionnaire de configuration. Par conséquent, lorsqu'il s'agit de créer des conteneurs, trois options viennent à l'esprit : installer Docker dans Docker, installer Docker sur une machine exécutant Ansible ou créer des conteneurs dans une console cloud. Nous avons convenu de garder le silence sur les plugins pour Jenkins dans cet article. Souviens-toi?

J'ai décidé : eh bien, puisque les conteneurs « prêts à l'emploi » peuvent être collectés dans la console cloud, alors pourquoi s'embêter ? Gardez-le propre, n'est-ce pas ? Je souhaite collecter les conteneurs Jenkins dans la console cloud, puis les lancer dans le cuber à partir de là. De plus, Google dispose de canaux très riches au sein de son infrastructure, ce qui aura un effet bénéfique sur la rapidité de déploiement.

Pour vous connecter à la console cloud, vous avez besoin de deux choses : gcloud et les droits d'accès à API Google Cloud pour l'instance de VM avec laquelle cette même connexion sera établie.

Pour ceux qui envisagent de se connecter du tout depuis le cloud Google
Google autorise la possibilité de désactiver l'autorisation interactive dans ses services. Cela vous permettra de vous connecter à la console même à partir d'une machine à café, si elle exécute * nix et dispose elle-même d'une console.

S'il est nécessaire que je traite cette question plus en détail dans le cadre de cette note, écrivez dans les commentaires. Si nous obtenons suffisamment de votes, j’écrirai une mise à jour sur ce sujet.

Le moyen le plus simple d’accorder des droits consiste à utiliser l’interface Web.

  1. Arrêtez l'instance de VM à partir de laquelle vous vous connecterez ensuite à la console cloud.
  2. Ouvrez les détails de l'instance et cliquez sur Изменить.
  3. Tout en bas de la page, sélectionnez la portée d'accès à l'instance Accès complet à toutes les API Cloud.

    Capture d'écran
    Nous créons une tâche de déploiement dans GKE sans plugins, SMS ni inscription. Jetons un coup d'œil sous la veste de Jenkins

  4. Enregistrez vos modifications et lancez l'instance.

Une fois le chargement de la VM terminé, connectez-vous via SSH et assurez-vous que la connexion s'effectue sans erreur. Utilisez la commande :

gcloud alpha cloud-shell ssh

Une connexion réussie ressemble à ceci
Nous créons une tâche de déploiement dans GKE sans plugins, SMS ni inscription. Jetons un coup d'œil sous la veste de Jenkins

Déployer sur GKE

Puisque nous nous efforçons par tous les moyens de passer complètement à IaC (Infrastucture as a Code), nos fichiers Docker sont stockés dans Git. C'est d'une part. Et le déploiement dans Kubernetes est décrit par un fichier yaml, qui n'est utilisé que par cette tâche, qui elle-même est également comme du code. Cela vient de l'autre côté. En général, je veux dire, le plan est le suivant :

  1. On prend les valeurs des variables CONSTRUCTION_VERSION et, éventuellement, les valeurs des variables qui seront transmises ENV.
  2. Téléchargez le fichier docker depuis Git.
  3. Générez du yaml pour le déploiement.
  4. Nous téléchargeons ces deux fichiers via scp sur la console cloud.
  5. Nous y construisons un conteneur et le plaçons dans le registre des conteneurs
  6. Nous appliquons le fichier de déploiement de charge au cuber.

Soyons plus précis. Une fois que nous avons commencé à parler ENV, alors supposons que nous devions transmettre les valeurs de deux paramètres : PARAM1 и PARAM2. Nous ajoutons leur tâche de déploiement, tapez - Paramètre de chaîne.

Capture d'écran
Nous créons une tâche de déploiement dans GKE sans plugins, SMS ni inscription. Jetons un coup d'œil sous la veste de Jenkins

Nous générerons yaml avec une simple redirection echo déposer. On suppose, bien sûr, que vous avez dans votre fichier docker PARAM1 и PARAM2que le nom de la charge sera application géniale, et le conteneur assemblé avec l'application de la version spécifiée se trouve dans Registre des conteneurs le long du chemin gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION$BUILD_VERSION vient d'être sélectionné dans la liste déroulante.

Liste des équipes

touch deploy.yaml
echo "apiVersion: apps/v1" >> deploy.yaml
echo "kind: Deployment" >> deploy.yaml
echo "metadata:" >> deploy.yaml
echo "  name: awesomeapp" >> deploy.yaml
echo "spec:" >> deploy.yaml
echo "  replicas: 1" >> deploy.yaml
echo "  selector:" >> deploy.yaml
echo "    matchLabels:" >> deploy.yaml
echo "      run: awesomeapp" >> deploy.yaml
echo "  template:" >> deploy.yaml
echo "    metadata:" >> deploy.yaml
echo "      labels:" >> deploy.yaml
echo "        run: awesomeapp" >> deploy.yaml
echo "    spec:" >> deploy.yaml
echo "      containers:" >> deploy.yaml
echo "      - name: awesomeapp" >> deploy.yaml
echo "        image: gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION:latest" >> deploy.yaml
echo "        env:" >> deploy.yaml
echo "        - name: PARAM1" >> deploy.yaml
echo "          value: $PARAM1" >> deploy.yaml
echo "        - name: PARAM2" >> deploy.yaml
echo "          value: $PARAM2" >> deploy.yaml

Agent Jenkins après la connexion en utilisant gcloud alpha cloud-shell ssh le mode interactif n'est pas disponible, nous envoyons donc des commandes à la console cloud en utilisant le paramètre --commande.

Nous nettoyons le dossier personnel dans la console cloud de l'ancien fichier docker :

gcloud alpha cloud-shell ssh --command="rm -f Dockerfile"

Placez le fichier docker fraîchement téléchargé dans le dossier personnel de la console cloud à l'aide de scp :

gcloud alpha cloud-shell scp localhost:./Dockerfile cloudshell:~

Nous collectons, étiquetons et transmettons le conteneur au registre des conteneurs :

gcloud alpha cloud-shell ssh --command="docker build -t awesomeapp-$BUILD_VERSION ./ --build-arg BUILD_VERSION=$BUILD_VERSION --no-cache"
gcloud alpha cloud-shell ssh --command="docker tag awesomeapp-$BUILD_VERSION gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION"
gcloud alpha cloud-shell ssh --command="docker push gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION"

Nous faisons de même avec le fichier de déploiement. Veuillez noter que les commandes ci-dessous utilisent des noms fictifs du cluster sur lequel le déploiement a lieu (cluster AWSM) et le nom du projet (projet génial), où se trouve le cluster.

gcloud alpha cloud-shell ssh --command="rm -f deploy.yaml"
gcloud alpha cloud-shell scp localhost:./deploy.yaml cloudshell:~
gcloud alpha cloud-shell ssh --command="gcloud container clusters get-credentials awsm-cluster --zone us-central1-c --project awesome-project && 
kubectl apply -f deploy.yaml"

Nous exécutons la tâche, ouvrons la sortie de la console et espérons voir l'assemblage réussi du conteneur.

Capture d'écran
Nous créons une tâche de déploiement dans GKE sans plugins, SMS ni inscription. Jetons un coup d'œil sous la veste de Jenkins

Et puis le déploiement réussi du conteneur assemblé

Capture d'écran
Nous créons une tâche de déploiement dans GKE sans plugins, SMS ni inscription. Jetons un coup d'œil sous la veste de Jenkins

J'ai délibérément ignoré le paramètre Entrée. Pour une raison simple : une fois que vous l'avez configuré charge de travail avec un nom donné, il restera opérationnel, quel que soit le nombre de déploiements sous ce nom que vous effectuez. Eh bien, en général, cela dépasse un peu le cadre de l'histoire.

Au lieu de conclusions

Toutes les étapes ci-dessus n'auraient probablement pas pu être effectuées, mais simplement installé un plugin pour Jenkins, leur muuulion. Mais pour une raison quelconque, je n'aime pas les plugins. Eh bien, plus précisément, je n'y recourt que par désespoir.

Et j'aime juste aborder un nouveau sujet pour moi. Le texte ci-dessus est aussi un moyen de partager les découvertes que j'ai faites en résolvant le problème décrit au tout début. Partagez avec ceux qui, comme lui, ne sont pas du tout un loup terrible en devops. Si mes découvertes aident au moins quelqu'un, je serai heureux.

Source: habr.com

Ajouter un commentaire