Wir probieren neue Werkzeuge zur Erstellung und Automatisierung von Deployments in Kubernetes aus.

Wir probieren neue Werkzeuge zur Erstellung und Automatisierung von Deployments in Kubernetes aus.

Hallo! In letzter Zeit sind viele großartige Automatisierungstools sowohl fĂŒr den Build von Docker-Images als auch fĂŒr das Deployment in Kubernetes erschienen. Deshalb habe ich beschlossen, mit GitLab zu experimentieren, seine Funktionen grĂŒndlich zu erforschen und natĂŒrlich eine Pipeline einzurichten.

Die Inspiration fĂŒr diese Arbeit war die Webseite kubernetes.io, die automatisch aus den Quellcodes generiert wird, und fĂŒr jeden eingereichten Pull-Request generiert der Bot automatisch eine Vorschau-Version der Webseite mit Ihren Änderungen und stellt einen Link zur VerfĂŒgung, um sie anzusehen.

Ich habe versucht, einen Ă€hnlichen Prozess von Grund auf zu erstellen, aber vollstĂ€ndig auf GitLab CI und freien Tools basierend, die ich gewohnt bin zu verwenden, um Anwendungen in Kubernetes zu deployen. Heute werde ich Ihnen endlich mehr darĂŒber erzĂ€hlen.

In diesem Artikel werden solche Tools wie
Hugo, qbec, kaniko, git-crypt und GitLab CI mit der Erstellung dynamischer Umgebungen behandelt.

Inhalt

  1. EinfĂŒhrung in Hugo
  2. Vorbereitung des Dockerfiles
  3. EinfĂŒhrung in kaniko
  4. EinfĂŒhrung in qbec
  5. Wir testen GitLab-Runner mit Kubernetes-Executor
  6. Deployment von Helm-Charts mit qbec
  7. EinfĂŒhrung in git-crypt
  8. Wir erstellen ein Toolbox-Image
  9. Unsere erste Pipeline und das Builden von Images nach Tags
  10. Automatisierung des Deployments
  11. Artefakte und Build bei Push in Master
  12. Dynamische Umgebungen
  13. Review Apps

1. EinfĂŒhrung in Hugo

Als Beispiel fĂŒr unser Projekt werden wir versuchen, eine Webseite zur Veröffentlichung von Dokumentationen zu erstellen, die auf Hugo basiert. Hugo ist ein statischer Inhaltsgenerator.

FĂŒr diejenigen, die mit statischen Generatoren nicht vertraut sind, werde ich sie etwas ausfĂŒhrlicher erlĂ€utern. Im Gegensatz zu herkömmlichen Website-Engines mit einer Datenbank und PHP, die bei Benutzeranfragen die Seiten dynamisch generieren, funktionieren statische Generatoren etwas anders. Sie ermöglichen es, die Quellcodes, in der Regel eine Sammlung von Dateien im Markdown-Format und Themenvorlagen, zu nehmen und sie in eine vollstĂ€ndig einsatzbereite Webseite zu kompilieren.

Das heißt, am Ende erhalten Sie eine Verzeichnisstruktur und eine Sammlung generierter HTML-Dateien, die Sie einfach auf jeden gĂŒnstigen Hosting-Service hochladen können, um eine funktionierende Webseite zu erhalten.

Hugo kann lokal installiert und ausprobiert werden:

Wir initialisieren eine neue Webseite:

hugo new site docs.example.org

Und gleichzeitig ein Git-Repository:

cd docs.example.org
git init

Unser Webseite ist noch jungfrĂ€ulich und damit etwas darauf erscheint, mĂŒssen wir zuerst ein Thema verbinden; ein Thema ist einfach eine Sammlung von Vorlagen und festgelegten Regeln, nach denen unsere Webseite generiert wird.

Als Thema werden wir verwenden Learn, die meiner Meinung nach perfekt fĂŒr eine Dokumentationswebsite geeignet ist.

Besonderes Augenmerk möchte ich darauf legen, dass es nicht erforderlich ist, die Themendateien im Repository unseres Projekts zu speichern. Stattdessen können wir sie einfach unter Verwendung von git submodule:

git submodule add https://github.com/matcornic/hugo-theme-learn themes/learn

So befinden sich in unserem Repository nur die Dateien, die direkt mit unserem Projekt zu tun haben, wĂ€hrend das eingebundene Thema als Link zu einem bestimmten Repository und dem dortigen Commit bleibt. Das bedeutet, dass wir es jederzeit aus der ursprĂŒnglichen Quelle abrufen können, ohne Angst vor inkompatiblen Änderungen zu haben.

Lass uns die Konfiguration anpassen config.toml:

baseURL = "http://docs.example.org/"
languageCode = "de-de"
title = "Meine Dokumentationsseite"
theme = "learn"

Bereits zu diesem Zeitpunkt können wir starten:

hugo server

Und unter der Adresse http://localhost:1313/ unser frisch erstellte Website ĂŒberprĂŒfen. Alle Änderungen, die im Verzeichnis vorgenommen werden, aktualisieren automatisch die geöffnete Seite im Browser, sehr praktisch!

Lass uns versuchen, eine Titelseite in content/_index.md:

# My docs site

## Welcome to the docs!

You will be very smart :-)

Screenshot der gerade erstellten Seite

Wir probieren neue Werkzeuge zur Erstellung und Automatisierung von Deployments in Kubernetes aus.

Um die Website zu generieren, reicht es, Folgendes auszufĂŒhren:

hugo

Der Inhalt des Verzeichnisses Alles im Ordner wird deine Website sein.
Ja, ĂŒbrigens, lass uns das gleich in .gitignore:

echo /public > .gitignore

Vergessen wir nicht, unsere Änderungen zu committen:

git add .
git commit -m "Neue Website erstellt"

2. Vorbereitung des Dockerfile

Es ist an der Zeit, die Struktur unseres Repositories zu definieren. Normalerweise verwende ich so etwas wie:

.
├── deploy
│   ├── app1
│   └── app2
└── dockerfiles
    ├── image1
    └── image2

  • dockerfiles/ — enthĂ€lt Verzeichnisse mit Dockerfiles und allem Notwendigen zum Erstellen unserer Docker-Images.
  • deploy/ — enthĂ€lt Verzeichnisse fĂŒr das Deployment unserer Anwendungen in Kubernetes.

