Essai de nouveaux outils pour la construction et l'automatisation du déploiement dans Kubernetes.

Essai de nouveaux outils pour la construction et l'automatisation du déploiement dans Kubernetes.

Bonjour ! Récemment, de nombreux outils d'automatisation intéressants ont été lancés, tant pour la création d'images Docker que pour le déploiement dans Kubernetes. J'ai donc décidé d'explorer GitLab, d'étudier ses fonctionnalités et bien sûr, de configurer un pipeline.

L'inspiration pour ce travail a été le site kubernetes.io, qui est généré à partir de codes sources automatiquement, et pour chaque demande de tirage envoyée, le robot génère automatiquement une version d'aperçu du site avec vos modifications et fournit un lien pour sa consultation.

J'ai tenté d'établir un processus similaire depuis le début, mais entièrement construit sur GitLab CI et des outils gratuits que j'ai l'habitude d'utiliser pour déployer des applications dans Kubernetes. Aujourd'hui, je vais enfin vous en parler plus en détail.

Cet article abordera des outils tels que :
Hugo, qbec, kaniko, git-crypt et GitLab CI pour la création d'environnements dynamiques.

Table des matières

  1. Découverte de Hugo
  2. Préparation du Dockerfile
  3. Découverte de kaniko
  4. Découverte de qbec
  5. Essai de GitLab-runner avec Kubernetes-executor
  6. Déploiement de chartes Helm avec qbec
  7. Découverte de git-crypt
  8. Création d'une image toolbox
  9. Notre premier pipeline et construction d'images par étiquettes
  10. Automatisation du déploiement
  11. Artifacts et construction lors d'un push sur master
  12. Environnements dynamiques
  13. Applications de révision

1. Découverte de Hugo

Pour l'exemple de notre projet, nous allons essayer de créer un site pour la publication de documentation, construit sur Hugo. Hugo est un générateur de contenu statique.

Pour ceux qui ne connaissent pas les générateurs statiques, je vais en dire un peu plus. Contrairement aux moteurs de sites classiques avec une base de données et quelque chose comme du php, qui, à la demande de l'utilisateur, génèrent des pages à la volée, les générateurs statiques fonctionnent un peu différemment. Ils permettent de prendre des sources, généralement un ensemble de fichiers en Markdown et des modèles de thèmes, puis de les compiler en un site complètement prêt.

Cela signifie qu'à la sortie, vous obtiendrez une structure de répertoires et un ensemble de fichiers html générés, que vous pourrez simplement télécharger sur n'importe quel hébergement bon marché et obtenir un site opérationnel.

Hugo peut être installé localement et essayé :

Initialisons un nouveau site :

hugo new site docs.example.org

Et en même temps, un dépôt git :

cd docs.example.org
git init

Pour l'instant, notre site est vierge et pour y voir quelque chose, nous devons d'abord connecter un thème, le thème n'est rien d'autre qu'un ensemble de modèles et de règles définies selon lesquelles notre site est généré.

Nous allons utiliser comme thème Learn, qui, à mon avis, convient parfaitement pour un site de documentation.

Une attention particulière doit être accordée au fait que nous n'avons pas besoin de conserver les fichiers du thème dans le dépôt de notre projet, à la place, nous pouvons simplement l’ajouter en utilisant git submodule:

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

Ainsi, notre dépôt contiendra uniquement les fichiers directement liés à notre projet, tandis que le thème connecté restera sous forme de lien vers un dépôt spécifique et un commit dedans, ce qui signifie qu'il pourra toujours être récupéré à partir de la source originale sans craindre d'incompatibilités.

Corrigeons le config config.toml:

baseURL = "http://docs.example.org/"
languageCode = "en-us"
title = "Mon site de documentation"
theme = "learn"

À ce stade, nous pouvons déjà lancer :

hugo server

Et à l'adresse http://localhost:1313/ vérifier notre site nouvellement créé, toutes les modifications apportées dans le répertoire mettent automatiquement à jour la page ouverte dans le navigateur, très pratique !

Essayons de créer une page d'accueil dans content/_index.md:

# My docs site

## Welcome to the docs!

You will be very smart :-)

Capture d'écran de la page nouvellement créée

Essai de nouveaux outils pour la construction et l'automatisation du déploiement dans Kubernetes.

Pour générer le site, il suffit de lancer :

hugo

Le contenu du répertoire public/ sera votre site.
Au fait, ajoutons-le directement dans .gitignore:

echo /public > .gitignore

N’oublions pas de valider nos modifications :

git add .
git commit -m "Nouveau site créé"

2. Préparation du Dockerfile

Il est temps de définir la structure de notre dépôt. En général, j'utilise quelque chose comme :

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

  • dockerfiles/ contiennent des répertoires avec des Dockerfiles et tout le nécessaire pour construire nos images docker.
  • deploy/ contient des répertoires pour déployer nos applications dans Kubernetes

Ainsi, notre premier Dockerfile sera créé dans 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" ]

Comme vous pouvez le constater, le Dockerfile contient deux DE, cette fonctionnalité s'appelle multi-stage build et permet d'exclure tout ce qui est inutile du docker-image final.
Ainsi, l'image finale contiendra uniquement darkhttpd (un serveur HTTP léger) et public/ — le contenu de notre site statiquement généré.

N’oublions pas de valider nos modifications :

git add dockerfiles/website
git commit -m "Ajouter Dockerfile pour le site"

3. Introduction à kaniko

En tant qu'assembleur d'images docker, j'ai choisi d'utiliser kaniko, car son fonctionnement ne nécessite pas de démon docker, et la construction peut être effectuée sur n'importe quelle machine, tout en stockant le cache directement dans le registre, ce qui élimine la nécessité d'avoir un stockage persistant complet.

Pour construire l'image, il suffit de lancer un conteneur avec kaniko executor et de lui transmettre le contexte de construction actuel, cela peut être fait localement, 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

Où registry.gitlab.com / kvaps / docs.example.org / website — le nom de votre image docker, après la construction, elle sera automatiquement poussée dans le registre docker.

Paramètre —cache permet de mettre en cache les couches dans le registre docker. Dans l'exemple donné, elles seront sauvegardées dans registry.gitlab.com / kvaps / docs.example.org / website / cache, mais vous pouvez également spécifier un autre chemin à l'aide du paramètre —cache-repo.

Capture d'écran du docker-registry

Essai de nouveaux outils pour la construction et l'automatisation du déploiement dans Kubernetes.

4. Introduction à qbec

Qbec est un outil de déploiement qui permet de décrire de manière déclarative les manifestes de votre application et de les déployer dans Kubernetes. L'utilisation de Jsonnet comme syntaxe principale permet de simplifier considérablement la description des différences pour plusieurs environnements, tout en éliminant presque entièrement la répétabilité du code.

Cela peut être particulièrement pertinent dans les cas où vous devez déployer une application sur plusieurs clusters avec des paramètres différents et souhaiter les décrire de manière déclarative dans Git.

Qbec permet également de rendre les graphiques Helm en leur transmettant les paramètres nécessaires, et ensuite de les manipuler comme des manifestes ordinaires, y compris la possibilité d'appliquer diverses mutations, ce qui permet d'éviter d'utiliser ChartMuseum. Ainsi, il est possible de stocker et de rendre des graphiques directement depuis git, où ils ont leur place.

Comme je l'ai dit précédemment, tous les déploiements seront stockés dans le répertoire deploy/:

mkdir deploy
cd deploy

Initialisons notre première application :

qbec init website
cd website

Actuellement, la structure de notre application ressemble à cela :

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

jetons un œil au fichier qbec.yaml:

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

Ici, nous nous intéressons principalement à spec.environments, qbec a déjà créé pour nous un environnement par défaut et a pris l'adresse du serveur ainsi que le namespace de notre kubeconfig actuel.
Maintenant, lors du déploiement dans default l'environnement, qbec déploiera toujours uniquement dans le cluster Kubernetes spécifié et dans le namespace spécifié, ce qui signifie que vous n'aurez plus besoin de basculer entre les contextes et les namespaces pour effectuer un déploiement.
En cas de besoin, vous pouvez toujours mettre à jour les paramètres dans ce fichier.

Tous vos environnements sont décrits dans qbec.yaml, et dans le fichier params.libsonnet, où il est indiqué d'où prendre les paramètres pour eux.

Ensuite, nous voyons deux répertoires :

  • components/ — tous les manifests pour notre application seront stockés ici, ils peuvent être décrits soit en jsonnet soit dans des fichiers yaml ordinaires
  • environments/ — ici, nous décrirons toutes les variables (paramètres) pour nos environnements.

Par défaut, nous avons deux fichiers :

  • environments/base.libsonnet — il contiendra des paramètres communs pour tous les environnements
  • environments/default.libsonnet — contient des paramètres redéfinis pour l'environnement default

Ouvrons environments/base.libsonnet et ajoutons les paramètres pour notre premier composant :

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

Créons également notre premier composant 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,
                },
              },
            ],
          },
        },
      ],
    },
  },
]

Dans ce fichier, nous avons décrit trois entités Kubernetes : Déploiement, Service et Ingress. Si nous le souhaitions, nous pourrions les séparer en différents composants, mais à ce stade, un seul nous suffit.

Syntaxe jsonnet ressemble beaucoup à du json normal, en fait, du json normal est déjà un jsonnet valide, donc au début, il vous sera peut-être plus facile d'utiliser des services en ligne comme yaml2json pour convertir votre yaml habituel en json, ou, si vos composants ne contiennent aucune variable, vous pouvez tout à fait les décrire sous forme de yaml normal.

Lors de l'utilisation de jsonnet je vous recommande vivement d'installer un plugin pour votre éditeur.

Par exemple, pour vim, il existe un plugin vim-jsonnet, qui ajoute la coloration syntaxique et exécute automatiquement jsonnet fmt à chaque sauvegarde (nécessite l'installation de jsonnet).

Tout est prêt, nous pouvons maintenant commencer le déploiement :

Pour voir ce que nous avons obtenu, exécutons :

qbec show default

En sortie, vous verrez les manifestes yaml rendus qui seront appliqués dans le cluster par défaut.

Super, maintenant appliquons :

qbec apply default

En sortie, vous verrez toujours ce qui sera fait dans votre cluster, qbec vous demandera de confirmer les changements en tapant y vous pourrez confirmer vos intentions.

C'est fait, notre application est déployée !

En cas de modifications, vous pourrez toujours exécuter :

qbec diff default

pour voir comment ces changements vont affecter le déploiement actuel.

N’oublions pas de valider nos modifications :

cd ..\/..\ngit add deploy\/website\ngit commit -m "Ajout du déploiement pour le site web"

5. Essayons Gitlab-runner avec Kubernetes-executor.

Jusqu'à récemment, j'utilisais uniquement des exécutants conventionnels. gitlab-runner sur une machine préparée à l'avance (un conteneur LXC) avec un shell- ou docker-executor. Au départ, nous avions plusieurs de ces exécutants définis globalement dans notre GitLab. Ils construisaient des images Docker pour tous les projets.

Mais comme l'a montré la pratique, cette option n'est pas la plus optimale, que ce soit en termes de praticité ou de sécurité. Il est de loin préférable et idéologiquement plus juste d'avoir des exécutants déployés pour chaque projet, voire pour chaque environnement.

Heureusement, ce n'est pas un problème, car maintenant nous allons déployer gitlab-runner directement comme partie de notre projet dans Kubernetes.

Gitlab fournit un chart helm prêt à l'emploi pour déployer gitlab-runner dans Kubernetes. Donc, tout ce dont vous avez besoin, c'est de connaître le registration token de notre projet dans Paramètres —> CI \/ CD —> Exécutants et de le transmettre à helm :

helm repo add gitlab https:\/\/charts.gitlab.io\n\nhelm install gitlab-runner \n  --set gitlabUrl=https:\/\/gitlab.com \n  --set runnerRegistrationToken=yga8y-jdCusVDn_t4Wxc \n  --set rbac.create=true \n  gitlab\/gitlab-runner

Où :

  • https://gitlab.com — l'adresse de votre serveur Gitlab.
  • yga8y-jdCusVDn_t4Wxc — registration token pour votre projet.
  • rbac.create=true — donne à l'exécutant les privilèges nécessaires pour pouvoir créer des pods afin d'exécuter nos tâches avec l'exécuteur Kubernetes.

Si tout a été fait correctement, vous devriez voir l'exécutant enregistré dans la section Exécutants, dans les paramètres de votre projet.

Capture d'écran de l'exécutant ajouté.

Essai de nouveaux outils pour la construction et l'automatisation du déploiement dans Kubernetes.

C'est aussi simple que ça ? — oui, aussi simple ! Plus de tracas avec l'enregistrement manuel des exécutants, à partir de maintenant, les exécutants seront créés et détruits automatiquement.

6. Déploiement des charts Helm avec QBEC

Puisque nous avons décidé de considérer gitlab-runner comme faisant partie de notre projet, il est temps de le décrire dans notre dépôt Git.

Nous pourrions le décrire comme un composant séparé site web, mais à l'avenir, nous prévoyons de déployer différentes copies site web très souvent, contrairement gitlab-runner, qui ne sera déployé qu'une seule fois sur chaque cluster Kubernetes. Alors, initialisons une application distincte pour cela :

cd deploy\nqbec init gitlab-runner\ncd gitlab-runner

Cette fois, nous n'allons pas décrire les entités Kubernetes manuellement, mais nous allons utiliser un chart Helm prêt à l'emploi. Un des avantages de qbec est la possibilité de rendre les charts Helm directement depuis un dépôt Git.

Connectons-le en utilisant git submodule :

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

Le répertoire vendor/gitlab-runner contient le dépôt avec le chart pour gitlab-runner.

De la même manière, vous pouvez connecter d'autres dépôts, par exemple, le dépôt entier avec les charts officiels. https://github.com/helm/charts

Décrivons le composant 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,
  }
)

Le premier argument pour expandHelmTemplate est le chemin vers le chart, puis params.values, que nous allons prendre à partir des paramètres d'environnement, suivi d'un objet contenant

  • nameTemplate — le nom de la release
  • namespace — le namespace transmis à Helm
  • thisFile — un paramètre obligatoire, transmettant le chemin vers le fichier actuel
  • verbose — montre la commande helm template avec tous les arguments lors du rendu du chart.

Décrivons maintenant les paramètres pour notre composant dans 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,
      },
    },
  },
}

Veuillez noter runnerRegistrationToken nous le prenons du fichier externe secrets/base.libsonnet, créons-le :

{
  runnerRegistrationToken: 'yga8y-jdCusVDn_t4Wxc',
}

Vérifions si tout fonctionne :

qbec show default

si tout est en ordre, nous pouvons supprimer notre release précédemment déployée via Helm :

helm uninstall gitlab-runner

et le redéployer, mais déjà via qbec :

qbec apply default

7. Introduction à git-crypt

Git-crypt est un outil qui permet de configurer un chiffrement transparent pour votre dépôt.

Actuellement, la structure de notre répertoire pour gitlab-runner ressemble à cela :

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

Mais stocker des secrets dans Git n'est pas sûr, n'est-ce pas ? Il nous faut donc les chiffrer correctement.

En général, pour une seule variable, ce n'est pas toujours logique. Vous pouvez transmettre des secrets dans qbec et via des variables d'environnement dans votre système CI.
Il convient de noter qu'il existe des projets plus complexes qui peuvent contenir beaucoup plus de secrets, et les transmettre tous via des variables d'environnement serait extrêmement difficile.

De plus, dans ce cas, je n'aurais pas pu vous parler d'un outil aussi formidable que git-crypt.

git-crypt il est également pratique car il permet de conserver tout l'historique des secrets, ainsi que de comparer, fusionner et résoudre les conflits comme nous avons l'habitude de le faire avec Git.

La première chose à faire après l'installation git-crypt est de générer des clés pour notre dépôt :

git crypt init

Si vous avez une clé PGP, vous pouvez immédiatement vous ajouter en tant que collaborateur à ce projet :

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

Ainsi, vous pourrez toujours déchiffrer ce dépôt en utilisant votre clé privée.

Si vous n'avez pas de clé PGP et qu'aucune n'est prévue, vous pouvez suivre une autre voie et exporter la clé du projet :

git crypt export-key /path/to/keyfile

De cette façon, quiconque possède le keyfile pourra déchiffrer votre dépôt.

Il est temps de configurer notre premier secret.
Rappelons que nous sommes toujours dans le répertoire deploy/gitlab-runner/, où nous avons un répertoire secrets/, créons donc un fichier pour chiffrer tous les fichiers qui s'y trouvent secrets/.gitattributes avec ce contenu :

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

Comme on peut le voir dans le contenu, tous les fichiers correspondant au modèle * seront traités par git-crypt, à l'exception de .gitattributes

Nous pouvons vérifier cela en exécutant :

git crypt status -e

En sortie, nous obtiendrons la liste de tous les fichiers dans le dépôt pour lesquels le chiffrement est activé.

Voilà, maintenant nous pouvons commettre nos changements :

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

Pour verrouiller le dépôt, il suffit d'exécuter :

git crypt lock

et tous les fichiers chiffrés se transformeront en quelque chose de binaire, il sera impossible de les lire.
Pour déchiffrer le dépôt, exécutez :

git crypt unlock

8. Créons une image toolbox

L'image Toolbox est une image avec tous les outils que nous allons utiliser pour déployer notre projet. Elle sera utilisée par GitLab Runner pour exécuter des tâches de déploiement standard.

C'est simple, créons un nouveau dockerfiles/toolbox/Dockerfile avec ce contenu :

DE l'image 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

Comme vous pouvez le constater, dans cette image, nous installons tous les outils que nous avons utilisés pour déployer notre application. À part cela, nous n'en avons pas besoin ici kubectl, mais vous pourriez vouloir l'expérimenter lors de la configuration du pipeline.

De plus, pour pouvoir communiquer avec Kubernetes et y déployer, nous devons configurer un rôle pour les pods générés par gitlab-runner.

Pour cela, allons dans le répertoire du gitlab-runner :

cd deploy/gitlab-runner

et ajoutons un nouveau composant 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,
      },
    ],
  },
]

Nous allons également décrire les nouveaux paramètres dans environments/base.libsonnet, qui ressemble maintenant à :

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

Veuillez noter $.components.rbac.name référence à nom pour le composant rbac

Vérifions ce qui a changé :

qbec diff default

et appliquons nos modifications dans Kubernetes :

qbec apply default

N'oublions pas de valider nos modifications dans 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. Notre premier pipeline et la construction d'images par étiquettes

À la racine du projet, nous allons créer .gitlab-ci.yml avec ce contenu :

.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

Veuillez noter que nous utilisons GIT_SUBMODULE_STRATEGY: normal pour les travaux où il est nécessaire d'initialiser explicitement les sous-modules avant l'exécution.

N’oublions pas de valider nos modifications :

git add .gitlab-ci.yml
git commit -m "Automatiser la construction de docker"

Je pense qu'on peut l'appeler version v0.0.1 et ajouter la balise :

git tag v0.0.1

Nous allons ajouter des balises chaque fois que nous devrons publier une nouvelle version. Les balises dans les images Docker seront liées aux balises Git. Chaque push avec une nouvelle balise lancera la construction d'images avec cette balise.

Nous exécuterons git push —tags, et regardons notre premier pipeline :

Capture d'écran du premier pipeline

Essai de nouveaux outils pour la construction et l'automatisation du déploiement dans Kubernetes.

Notez que la construction par balises convient pour la création d'images Docker, mais ne convient pas pour le déploiement d'applications dans Kubernetes. Comme de nouvelles balises peuvent être attribuées à d'anciens commits, l'initialisation du pipeline pour celles-ci entraînera le déploiement d'une ancienne version.

Pour résoudre ce problème, la construction des images Docker est généralement liée à des balises, tandis que le déploiement de l'application se fait avec une branche master, qui contient les versions codées en dur des images construites. Dans ce cas, vous pourrez initier un rollback simplement avec un revert master-de la branche.

10. Automatisation du déploiement

Pour que Gitlab-runner puisse déchiffrer nos secrets, nous devons exporter la clé du dépôt et l'ajouter aux variables d'environnement de notre CI :

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

nous enregistrerons la chaîne obtenue dans Gitlab, pour cela, allons dans les paramètres de notre projet :
Paramètres —> CI / CD —> Variables

Et créons une nouvelle variable :

Type
Clé
Valeur
Protégé
Masqué
Portée

Fichier
GITCRYPT_KEY
<your string>
true (pour la durée de l'apprentissage, vous pouvez aussi faux)
true
Tous les environnements

Capture d'écran de la variable ajoutée

Essai de nouveaux outils pour la construction et l'automatisation du déploiement dans Kubernetes.

Maintenant, mettons à jour notre .gitlab-ci.yml en ajoutant :

.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

Ici, nous avons utilisé plusieurs nouvelles options pour qbec :

  • —root some/app — permet de définir le répertoire de l'application spécifique
  • —force:k8s-context __incluster__ — c'est une variable magique qui indique que le déploiement se fera dans le même cluster que celui où est exécuté gitlab-runner. Il est nécessaire de le faire, car sinon qbec essaiera de trouver un serveur Kubernetes adapté dans votre kubeconfig
  • —wait — force qbec à attendre que les ressources qu'il crée passent à l'état Prêt avant de se terminer avec un code de sortie réussi.
  • —yes — désactive simplement le shell interactif Êtes-vous sûr ? lors du déploiement.

N’oublions pas de valider nos modifications :

git add .gitlab-ci.yml
git commit -m "Automatiser le déploiement"

Et après git push nous verrons comment nos applications ont été déployées :

Capture d'écran du deuxième pipeline

Essai de nouveaux outils pour la construction et l'automatisation du déploiement dans Kubernetes.

11. Artéfacts et construction lors du push dans master

Les étapes décrites ci-dessus sont généralement suffisantes pour construire et livrer presque n'importe quel microservice, mais nous ne voulons pas ajouter de tag chaque fois que nous avons besoin de mettre à jour le site. Par conséquent, nous allons prendre une approche plus dynamique et configurer le déploiement par digest dans la branche master.

L'idée est simple : désormais, l'image de notre site web sera reconstruite à chaque push dans master, puis déployée automatiquement dans Kubernetes.

Mettons à jour ces deux jobs dans notre .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"

Notez que nous avons ajouté la branche master sur refs pour le job build_website et nous utilisons désormais $CI_COMMIT_REF_NAME au lieu de $CI_COMMIT_TAG, c'est-à-dire que nous nous dégageons des tags dans Git et que nous allons maintenant pousser une image avec le nom de la branche du commit qui a initié le pipeline. Il convient de noter que cela fonctionnera également avec les tags, ce qui nous permettra de conserver des snapshots du site avec une version spécifique dans le docker-registry.

Lorsque le nom de la balise docker pour la nouvelle version du site peut rester inchangé, nous devons tout de même décrire les modifications pour Kubernetes, sinon il ne redéploiera simplement pas l'application à partir de la nouvelle image, car il ne remarquera aucun changement dans le manifeste de déploiement.

L'option —vm:ext-str digest="$DIGEST" pour qbec — cela permet de passer une variable externe dans jsonnet. Nous voulons que chaque version de notre application soit redéployée dans le cluster. Utiliser un nom de balise qui peut maintenant rester inchangé n'est plus possible ici, car nous devons nous lier à une version spécifique de l'image et déclencher le déploiement lors de son changement.

Ici, la possibilité de Kaniko de sauvegarder le digest de l'image dans un fichier nous aidera (option —digest-file)
Ensuite, nous transmettrons ce fichier et le lirons au moment du déploiement.

Mettons à jour les paramètres pour notre deploy/website/environments/base.libsonnet qui aura maintenant l'apparence suivante :

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

C'est fait, maintenant chaque commit dans master initialisera la construction de l'image docker pour site web, puis son déploiement dans Kubernetes.

N’oublions pas de valider nos modifications :

git add .
git commit -m "Configurer la construction dynamique"

Vérifions, après git push nous devrions voir quelque chose de similaire :

Capture d'écran du pipeline pour master

Essai de nouveaux outils pour la construction et l'automatisation du déploiement dans Kubernetes.

En principe, nous n'avons pas besoin de redéployer gitlab-runner à chaque push, sauf si, bien sûr, rien n'a changé dans sa configuration, corrigeons cela dans .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 permettra de suivre les modifications dans deploy/gitlab-runner/ et déclenchera notre tâche uniquement en cas d'éventuels changements.

N’oublions pas de valider nos modifications :

git add .gitlab-ci.yml
git commit -m "Réduire le déploiement de gitlab-runner"

git push, c'est mieux ainsi :

Capture d'écran du pipeline mis à jour

Essai de nouveaux outils pour la construction et l'automatisation du déploiement dans Kubernetes.

12. Environnements dynamiques

Il est temps de diversifier notre pipeline avec des environnements dynamiques.

Pour commencer, mettons à jour la tâche build_website dans notre .gitlab-ci.yml, en supprimant le bloc only, ce qui fera en sorte que Gitlab la déclenche à chaque commit dans n'importe quelle branche :

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/

Ensuite, mettons à jour la tâche deploy_website, ajoutons un bloc là-bas 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"

Cela permettra à Gitlab d'associer le job avec prod l'environnement et d'afficher le bon lien vers celui-ci.

Maintenant, ajoutons deux autres jobs :

deploy_website:
  extends: .deploy_qbec_app
  environment:
    name: prod
    url: https://docs.example.org
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST"

deploy_review:
  extends: .deploy_qbec_app
  environment:
    name: review/$CI_COMMIT_REF_NAME
    url: http://$CI_ENVIRONMENT_SLUG.docs.example.org
    on_stop: stop_review
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply review --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST" --vm:ext-str subdomain="$CI_ENVIRONMENT_SLUG" --app-tag "$CI_ENVIRONMENT_SLUG"
  only:
    refs:
    - branches
  except:
    refs:
      - master

stop_review:
  extends: .deploy_qbec_app
  environment:
    name: review/$CI_COMMIT_REF_NAME
    action: stop
  stage: deploy
  before_script:
    - git clone "$CI_REPOSITORY_URL" master
    - cd master
  script:
    - qbec delete review --root deploy/website --force:k8s-context __incluster__ --yes --vm:ext-str digest="$DIGEST" --vm:ext-str subdomain="$CI_ENVIRONMENT_SLUG" --app-tag "$CI_ENVIRONMENT_SLUG"
  variables:
    GIT_STRATEGY: none
  only:
    refs:
    - branches
  except:
    refs:
      - master
  when: manual

Ils seront déclenchés lors d'un push sur n'importe quelle branche sauf master et déploieront la version preview du site.

Nous voyons une nouvelle option pour qbec : —app-tag — elle permet de taguer les versions déployées de l'application et de ne travailler qu'à l'intérieur de ce tag, lors de la création et de la destruction des ressources dans Kubernetes qbec n'opérera qu'avec celles-ci.
Ainsi, nous n'avons pas besoin de créer un environnement séparé pour chaque review, mais simplement de réutiliser le même.

Ici, nous utilisons également qbec apply review, à la place de qbec apply default — c'est justement à ce moment-là que nous allons essayer de décrire les différences pour nos environnements (review et default) :

Ajoutons l'environnement dans deploy/website/qbec.yaml spec: environments: review: defaultNamespace: docs server: https://kubernetes.example.org:8443

Ensuite, déclarons-le dans

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:

Et nous écrirons des paramètres personnalisés pour celui-ci dans

deploy/website/environments/review.libsonnet Prenons également un moment pour examiner le job:

// 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 , il sera déclenché lors de la suppression d'une branche et pour que gitlab n'essaie pas d'effectuer un checkout dessus, nous utilisonsGIT_STRATEGY: none , plus tard nous clonons-la branche et supprimons la review à travers elle. master-branche et supprimons la critique par ce biais.
C'est un peu compliqué, mais je n'ai pas trouvé de méthode plus élégante pour l'instant.
Une alternative pourrait être de déployer chaque revue dans un espace de noms séparé, que l'on peut toujours supprimer entièrement.

N’oublions pas de valider nos modifications :

git add .
git commit -m "Activer la révision automatique"

git push, git checkout -b test, git push origin test, vérifions :

Capture d'écran des environnements créés dans Gitlab

Essai de nouveaux outils pour la construction et l'automatisation du déploiement dans Kubernetes.

Tout fonctionne ? — parfait, supprimons notre branche de test : git checkout master, git push origin :test, vérifions que les tâches de suppression de l'environnement se sont exécutées sans erreurs.

Il convient de préciser ici que n'importe quel développeur du projet peut créer des branches, il peut également modifier .gitlab-ci.yml le fichier et accéder aux variables secrètes.
C'est pourquoi il est fortement recommandé de n'autoriser leur utilisation que pour les branches protégées, par exemple dans master, ou de créer un ensemble de variables distinct pour chaque environnement.

13. Applications de révision

Applications de révision c'est une fonctionnalité de Gitlab qui permet d'ajouter un bouton pour chaque fichier dans le dépôt, permettant de le prévisualiser rapidement dans l'environnement déployé.

Pour que ces boutons apparaissent, il faut créer un fichier .gitlab/route-map.yml et décrire toutes les transformations de chemins, dans notre cas ce sera très simple :

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

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

N’oublions pas de valider nos modifications :

git add .gitlab/
git commit -m "Activer les applications de révision"

git push, et vérifions :

Capture d'écran du bouton Application de révision

Essai de nouveaux outils pour la construction et l'automatisation du déploiement dans Kubernetes.

Le travail est terminé !

Sources du projet :

Merci de votre attention, j'espère que cela vous a plu Essai de nouveaux outils pour la construction et l'automatisation du déploiement dans Kubernetes.

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster