
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 , die automatisch aus 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
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.orgUnd gleichzeitig ein Git-Repository:
cd docs.example.org
git initUnser 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 , 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/learnSo 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 serverUnd unter der Adresse 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

Um die Website zu generieren, reicht es, Folgendes auszufĂŒhren:
hugoDer Inhalt des Verzeichnisses Alles im Ordner wird deine Website sein.
Ja, ĂŒbrigens, lass uns das gleich in .gitignore:
echo /public > .gitignoreVergessen 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 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, , 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.1Wo 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

4. EinfĂŒhrung in 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 deploylassen Sie uns unsere erste Anwendung initialisieren:
qbec init website
cd websiteJetzt sieht die Struktur unserer Anwendung so aus:
.
âââ components
âââ environments
â âââ base.libsonnet
â âââ default.libsonnet
âââ params.libsonnet
âââ qbec.yamlsehen 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 defaultIm Ergebnis werden Sie die gerenderten yaml-Manifeste sehen, die im Cluster default angewendet werden.
Ausgezeichnet, jetzt wenden wir an:
qbec apply defaultIm 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 defaultum 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:
- 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.

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-runnerDieses 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-runnerJetzt 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.
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 defaultWenn alles in Ordnung ist, können wir unser zuvor ĂŒber Helm bereitgestelltes Release löschen:
helm uninstall gitlab-runnerund es ĂŒber qbec erneut bereitstellen:
qbec apply default7. EinfĂŒhrung in 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 initWenn 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.comAuf 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/keyfileSo 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 !diffWie 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 -eIm 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 lockund 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 unlock8. 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/helmWie 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-runnerund 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 defaultund unsere Ănderungen in Kubernetes anwenden:
qbec apply defaultVergessen 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:
- tagsBitte 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.1Wir 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

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; echoden 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

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 --yesHier 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

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

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

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: manualDiese 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:8443Dann 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.thisFileUnd 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

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
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

Job ist erledigt!
Quellcode des Projekts:
- in Gitlab:
- in GitHub:
Danke fĂŒr Ihre Aufmerksamkeit, ich hoffe, es hat Ihnen gefallen ![]()
Quelle: habr.com