So erstellen wir unser erstes Dockerfile unter dem Pfad dockerfiles/website/Dockerfile

FROM alpine:3.11 as builder
ARG HUGO_VERSION=0.62.0
RUN wget -O- https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_linux-64bit.tar.gz | tar -xz -C /usr/local/bin
ADD . /src
RUN hugo -s /src

FROM alpine:3.11
RUN apk add --no-cache darkhttpd
COPY --from=builder /src/public /var/www
ENTRYPOINT [ "/usr/bin/darkhttpd" ]
CMD [ "/var/www" ]

Wie Sie sehen können, enthĂ€lt das Dockerfile zwei FROM, diese FĂ€higkeit wird als multi-stage build bezeichnet und ermöglicht es, alles Unnötige aus dem finalen Docker-Image auszuschließen.
So wird das finale Image nur darkhttpd (ein leichtgewichtiger HTTP-Server) und Alles im Ordner — den Inhalt unserer statisch generierten Website enthalten.

Vergessen wir nicht, unsere Änderungen zu committen:

git add dockerfiles/website
git commit -m "Dockerfile fĂŒr die Website hinzufĂŒgen"

3. EinfĂŒhrung in kaniko

Als Docker-Bildgenerator habe ich beschlossen, kaniko, da fĂŒr seine AusfĂŒhrung kein Docker-Daemon erforderlich ist und der Build auf jedem Computer durchgefĂŒhrt werden kann, wobei der Cache direkt im Registry gespeichert wird, wodurch die Notwendigkeit einer vollstĂ€ndigen persistenten Speicherung entfĂ€llt.

Um das Bild zu erstellen, genĂŒgt es, einen Container mit kaniko executor zu starten und ihm den aktuellen Build-Kontext zu ĂŒbergeben; das kann auch lokal ĂŒber Docker erfolgen:

docker run -ti --rm 
  -v $PWD:\/workspace 
  -v ~\/\.docker\/config.json:\/kaniko\/\.docker\/config.json:ro 
  gcr.io\/kaniko-project\/executor:v0.15.0 
  --cache 
  --dockerfile=dockerfiles\/website\/Dockerfile 
  --destination=registry.gitlab.com\/kvaps\/docs.example.org\/website:v0.0.1

Wo registry.gitlab.com\/kvaps\/docs.example.org\/website — der Name Ihres Docker-Bildes, das nach dem Build automatisch in die Docker-Registry gepusht wird.

Parameter —cache ermöglicht das Cachen von Schichten in der Docker-Registry; im oben genannten Beispiel werden sie in registry.gitlab.com\/kvaps\/docs.example.org\/website\/cache, aber Sie können auch einen anderen Pfad mit dem Parameter —cache-repo.

Screenshot der Docker-Registry

Wir probieren neue Werkzeuge zur Erstellung und Automatisierung von Deployments in Kubernetes aus.

4. EinfĂŒhrung in Qbec

Qbec ist ein Deployment-Tool, das es ermöglicht, die Manifestdateien Ihrer Anwendung deklarativ zu beschreiben und diese in Kubernetes zu deployen. Die Verwendung von Jsonnet als Hauptsyntax vereinfacht die Beschreibung der Unterschiede fĂŒr mehrere Umgebungen erheblich und beseitigt fast vollstĂ€ndig Redundanzen im Code.

Dies kann besonders relevant sein, wenn Sie eine Anwendung in mehreren Clustern mit unterschiedlichen Parametern deployen mĂŒssen und diese deklarativ in Git beschreiben möchten.

Qbec ermöglicht es auch, Helm-Charts zu rendern, indem Sie die erforderlichen Parameter ĂŒbergeben, und sie anschließend wie normale Manifestdateien zu behandeln, einschließlich der Anwendung verschiedener Mutationen, was wiederum die Notwendigkeit verringert, ChartMuseum zu verwenden. Das bedeutet, dass Charts direkt aus Git gespeichert und gerendert werden können, wo sie hingehören.

Wie bereits erwÀhnt, werden wir alle Deployments im Verzeichnis deploy/:

mkdir deploy
cd deploy

lassen Sie uns unsere erste Anwendung initialisieren:

qbec init website
cd website

Jetzt sieht die Struktur unserer Anwendung so aus:

.
├── components
├── environments
│   ├── base.libsonnet
│   └── default.libsonnet
├── params.libsonnet
└── qbec.yaml

sehen wir uns die Datei qbec.yaml:

apiVersion: qbec.io\/v1alpha1
kind: App
metadata:
  name: website
spec:
  environments:
    default:
      defaultNamespace: docs
      server: https:\/\/kubernetes.example.org:8443
  vars: {}

Hier interessiert uns in erster Linie spec.environments, qbec hat bereits fĂŒr uns die Standardumgebung erstellt und die Serveradresse sowie den Namespace aus unserem aktuellen kubeconfig ĂŒbernommen.
Jetzt bei der Bereitstellung in default der Umgebung wird qbec immer nur im angegebenen Kubernetes-Cluster und im angegebenen Namespace bereitstellen, das heißt, Sie mĂŒssen nicht mehr zwischen Kontexten und Namespaces wechseln, um die Bereitstellung durchzufĂŒhren.
Bei Bedarf können Sie die Einstellungen in dieser Datei jederzeit aktualisieren.

Alle Ihre Umgebungen werden in qbec.yaml, und in der Datei params.libsonnet, in der festgelegt ist, woher die Parameter fĂŒr sie stammen mĂŒssen.

Weiter sehen wir zwei Verzeichnisse:

  • components/ — hier werden alle Manifeste fĂŒr unsere Anwendung gespeichert, sie können sowohl in Jsonnet als auch in normalen YAML-Dateien beschrieben werden.
  • environments/ — hier werden wir alle Variablen (Parameter) fĂŒr unsere Umgebungen beschreiben.

