We proberen nieuwe tools voor het bouwen en automatiseren van deployments in Kubernetes

We proberen nieuwe tools voor het bouwen en automatiseren van deployments in Kubernetes

Hallo! De laatste tijd zijn er veel geweldige automatiseringstools beschikbaar gekomen, zowel voor het bouwen van Docker-images als voor het deployen in Kubernetes. Daarom besloot ik om met GitLab te experimenteren, de mogelijkheden grondig te onderzoeken en natuurlijk een pipeline in te stellen.

De inspiratie voor dit werk kwam van de site kubernetes.io, die automatisch wordt gegenereerd uit broncode en voor elke ingediende pull request genereert de robot automatisch een previewversie van de site met jouw wijzigingen en biedt een link voor weergave.

Ik heb geprobeerd zo'n proces vanaf nul op te bouwen, maar volledig gebaseerd op GitLab CI en de gratis tools die ik gewend ben te gebruiken voor het deployen van applicaties in Kubernetes. Vandaag zal ik je eindelijk meer hierover vertellen.

In dit artikel worden de volgende tools besproken:
Hugo, qbec, kaniko, git-crypt en GitLab CI met het creëren van dynamische omgevingen.

Inhoud

  1. Kennismaking met Hugo
  2. Voorbereiding Dockerfile
  3. Kennismaking met kaniko
  4. Kennismaking met qbec
  5. We testen GitLab-runner met Kubernetes-executor
  6. Deployen van Helm-charts met qbec
  7. Kennismaking met git-crypt
  8. We creëren een toolbox-image
  9. Onze eerste pipeline en het bouwen van beelden op basis van tags
  10. Automatisering van deployment
  11. Artifacts en bouw bij push naar master
  12. Dynamische omgevingen
  13. Review Apps

1. Kennismaking met Hugo

Als voorbeeld van ons project zullen we een website voor het publiceren van documentatie maken, gebouwd op Hugo. Hugo is een statische contentgenerator.

Voor degenen die niet bekend zijn met statische generators, zal ik daar iets meer over vertellen. In tegenstelling tot gewone CMS-systemen met een database en een of andere PHP, die bij een gebruikersverzoek pagina's on-the-fly genereren, zijn statische generators iets anders opgebouwd. Ze stellen je in staat om bronnen, meestal een set bestanden in Markdown-opmaak en templates, om te zetten in een volledig werkende website.

Dus aan het einde krijg je een directorystructuur en een set gegenereerde html-bestanden, die je eenvoudig op elke goedkope hosting kunt uploaden om een werkende website te krijgen.

Hugo kan lokaal worden geïnstalleerd en je kunt het in de praktijk proberen:

Initialiseer een nieuwe site:

hugo new site docs.example.org

En initieer ook een git-repository:

cd docs.example.org
git init

Onze website is voorlopig helemaal nieuw en om er iets op te laten verschijnen, moeten we eerst een thema aansluiten. Een thema is simpelweg een set sjablonen en regels die bepalen hoe onze site wordt gegenereerd.

Als thema zullen we gebruiken Learn, wat, naar mijn mening, perfect past bij een documentatiesite.

Het is belangrijk op te merken dat we de themabestanden niet in de repository van ons project hoeven op te slaan; in plaats daarvan kunnen we het eenvoudigweg aansluiten via git submodule:

git submodule add https://github.com/matcornic/hugo-theme-learn themes/learn

Op deze manier bevat onze repository alleen de bestanden die direct betrekking hebben op ons project, terwijl het aangesloten thema blijft bestaan in de vorm van een link naar een specifieke repository en commit daarin, wat betekent dat we het altijd uit de originele bron kunnen ophalen zonder bang te zijn voor incompatibele wijzigingen.

Laten we de configuratie aanpassen config.toml:

baseURL = "http://docs.example.org/"
languageCode = "nl"
title = "Mijn Documentatie Site"
theme = "learn"

Op dit punt kunnen we al starten met:

hugo server

En op het adres http://localhost:1313/ kunnen we onze pas aangemaakte website controleren. Alle wijzigingen die in de directory worden aangebracht, worden automatisch bijgewerkt in de geopende pagina in de browser, wat handig is!

