Provare nuovi strumenti per creare e automatizzare la distribuzione in Kubernetes

Provare nuovi strumenti per creare e automatizzare la distribuzione in Kubernetes

Ciao! Recentemente sono stati rilasciati molti interessanti strumenti di automazione sia per la creazione di immagini Docker che per la distribuzione su Kubernetes. A questo proposito, ho deciso di giocare con GitLab, studiarne a fondo le capacità e, ovviamente, impostare la pipeline.

Questo lavoro è stato ispirato dal sito web kubernetes.io, da cui viene generato codici sorgente automaticamente e per ogni richiesta di pool inviata, il robot genera automaticamente una versione di anteprima del sito con le modifiche e fornisce un collegamento per la visualizzazione.

Ho provato a creare un processo simile da zero, ma interamente basato su Gitlab CI e strumenti gratuiti che sono abituato a utilizzare per distribuire applicazioni su Kubernetes. Oggi finalmente vi racconterò di più su di loro.

L'articolo discuterà di strumenti come:
Hugo, qbec, Kaniko, git-crypt и CI GitLab con la creazione di ambienti dinamici.

contenuto

  1. Incontra Ugo
  2. Preparazione del Dockerfile
  3. Conoscere Kaniko
  4. Conoscere qbec
  5. Provando Gitlab-runner con l'esecutore Kubernetes
  6. Distribuzione dei grafici Helm con qbec
  7. Presentazione di git-crypt
  8. Creazione di un'immagine della casella degli strumenti
  9. La nostra prima pipeline e assemblaggio di immagini tramite tag
  10. Automazione della distribuzione
  11. Artefatti e assemblaggio quando si spinge verso il master
  12. Ambienti dinamici
  13. Rivedi le app

1. Conoscere Hugo

Come esempio del nostro progetto, proveremo a creare un sito per la pubblicazione di documentazione basato su Hugo. Hugo è un generatore di contenuti statici.

Per coloro che non hanno familiarità con i generatori statici, vi dirò qualcosa in più su di loro. A differenza dei motori di siti web convenzionali con un database e un po’ di PHP, che, quando richiesto da un utente, generano pagine al volo, i generatori statici sono progettati in modo leggermente diverso. Ti consentono di prendere fonti, in genere una serie di file in markup Markdown e modelli di temi, quindi compilarli in un sito Web completamente finito.

Cioè, di conseguenza, riceverai una struttura di directory e una serie di file HTML generati, che potrai semplicemente caricare su qualsiasi hosting economico e ottenere un sito Web funzionante.

Puoi installare Hugo localmente e provarlo:

Inizializzazione di un nuovo sito:

hugo new site docs.example.org

E allo stesso tempo il repository git:

cd docs.example.org
git init

Finora, il nostro sito è incontaminato e affinché qualcosa appaia su di esso, dobbiamo prima collegare un tema; un tema è solo un insieme di modelli e regole specificate in base ai quali viene generato il nostro sito.

Per il tema che useremo Imparare, che secondo me si adatta perfettamente ad un sito di documentazione.

Vorrei prestare particolare attenzione al fatto che non è necessario salvare i file del tema nel repository del nostro progetto; possiamo invece semplicemente collegarlo utilizzando sottomodulo git:

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

Pertanto, il nostro repository conterrà solo file direttamente correlati al nostro progetto e il tema collegato rimarrà come collegamento a un repository specifico e un commit in esso, ovvero potrà sempre essere estratto dalla fonte originale e non aver paura di modifiche incompatibili.

Correggiamo la configurazione config.toml:

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

Già in questa fase puoi eseguire:

hugo server

E all'indirizzo http://localhost:1313/ controlla il nostro sito web appena creato, tutte le modifiche apportate alla directory aggiornano automaticamente la pagina aperta nel browser, molto comodo!

Proviamo a creare una copertina in contenuto/_index.md:

# My docs site

## Welcome to the docs!

You will be very smart :-)

Screenshot della pagina appena creata

Provare nuovi strumenti per creare e automatizzare la distribuzione in Kubernetes

Per generare un sito, basta eseguire:

hugo

Contenuto della directory pubblico/ e sarà il tuo sito web.
Sì, a proposito, aggiungiamolo immediatamente .gitignore:

echo /public > .gitignore

Non dimenticare di confermare le nostre modifiche:

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

2. Preparazione del Dockerfile

È tempo di definire la struttura del nostro repository. Di solito uso qualcosa come:

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

  • dockerfile/ — contiene directory con file Docker e tutto il necessario per creare le nostre immagini Docker.
  • distribuire/ — contiene le directory per la distribuzione delle nostre applicazioni su Kubernetes

Pertanto, creeremo il nostro primo Dockerfile lungo il percorso dockerfiles/sito Web/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" ]

Come puoi vedere, il Dockerfile ne contiene due DA, questa funzionalità viene chiamata costruzione in più fasi e ti consente di escludere tutto ciò che non è necessario dall'immagine docker finale.
Pertanto, l'immagine finale conterrà solo scurohttpd (server HTTP leggero) e pubblico/ — il contenuto del nostro sito web generato staticamente.

Non dimenticare di confermare le nostre modifiche:

git add dockerfiles/website
git commit -m "Add Dockerfile for website"

3. Conoscere Kaniko

Come costruttore di immagini docker, ho deciso di utilizzare Kaniko, poiché il suo funzionamento non richiede un demone docker e la compilazione stessa può essere eseguita su qualsiasi macchina e la cache può essere archiviata direttamente nel registro, eliminando così la necessità di disporre di un archivio persistente completo.

Per creare l'immagine, esegui semplicemente il contenitore con esecutore testamentario Kaniko e passargli il contesto di build corrente; questo può essere fatto anche localmente, tramite 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

Где registro.gitlab.com/kvaps/docs.example.org/website — il nome dell'immagine della finestra mobile; dopo la creazione, verrà automaticamente avviata nel registro della finestra mobile.

Parametro --cache ti consente di memorizzare nella cache i livelli nel registro della finestra mobile; per l'esempio fornito, verranno salvati in registro.gitlab.com/kvaps/docs.example.org/website/cache, ma è possibile specificare un altro percorso utilizzando il parametro --cache-repository.

Screenshot del registro della finestra mobile

Provare nuovi strumenti per creare e automatizzare la distribuzione in Kubernetes

4. Conoscere qbec

Qbec è uno strumento di distribuzione che ti consente di descrivere in modo dichiarativo i manifesti della tua applicazione e di distribuirli su Kubernetes. L'utilizzo di Jsonnet come sintassi principale consente di semplificare notevolmente la descrizione delle differenze tra più ambienti e inoltre elimina quasi completamente la ripetizione del codice.

Ciò può essere particolarmente vero nei casi in cui è necessario distribuire un'applicazione su più cluster con parametri diversi e si desidera descriverli in modo dichiarativo in Git.

Qbec ti consente anche di eseguire il rendering dei grafici Helm passando loro i parametri necessari e quindi di gestirli allo stesso modo dei normali manifest, incluso puoi applicare loro varie mutazioni e questo, a sua volta, ti consente di eliminare la necessità di utilizzare ChartMuseum. Cioè, puoi archiviare ed eseguire il rendering dei grafici direttamente da git, a cui appartengono.

Come ho detto prima, memorizzeremo tutte le distribuzioni nella directory distribuire/:

mkdir deploy
cd deploy

Inizializziamo la nostra prima applicazione:

qbec init website
cd website

Ora la struttura della nostra applicazione è simile alla seguente:

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

diamo un'occhiata al fascicolo qbec.yaml:

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

Qui ci interessa soprattutto ambienti spec, qbec ha già creato un ambiente predefinito per noi e ha preso l'indirizzo del server, nonché lo spazio dei nomi dal nostro attuale kubeconfig.
Ora durante la distribuzione in difetto ambiente, qbec eseguirà sempre la distribuzione solo nel cluster Kubernetes specificato e nello spazio dei nomi specificato, ovvero non sarà più necessario passare da un contesto all'altro e da uno spazio dei nomi all'altro per eseguire una distribuzione.
Se necessario, puoi sempre aggiornare le impostazioni in questo file.

Tutti i tuoi ambienti sono descritti in qbec.yamle nel file params.libsonnet, dove dice dove ottenere i relativi parametri.

Successivamente vediamo due directory:

  • componenti / — tutti i manifest per la nostra applicazione verranno archiviati qui; possono essere descritti sia in jsonnet che in normali file yaml
  • ambienti/ — qui descriveremo tutte le variabili (parametri) per i nostri ambienti.

Per impostazione predefinita abbiamo due file:

  • ambienti/base.libsonnet - conterrà parametri comuni a tutti gli ambienti
  • ambienti/default.libsonnet — contiene parametri sovrascritti per l'ambiente difetto

apriamo ambienti/base.libsonnet e aggiungi lì i parametri per il nostro primo componente:

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

Creiamo anche il nostro primo componente componenti/sito web.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 questo file abbiamo descritto tre entità Kubernetes contemporaneamente, queste sono: Distribuzione, Servizi и Ingresso. Se volessimo, potremmo inserirli in diversi componenti, ma in questa fase ce ne basterà uno solo.

sintassi jsonnet è molto simile al json normale, in linea di principio il json normale è già un jsonnet valido, quindi all'inizio potrebbe essere più semplice per te utilizzare servizi online come yaml2json per convertire il tuo solito yaml in json o, se i tuoi componenti non contengono variabili, possono essere descritti sotto forma di yaml regolare.

Quando si lavora con jsonnet Consiglio vivamente di installare un plugin per il tuo editor

Ad esempio, esiste un plugin per vim vim-jsonnet, che attiva l'evidenziazione della sintassi e viene eseguito automaticamente jsonnet fmt ogni volta che salvi (richiede jsonnet installato).

Tutto è pronto, ora possiamo iniziare a distribuire:

Per vedere cosa abbiamo ottenuto, eseguiamo:

qbec show default

All'output, vedrai i manifest yaml renderizzati che verranno applicati al cluster predefinito.

Ottimo, ora applica:

qbec apply default

In output vedrai sempre cosa verrà fatto nel tuo cluster, qbec ti chiederà di accettare le modifiche digitando y potrai confermare le tue intenzioni.

La nostra applicazione è pronta e distribuita!

Se apporti modifiche, puoi sempre fare:

qbec diff default

per vedere come queste modifiche influenzeranno la distribuzione corrente

Non dimenticare di confermare le nostre modifiche:

cd ../..
git add deploy/website
git commit -m "Add deploy for website"

5. Provare Gitlab-runner con l'esecutore Kubernetes

Fino a poco tempo fa usavo solo il normale gitlab runner su una macchina pre-preparata (contenitore LXC) con shell o docker-esecutore. Inizialmente, nel nostro gitlab avevamo definiti diversi corridori di questo tipo a livello globale. Hanno raccolto immagini docker per tutti i progetti.

Ma come ha dimostrato la pratica, questa opzione non è la più ideale, sia in termini di praticità che di sicurezza. È molto meglio e ideologicamente più corretto disporre di corridori separati per ciascun progetto, o anche per ciascun ambiente.

Fortunatamente, questo non è affatto un problema, poiché ora ci schiereremo gitlab runner direttamente come parte del nostro progetto direttamente in Kubernetes.

Gitlab fornisce un diagramma timone già pronto per la distribuzione di gitlab-runner su Kubernetes. Quindi tutto quello che devi fare è scoprirlo token di registrazione per il nostro progetto in Impostazioni -> CI/CD -> Runner e passalo al timone:

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

Dove:

  • https://gitlab.com — l'indirizzo del tuo server Gitlab.
  • yga8y-jdCusVDn_t4Wxc — token di registrazione per il tuo progetto.
  • rbac.create=true — fornisce al corridore la quantità necessaria di privilegi per poter creare pod per eseguire le nostre attività utilizzando kubernetes-executor.

Se tutto è fatto correttamente, dovresti vedere un corridore registrato nella sezione Runners, nelle impostazioni del progetto.

Screenshot del corridore aggiunto

Provare nuovi strumenti per creare e automatizzare la distribuzione in Kubernetes

È così semplice? - sì, è così semplice! Non dovrai più preoccuparti di registrare manualmente i corridori, d'ora in poi i corridori verranno creati e distrutti automaticamente.

6. Distribuisci i grafici Helm con QBEC

Dal momento che abbiamo deciso di considerare gitlab runner parte del nostro progetto, è tempo di descriverlo nel nostro repository Git.

Potremmo descriverlo come un componente separato sito web, ma in futuro prevediamo di distribuire copie diverse sito web molto spesso, a differenza gitlab runner, che verrà distribuito solo una volta per cluster Kubernetes. Quindi inizializziamo un'applicazione separata per questo:

cd deploy
qbec init gitlab-runner
cd gitlab-runner

Questa volta non descriveremo manualmente le entità Kubernetes, ma utilizzeremo un grafico Helm già pronto. Uno dei vantaggi di qbec è la capacità di eseguire il rendering dei grafici Helm direttamente da un repository Git.

Colleghiamolo utilizzando il sottomodulo git:

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

Ora la directory fornitore/corridore gitlab Abbiamo un repository con un grafico per gitlab-runner.

In modo simile, puoi collegare altri repository, ad esempio l'intero repository con le carte ufficiali https://github.com/helm/charts

Descriviamo il componente componenti/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,
  }
)

Il primo argomento a espandereHelmTemplate passiamo il percorso al grafico, allora parametri.valori, che prendiamo dai parametri ambientali, poi arriva l'oggetto

  • nomeModello - titolo della pubblicazione
  • namespace — spazio dei nomi trasferito a helm
  • questa vita — un parametro obbligatorio che trasmette il percorso del file corrente
  • verboso - mostra il comando modello di timone con tutti gli argomenti durante il rendering del grafico

Ora descriviamo i parametri per il nostro componente in ambienti/base.libsonnet:

local secrets = import '../secrets/base.libsonnet';

{
  components: {
    gitlabRunner: {
      name: 'gitlab-runner',
      values: {
        gitlabUrl: 'https://gitlab.com/',
        rbac: {
          create: true,
        },
        runnerRegistrationToken: secrets.runnerRegistrationToken,
      },
    },
  },
}

Nota runnerRegistrationToken prendiamo da un file esterno secrets/base.libsonnet, creiamolo:

{
  runnerRegistrationToken: 'yga8y-jdCusVDn_t4Wxc',
}

Controlliamo se funziona tutto:

qbec show default

se tutto è in ordine, possiamo eliminare la nostra versione precedentemente distribuita tramite Helm:

helm uninstall gitlab-runner

e distribuirlo allo stesso modo, ma tramite qbec:

qbec apply default

7. Introduzione a git-crypt

Git-cripta è uno strumento che ti consente di impostare una crittografia trasparente per il tuo repository.

Al momento, la nostra struttura di directory per gitlab-runner è simile alla seguente:

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

Ma archiviare i segreti in Git non è sicuro, vero? Quindi dobbiamo crittografarli correttamente.

Di solito, per il bene di una variabile, questo non ha sempre senso. Puoi trasferire i segreti a qbec e attraverso le variabili di ambiente del tuo sistema CI.
Ma vale la pena notare che esistono anche progetti più complessi che possono contenere molti più segreti; trasferirli tutti tramite variabili d’ambiente sarà estremamente difficile.

Inoltre, in questo caso non saprei parlarvi di uno strumento così meraviglioso come git-crypt.

git-crypt È anche comodo in quanto consente di salvare l'intera cronologia dei segreti, nonché di confrontare, unire e risolvere i conflitti nello stesso modo in cui siamo abituati a fare nel caso di Git.

La prima cosa dopo l'installazione git-crypt dobbiamo generare le chiavi per il nostro repository:

git crypt init

Se disponi di una chiave PGP, puoi aggiungerti immediatamente come collaboratore per questo progetto:

git-crypt add-gpg-user [email protected]

In questo modo puoi sempre decrittografare questo repository utilizzando la tua chiave privata.

Se non hai una chiave PGP e non te la aspetti, puoi andare dall'altra parte ed esportare la chiave del progetto:

git crypt export-key /path/to/keyfile

Quindi, chiunque abbia un'esportazione file di chiavi sarà in grado di decrittografare il tuo repository.

È ora di stabilire il nostro primo segreto.
Lascia che ti ricordi che siamo ancora nella directory distribuire/gitlab-runner/, dove abbiamo una directory segreti/, crittifichiamo tutti i file in esso contenuti, per questo creeremo un file secrets/.gitattributes con il seguente contenuto:

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

Come si può vedere dal contenuto, tutti i file sono mascherati * verrà attraversato git-crypt, tranne la maggior parte .gitattributes

Possiamo verificarlo eseguendo:

git crypt status -e

L'output sarà un elenco di tutti i file nel repository per i quali è abilitata la crittografia

Questo è tutto, ora possiamo tranquillamente effettuare il commit delle nostre modifiche:

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

Per bloccare un repository, basta eseguire:

git crypt lock

e immediatamente tutti i file crittografati si trasformeranno in qualcosa di binario, sarà impossibile leggerli.
Per decrittografare il repository, esegui:

git crypt unlock

8. Creare un'immagine della casella degli strumenti

Un'immagine della casella degli strumenti è un'immagine con tutti gli strumenti che utilizzeremo per distribuire il nostro progetto. Verrà utilizzato dal runner Gitlab per eseguire attività di distribuzione tipiche.

Tutto è semplice qui, creiamone uno nuovo dockerfile/toolbox/Dockerfile con il seguente contenuto:

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

Come puoi vedere, in questa immagine installiamo tutte le utilità che abbiamo utilizzato per distribuire la nostra applicazione. Non ne abbiamo bisogno qui a meno che kubectl, ma potresti voler giocarci durante la fase di configurazione della pipeline.

Inoltre, per poter comunicare con Kubernetes e distribuirlo, dobbiamo configurare un ruolo per i pod generati da gitlab-runner.

Per fare ciò, andiamo nella directory con gitlab-runner:

cd deploy/gitlab-runner

e aggiungi un nuovo componente componenti/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,
      },
    ],
  },
]

Descriveremo anche i nuovi parametri in ambienti/base.libsonnet, che ora assomiglia a questo:

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

Nota $.components.rbac.nome si riferisce a Nome per componente RBAC

Controlliamo cosa è cambiato:

qbec diff default

e applica le nostre modifiche a Kubernetes:

qbec apply default

Inoltre, non dimenticare di confermare le nostre modifiche su git:

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. La nostra prima pipeline e assemblaggio di immagini tramite tag

Alla radice del progetto che creeremo .gitlab-ci.yml con il seguente contenuto:

.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

Si prega di notare che usiamo GIT_SUBMODULE_STRATEGY: normale per quei lavori in cui è necessario inizializzare esplicitamente i sottomoduli prima dell'esecuzione.

Non dimenticare di confermare le nostre modifiche:

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

Penso che possiamo tranquillamente chiamarla una versione v0.0.1 e aggiungi il tag:

git tag v0.0.1

Aggiungeremo tag ogni volta che avremo bisogno di rilasciare una nuova versione. I tag nelle immagini Docker saranno legati ai tag Git. Ogni push con un nuovo tag inizializzerà la creazione di immagini con questo tag.

eseguire git push --tagse diamo un'occhiata alla nostra prima pipeline:

Screenshot della prima pipeline

Provare nuovi strumenti per creare e automatizzare la distribuzione in Kubernetes

Vale la pena attirare la vostra attenzione sul fatto che l'assemblaggio tramite tag è adatto per creare immagini docker, ma non è adatto per distribuire un'applicazione su Kubernetes. Poiché è possibile assegnare nuovi tag a vecchi commit, in questo caso l'inizializzazione della pipeline porterà alla distribuzione della vecchia versione.

Per risolvere questo problema, solitamente la creazione delle immagini docker è legata ai tag e la distribuzione dell'applicazione a un ramo Mastercard, in cui le versioni delle immagini raccolte sono codificate. Qui è dove puoi inizializzare il rollback con un semplice ripristino Mastercard-rami.

10. Automazione della distribuzione

Affinché Gitlab-runner possa decrittografare i nostri segreti, dovremo esportare la chiave del repository e aggiungerla alle nostre variabili di ambiente CI:

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

Salveremo la riga risultante in Gitlab; per fare ciò andiamo nelle impostazioni del nostro progetto:
Impostazioni -> CI/CD -> Variabili

E creiamo una nuova variabile:

Tipologia
Le
Valore
Protetta
Masked
Obbiettivo

File
GITCRYPT_KEY
<your string>
true (durante l'allenamento puoi false)
true
All environments

Screenshot della variabile aggiunta

Provare nuovi strumenti per creare e automatizzare la distribuzione in Kubernetes

Ora aggiorniamo il nostro .gitlab-ci.yml aggiungendo ad esso:

.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

Qui abbiamo abilitato diverse nuove opzioni per qbec:

  • --root alcuni/app — consente di determinare la directory di un'applicazione specifica
  • --force:contesto-k8s __incluster__ - questa è una variabile magica che dice che la distribuzione avverrà nello stesso cluster in cui è in esecuzione gtilab-runner. Ciò è necessario perché altrimenti qbec proverà a trovare un server Kubernetes adatto nel tuo kubeconfig
  • --Aspettare — forza qbec ad attendere finché le risorse create non entrano nello stato Pronto e solo allora escono con un codice di uscita corretto.
  • -SÌ - disabilita semplicemente la shell interattiva Sei sicuro? quando distribuito.

Non dimenticare di confermare le nostre modifiche:

git add .gitlab-ci.yml
git commit -m "Automate deploy"

E dopo spingere vedremo come sono state implementate le nostre applicazioni:

Screenshot della seconda pipeline

Provare nuovi strumenti per creare e automatizzare la distribuzione in Kubernetes

11. Artefatti e assemblaggio quando si spinge verso il master

In genere, i passaggi sopra descritti sono sufficienti per creare e fornire quasi tutti i microservizi, ma non vogliamo aggiungere un tag ogni volta che dobbiamo aggiornare il sito. Pertanto, prenderemo un percorso più dinamico e configureremo una distribuzione digest nel ramo principale.

L'idea è semplice: ora l'immagine del ns sito web verrà ricostruito ogni volta che entri Mastercarde quindi eseguire la distribuzione automatica su Kubernetes.

Aggiorniamo questi due lavori nel ns .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"

Tieni presente che abbiamo aggiunto un thread Mastercard к rif per posti di lavoro build_sito web e ora usiamo $CI_COMMIT_REF_NAME invece di $CI_COMMIT_TAG, cioè siamo slegati dai tag in Git e ora invieremo un'immagine con il nome del ramo commit che ha inizializzato la pipeline. Vale la pena notare che funzionerà anche con i tag, che ci permetteranno di salvare istantanee di un sito con una versione specifica nel registro del docker.

Quando il nome del docker tag per una nuova versione del sito può rimanere invariato, dobbiamo comunque descrivere le modifiche a Kubernetes, altrimenti semplicemente non ridistribuirà l'applicazione dalla nuova immagine, poiché non noterà alcun cambiamento nell'immagine. manifesto di distribuzione.

Opzione —vm:ext-str digest="$DIGEST" per qbec: consente di passare una variabile esterna a jsonnet. Vogliamo che venga ridistribuito nel cluster con ogni rilascio della nostra applicazione. Non possiamo più utilizzare il nome del tag, che ora può essere immutabile, poiché dobbiamo essere legati a una versione specifica dell'immagine e attivare la distribuzione quando cambia.

Qui saremo aiutati dalla capacità di Kaniko di salvare un'immagine digest in un file (opzione --file-digest)
Quindi trasferiremo questo file e lo leggeremo al momento della distribuzione.

Aggiorniamo i parametri per il nostro deploy/sito web/environments/base.libsonnet che ora sarà simile a questo:

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

Fatto, ora esegui qualsiasi impegno Mastercard inizializza la build dell'immagine docker per sito webe quindi distribuirlo a Kubernetes.

Non dimenticare di confermare le nostre modifiche:

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

Controlleremo più tardi spingere dovremmo vedere qualcosa del genere:

Screenshot della pipeline per master

Provare nuovi strumenti per creare e automatizzare la distribuzione in Kubernetes

In linea di principio, non abbiamo bisogno di ridistribuire gitlab-runner ad ogni push, a meno che, ovviamente, non sia cambiato nulla nella sua configurazione, sistemiamolo 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/**/*

i cambiamenti ti consentirà di monitorare i cambiamenti in distribuire/gitlab-runner/ e attiverà il nostro lavoro solo se ce ne sono

Non dimenticare di confermare le nostre modifiche:

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

spingere, così va meglio:

Screenshot della pipeline aggiornata

Provare nuovi strumenti per creare e automatizzare la distribuzione in Kubernetes

12. Ambienti dinamici

È tempo di diversificare la nostra pipeline con ambienti dinamici.

Innanzitutto, aggiorniamo il lavoro build_sito web nel nostro .gitlab-ci.yml, rimuovendo il blocco da esso esclusivamente, che forzerà Gitlab ad attivarlo su qualsiasi commit su qualsiasi ramo:

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/

Quindi aggiorna il lavoro distribuzione_sito web, aggiungi un blocco lì ambiente:

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"

Ciò consentirà a Gitlab di associare il lavoro pungolo ambiente e visualizzare il collegamento corretto ad esso.

Ora aggiungiamo altri due lavori:

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

Verranno lanciati tramite push su qualsiasi ramo tranne master e distribuiranno la versione di anteprima del sito.

Vediamo una nuova opzione per qbec: --app-tag — ti consente di taggare le versioni distribuite dell'applicazione e di lavorare solo all'interno di questo tag; durante la creazione e la distruzione di risorse in Kubernetes, qbec funzionerà solo con esse.
In questo modo non possiamo creare un ambiente separato per ogni recensione, ma semplicemente riutilizzare lo stesso.

Anche qui usiamo qbec applica la revisioneinvece di qbec applica l'impostazione predefinita - questo è esattamente il momento in cui proveremo a descrivere le differenze per i nostri ambienti (revisione e default):

aggiungere recensioni ambiente dentro deploy/sito web/qbec.yaml

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

Poi lo dichiareremo deploy/sito web/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

E annota i parametri personalizzati deploy/sito web/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',
    },
  },
}

Diamo anche uno sguardo più da vicino a jobu stop_review, verrà attivato quando il ramo viene eliminato e in modo che gitlab non tenti di effettuare il checkout viene utilizzato GIT_STRATEGY: nessuno, poi cloniamo Mastercard-ramificare ed eliminare la recensione attraverso di esso.
È un po’ confuso, ma non ho ancora trovato un modo più bello.
Un'opzione alternativa sarebbe quella di distribuire ciascuna recensione nello spazio dei nomi di un hotel, che può sempre essere completamente demolito.

Non dimenticare di confermare le nostre modifiche:

git add .
git commit -m "Enable automatic review"

spingere, git checkout -b test, test di origine git push, controllo:

Screenshot degli ambienti creati in Gitlab

Provare nuovi strumenti per creare e automatizzare la distribuzione in Kubernetes

Funziona tutto? - ottimo, elimina il nostro ramo di prova: master di git checkout, git push origine:test, controlliamo che i processi di eliminazione dell'ambiente abbiano funzionato senza errori.

Qui vorrei subito chiarire che qualsiasi sviluppatore in un progetto può creare rami, può anche cambiare .gitlab-ci.yml file e accedere alle variabili segrete.
Pertanto, si consiglia vivamente di consentirne l'uso solo per i rami protetti, ad esempio in Mastercardoppure creare un set separato di variabili per ogni ambiente.

13. Rivedi le app

Rivedi le app Questa è una funzionalità di GitLab che ti consente di aggiungere un pulsante per ogni file nel repository per visualizzarlo rapidamente in un ambiente distribuito.

Affinché questi pulsanti vengano visualizzati, è necessario creare un file .gitlab/route-map.yml e descrivere tutte le trasformazioni di percorso in esso contenute; nel nostro caso sarà molto semplice:

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

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

Non dimenticare di confermare le nostre modifiche:

git add .gitlab/
git commit -m "Enable review apps"

spingeree controlla:

Screenshot del pulsante dell'app Revisione

Provare nuovi strumenti per creare e automatizzare la distribuzione in Kubernetes

Il lavoro è finito!

Fonti del progetto:

Grazie per l'attenzione, spero che vi sia piaciuto Provare nuovi strumenti per creare e automatizzare la distribuzione in Kubernetes

Fonte: habr.com

Aggiungi un commento