
Hej! Der er kommet mange fantastiske automatiseringsværktøjer til Docker-bygning og til implementering i Kubernetes for nylig. Derfor besluttede jeg at lege med GitLab, for grundigt at udforske dets muligheder og selvfølgelig sætte en pipeline op.
Inspirationen til dette arbejde kom fra hjemmesiden , som genereres automatisk fra automatically, og for hver sendt pull request genererer robotten automatisk en preview-udgave af hjemmesiden med dine ændringer og giver et link til visning.
Jeg har forsøgt at opbygge en lignende proces fra bunden, men helt bygget på GitLab CI og de gratis værktøjer, som jeg er vant til at bruge til implementering af applikationer i Kubernetes. I dag vil jeg endelig fortælle dig mere om dem.
Artiklen vil gennemgå værktøjer som:
Hugo, qbec, kaniko, git-crypt og GitLab CI til oprettelse af dynamiske miljøer.
Indhold
1. Introduktion til Hugo
Som eksempel på vores projekt vil vi forsøge at oprette en hjemmeside til publicering af dokumentation bygget på Hugo. Hugo er en statisk indholdsgenerator.
For dem, der ikke er bekendt med statiske generatorer, vil jeg fortælle lidt mere om dem. I modsætning til almindelige websites med en database og en eller anden form for PHP, som genererer sider on-the-fly ved brugerforespørgsel, fungerer statiske generatorer lidt anderledes. De tillader at tage kildematerialer, som regel et sæt filer i Markdown-notation og temaskabeloner, og derefter kompilere dem til en helt færdig hjemmeside.
Det vil sige, at du i slutningen får en struktur af mapper og et sæt genererede html-filer, som du blot kan uploade til enhver billig hosting og få en fungerende hjemmeside.
Hugo kan installeres lokalt, så du kan prøve det af:
Initialiser en ny hjemmeside:
hugo new site docs.example.orgOg samtidig et git-repositorium:
cd docs.example.org
git initIndtil videre er vores hjemmeside helt ren, og for at få noget til at dukke op på den, skal vi først tilslutte et tema. Et tema er bare et sæt skabeloner og regler, som vores hjemmeside genereres efter.
Som tema vil vi bruge , som efter min mening passer perfekt til en dokumentationswebside.
Det skal bemærkes, at vi ikke behøver at gemme tema-filerne i vores projektrepositorium; i stedet kan vi blot tilføje dem ved hjælp af git submodule:
git submodule add https://github.com/matcornic/hugo-theme-learn themes/learnPå den måde vil vores repository kun indeholde filer, der er direkte relateret til vores projekt, mens det tilknyttede tema forbliver som et link til et bestemt repository og commit i det, så vi altid kan hente det fra den originale kilde uden at frygte inkompatible ændringer.
Lad os justere konfigurationen config.toml:
baseURL = "http://docs.example.org/"
languageCode = "da-dk"
title = "Min Docs-hjemmeside"
theme = "learn"Allerede på dette trin kan vi starte:
hugo serverOg på adressen kan vi tjekke vores nyoprettede webside; alle ændringer foretaget i mappen opdateres automatisk, ligesom den åbnede side i browseren, meget praktisk!
Lad os prøve at oprette en titelside i content/_index.md:
# My docs site
## Welcome to the docs!
You will be very smart :-)Skærmbillede af den netop oprettede side

