
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