StandardmĂ€ĂŸig haben wir zwei Dateien:

  • environments/base.libsonnet — diese wird die allgemeinen Parameter fĂŒr alle Umgebungen enthalten.
  • environments/default.libsonnet — enthĂ€lt fĂŒr die Umgebung ĂŒberschriebenen Parameter. default

Lassen Sie uns environments/base.libsonnet öffnen und dort die Parameter fĂŒr unsere erste Komponente hinzufĂŒgen:

{
  components: {
    website: {
      name: 'example-docs',
      image: 'registry.gitlab.com/kvaps/docs.example.org/website:v0.0.1',
      replicas: 1,
      containerPort: 80,
      servicePort: 80,
      nodeSelector: {},
      tolerations: [],
      ingressClass: 'nginx',
      domain: 'docs.example.org',
    },
  },
}

Lassen Sie uns auch unsere erste Komponente erstellen components/website.jsonnet:

lokale Umgebung = {
  name: std.extVar('qbec.io/env'),
  namespace: std.extVar('qbec.io/defaultNs'),
};
lokale p = import '../params.libsonnet';
lokale params = p.components.website;

[
  {
    apiVersion: 'apps/v1',
    kind: 'Deployment',
    metadata: {
      labels: { app: params.name },
      name: params.name,
    },
    spec: {
      replicas: params.replicas,
      selector: {
        matchLabels: {
          app: params.name,
        },
      },
      template: {
        metadata: {
          labels: { app: params.name },
        },
        spec: {
          containers: [
            {
              name: 'darkhttpd',
              image: params.image,
              ports: [
                {
                  containerPort: params.containerPort,
                },
              ],
            },
          ],
          nodeSelector: params.nodeSelector,
          tolerations: params.tolerations,
          imagePullSecrets: [{ name: 'regsecret' }],
        },
      },
    },
  },
  {
    apiVersion: 'v1',
    kind: 'Service',
    metadata: {
      labels: { app: params.name },
      name: params.name,
    },
    spec: {
      selector: {
        app: params.name,
      },
      ports: [
        {
          port: params.servicePort,
          targetPort: params.containerPort,
        },
      ],
    },
  },
  {
    apiVersion: 'extensions/v1beta1',
    kind: 'Ingress',
    metadata: {
      annotations: {
        'kubernetes.io/ingress.class': params.ingressClass,
      },
      labels: { app: params.name },
      name: params.name,
    },
    spec: {
      rules: [
        {
          host: params.domain,
          http: {
            paths: [
              {
                backend: {
                  serviceName: params.name,
                  servicePort: params.servicePort,
                },
              },
            ],
          },
        },
      ],
    },
  },
]

In dieser Datei haben wir sofort drei Kubernetes-EntitĂ€ten beschrieben, nĂ€mlich: Deployment, Service und Ingress. Bei Bedarf könnten wir sie in verschiedene Komponenten auslagern, aber in diesem Stadium genĂŒgt uns eine einzige.

Syntax jsonnet Ă€hnelt sehr dem normalen json, im Grunde ist normales json bereits ein valides jsonnet, sodass es anfangs vielleicht einfacher fĂŒr Sie sein wird, Online-Dienste wie yaml2json zu verwenden, um das Ihnen vertraute yaml in json zu konvertieren, oder, wenn Ihre Komponenten keine Variablen enthalten, können sie durchaus in Form von normalem yaml beschrieben werden.

Bei der Arbeit mit jsonnet empfehle ich Ihnen dringend, ein Plugin fĂŒr Ihren Editor zu installieren.

Zum Beispiel gibt es fĂŒr vim ein Plugin vim-jsonnet, das die Syntaxhervorhebung aktiviert und automatisch jsonnet fmt bei jedem Speichern ausfĂŒhrt (erfordert, dass jsonnet installiert ist).

Alles ist bereit, jetzt können wir mit dem Deployment beginnen:

Um zu sehen, was wir erreicht haben, fĂŒhren wir aus:

qbec show default

Im Ergebnis werden Sie die gerenderten yaml-Manifeste sehen, die im Cluster default angewendet werden.

Ausgezeichnet, jetzt wenden wir an:

qbec apply default

Im Ergebnis sehen Sie immer, was in Ihrem Cluster durchgefĂŒhrt wird, qbec wird Sie auffordern, den Änderungen zuzustimmen, indem Sie eingeben: y Sie können Ihre Absichten bestĂ€tigen.

Fertig, jetzt ist unsere Anwendung bereitgestellt!

Wenn Änderungen vorgenommen werden, können Sie jederzeit ausfĂŒhren:

qbec diff default

um zu sehen, wie sich diese Änderungen auf die aktuelle Bereitstellung auswirken

Vergessen wir nicht, unsere Änderungen zu committen:

cd ..\/..
git add deploy\/website
git commit -m "Add deploy for website"

5. Wir testen Gitlab-runner mit Kubernetes-executor

Bis vor kurzem habe ich nur gewöhnliche gitlab-runner auf einer im Voraus vorbereiteten Maschine (LXC-Container) mit shell- oder docker-executor verwendet. UrsprĂŒnglich hatten wir mehrere solcher Runner, die global in unserem GitLab definiert waren. Sie sammelten Docker-Images fĂŒr alle Projekte.

Aber wie die Praxis gezeigt hat, ist diese Option nicht die idealste, sowohl in Bezug auf PraktikabilitĂ€t als auch auf Sicherheit. Es ist viel besser und ideologisch richtiger, separate Runner fĂŒr jedes Projekt oder sogar fĂŒr jede Umgebung bereitzustellen.

GlĂŒcklicherweise ist das ĂŒberhaupt kein Problem, denn jetzt werden wir direkt als Teil unseres Projekts in Kubernetes bereitstellen. gitlab-runner Gitlab bietet ein fertiges Helm-Chart zur Bereitstellung von Gitlab-Runner in Kubernetes. Alles, was Sie tun mĂŒssen, ist zu erfahren,

registration token fĂŒr unser Projekt in Einstellungen —> CI \/ CD —> Runner und es Helm zu ĂŒbergeben: helm repo add gitlab https:\/\/charts.gitlab.iohelm install gitlab-runner --set gitlabUrl=https:\/\/gitlab.com --set runnerRegistrationToken=yga8y-jdCusVDn_t4Wxc --set rbac.create=true gitlab\/gitlab-runner

— Adresse Ihres Gitlab-Servers.

Wo:

  • https://gitlab.com yga8y-jdCusVDn_t4Wxc
  • — registration token fĂŒr Ihr Projekt. rbac.create=true
  • — gibt dem Runner die notwendige Anzahl an Berechtigungen, um Pods fĂŒr die AusfĂŒhrung unserer Aufgaben mit dem Kubernetes-Executor erstellen zu können. Wenn alles richtig gemacht ist, sollten Sie den registrierten Runner im Abschnitt

Runners , in den Einstellungen Ihres Projekts sehen.Screenshot des hinzugefĂŒgten Runners

So einfach? — Ja, so einfach! Kein Aufwand mehr mit der manuellen Registrierung von Runnern, von nun an werden Runner automatisch erstellt und gelöscht.

Wir probieren neue Werkzeuge zur Erstellung und Automatisierung von Deployments in Kubernetes aus.

6. Bereitstellung von Helm-Charts mit QBEC

Da wir beschlossen haben, es als

Teil unseres Projekts zu betrachten, ist es an der Zeit, es in unserem Git-Repository zu dokumentieren. gitlab-runner Wir könnten es als separate Komponente beschreiben

website , aber in Zukunft planen wir, verschiedene Kopiensehr hĂ€ufig bereitzustellen, im Gegensatz , aber in Zukunft planen wir, verschiedene Kopien , die nur einmal auf jedem Kubernetes-Cluster bereitgestellt wird. Also lassen Sie uns eine separate Anwendung dafĂŒr initialisieren: gitlab-runnercd deploy qbec init gitlab-runner cd gitlab-runner

cd deploy
qbec init gitlab-runner
cd gitlab-runner

Dieses Mal werden wir die Kubernetes-Objekte nicht manuell beschreiben, sondern einen fertigen Helm-Chart verwenden. Ein Vorteil von qbec ist die Möglichkeit, Helm-Charts direkt aus dem Git-Repository zu rendern.

Lass uns es mit git submodule einbinden:

git submodule add https://gitlab.com/gitlab-org/charts/gitlab-runner vendor/gitlab-runner

Jetzt enthĂ€lt das Verzeichnis vendor/gitlab-runner ein Repository mit dem Chart fĂŒr gitlab-runner.

Auf Àhnliche Weise können auch andere Repositories eingebunden werden, zum Beispiel das gesamte Repository mit den offiziellen Charts. https://github.com/helm/charts

Lass uns die Komponente beschreiben components/gitlab-runner.jsonnet:

local env = {
  name: std.extVar('qbec.io/env'),
  namespace: std.extVar('qbec.io/defaultNs'),
};
local p = import '../params.libsonnet';
local params = p.components.gitlabRunner;

std.native('expandHelmTemplate')(
  '../vendor/gitlab-runner',
  params.values,
  {
    nameTemplate: params.name,
    namespace: env.namespace,
    thisFile: std.thisFile,
    verbose: true,
  }
)

Das erste Argument fĂŒr expandHelmTemplate geben wir den Pfad zum Chart an, dann params.values, die wir aus den Umgebungsparametern nehmen, dann folgt ein Objekt mit

  • nameTemplate — der Name des Releases,
  • Namespace — der Namespace, der an Helm ĂŒbergeben wird,
  • thisFile — ein erforderlicher Parameter, der den Pfad zur aktuellen Datei ĂŒbergibt,
  • verbose — zeigt den Befehl helm template mit allen Argumenten beim Rendern des Charts an.

Jetzt beschreiben wir die Parameter fĂŒr unsere Komponente in environments/base.libsonnet:

local secrets = import '../secrets/base.libsonnet';

{
  components: {
    gitlabRunner: {
      name: 'gitlab-runner',
      values: {
        gitlabUrl: 'https://gitlab.com/',
        rbac: {
          create: true,
        },
        runnerRegistrationToken: secrets.runnerRegistrationToken,
      },
    },
  },
}

Bitte beachten Sie runnerRegistrationToken nehmen wir aus der externen Datei secrets/base.libsonnet, lass uns diese erstellen:

{
  runnerRegistrationToken: 'yga8y-jdCusVDn_t4Wxc',
}

Lass uns ĂŒberprĂŒfen, ob alles funktioniert:

qbec show default

Wenn alles in Ordnung ist, können wir unser zuvor ĂŒber Helm bereitgestelltes Release löschen:

helm uninstall gitlab-runner

und es ĂŒber qbec erneut bereitstellen:

qbec apply default

7. EinfĂŒhrung in git-crypt

Git-crypt ist ein Tool, das eine transparente VerschlĂŒsselung fĂŒr dein Repository ermöglicht.

Momentan sieht die Struktur unseres Verzeichnisses fĂŒr gitlab-runner so aus:

.
├── components
│   ├── gitlab-runner.jsonnet
├── environments
│   ├── base.libsonnet
│   └── default.libsonnet
├── params.libsonnet
├── qbec.yaml
├── secrets
│   └── base.libsonnet
└── vendor
    └── gitlab-runner (submodule)

Aber es ist unsicher, Geheimnisse in Git zu speichern, oder? Also mĂŒssen wir sie ordentlich verschlĂŒsseln.

In der Regel macht es nicht immer Sinn, nur fĂŒr eine Variable das zu tun. Du kannst Geheimnisse auch in qbec und ĂŒber Umgebungsvariablen deines CI-Systems ĂŒbergeben.
Es ist jedoch zu beachten, dass es auch komplexere Projekte gibt, die wesentlich mehr Geheimnisse enthalten können, die alle ĂŒber Umgebungsvariablen zu ĂŒbertragen, Ă€ußerst schwierig sein wird.

DarĂŒber hinaus wĂ€re es mir in diesem Fall nicht gelungen, Ihnen von einem so großartigen Tool wie git-crypt.

git-crypt zu erzĂ€hlen, das außerdem praktisch ist, weil es die gesamte Historie der Geheimnisse speichern und außerdem Vergleiche, ZusammenfĂŒhrungen und Konfliktlösungen so ermöglichen kann, wie wir es von Git gewohnt sind.

Als erstes nach der Installation git-crypt mĂŒssen wir SchlĂŒssel fĂŒr unser Repository generieren:

git crypt init

Wenn Sie einen PGP-SchlĂŒssel haben, können Sie sich sofort als Mitwirkenden fĂŒr dieses Projekt hinzufĂŒgen:

git-crypt add-gpg-user kvapss@gmail.com

Auf diese Weise können Sie dieses Repository immer mit Ihrem privaten SchlĂŒssel entschlĂŒsseln.

Wenn Sie jedoch keinen PGP-SchlĂŒssel haben und auch nicht erwarten, einen zu bekommen, können Sie einen anderen Weg gehen und den ProjektschlĂŒssel exportieren:

git crypt export-key /path/to/keyfile

So kann jeder, der ĂŒber den exportierten keyfile verfĂŒgt, Ihr Repository entschlĂŒsseln.

Es ist an der Zeit, unser erstes Geheimnis einzurichten.
Ich erinnere daran, dass wir uns weiterhin im Verzeichnis deploy/gitlab-runner/, wo wir ein Verzeichnis secrets/, lassen Sie uns alle Dateien darin verschlĂŒsseln, indem wir eine Datei erstellen secrets/.gitattributes mit folgendem Inhalt:

* filter=git-crypt diff=git-crypt
.gitattributes !filter !diff

Wie aus dem Inhalt zu ersehen ist, werden alle Dateien nach der Maske * durchlaufen, git-cryptmit Ausnahme von .gitattributes

Das können wir ĂŒberprĂŒfen, indem wir ausfĂŒhren:

git crypt status -e

Im Ergebnis erhalten wir eine Liste aller Dateien im Repository, fĂŒr die die VerschlĂŒsselung aktiviert ist.

Das ist alles, jetzt können wir unsere Änderungen sicher committen:

cd ../../
git add .
git commit -m "Add deploy for gitlab-runner"

Um das Repository zu sperren, genĂŒgt es, Folgendes auszufĂŒhren:

git crypt lock

und sofort werden alle verschlĂŒsselten Dateien zu einem binĂ€ren Etwas, das nicht lesbar ist.
Um das Repository zu entschlĂŒsseln, fĂŒhren Sie aus:

git crypt unlock

8. Wir erstellen ein Toolbox-Image

Das Toolbox-Image ist ein Bild mit allen Tools, die wir fĂŒr das Deployment unseres Projekts verwenden werden. Es wird vom GitLab Runner fĂŒr die AusfĂŒhrung typischer Deployment-Aufgaben verwendet.

Hier ist alles einfach, erstellen Sie eine neue dockerfiles/toolbox/Dockerfile mit folgendem Inhalt:

VON alpine:3.11

RUN apk add --no-cache git git-crypt

RUN QBEC_VER=0.10.3 
 && wget -O- https://github.com/splunk/qbec/releases/download/v${QBEC_VER}/qbec-linux-amd64.tar.gz 
     | tar -C /tmp -xzf - 
 && mv /tmp/qbec /tmp/jsonnet-qbec /usr/local/bin/

RUN KUBECTL_VER=1.17.0 
 && wget -O /usr/local/bin/kubectl 
      https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VER}/bin/linux/amd64/kubectl 
 && chmod +x /usr/local/bin/kubectl

RUN HELM_VER=3.0.2 
 && wget -O- https://get.helm.sh/helm-v${HELM_VER}-linux-amd64.tar.gz 
     | tar -C /tmp -zxf - 
 && mv /tmp/linux-amd64/helm /usr/local/bin/helm

Wie Sie sehen können, installieren wir in diesem Image alle Tools, die wir fĂŒr das Deployment unserer Anwendung verwendet haben. Wir brauchen hier nur kein kubectl, aber möglicherweise möchten Sie damit in der Pipeline-Konfiguration experimentieren.

Um mit Kubernetes zu kommunizieren und Deployments durchzufĂŒhren, mĂŒssen wir eine Rolle fĂŒr die von gitlab-runner generierten Pods einrichten.

DafĂŒr gehen wir in das Verzeichnis des gitlab-runners:

cd deploy/gitlab-runner

und fĂŒgen ein neues Element hinzu components/rbac.jsonnet:

local env = {
  name: std.extVar('qbec.io/env'),
  namespace: std.extVar('qbec.io/defaultNs'),
};
local p = import '../params.libsonnet';
local params = p.components.rbac;

[
  {
    apiVersion: 'v1',
    kind: 'ServiceAccount',
    metadata: {
      labels: {
        app: params.name,
      },
      name: params.name,
    },
  },
  {
    apiVersion: 'rbac.authorization.k8s.io/v1',
    kind: 'Role',
    metadata: {
      labels: {
        app: params.name,
      },
      name: params.name,
    },
    rules: [
      {
        apiGroups: [
          '*',
        ],
        resources: [
          '*',
        ],
        verbs: [
          '*',
        ],
      },
    ],
  },
  {
    apiVersion: 'rbac.authorization.k8s.io/v1',
    kind: 'RoleBinding',
    metadata: {
      labels: {
        app: params.name,
      },
      name: params.name,
    },
    roleRef: {
      apiGroup: 'rbac.authorization.k8s.io',
      kind: 'Role',
      name: params.name,
    },
    subjects: [
      {
        kind: 'ServiceAccount',
        name: params.name,
        namespace: env.namespace,
      },
    ],
  },
]

Lassen Sie uns auch die neuen Parameter in environments/base.libsonnet, die jetzt so aussieht:

local secrets = import '../secrets/base.libsonnet';

{
  components: {
    gitlabRunner: {
      name: 'gitlab-runner',
      values: {
        gitlabUrl: 'https://gitlab.com/',
        rbac: {
          create: true,
        },
        runnerRegistrationToken: secrets.runnerRegistrationToken,
        runners: {
          serviceAccountName: $.components.rbac.name,
          image: 'registry.gitlab.com/kvaps/docs.example.org/toolbox:v0.0.1',
        },
      },
    },
    rbac: {
      name: 'gitlab-runner-deploy',
    },
  },
}

Bitte beachten Sie $.components.rbac.name verweist auf name fĂŒr das Element rbac

Lassen Sie uns ĂŒberprĂŒfen, was sich geĂ€ndert hat:

qbec diff default

und unsere Änderungen in Kubernetes anwenden:

qbec apply default

Vergessen Sie auch nicht, unsere Änderungen in git zu committen:

cd ../..
git add dockerfiles/toolbox
git commit -m "Add Dockerfile for toolbox"
git add deploy/gitlab-runner
git commit -m "Configure gitlab-runner to use toolbox"

9. Unser erster Pipeline und die Erstellung von Images nach Tags

Im Hauptverzeichnis des Projekts erstellen wir .gitlab-ci.yml mit folgendem Inhalt:

.build_docker_image:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:debug-v0.15.0
    entrypoint: [""]
  before_script:
    - echo "{"auths":{"$CI_REGISTRY":{"username":"$CI_REGISTRY_USER","password":"$CI_REGISTRY_PASSWORD"}}}" > /kaniko/.docker/config.json

build_toolbox:
  extends: .build_docker_image
  script:
    - /kaniko/executor --cache --context $CI_PROJECT_DIR/dockerfiles/toolbox --dockerfile $CI_PROJECT_DIR/dockerfiles/toolbox/Dockerfile --destination $CI_REGISTRY_IMAGE/toolbox:$CI_COMMIT_TAG
  only:
    refs:
      - tags

build_website:
  extends: .build_docker_image
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  script:
    - /kaniko/executor --cache --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dockerfiles/website/Dockerfile --destination $CI_REGISTRY_IMAGE/website:$CI_COMMIT_TAG
  only:
    refs:
      - tags

Bitte beachten Sie, dass wir GIT_SUBMODULE_STRATEGY: normal fĂŒr die Aufgaben verwenden, bei denen Submodule vor der AusfĂŒhrung explizit initialisiert werden mĂŒssen.

Vergessen wir nicht, unsere Änderungen zu committen:

git add .gitlab-ci.yml
git commit -m "Automatisierung des Docker-Baus"

Ich denke, man kann dies ohne Bedenken als Version v0.0.1 bezeichnen und das Tag anhÀngen:

git tag v0.0.1

Wir werden jedes Mal Tags setzen, wenn wir eine neue Version veröffentlichen mĂŒssen. Tags in Docker-Images werden an Git-Tags gebunden. Jeder Push mit einem neuen Tag wird den Bau von Images mit diesem Tag initiieren.

Lassen Sie uns git push —tags, und werfen wir einen Blick auf unser erstes Pipeline:

Screenshot des ersten Pipelines

Wir probieren neue Werkzeuge zur Erstellung und Automatisierung von Deployments in Kubernetes aus.

Es ist wichtig zu beachten, dass das Bauen nach Tags fĂŒr die Erstellung von Docker-Images geeignet ist, jedoch nicht fĂŒr das Deployen von Anwendungen in Kubernetes. Da neue Tags auch fĂŒr Ă€ltere Commits vergeben werden können, wĂŒrde die Initialisierung der Pipeline fĂŒr diese zu einem Deployment der alten Version fĂŒhren.

Um dieses Problem zu lösen, wird normalerweise der Bau von Docker-Images an Tags gebunden, wĂ€hrend das Deployment der Anwendung an einen Branch knĂŒpft master, in dem die Versionen der erstellten Images festgelegt sind. In diesem Fall können Sie ein Rollback einfach durch einen Revert master-Branch durchfĂŒhren.

10. Automatisierung des Deployments

Damit der Gitlab Runner unsere Geheimnisse entschlĂŒsseln kann, mĂŒssen wir den Repository-SchlĂŒssel exportieren und ihm in unseren CI-Umgebungsvariablen hinzufĂŒgen:

git crypt export-key /tmp/docs-repo.key
base64 -w0 /tmp/docs-repo.key; echo

den erhaltenen String speichern wir in Gitlab, indem wir zu den Einstellungen unseres Projekts gehen:
Einstellungen —> CI / CD —> Variablen

Und wir erstellen eine neue Variable:

Typ
SchlĂŒssel
Wert
GeschĂŒtzt
Maskiert
Bereich

Datei
GITCRYPT_KEY
<your string>
true (wÀhrend des Trainings kann auch false)
true
Alle Umgebungen

Screenshot der hinzugefĂŒgten Variable

Wir probieren neue Werkzeuge zur Erstellung und Automatisierung von Deployments in Kubernetes aus.

Jetzt aktualisieren wir unser .gitlab-ci.yml indem wir hinzufĂŒgen:

.deploy_qbec_app:
  stage: deploy
  only:
    refs:
      - master

deploy_gitlab_runner:
  extends: .deploy_qbec_app
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  before_script:
    - base64 -d "$GITCRYPT_KEY" | git-crypt unlock -
  script:
    - qbec apply default --root deploy/gitlab-runner --force:k8s-context __incluster__ --wait --yes

deploy_website:
  extends: .deploy_qbec_app
  script:
    - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes

Hier haben wir einige neue Optionen fĂŒr qbec verwendet:

  • —root some/app — bestimmt das Verzeichnis der spezifischen Anwendung
  • —force:k8s-context __incluster__ — ist eine magische Variable, die sagt, dass das Deployment im selben Cluster stattfindet, in dem der gitlab-runner lĂ€uft. Dies ist notwendig, da qbec ansonsten versuchen wĂŒrde, einen passenden Kubernetes-Server in Ihrer kubeconfig zu finden.
  • —wait — zwingt qbec abzuwarten, bis die erstellten Ressourcen den Status 'Bereit' erreicht haben und erst dann erfolgreich mit einem Exit-Code terminiert wird.
  • —yes — deaktiviert einfach die interaktive Shell. Sind Sie sicher? beim Deployment.

Vergessen wir nicht, unsere Änderungen zu committen:

git add .gitlab-ci.yml
git commit -m "Automatisierung des Deployments"

Und danach git push werden wir sehen, wie unsere Anwendungen deployed wurden:

Screenshot des zweiten Pipelines

Wir probieren neue Werkzeuge zur Erstellung und Automatisierung von Deployments in Kubernetes aus.

11. Artefakte und Builds beim Push in den Master

In der Regel reichen die oben beschriebenen Schritte aus, um fast jeden Mikrodienst zu bauen und zu liefern, aber wir wollen nicht bei jedem Update der Website ein Tag setzen. Daher werden wir einen dynamischeren Ansatz wÀhlen und das Deployment nach Digest im Master-Zweig konfigurieren.

Die Idee ist einfach: Unser Image , aber in Zukunft planen wir, verschiedene Kopien wird jedes Mal neu gebaut, wenn ein Push in master, und danach automatisch in Kubernetes deployed.

Lassen Sie uns diese beiden Jobs in unserem .gitlab-ci.yml:

build_website:
  extends: .build_docker_image
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  script:
    - mkdir -p $CI_PROJECT_DIR/artifacts
    - /kaniko/executor --cache --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dockerfiles/website/Dockerfile --destination $CI_REGISTRY_IMAGE/website:$CI_COMMIT_REF_NAME --digest-file $CI_PROJECT_DIR/artifacts/website.digest
  artifacts:
    paths:
      - artifacts/
  only:
    refs:
      - master
      - tags

deploy_website:
  extends: .deploy_qbec_app
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST"

Bitte beachten Sie, dass wir den Branch master zu refs fĂŒr den Job build_website hinzugefĂŒgt haben und wir jetzt verwenden anstatt $CI_COMMIT_REF_NAME$CI_COMMIT_TAG, also lösen wir uns von Tags in Git und werden jetzt das Image mit dem Namen des Branches pushen, der den Pipeline-Job initialisiert hat. Es ist erwĂ€hnenswert, dass dies auch mit Tags funktionieren wird, was uns ermöglicht, Snapshot-Versionen der Website im Docker-Registry zu speichern.

Wenn der Name des Docker-Tags fĂŒr die neue Version der Website konstant bleiben kann, mĂŒssen wir dennoch Änderungen fĂŒr Kubernetes dokumentieren, andernfalls wird die Anwendung einfach nicht aus dem neuen Image neu bereitgestellt, da keine Änderungen im Deployment-Manifest erkannt werden.

Option —vm:ext-str digest="$DIGEST" fĂŒr qbec — ermöglicht das Übergeben einer externen Variablen in jsonnet. Wir möchten, dass unsere Anwendung mit jeder Version neu bereitgestellt wird. Wir können hier nicht mehr den Namen des Tags verwenden, der jetzt konstant bleiben könnte, da wir an einer bestimmten Version des Images festhalten und den Deploy bei dessen Änderung auslösen mĂŒssen.

Hier hilft uns die Möglichkeit von Kaniko, den Digest des Images in einer Datei zu speichern (Option —digest-file)
Dann ĂŒbergeben wir diese Datei und lesen sie wĂ€hrend des Deploys.

Wir aktualisieren die Parameter fĂŒr unser deploy/website/environments/base.libsonnet das jetzt so aussehen wird:

{
  components: {
    website: {
      name: 'example-docs',
      image: 'registry.gitlab.com/kvaps/docs.example.org/website@' + std.extVar('digest'),
      replicas: 1,
      containerPort: 80,
      servicePort: 80,
      nodeSelector: {},
      tolerations: [],
      ingressClass: 'nginx',
      domain: 'docs.example.org',
    },
  },
}

Fertig, nun wird jeder Commit in master einen Docker-Image-Build fĂŒr , aber in Zukunft planen wir, verschiedene Kopien, und anschließend wird es in Kubernetes bereitgestellt.

Vergessen wir nicht, unsere Änderungen zu committen:

git add .
git commit -m "Dynamische Build-Konfiguration"

ÜberprĂŒfen wir, nach git push sollten wir etwas Ähnliches sehen:

Screenshot des Pipelines fĂŒr master

Wir probieren neue Werkzeuge zur Erstellung und Automatisierung von Deployments in Kubernetes aus.

Im Grunde ist es nicht nötig, den gitlab-runner bei jedem Push neu bereitzustellen, solange sich an seiner Konfiguration nichts geÀndert hat. Lassen Sie uns das in .gitlab-ci.yml:

deploy_gitlab_runner:
  extends: .deploy_qbec_app
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  before_script:
    - base64 -d "$GITCRYPT_KEY" | git-crypt unlock -
  script:
    - qbec apply default --root deploy/gitlab-runner --force:k8s-context __incluster__ --wait --yes
  only:
    changes:
      - deploy/gitlab-runner/**/*

changes ĂŒberwachen, um Änderungen an deploy/gitlab-runner/ zu verfolgen und unsere Aufgabe nur bei Vorhandensein solcher auszulösen.

Vergessen wir nicht, unsere Änderungen zu committen:

git add .gitlab-ci.yml
git commit -m "Reduzierung des Deploys des gitlab-runner"

git push, das ist besser:

Screenshot des aktualisierten Pipelines

Wir probieren neue Werkzeuge zur Erstellung und Automatisierung von Deployments in Kubernetes aus.

12. Dynamische Umgebungen

Es ist an der Zeit, unseren Pipeline mit dynamischen Umgebungen zu bereichern.

Zuerst aktualisieren wir die Aufgabe build_website in unserem .gitlab-ci.yml, indem wir den Block onlyentfernen, was Gitlab dazu bringt, sie bei jedem Commit in jedem Branch auszulösen:

build_website:
  extends: .build_docker_image
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  script:
    - mkdir -p $CI_PROJECT_DIR/artifacts
    - /kaniko/executor --cache --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dockerfiles/website/Dockerfile --destination $CI_REGISTRY_IMAGE/website:$CI_COMMIT_REF_NAME --digest-file $CI_PROJECT_DIR/artifacts/website.digest
  artifacts:
    paths:
      - artifacts/

Dann aktualisieren wir die Aufgabe deploy_website, fĂŒgen wir einen Block hinzu Umgebung:

deploy_website:
  extends: .deploy_qbec_app
  environment:
    name: prod
    url: https://docs.example.org
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST"

Das ermöglicht Gitlab, den Job mit prod der Umgebung zu verknĂŒpfen und den richtigen Link dafĂŒr auszugeben.

Jetzt fĂŒgen wir noch zwei Jobs hinzu:

deploy_website:
  extends: .deploy_qbec_app
  environment:
    name: prod
    url: https://docs.example.org
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST"

deploy_review:
  extends: .deploy_qbec_app
  environment:
    name: review/$CI_COMMIT_REF_NAME
    url: http://$CI_ENVIRONMENT_SLUG.docs.example.org
    on_stop: stop_review
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply review --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST" --vm:ext-str subdomain="$CI_ENVIRONMENT_SLUG" --app-tag "$CI_ENVIRONMENT_SLUG"
  only:
    refs:
    - branches
  except:
    refs:
      - master

stop_review:
  extends: .deploy_qbec_app
  environment:
    name: review/$CI_COMMIT_REF_NAME
    action: stop
  stage: deploy
  before_script:
    - git clone "$CI_REPOSITORY_URL" master
    - cd master
  script:
    - qbec delete review --root deploy/website --force:k8s-context __incluster__ --yes --vm:ext-str digest="$DIGEST" --vm:ext-str subdomain="$CI_ENVIRONMENT_SLUG" --app-tag "$CI_ENVIRONMENT_SLUG"
  variables:
    GIT_STRATEGY: none
  only:
    refs:
    - branches
  except:
    refs:
      - master
  when: manual

Diese werden bei einem Push in beliebige Branches außer master ausgelöst und werden die Vorschauversion der Website bereitstellen.

Wir sehen die neue Option fĂŒr qbec: —app-tag — sie ermöglicht das Taggen der bereitgestellten Versionen der Anwendung und funktioniert nur im Rahmen dieses Tags; bei der Erstellung und Zerstörung von Ressourcen in Kubernetes wird qbec nur mit diesen arbeiten.
So können wir eine separate Umgebung fĂŒr jedes Review vermeiden und einfach dieselbe wiederverwenden.

Hier verwenden wir ebenfalls qbec apply review, statt qbec apply default — das ist der Moment, in dem wir versuchen werden, die Unterschiede fĂŒr unsere Umgebungen (Review und Default) zu beschreiben:

FĂŒgen wir hinzu review Umgebung in deploy/website/qbec.yaml

spec:
  environments:
    review:
      defaultNamespace: docs
      server: https://kubernetes.example.org:8443

Dann erklÀren wir sie in deploy/website/params.libsonnet:

local env = std.extVar('qbec.io/env');
local paramsMap = {
  _: import './environments/base.libsonnet',
  default: import './environments/default.libsonnet',
  review: import './environments/review.libsonnet',
};

if std.objectHas(paramsMap, env) then paramsMap[env] else error 'environment ' + env + ' not defined in ' + std.thisFile

Und wir werden benutzerdefinierte Parameter dafĂŒr in deploy/website/environments/review.libsonnet:

// this file has the param overrides for the default environment
local base = import './base.libsonnet';
local slug = std.extVar('qbec.io/tag');
local subdomain = std.extVar('subdomain');

base {
  components+: {
    website+: {
      name: 'example-docs-' + slug,
      domain: subdomain + '.docs.example.org',
    },
  },
}

Lassen Sie uns auch einen genaueren Blick auf den Job stop_review, dieser wird ausgelöst, wenn der Branch gelöscht wird, und damit Gitlab nicht versucht, ein Checkout darauf zu machen, wird verwendet GIT_STRATEGY: none, spĂ€ter klonen wir master-den Branch und löschen das Review ĂŒber ihn.
Ein wenig kompliziert, aber einen schöneren Weg habe ich bisher nicht gefunden.
Eine alternative Option könnte sein, jedes Review in einen separaten Namespace zu deployen, den man jederzeit komplett löschen kann.

Vergessen wir nicht, unsere Änderungen zu committen:

git add .
git commit -m "Automatische ÜberprĂŒfung aktivieren"

git push, git checkout -b test, git push origin test, ĂŒberprĂŒfen wir:

Screenshot der erstellten Umgebungen in Gitlab

Wir probieren neue Werkzeuge zur Erstellung und Automatisierung von Deployments in Kubernetes aus.

Funktioniert alles? — perfekt, wir löschen unseren Testbranch: git checkout master, git push origin :test, ĂŒberprĂŒfen wir, ob die Jobs zum Löschen der Umgebung fehlerfrei durchgefĂŒhrt wurden.

Hier möchte ich gleich klarstellen, dass jeder Entwickler im Projekt Branches erstellen kann; er kann auch die .gitlab-ci.yml Datei Àndern und auf geheime Variablen zugreifen.
Daher wird dringend empfohlen, deren Nutzung nur fĂŒr geschĂŒtzte Branches zu erlauben, beispielsweise in master, oder ein separates Set von Variablen fĂŒr jede Umgebung zu erstellen.

13. Review Apps

Review Apps Dies ist eine Funktion von GitLab, die es ermöglicht, fĂŒr jede Datei im Repository einen Button fĂŒr die schnelle Ansicht in der bereitgestellten Umgebung hinzuzufĂŒgen.

Um diese Buttons erscheinen zu lassen, muss eine Datei .gitlab/route-map.yml erstellt und darin alle Pfadtransformationen beschrieben werden. In unserem Fall wird das sehr einfach sein:

# Indices
- source: /content/(.+?)_index.(md|html)/ 
  public: '1'

# Pages
- source: /content/(.+?).(md|html)/ 
  public: '1/'

Vergessen wir nicht, unsere Änderungen zu committen:

git add .gitlab/
git commit -m "Review Apps aktivieren"

git push, und wir ĂŒberprĂŒfen:

Screenshot des Review App-Buttons

Wir probieren neue Werkzeuge zur Erstellung und Automatisierung von Deployments in Kubernetes aus.

Job ist erledigt!

Quellcode des Projekts:

Danke fĂŒr Ihre Aufmerksamkeit, ich hoffe, es hat Ihnen gefallen Wir probieren neue Werkzeuge zur Erstellung und Automatisierung von Deployments in Kubernetes aus.

Quelle: habr.com

ZuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz kaufen, VPS VDS Server đŸ”„ ZuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz kaufen, VPS VDS Server - ProHoster