For at generere hjemmesiden er det nok at køre:
hugoIndholdet af mappen public/ vil udgøre din hjemmeside.
Ja, forresten, lad os straks tilføje den til .gitignore:
echo /public > .gitignoreHusk at committe vores ændringer:
git add .
git commit -m "Ny hjemmeside oprettet"2. Forberedelse af Dockerfile
Det er tid til at definere strukturen af vores repository. Jeg bruger typisk noget som:
.
├── deploy
│ ├── app1
│ └── app2
└── dockerfiles
├── image1
└── image2- dockerfiles/ — indeholder mapper med Dockerfiles og alt nødvendigt til opbygning af vores docker-billeder.
- deploy/ — indeholder mapper til implementering af vores applikationer i Kubernetes
Dermed vil vi oprette vores første Dockerfile på stien 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"]Som du kan se, indeholder Dockerfile to FROM, denne funktion kaldes og gør det muligt at udelukke alt unødvendigt fra det endelige docker-billede.
Således vil vores endelige billede kun indeholde darkhttpd (en letvægts HTTP-server) og public/ — indholdet af vores statisk genererede hjemmeside.
Husk at committe vores ændringer:
git add dockerfiles/website
git commit -m "Tilføj Dockerfile til hjemmeside"3. Introduktion til kaniko
Som samler af Docker-billeder har jeg besluttet at bruge , da det ikke kræver en Docker-daemon og selve bygningen kan udføres på enhver maskine, med kassen direkte gemt i registry, hvilket eliminerer behovet for at have et fuldt ud fungerende persistent lager.
For at bygge billedet er det nok at starte en container med kaniko executor og give den den nuværende byggekontekst, hvilket kan gøres lokalt gennem docker:
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.1Where registry.gitlab.com/kvaps/docs.example.org/website — navnet på dit Docker-billede, som efter bygningen automatisk vil blive skubbet til Docker-registry.
Parameter —cache muliggør at cache lag i Docker-registry, i det givne eksempel vil de blive gemt i registry.gitlab.com/kvaps/docs.example.org/website/cache, men du kan også angive en anden sti ved hjælp af parameteren —cache-repo.
Screenshot af Docker-registry

4. Introduktion til Qbec
— er et deployværktøj, der gør det muligt at beskrive manifestet for din applikation deklarativt og deployere det til Kubernetes. Brugen af Jsonnet som primær syntaks forenkler i høj grad beskrivelsen af forskelle for flere miljøer og eliminerer næsten helt gentagelse af kode.
Dette kan være særligt relevant i de tilfælde, hvor du skal deployere en applikation til flere klynger med forskellige parametre, og du ønsker at beskrive dem deklarativt i Git.
Qbec tillader også at gengive Helm-diagrammer ved at give dem de nødvendige parametre, og herefter kunne behandle dem som almindelige manifests, herunder at anvende forskellige mutationer, hvilket igen eliminerer behovet for at bruge ChartMuseum. Det vil sige, at man kan gemme og gengive diagrammer direkte fra git, hvor de hører hjemme.
Som jeg nævnte tidligere, vil vi gemme alle deployment i mappen deploy/:
mkdir deploy
cd deployLad os initialisere vores første applikation:
qbec init website
cd websiteNu ser strukturen af vores applikation således ud:
.
├── components
├── environments
│ ├── base.libsonnet
│ └── default.libsonnet
├── params.libsonnet
└── qbec.yamlLad os se på filen qbec.yaml:
apiVersion: qbec.io/v1alpha1
kind: App
metadata:
name: website
spec:
environments:
default:
defaultNamespace: docs
server: https://kubernetes.example.org:8443
vars: {}Her er vi først og fremmest interesserede i spec.environments, qbec har allerede oprettet et standardmiljø for os og hentet serveradressen samt namespace fra vores nuværende kubeconfig.
Når vi deployerer til default miljøet, vil qbec altid kun deployere til den angivne Kubernetes-klynge og det angivne namespace, hvilket betyder, at du ikke længere skal skifte mellem kontekster og namespaces for at udføre en deployment.
Om nødvendigt kan du altid opdatere indstillingerne i denne fil.
Alle dine miljøer beskrives i qbec.yaml, og i filen params.libsonnet, hvor det står, hvorfra parametrene skal hentes.
Dernæst ser vi to mapper:
- components/ — her vil alle manifestfiler til vores applikation blive opbevaret, de kan beskrives både i jsonnet og som almindelige yaml-filer
- environments/ — her vil vi beskrive alle variabler (parametre) for vores miljøer.
Som standard har vi to filer:
- environments/base.libsonnet — den vil indeholde fælles parametre for alle miljøer
- environments/default.libsonnet — indeholder parametre, der er overstyret for miljøet default
Lad os åbne environments/base.libsonnet og tilføje parametre til vores første komponent:
{
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',
},
},
}Lad os også oprette vores første komponent 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,
},
},
],
},
},
],
},
},
]I denne fil har vi beskrevet tre Kubernetes-ressourcer: Deployment, Service og IngressHvis vi ønskede, kunne vi have delt dem op i forskellige komponenter, men på dette tidspunkt er én nok for os.
Syntaks jsonnet er meget lig almindelig json, i princippet er almindelig json allerede en gyldig jsonnet, så i starten vil det muligvis være lettere for dig at bruge online-tjenester som yaml2json for at konvertere det velkendte yaml til json, eller hvis dine komponenter ikke indeholder nogen variabler, kan de helt klart beskrives som almindelig yaml.
Når man arbejder med jsonnet jeg anbefaler stærkt at installere en plugin til din editor
For eksempel til vim er der en plugin vim-jsonnet, som inkluderer syntaksfremhævelse og automatisk udfører jsonnet fmt ved hver gemmehandling (kræver, at jsonnet er installeret).
Alt er klar, nu kan vi begynde at deploye:
For at se, hvad vi har fået, udfører vi:
qbec show defaultI output vil du se de rendererede yaml-manifester, der vil blive anvendt i default-clusteret.
Fremragende, nu anvender vi:
qbec apply defaultI output vil du altid se, hvad der vil blive foretaget i dit cluster, qbec vil bede dig om at acceptere ændringerne ved at indtaste y du kan bekræfte dine intentioner.
Færdig, nu er vores application deployet!
Hvis der foretages ændringer, kan du altid udføre:
qbec diff defaultfor at se, hvordan disse ændringer vil påvirke den aktuelle deployment
Husk at committe vores ændringer:
cd ../..
git add deploy/website
git commit -m "Tilføj deployment for website"5. Vi prøver Gitlab-runner med Kubernetes-executor
Indtil for nylig har jeg kun brugt en almindelig gitlab-runner på en forberedt maskine (LXC-container) med shell- eller docker-executor. Oprindeligt havde vi adskillige sådanne runners globalt defineret i vores GitLab. De samlede docker-billeder for alle projekter.
Men som erfaringen har vist — denne løsning er ikke den mest ideelle, hverken praktisk eller sikkerhedsmæssigt. Det er langt bedre og mere ideologisk korrekt at have separate runners deployeret for hvert projekt, eller endda for hver miljø.
Heldigvis er dette slet ikke et problem, da vi nu vil deployere gitlab-runner direkte som en del af vores projekt lige ind i Kubernetes.
GitLab leverer et klarlagt helm-chart til deployment af gitlab-runner i Kubernetes. Så alt, hvad du behøver, er at finde ud af registration token for vores projekt i Indstillinger —> CI / CD —> Runners og videregive det til helm:
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-runnerHvor:
- — adressen til din GitLab-server.
- yga8y-jdCusVDn_t4Wxc — registration token for dit projekt.
- rbac.create=true — giver runneren det nødvendige antal rettigheder til at kunne oprette pods til udførelse af vores opgaver med kubernetes-executor.
Hvis alt er gjort korrekt, skulle du se den registrerede runner i afsnittet Runners, i dine projektindstillinger.
Skærmbillede af den tilføjede runner

Var det så nemt? — Ja, så nemt! Ingen flere problemer med manuel registrering af runners, fra nu af vil runners blive oprettet og destrueret automatisk.
6. Deployment af Helm-charts med QBEC
Da vi har besluttet at betragte gitlab-runner som en del af vores projekt, er det tid til at beskrive det i vores Git-repository.
Vi kunne beskrive det som en separat komponent website, men vi planlægger i fremtiden at deployere forskellige kopier website meget ofte, i modsætning til gitlab-runner, som kun vil blive deployeret én gang for hver Kubernetes-klynge. Så lad os initialisere en separat applikation til det:
cd deploy
qbec init gitlab-runner
cd gitlab-runnerDenne gang vil vi ikke beskrive Kubernetes-objekter manuelt, men tage et færdigt Helm-diagram. En af fordelene ved qbec er muligheden for at gengive Helm-diagrammer direkte fra et Git-repositorium.
Lad os tilføje det ved hjælp af git-submodul:
git submodule add https://gitlab.com/gitlab-org/charts/gitlab-runner vendor/gitlab-runnerNu indeholder mappen vendor/gitlab-runner et repository med diagrammet til gitlab-runner.
På samme måde kan man tilføje andre repositories, for eksempel hele repositoryet med de officielle diagrammer.
Lad os beskrive komponenten 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,
}
)Det første argument til expandHelmTemplate giver vi stien til diagrammet, derefter params.values, som vi henter fra miljøparametrene; derefter er der et objekt med
- nameTemplate — navnet på udgivelsen
- namespace — det namespace, der overføres til Helm
- thisFile — et obligatorisk parameter, der giver stien til den aktuelle fil
- verbose — viser kommandoen helm template med alle argumenter under gengivelsen af diagrammet.
Lad os nu beskrive parametrene for vores komponent i 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,
},
},
},
}Vær opmærksom på runnerRegistrationToken henter vi fra en ekstern fil secrets/base.libsonnet, lad os oprette den:
{
runnerRegistrationToken: 'yga8y-jdCusVDn_t4Wxc',
}Lad os tjekke, om alt fungerer:
qbec show defaulthvis alt er i orden, kan vi slette vores tidligere udgivelser, der blev implementeret via Helm:
helm uninstall gitlab-runnerog implementere den igen, men denne gang via qbec:
qbec apply default7. Introduktion til git-crypt
er et værktøj, der gør det muligt at konfigurere gennemsigtig kryptering for dit repository.
I øjeblikket ser strukturen af vores mappe til gitlab-runner sådan her ud:
.
├── components
│ ├── gitlab-runner.jsonnet
├── environments
│ ├── base.libsonnet
│ └── default.libsonnet
├── params.libsonnet
├── qbec.yaml
├── secrets
│ └── base.libsonnet
└── vendor
└── gitlab-runner (submodule)Men det er ikke sikkert at gemme hemmeligheder i Git, ikke sandt? Så vi skal sørge for at kryptere dem korrekt.
Normalt giver det ikke altid mening at gøre det for én enkelt variabel. Du kan overføre hemmeligheder i qbec og gennem miljøvariabler i dit CI-system.
Men skal bemærkes, at der findes mere komplekse projekter, som kan indeholde langt flere hemmeligheder, og det vil være ekstremt vanskeligt at transmitterer dem alle gennem miljøvariabler.Desuden ville jeg i så fald ikke være i stand til at fortælle dig om et så fantastisk værktøj som git-crypt.
git-crypt er også praktisk, da det tillader at bevare hele historien om hemmelighederne, samt at sammenligne, merge og løse konflikter, ligesom vi er vant til at gøre det med Git.
Det første vi skal gøre efter installationen git-crypt er at generere nøgler til vores repository:
git crypt initHvis du har en PGP-nøgle, kan du straks tilføje dig selv som collaborator på dette projekt:
git-crypt add-gpg-user kvapss@gmail.comDermed vil du altid kunne dekryptere dette repository ved hjælp af din private nøgle.
Hvis du ikke har en PGP-nøgle og ikke planlægger at skaffe dig en, kan du tage en anden vej og eksportere projektets nøgle:
git crypt export-key /path/to/keyfileDermed kan alle, som besidder den eksporterede keyfile kunne dekryptere dit repository.
Det er tid til at konfigurere vores første hemmelighed.
Husk, vi er stadig i kataloget deploy/gitlab-runner/, hvor vi har et katalog secrets/, lad os kryptere alle filer deri, så vi opretter en fil secrets/.gitattributes med følgende indhold:
* filter=git-crypt diff=git-crypt
.gitattributes !filter !diffSom det fremgår af indholdet, vil alle filer efter mønsteret * blive kørt gennem git-crypt, undtagen selve .gitattributes
Vi kan kontrollere dette ved at køre:
git crypt status -eUdgangen vil give os en liste over alle filer i repositoryet, for hvilke krypteringen er aktiveret.
Det var det, nu kan vi trygt committe vores ændringer:
cd ../..
git add .
git commit -m "Add deploy for gitlab-runner"For at låse repositoryet er det nok at udføre:
git crypt lockog straks vil alle krypterede filer blive til binære noget, der ikke kan læses.
For at dekryptere repositoryet skal du udføre:
git crypt unlock8. Opretter toolbox-billede
Toolbox-billede er et billede med alle de værktøjer, som vi vil bruge til at deployere vores projekt. Det vil blive brugt af gitlab-runneren til at udføre typiske deploy-opgaver.
Her er det ganske enkelt, vi opretter en ny dockerfiles/toolbox/Dockerfile med følgende indhold:
FRA 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/helmSom du kan se, installerer vi alle værktøjer i dette billede, som vi har brugt til at implementere vores applikation. Her er der ikke behov for andet kubectl, men du vil måske lege med det i konfigurationsfasen af din pipeline.
For at kunne kommunikere med Kubernetes og udføre en implementering, skal vi konfigurere en rolle for de pods, der genereres af gitlab-runneren.
Lad os gå til mappen med gitlab-runneren:
cd deploy/gitlab-runnerog tilføje en ny komponent 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,
},
],
},
]Vi beskriver også de nye parametre i environments/base.libsonnet, som nu ser sådan ud:
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',
},
},
}Vær opmærksom på $.components.rbac.name henviser til name for komponenten rbac
Lad os tjekke, hvad der er ændret:
qbec diff defaultog anvende vores ændringer i Kubernetes:
qbec apply defaultGlem ikke at committe vores ændringer til git:
cd ../..
git add dockerfiles/toolbox
git commit -m "Tilføj Dockerfile til toolbox"
git add deploy/gitlab-runner
git commit -m "Konfigurer gitlab-runner til at bruge toolbox"9. Vores første pipeline og opbygning af billeder efter tags
I roden af projektet opretter vi .gitlab-ci.yml med følgende indhold:
.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:
- tagsBemærk, at vi bruger GIT_SUBMODULE_STRATEGY: normal til jobber, hvor det er nødvendigt at initialisere undermoduler før udførelsen.
Husk at committe vores ændringer:
git add .gitlab-ci.yml
git commit -m "Automate docker build"Jeg tror, vi kan trygt kalde dette version v0.0.1 og tilføje tag:
git tag v0.0.1Vi vil tilføje tags hver gang vi har brug for at frigive en ny version. Tags i Docker-billeder vil være knyttet til Git-tags. Hver push med et nyt tag vil inicialisere bygningen af billederne med dette tag.
Vi udfører git push —tags, og ser på vores første pipeline:
Screenshot af den første pipeline

Det er vigtigt at nævne, at byggeri efter tags er passende til bygning af docker-billeder, men ikke egnet til deployment af applikationen i Kubernetes. Nyere tags kan tildeles til ældre commits, og i så fald vil initialiseringen af pipelinen for dem føre til deployment af den gamle version.
For at løse dette problem knyttes bygningen af docker-billeder normalt til tags, mens deployeringen af applikationen knyttes til en gren master, hvor versionerne af de byggede billeder er hardkodet. I dette tilfælde kan du initialisere et rollback med en simpel revert master-gren.
10. Automatisering af deployment
For at Gitlab-runner kan dekryptere vores hemmeligheder, skal vi eksportere repository-nøglen og tilføje den til miljøvariablerne i vores CI:
git crypt export-key /tmp/docs-repo.key
base64 -w0 /tmp/docs-repo.key; echoden resulterende streng gemmer vi i Gitlab, for at gøre dette går vi til indstillingerne for vores projekt:
Indstillinger —> CI / CD —> Variabler
Og opretter en ny variabel:
Type
Nøgle
Værdi
Beskyttet
Maskeret
Omfang
Fil
GITCRYPT_KEY
<your string>
true (under træningen kan også false)
true
Alle miljøer
Screenshot af den tilføjede variabel

Nu opdaterer vi vores .gitlab-ci.yml ved at tilføje:
.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 --yesHer har vi anvendt flere nye indstillinger for qbec:
- —root some/app — giver mulighed for at angive biblioteket for den specifikke applikation
- —force:k8s-context __incluster__ — dette er en magisk variabel, der angiver, at deployment vil finde sted i den samme klynge, hvor gitlab-runner kører. Dette er nødvendigt, da qbec ellers vil forsøge at finde en passende Kubernetes-server i din kubeconfig
- —wait — beder qbec om at vente, indtil de ressourcer, der oprettes, er i status Ready, og derefter afslutte med en succesfuld exit-kode.
- —yes — deaktiverer blot den interaktive shell Er du sikker? ved deployment.
Husk at committe vores ændringer:
git add .gitlab-ci.yml
git commit -m "Automatiser deployment"Og efter git push vil vi se, hvordan vores applikationer er blevet deployeret:
Skærmbillede af den anden pipeline

11. Artefakter og bygning ved push til master
De ovenstående trin er normalt tilstrækkelige til at bygge og levere næsten enhver mikrotjeneste, men vi ønsker ikke at skulle tilføje et tag hver gang, vi har brug for at opdatere hjemmesiden. Derfor vil vi tage en mere dynamisk tilgang og konfigurere deployment efter digest i master-grenen.
Ideen er enkel: nu vil vores billede blive genopbygget hver gang der pushes til website , og efterfølgende automatisk deployeres til Kubernetes. masterLad os opdatere disse to job i vores
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 - tagsdeploy_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" .gitlab-ci.yml:
Bemærk, at vi har tilføjet grenenrefs master pull-requesten på GitHub til jobbet build_website og vi bruger nu $CI_COMMIT_REF_NAME $CI_COMMIT_TAG i stedet for , hvilket vil sige, at vi slipper for tags i Git og nu vil pushe et billede med navnet på den commit-gren, der initierede pipelinen. Det er værd at bemærke, at dette også vil fungere med tags, hvilket gør det muligt for os at gemme snapshots af hjemmesiden med en bestemt version i docker-registry., det vil sige, at vi løsner os fra tags i Git, og nu vil vi pushe et billede med navnet på den gren, der initierede pipeline. Det skal bemærkes, at dette også vil fungere med tags, hvilket giver os mulighed for at bevare snapshots af siden med en bestemt version i docker-registry.
Når navnet på docker-taggen for den nye version af hjemmesiden kan forblive det samme, skal vi stadig beskrive ændringerne til Kubernetes, ellers vil det simpelthen ikke gen-deployere applikationen fra det nye billede, da det ikke vil bemærke nogen ændringer i deployment-manifestet.
Mulighed —vm:ext-str digest="%DIGEST%" for qbec — giver mulighed for at videregive en ekstern variabel til jsonnet. Vi ønsker, at vores applikation skal blive gen-deployet i klyngen med hver udgivelse. At bruge et tagnavn, som nu kan være det samme, kan vi ikke længere, da vi skal binde os til en specifik version af billedet og trigge deployment ved dens ændring.
Her kan Kanikos mulighed for at gemme billedets digest i en fil hjælpe os (mulighed —digest-file)
Så videregiver vi denne fil og læser den på deployment-momentet.
Vi opdaterer parametrene for vores deploy/website/environments/base.libsonnet som nu vil se sådan ud:
{
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',
},
},
}Færdig, nu initierer enhver commit bygningen af docker-billedet for master , og derefter dens deployment i Kubernetes. websitegit add . git commit -m "Konfigurer dynamisk bygning"
Husk at committe vores ændringer:
Lad os tjekke, eftervi skal se noget lignende: git push Screenshot af pipeline for master
I princippet har vi ikke brug for at gen-deploye gitlab-runner ved hver push, medmindre selvfølgelig, at der ikke er ændret noget i dens konfiguration, lad os rette dette i

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/**/* .gitlab-ci.yml:
vil tillade at overvåge ændringer ichanges og vil trigge vores job kun ved tilstedeværelse af sådanne deploy/gitlab-runner/ git add .gitlab-ci.yml git commit -m "Reducer gitlab-runner deploy"
Husk at committe vores ændringer:
, så meget bedre:git pushScreenshot af den opdaterede pipeline
12. Dynamiske miljøer

Det er tid til at variere vores pipeline med dynamiske miljøer.
For at starte med opdaterer vi jobbet
i vores og vi bruger nu , ved at fjerne blokken .gitlab-ci.ymlonly , hvilket vil få Gitlab til at trigge det ved enhver commit i enhver gren: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/
Så opdaterer vi jobbetdeploy_website deploy_website, lad os tilføje en blok 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"Dette vil gøre det muligt for GitLab at associere jobbet med prod miljøet og vise det korrekte link til det.
Lad os nu tilføje to flere jobs:
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: manualDe vil blive udløst ved push til enhver branch undtagen master og vil udrullere en preview-version af hjemmesiden.
Vi ser en ny mulighed for qbec: —app-tag — det giver mulighed for at tagge de udrullede versioner af applikationen og kun arbejde inden for den tag, når der oprettes og ødelægges ressourcer i Kubernetes, vil qbec kun operere med dem.
Dermed kan vi undgå at oprette et separat miljø for hver anmeldelse og blot genbruge det samme.
Her bruger vi også qbec apply review, i stedet for qbec apply default — dette er netop det tidspunkt, hvor vi vil forsøge at beskrive forskellene for vores miljøer (review og default):
Lad os tilføje review miljøet i deploy/website/qbec.yaml
spec:
environments:
review:
defaultNamespace: docs
server: https://kubernetes.example.org:8443Derefter erklærer vi det i 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.thisFileOg vi vil skrive tilpassede parametre for det i 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',
},
},
}Lad os også tage et nærmere kig på jobbet stop_review, det vil blive udløst ved sletning af branch, og for at GitLab ikke forsøger at tage checkout på det, bruges GIT_STRATEGY: none, senere kloner vi master-branchen og sletter anmeldelsen gennem den.
Lidt besværligt, men jeg har endnu ikke fundet en smukkere måde.
En alternativ mulighed kan være at implementere hver review i et eget namespace, som altid kan slettes helt.
Husk at committe vores ændringer:
git add .
git commit -m "Aktiver automatisk review"git push, git checkout -b test, git push origin test, vi tjekker:
Skærmbillede af de oprettede miljøer i Gitlab

Fungerer det? — perfekt, vi sletter vores testgren: git checkout master, git push origin :test, vi tjekker at jobbene til sletning af miljøet kørte uden fejl.
Her vil jeg straks bemærke, at enhver udvikler i projektet kan oprette grene, og han kan også ændre .gitlab-ci.yml filen og få adgang til de hemmelige variabler.
Derfor anbefales det stærkt kun at tillade brugen af dem for protected-grene, f.eks. i master, eller at oprette et separat miljø af variabler til hvert miljø.
13. Review Apps
det er en funktion i Gitlab, der gør det muligt at tilføje knapper til hurtig visning af hver fil i repositoret i det deployerede miljø.
For at disse knapper kan vises, skal der oprettes en fil .gitlab/route-map.yml og beskrive alle stitransformationer i den, i vores tilfælde vil det være meget enkelt:
# Indices
- source: /content/(.+?)_index.(md|html)/
public: '1'
# Pages
- source: /content/(.+?).(md|html)/
public: '1/'Husk at committe vores ændringer:
git add .gitlab/
git commit -m "Aktivér review apps"git push, og vi tjekker:
Skærmbillede af Review App knappen

Jobbet er færdigt!
Kildeteksterne til projektet:
- på Gitlab:
- på GitHub:
Tak for opmærksomheden, jeg håber, I kunne lide det ![]()
Kilde: habr.com