Laten we proberen een titelpagina te maken in content/_index.md:

# My docs site

## Welcome to the docs!

You will be very smart :-)

Screenshot van de net aangemaakte pagina

We proberen nieuwe tools voor het bouwen en automatiseren van deployments in Kubernetes

Om de website te genereren, is het voldoende om te draaien:

hugo

De inhoud van de directory public/ zal je website zijn.
Oh, en laten we het meteen toevoegen aan .gitignore:

echo /public > .gitignore

Vergeet niet onze wijzigingen te committen:

git add .
git commit -m "Nieuwe site aangemaakt"

2. Voorbereiding van Dockerfile

Het is tijd om de structuur van onze repository te bepalen. Meestal gebruik ik iets als:

.
├── deploy
│   ├── app1
│   └── app2
└── dockerfiles
    ├── image1
    └── image2

  • dockerfiles/ bevat mappen met Dockerfiles en alles wat nodig is voor het bouwen van onze docker-images.
  • deploy/ bevat mappen voor het implementeren van onze applicaties in Kubernetes.

Op deze manier creëren we onze eerste Dockerfile op het pad 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" ]

Zoals je kunt zien, bevat de Dockerfile twee FROM, deze mogelijkheid wordt " multi-stage build genoemd en maakt het mogelijk om alles wat niet nodig is uit de uiteindelijke docker-image te verwijderen.
Op deze manier zal ons definitieve beeld alleen bevatten darkhttpd (lichtgewicht HTTP-server) en public/ — de inhoud van onze statisch gegenereerde website.

Vergeet niet onze wijzigingen te committen:

git add dockerfiles/website
git commit -m "Voeg Dockerfile voor website toe"

3. Kennismaking met kaniko

Als bouwtool voor docker-images heb ik ervoor gekozen om kaniko, aangezien voor de werking ervan geen docker-daemon nodig is en de bouw op elke machine kan worden uitgevoerd, waarbij de cache direct in de registry wordt opgeslagen, zodat er geen volledige persistent storage vereist is.

Om de image te bouwen, volstaat het om een container met kaniko executor te starten en deze de huidige buildcontext te geven; dit kan ook lokaal via 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.1

Waarbij registry.gitlab.com/kvaps/docs.example.org/website — de naam van uw docker-image, na de bouw wordt deze automatisch gepusht naar de docker registry.

Parameter —cache stelt u in staat om lagen in de docker registry te cachen. Voor het gegeven voorbeeld worden ze opgeslagen in registry.gitlab.com/kvaps/docs.example.org/website/cache, maar u kunt ook een ander pad opgeven met het parameter —cache-repo.

Screenshot docker-registry

We proberen nieuwe tools voor het bouwen en automatiseren van deployments in Kubernetes

4. Kennismaking met qbec

Qbec is een deploy-tool waarmee u de manifesten van uw applicatie declaratief kunt beschrijven en deze in Kubernetes kunt implementeren. Het gebruik van Jsonnet als de belangrijkste syntaxis maakt het mogelijk om de beschrijving van verschillen voor meerdere omgevingen aanzienlijk te vereenvoudigen en elimineert bijna volledig de herhaling van code.

Dit kan vooral relevant zijn in gevallen waarin u een applicatie naar meerdere clusters met verschillende parameters moet implementeren en u deze declaratief in Git wilt beschrijven.

Qbec maakt het ook mogelijk om Helm-diagrammen te renderen door de benodigde parameters door te geven en deze later te behandelen alsof het gewone manifesten zijn, waaronder het toepassen van verschillende mutaties, wat op zijn beurt het gebruik van ChartMuseum overbodig maakt. Dit betekent dat u diagrammen rechtstreeks uit git kunt opslaan en renderen, waar ze ook echt thuishoren.

Zoals ik eerder zei, zullen we alle deployments opslaan in de map deploy/:

mkdir deploy
cd deploy

Laten we onze eerste applicatie initialiseren:

qbec init website
cd website

Momenteel ziet de structuur van onze applicatie er als volgt uit:

.
├── components
├── environments
│   ├── base.libsonnet
│   └── default.libsonnet
├── params.libsonnet
└── qbec.yaml

laten we het bestand bekijken qbec.yaml:

apiVersion: qbec.io/v1alpha1
kind: App
metadata:
  name: website
spec:
  environments:
    default:
      defaultNamespace: docs
      server: https://kubernetes.example.org:8443
  vars: {}

Hier zijn we in de eerste plaats geïnteresseerd in spec.environments, qbec heeft al voor ons de default omgeving aangemaakt en het serveradres en de namespace uit onze huidige kubeconfig gehaald.
Nu bij het deployen naar default de omgeving zal qbec altijd alleen naar de opgegeven Kubernetes-cluster en in de opgegeven namespace deployen, wat betekent dat je niet meer tussen contexten en namespaces hoeft te schakelen om te kunnen deployen.
Indien nodig kun je altijd de instellingen in dit bestand bijwerken.

Al je omgevingen worden beschreven in qbec.yaml, en in het bestand params.libsonnet, waar staat waar de parameters voor hen vandaan moeten komen.

Verder zien we twee directories:

  • components/ — hier worden alle manifesten voor onze applicatie opgeslagen, ze kunnen zowel in jsonnet als in gewone yaml-bestanden beschreven worden
  • environments/ — hier zullen we alle variabelen (parameters) voor onze omgevingen beschrijven.

Standaard hebben we twee bestanden:

  • environments/base.libsonnet — dit zal algemene parameters voor alle omgevingen bevatten
  • environments/default.libsonnet — bevat parameters die zijn overschreven voor de omgeving default

Laten we het openen environments/base.libsonnet en daar parameters voor onze eerste component toevoegen:

{
  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',
    },
  },
}

Laten we ook onze eerste component creëren components/website.jsonnet:

local env = {
  name: std.extVar('qbec.io/env'),
  namespace: std.extVar('qbec.io/defaultNs'),
};
local p = import '../params.libsonnet';
local params = p.components.website;

[
  {
    apiVersion: 'apps/v1',
    kind: 'Deployment',
    metadata: {
      labels: { app: params.name },
      name: params.name,
    },
    spec: {
      replicas: params.replicas,
      selector: {
        matchLabels: {
          app: params.name,
        },
      },
      template: {
        metadata: {
          labels: { app: params.name },
        },
        spec: {
          containers: [
            {
              name: 'darkhttpd',
              image: params.image,
              ports: [
                {
                  containerPort: params.containerPort,
                },
              ],
            },
          ],
          nodeSelector: params.nodeSelector,
          tolerations: params.tolerations,
          imagePullSecrets: [{ name: 'regsecret' }],
        },
      },
    },
  },
  {
    apiVersion: 'v1',
    kind: 'Service',
    metadata: {
      labels: { app: params.name },
      name: params.name,
    },
    spec: {
      selector: {
        app: params.name,
      },
      ports: [
        {
          port: params.servicePort,
          targetPort: params.containerPort,
        },
      ],
    },
  },
  {
    apiVersion: 'extensions/v1beta1',
    kind: 'Ingress',
    metadata: {
      annotations: {
        'kubernetes.io/ingress.class': params.ingressClass,
      },
      labels: { app: params.name },
      name: params.name,
    },
    spec: {
      rules: [
        {
          host: params.domain,
          http: {
            paths: [
              {
                backend: {
                  serviceName: params.name,
                  servicePort: params.servicePort,
                },
              },
            ],
          },
        },
      ],
    },
  },
]

In dit bestand hebben we drie Kubernetes-entiteiten beschreven, dit zijn: Deployment, Service en Ingress. Als we dat wilden, konden we ze in verschillende componenten scheiden, maar op dit moment is één genoeg voor ons.

Syntax jsonnet lijkt erg op gewone json; in principe is gewone json al een geldige jsonnet, dus in het begin is het misschien makkelijker om gebruik te maken van online diensten zoals yaml2json om de vertrouwde yaml naar json te converteren, of als je componenten geen variabelen bevatten, kunnen ze heel goed als gewone yaml worden beschreven.

Bij het werken met jsonnet Ik raad je ten zeerste aan om een plugin voor je editor te installeren.

Bijvoorbeeld, voor vim is er een plugin. vim-jsonnet, die de syntax highlighting omvat en automatisch uitvoert jsonnet fmt bij elke opslag (vereist dat jsonnet is geïnstalleerd).

Alles is klaar, laten we beginnen met de deploy:

Om te zien wat we hebben, voeren we uit:

qbec show default

Als resultaat zie je de gerenderde yaml-manifesten die in de cluster default zullen worden toegepast.

Geweldig, laten we nu toepassen:

qbec apply default

Bij het resultaat zie je altijd wat er in jouw cluster zal worden gedaan, qbec vraagt je om met de wijzigingen akkoord te gaan door y in te voeren om je intenties te bevestigen.

Klaar, onze applicatie is nu gedeployed!

Bij wijzigingen heb je altijd de mogelijkheid om uit te voeren:

qbec diff default

om te zien hoe deze wijzigingen de huidige deployment beïnvloeden

Vergeet niet onze wijzigingen te committen:

cd ..\/..
git add deploy\/website
git commit -m "Voeg deployment voor website toe"

5. Proberen met Gitlab-runner en Kubernetes-executor

Tot voor kort gebruikte ik alleen de gewone gitlab-runner op een vooraf voorbereid systeem (LXC-container) met shell- of docker-executor. Oorspronkelijk hadden we een aantal zulke runners globaal gedefinieerd in onze GitLab. Ze bouwden docker-images voor alle projecten.

Maar zoals de praktijk heeft laten zien, is deze optie niet de meest ideale, zowel qua praktisch gebruik als op het gebied van veiligheid. Het is veel beter en ideologisch correcter om aparte runners te hebben die gedeplyed zijn voor elk project, of zelfs voor elke omgeving.

Gelukkig is dit helemaal geen probleem, want nu gaan we deployen gitlab-runner direct als onderdeel van ons project, recht in Kubernetes.

Gitlab biedt een kant-en-klaar helm-chart voor het deployen van gitlab-runner in Kubernetes. Dit betekent dat alles wat je moet doen, is het volgende achterhalen: registration token voor ons project in Instellingen —> CI \/ CD —> Runners en het doorgeven aan 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-runner

Waarbij:

  • https://gitlab.com — het adres van je Gitlab-server.
  • yga8y-jdCusVDn_t4Wxc — registration token voor je project.
  • rbac.create=true — geeft de runner het nodige aantal privileges om pods te kunnen creëren voor het uitvoeren van onze taken met de kubernetes-executor.

Als alles correct is gedaan, zou je de geregistreerde runner moeten zien in de sectie Runners, in de instellingen van je project.

Screenshot van de toegevoegde runner

We proberen nieuwe tools voor het bouwen en automatiseren van deployments in Kubernetes

Zo simpel? — ja, zo simpel! Geen gedoe meer met handmatige registratie van runners, vanaf nu zullen runners automatisch worden aangemaakt en vernietigd.

6. Deployen van Helm-charts met QBEC

Aangezien we hebben besloten om dit gitlab-runner als onderdeel van ons project te beschouwen, is het tijd om het te beschrijven in onze Git-repository.

We zouden het als een aparte component kunnen beschrijven website, maar in de toekomst zijn we van plan om verschillende kopieën te deployen website heel vaak, in tegenstelling tot gitlab-runner, dat slechts één keer op elke Kubernetes-cluster zal worden gedeployed. Laten we dus een aparte applicatie voor hem initialiseren:

cd deploy
qbec init gitlab-runner
cd gitlab-runner

Deze keer beschrijven we de Kubernetes-entiteiten niet handmatig, maar nemen we een kant-en-klaar Helm-chart. Een van de voordelen van qbec is de mogelijkheid om Helm-charts rechtstreeks uit de Git-repository te renderen.

Laten we het verbinden met behulp van git submodule:

git submodule add https://gitlab.com/gitlab-org/charts/gitlab-runner vendor/gitlab-runner

Nu bevat de directory vendor/gitlab-runner een repository met de chart voor gitlab-runner.

Op dezelfde manier kunnen we andere repositories aansluiten, bijvoorbeeld de volledige repository met de officiële charts. https://github.com/helm/charts

Laten we de component beschrijven 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 eerste argument aan expandHelmTemplate geven we het pad naar de chart, daarna params.values, die we halen uit de omgevingsparameters, dan komt het object met

  • nameTemplate — de naam van de release
  • namespace — de namespace die aan Helm wordt doorgegeven
  • thisFile — een vereiste parameter die het pad naar het huidige bestand doorgeeft
  • verbose — toont de commandoregel helm template met alle argumenten tijdens het renderen van de chart.

Laten we nu de parameters voor onze component beschrijven 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,
      },
    },
  },
}

Let op runnerRegistrationToken nemen we uit een extern bestand secrets/base.libsonnet, laten we het aanmaken:

{
  runnerRegistrationToken: 'yga8y-jdCusVDn_t4Wxc',
}

Laten we controleren of alles werkt:

qbec show default

als alles in orde is, kunnen we onze eerder via Helm gedeployde release verwijderen:

helm uninstall gitlab-runner

en dezelfde opnieuw deployen, maar nu via qbec:

qbec apply default

7. Kennismaking met git-crypt

Git-crypt is een tool die je in staat stelt om transparante encryptie voor je repository in te stellen.

Momenteel ziet de structuur van onze directory voor gitlab-runner er zo uit:

.
├── components
│   ├── gitlab-runner.jsonnet
├── environments
│   ├── base.libsonnet
│   └── default.libsonnet
├── params.libsonnet
├── qbec.yaml
├── secrets
│   └── base.libsonnet
└── vendor
    └── gitlab-runner (submodule)

Maar het is niet veilig om geheimen in Git op te slaan, klopt dat? Dus we moeten ze goed versleutelen.

Normaal gesproken heeft het niet altijd zin om voor één variabele te encrypten. Je kunt geheimen ook doorgeven in qbec en via omgevingsvariabelen van je CI-systeem.
Maar het is vermeldenswaard dat er ook complexere projecten zijn die veel meer geheimen kunnen bevatten; het doorgeven van al deze via omgevingsvariabelen zal uiterst moeilijk zijn.

Bovendien zou ik je in dat geval niet kunnen vertellen over zo'n geweldig hulpmiddel als git-crypt.

git-crypt dat bovendien handig is omdat het de gehele geschiedenis van geheimen kan bewaren, evenals het vergelijken, mergen en oplossen van conflicten zoals we gewend zijn te doen met Git.

Als eerste stap na installatie git-crypt moeten we sleutels genereren voor onze repository:

git crypt init

Als je een PGP-sleutel hebt, kun je jezelf onmiddellijk toevoegen als collaborator voor dit project:

git-crypt add-gpg-user kvapss@gmail.com

Op deze manier kun je deze repository altijd ontcijferen met je privésleutel.

Als je echter geen PGP-sleutel hebt en niet van plan bent er een te krijgen, kun je een andere weg inslaan en de project sleutel exporteren:

git crypt export-key \/path\/to\/keyfile

Op deze manier kan iedereen die de geëxporteerde keyfile heeft, je repository ontcijferen.

Het is tijd om ons eerste geheim in te stellen.
Ik herinner je eraan dat we nog steeds in de directory zijn deploy\/gitlab-runner\/, waar we een directory hebben secrets\/, laten we al de bestanden daarin versleutelen door het bestand secrets\/.gitattributes aan te maken met de volgende inhoud:

* filter=git-crypt diff=git-crypt
.gitattributes !filter !diff

Zoals blijkt uit de inhoud, zullen alle bestanden met de masker * door git-cryptgefilterd worden, met uitzondering van de .gitattributes

Dit kunnen we controleren door te draaien:

git crypt status -e

We krijgen een lijst van alle bestanden in de repository waarvoor encryptie is ingeschakeld als output.

Dat is alles, nu kunnen we gerust onze wijzigingen committen:

cd ..\/..
git add .
git commit -m "Add deploy for gitlab-runner"

Om de repository te vergrendelen, volstaat het om uit te voeren:

git crypt lock

en meteen zullen al de versleutelde bestanden veranderen in een binair iets, het zal onmogelijk zijn om ze te lezen.
Om de repository te ontcijferen, voer uit:

git crypt unlock

8. We maken een toolbox-image

Een toolbox-image is een image met alle tools die we gaan gebruiken voor het implementeren van ons project. Deze zal door de GitLab-runner gebruikt worden voor het uitvoeren van standaard implementatie-taken.

Hier is het eenvoudig, maak een nieuwe dockerfiles\/toolbox\/Dockerfile aan te maken met de volgende inhoud:

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

Zoals u kunt zien, installeren we in deze afbeelding alle hulpprogramma's die we hebben gebruikt voor de implementatie van onze applicatie. We hebben alleen dit niet nodig kubectl, maar misschien wilt u er in de configuratiefase mee experimenteren.

Daarnaast moeten we een rol instellen voor de pods die door de gitlab-runner worden gegenereerd, zodat we met Kubernetes kunnen communiceren en implementeren.

Laten we naar de directory met de gitlab-runner gaan:

cd deploy/gitlab-runner

en laten we een nieuwe component toevoegen 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,
      },
    ],
  },
]

Laten we ook de nieuwe parameters beschrijven in environments/base.libsonnet, dat er nu als volgt uitziet:

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',
    },
  },
}

Let op $.components.rbac.name verwijst naar naam voor de component rbac

Laten we controleren wat er is veranderd:

qbec diff default

en laten we onze wijzigingen in Kubernetes toepassen:

qbec apply default

Vergeet ook niet onze wijzigingen naar git te 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. Onze eerste pijplijn en het bouwen van afbeeldingen op tags

In de root van het project maken we .gitlab-ci.yml aan te maken met de volgende inhoud:

.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

Let op, we gebruiken GIT_SUBMODULE_STRATEGY: normal voor de jobs waar submodules expliciet moeten worden geïnitialiseerd voordat ze worden uitgevoerd.

Vergeet niet onze wijzigingen te committen:

git add .gitlab-ci.yml
git commit -m "Automatiseer docker build"

Ik denk dat we dit gerust een versie kunnen noemen v0.0.1 en taggen:

git tag v0.0.1

We zullen tags aanmaken telkens wanneer we een nieuwe versie willen uitbrengen. Tags in Docker-images zullen gekoppeld zijn aan Git-tags. Elke push met een nieuwe tag zal de bouw van beelden met die tag initialiseren.

Laten we uitvoeren git push —tags, en laten we ons eerste pipeline zien:

Screenshot van het eerste pipeline

We proberen nieuwe tools voor het bouwen en automatiseren van deployments in Kubernetes

Het is belangrijk op te merken dat de bouw op tags geschikt is voor het bouwen van docker-images, maar niet voor de deploy van een applicatie in Kubernetes. Aangezien nieuwe tags ook aan oudere commits kunnen worden toegewezen, zou het initialiseren van de pipeline voor deze tags leiden tot de deploy van een oude versie.

Om dit probleem op te lossen, wordt meestal de bouw van docker-images aan tags gekoppeld, terwijl de deploy van de applicatie aan de branch master, waarin de versies van de samengestelde beelden hard gecodeerd zijn. Alleen in dit geval kun je een rollback eenvoudig initieren met een revert master-branch.

10. Automatisering van de deploy

Om ervoor te zorgen dat Gitlab-runner onze geheimen kan ontcijferen, moeten we de repository-sleutel exporteren en deze aan de omgevingsvariabelen van onze CI toevoegen:

git crypt export-key /tmp/docs-repo.key
base64 -w0 /tmp/docs-repo.key; echo

de verkregen string zullen we in Gitlab opslaan, hiervoor gaan we naar de instellingen van ons project:
Instellingen —> CI / CD —> Variabelen

En we zullen een nieuwe variabele aanmaken:

Type
Key
Waarde
Protected
Masked
Scope

Bestand
GITCRYPT_KEY
<your string>
true (voor de tijd van de training kan dit ook false)
true
All environments

Screenshot van de toegevoegde variabele

We proberen nieuwe tools voor het bouwen en automatiseren van deployments in Kubernetes

Laten we nu onze .gitlab-ci.yml bijwerken door het toe te voegen:

.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

Hier hebben we verschillende nieuwe opties voor qbec toegepast:

  • —root some/app — stelt de map van de specifieke applicatie in
  • —force:k8s-context __incluster__ — dit is een magische variabele die aangeeft dat de deployment plaatsvindt in dezelfde cluster als waar de gitlab-runner draait. Dit is nodig, anders zal qbec proberen een geschikte Kubernetes-server in je kubeconfig te vinden.
  • —wait — dwingt qbec te wachten tot de aangemaakte resources in de status Ready zijn en vervolgens met een succesvolle exit-code eindigt.
  • —yes — schakelt gewoon de interactiviteit van de shell uit. Weet je het zeker? bij de deployment.

Vergeet niet onze wijzigingen te committen:

git add .gitlab-ci.yml
git commit -m "Automatiseer deployment"

En daarna git push zullen we zien hoe onze applicaties zijn gedeployed:

Screenshot van de tweede pipeline

We proberen nieuwe tools voor het bouwen en automatiseren van deployments in Kubernetes

11. Artefacten en build bij push naar master

De hierboven beschreven stappen zijn meestal voldoende voor de build en levering van bijna elke microservice, maar we willen niet elke keer een tag toevoegen wanneer we de site willen bijwerken. Daarom nemen we een meer dynamische aanpak en configureren we de deployment op basis van digest in de master-tak.

Het idee is eenvoudig: nu zal ons website de elke keer worden herbouwd bij een push naar master, en daarna automatisch worden gedeployed naar Kubernetes.

Laten we deze twee jobs in onze .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"

Let op, we hebben de tak master aan refs voor de job build_website en we gebruiken nu $CI_COMMIT_REF_NAME in plaats van $CI_COMMIT_TAG, dat wil zeggen, we maken ons los van tags in Git en zullen nu een image pushen met de naam van de tak die de pipeline heeft geïnitialiseerd. Opmerkelijk is dat dit ook met tags zal werken, wat ons in staat stelt om snapshots van de site met een specifieke versie in docker-registry op te slaan.

Wanneer de naam van de docker-tag voor de nieuwe versie van de website mogelijk constant kan blijven, moeten we nog steeds de wijzigingen voor Kubernetes beschrijven, anders zal het de applicatie gewoon niet opnieuw implementeren vanuit een nieuw beeld, omdat het geen wijzigingen in de deployment-manifest detecteert.

Optie —vm:ext-str digest=»$DIGEST» voor qbec — stelt ons in staat om een externe variabele in jsonnet door te geven. We willen dat met elke release van onze applicatie deze opnieuw wordt geïmplementeerd in het cluster. We kunnen hier geen gebruik meer maken van een tagnaam die nu mogelijk constant kan zijn, omdat we afhankelijk moeten zijn van een specifieke versie van het beeld en de deployment moeten triggeren bij een wijziging.

Hier helpt ons de mogelijkheid van Kaniko om de digest van het beeld in een bestand op te slaan (optie —digest-file)
Vervolgens zullen we dit bestand doorgeven en lezen op het moment van implementatie.

We zullen de parameters voor onze deploy/website/environments/base.libsonnet die er nu als volgt uitziet:

{
  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',
    },
  },
}

Klaar, nu zal elke commit in master de build van het docker-beeld initialiseren voor website, en vervolgens de implementatie in Kubernetes.

Vergeet niet onze wijzigingen te committen:

git add .
git commit -m "Configure dynamic build"

Laten we controleren, na git push zou je iets dergelijks moeten zien:

Screenshot van de pipeline voor master

We proberen nieuwe tools voor het bouwen en automatiseren van deployments in Kubernetes

In principe hoeven we de gitlab-runner niet opnieuw te implementeren bij elke push, tenzij er natuurlijk iets is veranderd in zijn configuratie, laten we dit aanpassen 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/**/*

wijzigingen zal toezicht houden op wijzigingen in deploy\/gitlab-runner\/ en zal onze job alleen triggeren bij dergelijke wijzigingen

Vergeet niet onze wijzigingen te committen:

git add .gitlab-ci.yml
git commit -m "Reduce gitlab-runner deploy"

git push, zo is het beter:

Screenshot van de bijgewerkte pipeline

We proberen nieuwe tools voor het bouwen en automatiseren van deployments in Kubernetes

12. Dynamische omgevingen

Het is tijd om onze pipeline te diversifiëren met dynamische omgevingen.

Als eerste zullen we de job build_website in onze .gitlab-ci.yml, de block only, te verwijderen, waardoor Gitlab deze zal triggeren bij elke commit in elke branch:

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/

Vervolgens zullen we de job bijwerken deploy_website, laten we daar een blok aan toevoegen omgeving:

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"

Dit stelt Gitlab in staat om de job te associëren met prod de omgeving en de juiste link naar die omgeving te tonen.

Laten we nu nog twee jobs toevoegen:

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

Deze zullen worden uitgevoerd bij elke push naar alle branches behalve master en zullen de preview versie van de website implementeren.

We zien een nieuwe optie voor qbec: —app-tag — deze stelt ons in staat om de geïmplementeerde versies van de applicatie te taggen en alleen binnen deze tag te werken. Bij het maken en vernietigen van bronnen in Kubernetes zal qbec alleen met deze tags werken.
Op deze manier hoeven we geen aparte omgeving voor elke review aan te maken, maar kunnen we eenvoudig dezelfde hergebruiken.

Hier gebruiken we ook qbec apply review, in plaats van qbec apply default — dit is precies het moment waarop we zullen proberen de verschillen tussen onze omgevingen (review en default) te beschrijven:

Laten we toevoegen review omgeving in deploy/website/qbec.yaml

spec:
  environments:
    review:
      defaultNamespace: docs
      server: https://kubernetes.example.org:8443

Vervolgens zullen we het verklaren 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.thisFile

En we zullen aangepaste parameters voor hem opschrijven 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',
    },
  },
}

Laten we ook eens goed kijken naar de job stop_review, deze zal worden getriggerd bij het verwijderen van de branch, en om te voorkomen dat Gitlab probeert deze uit te checken, wordt GIT_STRATEGY: nonegebruikt, later zullen we klonen master-branch en verwijderen review via deze.
Een beetje omslachtig, maar een mooiere manier heb ik nog niet gevonden.
Een alternatief kan zijn om elke review in een aparte namespace te implementeren, die altijd helemaal kan worden gewist.

Vergeet niet onze wijzigingen te committen:

git add .
git commit -m "Automatische review inschakelen"

git push, git checkout -b test, git push origin test, controleren:

Screenshot van de aangemaakte omgevingen in Gitlab

We proberen nieuwe tools voor het bouwen en automatiseren van deployments in Kubernetes

Werkt alles? — geweldig, we verwijderen onze testbranch: git checkout master, git push origin :test, controleren of de jobs voor het verwijderen van de omgeving zonder fouten zijn uitgevoerd.

Hier wil ik meteen verduidelijken dat elke ontwikkelaar in het project branches kan aanmaken, hij kan ook het .gitlab-ci.yml bestand wijzigen en toegang krijgen tot de geheime variabelen.
Daarom wordt ten zeerste aanbevolen om hun gebruik alleen toe te staan voor protected-branches, bijvoorbeeld in master, of een aparte set variabelen voor elke omgeving aan te maken.

13. Review Apps

Review Apps dit is een functie van Gitlab, waarmee je voor elk bestand in de repository een knop kunt toevoegen voor snelle weergave in de gedeployde omgeving.

Om deze knoppen te laten verschijnen, moet je een bestand aanmaken .gitlab/route-map.yml en daar alle padtransformaties beschrijven, in ons geval zal dat heel eenvoudig zijn:

# Indices
- source: /content/(.+?)_index.(md|html)/ 
  public: '1'

# Pages
- source: /content/(.+?).(md|html)/ 
  public: '1/'

Vergeet niet onze wijzigingen te committen:

git add .gitlab/
git commit -m "Review apps inschakelen"

git push, en controleren:

Screenshot van de Review App-knop

We proberen nieuwe tools voor het bouwen en automatiseren van deployments in Kubernetes

Job is gedaan!

Projectbronnen:

Bedankt voor je aandacht, ik hoop dat je het leuk vond We proberen nieuwe tools voor het bouwen en automatiseren van deployments in Kubernetes

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster