logo
< Back Post-Image

Continuous delivery avec Flux CD et Concourse-CI

Nous en avons déjà parlé sur ce blog, DevOps ne désigne pas une série d’outils mais bien des pratiques de travail que certains outils, en effet, aident à mettre en place. Dans le même ordre d’idée, il n’y a pas d’outils GitOps mais bien une série de pratiques et de principes qui peuvent être mis en application par différents outils. Pour l’histoire, le terme GitOps sort tout droit de chez Weave, les créateurs du plugin CNI Weave ainsi que de Flagger, un opérateur Kubernetes de continuous delivery.

GitOps ?

Pour expliquer la problématique que GitOps tente de résoudre, revenons un peu en arrière et parlons d’Infrastructure as Code. Ce concept désigne le fait de décrire, de manière déclarative, dans des fichiers texte notre infrastructure. Les avantages sont nombreux mais on notera principalement :

  • lisibilité
  • versionnement dans Git

Grâce à Git, nous sommes capables de rapidement revenir à un état antérieur (git revert), d’effectuer un différentiel entre deux versions (git diff), d’agresser votre collègue ayant commis une erreur (git blame) !!

Git devient notre single source of truth et permet d’obtenir une reproductibilité quasiment parfaite. Bien entendu, votre infrastructure n’est pas la seule concernée, tout peut, et doit finir dans votre Git : configuration, dashboards, monitoring etc. Grâce à des outils comme les Pull Requests de GitHub, vous êtes en mesure de contrôler les modifications apportées à votre code.

Ce sont là trois des quatre principes de GitOps :

  • Votre système est déclarativement décrit
  • L’état désiré de votre système est stocké dans Git
  • Les changements sont contrôlés et appliqués dès leur validation

Mais qu’est-ce que GitOps apporte de plus ? Et bien des tas de choses. Les premiers bénéfices viennent évidemment de Git. Il devient le seul outil que vos développeurs ont besoin de connaître, il permet d’obtenir un historique clair des modifications effectuées et ses capacités de revert/rollback permettent de contrôler facilement, a posteriori, des modifications. GitOps s’applique aussi bien à vos applications qu’à votre infrastructure, vous n’avez donc plus qu’un seul workflow pour déployer en production. Finalement ce que vous developpez ce ne sont pas des conteneurs, c’est du code.

La quatrième principe est qu’une fois l’état désiré déployé, un système doit se charger de contrôler que cet état est maintenu et que le système déployé ne dérive pas de la source de vérité.

Et dans le détail alors ?

GitOps va donc se comporter comme une interface entre votre code et vos applications/infrastructures/conteneurs. Vous n’intéragissez qu’avec Git, vous utilisez un système de review (Gerrit, PR, MR, etc) pour soumettre des modifications sur votre infrastructure. Vous utilisez les fonctions natives de Git pour effectuer un rolling update ou un rollback. Tout est standard, vous n’avez pas à connaître les subtilités de chacune des CLI (kubectl, ctr, awscli, openstack-cli, etc) nécessaires à votre projet. Avec GitOps il y a une claire séparation entre la déclaration de votre système et la manière dont cet état est appliqué.

Weave Flux s’occupe des fonctions principales :

  • Synchronisation du contenu d’un dépôt Git avec votre cluster
  • Déploiement automatique de nouvelles images Docker

flux-workflow

Quant à Concourse-CI, nous en avons déjà parlé dans un précédent article. Dans cet article, nous avons mis une chaîne de CI/CD complète jusqu’au déploiement sur Kubernetes. Nous allons reproduire le même type de workflow mais en déléguant la partie “CD” à Flux. Concourse-CI se chargera donc uniquement de builder nos images à chaque nouveau commit.

Tout ça est en réalité très simple, mais terriblement puissant.

Mise en pratique

Use case

Nous allons utiliser une application toute simple, une page web avec un Hello World. Concourse-CI va se charger de builder notre application puis Flux se chargera continuellement de la déployer sur notre cluster Kubernetes. Nous changerons le code de cette application et observerons le résultat.

Préparation

Notre application

Je vais utiliser cette application : https://github.com/particuleio/demo-concourse-flux

L’application est composée d’un fichier index.php et d’une image PNG. Le Dockerfile servant à builder l’application se trouve à la racine et j’ai crée un dossier “deploy” dans lequel se trouvent les déclarations de ressources Kubernetes. Le cluster Kubernetes sur lequel sera déployée l’application est en 1.15.

Mise en place de Concourse

concourse-logo

Le guide d’installation est disponible ici. On ne rentre pas dans le détail ici, il n’y a rien de spécial à connaître ou à effectuer côté Concourse.

Mise en place de Flux

https://github.com/weaveworks/flux/blob/master/site/get-started.md

Tout se déploie naturellement directement sur Kubernetes :

git clone https://github.com/weaveworks/flux
cd flux/

Un seul fichier a besoin d’être édité : deploy/flux-deployment.yaml

- --git-url=ssh://git@github.com/particuleio/demo-concourse-flux
- --git-branch=master
- --git-path=deploy
  • --git-url spécifie le dépôt git qui sera surveillé par Flux. Pensez à récupérer la clé publique de votre Flux et à l’installer dans les Deploy Keys de votre projet sur Github. Oui, seul un dépôt peut être surveillé à la fois.
  • --git-branch, je pense que c’est assez clair.
  • --git-path précise les chemins dans votre dépôt qui seront effectivement surveillés. Dans notre cas, nous avons mis le fichier des ressources Kubernetes dans un dossier “deploy”.

Flux n’impose aucune hiérarchie, --git-path permet justement de vous donner la possibilité de choisir la votre si vous le souhaitez.

Une fois que c’est prêt : kubectl apply -f deploy/

Création du Pipeline

resources:
  - name: git
    type: git
    source:
      uri: https://github.com/particuleio/demo-concourse-flux
      branch: master
  - name: version
    type: semver
    source:
      driver: git
      uri: git@github.com:particuleio/demo-concourse-flux
      branch: version
      file: version
      private_key: {{rguichard_pkey}}
  - name: image-helloworld
    type: docker-image
    source:
      repository: particule/demo-concourse-flux
      username: rguichard
      password: {{dh-rguichard-passwd}}
jobs:
  - name: "Docker Build"
    public: false
    plan:
      - get: version
        params:
          bump: patch
      - get: git
        trigger: true
      - put: image-helloworld
        params:
          build: git
          tag_file: version/version
      - put: version
        params:
          file: version/version

Afin de suivre les versions de notre application, nous utilisons la ressource semver. Cette ressource a besoin d’un backend pour stocker ce numéro de version. Nous avons fait le choix simple de stocker cette version dans le même dépôt que l’application, mais dans une branche séparée. Nous aurions pu la stocker dans un bucket S3, ce qui parait plus propre. Notons que nous choisissons, question de simplicité, d’effectuer des patchs (le z de x.y.z de semver), à retenir pour la configuration de Flux ;)

Le reste du pipeline est plutôt simple, vous pourriez reproduire la même chose avec n’importe quelle CI assez facilement.

Configuration Flux

La majeure partie du travail a déjà été effectué en réalité. Il nous reste seulement à ajouter quelques annotations à notre deployment pour préciser à Flux le comportement que nous souhaitons obtenir.

flux.weave.works/automated: "true"
flux.weave.works/tag.helloworld: semver:~1.0
  • automated, active flux, logique.

La seconde ligne est la plus importante. Elle permet, pour le conteneur “helloworld” de suivre semver pour les versions de ses images. Nous précisons même que la version Semver doit matcher une 1.0. Seuls les patchs de la 1.0 seront donc pris en compte par Flux pour mettre à jour notre image.

On peut donc lancer tout ça !

Pour Concourse, cela se passe avec la CLI Fly, pour Flux normalement vous n’avez rien à faire puisqu’il a déjà commencé à surveiller votre dépot git

concourse

concourse

ts=2019-06-26T16:49:23.845635134Z caller=daemon.go:652 component=daemon event="**Automated release of particule/demo-concourse-flux:1.0.1**" logupstream=false

Une fois que tout s’est déroulé correctement on peut constater plusieurs choses :

  • Notre application répond bien sur la règle Ingress spécifiée
  • Un nouveau tag est apparu sur notre image Docker
  • Le contenu du fichier version de la branche version a changé (bump)
  • Flux a commité le changement de version de notre application (git log)

Et là c’est le drame.

Puisque Flux a commit dans notre répo, cela a déclenché la CI. Et on est reparti pour un tour. Fort heureusement, Flux a un moyen de prévenir cela. Il faut retourner dans la configuration et y ajouter --git-ci-skip. Cela pour effet d’ajouter [ci skip] au message commit et de faire comprendre à la quasi totalité des CI de ne pas lancer un nouveau build.

Et GitOps dans tout ça ?

Et bien on y est. Si vous avez des changements à faire, ceux ci ne passeront qu’au travers d’un commit ou d’une PR sur le dépot https://github.com/particuleio/demo-concourse-flux. Vous pouvez vous amusez à changer la couleur du texte et à voir Concourse builder votre nouvelle image puis Flux la déployer et synchroniser l’état de l’application sur le cluster Kubernetes avec la déclaration du deployment dans Git.

Et si vous souhaitez changer la définition de votre Ingress, vous pouvez commit ces changements et voir Flux (Concourse n’intervient pas ici) mettre à jour votre Ingress sur votre cluster sans que vous ayez à vous connecter à votre cluster et lancer un kubectl apply.

Romain Guichard - @herrguichard