Încercăm noi instrumente pentru construirea și automatizarea desfășurării în Kubernetes

Încercăm noi instrumente pentru construirea și automatizarea desfășurării în Kubernetes

Salut! În ultima vreme au apărut multe instrumente grozave de automatizare atât pentru construirea imaginilor Docker, cât și pentru desfășurarea în Kubernetes. Din acest motiv, am decis să mă joc cu GitLabul, să-i studiez capacitățile și, desigur, să configurez un pipeline.

Inspirația pentru această lucrare a fost site-ul kubernetes.io, care este generat din coduri sursă în mod automat, iar pentru fiecare cerere de pull trimisă, robotul generează automat o versiune preview a site-ului cu modificările tale și oferă un link pentru vizualizare.

Am încercat să construiesc un proces similar de la zero, dar complet bazat pe GitLab CI și instrumente gratuite pe care le folosesc pentru desfășurarea aplicațiilor în Kubernetes. Astăzi, în sfârșit, vă voi povesti mai multe despre ele.

Articolul va analiza instrumente precum:
Hugo, qbec, kaniko, git-crypt și GitLab CI cu crearea de medii dinamice.

Cuprins

  1. Introducere în Hugo
  2. Pregătirea Dockerfile-ului
  3. Introducere în kaniko
  4. Introducere în qbec
  5. Încercăm GitLab-runner cu executor Kubernetes
  6. Desfășurarea chart-urilor Helm cu qbec
  7. Introducere în git-crypt
  8. Creăm imaginea toolbox
  9. Primul nostru pipeline și construirea imaginilor după etichete
  10. Automatizarea desfășurării
  11. Artefacte și construirea la push în master
  12. Mediile dinamice
  13. Review Apps

1. Introducere în Hugo

Ca exemplu al proiectului nostru, vom încerca să creăm un site pentru publicarea documentației, construit pe Hugo. Hugo este un generator statitic de conținut.

Pentru cei care nu sunt familiarizați cu generatoarele statice, voi oferi câteva informații suplimentare. Spre deosebire de motoarele de site obișnuite, cu baze de date și cu ceva php, care, la cererea utilizatorului, generează pagini pe loc, generatoarele statice funcționează puțin diferit. Ele permit preluarea surselor, de obicei un set de fișiere în formatare Markdown și șabloane de teme, apoi compilarea acestora într-un site complet gata.

Adică, la final, veți obține o structură de directoare și un set de fișiere html generate, care pot fi pur și simplu transferate pe orice hosting ieftin pentru a obține un site funcțional.

Hugo poate fi instalat local și testat în practică:

Inițializăm un nou site:

hugo new site docs.example.org

Și, de asemenea, un repo git:

cd docs.example.org
git init

Până acum, site-ul nostru este complet curat și, pentru a adăuga ceva pe el, trebuie mai întâi să conectăm o temă. O temă este pur și simplu un set de șabloane și reguli stabilite după care se generează site-ul nostru.

Ca temă, vom folosi Learn, care, în opinia mea, se potrivește perfect pentru un site cu documentație.

Merită să menționăm că nu trebuie să păstrăm fișierele temei în depozitul nostru de proiect. În schimb, putem să le conectăm folosind git submodule:

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

Astfel, în depozitul nostru vor exista doar fișierele care se referă direct la proiectul nostru, iar tema conectată va rămâne sub formă de link către un depot specific și un commit în acesta, ceea ce înseamnă că poate fi oricând extrasă din sursa originală fără a ne teme de modificări incompatibile.

Să modificăm configurația config.toml:

baseURL = "http://docs.example.org/"
languageCode = "en-us"
title = "My Docs Site"
theme = "learn"

Deja în acest etapă, putem porni:

hugo server

Și la adresa http://localhost:1313/ să verificăm site-ul creat de noi, toate modificările efectuate în director sunt actualizate automat în pagina deschisă în browser, foarte convenabil!

Să încercăm să creăm o pagină de titlu în content/_index.md:

# My docs site

## Welcome to the docs!

You will be very smart :-)

Captură de ecran a paginii tocmai create

Încercăm noi instrumente pentru construirea și automatizarea desfășurării în Kubernetes

Pentru a genera site-ul, este suficient să rulăm:

hugo

Conținutul directorului public/ va reprezenta site-ul vostru.
Da, apropo, să o adăugăm imediat în .gitignore:

echo /public > .gitignore

Nu uitați să comiteți modificările noastre:

git add .
git commit -m "New site created"

2. Pregătirea Dockerfile-ului

A sosit timpul să definim structura depozitului nostru. De obicei, folosesc ceva de genul:

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

  • dockerfiles/ — conține directoare cu Dockerfiles și tot ceea ce este necesar pentru construirea imaginilor noastre docker.
  • deploy/ — conține directoare pentru desfășurarea aplicațiilor noastre în Kubernetes.

Astfel, primul nostru Dockerfile îl vom crea pe calea 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" ]

După cum puteți observa, Dockerfile-ul conține două FROM, această capacitate se numește multi-stage build și permite excluderea din imaginea finală docker a tot ceea ce nu este necesar.
Prin urmare, imaginea finală va conține doar darkhttpd (un server HTTP ușor) și public/ — conținutul site-ului nostru generat static.

Nu uitați să comiteți modificările noastre:

git add dockerfiles/website
git commit -m "Adăugați Dockerfile pentru site"

3. Familiarizarea cu kaniko

Ca builder pentru imaginile docker, am decis să folosesc kaniko, deoarece pentru funcționarea sa nu este necesar un demon docker, iar construcția poate fi realizată pe orice mașină, stocând cache-ul direct în registry, eliminând astfel necesitatea de a avea un depozit persistent complet.

Pentru a construi imaginea, este suficient să porniți un container cu kaniko executor și să-i transmiteți contextul curent de construire, lucru care poate fi realizat și local, prin 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

Unde registry.gitlab.com/kvaps/docs.example.org/website — este numele imaginii dumneavoastră docker, care, după construcție, va fi automat împins în registry docker.

Parametru —cache permite caching-ul stratelor în docker registry, în exemplul dat, acestea vor fi salvate în registry.gitlab.com/kvaps/docs.example.org/website/cache, dar puteți specifica și un alt drum folosind parametrul —cache-repo.

Screenshot docker-registry

Încercăm noi instrumente pentru construirea și automatizarea desfășurării în Kubernetes

4. Familiarizarea cu qbec

Qbec — este un instrument de desfășurare care permite descrierea declarativă a manifestelor aplicației dumneavoastră și desfășurarea lor în Kubernetes. Utilizarea Jsonnet ca sintaxă principală permite simplificarea semnificativă a descrierii diferențelor pentru mai multe medii, precum și eliminarea aproape totală a repetitivității codului.

Acest lucru poate fi deosebit de relevant în situațiile în care trebuie să desfășurați aplicația în mai multe clustere cu parametri diferiți și doriți să le descrieți declarativ în Git.

Qbec permite, de asemenea, renderizarea chart-urilor Helm transmițându-le parametrii necesari și ulterior lucrând cu ele la fel ca cu manifestele obișnuite, inclusiv aplicându-le diverse mutații, ceea ce, la rândul său, elimină necesitatea utilizării ChartMuseum. Adică, puteți stoca și reda chart-uri direct din git, acolo unde le este locul.

Așa cum am spus mai devreme, toate desfășurările vor fi stocate în directorul deploy/:

mkdir deploy
cd deploy

Haideți să inițializăm prima noastră aplicație:

qbec init website
cd website

Acum structura aplicației noastre arată astfel:

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

să ne uităm la fișier qbec.yaml:

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

Aici ne interesează în primul rând spec.environments, qbec a creat deja pentru noi mediu default și a preluat adresa serverului, precum și namespace din kubeconfig-ul nostru curent.
Acum, la implementare în default mediu, qbec va implementa întotdeauna doar în clusterul Kubernetes specificat și în namespace-ul specificat, ceea ce înseamnă că nu va mai trebui să comutați între contexte și namespace-uri pentru a efectua implementarea.
În caz de nevoie, puteți actualiza întotdeauna setările din acest fișier.

Toate mediile dvs. sunt descrise în qbec.yaml, și în fișierul params.libsonnet, unde se precizează de unde trebuie să preia parametrii pentru acestea.

Apoi vedem două directoare:

  • components/ — aici vor fi stocate toate manifestele pentru aplicația noastră, acestea pot fi descrise atât în jsonnet cât și în fișiere yaml obișnuite
  • environments/ — aici vom descrie toate variabilele (parametrii) pentru mediile noastre.

Implicit avem două fișiere:

  • environments/base.libsonnet — acesta va conține parametrii comuni pentru toate mediile
  • environments/default.libsonnet — conține parametrii suprascrişi pentru mediu default

Să deschidem environments/base.libsonnet și să adăugăm acolo parametrii pentru prima noastră componentă:

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

Să creăm și prima noastră componentă 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,
                },
              },
            ],
          },
        },
      ],
    },
  },
]

În acest fișier am descris trei entități Kubernetes, și anume: Deployment, Serviciu și Ingress. Dacă dorim, am putea să le scoatem în componente separate, dar în acest stadiu ne ajunge una singură.

Sintaxa jsonnet este foarte similar cu json obișnuit; de fapt, json obișnuit este deja un jsonnet valid, așa că pentru o vreme ar putea fi mai ușor să folosiți servicii online precum yaml2json pentru a converti yaml-ul cunoscut într-un json, sau, dacă componentele dumneavoastră nu conțin variabile, atunci pot fi descrise simplu ca yaml obișnuit.

Când lucrați cu jsonnet vă recomand cu căldură să instalați un plugin pentru editorul dumneavoastră.

De exemplu, pentru vim există pluginul vim-jsonnet, care include evidențierea sintaxei și execută automat jsonnet fmt la fiecare salvare (necessită instalarea jsonnet).

Totul este pregătit, acum putem începe implementarea:

Pentru a vedea ce am obținut, vom executa:

qbec show default

La ieșire veți vedea manifestele yaml renderizate, care vor fi aplicate în clusterul default.

Excelent, acum să aplicăm:

qbec apply default

La ieșire, veți vedea întotdeauna ce se va face în clusterul vostru; qbec vă va solicita să confirmați modificările tastând Stabiliți o parolă și păstrați-o în siguranță! vei putea confirma intențiile tale.

Gata, acum aplicația noastră este implementată!

În cazul în care efectuați modificări, veți putea întotdeauna să executați:

qbec diff default

pentru a vedea cum aceste modificări vor afecta desfășurarea actuală

Nu uitați să comiteți modificările noastre:

cd ..\/..
git add deploy\/website
git commit -m "Adaugă desfășurare pentru website"

5. Testăm Gitlab-runner cu executor Kubernetes

Până nu demult, am folosit doar obișnuitul gitlab-runner pe o mașină pregătită anterior (container LXC) cu executor shell sau docker. Inițial, am avut câțiva astfel de runneri, global definiti în GitLab-ul nostru. Aceștia compilau imagini docker pentru toate proiectele.

Dar, cum a arătat practica, această variantă nu este cea mai ideală, atât din punct de vedere al practicabilității, cât și al securității. Este mult mai bine și ideologic corect să avem runneri separați desfășurați pentru fiecare proiect, sau chiar pentru fiecare mediu.

Din fericire, aceasta nu este o problemă, deoarece acum vom desfășura gitlab-runner direct ca parte a proiectului nostru în Kubernetes.

GitLab oferă un chart helm pregătit pentru desfășurarea gitlab-runner în Kubernetes. Așadar, tot ce trebuie să faceți este să aflați registration token pentru proiectul nostru în Setări —> CI \/ CD —> Runners și să-l transmiteți 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

Unde:

  • https://gitlab.com — adresa serverului dvs. GitLab.
  • yga8y-jdCusVDn_t4Wxc — registration token pentru proiectul dvs.
  • rbac.create=true — oferă runner-ului numărul necesar de privilegii pentru a putea crea poduri pentru a-și desfășura sarcinile folosind executorul kubernetes.

Dacă totul a fost realizat corect, ar trebui să vedeți un runner înregistrat în secțiunea Runners, în setările proiectului dvs.

Captura de ecran a runnerului adăugat

Încercăm noi instrumente pentru construirea și automatizarea desfășurării în Kubernetes

Așa de simplu? — da, așa de simplu! Nicio bătaie de cap cu înregistrarea runnerelor manual, de acum înainte runnerii vor fi creați și distruși automat.

6. Desfășurarea Helm-chart-urilor cu QBEC

Dat fiind că am decis să considerăm gitlab-runner parte a proiectului nostru, a venit timpul să îl descriem în repository-ul nostru Git.

Am putea să-l descriem ca un component separat website, dar în continuare ne planificăm să desfășurăm copii diferite website foarte des, spre deosebire de gitlab-runner, care va fi desfășurat doar o dată pe fiecare cluster Kubernetes. Așa că să inițializăm o aplicație separată pentru acesta:

cd deploy
qbec init gitlab-runner
cd gitlab-runner

De data aceasta, nu vom descrie entitățile Kubernetes manual, ci vom folosi un chart Helm gata făcut. Unul dintre avantajele qbec este capacitatea de a reda charturi Helm direct dintr-un repository Git.

Să-l conectăm folosind submodul git:

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

Acum directorul vendor/gitlab-runner conține repository-ul nostru cu chartul pentru gitlab-runner.

În mod similar, putem conecta și alte repository-uri, de exemplu, întreg repository-ul cu charturile oficiale. https://github.com/helm/charts

Să descriem componenta 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,
  }
)

Primul argument pentru expandHelmTemplate este calea către chart, apoi params.values, pe care le vom lua din parametrii de mediu, apoi urmează un obiect cu

  • nameTemplate — numele release-ului
  • namespace — spațiul de nume transmis lui Helm
  • thisFile — parametru obligatoriu care transmite calea către fișierul curent
  • verbose — arată comanda helm template cu toate argumentele la redarea chartului.

Acum să descriem parametrii pentru componenta noastră în 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ă rugăm să observați runnerRegistrationToken îl luăm dintr-un fișier extern secrets/base.libsonnet, să-l creăm:

{
  runnerRegistrationToken: 'yga8y-jdCusVDn_t4Wxc',
}

Să verificăm dacă totul funcționează:

qbec show default

dacă totul este în regulă, putem șterge release-ul nostru anterioar, care a fost desfășurat prin Helm:

helm uninstall gitlab-runner

și să-l desfășurăm din nou, dar acum prin qbec:

qbec apply default

7. Familiarizare cu git-crypt

Git-crypt este un instrument care permite configurarea criptării transparente pentru repository-ul tău.

În prezent, structura directorului nostru pentru gitlab-runner arată astfel:

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

Dar a stoca secrete în Git nu este sigur, nu-i așa? Așa că trebuie să le criptăm corespunzător.

De obicei, pentru o singură variabilă nu are mereu sens. Poți transmite secretele în qbec și prin variabilele de mediu ale sistemului tău CI.
Dar merită menționat că există și proiecte mai complexe, care pot conține mult mai multe secrete, iar transmiterea tuturor lor prin intermediul variabilelor de mediu va fi extrem de dificilă.

În plus, în acest caz nu aș fi putut să vă vorbesc despre un instrument atât de minunat numit git-crypt.

git-crypt este, de asemenea, convenabil deoarece permite păstrarea întregii istorii a secretelor, precum și compararea, combinarea și rezolvarea conflictelor exact așa cum suntem obișnuiți să facem în cazul Git.

Primul lucru după instalare git-crypt trebuie să generăm chei pentru depozitul nostru:

git crypt init

Dacă ai un cheie PGP, poți să te adaugi imediat ca colaborator pentru acest proiect:

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

Astfel, vei putea extorce acest depozit folosind cheia ta privată.

Dacă nu ai o cheie PGP și nu se preconizează că o vei avea, poți urma o altă cale și exporta cheia proiectului:

git crypt export-key /path/to/keyfile

Astfel, oricine deține cheia exportată keyfile va putea extorce depozitul tău.

A sosit timpul să configurăm primul nostru secret.
Îți amintesc, suntem în continuare în directorul deploy/gitlab-runner/, unde avem un director secrets/, să criptăm toate fișierele din el, pentru aceasta să creăm un fișier secrets/.gitattributes cu următorul conținut:

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

După cum se vede din conținut, toate fișierele conform măștii * vor fi procesate prin git-crypt, cu excepția celei proprii .gitattributes

Putem verifica aceasta executând:

git crypt status -e

La ieșire vom obține o listă a tuturor fișierelor din depozit pentru care criptarea este activată.

Și gata, acum putem să comitem fără grijă modificările noastre:

cd ../..
git add .
git commit -m "Adaugă implementarea pentru gitlab-runner"

Pentru a bloca depozitul, este suficient să execuți:

git crypt lock

și imediat toate fișierele criptate se vor transforma într-o entitate binară, citirea lor va fi imposibilă.
Pentru a decripta depozitul, executați:

git crypt unlock

8. Creăm imaginea toolbox

Imaginea toolbox este o imagine cu toate instrumentele pe care le vom folosi pentru implementarea proiectului nostru. Aceasta va fi utilizată de gitlab-runner pentru a efectua sarcinile tipice de implementare.

Aici totul este simplu, creăm un nou dockerfiles/toolbox/Dockerfile cu următorul conținut:

DIN 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

Așa cum puteți observa, în această imagine instalăm toate utilitarele pe care le-am folosit pentru a desfășura aplicația noastră. Nu avem nevoie aici de kubectl, dar este posibil să doriți să experimentați cu el în etapa de configurare a pipeline-ului.

De asemenea, pentru a putea interacționa cu Kubernetes și a desfășura aplicații în el, trebuie să configurăm un rol pentru podurile generate de gitlab-runner.

Pentru asta, să mergem în directorul cu gitlab-runner:

cd deploy/gitlab-runner

și să adăugăm o nouă componentă 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,
      },
    ],
  },
]

De asemenea, vom descrie noile parametre în environments/base.libsonnet, care acum arată astfel:

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ă rugăm să observați $.components.rbac.name se referă la name pentru componenta rbac

Hai să vedem ce s-a schimbat:

qbec diff default

și să aplicăm modificările noastre în Kubernetes:

qbec apply default

Nu uitați să comiteți modificările noastre în git:

cd ../../
git add dockerfiles/toolbox
git commit -m "Adaugă Dockerfile pentru toolbox"
git add deploy/gitlab-runner
git commit -m "Configurează gitlab-runner pentru a folosi toolbox"

9. Primul nostru pipeline și construcția imaginilor după etichete

În rădăcina proiectului, vom crea .gitlab-ci.yml cu următorul conținut:

.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

Vă rugăm să observați, folosim GIT_SUBMODULE_STRATEGY: normal pentru acele joburi unde trebuie să inițializăm clar submodulele înainte de a executa.

Nu uitați să comiteți modificările noastre:

git add .gitlab-ci.yml
git commit -m "Automatizare construirea docker"

Cred că putem să numim aceasta versiunea v0.0.1 și să etichetăm:

git tag v0.0.1

Vom eticheta de fiecare dată când va fi nevoie să lansăm o nouă versiune. Etichetele în imaginile Docker vor fi legate de etichetele Git. Fiecare push cu o nouă etichetă va iniția construirea imaginilor cu această etichetă.

Vom executa git push —tags, și să ne uităm la primul nostru pipeline:

Captura de ecran a primului pipeline

Încercăm noi instrumente pentru construirea și automatizarea desfășurării în Kubernetes

Este important de menționat că construirea pe baza etichetelor este potrivită pentru construirea imaginilor Docker, dar nu este adecvată pentru a desfășura aplicația în Kubernetes. Deoarece noile etichete pot fi atribuite și comenzilor mai vechi, în acest caz inițierea pipeline-ului pentru acestea va duce la desfășurarea versiunii vechi.

Pentru a rezolva această problemă, de obicei construirea imaginilor Docker este legată de etichete, iar desfășurarea aplicației este legată de ramura master, în care sunt hardcodate versiunile imaginilor construite. Doar în acest caz veți putea iniția rollback cu un simplu revert master-ramura.

10. Automatizarea desfășurării

Pentru ca Gitlab-runner să poată decripta secretele noastre, va trebui să exportăm cheia de repository și să o adăugăm în variabilele de mediu ale CI-ului nostru:

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

vom salva șirul obținut în Gitlab, pentru aceasta vom merge la setările proiectului nostru:
Settings —> CI / CD —> Variables

Și vom crea o nouă variabilă:

Tip
Cheie
Value
Protected
Masked
Domeniul

File
GITCRYPT_KEY
<your string>
true (pentru perioada de învățare putem și false)
true
All environments

Captura de ecran a variabilei adăugate

Încercăm noi instrumente pentru construirea și automatizarea desfășurării în Kubernetes

Acum vom actualiza .gitlab-ci.yml adăugând în el:

.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

Aici am utilizat câteva opțiuni noi pentru qbec:

  • —root some/app — permite definirea directorului pentru aplicația specifică
  • —force:k8s-context __incluster__ — aceasta este o variabilă magică care indică faptul că implementarea va avea loc în același cluster în care este rulant gitlab-runner. Este necesar să facem acest lucru, deoarece în caz contrar qbec va încerca să găsească un server Kubernetes adecvat în kubeconfig-ul dumneavoastră.
  • —wait — forțează qbec să aștepte ca resursele create să treacă în starea Ready și abia atunci să finalizeze cu un cod de ieșire de succes.
  • —yes — dezactivează pur și simplu shell-ul interactiv Ești sigur? la implementare.

Nu uitați să comiteți modificările noastre:

git add .gitlab-ci.yml
git commit -m "Automatizează implementarea"

Și după git push vom vedea cum au fost implementate aplicațiile noastre:

Captura de ecran a celui de-al doilea pipeline

Încercăm noi instrumente pentru construirea și automatizarea desfășurării în Kubernetes

11. Artefacte și construire la push pe master

În general, pașii descriși mai sus sunt suficienți pentru a construi și livra aproape orice microserviciu, dar nu vrem să etichetăm de fiecare dată când avem nevoie să actualizăm site-ul. Prin urmare, vom urma o cale mai dinamică și vom configura implementarea pe digest în ramura master.

Ideea este simplă: acum imaginea noastră website va fi reconstruită de fiecare dată când se face push în master, iar după aceea se va implementa automat în Kubernetes.

Să actualizăm aceste două joburi în .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"

Rețineți, am adăugat ramura master la refs pentru jobul build_website și acum folosim $CI_COMMIT_REF_NAME în loc de $CI_COMMIT_TAG, adică ne detașăm de etichete în Git și acum vom face push la imagine cu numele ramurii commit-ului care a inițializat pipeline-ul. Merită remarcat că aceasta va funcționa și cu etichete, ceea ce ne va permite să păstrăm snapshot-uri ale site-ului cu o versiune specifică în docker-registry.

Atunci când numele etichetei docker pentru noua versiune a site-ului poate rămâne neschimbat, trebuie totuși să descriem modificările pentru Kubernetes; altfel, acesta pur și simplu nu va redeploya aplicația din noul anumit, deoarece nu va observa nici o modificare în manifestul de deploy.

Opțiune —vm:ext-str digest=»$DIGEST» pentru qbec — permite trecerea unei variabile externe în jsonnet. Vrem ca, cu fiecare lansare a aplicației noastre, aceasta să fie redeployată în cluster. Nu mai putem folosi un nume de etichetă care acum poate fi constant, deoarece trebuie să ne legăm de o versiune specifică a imaginii și să declanșăm deploy-ul atunci când aceasta se modifică.

Aici ne ajută posibilitatea Kaniko de a salva digestul imaginii într-un fișier (opțiunea —digest-file)
Apoi, acest fișier îl vom transmite și îl vom citi în momentul deploy-ului.

Să actualizăm parametrii pentru deploy/website/environments/base.libsonnet care acum va arăta astfel:

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

Este gata, acum orice commit în master inițiază construirea imaginii docker pentru website, iar apoi și deploy-ul acesteia în Kubernetes.

Nu uitați să comiteți modificările noastre:

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

Să verificăm, după git push ar trebui să vedem ceva de genul:

Screenshot din pipeline pentru master

Încercăm noi instrumente pentru construirea și automatizarea desfășurării în Kubernetes

Practica nu necesită redeployarea gitlab-runner-ului la fiecare push, cu excepția cazului în care, desigur, nu s-au modificat configurațiile sale; să corectăm acest lucru în .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 va permite monitorizarea modificărilor în deploy/gitlab-runner/ și va declanșa job-ul nostru doar în cazul existenței acestora

Nu uitați să comiteți modificările noastre:

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

git push, așa e mai bine:

Screenshot din pipeline-ul actualizat

Încercăm noi instrumente pentru construirea și automatizarea desfășurării în Kubernetes

12. Medii dinamice

A venit momentul să diversificăm pipeline-ul nostru cu medii dinamice.

Pentru început, să actualizăm job-ul build_website din .gitlab-ci.yml, eliminând blocul only, ceea ce va determina Gitlab să-l declanșeze la orice commit în orice ramură:

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/

Apoi vom actualiza job-ul deploy_website, vom adăuga acolo un bloc 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"

Acest lucru va permite Gitlab-ului să asocieze job-ul cu prod mediu și să afișeze linkul corect către acesta.

Acum vom adăuga încă două job-uri:

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

Acestea vor fi activate la push pe orice ramură în afară de master și vor desfășura versiunea de previzualizare a site-ului.

Vedem o nouă opțiune pentru qbec: —app-tag — aceasta permite etichetarea versiunilor desfășurate ale aplicației și lucrul doar în cadrul acestei etichete, atunci când se creează și se distrug resurse în Kubernetes qbec va opera doar cu acestea.
Astfel, nu trebuie să creăm un mediu separat pentru fiecare revizuire, ci putem pur și simplu să reutilizăm același mediu.

Aici folosim de asemenea qbec apply review, în loc de qbec apply default — acesta este chiar momentul în care ne vom strădui să descriem diferențele pentru mediile noastre (revizuire și implicit):

Să adăugăm mediu de revizuire în deploy/website/qbec.yaml spec: environments: review: defaultNamespace: docs server: https://kubernetes.example.org:8443

Apoi, îl vom declara în

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:

Și vom înregistra parametrii personalizați pentru el în

deploy/website/environments/review.libsonnet Să ne uităm mai atent la job-ul:

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

stop_review , acesta va fi inițiat la ștergerea ramurii și pentru ca gitlab să nu încerce să facă checkout pe aceasta, se utilizeazăGIT_STRATEGY: none , ulterior vom clona-ramura și vom șterge revizuirea prin aceasta. master-ramura și distrugem review prin intermediul acesteia.
Este puțin complicat, dar nu am găsit încă o modalitate mai frumoasă.
O alternativă ar putea fi să desfășurăm fiecare review într-un spațiu de nume separat, care poate fi oricând șters complet.

Nu uitați să comiteți modificările noastre:

git add .
git commit -m "Activează revizuirea automată"

git push, git checkout -b test, git push origin test, verificăm:

Captură de ecran a mediu-urilor create în Gitlab

Încercăm noi instrumente pentru construirea și automatizarea desfășurării în Kubernetes

Funcționează totul? — excelent, ștergem ramura noastră de test: git checkout master, git push origin :test, verificăm că job-urile de ștergere a mediilor au funcționat fără erori.

Aici aș dori să subliniez că orice dezvoltator din proiect poate crea ramuri, el poate de asemenea să modifice .gitlab-ci.yml fișierul și să obțină acces la variabilele secrete.
Așadar, este recomandat cu tărie să se permită utilizarea acestora doar pentru ramuri protejate, de exemplu în master, sau să se creeze un set separat de variabile pentru fiecare mediu.

13. Aplicații de Revizuire

Review Apps aceasta este o funcționalitate a Gitlab-ului, care permite adăugarea unui buton pentru a vizualiza rapid fiecare fișier din repository în mediul desfășurat.

Pentru ca aceste butoane să apară, este necesar să creați un fișier .gitlab/route-map.yml și să descrieți în el toate transformările de căi, în cazul nostru va fi foarte simplu:

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

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

Nu uitați să comiteți modificările noastre:

git add .gitlab/
git commit -m "Activează aplicațiile de revizuire"

git push, și verificăm:

Captură de ecran a butonului Aplicație de Revizuire

Încercăm noi instrumente pentru construirea și automatizarea desfășurării în Kubernetes

Jobul este finalizat!

Sursele proiectului:

Mulțumesc pentru atenție, sper că v-a plăcut Încercăm noi instrumente pentru construirea și automatizarea desfășurării în Kubernetes

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster