Wie man einen 100-Gitlab-Job in 100 Zeilen auf Jsonnet beschreibt

In Fortsetzung des vorherigen Artikels zu Bereitstellungstools in Kubernetes möchte ich Ihnen erläutern, wie Sie Jsonnet verwenden können, um die Beschreibung des Jobs in Ihrer Datei .gitlab-ci.yml zu vereinfachen



Gegeben


Es gibt eine Monorepa, in der:


  • 10 Andockdateien
  • 30 beschriebene Bereitstellungen
  • 3 Umgebungen: Entwicklung , Inszenierung und Produktion

Herausforderung


Richten Sie eine Pipeline ein:


  • Das Erstellen von Docker-Images sollte durch Hinzufügen eines Git-Tags mit einer Version erfolgen.
  • Jeder Bereitstellungsvorgang sollte beim Senden an den Umgebungszweig und nur durch Ändern von Dateien in einem bestimmten Verzeichnis ausgeführt werden
  • Jede Umgebung verfügt über einen eigenen Gitlab-Runner mit einem separaten Tag, der nur die Bereitstellung in der Umgebung durchführt.
  • Nicht alle Anwendungen sollten in jeder der Umgebungen bereitgestellt werden, wir müssen die Pipeline beschreiben, um Ausnahmen vornehmen zu können.
  • Einige Bereitstellungen verwenden das Git-Submodul und sollten mit der festgelegten Variablen GIT_SUBMODULE_STRATEGY=normal

All dies zu beschreiben mag wie eine echte Hölle erscheinen, aber wir verzweifeln nicht und mit Jsonnet bewaffnet werden wir dies leicht und natürlich tun.


Lösung


gitlab-ci.yml verfügt über integrierte Funktionen zum Reduzieren der Beschreibung von wiederholten Jobs, die Sie beispielsweise erweitern oder einschließen können , bietet jedoch kein vollständiges Templating, sodass wir nicht die präzisesten und effizientesten beschreiben können.


Um dieses Problem zu lösen, empfehle ich die Verwendung von jsonnet, mit dem Sie die Code-Wiederholung bei der Beschreibung von Datenstrukturen fast vollständig beseitigen können.


Wenn Sie mit jsonnet arbeiten, empfehle ich dringend, ein Plugin für Ihren Editor zu installieren

Für vim gibt es beispielsweise ein vim-jsonnet-Plug-in , das die Syntaxhervorhebung aktiviert und bei jedem Speichern automatisch jsonnet fmt ausführt (dazu muss jsonnet installiert sein).

Schauen wir uns die Struktur unseres Repositorys an:


 . ├── deploy │  ├── analyse │  ├── basin │  ├── brush │  ├── copper │  ├── dinner │  ├── dirty │  ├── drab │  ├── drunk │  ├── education │  ├── fanatical │  ├── faulty │  ├── guarantee │  ├── guitar │  ├── hall │  ├── harmonious │  ├── history │  ├── iron │  ├── maniacal │  ├── mist │  ├── nine │  ├── pleasant │  ├── polish │  ├── receipt │  ├── shop │  ├── smelly │  ├── solid │  ├── stroke │  ├── thunder │  ├── ultra │  └── yarn └── dockerfiles ├── dinner ├── drunk ├── fanatical ├── guarantee ├── guitar ├── harmonious ├── shop ├── smelly ├── thunder └── yarn 

Docker-Images werden mit Kaniko erstellt


Die Bereitstellung von Anwendungen im Cluster erfolgt mit qbec . Jede Anwendung wird für drei verschiedene Umgebungen beschrieben. Um Änderungen am Cluster zu übernehmen, ist es ausreichend, Folgendes auszuführen:


 qbec apply <environment> --root deploy/<app> --yes 

wo:


  • <app> - der Name unserer Anwendung
  • <environment> ist eine unserer Umgebungen: devel , stage oder prod .

Am Ende sollten unsere Jobs so aussehen:


Montage:


 build:{{ image }}: stage: build tags: - build image: name: gcr.io/kaniko-project/executor:debug entrypoint: [""] script: - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dockerfiles/{{ image }}/Dockerfile --destination $CI_REGISTRY_IMAGE/{{ image }}:$CI_COMMIT_TAG only: refs: - tags 

Wobei anstelle von {{ image }} der Verzeichnisname aus den Andockdateien ersetzt wird


Bereitstellen:


 deploy:{{ environment }}:{{ app }}: stage: deploy tags: - {{ environment }} script: - qbec apply {{ environment }} --root deploy/{{ app }} --force:k8s-context __incluster__ --wait --yes only: changes: - deploy/{{ app }}/**/* refs: - {{ environment }} 

Wobei anstelle von {{ app }} der Verzeichnisname von deploy ersetzt wird,
und anstelle von {{ environment }} - der Name der Umgebung, in der Sie implementieren möchten.


Beschreiben wir die Prototypen unserer Jobs als Objekte in einer separaten lib / jobs.jsonnet


 { //    docker- dockerImage(name):: { tags: ['build'], stage: 'build', image: { name: 'gcr.io/kaniko-project/executor:debug-v0.15.0', entrypoint: [''], }, script: [ 'echo "{\\"auths\\":{\\"$CI_REGISTRY\\":{\\"username\\":\\"$CI_REGISTRY_USER\\",\\"password\\":\\"$CI_REGISTRY_PASSWORD\\"}}}" > /kaniko/.docker/config.json', '/kaniko/executor --cache --context $CI_PROJECT_DIR/dockerfiles/' + name + ' --dockerfile $CI_PROJECT_DIR/dockerfiles/' + name + '/Dockerfile --destination $CI_REGISTRY_IMAGE/' + name + ':$CI_COMMIT_TAG --build-arg VERSION=$CI_COMMIT_TAG', ], }, //    qbec- qbecApp(name): { stage: 'deploy', script: [ 'qbec apply $CI_COMMIT_REF_NAME --root deploy/' + name + ' --force:k8s-context __incluster__ --wait --yes', ], only: { changes: [ 'deploy/' + name + '/**/*', ], }, }, } 

Bitte beachten Sie, dass ich absichtlich keine refs und tags , um unsere Bibliothek flexibler zu gestalten und Ihnen die Funktionen von jsonnet vollständig zu demonstrieren. Wir werden sie später aus der Hauptdatei hinzufügen.


Nun werden wir unser .gitlab-ci.jsonnet beschreiben :


 //    local jobs = import 'lib/jobs.libsonnet'; //    local ref(x) = { only+: { refs: [x] } }; local tag(x) = { tags: [x] }; local submodule(x) = { variables+: { GIT_SUBMODULE_STRATEGY: x } }; { // C docker- ['build:' + x]: jobs.dockerImage(x) + tag('build') + ref('tags') for x in [ 'dinner', 'drunk', 'fanatical', 'guarantee', 'guitar', 'harmonious', 'shop', 'smelly', 'thunder', 'yarn', ] } + { //         'prod' ['deploy:prod:' + x]: jobs.qbecApp(x) + tag('prod') + ref('prod') for x in [ 'dinner', 'hall', ] } + { //   git-submodule ['deploy:' + env + ':' + app]: jobs.qbecApp(app) + tag(env) + ref(env) + submodule('normal') for env in ['devel', 'stage', 'prod'] for app in [ 'brush', 'fanatical', 'history', 'shop', ] } + { //    ['deploy:' + env + ':' + app]: jobs.qbecApp(app) + tag(env) + ref(env) for env in ['devel', 'stage', 'prod'] for app in [ 'analyse', 'basin', 'copper', 'dirty', 'drab', 'drunk', 'education', 'faulty', 'guarantee', 'guitar', 'harmonious', 'iron', 'maniacal', 'mist', 'nine', 'pleasant', 'polish', 'receipt', 'smelly', 'solid', 'stroke', 'thunder', 'ultra', 'yarn', ] } 

Beachten Sie die Funktionen ref , tag und submodule am Anfang der Datei, mit denen Sie ein überschreibendes Objekt erstellen können.


Eine kleine Erklärung: Wenn Sie " +: " anstelle von " : " zum Überschreiben von Objekten verwenden, können Sie einem vorhandenen Objekt oder einer vorhandenen Liste einen Wert hinzufügen.


Zum Beispiel " : " für refs :


 local job = { script: ['echo 123'], only: { refs: ['tags'] }, }; local ref(x) = { only+: { refs: [x] } }; job + ref('prod') 

wird zurückkehren:


 { "only": { "refs": [ "prod" ] }, "script": [ "echo 123" ] } 

Und hier ist das " +: " für refs :


 local job = { script: ['echo 123'], only: { refs: ['tags'] }, }; local ref(x) = { only+: { refs+: [x] } }; job + ref('prod') 

wird zurückkehren:


 { "only": { "refs": [ "prod", "tags" ] }, "script": [ "echo 123" ] } 

Wie Sie sehen, können Sie mit Jsonnet Ihre Objekte sehr effizient beschreiben und zusammenführen. Am Ausgang erhalten Sie immer fertiges JSON, das wir sofort in unsere Datei .gitlab-ci.yml schreiben können:


 jsonnet .gitlab-ci.jsonnet > .gitlab-ci.yml 

Überprüfen Sie die Anzahl der Zeilen:


 # wc -l .gitlab-ci.jsonnet lib/jobs.libsonnet .gitlab-ci.yml 77 .gitlab-ci.jsonnet 24 lib/jobs.libsonnet 1710 .gitlab-ci.yml 

Meiner Meinung nach ist es sehr gut!


Sie können weitere Beispiele sehen und Jsonnet direkt auf der offiziellen Website fühlen: jsonnet.org
Wenn Sie, wie ich, wie Jsonnet, dann schließen Sie sich unserer Gruppe im Telegramm t.me/jsonnet_ru an

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


All Articles