
Hallo! In letzter Zeit wurden viele coole Automatisierungstools zum Erstellen von Docker-Images und für die Bereitstellung auf Kubernetes veröffentlicht. In diesem Zusammenhang habe ich mich entschlossen, mit dem Gitlab zu spielen, seine Fähigkeiten zu untersuchen und natürlich die Pipeline zu konfigurieren.
Die Inspiration für diese Arbeit war die Site kubernetes.io , die automatisch aus Quellcodes generiert wird. Für jeden gesendeten Pool generiert der Roboter automatisch eine Vorschau-Version der Site mit Ihren Änderungen und bietet einen Link zum Anzeigen.
Ich habe versucht, einen ähnlichen Prozess von Grund auf neu zu erstellen, der jedoch vollständig auf Gitlab CI und kostenlosen Tools basiert, mit denen ich Anwendungen in Kubernetes bereitgestellt habe. Heute werde ich Ihnen endlich mehr darüber erzählen.
In dem Artikel werden Tools wie die folgenden behandelt:
Hugo , qbec , kaniko , git-crypt und GitLab CI bei der Erstellung dynamischer Umgebungen.
Inhalt
- Wir stellen Hugo vor
- Dockerfile vorbereiten
- Bekanntschaft mit Kaniko
- Wir stellen vor: qbec
- Versucht Gitlab-Runner mit Kubernetes-Executor
- Einsatz von Helmkarten mit qbec
- EinfĂĽhrung in die Git-Krypta
- Erstellen Sie ein Toolbox-Image
- Unsere erste Pipeline und Assemblierung von Bildern nach Tags
- Bereitstellungsautomatisierung
- Artefakte und Push-Build-in-Master
- Dynamische Umgebungen
- Apps ĂĽberprĂĽfen
1. Hugo vorstellen
Als Beispiel für unser Projekt werden wir versuchen, eine Website für die Veröffentlichung von Dokumentationen zu erstellen, die auf Hugo basieren. Hugo ist ein statischer Inhaltsgenerator.
Für diejenigen, die mit statischen Generatoren nicht vertraut sind, werde ich Ihnen etwas mehr über sie erzählen. Im Gegensatz zu regulären Datenbank-Site-Engines und einigen PHP-Engines, die nach Aufforderung durch einen Benutzer Seiten im laufenden Betrieb generieren, sind statische Generatoren etwas anders angeordnet. Sie ermöglichen es Ihnen, die Quelle, in der Regel eine Reihe von Dateien in den Markup- und Designvorlagen von Markdown, zu nehmen und sie dann zu einer vollständig fertigen Site zu kompilieren.
Das heißt, am Ausgang erhalten Sie eine Verzeichnisstruktur und eine Reihe von generierten HTML-Dateien, die einfach auf jedes billige Hosting hochgeladen werden können und eine funktionierende Site erhalten.
Hugo kann lokal installiert und ausprobiert werden:
Wir initialisieren die neue Site:
hugo new site docs.example.org
Und zur gleichen Zeit das Git-Repository:
cd docs.example.org git init
Bisher ist unsere Website makellos und damit etwas darauf erscheint, mĂĽssen wir zuerst ein Thema, ein Thema, verknĂĽpfen. Es handelt sich lediglich um eine Reihe von Vorlagen und voreingestellten Regeln, mit denen unsere Website generiert wird.
Als Thema verwenden wir Learn , das meiner Meinung nach am besten fĂĽr eine Site mit Dokumentation geeignet ist.
Ich möchte besonders darauf achten, dass wir keine Themendateien im Repository unseres Projekts speichern müssen, sondern sie einfach mit dem Git-Submodul verbinden können :
git submodule add https://github.com/matcornic/hugo-theme-learn themes/learn
Daher befinden sich in unserem Repository nur Dateien, die in direktem Zusammenhang mit unserem Projekt stehen, und das verknüpfte Thema bleibt in Form eines Links zu einem bestimmten Repository und wird darin festgeschrieben. Das heißt, es kann immer aus der Originalquelle abgerufen werden und hat keine Angst vor inkompatiblen Änderungen.
Fix config.toml config:
baseURL = "http://docs.example.org/" languageCode = "en-us" title = "My Docs Site" theme = "learn"
Bereits in dieser Phase können Sie Folgendes ausführen:
hugo server
Und unter http: // localhost: 1313 / überprüfen Sie unsere neu erstellte Site. Alle am Verzeichnis vorgenommenen Änderungen werden automatisch aktualisiert, und die geöffnete Seite im Browser ist sehr praktisch.
Versuchen wir, ein Deckblatt in content / _index.md zu erstellen:
# My docs site ## Welcome to the docs! You will be very smart :-)
Screenshot der neu erstellten Seite Um eine Site zu generieren, fĂĽhren Sie einfach Folgendes aus:
hugo
Der Inhalt des public / -Verzeichnisses ist Ihre Site.
Ja, ĂĽbrigens, fĂĽgen wir es sofort zu .gitignore hinzu :
echo /public > .gitignore
Vergessen Sie nicht, unsere Änderungen zu übernehmen:
git add . git commit -m "New site created"
2. Vorbereiten der Docker-Datei
Es ist Zeit, die Struktur unseres Repository zu bestimmen. Normalerweise benutze ich etwas wie:
. ├── deploy │ ├── app1 │ └── app2 └── dockerfiles ├── image1 └── image2
- dockerfiles / - enthält Verzeichnisse mit Dockerfiles und allem, was Sie zum Erstellen unserer Docker-Images benötigen.
- deploy / - enthält Verzeichnisse für die Bereitstellung unserer Anwendungen auf Kubernetes
Daher 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, enthält die Docker-Datei zwei FROMs. Diese Funktion wird als mehrstufige Erstellung bezeichnet und ermöglicht es Ihnen, alles Unnötige vom endgültigen Docker-Image auszuschließen.
Daher enthält das endgültige Image nur darkhttpd (einen einfachen HTTP-Server) und public / - den Inhalt unserer statisch generierten Site.
Vergessen Sie nicht, unsere Änderungen zu übernehmen:
git add dockerfiles/website git commit -m "Add Dockerfile for website"
3. Bekanntschaft mit Kaniko
Als Sammler von Docker-Images habe ich mich fĂĽr Kaniko entschieden , da fĂĽr die Arbeit kein Docker-Daemon erforderlich ist. Die Assembly selbst kann auf jedem Computer ausgefĂĽhrt und der Cache direkt in der Registrierung gespeichert werden, sodass kein dauerhafter Speicher mehr erforderlich ist .
Um das Image zu erstellen, starten Sie einfach den Container mit Kaniko Executor und übergeben Sie den aktuellen Build-Kontext an ihn. Dies können Sie lokal über Docker tun:
docker run -ti --rm \ -v $PWD:/workspace \ -v ~/.docker/config.json:/kaniko/.docker/config.json:ro \ gcr.io/kaniko-project/executor:v0.15.0 \ --cache \ --dockerfile=dockerfiles/website/Dockerfile \ --destination=registry.gitlab.com/kvaps/docs.example.org/website:v0.0.1
Dabei ist registry.gitlab.com/kvaps/docs.example.org/website der Name Ihres Docker-Image. Nach dem Zusammenbau wird es automatisch in das Docker-Register verschoben.
Mit dem Parameter --cache können Sie Layer in der Docker-Registrierung zwischenspeichern. Für das angegebene Beispiel werden sie in registry.gitlab.com/kvaps/docs.example.org/website/cache gespeichert. Mit dem Parameter --cache können Sie jedoch einen anderen Pfad angeben repo .
Screenshot Docker-Registry
4. Bekanntschaft mit qbec
Qbec ist ein Bereitstellungstool, mit dem Sie das Manifest Ihrer Anwendung deklarativ beschreiben und für Kubernetes bereitstellen können. Durch die Verwendung von Jsonnet als Hauptsyntax können Sie die Beschreibung von Unterschieden für mehrere Umgebungen erheblich vereinfachen und die Code-Wiederholbarkeit nahezu vollständig eliminieren.
Dies kann insbesondere dann der Fall sein, wenn Sie eine Anwendung in mehreren Clustern mit unterschiedlichen Parametern bereitstellen müssen und diese deklarativ in Git beschreiben möchten.
Mit Qbec können Sie Helm-Diagramme auch rendern, indem Sie ihnen die erforderlichen Parameter übergeben und sie anschließend ausführen. Außerdem können Sie reguläre Manifeste mit verschiedenen Mutationen erstellen, die auf sie angewendet werden können. Auf diese Weise muss das ChartMuseum nicht mehr verwendet werden. Das heißt, Sie können Diagramme direkt in git speichern und rendern, wo sie sich befinden.
Wie ich bereits sagte, speichern wir alle Bereitstellungen im Verzeichnis deploy / :
mkdir deploy cd deploy
Initialisieren wir unsere erste Anwendung:
qbec init website cd website
Nun sieht die Struktur unserer Anwendung so aus:
. ├── components ├── environments │ ├── base.libsonnet │ └── default.libsonnet ├── params.libsonnet └── qbec.yaml
Schauen Sie sich die Datei qbec.yaml an :
apiVersion: qbec.io/v1alpha1 kind: App metadata: name: website spec: environments: default: defaultNamespace: docs server: https://kubernetes.example.org:8443 vars: {}
Hier interessieren uns vor allem spec.environments , qbec hat bereits eine Standardumgebung fĂĽr uns angelegt und die Serveradresse sowie den Namespace aus unserer aktuellen kubeconfig ĂĽbernommen.
Jetzt wird qbec bei der Bereitstellung in der Standardumgebung immer nur im angegebenen Kubernetes-Cluster und im angegebenen Namespace bereitgestellt. Sie mĂĽssen also nicht mehr zwischen Kontexten und Namespaces wechseln, um eine Bereitstellung durchzufĂĽhren.
Bei Bedarf können Sie die Einstellungen in dieser Datei jederzeit aktualisieren.
Alle Ihre Umgebungen sind in qbec.yaml und in der Datei params.libsonnet beschrieben , in der angegeben ist, wo Sie die Parameter fĂĽr diese Umgebungen festlegen mĂĽssen.
Als nächstes sehen wir zwei Verzeichnisse:
- komponenten / - alle manifeste für unsere anwendung werden hier gespeichert, sie können sowohl in jsonnet als auch in normalen yaml-dateien beschrieben werden
- Umgebungen / - Hier werden alle Variablen (Parameter) fĂĽr unsere Umgebungen beschrieben.
Standardmäßig haben wir zwei Dateien:
- environ / base.libsonnet - enthält allgemeine Parameter für alle Umgebungen
- environ / default.libsonnet - Enthält Parameter, die für die Standardumgebung überschrieben werden
Ă–ffnen wir die Datei surroundings / base.libsonnet und fĂĽgen dort die Parameter fĂĽr unsere erste Komponente hinzu:
{ 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', }, }, }
Wir werden auch unsere erste Komponente components / website.jsonnet erstellen:
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 wurden sofort drei Kubernetes-Entitäten beschrieben: Deployment , Service und Ingress . Auf Wunsch können wir sie in verschiedene Komponenten aufteilen, aber in diesem Stadium reicht uns eine.
Die Syntax von jsonnet ist der von regulärem json sehr ähnlich, im Prinzip ist reguläres jsonnet bereits gültig. Daher ist es möglicherweise zunächst einfacher, Onlinedienste wie yaml2json zu verwenden, um Ihr übliches yaml nach json zu konvertieren, oder wenn Ihre Komponenten keine Variablen enthalten Sie können in Form von gewöhnlichem Yam beschrieben werden.
Wenn Sie mit jsonnet arbeiten, empfehle ich dringend, ein Plugin fĂĽr Ihren Editor zu installieren
FĂĽr vim gibt es beispielsweise ein vim-jsonnet-Plug-in , das die Syntaxhervorhebung aktiviert und bei jedem Speichern automatisch jsonnet fmt ausfĂĽhrt (dazu muss jsonnet installiert sein).
Alles ist fertig, jetzt können wir mit dem Deployment beginnen:
Um zu sehen, was passiert ist, machen wir Folgendes:
qbec show default
Am Ausgang sehen Sie gerenderte Yaml-Manifeste, die auf den Standardcluster angewendet werden.
Ok, jetzt bewerben:
qbec apply default
Am Ausgang sehen Sie immer, was in Ihrem Cluster getan wird. Qbec fordert Sie auf, die Änderungen zu akzeptieren. Durch Eingabe von y können Sie Ihre Absichten bestätigen.
Fertig, jetzt ist unsere Anwendung angedockt!
Wenn Sie Änderungen vornehmen, können Sie immer Folgendes tun:
qbec diff default
um zu sehen, wie sich diese Änderungen auf die aktuelle Bereitstellung auswirken
Vergessen Sie nicht, unsere Änderungen zu übernehmen:
cd ../.. git add deploy/website git commit -m "Add deploy for website"
5. Probieren Sie Gitlab-runner mit Kubernetes-executor aus
Bis vor kurzem habe ich auf einer vorbereiteten Maschine (LXC-Container) mit Shell oder Docker-Executor nur den üblichen Gitlab-Runner verwendet . Anfangs hatten wir mehrere dieser Läufer global in unserem Hitlab definiert. Sie sammelten Docker-Bilder für alle Projekte.
Aber wie die Praxis gezeigt hat, ist diese Option sowohl in Bezug auf die Praktikabilität als auch in Bezug auf die Sicherheit nicht optimal. Es ist viel besser und ideologisch korrekter, für jedes Projekt und sogar für jede Umgebung separate Läufer einzusetzen.
GlĂĽcklicherweise ist dies ĂĽberhaupt kein Problem, da wir gitlab-runner jetzt als Teil unseres Projekts direkt auf Kubernetes bereitstellen werden.
Gitlab bietet ein fertiges Steuerdiagramm für die Bereitstellung von gitlab-runner in Kubernetes. Alles was Sie tun müssen, ist das Registrierungs-Token für unser Projekt unter Einstellungen -> CI / CD -> Läufer herauszufinden und es zu übergeben.
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-runner
Wo:
- https://gitlab.com ist die Adresse Ihres Gitlab-Servers.
- yga8y-jdCusVDn_t4Wxc - Registrierungs-Token fĂĽr Ihr Projekt.
- rbac.create = true - Gibt dem Läufer die erforderliche Anzahl von 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 Läufer im Abschnitt Läufer in den Einstellungen Ihres Projekts sehen.
Screenshot des hinzugefügten Läufers Ist es so einfach - ja so einfach! Kein Ärger mehr mit der manuellen Registrierung von Läufern, von nun an werden Läufer automatisch erstellt und zerstört.
6. Bereitstellung von Helmkarten mit QBEC
Da wir beschlossen haben, gitlab-runner als Teil unseres Projekts zu betrachten, ist es an der Zeit, es in unserem Git-Repository zu beschreiben.
Wir könnten es als separate Komponente der Website bezeichnen , aber wir planen, in Zukunft sehr oft unterschiedliche Kopien der Website bereitzustellen , im Gegensatz zu gitlab-runner , das für jeden Kubernetes-Cluster nur einmal bereitgestellt wird. Also lasst uns eine separate Anwendung dafür initialisieren:
cd deploy qbec init gitlab-runner cd gitlab-runner
Dieses Mal werden wir Kubernetes Entitäten nicht manuell beschreiben, sondern ein fertiges Helm-Diagramm erstellen. Einer der Vorteile von qbec ist die Möglichkeit, Helm-Diagramme direkt aus dem Git-Repository zu rendern.
Lassen Sie es uns mit Git-Submodul einstecken:
git submodule add https://gitlab.com/gitlab-org/charts/gitlab-runner vendor/gitlab-runner
Jetzt enthält das Verzeichnis vendor / gitlab-runner unser Repository mit einem Diagramm für gitlab-runner.
Ebenso können Sie andere Repositorys, beispielsweise das gesamte Repository, mit den offiziellen Charts https://github.com/helm/charts verbinden
Beschreiben wir die Komponente 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 fĂĽr expandHelmTemplate ĂĽbergeben wir den Pfad zum Diagramm, dann params.values , die wir aus den Umgebungsparametern entnehmen , und dann ein Objekt mit
- nameTemplate - Versionsname
- Namespace - Der Namespace wurde an helm ĂĽbergeben
- thisFile - Erforderlicher Parameter, der den Pfad zur aktuellen Datei ĂĽbergibt
- verbose - Zeigt den Befehl helm template mit allen Argumenten beim Rendern des Diagramms an
Nun werden wir die Parameter fĂĽr unsere Komponente in environ / base.libsonnet beschreiben :
local secrets = import '../secrets/base.libsonnet'; { components: { gitlabRunner: { name: 'gitlab-runner', values: { gitlabUrl: 'https://gitlab.com/', rbac: { create: true, }, runnerRegistrationToken: secrets.runnerRegistrationToken, }, }, }, }
Beachten Sie das runnerRegistrationToken, das wir aus der Datei external secrets / base.libsonnet entnehmen. Erstellen wir es:
{ runnerRegistrationToken: 'yga8y-jdCusVDn_t4Wxc', }
ĂśberprĂĽfen Sie, ob alles funktioniert:
qbec show default
Wenn alles in Ordnung ist, können wir unsere frühere Version über die Helm-Version entfernen:
helm uninstall gitlab-runner
und bereitstellen, aber schon ĂĽber qbec:
qbec apply default
7. EinfĂĽhrung in Git-Crypt
Git-Crypt ist ein Tool, mit dem Sie eine transparente Verschlüsselung für Ihr Repository einrichten können.
Im Moment 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)
Geheimnisse in Git aufzubewahren ist aber nicht sicher, oder? Also mĂĽssen wir sie richtig verschlĂĽsseln.
Normalerweise ist dies aus Gründen einer einzelnen Variablen nicht immer sinnvoll. Sie können Geheimnisse an qbec und über die Umgebungsvariablen Ihres CI-Systems weitergeben.
Es ist jedoch anzumerken, dass es komplexere Projekte gibt, die viel mehr Geheimnisse enthalten können, und es äußerst schwierig sein wird, sie alle durch Umgebungsvariablen zu übertragen.
Außerdem würde ich Ihnen in diesem Fall kein so wunderbares Tool wie Git-Crypt vorstellen können .
git-crypt ist auch deshalb praktisch, weil Sie damit den gesamten Verlauf von Geheimnissen speichern sowie Konflikte auf die gleiche Weise vergleichen, zusammenführen und lösen können, wie wir dies bei Git getan haben.
Zunächst müssen wir nach der Installation von git-crypt Schlüssel für unser Repository generieren:
git crypt init
Wenn Sie einen PGP-Schlüssel haben, können Sie sich sofort als Mitbearbeiter 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.
Wenn Sie keinen PGP-Schlüssel haben und dieser nicht erwartet wird, können Sie den Projektschlüssel auch in die andere Richtung exportieren:
git crypt export-key /path/to/keyfile
Auf diese Weise kann jeder mit einer exportierten SchlĂĽsseldatei Ihr Repository entschlĂĽsseln.
Es ist Zeit, unser erstes Geheimnis aufzubauen.
Ich möchte Sie daran erinnern, dass wir uns noch im Verzeichnis deploy / gitlab-runner / befinden, in dem sich das Verzeichnis secrets / befindet. Verschlüsseln wir alle darin enthaltenen Dateien. Dazu erstellen wir die Datei secrets / .gitattributes mit dem folgenden Inhalt:
* filter=git-crypt diff=git-crypt .gitattributes !filter !diff
Wie Sie dem Inhalt entnehmen können, werden alle Dateien von mask * mit Ausnahme von .gitattributes selbst über git-crypt ausgeführt
Wir können dies überprüfen, indem wir Folgendes ausführen:
git crypt status -e
Am Ausgang 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 übernehmen:
cd ../.. git add . git commit -m "Add deploy for gitlab-runner"
Um das Repository zu blockieren, gehen Sie wie folgt vor:
git crypt lock
und dann verwandeln sich alle verschlüsselten Dateien in etwas Binäres, es wird unmöglich sein, sie zu lesen.
So entschlĂĽsseln Sie ein Repository:
git crypt unlock
Ein Toolbox-Image ist ein solches Image mit allen Tools, die wir zum Bereitstellen unseres Projekts verwenden. Es wird vom gitlab-Läufer verwendet, um typische Bereitstellungsaufgaben auszuführen.
Alles ist hier einfach, wir erstellen eine neue Docker-Datei / Toolbox / Dockerfile mit folgendem Inhalt:
FROM alpine:3.11 RUN apk add --no-cache git git-crypt RUN QBEC_VER=0.10.3 \ && wget -O- https://github.com/splunk/qbec/releases/download/v${QBEC_VER}/qbec-linux-amd64.tar.gz \ | tar -C /tmp -xzf - \ && mv /tmp/qbec /tmp/jsonnet-qbec /usr/local/bin/ RUN KUBECTL_VER=1.17.0 \ && wget -O /usr/local/bin/kubectl \ https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VER}/bin/linux/amd64/kubectl \ && chmod +x /usr/local/bin/kubectl RUN HELM_VER=3.0.2 \ && wget -O- https://get.helm.sh/helm-v${HELM_VER}-linux-amd64.tar.gz \ | tar -C /tmp -zxf - \ && mv /tmp/linux-amd64/helm /usr/local/bin/helm
Wie Sie sehen, installieren wir in diesem Image alle Dienstprogramme, die wir zum Bereitstellen unserer Anwendung verwendet haben. Wir brauchen hier kein Kubectl , aber Sie möchten vielleicht schon beim Einrichten der Pipeline damit spielen.
Außerdem müssen wir die Rolle für die von gitlab-runner generierten Pods konfigurieren, um mit Kubernetes kommunizieren und ein Deployment durchführen zu können.
Gehen Sie dazu mit gitlab-runner in das Verzeichnis:
cd deploy/gitlab-runner
und fĂĽge eine neue Komponente components / rbac.jsonnet hinzu :
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, }, ], }, ]
Wir werden auch die neuen Parameter in environ / base.libsonnet beschreiben , die jetzt so aussehen:
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', }, }, }
Hinweis $ .components.rbac.name bezieht sich auf den Namen der Komponente rbac
Lassen Sie uns überprüfen, was sich geändert hat:
qbec diff default
und wende unsere Änderungen auf Kubernetes an:
qbec apply default
Vergessen Sie auch nicht, unsere Änderungen an git vorzunehmen:
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. Unsere erste Pipeline und Assemblierung von Bildern nach Tags
Im Stammverzeichnis des Projekts erstellen wir die Datei .gitlab-ci.yml mit folgendem Inhalt:
.build_docker_image: stage: build image: name: gcr.io/kaniko-project/executor:debug-v0.15.0 entrypoint: [""] before_script: - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json build_toolbox: extends: .build_docker_image script: - /kaniko/executor --cache --context $CI_PROJECT_DIR/dockerfiles/toolbox --dockerfile $CI_PROJECT_DIR/dockerfiles/toolbox/Dockerfile --destination $CI_REGISTRY_IMAGE/toolbox:$CI_COMMIT_TAG only: refs: - tags build_website: extends: .build_docker_image variables: GIT_SUBMODULE_STRATEGY: normal script: - /kaniko/executor --cache --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dockerfiles/website/Dockerfile --destination $CI_REGISTRY_IMAGE/website:$CI_COMMIT_TAG only: refs: - tags
Bitte beachten Sie, dass wir GIT_SUBMODULE_STRATEGY verwenden: Normal fĂĽr Jobs, bei denen Sie Submodule vor der AusfĂĽhrung explizit initialisieren mĂĽssen.
Vergessen Sie nicht, unsere Änderungen zu übernehmen:
git add .gitlab-ci.yml git commit -m "Automate docker build"
Ich denke, Sie können es sicher Version v0.0.1 nennen und ein Tag hängen:
git tag v0.0.1
Wir werden Tags hängen, wenn wir eine neue Version veröffentlichen müssen. Tags in Docker-Bildern werden an Git-Tags gebunden. Jeder Push mit einem neuen Tag initialisiert die Assemblierung von Bildern mit diesem Tag.
FĂĽhren Sie git push - tags aus und sehen Sie sich unsere erste Pipeline an:
Screenshot der ersten Pipeline Es ist zu beachten, dass das Assemblieren nach Tags zum Assemblieren von Docker-Images geeignet ist, jedoch nicht zum Bereitstellen einer Anwendung in Kubernetes. Da auch alten Commits neue Tags zugewiesen werden können, führt in diesem Fall die Initialisierung der Pipeline für diese zum Deployment der alten Version.
Um dieses Problem zu lösen, werden in der Regel Docker-Images an Tags angehängt und die Anwendung im Master- Zweig bereitgestellt, in dem Versionen der gesammelten Images fest codiert sind. In diesem Fall können Sie das Rollback mit einem einfachen Master- Tag "Zurücksetzen" initialisieren.
10. Automatisierung der Bereitstellung
Damit Gitlab-runner unsere Geheimnisse entschlĂĽsseln kann, mĂĽssen wir den Repository-SchlĂĽssel exportieren und zu den Umgebungsvariablen unseres CIs hinzufĂĽgen:
git crypt export-key /tmp/docs-repo.key base64 -w0 /tmp/docs-repo.key; echo
Speichern Sie den resultierenden String in Gitlab. Gehen Sie dazu zu den Einstellungen unseres Projekts:
Einstellungen -> CI / CD -> Variablen
Und erstelle eine neue Variable:
.gitlab-ci.yml :
.deploy_qbec_app: stage: deploy only: refs: - master deploy_gitlab_runner: extends: .deploy_qbec_app variables: GIT_SUBMODULE_STRATEGY: normal before_script: - base64 -d "$GITCRYPT_KEY" | git-crypt unlock - script: - qbec apply default --root deploy/gitlab-runner --force:k8s-context __incluster__ --wait --yes deploy_website: extends: .deploy_qbec_app script: - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes
qbec:
- --root some/app —
- --force:k8s-context __incluster__ — , gtilab-runner. , qbec Kubernetes- kubeconfig
- --wait — qbec , Ready exit-code.
- --yes — Are you sure? .
:
git add .gitlab-ci.yml git commit -m "Automate deploy"
git push :
11. push master
, . digest master-.
: website push master , Kubernetes.
.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"
, master refs build_website $CI_COMMIT_REF_NAME $CI_COMMIT_TAG , Git . , , docker-registry.
docker- , Kubernetes, , .
--vm:ext-str digest="$DIGEST" qbec — jsonnet. . , , , .
Kaniko digest ( --digest-file )
.
deploy/website/environments/base.libsonnet :
{ 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', }, }, }
, master docker- website , Kubernetes.
:
git add . git commit -m "Configure dynamic build"
, git push - :
gitlab-runner push, , , , .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 deploy/gitlab-runner/
:
git add .gitlab-ci.yml git commit -m "Reduce gitlab-runner deploy"
git push , - :
12. Dynamic environments
.
build_website .gitlab-ci.yml , only , Gitlab :
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/
deploy_website , 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"
Gitlab prod .
:
deploy_website: extends: .deploy_qbec_app environment: name: prod url: https://docs.example.org script: - DIGEST="$(cat artifacts/website.digest)" - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST" deploy_review: extends: .deploy_qbec_app environment: name: review/$CI_COMMIT_REF_NAME url: http://$CI_ENVIRONMENT_SLUG.docs.example.org on_stop: stop_review script: - DIGEST="$(cat artifacts/website.digest)" - qbec apply review --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST" --vm:ext-str subdomain="$CI_ENVIRONMENT_SLUG" --app-tag "$CI_ENVIRONMENT_SLUG" only: refs: - branches except: refs: - master stop_review: extends: .deploy_qbec_app environment: name: review/$CI_COMMIT_REF_NAME action: stop stage: deploy before_script: - git clone "$CI_REPOSITORY_URL" master - cd master script: - qbec delete review --root deploy/website --force:k8s-context __incluster__ --yes --vm:ext-str digest="$DIGEST" --vm:ext-str subdomain="$CI_ENVIRONMENT_SLUG" --app-tag "$CI_ENVIRONMENT_SLUG" variables: GIT_STRATEGY: none only: refs: - branches except: refs: - master when: manual
push master preview .
qbec: --app-tag — , Kubernetes qbec .
review, .
qbec apply review , qbec apply default — (review default):
review deploy/website/qbec.yaml
spec: environments: review: defaultNamespace: docs server: https://kubernetes.example.org:8443
deploy/website/params.libsonnet :
local env = std.extVar('qbec.io/env'); local paramsMap = { _: import './environments/base.libsonnet', default: import './environments/default.libsonnet', review: import './environments/review.libsonnet', }; if std.objectHas(paramsMap, env) then paramsMap[env] else error 'environment ' + env + ' not defined in ' + std.thisFile
deploy/website/environments/review.libsonnet :
stop_review , gitlab checkout GIT_STRATEGY: none , master - review .
, .
review , .
:
git add . git commit -m "Enable automatic review"
git push , git checkout -b test , git push origin test , :
? — , : git checkout master , git push origin :test , environment .
, , .gitlab-ci.yml .
protected-, master , .
13. Review Apps
Review Apps , .
, .gitlab/route-map.yml , :
# Indices - source: /content\/(.+?)_index\.(md|html)/ public: '\1' # Pages - source: /content\/(.+?)\.(md|html)/ public: '\1/'
:
git add .gitlab/ git commit -m "Enable review apps"
git push , :
Job is done!
:
, 