Dynamische Erstellung und Bereitstellung von Docker-Images mit werf am Beispiel einer Website für versionierte Dokumentation

Wir haben bereits mehrfach über unser GitOps-Tool berichtet, werfund diesmal möchten wir unsere Erfahrung beim Erstellen der Website mit der Dokumentation des Projekts selbst teilen — werf.io (die russische Version ist — ru.werf.io). Es handelt sich um eine ganz gewöhnliche statische Website, deren Aufbau jedoch interessant ist, da sie mit einer dynamischen Anzahl von Artefakten erstellt wurde.

Dynamische Erstellung und Bereitstellung von Docker-Images mit werf am Beispiel einer Website für versionierte Dokumentation

Wir werden nicht auf die Details der Website-Struktur eingehen: die Generierung des allgemeinen Menüs für alle Versionen, Seiten mit Informationen über Releases usw. Stattdessen konzentrieren wir uns auf die Fragen und Besonderheiten des dynamischen Builds und ein wenig auf die begleitenden CI/CD-Prozesse.

Einführung: wie die Website strukturiert ist

Zunächst einmal wird die Dokumentation zu werf zusammen mit dem Code aufbewahrt. Das stellt bestimmte Anforderungen an die Entwicklung, die insgesamt über den Rahmen dieses Artikels hinausgehen, aber zumindest kann gesagt werden, dass:

  • Neue Funktionen von werf sollten nicht eingeführt werden, ohne die Dokumentation zu aktualisieren, und umgekehrt bedeuten Änderungen in der Dokumentation die Veröffentlichung einer neuen Version von werf;
  • Das Projekt hat eine recht intensive Entwicklung: Neue Versionen können mehrmals täglich veröffentlicht werden;
  • Jegliche manuelle Operationen beim Deployen der Website mit einer neuen Version der Dokumentation sind mindestens mühsam;
  • Im Projekt wird der Ansatz der semantischen Versionierung, mit 5 Stabilitätskanälen, verfolgt. Der Veröffentlichungsprozess sieht vor, dass Versionen nacheinander durch die Kanäle in aufsteigender Reihenfolge der Stabilität проходen: von alpha bis rock-solid;
  • Die Website hat eine russische Version, die parallel zur Hauptversion (d. h. der englischsprachigen Version) „lebt und sich entwickelt“ (d. h. ihr Inhalt wird aktualisiert).

Um dem Benutzer diese gesamte „interne Küche“ zu verbergen und ihm das anzubieten, was „einfach funktioniert“, haben wir ein separates Tool zur Installation und Aktualisierung von werf sind multiwerfentwickelt. Es genügt, die Versionsnummer und den Stabilitätskanal anzugeben, den Sie bereit sind zu verwenden, und multiwerf überprüft, ob eine neue Version im Kanal verfügbar ist, und lädt sie bei Bedarf herunter.

Im Auswahlmenü für die Versionen der Website sind die neuesten Versionen von werf in jedem Kanal verfügbar. Standardmäßig wird unter der Adresse werf.io/documentation die Version des stabilsten Kanals für die letzte Veröffentlichung geöffnet — sie wird auch von Suchmaschinen indexiert. Die Dokumentation für den Kanal ist unter separaten Adressen verfügbar (z. B. werf.io/v1.0-beta/documentation für die Beta-Version 1.0).

Insgesamt sind auf der Website die folgenden Versionen verfügbar:

  1. die Hauptversion (standardmäßig geöffnet),
  2. für jeden aktiven Update-Kanal jeder Version (zum Beispiel, werf.io/v1.0-beta).

Um eine spezifische Version der Website zu generieren, genügt es im Allgemeinen, sie mit den Mitteln von Jekyll, indem man im Verzeichnis /docs des werf-Repositories den entsprechenden Befehl ausführt (jekyll build), nachdem man zu dem benötigten Git-Tag der Version gewechselt hat.

Es bleibt nur hinzuzufügen, dass:

  • für den Build selbst das Tool (werf) verwendet wird;
  • CI/CD-Prozesse basieren auf GitLab CI;
  • und all das funktioniert natürlich in Kubernetes.

Aufgaben

Jetzt formulieren wir die Aufgaben, die alle genannten Spezifikationen berücksichtigen:

  1. Nach dem Wechsel der Version von werf auf einem beliebigen Update-Kanal soll die Dokumentation auf der Website automatisch aktualisiert werden..
  2. Für die Entwicklung muss es manchmal möglich sein, Vorschaudateien der Website zu betrachten..

Die Website muss nach dem Wechsel der Version auf einem beliebigen Kanal der entsprechenden Git-Tags neu kompiliert werden, wobei wir im Prozess des Erstellens des Images die folgenden Besonderheiten erhalten:

  • Da sich die Liste der Versionen in den Kanälen ändert, muss nur die Dokumentation für die Kanäle, in denen sich die Version geändert hat, neu erstellt werden. Es wäre nicht schön, alles komplett neu zu bauen.
  • Der Satz von Kanälen für Releases kann sich ändern. Zu einem bestimmten Zeitpunkt kann es beispielsweise keine Version auf den Kanälen geben, die stabiler ist als das frühzeitige Release 1.1, aber mit der Zeit werden sie erscheinen – soll man in diesem Fall die Builds manuell ändern?

Es stellt sich heraus, dass der Build hängt von sich ändernden externen Daten ab..

Implementierung

Wahl des Ansatzes

Als Option könnte man jede benötigte Version als separates Pod in Kubernetes ausführen. Diese Option impliziert eine größere Anzahl von Objekten im Cluster, die mit zunehmender Zahl stabiler werf-Releases wachsen wird. Das bedeutet wiederum komplexere Wartung: Für jede Version gibt es einen eigenen HTTP-Server, wobei die Last gering ist. Natürlich verursacht das auch höhere Ressourcenausgaben.

Wir sind den Weg gegangen alle benötigten Versionen in einem Image zu erstellen.Die kompilierten Statischen aller Versionen der Website befinden sich in einem Container mit NGINX, und der Verkehr für das entsprechende Deployment kommt über NGINX Ingress. Eine einfache Struktur – stateless-Anwendung – ermöglicht es, das Deployment leicht zu skalieren (je nach Last) mit den Mitteln von Kubernetes.

Genauer gesagt, erstellen wir zwei Images: eines für den Produktions-Workflow und das andere zusätzlich für den Entwicklungs-Workflow. Das zusätzliche Image wird (gestart) nur im Entwicklungs-Workflow zusammen mit dem Hauptimage verwendet und enthält die Version der Website aus dem Review-Commit, während das Routing zwischen ihnen mithilfe von Ingress-Ressourcen erfolgt.

werf vs git clone und Artefakte

Wie bereits erwähnt, um die statischen Inhalte der Website für eine bestimmte Version der Dokumentation zu generieren, muss ein Build durchgeführt werden, wobei auf den entsprechenden Tag des Repositories gewechselt wird. Man könnte dies auch tun, indem man das Repository bei jedem Build klont und die entsprechenden Tags aus der Liste auswählt. Dies ist jedoch eine recht ressourcenintensive Operation und außerdem erfordert sie das Schreiben nicht trivialisierbare Anweisungen… Ein weiterer schwerwiegender Nachteil ist, dass bei diesem Ansatz während des Builds nichts zwischengespeichert werden kann.

Hier kommt uns das Tool werf zur Hilfe, das intelligente Cache-Verwaltung implementiert und es ermöglicht, externe Repositorieszu verwenden. Die Nutzung von werf zur Hinzufügung von Code aus dem Repository beschleunigt den Build erheblich, da werf im Grunde das Repository einmal klont und dann bei Bedarf nur fetch verwendet. Darüber hinaus können wir beim Hinzufügen von Daten aus dem Repository nur die benötigten Verzeichnisse auswählen (in unserem Fall ist dies das Verzeichnis docs), was das Volumen der hinzugefügten Daten erheblich reduziert.

Da Jekyll ein Tool zur Kompilierung von Statischen ist und im endgültigen Image nicht benötigt wird, wäre es logisch, die Kompilierung im werf-Artefakt, und das endgültige Image nur das Kompilierungsergebnis zu importieren..

Wir schreiben werf.yaml

Jetzt haben wir uns entschieden, dass wir jede Version in einem separaten werf-Artefakt kompilieren werden. Wir wissen jedoch nicht, wie viele dieser Artefakte beim Build vorhanden sein werden, daher können wir keine feste Build-Konfiguration schreiben (streng genommen könnten wir dies trotzdem tun, aber es wäre nicht besonders effizient).

werf ermöglicht es, Go-Templates in seiner Konfigurationsdatei (werf.yaml), was die Möglichkeit bietet, die Konfiguration „on the fly“ je nach externen Daten zu generieren (genau das, was wir brauchen!). Externe Daten sind in unserem Fall Informationen über Versionen und Releases, auf deren Grundlage wir die erforderliche Anzahl an Artefakten erstellen und letztlich zwei Images erhalten: werf-doc werf-dev und zum Starten in unterschiedlichen Workflows. zum Starten auf verschiedenen Konturen.

Externe Daten werden über Umgebungsvariablen übermittelt. Hier ist ihre Zusammensetzung:

  • VERÖFFENTLICHUNGEN — eine Zeile mit einer Liste von Veröffentlichungen und der dazugehörigen aktuellen Version von werf, als Liste von durch Leerzeichen getrennten Werten im Format %. Beispiel: 1.0%v1.0.4-beta.20
  • KANÄLE — eine Zeile mit einer Liste von Kanälen und der dazugehörigen aktuellen Version von werf, als Liste von durch Leerzeichen getrennten Werten im Format %. Beispiel: 1.0-beta%v1.0.4-beta.20 1.0-alpha%v1.0.5-alpha.22
  • ROOT_VERSION — die Version der werf-Veröffentlichung, die standardmäßig auf der Website angezeigt wird (es ist nicht immer erforderlich, die Dokumentation zur höchsten Release-Nummer auszugeben). Beispiel: v1.0.4-beta.20
  • REVIEW_SHA — der Hash des Review-Commits, aus dem die Version für die Testumgebung erstellt werden soll.

Diese Variablen werden in der GitLab CI-Pipeline befüllt, und wie genau, wird unten beschrieben.

Zuerst definieren wir zur Bequemlichkeit in werf.yaml Go-Template-Variablen und weisen ihnen Werte aus den Umgebungsvariablen zu:

{{ $_ := set . "WerfVersions" (cat (env "KANÄLE") (env "VERÖFFENTLICHUNGEN") | splitList " ") }}
{{ $Root := . }}
{{ $_ := set . "WerfRootVersion" (env "ROOT_VERSION") }}
{{ $_ := set . "WerfReviewCommit" (env "REVIEW_SHA") }}

Die Beschreibung des Artefakts zur Komposition der statischen Version der Website ist insgesamt für alle unsere erforderlichen Fälle identisch (einschließlich der Generierung der Wurzelversion sowie der Version für die Entwicklungsumgebung). Daher werden wir sie in einen separaten Block mit der Funktion verschieben definieren — zum anschließenden Wiederverwenden mit include. Dem Template werden folgende Argumente übergeben:

  • Version — die generierte Version (Tag-Name);
  • Channel — der Name des Update-Kanals, für den das Artefakt generiert wird;
  • Commit — der Hash des Commits, wenn das Artefakt für den Review-Commit generiert wird;
  • Kontext.

Beschreibung des Artefakt-Templates

{{- define "doc_artifact" -}}
{{- $Root := index . "Root" -}}
Artefakt: doc-{{ .Channel }}
von: jekyll/builder:3
mount:
- von: build_dir
  nach: /usr/local/bundle
ansible:
  install:
  - shell: |
      export PATH=/usr/jekyll/bin/:$PATH
  - name: "Abhängigkeiten installieren"
    shell: bundle install
    args:
      executable: /bin/bash
      chdir: /app/docs
  beforeSetup:
{{- if .Commit }}
  - shell: echo "SHA überprüfen - {{ .Commit }}."
{{- end }}
{{- if eq .Channel "root" }}
  - name: "releases.yml HASH: {{ $Root.Files.Get "releases.yml" | sha256sum }}"
    copy:
      content: |
{{ $Root.Files.Get "releases.yml" | indent 8 }}
      dest: /app/docs/_data/releases.yml
{{- else }}
  - file:
      path: /app/docs/_data/releases.yml
      state: touch
{{- end }}
  - file:
      path: "{{`{{ item }}`}}"
      state: directory
      mode: 0777
    with_items:
    - /app/main_site/
    - /app/ru_site/
  - file:
      dest: /app/docs/pages_ru/cli
      state: link
      src: /app/docs/pages/cli
  - shell: |
      echo -e "werfVersion: {{ .Version }}nwerfChannel: {{ .Channel }}" > /tmp/_config_additional.yml
      export PATH=/usr/jekyll/bin/:$PATH
{{- if and (ne .Version "review") (ne .Channel "root") }}
{{- $_ := set . "BaseURL" ( printf "v%s" .Channel ) }}
{{- else if ne .Channel "root" }}
{{- $_ := set . "BaseURL" .Channel }}
{{- end }}
      jekyll build -s /app/docs  -d /app/_main_site/{{ if .BaseURL }} --baseurl /{{ .BaseURL }}{{ end }} --config /app/docs/_config.yml,/tmp/_config_additional.yml
      jekyll build -s /app/docs  -d /app/_ru_site/{{ if .BaseURL }} --baseurl /{{ .BaseURL }}{{ end }} --config /app/docs/_config.yml,/app/docs/_config_ru.yml,/tmp/_config_additional.yml
    args:
      executable: /bin/bash
      chdir: /app/docs
git:
- url: https://github.com/flant/werf.git
  nach: /app/
  owner: jekyll
  group: jekyll
{{- if .Commit }}
  commit: {{ .Commit }}
{{- else }}
  tag: {{ .Version }}
{{- end }}
  stageDependencies:
    install: ['docs/Gemfile','docs/Gemfile.lock']
    beforeSetup: '**/*'
  includePaths: 'docs'
  excludePaths: '**/*.sh'
{{- end }}

Der Name des Artefakts muss eindeutig sein. Wir können dies erreichen, indem wir beispielsweise den Kanalnamen (den Wert der Variable .Channel) als Suffix des Artefaktnamens hinzufügen: Artefakt: doc-{{ .Channel }}. Es ist jedoch zu beachten, dass beim Import von Artefakten auf dieselben Namen verwiesen werden muss.

Bei der Beschreibung des Artefakts wird eine Möglichkeit von werf verwendet, die Montierung. Die Montage mit dem angegebenen Arbeitsverzeichnis build_dir ermöglicht es, den Jekyll-Cache zwischen den Ausführungen der Pipeline zu speichern, was die Neubauzeit erheblich beschleunigt..

Außerdem haben Sie möglicherweise die Verwendung der Datei releases.yml bemerkten – dies ist eine YAML-Datei mit Release-Daten, die von github.com (Artefakt, das bei der Ausführung der Pipeline erstellt wird). Sie ist beim Kompilieren der Website erforderlich, interessiert uns aber in diesem Kontext, da ihr Zustand davon abhängt, ob nur ein Artefakt erneut gebaut werden kann – das Artefakt der Wurzelversion (in anderen Artefakten wird es nicht benötigt).

Dies wird durch den bedingten Operator if Go-Templates und die Konstruktion {{ $Root.Files.Get "releases.yml" | sha256sum }} in der Phase Stadium. So funktioniert es: Bei der Erstellung des Artefakts für die Wurzelversion (Variable .Channel entspricht root) hat der Hash der Datei releases.yml Einfluss auf die Signatur der gesamten Phase, da er Bestandteil des Namens der Ansible-Aufgabe ist (Parameter name). Wenn sich also der Inhalt der Datei releases.yml ändert, wird das entsprechende Artefakt neu erstellt.

Bitte beachten Sie auch die Arbeit mit dem externen Repository. Im Artefaktbild aus dem werf-Repositorywird nur das Verzeichnis hinzugefügt, /docsabhängig von den übergebenen Parametern werden sofort die erforderlichen Tag-Daten oder Review-Commits hinzugefügt.

Um die Vorlage des Artefakts zur Generierung der Beschreibung des Artefakts für übergebene Versionen der Kanäle und Releases zu verwenden, organisieren wir eine Schleife über die Variable .WerfVersions in werf.yaml:

{{ range .WerfVersions -}}
{{ $VersionsDict := splitn "%" 2 . -}}
{{ dict "Version" $VersionsDict._1 "Channel" $VersionsDict._0 "Root" $Root | include "doc_artifact" }}
---
{{ end -}}

Da die Schleife mehrere Artefakte generiert (wir hoffen darauf), muss der Trennzeichen zwischen ihnen — die Sequenz — berücksichtigt werden --- (näheres zum Syntax der Konfigurationsdatei siehe im Dokumentation). Wie bereits erwähnt, übergeben wir beim Aufruf der Vorlage in der Schleife die Parameter für Version, URL und den Wurzelkontext.

Ähnlich, jedoch ohne Schleife, rufen wir die Vorlage des Artefakts für "Sonderfälle" auf: für die Wurzelversion sowie für die Version aus dem Review-Commit:

{{ dict "Version" .WerfRootVersion "Channel" "root" "Root" $Root  | include "doc_artifact" }}
---
{{- if .WerfReviewCommit }}
{{ dict "Version" "review" "Channel" "review" "Commit" .WerfReviewCommit "Root" $Root  | include "doc_artifact" }}
{{- end }}

Bitte beachten Sie, dass das Artefakt für den Review-Commit nur erstellt wird, wenn die Variable .WerfReviewCommit.

vorhanden ist. Die Artefakte sind bereit — es ist Zeit, mit dem Import zu beginnen!

Das endgültige Bild, das zum Ausführen in Kubernetes gedacht ist, ist ein gewöhnlicher NGINX, dem eine Serverkonfigurationsdatei nginx.conf und statische Dateien aus den Artefakten hinzugefügt werden. Neben dem Artefakt der Wurzelversion der Website müssen wir die Schleife über die Variable .WerfVersions zum Import der Artefakte für Versionen der Kanäle und Releases wiederholen + die zuvor festgelegte Regel für die Benennung der Artefakte einhalten. Da jedes Artefakt die Versionen der Website für zwei Sprachen speichert, importieren wir sie an die in der Konfiguration vorgesehenen Orte.

Beschreibung des endgültigen Bildes werf-doc

image: werf-doc
from: nginx:stable-alpine
ansible:
  setup:
  - name: "Setup /etc/nginx/nginx.conf"
    copy:
      content: |
{{ .Files.Get ".werf/nginx.conf" | indent 8 }}
      dest: /etc/nginx/nginx.conf
  - file:
      path: "{{`{{ item }}`}}"
      state: directory
      mode: 0777
    with_items:
    - /app/main_site/assets
    - /app/ru_site/assets
import:
- artifact: doc-root
  add: /app/_main_site
  to: /app/main_site
  before: setup
- artifact: doc-root
  add: /app/_ru_site
  to: /app/ru_site
  before: setup
{{ range .WerfVersions -}}
{{ $VersionsDict := splitn "%" 2 . -}}
{{ $Channel := $VersionsDict._0 -}}
{{ $Version := $VersionsDict._1 -}}
- artifact: doc-{{ $Channel }}
  add: /app/_main_site
  to: /app/main_site/v{{ $Channel }}
  before: setup
{{ end -}}
{{ range .WerfVersions -}}
{{ $VersionsDict := splitn "%" 2 . -}}
{{ $Channel := $VersionsDict._0 -}}
{{ $Version := $VersionsDict._1 -}}
- artifact: doc-{{ $Channel }}
  add: /app/_ru_site
  to: /app/ru_site/v{{ $Channel }}
  before: setup
{{ end -}}

Ein zusätzliches Image, das zusammen mit dem Hauptimage im Dev-Kontur läuft, enthält nur zwei Versionen der Website: die Version aus dem Review-Commit und die Stammversion der Website (dort sind die gemeinsamen Assets und, wenn Sie sich erinnern, die Daten zu den Releases). Somit unterscheidet sich das zusätzliche Image nur in der Importsektion (nun ja, und natürlich im Namen):

image: werf-dev
...
import:
- artifact: doc-root
  add: /app/_main_site
  to: /app/main_site
  before: setup
- artifact: doc-root
  add: /app/_ru_site
  to: /app/ru_site
  before: setup
{{- if .WerfReviewCommit  }}
- artifact: doc-review
  add: /app/_main_site
  to: /app/main_site/review
  before: setup
- artifact: doc-review
  add: /app/_ru_site
  to: /app/ru_site/review
  before: setup
{{- end }}

Wie bereits oben angemerkt, wird das Artifact für den Review-Commit nur generiert, wenn die Umgebungsvariable gesetzt ist. REVIEW_SHA. Man hätte das Image werf-dev ganz weglassen können, wenn keine Umgebungsvariable vorhanden ist, REVIEW_SHA, aber um sicherzustellen, dass die Bereinigung gemäß den Richtlinien Docker-Images in werf für das Image werf-dev funktioniert, lassen wir es nur mit dem Artifact der Stammversion bauen (es wurde sowieso bereits gebaut), um die Struktur des Pipelines zu vereinfachen.

Der Bau ist abgeschlossen! Lassen Sie uns zu CI/CD und den wichtigen Details übergehen.

Pipeline in GitLab CI und Besonderheiten des dynamischen Builds

Bei der Ausführung des Builds müssen wir die Umgebungsvariablen setzen, die verwendet werden in werf.yaml. Dies betrifft nicht die Variable REVIEW_SHA, die wir beim Aufruf der Pipeline von dem GitHub-Hook setzen werden.

Die erforderlichen externen Daten werden wir in ein Bash-Skript auslagern generate_artifacts, das zwei Pipeline-Artifacts für GitLab generieren wird:

  • Datei releases.yml mit den Daten zu den Releases,
  • Datei common_envs.sh, das die Umgebungsvariablen zum Export enthält.

Inhalt der Datei generate_artifacts Sie finden es in unserem Repository mit Beispielen. Das Abrufen der Daten ist nicht das Thema des Artikels, aber die Datei common_envs.sh ist wichtig für uns, da die Funktionalität von werf davon abhängt. Hier ein Beispiel für den Inhalt:

export RELEASES='1.0%v1.0.6-4'
export CHANNELS='1.0-alpha%v1.0.7-1 1.0-beta%v1.0.7-1 1.0-ea%v1.0.6-4 1.0-stable%v1.0.6-4 1.0-rock-solid%v1.0.6-4'
export ROOT_VERSION='v1.0.6-4'

Die Ausgabe eines solchen Skripts kann beispielsweise mit einer Bash-Funktion verwendet werden. source.

Und jetzt wird es interessant. Damit sowohl der Build als auch das Deployment der Anwendung ordnungsgemäß funktionieren, muss sichergestellt werden, dass werf.yaml wurde gleich sind mindestens im Rahmen einer Pipeline. Wenn diese Bedingung nicht erfüllt ist, werden die Signaturen der Phasen, die werf beim Build und beispielsweise beim Deploy berechnet, unterschiedlich sein. Dies führt zu einem Deployment-Fehler, da das für das Deployment benötigte Image fehlen wird.

Mit anderen Worten, wenn während des Bildens des Website-Images die Informationen zu den Releases und Versionen gleich sind, aber zum Zeitpunkt des Deployments eine neue Version erscheint und die Umgebungsvariablen andere Werte haben, wird das Deployment mit einem Fehler enden: denn das Artefakt der neuen Version ist noch nicht erstellt.

Wenn die Generierung werf.yaml von externen Daten abhängt (zum Beispiel von einer Liste aktueller Versionen, wie in unserem Fall), müssen die Zusammenstellung und die Werte dieser Daten im Rahmen der Pipeline fixiert werden. Dies ist besonders wichtig, wenn sich externe Parameter ziemlich häufig ändern.

Wir werden externe Daten abrufen und festhalten in der ersten Phase der Pipeline in GitLab (Prebuild) und sie dann als GitLab CI-Artefaktweitergeben. Das ermöglicht es, Aufgaben der Pipeline (Build, Deployment, Cleanup) mit derselben Konfiguration zu starten und neu zu starten in werf.yaml.

Inhalt der Phase Prebuild der Datei .gitlab-ci.yml:

Prebuild:
  stage: prebuild
  script:
    - bash . /generate_artifacts 1> common_envs.sh
    - cat ./common_envs.sh
  artifacts:
    paths:
      - releases.yml
      - common_envs.sh
    expire_in: 2 weeks

Nachdem die externen Daten im Artefakt fixiert wurden, kann das Build und Deployment unter Verwendung der Standard-Pipeline-Phasen GitLab CI: Build und Deploy durchgeführt werden. Die Pipeline wird durch Hooks aus dem GitHub-Repository werf ausgeführt (d. h. bei Änderungen im GitHub-Repository). Die Daten dafür können in den Projekteigenschaften von GitLab im Bereich CI / CD-Einstellungen -> Pipeline-Trigger, entnommen werden, und dann erstellen wir den entsprechenden Webhook in GitHub (Einstellungen -> Webhooks).

Die Build-Phase wird wie folgt aussehen:

Build:
  stage: build
  script:
    - type multiwerf && . $(multiwerf use 1.0 alpha --as-file)
    - type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
    - source common_envs.sh
    - werf build-and-publish --stages-storage :local
  except:
    refs:
      - schedules
  dependencies:
    - Prebuild

GitLab wird zwei Artefakte aus der Phase zur Build-Phase hinzufügen, Prebuildsodass wir die Variablen mit den vorbereiteten Eingabedaten durch die Konstruktion exportieren source common_envs.sh. Wir starten die Build-Phase in allen Fällen, außer beim zeitgesteuerten Start des Pipelines. Zeitgesteuert wird ein Pipeline für die Bereinigung gestartet - in diesem Fall ist ein Build nicht notwendig.

In der Deploy-Phase werden wir zwei Aufgaben beschreiben - separat für die Bereitstellung auf den Produktions- und dev-Umgebungen, unter Verwendung einer YAML-Vorlage:

.base_deploy: &base_deploy
  stage: deploy
  script:
    - type multiwerf && . $(multiwerf use 1.0 alpha --as-file)
    - type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
    - source common_envs.sh
    - werf deploy --stages-storage :local
  dependencies:
    - Prebuild
  except:
    refs:
      - schedules

Bereitstellung in der Produktion:
  <<: *base_deploy
  variables:
    WERF_KUBE_CONTEXT: prod
  environment:
    name: production
    url: werf.io
  only:
    refs:
      - master
  except:
    variables:
      - $REVIEW_SHA
    refs:
      - schedules

Bereitstellung für Test:
  <<: *base_deploy
  variables:
    WERF_KUBE_CONTEXT: dev
  environment:
    name: test
    url: werf.test.flant.com
  except:
    refs:
      - schedules
  only:
    variables:
      - $REVIEW_SHA

Die Aufgaben unterscheiden sich im Wesentlichen nur durch die Angabe des Clusterkontexts, in den werf die Bereitstellung durchführen soll (WERF_KUBE_CONTEXT), und durch die Festlegung von Umgebung variablen für die Umgebung (environment.name und environment.url), die dann in den Helm-Chart-Vorlagen verwendet werden. Wir werden den Inhalt der Vorlagen nicht angeben, da sie nichts Interessantes für das behandelte Thema enthalten, aber Sie können sie im Repository zum Artikel finden..

Der letzte Schliff

Da neue Versionen von werf recht häufig veröffentlicht werden, werden auch neue Images häufig erstellt, und das Docker-Registry wird kontinuierlich wachsen. Daher ist es unbedingt erforderlich, die automatische Bereinigung von Images gemäß den Richtlinien einzurichten. Das ist ganz einfach zu machen.

Für die Umsetzung sind folgende Schritte erforderlich:

  • Eine Bereinigungsphase hinzufügen in .gitlab-ci.yml;
  • Eine zeitgesteuerte Ausführung der Bereinigungsaufgabe hinzufügen;
  • Eine Umgebungsvariable mit dem Token für die Schreibberechtigung einrichten.

Wir fügen eine Bereinigungsphase hinzu in .gitlab-ci.yml:

Cleanup:
  stage: cleanup
  script:
    - type multiwerf && . $(multiwerf use 1.0 alpha --as-file)
    - type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
    - source common_envs.sh
    - docker login -u nobody -p ${WERF_IMAGES_CLEANUP_PASSWORD} ${WERF_IMAGES_REPO}
    - werf cleanup --stages-storage :local
  only:
    refs:
      - schedules

Fast alles haben wir bereits kurz zuvor gesehen - nur für die Bereinigung muss man sich vorher mit einem Token im Docker-Registry anmelden, das die Rechte zum Löschen von Images im Docker-Registry hat (das automatisch ausgegebene Token von GitLab CI hat solche Rechte nicht). Das Token muss im Voraus in GitLab erstellt und dessen Wert in der Umgebungsvariable WERF_IMAGES_CLEANUP_PASSWORD des Projekts (CI/CD Einstellungen -> Variablen).

Die Hinzufügung der Bereinigungsaufgabe mit dem erforderlichen Zeitplan erfolgt in CI/CD ->
Zeitpläne
.

Alles: Das Projekt im Docker Registry wird nicht länger von ungenutzten Images ständig wachsen.

Am Ende des praktischen Teils erinnere ich daran, dass die vollständigen Listings aus dem Artikel verfügbar sind in Git:

Ergebnis

  1. Wir haben eine logische Struktur des Builds erhalten: ein Artefakt pro Version.
  2. Der Build ist universell und erfordert keine manuellen Änderungen bei neuen Versionen von werf: Die Dokumentation auf der Website wird automatisch aktualisiert.
  3. Es werden zwei Images für verschiedene Umgebungen gebaut.
  4. Es funktioniert schnell, da das Caching maximal genutzt wird — bei der Veröffentlichung einer neuen Version von werf oder beim Aufruf des GitHub-Webhooks für den Review-Commit wird nur das entsprechende Artefakt mit der geänderten Version neu gebaut.
  5. Man muss nicht an das Löschen ungenutzter Images denken: Die Reinigung gemäß den werf-Politiken wird die Ordnung im Docker Registry aufrechterhalten.

Das DBMS Tarantool ist ein attraktives, zukunftsträchtiges Produkt zur Erstellung von hochbelasteten Anwendungen.

  • Die Verwendung von werf ermöglicht es dem Build, schnell zu arbeiten, dank des Cachings sowohl des Builds als auch des Cachings bei der Arbeit mit externen Repositories.
  • Die Arbeit mit externen Git-Repositories erspart die Notwendigkeit, das Repository jedes Mal vollständig zu klonen oder das Rad mit komplexer Optimierungslogik neu zu erfinden. werf verwendet Cache und führt das Klonen nur einmal durch, danach verwendet es fetch und nur nach Bedarf.
  • Die Möglichkeit, Go-Templates in der Build-Konfigurationsdatei zu verwenden werf.yaml ermöglicht es, einen Build zu beschreiben, dessen Ergebnis von externen Daten abhängt.
  • Die Nutzung der Montage in werf beschleunigt den Aufbau von Artefakten erheblich — dank eines Caches, der für alle Pipelines gemeinschaftlich ist.
  • werf ermöglicht eine einfache Konfiguration der Reinigung, was besonders relevant ist, wenn dynamische Builds durchgeführt werden.

P.S.

Lesen Sie auch in unserem Blog:

Quelle: habr.com

60GB SSD 8Gb DDR4