PIPELINE CI/CD GITOPS AVEC KUBERNETES, FLUX, GITLAB CE, ET AWS

Contexte et objectifs

Présentation de la chaîne CI/CD

Avec la culture DevOps, qui consiste à automatiser au maximum la chaîne d’intégration et réduire l’intervention humaine, différentes structures se sont développées. Aujourd’hui, cette chaîne d’intégration, aussi appelée, pipeline CI/CD, est divisée en deux parties :

  • L’intégration Continue ou « Continuous Integration » (CI) permettant d’intégrer continuellement de nouvelles fonctionnalités grâce à l’exécution d’une suite d’actions, tout en supprimant ou limitant l’impact envers les utilisateurs ,
  • Le Déploiement Continu « Continuous Deployement » (CD) consistant à déployer les nouvelles fonctionnalités de façon totalement automatique. À la différence de la première partie de la chaîne d’intégration, il existe différents types de déploiements :
    • Le « Continuous Delivery » permettant de déployer automatiquement les fonctionnalités, avec néanmoins, l’intervention d’un leader technique/manager afin de valider le déploiement en production,
    • Le « Progressive Delivery », version plus moderne du « Continuous Delivery » consiste quant à lui, à déployer les nouvelles fonctionnalités progressivement pour les utilisateurs. Les plus connues sont « Canary Deployement », « A/B testing » ou encore « Observability ».

Kubernetes

Kubernetes est devenu LA solution par excellence pour l’orchestration des conteneurs. Dans Kubernetes tout est objet, il permet notamment, la gestion avancée du cycle de vie des conteneurs avec un mode déclaratif (à privilégier) assurant que l’état souhaité soit toujours atteint, la gestion de l’idempotence dans la déclaration des objets, la création d’une couche abstraite avec la notion de services et non plus d’adressage IP et encore énormément de choses… De plus, Kubernetes permet aisément en fonction de la demande, d’adapter les ressources manuellement et/ou automatiquement (scalabilité et élasticité) et de les rendre hautement disponibles (HA) sur différents types de providers (Cloud, On-premise, Machine Virtuelle, Bare Metal …).

GitOps

Définition

Le GitOps est attaché à la partie Delivery de chaîne d’Intégration Continue. Il repose sur l’outil de versioning Git, avec les plateformes telles que GitHub, GitLab ou CodeCommit(AWS)… Avec l’approche de type GitOps, le projet Git est considéré comme l’unique source de vérité pour la déclaration des infrastructures ou des applications. En effet, celles-ci sont déclarées dans des manifestes directement dans le projet Git. Lorsque des modifications sont apportées au repository Git, celles-ci sont automatiquement déployées dans le cluster. Ce redéploiement automatique est rendu possible grâce à un opérateur qui scrute en temps réel le projet Git. Cet opérateur, permet aussi de s’assurer que l’état effectif est conforme à l’état désiré au sein du cluster, on parle alors d’observabilité. Il existe deux approches quand on évoque le GitOps, la méthode classique de type « push » et la méthode de type « pull » davantage associée au GitOps.

Méthode Push Vs Pull

  • Méthode push : c’est la méthode classique pour réaliser un pipeline CI/CD. Après le commit, suite à la mise à jour du code et le push de cette nouvelle version sur le Source Code Management (SCM), une série de d’actions est exécutée (exécution de tests, building d’une nouvelle image, scan des failles de sécurités, redéploiement de la nouvelle image…) grâce à des outils de CI tels que GitLab CI, Jenkins, Travis CI… permettant d’ordonnancer les stages. Cette méthode peut aussi être associée à l’approche de type GitOps car l’application ou l’infrastructure est déclarée dans le projet Git considéré comme unique source de vérité.
Pipeline CI/CD classique (source: Weaveworks)
  • Méthode pull : C’est la méthode davantage associée au pipeline CI/CD de type GitOps. En effet, le déploiement d’une nouvelle image dans le cluster n’est pas effectué par l’outil de CI mais par un opérateur. Celui-ci correspond à un deamon constamment en exécution dans le cluster Kubernetes, qui va scruter en temps réel le repository où sont hébergées les images et automatiquement détecter lorsqu’une nouvelle image est buildée, grâce à la mise à jour du tag de celle-ci. La détection d’une nouvelle image permet à l’opérateur de redéployer automatiquement l’application dans sa nouvelle version. L’opérateur permet aussi d’intégrer la notion d’observabilité au sein du cluster Kubernetes en s’assurant que l’état effectif est conforme à l’état désiré. En cas de non conformité la possibilité de remonter des alertes à l’aide de différents outils.
Pipeline CI/CD avec approche GitOps (source: Weaveworks)

Worflow GitOps avec Flux CD

Composants GitOps dans un cluster Kubernetes (source: Weaveworks)

Le projet

Présentation du projet

Dans ce tutoriel, nous démontrerons comment mettre en place et gérer un projet Git via l’application GitLab hébergée sur un hôte privé. Nous activerons la registry Docker native à GitLab, puis nous verrons comment intégrer facilement un cluster Kubernetes directement depuis l’interface web.

Dans la seconde partie de ce tutoriel, nous réaliserons un pipeline CI/CD classique avec GitLab CI et Kubernetes. Enfin, nous déploierons l’opérateur Flux dans le cluster et nous modifierons le précédent pipeline afin d’attribuer la gestion de la partie CD à Flux et au GitOps.

Les deux pipelines seront basés sur le déploiement d’une simple application web développée en Node.js. Ici, la complexité de l’application n’est pas importante, l’essentiel est de comprendre comment construire, mettre en place les différentes briques et les ordonnancer.

Infrastructure Cible

Infrastructure finale cible

Configuration

1 – Mise en place de l’environnement

a – Information sur l’infrastructure

Notre infrastructure sera hébergée sur le provider cloud AWS. Nous disposerons de 4 serveurs de type instance EC2. L’ensemble de nos VMs utiliseront la distribution Linux Debian 9.

Un serveur EC2 hébergera l’application GitLab Community Edition déployée dans un conteneur Docker. Voici les caractéristiques :

Adresse IPv4 publique35.170.208.112
DNS IPv4 publique ec2-35-170-208-112.compute-1.amazonaws.com
Address IPv4 privée172.31.71.90
Hostnameip-172-31-71-90

Les 3 autres serveurs EC2 représenteront un cluster Kubernetes multi-noeuds avec un master et deux workers. Voici les caractéristiques :

Nom EC2Adresse IPv4 publiqueDNS IPv4 publiqueAddress IPv4 privéeHostname
KubernetesMaster3.208.239.132ec2-3-208-239-132.compute-1.amazonaws.com172.31.67.215master
KubernetesWorker1
35.173.110.79ec2-35-173-110-79.compute-1.amazonaws.com172.31.65.184worker1
KubernetesWorker254.209.65.74ec2-54-209-65-74.compute-1.amazonaws.com172.31.69.217worker2

b – Déploiement de l’infrastructure

Déploiement sur AWS

Afin de déployer entièrement une infrastructure fonctionnelle et prête à l’emploi, je vous fournis un tutoriel complet ainsi que la stack CloudFormation. Les ressources se trouvent sur mon repository GitHub à l’adresse suivante :

→ Repos GitHub : https://github.com/samiamoura/gitops-training/tree/master/aws-stack

Déploiement sur AWS Educate

Si vous suivez le cursus DevOps chez EazyTrainig et que vous avez accès à la plate-forme d’entraînement AWS Educate, je vous fournis également un tutoriel complet ainsi que la stack CloudFormation permettant de déployer la même infrastructure. Les ressources se trouvent sur mon repositroy GitHub à l’adresse suivante :

→ Repos GitHub : https://github.com/samiamoura/gitops-training/tree/master/aws-educate-stack

Quel que soit le mode de déploiement, l’infrastructure met environ 5 minutes pour être provisionnée et environ 5 minutes supplémentaires pour être fonctionnelle. Voici le schéma de l’infrastructure après le déploiement :

Infrastructure après les déploiement de la stack CloudFormation

2 – Récupération du code source

a – Récupération du code de l’application web

Tout au long de ce tutoriel, nous travaillerons avec une application web appelée  « shark-app ». Cette application, développée en Node.js, contient deux pages web qui donnent une brève présentation sur les requins. Le serveur de l’application écoute sur le port 8080.

Le code source de l’application se trouve sur mon repository GitHub à l’adresse suivante :

→ Repos GitHub : https://github.com/samiamoura/gitops-training

Depuis le serveur GitLab, récupérez le code source à l’aide de la commande suivante :

git clone https://github.com/samiamoura/gitops-training.git

b – Test de l’application

Nous allons tester que l’application est fonctionnelle.

Depuis le serveur GitLab, se rendre dans le dossier « gitops-training/shark-application » à l’aide de la commande suivante :

cd $HOME/gitops-training/shark-application

Buildez l’image à partir du Dockerfile présent dans le dossier à l’aide de la commande suivante :

docker build -t shark-app:1.0 .

Démarrez un conteneur « shark-app » à partir de cette image et exposez-le sur le port 8080 de l’hôte à l’aide de la commande suivante :

docker run -d --name shark-app -p 8080:8080 shark-app:1.0

Testez que le conteneur « shark-app » est Up à l’aide de la commande suivante :

docker container  ps | grep -i shark-app

Le résultat doit être similaire à :

CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                    NAMES
d04f583994e9   shark-app:1.0    "docker-entrypoint.s…"   3 minutes ago   Up 7 seconds   0.0.0.0:8080->8080/tcp   shark-app

Le conteneur « shark-app » est maintenant exposé sur le port 8080 du serveur GitLab. Testons que l’application est fonctionnelle. Entrez l’adresse IP publique de l’hôte sur le port 8080 dans un navigateur :

http://35.170.208.112:8080/

Le résultat doit être similaire à :

L’application est fonctionnelle.

Stoppez et supprimez le conteneur « shark-app » à l’aide des commandes suivantes :

docker container stop shark-app
docker container rm shark-app

3 – Gestion du projet GitLab

Pour rappel, nous avons déployé l’application GitLab dans un conteneur Docker. L’interface web de l’application est joignable en utilisant l’adresse IP publique du serveur GitLab. Entrez l’adresse IP publique du seveur GitLab dans un navigateur.

http://35.170.208.112/

Etant donné qu’il s’agit d’une instance fraichement déployée, vous arrivez sur une page web vous demandant de définir le mot de passe pour l’utilisateur « root ». Définissez un mot de passe assez robuste puis connectez-vous en tant que « root ».

a – Définition de l’utilisateur et création du projet

Pour des raisons de sécurité, il est recommandé d’éviter d’utiliser le compte « root » et de créer un utilisateur spécifique. Nous avons créé l’utilisateur  « samiamoura » en tant qu’administrateur. Après avoir créé un utilisateur et défini son mot de passe, connectez-vous avec celui-ci. Lors de la première connexion, il vous sera demandé de changer le mot de passe.

Depuis l’interface web de l’application GitLab, créez un projet avec les caractéristiques suivantes :

  • Project name : shark-application
  • Visibility Level : Public

Puis cliquez sur Create Project.

b – Activation de la Registry GitLab

GitLab possède nativement une registry Docker permettant d’héberger nos images Docker directement sur l’application. Cependant, il est nécessaire d’activer cette fonctionnalité pour chaque projet.

Pour activer la registry Docker de GitLab, je vous redirige vers un article que j’ai rédigé, traitant en détail ce sujet. Rendez-vous sur le paragraphe #6 de l’article à l’adresse suivante :

Article sur l’activation de la Registry GitLab

Après avoir activé la registry Docker pour le projet « shark-application », vous pouvez récupérer les caractéristiques de celle-ci en cliquant, dans le menu latéral à gauche, sur Registry :

Caractéristiques du container registry GitLab

L’adresse de la registry correspond à : ec2-35-170-208-112.compute-1.amazonaws.com/samiamoura/shark-application

Vous pouvez vous logguer à la registry à l’aide de la commande suivante :

docker login ec2-35-170-208-112.compute-1.amazonaws.com

Vous pouvez puller, tagguer et pousser des images sur la registry à l’aide des commandes suivantes :

docker pull nginx:latest
docker tag nginx:latest ec2-35-170-208-112.compute-1.amazonaws.com/samiamoura/shark-application/nginx:latest
docker push ec2-35-170-208-112.compute-1.amazonaws.com/samiamoura/shark-application/nginx:latest

c – Déploiement de la clé SSH

Pour interagir aisément et en toute sécurité avec notre projet hébergé sur GitLab, il est recommandé de générer une paire de clés privée/publique.

Depuis le serveur GitLab, générez une paire de clés privée/publique à l’aide de la commande suivante :

ssh-keygen -t rsa

Récupérez le contenu de la clé publique à l’aide la commande suivante :

cat $HOME/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO6qL3BTiBRMOq95HMHEuQfUpXPqRIX2va/JSiHkX1AuC56+nwxeM6itTgX2N11uzYS/SHTDMygZNMckPP6o/G8qM/EuwwFWL5xJRtBLS8L92RpR8YG5kHhwGSJkDCSj07ZYp4TW/fMbup9ZDGgkVUzbJC9Am42UvC3x/Nt9Mn8VG5syXcbU3f7mQZU+6FmOamexZhx5bOPOjKbXbYpB+5Bl1JLJYvWpf5F5ks2YFHsX7VeCgyEdbGUmTIzDgHCx8N/IL5LFPii4oln4e/ZenysUZ+fF8+JXE9BJ0wtYg6n1Mc8GNJfEIfytSyQZBBbTAnUc2ZNqlYjGnnK88DAsYb admin@ip-172-31-71-90

Depuis l’interface web GitLab, se rendre dans Settings , puis sur la page suivante, dans le menu latéral à gauche, se rendre dans SSH Key. Ajoutez la clé publique récupérée précédemment et cliquez sur Add key.

Déploiement de la clé publique

d – Initialisation du projet Git

Après avoir créé notre projet et déployé la clé publique sur l’application GitLab, nous allons à présent créer un dossier qui sera notre répertoire de travail durant ce tutoriel.

Depuis le serveur GitLab, créez un dossier « shark-application » et copiez le code source de l’application à l’intérieur, à l’aide des commandes suivantes :
Information : Le code source de l’application se trouve dans le dossier gitops-training cloné au début de cet article :

cd $HOME
mkdir $HOME/shark-application
cp $HOME/gitops-training/shark-application/* -r $HOME/shark-application/

Verifiez que le code source à bien été copié dans le répertoire à l’aide de la commande suivante :

ls -l $HOME/shark-application
total 12
drwxr-xr-x 4 admin admin 4096 Jul 20 22:14 app
-rw-r--r-- 1 admin admin  289 Jul 20 22:14 Dockerfile

À la racine du répertoire « shark-application », initialisez le projet Git, ajoutez le remote distant, ajoutez tous les fichiers, commitez les changements et poussez la mise à jour sur le projet GitLab :
Information : Editez la variable GIT_URL avec l’url de votre projet Git.

GIT_URL=ssh://git@ec2-35-170-208-112.compute-1.amazonaws.com:2222/samiamoura/shark-application.git
cd $HOME/shark-application
git init
git remote add origin $GIT_URL
git add .
git commit -m "Initial commit"
git push -u origin master

Le code source est désormais hébergé sur notre SCM GitLab :

4 – Intégration du cluster Kubernetes au projet GitLab

GitLab permet facilement d’intégrer un cluster Kubernetes à n’importe quel projet via son interface web. Pour ce faire, dans cette section, nous allons créer les ressources et récupérer les informations nécessaires.

Information : L’ensemble des commandes de cette section seront à effectuer, depuis le nœud master de notre serveur Kubernetes.

a – Autorisation des requêtes réseau sortantes

Pour pouvoir intégrer le cluster Kubernetes, il faut autoriser les requêtes réseau sortantes. Depuis l’interface web GitLab, se rendre dans Admin Area en cliquant sur l’icône , sur la page suivante dans le menu latéral à gauche, se rendre dans Settings > Network.

Sur la page de configuration Network, se rendre sur Outbound requests, cliquez sur Expand et cochez la case « Allow requests to the local network from hooks and services » puis sauvegarder les changements.

Autoriser les requêtes réseau sortantes

b – Création des ressources

Pour intégrer le cluster Kubernetes à GitLab, nous devons créer les autorisations nécessaires. Effectivement, GitLab a besoin d’administrer le cluster pour pouvoir effectuer les opérations nécessaires. Nous utiliserons le principe de RBAC de Kuberntes pour attribuer les permissions requises à l’application GitLab.

Logguez-vous en tant que super administrateur à l’aide de la commande suivante :

sudo su -
Création du ServiceAccount

Créez un ServiceAccount gitlab-admin pour l’instance GitLab à l’aide de la commande suivante :

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: gitlab-admin
  namespace: kube-system
EOF
serviceaccount/gitlab-admin created
Attribution des permissions

À présent, nous devons associer le ServiceAccount gitlab-admin précédemment créé, au ClusterRole cluster-admin déjà présent dans notre cluster à l’aide d’un ClusterRoleBinding. À noter que le ClusterRole cluster-admin, bénéficie de toutes les permissions sur l’ensemble des ressources du cluster Kubernetes.

Créez un ClusterRoleBinding gitlab-admin à l’aide de la commande suivante :

cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: gitlab-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: gitlab-admin
  namespace: kube-system
EOF
clusterrolebinding.rbac.authorization.k8s.io/gitlab-admin created

Les ressources et autorisations nécessaires à l’intégration du cluster Kubernetes sont maintenant créées.

c – Récupération des informations

À présent nous allons récupérer les informations permettant de configurer l’intégration du cluster à GitLab.

Récupérez l’URL de l’API Server à l’aide de la commande suivante :

APIURL=$(kubectl config view -o jsonpath='{.clusters[0].cluster.server}')
echo $APIURL

Le résultat doit être similaire à :

https://172.31.67.215:6443

Récupérez le root Certificate Authority (CA) à l’aide de la commande suivante :

kubectl config view --raw \
-o=jsonpath='{.clusters[0].cluster.certificate-authority-data}' \
| base64 --decode

Le résultat doit être similaire à :

-----BEGIN CERTIFICATE-----
MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
cm5ldGVzMB4XDTIwMDcyMDIwNTkzNFoXDTMwMDcxODIwNTkzNFowFTETMBEGA1UE
AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMWz
W0AZfbwOtWFtMz2x7mpkQ0h1uD4RLihEWk1E4MyNkWg/DghZ3KUFGTRWxjUXnqQp
1F2urfM34Hl/Q8LrH56KMSIBQvuUjo8SMs0XvBiOtD3IIcjcK9zc8Q75J0W5hbUh
S460lX0OxjVHDUJKC6SJyfsVeePm3ztipUDN7AVSp30yBP1qODMbKWplmYAWSy/I
1WZPrtn+gTptzSccmrK58u/ZHzDSQBQTgXyGPeeyDmmzdX1PSj+Y3te/1gWfpArF
MVvng1xA5GR+EkOSx9CL0Tr2Qmklp+LJrdLFf9BnjHD3sD5vMwJtD4MDY8ZGApiK
iDCGd5KqTZ1MdOsAm9UCAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABI6EytDPnInz4pAI1xNgWQJnLLY
uaSfpm/PdX/B/RyEfytYx7rVT7r/eRmxqUu60VmPMHSjrJPDH0CYjFsXofk4GpZs
rkKFqfBcEepZ/Z64r26LypCjEgHZOm1Hkxd6u3+AJxOTtZCQ5Isx7s+o6DvlovSE
wWpcaOidlqneuEJapf1P7FyFOtPNeCdnvBH3hVY/UBqvSl40EOIFZEkE8iAkLwMh
8aT5O5i7hdQjli/gmOgahzY4BOQLUS7q4t1QzbmzbY5VFAuJq0kLArGPltRcldJp
c4+RCyT39mSnCnY/0TCNQHRR2XOSyH+eunw0CigACz8aQOd24tB3C9e1v88=
-----END CERTIFICATE-----

Récupérez le Secret associé au ServiceAccount gitlab-admin puis récupérez le token associé à l’aide des commandes suivantes :

SECRET=$(kubectl -n kube-system get secret | grep gitlab-admin | awk '{print $1}')
TOKEN=$(kubectl -n kube-system get secret $SECRET -o jsonpath='{.data.token}' | base64 --decode)
echo $TOKEN

Le résultat doit être similaire à :

eyJhbGciOiJSUzI1NiIsImtpZCI6IkY2czdtNzZrTGp3UHFabXhEdllmYU11TmZ2d3cwNGpwc2p2R3Mwbks4NlEifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJnaXRsYWItYWRtaW4tdG9rZW4tazVqOXAiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZ2l0bGFiLWFkbWluIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiMWI1YTFmMTAtOTMyZC00ZDI4LWIxZDYtZjIyNjYyM2QzYjIzIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmUtc3lzdGVtOmdpdGxhYi1hZG1pbiJ9.kudhWIYapdYl8fvY8lxWB_FJXhxd6zc-AtlYMvPFJhoxPh2giau_DYQsLwwddoTsaDj5hPCyYVURh7mChEhO7d1tPK8G6OZnsiIOaAkuiYp74eGaH3tRywQBJb7mqAtkZE0dEB29XRILzPOlC8nXXDssmhm9Mq_NTEM4pSQDt0jQGTrfOTN0ym80xjhcGhfQm-chgCLLuSqAnRphTqw--zx_K9OTqhnRPJhcgmojmJxwYmrAZCtrHd5-P6KVwYyn0qrvKvnLlSr-rumGIdZwmSo_00HluxwuV29JbHMPOrAdM89L8CYui7hkMJApAXSM87g3ROTyGtYGj34nxpb8wg

Nous disposons à présent des informations nécessaires pour configurer l’intégration du cluster Kubernetes à GitLab.

d – Configuration et intégration du cluster Kubernetes

Depuis la page principale du projet, dans le menu latéral à gauche, se rendre dans Operations > Kubernetes :

Sur la page suivante, cliquez sur Add Kubernetes cluster :

Sur la nouvelle page, cliquez sur Add existing cluster, et configurez l’intégration du cluster avec les informations récupérées lors de la section précédente :

  • Kubernetes cluster name : k8s cluster
  • API URL : https://172.31.67.215:6443
  • CA Certificate :
-----BEGIN CERTIFICATE-----
MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
cm5ldGVzMB4XDTIwMDcyMDIwNTkzNFoXDTMwMDcxODIwNTkzNFowFTETMBEGA1UE
AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMWz
W0AZfbwOtWFtMz2x7mpkQ0h1uD4RLihEWk1E4MyNkWg/DghZ3KUFGTRWxjUXnqQp
1F2urfM34Hl/Q8LrH56KMSIBQvuUjo8SMs0XvBiOtD3IIcjcK9zc8Q75J0W5hbUh
S460lX0OxjVHDUJKC6SJyfsVeePm3ztipUDN7AVSp30yBP1qODMbKWplmYAWSy/I
1WZPrtn+gTptzSccmrK58u/ZHzDSQBQTgXyGPeeyDmmzdX1PSj+Y3te/1gWfpArF
MVvng1xA5GR+EkOSx9CL0Tr2Qmklp+LJrdLFf9BnjHD3sD5vMwJtD4MDY8ZGApiK
iDCGd5KqTZ1MdOsAm9UCAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABI6EytDPnInz4pAI1xNgWQJnLLY
uaSfpm/PdX/B/RyEfytYx7rVT7r/eRmxqUu60VmPMHSjrJPDH0CYjFsXofk4GpZs
rkKFqfBcEepZ/Z64r26LypCjEgHZOm1Hkxd6u3+AJxOTtZCQ5Isx7s+o6DvlovSE
wWpcaOidlqneuEJapf1P7FyFOtPNeCdnvBH3hVY/UBqvSl40EOIFZEkE8iAkLwMh
8aT5O5i7hdQjli/gmOgahzY4BOQLUS7q4t1QzbmzbY5VFAuJq0kLArGPltRcldJp
c4+RCyT39mSnCnY/0TCNQHRR2XOSyH+eunw0CigACz8aQOd24tB3C9e1v88=
-----END CERTIFICATE-----
  • Service Token :
eyJhbGciOiJSUzI1NiIsImtpZCI6IkY2czdtNzZrTGp3UHFabXhEdllmYU11TmZ2d3cwNGpwc2p2R3Mwbks4NlEifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJnaXRsYWItYWRtaW4tdG9rZW4tazVqOXAiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZ2l0bGFiLWFkbWluIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiMWI1YTFmMTAtOTMyZC00ZDI4LWIxZDYtZjIyNjYyM2QzYjIzIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmUtc3lzdGVtOmdpdGxhYi1hZG1pbiJ9.kudhWIYapdYl8fvY8lxWB_FJXhxd6zc-AtlYMvPFJhoxPh2giau_DYQsLwwddoTsaDj5hPCyYVURh7mChEhO7d1tPK8G6OZnsiIOaAkuiYp74eGaH3tRywQBJb7mqAtkZE0dEB29XRILzPOlC8nXXDssmhm9Mq_NTEM4pSQDt0jQGTrfOTN0ym80xjhcGhfQm-chgCLLuSqAnRphTqw--zx_K9OTqhnRPJhcgmojmJxwYmrAZCtrHd5-P6KVwYyn0qrvKvnLlSr-rumGIdZwmSo_00HluxwuV29JbHMPOrAdM89L8CYui7hkMJApAXSM87g3ROTyGtYGj34nxpb8wg
  • RBAC-enabled cluster : coché
  • GitLab-managed cluster : décoché

Puis cliquez sur Add Kubernetes cluster. Le cluster Kuberntes est à présent intégré à notre projet GitLab.

Information : Une fois le cluster Kubernetes intégré, il est possible d’installer des applications tierces telles que Helm directement sur le cluster.

Application à installer sur le cluster Kubernetes

5 – Mise en place d’une chaîne CI/CD simple

Dans ce chapitre, nous allons mettre en place un pipeline CI/CD simple avec GitLab CI qui sera déclenché à chaque fois qu’une mise à jour du projet Git aura été commitée puis poussée sur le SCM GitLab. Cette approche correspond à la méthode « push » définie au début de cet article.

Pour réaliser et exécuter des pipelines, GitLab CI prend en compte les instructions déclarées dans un fichier au format YAML. Ce fichier qui s’appelle .gitlab-ci.yml, doit être placé à la racine du projet Git. GitLab détecte automatiquement la présence de ce fichier et à chaque mise à jour du projet, les instructions sont exécutées.

Information : L’ensemble des commandes de cette section seront à effectuer depuis le serveur GitLab.

Créez le manifeste .gitlab-ci.yml à la racine du projet « shark-application » à l’aide de la commande suivante :

touch $HOME/shark-application/.gitlab-ci.yml

a – Configuration du Runner GitLab

Pour pouvoir réaliser des pipelines et exécuter les instructions définies dans le fichier .gitlab-ci.yml, GitLab utilise le principe de Runners. Les Runner sont des agents légers et scalables qui récupèrent les instructions des jobs via l’API de coordination et renvoient les résultats à l’instance GitLab.

Avant de mettre en place nos pipelines et de pouvoir les exécuter, il est nécessaire de configurer le Runner GitLab. Dans un premier temps, nous allons configurer le Runner pour que celui-ci puisse accepter d’exécuter des jobs qui ne sont pas taggués. Effectivement, pour faire simple et ne pas trop compliquer la compréhension de cet article, nous travaillerons uniquement avec des jobs non taggués.

Depuis l’interface web GitLab, se rendre dans Admin Area en cliquant sur l’icône  , sur la page suivante, dans le menu latéral à gauche, se rendre dans Overview > Runners. Puis sur la page qui apparaît, cliquez sur le crayon pour éditer les caractéristiques du Runner :

Sur la page suivante, remplir le formulaire avec les caractéristiques suivantes :

  • Run untagged jobs : Indicates whether this runner can pick jobs without tags coché
  • Les autres attributs peuvent être laissés avec les valeurs par défaut.

Puis cliquez sur Save changes.

Sur la même page se rendre dans la section Restrict projects for this Runner :

Cliquez sur Enable en face du projet sami/shark-application afin d’activer le Runner pour notre projet. Le résultat doit être similaire à :

À présent, nous pouvons vérifier que le Runner est bien activé pour notre projet. Depuis l’interface GitLab, sur la page principale du projet « shark-application », se rendre dans Settings > CI/CD. Sur la page suivante, atteindre Runners puis cliquez sur Expand afin d’afficher les informations liées au Runner. Puis en bas de de la configuration du Runner, vérifiez que la pastille du Runner est verte. Cela signifie que le Runner est fonctionnel. Si celle-ci est rouge, il vous faudra juste cliquer sur le bouton Resume.

Le résultat final doit etre similaire à :

Le Runner est maintenant activé pour notre projet sami/shark-application.

b – Définition des stages du pipeline

Le manifeste .gitlab-ci.yml doit commencer par définir les différents stages qui seront réalisés dans notre pipeline. Nous disposerons de 4 stages :

  • Le stage « build » qui permettra de builder l’image à partir du Dockerfile présent dans le répertoire,
  • Le stage « test » permettant de réaliser des tests simples sur l’image Docker qui aura été buildée précédemment,
  • Le stage « push » nous permettra de tagguer l’image Docker et de la pousser sur la Registry GitLab,
  • Le stage « deployment » qui déploiera l’application dans le cluster Kubernetes.

Nous reviendrons en détails sur chaque stage tout au long de ce chapitre.

Ajoutez les différentes stages au fichier .gitlab-ci.yml à l’aide de la commande suivante :

cat <<< 'stages:
  - build
  - test
  - push
  - deployment' >> $HOME/shark-application/.gitlab-ci.yml

c – Définition du stage « build »

Dans le stage « build », nous buildons une image Docker avec le tag tmp, nous nous connectons à la Registry GitLab et nous poussons l’artefact sur la Registry.

Ajoutez le stage « build » au fichier .gitlab-ci.yml à l’aide de la commande suivante :

cat <<< '
build:
  image: docker:stable
  stage: build
  services:
    - docker:dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:tmp .
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
    - docker push $CI_REGISTRY_IMAGE:tmp
  only:
  - master' >> $HOME/shark-application/.gitlab-ci.yml

Information : Les variables d’environnement CI_BUILD_TOKEN CI_REGISTRY seront automatiquement affectées par GitLab.

d – Définition du stage « test »

Dans le stage « test », nous exécutons un conteneur à partir de l’artefact qui a été produit précédemment, nous réalisons des tests simples sur l’application puis nous nettoyons l’environnement . Les tests sur l’application permettent d’effectuer :

  • Un test de disponibilité en s’assurant que l’application est joignable,
  • Un test de fonctionnalité en s’assurant que la page web principale de l’application nous retourne le bon message.

Ajoutez le stage « test » au fichier .gitlab-ci.yml à l’aide de la commande suivante :

cat <<< '
test:
  image: docker:stable
  stage: test
  services:
    - docker:dind
  script:
    - docker run -d --name shark-app -p 8080:8080 $CI_REGISTRY_IMAGE:tmp
    - sleep 10s
    - TEST=$(docker run --link shark-app samiamoura/curl -s http://shark-app:8080 | grep -i "Are you ready to learn about sharks?")
    - if [ $? -eq 0 ]; then  echo \"TEST OK\"; docker rm -f shark-app ; exit 0; else echo \"TEST KO\"; docker rm -f shark-app ; exit 1; fi;
  only:
  - master' >> $HOME/shark-application/.gitlab-ci.yml

e – Définition du stage « push »

Dans le stage « push », nous récupérons l’image buildée lors du stage « build » puis nous tagguons cette image avec deux tags différents :

  • Le premier tag correspond à la branche sur laquelle nous travaillons, en l’occurence la branche « master »,
  • Le second tag correspond quant à lui, au hash du commit que nous venons d’exécuter.

Information : Dans la section suivante, nous expliquerons, l’intérêt de tagguer l’image avec deux tags différents.

Ajoutez le stage « push » au fichier .gitlab-ci.yml à l’aide de la commande suivante :

cat <<< '
push:
  image: docker:stable
  stage: push
  services:
    - docker:dind
  script:
    - docker image pull $CI_REGISTRY_IMAGE:tmp
    - docker image tag $CI_REGISTRY_IMAGE:tmp $CI_REGISTRY_IMAGE:$CI_BUILD_REF
    - docker image tag $CI_REGISTRY_IMAGE:tmp $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
    - docker push $CI_REGISTRY_IMAGE:$CI_BUILD_REF
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
  only:
  - master' >> $HOME/shark-application/.gitlab-ci.yml

Information : Dans les versions plus récentes de l’application GitLab, la variable d’environnement CI_BUILD_REF n’existe plus. Celle-ci a été remplacée par CI_COMMIT_SHA.

f – Définition du stage « deployment »

Avant de définir le stage « deployment », nous devons dans un premier temps, définir dans des manifestes Kubernetes, les ressources qui seront déployées dans le cluster. Ces fichiers déclaratifs seront situés dans le répertoire shark-application/kubernetes.

Créez le répertoire shark-application/kubernetes à l’aide de la commande suivante :

mkdir $HOME/shark-application/kubernetes
Création des manifestes Kubernetes

Pour déployer l’application « shark-app » dans le cluster Kubernetes, nous allons devoir créer plusieurs fichiers dans le répertoire shark-application/kubernetes .

Un premier fichier shark-application/kubernetes/deployment.tpl template du manifeste deployment.yml. Ce manifeste, permettant de déployer notre application, spécifie une ressource Kubernetes de type « Deployment », avec les caractéristiques suivantes :

  • Un seul replicas de pod,
  • L’image utilisée pour déployer le pod sera de type « ec2-35-170-208-112.compute-1.amazonaws.com/samiamoura/shark-application:GIT_COMMIT ». Le tag GIT_COMMIT de l’image, sera remplacé par le hash du commit actuel. Cette astuce permettra de créer ou de mettre à jour l’application déjà déployée, à chaque fois que le stage sera exécuté. Voilà la raison pour laquelle nous produisons deux tags dans le stage « push ».

Créez le fichier shark-application/kubernetes/deployment.tpl à l’aide de la commande suivante :
Information : Editez la variable REGISTRY_URL avec l’adresse de votre Registry.

REGISTRY_URL=ec2-35-170-208-112.compute-1.amazonaws.com/samiamoura/shark-application

cat <<< 'apiVersion: apps/v1
kind: Deployment
metadata:
  name: shark-app
  labels:
    app: shark-app
spec:
  selector:
    matchLabels:
      app: shark-app
  template:
    metadata:
      labels:
        app: shark-app
    spec:
      containers:
      - name: shark-app
        image: '$REGISTRY_URL':GIT_COMMIT' > $HOME/shark-application/kubernetes/deployment.tpl 

Un second fichier shark-application/kubernetes/service.yml. Ce manifeste, permettant d’exposer notre application, spécifie une ressource Kubernetes de type « Service », avec les caractéristiques suivantes :

  • Un service de type NodePort permettant d’exposer notre application sur le port 30000 de tous les hôtes du cluster grâce à la fonction routing mesh de Kubernetes.

Créez le fichier shark-application/kubernetes/service.yml à l’aide de la commande suivante :

cat <<< 'apiVersion: v1
kind: Service
metadata:
  name: shark-app
spec:
  type: NodePort
  ports:
    - name: shark-app
      nodePort: 30000
      port: 80
      targetPort: 8080
      protocol: TCP
  selector:
    app: shark-app' > $HOME/shark-application/kubernetes/service.yml 
Ajout du stage « deployment »

Le stage « deployment » permet de déployer l’application « shark-app » dans le cluster Kubernetes. Dans ce stage nous pouvons noter plusieurs choses :

  • Nous utilisons l’image samiamoura/kubectl:1.18.0 pour exécuter les commandes kubectl,
  • Les credentials pour pouvoir interagir avec l’API Serveur sont renseignés grâce aux variables d’environnement KUBE_URL, KUBE_CA_PEM_FILE, KUBE_TOKEN. Celles-ci, sont automatiquement affectées par GitLab grâce à l’intégration de Kubernetes à GitLab effectuée un peu plus tôt dans le tutoriel,
  • Un objet de type « Deployment » est créé grâce au manifeste deployment.yml généré par le template deployment.tpl. Le tag GIT_COMMIT est remplacé par le hash du commit actuel grâce à la variable d’environnement CI_BUILD_REF,
  • Les ressources « Deployment » et « Service » sont créées ou mises à jour grâce à la commande « kubectl apply »
  • Ajoutez le stage « deployment » au fichier .gitlab-ci.yml à l’aide de la commande suivante :
cat <<< '
deployment:
  stage: deployment
  image: samiamoura/kubectl:1.18.0
  environment: test
  script:
    - kubectl config set-cluster my-cluster --server=${KUBE_URL} --certificate-authority="${KUBE_CA_PEM_FILE}"
    - kubectl config set-credentials admin --token=${KUBE_TOKEN}
    - kubectl config set-context my-context --cluster=my-cluster --user=admin --namespace default
    - kubectl config use-context my-context
    - cat kubernetes/deployment.tpl | sed 's/GIT_COMMIT/'"$CI_BUILD_REF/" > kubernetes/deployment.yml
    - kubectl apply -f kubernetes
  only:
  - master' >> $HOME/shark-application/.gitlab-ci.yml

Voici le fichier .gitlab-ci.yml complet :

stages:
  - build
  - test
  - push
  - deployment

build:
  image: docker:stable
  stage: build
  services:
    - docker:dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:tmp .
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
    - docker push $CI_REGISTRY_IMAGE:tmp
  only:
  - master

test:
  image: docker:stable
  stage: test
  services:
    - docker:dind
  script:
    - docker run -d --name shark-app -p 8080:8080 $CI_REGISTRY_IMAGE:tmp
    - sleep 10s
    - TEST=$(docker run --link shark-app samiamoura/curl -s http://shark-app:8080 | grep -i "Are you ready to learn about sharks?")
    - if [ $? -eq 0 ]; then  echo \"TEST OK\"; docker rm -f shark-app ; exit 0; else echo \"TEST KO\"; docker rm -f shark-app ; exit 1; fi;
  only:
  - master

push:
  image: docker:stable
  stage: push
  services:
    - docker:dind
  script:
    - docker image pull $CI_REGISTRY_IMAGE:tmp
    - docker image tag $CI_REGISTRY_IMAGE:tmp $CI_REGISTRY_IMAGE:$CI_BUILD_REF
    - docker image tag $CI_REGISTRY_IMAGE:tmp $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
    - docker push $CI_REGISTRY_IMAGE:$CI_BUILD_REF
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
  only:
  - master

deployment:
  stage: deployment
  image: samiamoura/kubectl:1.18.0
  environment: test
  script:
    - kubectl config set-cluster my-cluster --server=${KUBE_URL} --certificate-authority="${KUBE_CA_PEM_FILE}"
    - kubectl config set-credentials admin --token=${KUBE_TOKEN}
    - kubectl config set-context my-context --cluster=my-cluster --user=admin --namespace default
    - kubectl config use-context my-context
    - cat kubernetes/deployment.tpl | sed s/GIT_COMMIT/"$CI_BUILD_REF/" > kubernetes/deployment.yml
    - kubectl apply -f kubernetes
  only:
  - master

Le ressources pour exécuter notre pipeline CI/CD sont désormais créées.

6 – Exécution du pipeline CI/CD simple

a – Déploiement de l’application

Après avoir mis à jour notre projet « shark-application » avec les manifestes Kubernetes et le fichier .gitlab-ci.yml, nous devons à présent pousser ces modifications sur le SCM GitLab. Cette action déclenchera l’exécution du pipeline CI/CD.

Information : L’ensemble des commandes de cette section seront à effectuer depuis le serveur GitLab.

À la racine du répertoire « shark-application » ajoutez tous les fichiers, commitez les changement et poussez la mise à jour sur le projet GitLab :

cd $HOME/shark-application
git add .
git commit -m "Add pipeline CI/CD with GitLab and Kubernetes"
git push -u origin master

Depuis l’interface GitLab, dans le menu latéral à gauche du projet, se rendre dans CI/CD > Pipelines , nous pouvons constater qu’un pipeline a été déclenché suite au push :

Pipeline exécuté

Pour avoir une vue détaillée des 4 stages du pipeline, cliquez sur le l’état du pipeline correspondant :

Les stages du pipeline

Enfin pour avoir le détail des instructions exécutées lors de chaque stage du pipeline, cliquez sur le stage correspondant :

Instructions exécutées pendant le stage « build »

b – Test de l’application

Le pipeline s’étant exécuté avec succès, nous pouvons tester que l’application est deployée dans le cluster Kubernetes. Pour rappel, l’application est exposée sur le port 30000 des workers Kubernetes. Pour tester l’application, entrez dans un navigateur web l’adresse IP publique de chaque worker Kubernetes ainsi que le port 30000.

http://Adresse_IP_Publique_Worker_Node:30000
  • Test sur le kubernetes worker 1

L’adresse IP Publique du worker 1 est : 35.173.110.79. Entrez l’adresse http://35.173.110.79:30000 dans un navigateur.

Le résultat doit être similaire à :

  • Test sur le kubernetes worker 2

L’adresse IP Publique du worker 1 est : 54.209.65.74. Entrez l’adresse http://54.209.65.74:30000 dans un navigateur.

Le résultat doit être similaire à :

L’application a été déployée dans le cluster Kubernetes avec succès.

c – Mise à jour de l’application

Pour démontrer que notre pipeline CI/CD est fonctionnel, nous allons modifier légèrement le code source de l’application. Nous remplacerons le message « This app is working » par « This app is powered by GitLab and Kubernetes » sur la page principale de l’application.

Modifiez le message sur la page principale de l’application à l’aide de la commande suivante :

sed -i "s|<p>This app is working</p>|<p>This app is powered by GitLab and Kubernetes</p>|g" \
  $HOME/shark-application/app/views/index.html

À la racine du répertoire « shark-application » ajoutez tous les fichiers, commitez les changement et poussez la mise à jour sur le projet GitLab :

cd $HOME/shark-application
git add .
git commit -m "update main page with a new message"
git push -u origin master

Depuis l’interface GitLab, nous pouvons constatez qu’un nouveau pipeline a été déclenché :

Nouveau pipeline exécuté

Le pipeline s’étant exécuté une nouvelle fois avec succès, testons que l’application a été mise à jour.

  • Test sur le kubernetes worker 1

L’adresse IP Publique du worker 1 est : 35.173.110.79. Entrez l’adresse http://35.173.110.79:30000 dans un navigateur.

Le résultat doit être similaire à :

Application fonctionnelle worker 1
  • Test sur le kubernetes worker 2

L’adresse IP Publique du worker 1 est : 54.209.65.74. Entrez l’adresse http://54.209.65.74:30000 dans un navigateur.

Le résultat doit être similaire à :

Application fonctionnelle worker 2

L’application a été mise a jour avec succès.

Le pipeline CI/CD avec l’approche de type « push » est fonctionnel. Bien entendu, il pourrait beaucoup être amélioré avec notamment la mise en place d’un gitflow multi-branche, l’intégration de tests supplémentaires, une partie sécurité avec le scan des images Docker… et encore beaucoup d’autres choses.

La méthode « push » peut aussi être considérée comme une approche de type GitOps sans l’opérateur.

7 – Intégration du GitOps avec l’approche « pull »

Pour rappel, le GitOps repose sur 4 principes :

  • L’approche déclarative des applications et/ou des infrastructures dans le repository Git,
  • L’unique source de vérité (single source of truth) représentée par le projet Git,
  • L’opérateur en charge de scruter les modifications apportées au projet (repository d’images et repository code) et de redéployer automatiquement ces mises à jour dans le cluster,
  • L’observabilité permettant de s’assurer que l’état effectif du cluster est conforme avec l’état désiré.

Dans ce chapitre, nous allons légèrement modifier le pipeline CI/CD mis en place précédemment afin d’attribuer la partie CD de la chaîne d’intégration, à une approche GitOps de type « pull ». Effectivement, nous allons supprimer le stage « deployment » du fichier de pipeline. La partie déploiement ne sera plus gérée par GitLab CI mais par un opérateur. Dans ce tutoriel, nous avons choisi de travailler avec l’opérateur Flux appartenant au projet FluxCD.

a – Installation du binaire fluxctl

Le binaire fluxctl permet de dialoguer avec Flux, de manager les workloads ainsi qu’automatiser et faire machine arrière sur les déploiements. Vous trouverez davantage d’informations sur le site officiel officiel à l’adresse suivante : https://docs.fluxcd.io/en/1.20.0/references/fluxctl.html

Dans la stack CloudFormation que je vous fournis pour déployer l’infrastructure, fluxctl est déjà installé. Mais pour informations, voici les commandes pour installer l’utilitaire sur une distribution Linux Debian 9 :

sudo apt-get update
sudo apt-get -y install snapd
sudo snap install core
sudo snap install fluxctl --classic
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin

b – Configuration des ressources pour l’opérateur Flux

Un opérateur est un deamon (sous forme de pod) dans le cluster Kubernetes, qui scrute en temps réel les modifications apportées à la registry et au projet GitLab. Lorsque des modifications sont détectées, il est en charge de redéployer automatiquement les ressources mises à jour dans le cluster Kubernetes.

Il est possible de déployer l’opérateur Flux manuellement à l’aide de manifestes Kubernetes directement dans le cluster ou d’utiliser les Charts Helm. Dans ce tutoriel, nous utiliserons la méthode manuelle. Pour déployer Flux il existe un projet officiel sur GitHub à l’adresse suivante : https://github.com/fluxcd/flux

Cependant, nous utiliserons une configuration assez spécifique. En effet, nous disposons d’un hôte privé pour héberger notre application GitLab (nous n’utilisons pas GitLab.com). Ainsi, afin de faciliter la compréhension, j’ai décidé de forker le projet et de l’épurer pour vous fournir uniquement les manifestes dont nous aurons besoins. Danse ce chapitre, nous reviendrons en détail sur chaque composant.

Un certain nombre de ressources Kubernetes doivent être créées avant de déployer l’opérateur Flux. Toutes les ressources seront déployées dans le namespace « flux ».

Information L’ensemble des commandes de cette section seront à effectuer depuis le nœud master de notre cluster Kubernetes .

Récupération du projet

Le projet customisé se trouve sur mon GitHub à l’adresse suivante : https://github.com/samiamoura/gitops-training

Logguez-vous en tant que super administrateur à l’aide de la commande suivante :

sudo su -

Récupérez le projet à l’aide de la commande suivante :

git clone https://github.com/samiamoura/gitops-training.git
Création du namespace « flux »

Créez le namespace « flux » à l’aide de la commande suivante :

kubectl create namespace flux
namespace/flux cretated 
Génération des clés et création du Secret « flux-git-deploy »

Le pod flux aura besoin d’avoir accès au projet GitLab afin de pouvoir interagir avec celui-ci. La méthode privilégiée est la connexion de type SSH. Il est donc essentiel qu’il puisse avoir les crendentials nécessaires. Pour ce faire, nous allons générer une paire de clés privée/publique sur le serveur kubernetes master et partager la clé privée avec le pod flux. Comme il s’agit d’une donnée sensible nous utiliserons un objet Kubernetes de type Secret .

Génération d’une paire de clés privée/publique

Générez une paire de clés privée/publique à l’aide de la commande suivante :

ssh-keygen -t rsa 

Récupérez la clé publique à l’aide de la commande suivante :

cat $HOME/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkhB7EC7gVZfm1bO4SLCU7F20tQSRnU2TWsss6uqzMCjdCEt55UKn1hNgmvn/qd/JL8StJrb8oEFMI+8M2J165Y1XsHJyOcQJoD/SM6z0perdQqN8wLcDMpDuOjEG4nNoiaUpvFxOcUEMc4fIJZkTSxjPBLIb2UYrogrHubDpHtvdU0iNAHJOZvjmXU0D/IkpRNxpQSheoj0+GTrBOrwYH/2AVsejHmY756VCIl3CsDSYtdnLCCl7Y+Y2obzj8at9lWx0Dg4H/sMiF9GUQdO/0dpqe1DXSYb+y0gujdeM39i1vVbdI4kLh5afZBt02AO/wL8CPqcp0Y7EEa1Kb9nq/ root@master

Déployez la clé publique récupérée précédemment, sur l’application GitLab via l’interface web de la même façon que nous l’avons fait dans la partie #3-c de cet article.

Création du Secret « flux-git-deploy »

Créez le secret « flux-git-deploy » à partir de la clé privée « $HOME/.ssh/id_rsa » dans le namespace « flux » à l’aide de la commande suivante :

kubectl create secret generic flux-git-deploy --from-file=identity=$HOME/.ssh/id_rsa -n flux
secret/flux-git-deploy created
Ajout à la liste des hôtes connus et création du ConfigMap « flux-ssh-config »

Le pod flux aura besoin d’ajouter l’application GitLab à la liste des hôtes connus afin de pouvoir interagir avec notre projet GitLab. Pour ce faire, nous allons ajouter l’application GitLab à la liste des hôtes connus du serveur kubernetes master et partager ce fichier de configuration avec le pod à l’aide d’un objet Kubernetes de type « ConfigMap ».

Ajout à la liste des hôtes connus

Ajoutez l’application GitLab à la liste des hôtes connus du serveur kubernetes master à l’aide des commandes suivantes :
Information : Editez la variable GITLAB_PUBLICDNS avec le nom du DNS publique du serveur GitLab.

GITLAB_PUBLICDNS=ec2-35-170-208-112.compute-1.amazonaws.com

ssh-keyscan -H -p 2222 $GITLAB_PUBLICDNS >> $HOME/.ssh/known_hosts

Attention : Parfois cette méthode n’est pas suffisante, il est alors nécessaire de cloner le projet « shark-application » directement depuis le serveur kubernetes master afin de forcer l’ajout de l’application GitLab à la liste des hôtes connus.

Clonez le projet « shark-application » à l’aide des commandes suivantes :
Information : Editez la variable GIT_URL avec l’URL de votre projet GitLab « shark-application ».

GIT_URL=ssh://git@ec2-35-170-208-112.compute-1.amazonaws.com:2222/samiamoura/shark-application.git

git clone $GIT_URL
Création de la ConfigMap « flux-ssh-config »

Créez la ConfigMap « flux-ssh-config » à partir du fichier des hôtes connus « $HOME/.ssh/known_hosts » dans le namespace « flux » à l’aide de la commande suivante :

kubectl create configmap flux-ssh-config --from-file=$HOME/.ssh/known_hosts -n flux
configmap/flux-ssh-config created

c – Configuration et déploiement de Flux

L’installation de l’opérateur Flux dans le cluster Kubernetes se fait en déployant un certain nombre de ressources Kubernetes à l’aide de plusieurs manifestes. Nous nous intéresserons notamment à la ressource de type Deployment, flux-gitops/flux-deployment.yaml permettant de déployer un pod flux avec des spécifications particulières. C’est la configuration de cette ressource qui sera responsable de la communication entre l’opérateur et le projet GitLab.

Nous allons dans un premier temps, décrire les paramètres du manifeste flux-gitops/flux-deployment.yaml puis nous l’éditerons avec les informations nécessaires.

Description du déploiement flux

Dans la section « template.spec.volumes » du manifeste flux-gitops/flux-deployment.yaml, nous retrouvons les volumes qui seront associés au pod :

template:
...
  spec:
  ...
    volumes:
    - name: certs
      hostPath:
        path: /etc/docker/certs.d/ec2-35-170-208-112.compute-1.amazonaws.com
    - name: certs2
      hostPath:
        path: /etc/docker/certs.d/ec2-35-170-208-112.compute-1.amazonaws.com
    - name: ssh-config
      configMap:
        name: flux-ssh-config
    - name: git-key
      secret:
        secretName: flux-git-deploy
        defaultMode: 0400
  • Les volumes « certs » et « certs2 » de type hostPath, permettent de monter dans le pod le certificat de la registry GitLab,
  • Le volume « ssh-config » de type configMap, permet de monter dans le pod, le ConfigMap « flux-ssh-config » contenant le fichier des hôtes connus,
  • Le volume « git-key » de type Secret, permet de monter dans le pod, le Secret « flux-git-deploy » contenant la clé privée, avec les permissions en read-only.

Dans la section « template.spec.containers.volumeMounts » du manifeste flux-gitops/flux-deployment.yaml, nous retrouvons à quels emplacement dans le pod, seront montés les volumes précédemment définis :

template:
...
  spec:
  ...
    containers:
    ...
      volumeMounts:
      - name: certs
        mountPath: /usr/local/share/ca-certificates/
      - name: certs2
        mountPath: /etc/ssl/certs/
      - name: ssh-config
        mountPath: /root/.ssh
      - name: git-key
        mountPath: /etc/fluxd/ssh
        readOnly: true

Dans la section « template.spec.containers.args » du manifeste flux-gitops/flux-deployment.yaml, nous retrouvons la configuration Flux pour intégrer le projet GitLab :

template:
...
  spec:
  ...
    containers:
    ...
      args:
      - --git-url=ssh://git@ec2-35-170-208-112.compute-1.amazonaws.com:2222/samiamoura/shark-application.git
      - --git-branch=master
      - --git-path=kubernetes
      - --git-ci-skip
      ...
  • –git-url : cette instruction permet de définir l’URL du repository git à scruter,
  • –git-branch : cette instruction permet de définir la branche à surveiller,
  • –git-path : cette instruction permet de définir le dossier dans lequel seront hébergés les manifestes,
  • –git-ci-skip : cette instruction permet d’éviter de déclencher un nouveau pipeline lorsque Flux met à jour le projet GitLab. Avec cette instruction on évite de déclencher des pipelines en boucle infinie.
Configuration du manifeste flux

Dans cette section, nous allons configurer le manifeste flux-gitops/flux-deployment.yaml avec les informations liées à notre projet :

Ajoutez le répertoire où se situe le certificat de la Registy GitLab, dans le manifeste flux-gitops/flux-deployment.yaml à l’aide des commandes suivantes :

GITLAB_PUBLICDNS=$(/usr/local/bin/aws ec2 describe-instances \
  --query "Reservations[*].Instances[*].[PublicDnsName]" \
  --filters Name=tag:Name,Values=GitLabServer Name=instance-state-name,Values=running \
  --output text)

sed -i "s|/etc/docker/certs.d/|/etc/docker/certs.d/$GITLAB_PUBLICDNS|g" \
  $HOME/gitops-training/flux-gitops/flux-deployment.yaml

Ajoutez les informations concernant votre projet GitLab à l’aide des commandes suivantes :
Information : Éditez les variables GIT_URL et GIT_PATH avec vos informations.

GIT_URL=ssh://git@ec2-35-170-208-112.compute-1.amazonaws.com:2222/samiamoura/shark-application.git

GIT_PATH=kubernetes

sed -i "s|--git-url=YOUR_REPOS_URL|--git-url=$GIT_URL|g" \
  $HOME/gitops-training/flux-gitops/flux-deployment.yaml

sed -i "s|--git-path=YOUR_K8S_GIT_PATH|--git-path=$GIT_PATH|g" \
  $HOME/gitops-training/flux-gitops/flux-deployment.yaml

Déploiement de l’opérateur Flux

À présent l’ensemble de nos ressources étant configurées, nous pouvons déployer l’opérateur Flux dans le cluster Kubernetes.

Déployez le l’opérateur Flux et toutes ses ressources à l’aide de la commande suivante :

kubectl apply -f $HOME/gitops-training/flux-gitops
serviceaccount/flux created
clusterrole.rbac.authorization.k8s.io/flux created
clusterrolebinding.rbac.authorization.k8s.io/flux created
deployment.apps/flux created
deployment.apps/memcached created
service/memcached created

Comme expliqué précédemment, plusieurs ressources sont déployées pour que l’opérateur Flux puisse exécuter ses fonctions. Voici une brève description des ressources que nous avons déployées :

  • Un ServiceAccount, un ClusterRole et un ClusterRoleBinding permettant de fournir les authentifications et les autorisations nécessaires au fonctionnement de Flux,
  • Un Deployment permettant de déployer l’opérateur Flux,
  • Un Service et un Deployment pour « memcached » qui est utilisé par Flux pour garder en cache les metadatas des images.

L’opérateur Flux est à présent déployé dans le cluster Kubernetes.

Important : Il est important de regarder régulièrement les logs du pod Flux afin de pouvoir dépanner les éventuels problèmes. Dans certains cas, un message d’erreur à propos du certificat de la registry peut parfois remonter. Il n’empêche pas l’opérateur Flux de fonctionner. Généralement, vous pouvez supprimer le message en supprimant le pod Flux.

err="Get https://index.docker.io/v2/: x509: certificate signed by unknown authority"

Récupérez le nom du pod Flux et afficher les logs à l’aide des commandes suivantes :

PODFLUXNAME=$(kubectl get all -n flux | grep -i flux | grep -i pod \
  | awk '{print $1}' | awk -F "/" '{print $2}')

kubectl -n flux logs $PODFLUXNAME

Supprimez le pod Flux à l’aide de la commande suivante :

kubectl -n flux delete pod $PODFLUXNAME

d – Modification du pipeline

Modification du fichier de pipeline .gitlab-ci.yml

Comme mentionné en introduction de ce chapitre, c’est maintenant l’opérateur Flux qui sera chargé de redéployer dans le cluster, les changements effectués sur le projet et non plus l’outil de CI. Nous allons donc modifier légèrement le pipeline réalisé précédemment en supprimant le stage « deployment » du fichier .gitlab-ci.yml. Tout le reste du fichier restera identique.

Dans la nouvelle version du fichier.gitlab-ci.yml et du nouveau pipeline, il n’y a plus de commande kubectl pour interagir avec l’API Server du cluster Kubernetes.

Information : L’ensemble des commandes de cette section seront à effectuer depuis le serveur GitLab.

Depuis le serveur GitLab, supprimez le stage « deployment » du fichier .gitlab-ci.yml à l’aide de la commande suivante :

cat <<< 'stages:
  - build
  - test
  - push

build:
  image: docker:stable
  stage: build
  services:
    - docker:dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:tmp .
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
    - docker push $CI_REGISTRY_IMAGE:tmp
  only:
  - master

test:
  image: docker:stable
  stage: test
  services:
    - docker:dind
  script:
    - docker run -d --name shark-app -p 8080:8080 $CI_REGISTRY_IMAGE:tmp
    - sleep 10s
    - TEST=$(docker run --link shark-app samiamoura/curl -s http://shark-app:8080 | grep -i "Are you ready to learn about sharks?")
    - if [ $? -eq 0 ]; then  echo \"TEST OK\"; docker rm -f shark-app ; exit 0; else echo \"TEST KO\"; docker rm -f shark-app ; exit 1; fi;
  only:
  - master

push:
  image: docker:stable
  stage: push
  services:
    - docker:dind
  script:
    - docker image pull $CI_REGISTRY_IMAGE:tmp
    - docker image tag $CI_REGISTRY_IMAGE:tmp $CI_REGISTRY_IMAGE:$CI_BUILD_REF
    - docker image tag $CI_REGISTRY_IMAGE:tmp $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
    - docker push $CI_REGISTRY_IMAGE:$CI_BUILD_REF
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
  only:
  - master' > $HOME/shark-application/.gitlab-ci.yml
Modification des manifestes Kubernetes

Le fichier de pipeline mis à jour, à présent, il nous reste à configurer les manifestes Kubernetes pour qu’ils soient configurés avec Flux.

Nous n’utiliserons plus le template de déploiement shark-application/kubernetes/deployment.tpl nous pouvons donc le supprimer. À la place nous allons créer un nouveau manifeste Kubernetes shark-application/kubernetes/deployment.yml de type Deployment. Ce manifeste, qui sera mis à jour à chaque fois qu’une image avec un nouveau tag sera détectée grâce à l’intégration de Flux, permettra de redéployer automatiquement l’application dans le cluster Kubernetes. Voici les caractéristiques du nouveau manifeste :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: shark-app
  annotations:
    flux.weave.works/automated: "true"
    flux.weave.works/tag.shark-app: regexp:^((?!tmp).)*$
  labels:
    app: shark-app
spec:
  selector:
    matchLabels:
      app: shark-app
  template:
    metadata:
      labels:
        app: shark-app
    spec:
      containers:
      - name: shark-app
        image: ec2-35-170-208-112.compute-1.amazonaws.com/samiamoura/shark-application:master

Ce manifeste possède les mêmes spécifications que celui généré lors du pipeline avec l’approche de type « push » , sauf que la configuration pour intégrer Flux a été ajoutée. Celle-ci est assurée par les attributs de type annotations . Ci-dessous, la description des annotations :

  • flux.weave.works/automated: “true” : cette instruction permet d’activer le redéploiement automatique pour cette ressource,
  • flux.weave.works/tag.shark-app: regexp:^((?!tmp).)*$ : cette instruction permet de s’assurer que l’image avec le tag tmp ne sera pas prise en compte dans le redéploiement de la ressource.

Supprimez le template de déploiement shark-application/kubernetes/deployment.tpl à l’aide de la commande suivante :

rm -f $HOME/shark-application/kubernetes/deployment.tpl

Créez le manifeste shark-application/kubernetes/deployment.yml à l’aide des commandes suivantes :
 Information : Editez la variable REGISTRY_URL avec l’adresse de votre Registry.

REGISTRY_URL=ec2-35-170-208-112.compute-1.amazonaws.com/samiamoura/shark-application

cat <<< 'apiVersion: apps/v1
kind: Deployment
metadata:
  name: shark-app
  annotations:
    flux.weave.works/automated: "true"
    flux.weave.works/tag.shark-app: regexp:^((?!tmp).)*$
  labels:
    app: shark-app
spec:
  selector:
    matchLabels:
      app: shark-app
  template:
    metadata:
      labels:
        app: shark-app
    spec:
      containers:
      - name: shark-app
        image: '$REGISTRY_URL':master' > $HOME/shark-application/kubernetes/deployment.yml
Mise à jour du code source de l’application

Pour tester l’approche GitOps de type « pull », nous allons une nouvelle fois modifier légèrement le code source de l’application. Nous remplacerons le message « This app is powered by GitLab and Kubernetes » par « This app is now powered by Flux and GitOps » sur la page principale de l’application.

Modifiez le message sur la page principale de l’application à l’aide de la commande suivante :

sed -i "s|<p>This app is powered by GitLab and Kubernetes</p>|<p>This app is  now powered by Flux and GitOps</p>|g" \
  $HOME/shark-application/app/views/index.html

Notre nouveau pipeline a été configuré. Nous allons pouvoir l’exécuter dans le prochain chapitre.

8 – Exécution du pipeline CI/CD de type « pull »

a – Déploiement de l’application

Nous avons déployé l’opérateur Flux dans le cluster Kubernetes. Puis, nous avons mis à jour notre projet « shark-application » avec : la modification du fichier .gitlab-ci.yml, l’ajout d’un manifeste Kubernetes configuré avec Flux et enfin la modification du code source de l’application. Nous devons à présent pousser toutes ces modifications sur le SCM GitLab. Cette action déclenchera l’exécution d’un nouveau pipeline CI/CD.

Information : L’ensemble des commandes de cette section seront à effectuer depuis le serveur GitLab.

À la racine du répertoire « shark-application » ajoutez tous les fichiers, commitez les changements et poussez les mises à jour sur le projet GitLab :

cd $HOME/shark-application
git add .
git commit -m "Pipeline CI/CD with Flux and GitOps"
git push -u origin master

Depuis l’interface GitLab, dans le menu latéral à gauche du projet, se rendre dans CI/CD > Pipelines , nous pouvons constater que 3 pipelines ont été déclenchés à la suite push :

Trois pipelines GitOps
  • Un premier pipeline a été déclenché à la suite du push des mises à jour du projet, sur le SCM GitLab,
  • Les deux autres pipelines ont été déclenchés par Flux, lorsque celui-ci à mis à jour le manifeste de déploiement shark-application/kubernetes/deployment.yml sur le SCM GitLab; Un pipeline a été déclenché sur la branche master et le deuxième sur la branche flux-sync créée par Flux.
  • Les pipelines déclenchés par Flux sont dans l’état « skipped » et n’ont pas été réalisés. Ceci, grâce à l’instruction –git-ci-skip renseignée dans la configuration de Flux. Cela nous permet d’éviter que des pipelines ne soient exécutés en boucle lors de la mise à jour du projet par Flux.

Nous pouvons aussi constater que ce nouveau pipeline possède bien les 3 stages définis dans le fichier .gitlab-ci.yml et qu’il n’y a plus de stage « deployment » :

3 stages : build, test, push

b – Test de l’application

Le nouveau pipeline s’étant exécuté avec succès, nous pouvons tester que l’application a bien été mise à jour et redéployée dans le cluster Kubernetes. Pour rappel, l’application est exposée sur le port 30000 des workers Kubernetes. Pour tester l’application, entrez dans un navigateur web l’adresse IP publique de chaque worker Kuberntes ainsi que le port 30000.

http://Adresse_IP_Publique_Worker_Node:30000
  • Test sur le kubernetes worker 1

L’adresse IP Publique du worker 1 est : 35.173.110.79. Entrez l’adresse http://35.173.110.79:30000 dans un navigateur.

Le résultat doit être similaire à :

Application déployée avec GitOps – worker1
  • Test sur le kubernetes worker 2

L’adresse IP Publique du worker 1 est : 54.209.65.74. Entrez l’adresse http://54.209.65.74:30000 dans un navigateur.

Le résultat doit être similaire à :

Application déployée avec GitOps – worker2

L’application a été mise à jour et redéployée dans le cluster Kubernetes avec succès.

Le pipeline CI/CD GitOps avec l’approche de type « pull » est fonctionnel. Bien entendu, lui aussi pourrait être optimisé avec notamment le nommage des tags avec semver, ou l’ajouts de stages supplémentaires et beaucoup d’autres choses.

9 – Bilan

Méthode « push » vs « pull »

Finalement dans cet article, nous avons avons pu voir que les deux approches, s’apparentent à du GitOps. Effectivement, dans les deux méthodes « push » et « pull », nous disposons, dans un unique projet Git, les manifestes permettant la configuration de notre application. Cependant, avec l’approche du type « pull », nous avons la présence d’un opérateur permettant d’ajouter la notion d’observabilité dans cluster Kubernetes. De plus, avec cette approche, nous n’avons pas besoin de spécifier des credentials car nous opérons à l’intérieur du cluster. À l’inverse pour la méthode « push », nous avons du fournir les authentifications nécessaires pour interagir avec l’API Server et exécuter les commandes kubectl.

Si vous souhaitez en savoir davantage sur l’approche GitOps dans le monde de l’entreprise, je vous redirige vers un article et une vidéo très intéressante sur le sujet. Voici le lien : https://www.sokube.ch/post/gitops-and-the-millefeuille-dilemma.

Environnement

Version des applications

ApplicationVersion
GitLab CE11.11.0
Git2.11.0
Docker CE19.03.12
Kubernetes1.18.6
Flux1.20.0
AWS CLI2.0.34

Références

Techniques

Personnelles

Sami AMOURA
DevOps Enthusiast  | Docker Certified | Kubernetes Certified CKA & CKAD | AWS Certified


2 réflexions sur “PIPELINE CI/CD GITOPS AVEC KUBERNETES, FLUX, GITLAB CE, ET AWS”

  1. Avatar

    Du coup, comment ça se passe dans le cas d’un Kubernetes managé ? (si j’ai bien compris on a pas accès au Master )
    Il suffit de sauter la partie sur RBAC et ça fonctionnera ?

    1. Avatar

      Dans un cluster managé de type EKS tu as accès au master grâce à l’API Server et à l’aide du fichier $HOME/.kube/config que te fournit AWS, en fonction des droits que tu auras configuré. Tu peux donc créer les ressources que tu souhaites si tu es l’admin de ton cluster. De la même façon qu’un cluster non managé.
      Dans tous les clusters managés en étant administrateur tu pourras créer les ressources que tu souhaites.
      Petite précision : tu n’as pas accès « physiquement » au master mais tu peux interagir avec celui-ci grâce à l’API Server et aux credentials ($HOME/.kube/config) .

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *