
Hallo! In letzter Zeit sind viele großartige Automatisierungswerkzeuge sowohl für die Erstellung von Docker-Images als auch für das Deployment in Kubernetes erschienen. Daher habe ich beschlossen, mich mit GitLab zu beschäftigen, um seine Möglichkeiten ausführlich zu erkunden und natürlich einen Pipeline einzurichten.
Inspiration für diese Arbeit war die Website , die automatisch aus generiert wird und bei jedem eingereichten Pull-Request generiert der Bot automatisch eine Vorschau der Website mit Ihren Änderungen und stellt einen Link zur Ansicht bereit.
Ich habe versucht, einen ähnlichen Prozess von Grund auf neu zu erstellen, jedoch vollständig auf GitLab CI und freien Werkzeugen basierend, die ich gewöhnt bin, für das Deployment von Anwendungen in Kubernetes zu nutzen. Heute werde ich Ihnen endlich detaillierter darüber berichten.
In diesem Artikel werden folgende Werkzeuge behandelt:
Hugo, qbec, kaniko, git-crypt und GitLab CI und der Erstellung dynamischer Umgebungen.
Inhalt
1. Einführung in Hugo
Als Beispiel für unser Projekt werden wir versuchen, eine Website zur Veröffentlichung von Dokumentationen zu erstellen, die auf Hugo basiert. Hugo ist ein statischer Content-Generator.
Für diejenigen, die mit statischen Generatoren nicht vertraut sind, möchte ich etwas mehr darüber erzählen. Im Gegensatz zu herkömmlichen Website-Engines mit Datenbanken und PHP, die Seiten bei Benutzeranfragen in Echtzeit generieren, funktionieren statische Generatoren etwas anders. Sie ermöglichen es, Quelltexte, in der Regel eine Sammlung von Dateien im Markdown-Format und Themenvorlagen, zu nehmen und diese in eine vollständig einsatzbereite Website zu kompilieren.
Das heißt, am Ende erhält man eine Verzeichnisstruktur und eine Sammlung generierter HTML-Dateien, die einfach auf einem beliebigen günstigen Hosting bereitgestellt werden können, um eine funktionierende Website zu erhalten.
Hugo kann lokal installiert werden, um es auszuprobieren:
Wir initialisieren eine neue Website:
hugo new site docs.example.orgUnd gleich noch ein Git-Repository:
cd docs.example.org
git initDerzeit ist unsere Website noch unberührt. Damit etwas darauf erscheint, müssen wir zuerst ein Thema einbinden. Ein Thema ist lediglich eine Sammlung von Vorlagen und Regeln, nach denen unsere Seite generiert wird.
Als Thema verwenden wir , das meiner Meinung nach hervorragend für eine Dokumentationswebsite geeignet ist.
Es ist besonders wichtig zu betonen, dass wir die Dateien des Themas nicht im Repository unseres Projekts speichern müssen. Stattdessen können wir sie einfach über git submodule:
einbinden: git submodule add https://github.com/matcornic/hugo-theme-learn themes/learnSo befinden sich in unserem Repository nur die Dateien, die direkt zu unserem Projekt gehören, während das eingebundene Thema als Verweis auf ein bestimmtes Repository und dessen Commit bleibt. Das bedeutet, dass wir es jederzeit aus der originalen Quelle abrufen können, ohne uns vor inkompatiblen Änderungen fürchten zu müssen.
Lassen Sie uns die Konfiguration anpassen: config.toml:
baseURL = "http://docs.example.org/"
languageCode = "en-us"
title = "Meine Dokumentationsseite"
theme = "learn"Bereits an diesem Punkt können wir den Server starten:
hugo serverund unter der Adresse Überprüfen Sie unsere neu erstellte Website. Alle Änderungen im Verzeichnis werden automatisch aktualisiert und sofort im Browser angezeigt – sehr praktisch!
Lassen Sie uns 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, genügt es, Folgendes auszuführen:
hugoInhalt des Verzeichnisses public/ wird Ihre Website sein.
Übrigens, lassen Sie uns das gleich in .gitignore:
echo /public > .gitignoreVergessen Sie nicht, unsere Änderungen zu committen:
git add .
git commit -m "Neue Website erstellt"2. Vorbereitung des Dockerfiles
Es ist an der Zeit, die Struktur unseres Repositories festzulegen. Normalerweise verwende ich so etwas wie:
.
├── deploy
│ ├── app1
│ └── app2
└── dockerfiles
├── image1
└── image2- dockerfiles/ enthalten Verzeichnisse mit Dockerfiles und alles, was nötig ist, um unsere Docker-Images zu erstellen.
- deploy/ enthält Verzeichnisse zum Deployment unserer Anwendungen in Kubernetes.
Somit erstellen wir unser erstes Dockerfile unter 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 die Dockerfile zwei FROM, diese Funktion nennt man und ermöglicht es, alles Unnötige aus dem finalen Docker-Image auszuschließen.
Somit wird unser finales Image nur darkhttpd (ein leichtgewichtiger HTTP-Server) und public/ — den Inhalt unserer statisch generierten Webseite enthalten.
Vergessen Sie 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-Image-Builder habe ich beschlossen, , da für dessen Betrieb kein Docker-Daemon erforderlich ist und der Build auf jedem beliebigen Rechner durchgeführt werden kann, während der Cache direkt im Registry gespeichert wird, wodurch die Notwendigkeit eines vollwertigen Persistenzspeichers entfällt.
Um das Image zu bauen, genügt es, einen Container mit kaniko executor zu starten und ihm den aktuellen Build-Kontext zu übergeben, was auch lokal über Docker möglich ist:
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.1Dabei ist registry.gitlab.com/kvaps/docs.example.org/website — der Name Ihres Docker-Images, das nach dem Build automatisch in die Docker-Registry gepusht wird.
Parameter —cache ermöglicht das Caching von Schichten im Docker-Registry. Im vorliegenden Beispiel werden sie gespeichert in registry.gitlab.com/kvaps/docs.example.org/website/cache, aber Sie können auch einen anderen Pfad mit dem Parameter —cache-repo.
Screenshot von docker-registry

4. Einführung in qbec
ist ein Bereitstellungstool, das es ermöglicht, die Manifeste Ihrer Anwendung deklarativ zu beschreiben und sie in Kubernetes bereitzustellen. Die Verwendung von Jsonnet als Hauptsyntax vereinfacht die Beschreibung von Unterschieden für mehrere Umgebungen erheblich und reduziert nahezu vollständig die Wiederholung von Code.
Dies kann besonders relevant sein, wenn Sie eine Anwendung in mehrere Cluster mit unterschiedlichen Parametern bereitstellen müssen und diese deklarativ in Git beschreiben möchten.
Qbec ermöglicht auch das Rendern von Helm-Charts, indem die erforderlichen Parameter übergeben werden, sodass sie wie normale Manifeste behandelt werden können. Dies ermöglicht verschiedene Mutationen, was wiederum die Notwendigkeit eliminiert, ChartMuseum zu verwenden. Das bedeutet, dass Charts direkt aus Git gespeichert und gerendert werden können, wo sie auch hingehören.
Wie bereits erwähnt, werden wir alle Deployments im Verzeichnis speichern deploy/:
mkdir deploy
cd deployLassen Sie uns unsere erste Anwendung initialisieren:
qbec init website
cd websiteDerzeit sieht die Struktur unserer Anwendung so aus:
.
├── components
├── environments
│ ├── base.libsonnet
│ └── default.libsonnet
├── params.libsonnet
└── qbec.yamlWerfen wir einen Blick auf 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 unserer aktuellen kubeconfig übernommen.
Beim Deployment in default die Umgebung wird qbec immer nur in den angegebenen Kubernetes-Cluster und in den vorgesehenen Namespace deployen, das bedeutet, dass Sie nicht mehr zwischen Kontexten und Namespaces wechseln müssen, um das Deployment durchzuführen.
Falls erforderlich, können Sie die Einstellungen in dieser Datei jederzeit aktualisieren.
Alle Ihre Umgebungen werden in qbec.yaml, und in der Datei params.libsonnet, wo angegeben wird, woher die Parameter für sie stammen.
Weiterhin sehen wir zwei Verzeichnisse:
- components/ — hier werden alle Manifeste für unsere Anwendung gespeichert, die sowohl im Jsonnet- als auch im herkömmlichen YAML-Format beschrieben werden können.
- environments/ — hier werden wir alle Variablen (Parameter) für unsere Umgebungen beschreiben.
Standardmäßig haben wir zwei Dateien:
- environments/base.libsonnet — diese enthält gemeinsame Parameter für alle Umgebungen.
- environments/default.libsonnet — enthält parameter, die für die Umgebung überschrieben werden. default
Lassen Sie uns öffnen environments/base.libsonnet 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',
},
},
}Ebenfalls unsere erste Komponente erstellen components/website.jsonnet:
local env = {
name: std.extVar('qbec.io/env'),
namespace: std.extVar('qbec.io/defaultNs'),
};
local p = import '../params.libsonnet';
local 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 gleich drei Kubernetes-Entitäten beschrieben, nämlich: Deployment, Service und Ingress. Auf Wunsch könnten wir sie in verschiedene Komponenten auslagern, aber zu diesem Zeitpunkt genügt uns eine.
Syntax jsonnet ist sehr ähnlich wie normales json; im Prinzip ist normales json bereits ein gültiges jsonnet, sodass es für eine Weile einfacher sein könnte, Online-Dienste wie yaml2json zu verwenden, um Ihr gewohntes yaml in json zu konvertieren. Alternativ, wenn Ihre Komponenten keine Variablen enthalten, können sie ganz gut als reguläres yaml beschrieben werden.
Wenn Sie mit jsonnet Ich empfehle Ihnen, ein Plugin für Ihren Editor zu installieren.
Zum Beispiel gibt es für vim ein Plugin, vim-jsonnet, das Syntax-Highlighting bietet und automatisch jsonnet fmt bei jedem Speichern ausführt (erfordert installiertes jsonnet).
Alles ist bereit, jetzt können wir mit dem Deployment beginnen:
Um zu sehen, was wir erreicht haben, führen wir aus:
qbec show defaultIn der Ausgabe sehen Sie die gerenderten yaml-Manifeste, die im Cluster default angewendet werden.
Ausgezeichnet, jetzt wenden wir an:
qbec apply defaultIn der Ausgabe sehen Sie immer, was in Ihrem Cluster durchgeführt wird; qbec wird Sie bitten, den Änderungen zuzustimmen, indem Sie y Ihre Absichten bestätigen können.
Die Anwendung ist nun bereit und erfolgreich bereitgestellt!
Bei Änderungen können Sie jederzeit ausführen:
qbec diff defaultum zu sehen, wie sich diese Änderungen auf die aktuelle Bereitstellung auswirken
Vergessen Sie nicht, unsere Änderungen zu committen:
cd ../..
git add deploy/website
git commit -m "Deploy für die Website hinzufügen"5. Wir testen den GitLab-Runner mit dem Kubernetes-Executor
Bis vor kurzem habe ich nur den normalen gitlab-runner auf einer vorbereiteten Maschine (LXC-Container) mit Shell- oder Docker-Executor verwendet. Ursprünglich hatten wir mehrere solche Runner, die global in unserem GitLab definiert waren. Sie sammelten Docker-Images für alle Projekte.
Aber wie sich gezeigt hat, ist diese Option nicht die ideale, sowohl in Bezug auf die Praktikabilität als auch auf die Sicherheit. Es ist viel besser und ideologisch sinnvoller, separate Runner für jedes Projekt oder sogar für jede Umgebung bereitzustellen.
Glücklicherweise ist das kein Problem, denn nun werden wir bereitstellen gitlab-runner direkt als Teil unseres Projekts direkt in Kubernetes.
GitLab bietet ein vorgefertigtes Helm-Chart für die Bereitstellung des GitLab-Runners in Kubernetes. Das Einzige, was Sie brauchen, ist, den Registration Token für unser Projekt in Einstellungen —> CI / CD —> Runner zu finden und ihn an Helm weiterzugeben:
helm repo add gitlab https://charts.gitlab.io
helm install gitlab-runner
--set gitlabUrl=https://gitlab.com
--set runnerRegistrationToken=yga8y-jdCusVDn_t4Wxc
--set rbac.create=true
gitlab/gitlab-runnerWobei:
- – Adresse Ihres Gitlab-Servers.
- yga8y-jdCusVDn_t4Wxc – Registrierungstoken für Ihr Projekt.
- rbac.create=true – gewährt dem Runner die erforderlichen Berechtigungen, um Pods für die Ausführung unserer Aufgaben mit dem Kubernetes-Executor zu erstellen.
Wenn alles richtig gemacht wurde, sollten Sie den registrierten Runner im Abschnitt Runners, in den Einstellungen Ihres Projekts sehen.
Screenshot des hinzugefügten Runners

So einfach? – Ja, so einfach! Keine Probleme mehr mit der manuellen Registrierung von Runners; ab jetzt werden Runners automatisch erstellt und gelöscht.
6. Deployment von Helm-Charts mit QBEC
Da wir beschlossen haben, es als gitlab-runner Teil unseres Projekts zu betrachten, ist es an der Zeit, es in unserem Git-Repository zu beschreiben.
Wir könnten es als separates Modul beschreiben website, aber in Zukunft planen wir, verschiedene Kopien sehr häufig bereitzustellen, im Gegensatz zu website , das nur einmal pro Kubernetes-Cluster bereitgestellt wird. Lassen Sie uns also eine separate Anwendung dafür initialisieren: gitlab-runnercd deploy qbec init gitlab-runner cd gitlab-runner
cd bereitstellen
qbec init gitlab-runner
cd gitlab-runnerDieses Mal werden wir die Kubernetes-Entitäten nicht manuell beschreiben, sondern einen fertigen Helm-Chart verwenden. Ein Vorteil von qbec ist die Möglichkeit, Helm-Charts direkt aus einem Git-Repository zu rendern.
Lassen Sie uns das mit einem git submodule hinzufügen:
git submodule add https://gitlab.com/gitlab-org/charts/gitlab-runner vendor/gitlab-runnerJetzt enthält das Verzeichnis vendor/gitlab-runner unser Repository mit dem Chart für gitlab-runner.
Auf ähnliche Weise können auch andere Repositories eingebunden werden, zum Beispiel das komplette Repository mit den offiziellen Charts.
Lassen Sie 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,
}
)Als erstes Argument an expandHelmTemplate übergibt man den Pfad zum Chart, dann sind es params.values, die wir aus den Umgebungsparametern nehmen, gefolgt von einem Objekt mit
- nameTemplate — dem Namen der Bereitstellung,
- namespace — dem 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:
lokale 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 wir holen das aus einer externen Datei secrets/base.libsonnet, lassen Sie uns ihn erstellen:
{
runnerRegistrationToken: 'yga8y-jdCusVDn_t4Wxc',
}Lassen Sie 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 erneut bereitstellen, jetzt jedoch über qbec:
qbec apply default7. Einführung in git-crypt
ist ein Tool, das eine transparente Verschlüsselung für Ihr Repository einrichtet.
Derzeit sieht die Struktur unseres Verzeichnisses für gitlab-runner folgendermaßen aus:
.
├── components
│ ├── gitlab-runner.jsonnet
├── environments
│ ├── base.libsonnet
│ └── default.libsonnet
├── params.libsonnet
├── qbec.yaml
├── secrets
│ └── base.libsonnet
└── vendor
└── gitlab-runner (Submodul)Aber es ist nicht sicher, Geheimnisse in Git zu speichern, oder? Daher müssen wir diese ordnungsgemäß verschlüsseln.
Normalerweise macht es keinen Sinn, nur für eine Variable. Sie können Geheimnisse in qbec und über Umgebungsvariablen Ihres CI-Systems übergeben.
Es ist jedoch wichtig zu beachten, dass es auch komplexere Projekte gibt, die wesentlich mehr Geheimnisse enthalten können. Es wäre äußerst schwierig, all diese über Umgebungsvariablen zu übermitteln.Darüber hinaus wäre es mir in diesem Fall nicht gelungen, Ihnen über dieses großartige Werkzeug zu erzählen, wie git-crypt.
git-crypt das darüber hinaus auch praktisch ist, da es die gesamte Historie der Geheimnisse speichert und es ermöglicht, zu vergleichen, zu mergen und Konflikte zu lösen, genau wie wir es von Git gewohnt sind.
Nach der Installation müssen wir zunächst Schlüssel für unser Repository generieren: git-crypt 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
So können Sie dieses Repository jederzeit mit Ihrem privaten Schlüssel entschlüsseln.Sollten Sie keinen PGP-Schlüssel haben und nicht planen, einen zu erstellen, können Sie einen anderen Weg wählen und den Schlüssel des Projekts exportieren:
git crypt export-key /path/to/keyfile
Auf diese Weise kann jeder, der über die exportiertekeyfile verfügt, Ihr Repository entschlüsseln. Es ist Zeit, unser erstes Geheimnis einzurichten.
Ich erinnere daran, dass wir uns weiterhin im Verzeichnis
deploy/gitlab-runner/ bereitstellen/gitlab-runner/, wo wir ein Verzeichnis haben secrets/, lassen Sie uns alle Dateien darin verschlüsseln. Dazu erstellen wir eine Datei secrets/.gitattributes mit folgendem Inhalt:
* filter=git-crypt diff=git-crypt
.gitattributes !filter !diffWie aus dem Inhalt ersichtlich, werden alle Dateien mit dem Muster * durchlaufen git-crypt, mit Ausnahme von .gitattributes
Das können wir überprüfen, indem wir ausführen:
git crypt status -eAls Ergebnis erhalten wir eine Liste aller Dateien im Repository, für die die Verschlüsselung aktiviert ist.
Das war's, jetzt können wir unsere Änderungen ohne Bedenken 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 in binäre Objekte umgewandelt, die nicht gelesen werden können.
Um das Repository zu entschlüsseln, führen Sie aus:
git crypt unlock8. Erstellen eines Toolbox-Images
Das Toolbox-Image ist ein solches Image mit allen Werkzeugen, die wir für das Deployment unseres Projekts verwenden werden. Es wird vom GitLab-Runner für die Durchführung typischer Deployment-Aufgaben verwendet.
Hier ist alles einfach, wir erstellen ein neues 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 benötigen hier nur kubectl, aber möglicherweise möchten Sie damit während der Pipeline-Konfiguration experimentieren.
Um mit Kubernetes kommunizieren und Deployments durchführen zu können, müssen wir eine Rolle für die vom gitlab-runner generierten Pods einrichten.
Dafür wechseln 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, der 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 prüfen, was sich geändert hat:
qbec diff defaultund wenden Sie unsere Änderungen in Kubernetes an:
qbec apply defaultVergessen Sie nicht, unsere Änderungen in git zu committen:
cd ../..
git add dockerfiles/toolbox
git commit -m "Dockerfile für Toolbox hinzufügen"
git add deploy/gitlab-runner
git commit -m "GitLab-Runner für die Nutzung der Toolbox konfigurieren"9. Unsere erste Pipeline und das Erstellen von Images nach Tags
Im Root-Verzeichnis 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:
- tagsBeachten Sie, dass wir verwenden GIT_SUBMODULE_STRATEGY: normal für die Jobs, in denen die Submodule vor der Ausführung explizit initialisiert werden müssen.
Vergessen Sie nicht, unsere Änderungen zu committen:
git add .gitlab-ci.yml
git commit -m "Automatisierung des Docker-Baus"Ich denke, wir können dies ohne Bedenken als Version v0.0.1 bezeichnen und das Tag hinzufügen:
git tag v0.0.1Tags werden wir jedes Mal setzen, wenn wir eine neue Version veröffentlichen müssen. Die Tags in den Docker-Images werden an die Git-Tags gebunden. Jeder Push mit einem neuen Tag wird den Build von Images mit diesem Tag initialisieren.
Führen wir aus git push —tags, und sehen wir uns unsere erste Pipeline an:
Screenshot der ersten Pipeline

Es ist wichtig zu beachten, dass das Bauen nach Tags für das Erstellen von Docker-Images geeignet ist, jedoch nicht für das Deployment von Anwendungen in Kubernetes. Da neue Tags auch für alte Commits vergeben werden können, würde in diesem Fall die Initialisierung der Pipeline für diese Tags zu einem Deployment der alten Version führen.
Um dieses Problem zu lösen, werden normalerweise der Build von Docker-Images an Tags gebunden, während das Deployment der Anwendung an den Branch master, in dem die Versionen der gebauten Images festgelegt sind, gebunden wird. Nur so können Sie einen Rollback einfach durch ein Revert master-Branch initialisieren.
10. Automatisierung des Deployments
Damit der GitLab-Runner unsere Secrets entschlüsseln kann, müssen wir den Schlüssel des Repositories exportieren und ihn in die Umgebungsvariablen unserer CI einfügen:
git crypt export-key /tmp/docs-repo.key
base64 -w0 /tmp/docs-repo.key; echoder erhaltenen Zeichenkette speichern wir in GitLab. Dazu gehen wir in die Einstellungen unseres Projekts:
Einstellungen —> CI / CD —> Variablen
Erstellen wir eine neue Variable:
Type
Schlüssel
Value
Geschützt
Maskiert
wie der Zugriff auf Daten oder das Durchführen bestimmter Aktionen.
Datei
GITCRYPT_KEY
<Ihr String>
true (zum Lernen kann man 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 genutzt:
- —root some/app — ermöglicht die Angabe des Verzeichnisses einer spezifischen Anwendung
- —force:k8s-context __incluster__ — ist eine magische Variable, die angibt, dass das Deployment im selben Cluster erfolgt, in dem der gitlab-runner ausgeführt wird. Dies ist notwendig, da qbec sonst versuchen würde, einen geeigneten Kubernetes-Server in Ihrer kubeconfig zu finden.
- —wait — zwingt qbec, auf den Zustand Ready der erstellten Ressourcen zu warten, bevor es mit einem erfolgreichen Exit-Code abschließt.
- —yes — schaltet einfach die interaktive Shell aus. Sind Sie sicher? bei der Bereitstellung.
Vergessen Sie nicht, unsere Änderungen zu committen:
git add .gitlab-ci.yml
git commit -m "Automatisiere Deployment"Und danach git push Wir werden sehen, wie unsere Anwendungen bereitgestellt wurden:
Screenshot der zweiten Pipeline

11. Artefakte und Build bei Push in master
In der Regel sind die oben beschriebenen Schritte ausreichend um fast jeden Microservice zu erstellen und bereitzustellen, aber wir möchten nicht jedes Mal ein Tag vergeben, wenn wir die Website aktualisieren müssen. Daher werden wir einen dynamischeren Ansatz wählen und das Deployment im master-Zweig nach Digest einrichten.
Die Idee ist einfach: Das Image unseres website wird jedes Mal neu erstellt, wenn ein Push in mastergeschieht, und danach automatisch in Kubernetes bereitgestellt.
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 statt $CI_COMMIT_REF_NAME$CI_COMMIT_TAG
Wenn der Name des Docker-Tags für die neue Version der Website konstant bleibt, müssen wir dennoch die Änderungen für Kubernetes beschreiben. Andernfalls wird es die Anwendung einfach nicht aus dem neuen Image redeployen, da keine Änderungen im Deployment-Manifest erkannt werden.
Option —vm:ext-str digest=»$DIGEST» für qbec — ermöglicht das Übergeben einer externen Variable in jsonnet. Wir möchten, dass mit jeder Veröffentlichung unserer Anwendung sie im Cluster redeployt wird. Wir können hier keinen Tag-Namen verwenden, der nun gleichbleibend sein könnte, da wir auf eine spezielle Version des Images angewiesen sein müssen und das Deployment bei ihrer Änderung triggern müssen.
Hier hilft uns die Möglichkeit von Kaniko, den Image-Digest in einer Datei zu speichern (Option —digest-file)
Dann werden wir diese Datei übergeben und zum Zeitpunkt des Deployments lesen.
Wir werden die Parameter für unser deploy/website/environments/base.libsonnet nun so aussehen:
{
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 löst jeder Commit die master Initialisierung des Docker-Image-Baus für website, und danach dessen Bereitstellung in Kubernetes.
Vergessen Sie nicht, unsere Änderungen zu committen:
git add .
git commit -m "Dynamischen Build konfigurieren"Lassen Sie uns prüfen, nach git push sollten wir etwas Ähnliches sehen:
Screenshot des Pipelines für master

Im Grunde genommen müssen wir den gitlab-runner nicht bei jedem Push neu bereitstellen, solange sich natürlich nichts in seiner Konfiguration 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 verfolgen, bereitstellen/gitlab-runner/ und wird unseren Job nur bei Vorhandensein solcher Änderungen auslösen.
Vergessen Sie nicht, unsere Änderungen zu committen:
git add .gitlab-ci.yml
git commit -m "Reduziere gitlab-runner Bereitstellung"git push, das klingt besser:
Screenshot des aktualisierten Pipelines

12. Dynamische Umgebungen
Es ist an der Zeit, unseren Pipeline mit dynamischen Umgebungen zu bereichern.
Zunächst aktualisieren wir den Job build_website in unserem .gitlab-ci.yml, indem wir den Block entfernen only, die GitLab dazu bringt, sie bei jedem Commit in jeden 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 den Job deploy_website, und fügen einen Block hinzu environment:
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 es GitLab, den Job mit prod der Umgebung zu verknüpfen und den richtigen Link dazu 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: manualSie werden bei jedem Push in andere Branches als master ausgeführt und deployen eine Vorschauversion der Website.
Wir sehen eine neue Option für qbec: —app-tag — sie ermöglicht es, die deployten Versionen der Anwendung zu taggen und nur innerhalb dieses Tags zu arbeiten. Bei der Erstellung und Löschung von Ressourcen in Kubernetes wird qbec nur mit diesen operieren.
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 genau der Moment, in dem wir versuchen werden, die Unterschiede zwischen unseren 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 deklarieren 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 'Umgebung ' + env + ' ist nicht in ' + std.thisFile + ' definiert'Und wir schreiben 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 bei der Löschung des Branches ausgelöst, und damit GitLab nicht versucht, darauf zuzugreifen, verwenden wir GIT_STRATEGY: none, später klonen wir master-den Branch und löschen das Review darüber.
Ein wenig kompliziert, aber ich habe noch keinen eleganteren Weg gefunden.
Eine alternative Möglichkeit könnte sein, jedes Review in einen separaten Namespace zu deployen, den man jederzeit vollständig löschen kann.
Vergessen Sie nicht, unsere Änderungen zu committen:
git add .
git commit -m "Aktivieren Sie das automatische Review"git push, git checkout -b test, git push origin test, wir überprüfen:
Screenshot der erstellten Umgebungen in Gitlab

Funktioniert alles? — großartig, wir löschen unseren Testbranch: git checkout master, git push origin :test, wir überprüfen, ob die Jobs zum Löschen der Umgebung ohne Fehler ausgefü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 Verwendung nur für geschützte Branches zuzulassen, zum Beispiel in master, oder ein separates Set von Variablen für jede Umgebung zu erstellen.
13. Review Apps
Das ist eine Funktion von Gitlab, die es ermöglicht, für jede Datei im Repository eine Schaltfläche für die schnelle Anzeige in der bereitgestellten Umgebung hinzuzufügen.
Um diese Schaltflächen anzuzeigen, muss eine Datei erstellt werden .gitlab/route-map.yml und 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 Sie nicht, unsere Änderungen zu committen:
git add .gitlab/
git commit -m "Review Apps aktivieren"git push, und wir überprüfen:
Screenshot der Review App-Schaltfläche

Job abgeschlossen!
Quellcode des Projekts:
- in Gitlab:
- in GitHub:
Vielen Dank für Ihre Aufmerksamkeit, ich hoffe, es hat Ihnen gefallen ![]()
Quelle: habr.com
