Introduction

🌍 Dans un monde oĂč les utilisateurs exigent une disponibilitĂ© constante, mĂȘme de courts arrĂȘts de service peuvent entraĂźner des pertes significatives et compromettre l’expĂ©rience client. Ce guide propose une approche moderne et pratique pour garantir des dĂ©ploiements sans interruption, en utilisant une stratĂ©gie de dĂ©ploiement blue-green sur Google Cloud Platform (GCP). 🚀

L’objectif est de vous guider dans la crĂ©ation d’une application Ă  Ă©tat, hĂ©bergĂ©e sur Compute Engine, qui puisse ĂȘtre mise Ă  jour sans aucun temps d’arrĂȘt. Cette mĂ©thode est idĂ©ale pour les entreprises nĂ©cessitant un accĂšs ininterrompu, notamment celles oĂč la continuitĂ© de service est cruciale. đŸ’Œâœš

Dans ce guide, nous allons dĂ©tailler Ă©tape par Ă©tape l’implĂ©mentation complĂšte de ce projet. đŸ“œđŸ› ïž

Pour commencer, si vous souhaitez suivre cette implĂ©mentation, veuillez cloner le dĂ©pĂŽt suivant https://github.com/DJTJ21/Blue-Green-deployment-application-code.git qui contient uniquement le code de l’application. Nous allons le complĂ©ter ensemble progressivement. đŸ’»đŸ”§

Si vous prĂ©fĂ©rez avoir le code final prĂȘt Ă  l’emploi, vous pouvez cloner le dĂ©pĂŽt suivant https://github.com/DJTJ21/Blue-Green-terraform-GCP-packer-github-Action-project.git . Pour ceux qui aimeraient directement dĂ©ployer la solution, je prĂ©parerai un petit article dĂ©diĂ©. 📄✹

Sans plus tarder, lançons-nous dans l’implĂ©mentation du projet ! 🚀🔧

Étape 1 : CrĂ©ation d’un projet sur GCP 🌐

Pour créer un projet sur Google Cloud Platform (GCP) et établir une infrastructure robuste, suivez ces étapes :

  1. Création du projet :
    • Une fois connectĂ© Ă  la console , cliquez sur « SĂ©lectionner un projet » et ensuite sur « Nouveau projet« . đŸ› ïž
    • Donnez un nom au projet et sĂ©lectionnez l’organisation (facultatif) et l’emplacement. Cliquez sur « CrĂ©er« . 📝✹
  2. Activation de la facturation :
    • Allez dans « Facturation » et associez une mĂ©thode de paiement au projet. đŸ’łđŸ§Ÿ

Configuration du Compte de Service et Activation des APIs

1. Création et Configuration du Compte de Service

AprÚs avoir configuré votre projet GCP, créez un compte de service ou utilisez le compte de service par défaut créé lors de la création du projet et assignez les rÎles suivants :

  • roles/secretmanager.secretAccessor : accĂšs aux secrets pour la gestion sĂ©curisĂ©e des informations. 🔒
  • roles/storage.objectAdmin : administration des objets dans Cloud Storage. 📩
  • roles/compute.imageUser : utilisation d’images pour les VM Compute Engine. đŸ–„ïž
  • roles/cloudsql.editor : gestion des bases de donnĂ©es Cloud SQL. đŸ—„ïž
  • roles/logging.logWriter : accĂšs pour Ă©crire des journaux. 📝

Pour ce faire, cliquez sur la barre de menu et naviguez dans IAM et Administration :

Vous verrez le compte de service par défaut créé lors de la mise en place du projet.

Cliquez ensuite sur le crayon tout Ă  droite pour effectuer l’attribution de rĂŽle. đŸ–‹ïžđŸ”§

Cliquez ensuite sur ajouter un autre rîle. ➕🔧

Faite un filtre sur le role que vous aimeriez ajouter par exemple pour le role roles/logging.logWriter on a ceci :

Ensuite vous cliquez sur le rĂŽle puis sur enregistrer. Vous rĂ©pĂšteriez la action pour tous les autres rĂŽles.  

2. Activation des APIs Requises

Pour permettre la création de certaines ressources, vous devez activer plusieurs APIs dans votre projet, notamment les suivantes :

  • Compute Engine API (compute.googleapis.com) : pour la gestion des VM et autres ressources de calcul. đŸ–„ïž
  • IAM Service Account API (iam.googleapis.com) : pour gĂ©rer les comptes de service. đŸ‘€
  • Cloud Resource Manager API (cloudresourcemanager.googleapis.com) : pour la gestion des ressources et des permissions. 🔧
  • SQL Admin API (sqladmin.googleapis.com) : pour la gestion des bases de donnĂ©es Cloud SQL. đŸ—„ïž
  • Secret Manager API (secretmanager.googleapis.com) : pour stocker et accĂ©der aux secrets. 🔐
  • Service Networking API (servicenetworking.googleapis.com) : pour la configuration des rĂ©seaux et des connexions entre services. 🌐

Pour activer les APIs :

Cliquez sur la barre de menu et naviguez dans API et services puis dans bibliothĂšque.

Recherchez l’API que vous souhaitez activer dans la barre de recherche, par exemple pour l’API SQL Admin API, puis cliquez sur « Activer »

Ensuite vous verrez un bouton Activer s’il n’est pas encore activĂ© dans mon cas je l’ai dĂ©jĂ  activĂ©e. 

Bravo! Vous avez maintenant créé un projet, activĂ© les APIs et configurĂ© les rĂŽles nĂ©cessaires. 🎉🚀

Installation et Configuration de Google CLI (gcloud)

Pour installer Google Cloud CLI (gcloud), suivez ces étapes :

  1. Télécharger et Installer le Google Cloud SDK :
    • 🌐 Rendez-vous sur le site du Google Cloud SDK et tĂ©lĂ©chargez le package adaptĂ© Ă  votre systĂšme d’exploitation (Windows đŸȘŸ, macOS 🍏 ou Linux 🐧). Les instructions d’installation y sont trĂšs bien dĂ©taillĂ©es, donc je n’y reviendrai pas ici. 📄
  2. Initialiser le SDK :
    • AprĂšs l’installation, lancez la commande suivante pour configurer le CLI:
    • Suivez les instructions pour vous connecter, configurer le projet creer ci dessus.

Étape 2 : CrĂ©ation des images Docker 🐳 et de l’image VM packer đŸ’» avec Cloud Build

Nous allons maintenant plonger dans l’implĂ©mentation ! Si vous n’avez pas encore clonĂ© le dĂ©pĂŽt, c’est le moment idĂ©al pour le faire : https://github.com/DJTJ21/Blue-Green-deployment-application-code.git il est pour l’instant constituer uniquement des repertoires de l’application

Vous remarquerez sans doute que dans le rĂ©pertoire du frontend, il y a un Dockerfile, mais pas dans celui de l’API. 🧐 Nous aurions pu utiliser un Dockerfile pour construire l’image, mais dans ce cas, nous allons utiliser le build pack. 🚀🔧

Construction des images docker avec CloudBuild:

dans la racine des repertoires api et frontend creez un fichier cloudbuild.yml dans le fichier cloudbuild de l’api ajouter le contenue suivant :

steps:
  - id: configure-docker-authentication
    name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: 'gcloud'
    args: ['auth', 'configure-docker', '-q']

  - id: mvn-install
    volumes:
      - name: 'm2'
        path: /root/.m2
    name: 'maven:latest'
    entrypoint: 'mvn'
    args: ['install', '-DskipTests']

  - id: build-and-push-api-docker-image
    volumes:
      - name: 'm2'
        path: /root/.m2
    name: 'maven'
    entrypoint: 'mvn'
    env: ['LDS_IMAGE_NAME=gcr.io/${PROJECT_ID}/${_BACKEND_IMAGE_REPO_NAME}:${_BACKEND_IMAGE_TAG}']
    args: ['compile', 'com.google.cloud.tools:jib-maven-plugin:3.3.1:build']

substitutions:
  _BACKEND_IMAGE_REPO_NAME: 'techworld-backend'
  _BACKEND_IMAGE_TAG: 'latest'
  PROJECT_ID: 'votre-id-project'

options:
  machineType: 'E2_MEDIUM'
  logging: 'CLOUD_LOGGING_ONLY'

Nous allons clarifier les différentes étapes de ce fichier :

Configuration de l’authentification Docker :

Cette Ă©tape configure l’authentification Docker pour permettre Ă  Google Cloud Build de pousser des images Docker vers le registre de conteneurs Google. 🚀🔐

Installation des dependances Maven :

Cette Ă©tape installe les dĂ©pendances Maven sans exĂ©cuter les tests, en utilisant le volume /root/.m2 pour stocker les artefacts Maven en cache. 📩

Construire et Pousser l’Image Docker de l’API :

Cette Ă©tape compile l’application API et utilise le plugin Jib de Maven pour construire et pousser l’image Docker vers Google Container Registry (GCR) en utilisant l’authentification configurĂ©e prĂ©cĂ©demment. đŸ› ïžđŸšą

Substitutions:

Ces substitutions permettent de dĂ©finir des variables personnalisĂ©es utilisĂ©es dans le fichier. Ici, le nom du dĂ©pĂŽt d’images backend, la balise d’image et l’ID du projet GCP. đŸ”„đŸ–„ïž

Options :

machineType: SpĂ©cifie le type de machine utilisĂ©e pour le build. Ici, une machine de type E2_MEDIUM est utilisĂ©e. đŸ’»âš™ïž

logging: DĂ©finit les options de journalisation, ici configurĂ©es pour n’utiliser que Cloud Logging. 📊📝

Maintenant que nous avons bien compris le contenu du fichier cloudbuild, nous allons construire l’image Ă  partir de ce fichier. Pour cela, naviguez dans le rĂ©pertoire api/ et exĂ©cutez la commande suivante :

gcloud builds submit --config cloudbuild.yaml

Vous ne verrez aucun log pendant le processus; c’est tout Ă  fait normal. đŸ› ïž En revanche, un bucket sera créé dans votre projet pour y stocker tous les logs. 📂🔍

Une fois l’exĂ©cution terminĂ©e, si tout s’est bien passĂ©, vous obtiendrez une sortie similaire Ă  celle-ci. ✅✹

Vous pouvez tout de mĂȘme consulter les logs sur votre terminal grĂące Ă  cette commande : đŸ“œđŸ’»

gcloud builds log Id-du-build

Si nous naviguons dans notre projet GCP dans Artifact Registry, nous pouvons voir que l’image du backend a Ă©tĂ© mise Ă  jour.

A present passons au fichier cloudbuild du frontend ajoutez y le contenue suivant :

steps:
  - id: build-frontend-blue-docker-image

    name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'gcr.io/${PROJECT_ID}/${_FRONTEND_IMAGE_REPO_NAME}:blue', '.']

  - id: push-frontend-blue-docker-image
  
    name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'gcr.io/${PROJECT_ID}/${_FRONTEND_IMAGE_REPO_NAME}:blue']

  - id: prepare-frontend-green-styles
 
    name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: 'cp'
    args: ['src/green.index.html', 'src/index.html']

  - id: build-frontend-green-docker-image
  
    name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'gcr.io/${PROJECT_ID}/${_FRONTEND_IMAGE_REPO_NAME}:green', '.']

  - id: push-frontend-green-docker-image
 
    name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'gcr.io/${PROJECT_ID}/${_FRONTEND_IMAGE_REPO_NAME}:green']

substitutions:
  _FRONTEND_IMAGE_REPO_NAME: 'techworld-frontend'

options:
  machineType: 'E2_MEDIUM'
  logging: 'CLOUD_LOGGING_ONLY'

Nous allons clarifier les différentes étapes de ce fichier :

Build Frontend Blue Docker Image :

Cette Ă©tape construit une image Docker pour le frontend en utilisant le Dockerfile et la tagge avec le tag blue. đŸ› ïžđŸ””

Push Frontend Blue Docker Image :

Cette Ă©tape pousse l’image Docker construite prĂ©cĂ©demment vers Google Container Registry (GCR) . đŸš€đŸ””

Prepare Frontend Green Styles :

Cette Ă©tape copie le fichier green.index.html vers index.html pour prĂ©parer les styles du frontend pour l’image :greenđŸŒżđŸ–„ïž.

Build Frontend Green Docker Image :

Cette Ă©tape construit une image Docker pour le frontend en utilisant les styles prĂ©parĂ©s pour :green et la tagge avec :green. đŸ› ïžđŸŒż

Push Frontend Green Docker Image :

Cette Ă©tape pousse l’image Docker :green construite prĂ©cĂ©demment vers GCR. 🚀🌿

Les deux derniĂšres sections ayant dĂ©jĂ  Ă©tĂ© abordĂ©es prĂ©cĂ©demment, passons maintenant Ă  la construction des images du frontend. 🌟 Pour cela, naviguez dans le rĂ©pertoire frontend oĂč vous avez créé le fichier cloudbuild et exĂ©cutez la commande suivante :

gcloud builds submit --config=cloudbuild.yml

Le processus prendra un peu de temps. Si l’opĂ©ration rĂ©ussit, vous obtiendrez une sortie semblable Ă  celle-ci. ✅⌛

Une fois terminĂ©, si nous naviguons dans notre projet GCP, dans Artifact Registry, nous pouvons voir que les images du frontend ont Ă©tĂ© mises Ă  jour. 🌟📂

Maintenant que nous avons fini de construire les images, passons Ă  l’Ă©tape suivante : utiliser ces images pour crĂ©er une VM personnalisĂ©e avec Packer. 🚀✹

Construction de l’image VM Packer avec cloudbuild

đŸ”„ Packer, dĂ©veloppĂ© par HashiCorp, est un outil open-source puissant conçu pour simplifier la crĂ©ation d’images de machines. Avec Packer, vous pouvez crĂ©er des images identiques pour diverses plateformes (AWS, Azure, GCP, VMware, etc.) Ă  partir d’une seule configuration source

Points clés de Packer :

  • Automatisation : Packer automatise la crĂ©ation d’images, rĂ©duisant ainsi les erreurs humaines et augmentant la consistance des dĂ©ploiements.
  • Multiplateforme : Il supporte de multiples environnements, vous permettant de dĂ©ployer des images sur plusieurs infrastructures avec une seule configuration.
  • Rapide et LĂ©ger : Packer est conçu pour ĂȘtre rapide et lĂ©ger, facilitant des cycles de build rapides et une intĂ©gration fluide dans vos pipelines CI/CD.
  • CompatibilitĂ© avec les outils de gestion de configuration : Packer s’intĂšgre avec des outils comme Chef, Puppet et Ansible pour configurer vos images pendant le processus de crĂ©ation.

Maintenant que nous avons dĂ©couvert Packer 🎉, continuons avec notre mise en Ɠuvre ! 🚀 À la racine du projet, crĂ©ez un rĂ©pertoire nommĂ© cloudbuild-packer-image/ đŸ—‚ïž. Ensuite, naviguez Ă  l’intĂ©rieur et crĂ©ez deux autres rĂ©pertoires : config/ et tools/ đŸ› ïž. (Je suppose que tout le monde sait crĂ©er un rĂ©pertoire, non ? 😄). Le rĂ©pertoire config/ contiendra notre code Packer, tandis que le rĂ©pertoire tools/ renfermera les Ă©lĂ©ments qui seront provisionnĂ©s lors de la crĂ©ation de l’image de notre VM đŸ–„ïž.

Dans le rĂ©pertoire tools/, crĂ©ez un fichier install_package.sh qui contiendra le script d’installation des prĂ©requis nĂ©cessaires pour faire fonctionner notre application dans la VM Ă  crĂ©er. đŸ–„ïž N’oubliez pas d’y ajouter le contenu ci-dessous ! 📝🚀

cloud-init status -w
# Install docker, docker-compose, and mysql client
sudo apt-get update
sudo apt-get install -y docker.io docker docker-compose
sudo apt install -y mysql-client-core-8.0
gcloud auth configure-docker -q
sudo usermod -aG docker "$USER"

Le contenu est assez accessible pour tout le monde, donc je ne vais pas m’attarder sur les explications. 😄 Maintenant, dans le mĂȘme rĂ©pertoire, crĂ©ez un fichier docker-compose.yml qui utilisera les images construites prĂ©cĂ©demment pour dĂ©marrer les conteneurs applicatifs lors de l’initialisation de la VM. Ajoutez y le contenue suivant :


version: '3.9'
services:
  api:
    image: gcr.io/techworld-438822/techworld-backend:latest
    container_name: lds-api
    environment:
      - LDS_BUCKET=${LDS_BUCKET}
      - LDS_RESOURCE_PATH=${LDS_RESOURCE_PATH}
      - LDS_REST_PORT=${LDS_REST_PORT}
      - LDS_MYSQL_INSTANCE=${LDS_MYSQL_INSTANCE}
      - LDS_MYSQL_DATABASE=${LDS_MYSQL_DATABASE}
      - LDS_MYSQL_USERNAME=${LDS_MYSQL_USERNAME}
      - LDS_MYSQL_PASSWORD=${LDS_MYSQL_PASSWORD}
      - DATA_INIT_BUCKET_NAME=${DATA_INIT_BUCKET_NAME}
      - DATA_INIT_FILE_NAME=${DATA_INIT_FILE_NAME}
    ports:
      - ${LDS_REST_PORT}:${LDS_REST_PORT}
    networks:
      - lds
    logging:
      driver: gcplogs

  web:
    image: gcr.io/techworld-438822/techworld-frontend:${FRONTEND_VERSION_TAG}
    container_name: lds-web
    environment:
      - LDS_REST_URL=${LDS_REST_URL}
    ports:
      - '80:80'
    extra_hosts:
      - "host.docker.internal:host-gateway"
    networks:
      - lds
    logging:
      driver: gcplogs

networks:
  lds:
    driver: bridge

N’oubliez pas de modifier les noms des images avec les vĂŽtres !

À prĂ©sent, dans le rĂ©pertoire config/, crĂ©ez deux fichiers : jss_cd.pkr.hcl et variable.pkr.hcl, qui contiendront le code Packer chargĂ© de crĂ©er notre image VM. đŸ–„ïž . Ajoutez le contenu suivant dans le fichier variable.pkr.hcl. 📄🔧

variable "project_id" {
  type = string
}

variable "region" {
  type    = string
  default = "us-central1"
}

variable "zone" {
  type    = string
  default = "us-central1-a"
}

variable "img_name" {
  type = string
}

variable "img_desc" {
  type = string
}

variable "file_install_package_sh" {
  type    = string
  default = "../tools/install_package.sh"
}

variable "file_docker_compose_yml" {
  type    = string
  default = "../tools/docker-compose.yml"
}

C’est un simple fichier de variables, similaire Ă  celui de Terraform, donc rien de trĂšs nouveau Ă  ce niveau (si tu es familier avec Terraform 😄 ).

dans le fichier jss_cd.pkr.hcl ajoutez le contenue ci-dessous :


packer {
  required_plugins {
    googlecompute = {
      version = "1.0.16"
      source  = "github.com/hashicorp/googlecompute"
    }
  }
}

source "googlecompute" "main" {
  project_id            = "${var.project_id}"
  image_storage_locations = [
    "${var.region}",
  ]
  zone              = "${var.zone}"
  image_name        = "${var.img_name}"
  image_description = "${var.img_desc}"
  image_labels = {
    devops = "techworld-with-romuald"
  }
  image_family        = "jss-cd"
  source_image_family = "ubuntu-2204-lts"
  ssh_username        = "root"
  network             = "default"
  machine_type        = "n2-standard-2"
}

build {
  sources = ["sources.googlecompute.main"]

  provisioner "file" {
    sources     = [
      "${var.file_docker_compose_yml}",
    ]
    destination = "/"
  }

  provisioner "shell" {
    script             = "${var.file_install_package_sh}"
  }
}

Configuration de Packer:

Ce bloc configure Packer pour utiliser le plugin googlecompute (version 1.0.16) depuis le registre HashiCorp, qui permet de créer des images VM sur Google Cloud Platform (GCP).

Definition de la source :

Ce bloc dĂ©finit les paramĂštres de l’image sur GCP :

  • project_id : identifiant du projet GCP.
  • image_storage_locations et zone : emplacement et zone de stockage de l’image.
  • image_name, image_description, image_labels : informations sur l’image (nom, description, Ă©tiquettes).
  • image_family et source_image_family : famille de l’image cible et source (basĂ©e sur Ubuntu 22.04 LTS).
  • ssh_username : identifiant SSH pour accĂ©der Ă  la VM.
  • network et machine_type : rĂ©seau par dĂ©faut et type de machine (ici, n2-standard-2).

Bloc de construction :

Ce bloc build exĂ©cute la configuration sur l’image cible :

  • sources : utilise la source googlecompute.main dĂ©finie plus haut.
  • Provisioners :
    • file : transfĂšre le fichier Docker Compose (spĂ©cifiĂ© dans file_docker_compose_yml) vers la VM.
    • shell : exĂ©cute un script d’installation des prĂ©requis (dĂ©fini dans file_install_package_sh).

En definitif ce fichier packer configure un plugin pour Google Compute, dĂ©finit la source d’image avec des paramĂštres spĂ©cifiques (comme le nom, la zone, et les Ă©tiquettes), et utilise des provisioners pour copier des fichiers et exĂ©cuter des scripts shell dans l’image créée. đŸŒŸđŸ› ïž

✹ Maintenant, Ă  la racine de votre rĂ©pertoire cloudbuild-packer-image/, crĂ©ez le fichier cloudbuild_packer_vm_img.yaml 📝 et ajoutez-y le contenu suivant ! 🚀


steps:
  - name: "gcr.io/google.com/cloudsdktool/cloud-sdk"
    id: "Remove app image"
    env:
      - VM_IMAGE_NAME=$_VM_IMAGE_NAME
    script: |
      if [ -z $(gcloud compute images describe ${VM_IMAGE_NAME} --verbosity=none --format=text) ]; then
        echo "The image does not exist. Proceeding with next build step."
      else
        echo "Deleting the image."
        gcloud compute images delete ${VM_IMAGE_NAME} -q --verbosity=none
      fi

  - name: 'gcr.io/${_PACKER_BUILDER_PROJECT}/packer'
    dir: 'config'
    args:
      - init
      - .

  - name: 'gcr.io/${_PACKER_BUILDER_PROJECT}/packer'
    dir: 'config'
    args:
      - build
      - -var
      - project_id=$PROJECT_ID
      - -var
      - region=$_LOCATION
      - -var
      - zone=$_ZONE
      - -var
      - img_name=$_VM_IMAGE_NAME
      - -var
      - img_desc=Jss cd image from Packer which is triggered by Cloud Build
      - -var
      - file_install_package_sh=../tools/install_package.sh
      - -var
      - file_docker_compose_yml=../tools/docker-compose.yml
      - .

  - name: "gcr.io/google.com/cloudsdktool/cloud-sdk"
    id: "Public app image"
    env:
      - VM_IMAGE_NAME=$_VM_IMAGE_NAME
    script: |
      gcloud compute images add-iam-policy-binding ${VM_IMAGE_NAME} --member='allAuthenticatedUsers' --role='roles/compute.imageUser'

substitutions:
  _LOCATION: "us-central1"
  _ZONE: "us-central1-a"
  _VM_IMAGE_NAME: "techworld-vm-image"
  _PACKER_BUILDER_PROJECT: "hsa-public"

options:
  logging: CLOUD_LOGGING_ONLY

Voici les principales etapes de notre fichier :

Suppression de l’Image Existante 🔄

  • Cette Ă©tape vĂ©rifie si une image VM existe dĂ©jĂ  et la supprime si elle est prĂ©sente. Cela garantit que nous travaillons toujours avec les images les plus rĂ©centes.

Initialisation de Packer 🚀

  • Packer est utilisĂ© pour initialiser la configuration. Cela prĂ©pare l’environnement pour crĂ©er de nouvelles images VM.

Construction de l’Image VM avec Packer đŸ› ïž

  • Packer construit l’image VM en utilisant les variables fournies (comme l’ID du projet, la rĂ©gion, la zone, etc.) et les fichiers/scripts spĂ©cifiĂ©s. Cette Ă©tape assure que notre image est correctement configurĂ©e et prĂȘte Ă  l’emploi.

Publication de l’Image 🌐

  • Une fois l’image construite, des rĂšgles de politique IAM sont ajoutĂ©es pour rendre l’image publique, permettant Ă  tous les utilisateurs authentifiĂ©s d’y accĂ©der.

À ce stade, l’arborescence de notre projet devrait ĂȘtre la suivante : 🌳📂

Passons maintenant Ă  la crĂ©ation de l’image ! Pour cela, rendez-vous dans le rĂ©pertoire cloudbuild-packer-image 📂 et exĂ©cutez la commande suivante (que vous connaissez dĂ©jĂ  trĂšs bien 😉) :

gcloud builds submit --config=cloudbuild_packer_vm_img.yaml

🎉 Une fois la construction terminĂ©e avec succĂšs, la sortie affichĂ©e sera identique Ă  celle des Ă©tapes prĂ©cĂ©dentes.

Pour voir votre magnifique image, rendez-vous sur la console GCP đŸ–„ïž ! Dans la barre de recherche, tapez Compute Image, puis cliquez sur Images.

Assurez-vous que le message sous Images affiche bien Compute Engine – c’est lĂ  que votre crĂ©ation vous attend ! ✹

Etape 3 : Insfrastructure as code avec terraform

Maintenant, on attaque la partie que je considĂšre sĂ»rement comme la plus corsĂ©e
 du moins pour moi ! 😅 (J’avoue, j’ai du mal Ă  la rĂ©diger, elle est un peu longue, mais bon, on va y arriver ! đŸ’Ș). Allez, courage, on en sortira plus forts ! 🚀

afin de mieux vous situez sur l’infrastructure que nous allons mettre en place voici un shema de l’infrastructure question de voir un peu plus clair:

🚀 Allez, c’est parti pour le code Terraform de notre super infrastructure ! 🎉

Avant tout, mettons un peu d’ordre : crĂ©ez un rĂ©pertoire appelĂ© terraform-deployment. À l’intĂ©rieur, ajoutez un rĂ©pertoire modules oĂč on va ranger tous nos modules (oui, oui, nos prĂ©cieux composants rĂ©utilisables đŸ§©).

Ensuite, dans ce répertoire modules, on va organiser un peu notre monde :

  • database : pour tout ce qui est stockage et base de donnĂ©es 📚
  • load-balancer : pour le module qui gĂ©rera notre Ă©quilibrage de charge đŸ§˜â€â™‚ïž
  • networking : pour le rĂ©seau, parce que la connexion, c’est la clĂ© 🔗
  • vm-container : pour tout ce qui est crĂ©ation de nos Compute Engines ⚙

VoilĂ , avec tout ça en place, on est prĂȘt Ă  lancer notre infrastructure vers les Ă©toiles ! 🌌

đŸŽ© Et maintenant, place au module database ! 📚✹

Allez, préparez vos claviers, et lançons ce module database pour que vos données se sentent comme chez elles.

Débutons avec le fichier main.tf :


resource "google_sql_database_instance" "main" {
  name             = "cloud-deployment-gce-java-db"
  database_version = "MYSQL_8_0"
  region           = var.region
  settings {
    availability_type = var.availability_type
    backup_configuration {
      enabled            = true
      binary_log_enabled = true
    }
    location_preference {
      zone = var.zone
    }
    tier      = "db-custom-2-4096"
    disk_type = "PD_SSD"
    disk_size = 20
    ip_configuration {
      private_network = var.vpc_network_self_link
      ipv4_enabled    = false
    }
  }

  deletion_protection = false
  depends_on          = [google_service_networking_connection.private_vpc]
}

resource "google_sql_database" "main" {
  name      = "cloud_deployment_gce_db" # equal to api/src/main/resources/db/schema.sql
  charset   = "utf8"
  collation = "utf8_general_ci"
  instance  = google_sql_database_instance.main.name
}

resource "google_sql_user" "main" {
  name     = "cloud-deployment-gce-java"
  instance = google_sql_database_instance.main.name
  password = random_password.sql_password.result
  host     = "%"
}

resource "google_compute_global_address" "sql" {
  name          = "cloud-deployment-gce-java-db-address"
  purpose       = "VPC_PEERING"
  address_type  = "INTERNAL"
  prefix_length = 20
  network       = var.vpc_network_self_link
}

resource "random_password" "sql_password" {
  length           = 20
  min_lower        = 4
  min_numeric      = 4
  min_special      = 4
  min_upper        = 4
  override_special = "!@#*()-_=+[]{}:?"
}

resource "google_secret_manager_secret" "sql_password" {
  secret_id = "cloud-deployment-gce-java-db-password"
  project   = var.project_id

  replication {
    automatic = true
  }
}

resource "google_secret_manager_secret_version" "sql_password" {
  secret      = google_secret_manager_secret.sql_password.id
  enabled     = true
  secret_data = random_password.sql_password.result
}

resource "google_secret_manager_secret_iam_member" "sql_password" {
  project   = google_secret_manager_secret.sql_password.project
  secret_id = google_secret_manager_secret.sql_password.secret_id
  role      = "roles/secretmanager.secretAccessor"
  member    = "serviceAccount:${var.service_account}"
}

resource "google_service_networking_connection" "private_vpc" {
  network = var.vpc_network_self_link
  service = "servicenetworking.googleapis.com"
  reserved_peering_ranges = [
    google_compute_global_address.sql.name,
  ]
}

Allons-y, tentons vaillamment de dĂ©cortiquer ce fichier ! đŸ˜„đŸ› ïž

đŸŽ›ïž CrĂ©ation de l’Instance de Base de DonnĂ©es MySQL :

google_sql_database_instance : Crée une instance MySQL avec les paramÚtres spécifiés.

settings : Configuration de l’instance, incluant le type de disponibilitĂ©, la sauvegarde, la prĂ©fĂ©rence de localisation, le type et la taille du disque, et la configuration IP.

📚 CrĂ©ation de la Base de DonnĂ©es :

google_sql_database : CrĂ©e la base de donnĂ©es avec le nom, charset, collation, et l’instance associĂ©e.

🔑 CrĂ©ation de l’Utilisateur de la Base de DonnĂ©es :

google_sql_user : CrĂ©e un utilisateur pour la base de donnĂ©es avec le nom, l’instance associĂ©e, et le mot de passe gĂ©nĂ©rĂ©.

🌐 Adresse Globale de Peering VPC :

google_compute_global_address : Crée une adresse globale pour le peering VPC avec les paramÚtres spécifiés.

🔒 GĂ©nĂ©ration de Mot de Passe AlĂ©atoire :

random_password : GénÚre un mot de passe aléatoire avec les critÚres spécifiés.

Secret Manager pour le Mot de Passe de la Base de Données :

google_secret_manager_secret et google_secret_manager_secret_version : Créent un secret et une version du secret contenant le mot de passe de la base de données.

google_secret_manager_secret_iam_member : Attribue le rĂŽle d’accĂšs au secret Ă  un compte de service.

Connexion de Peering de Service :

google_service_networking_connection : Configure une connexion de peering de service avec les paramÚtres spécifiés.

Poursuivons avec les fichiers outputs et variables de ce module :

Voici le contenu du fichier variables.tf :

variable "project_id" {
  description = "The project ID to manage the Cloud SQL resources."
  type        = string
}

variable "region" {
  description = "The region of the Cloud SQL resource."
  type        = string
}

variable "zone" {
  description = "Google cloud zone where the resources will be created."
  type        = string
  default     = "us-central1-a"
}

variable "vpc_network_self_link" {
  description = "network_self_link is the self link of the VPC network to which the Cloud SQL instance is connected."
  type        = string
}

variable "availability_type" {
  description = "The availability type of the Cloud SQL instance, high availability (REGIONAL) or single zone (ZONAL)."
  type        = string
  validation {
    condition     = contains(["REGIONAL", "ZONAL"], var.availability_type)
    error_message = "Allowed values for type are \"REGIONAL\", \"ZONAL\"."
  }
}

variable "service_account" {
  description = "Service Account which should have read permission to access the database password."
  type        = string
}

Voici le contenu du fichier outputs.tf :

output "db_ip" {
  description = "The IPv4 address assigned for the master instance"
  value       = google_sql_database_instance.main.private_ip_address
}

output "connection_name" {
  description = "The connection name of the instance to be used in connection strings. For example, when connecting with Cloud SQL Proxy."
  value       = google_sql_database_instance.main.connection_name
}

output "database_name" {
  description = "Name of the Cloud SQL database"
  value       = google_sql_database.main.name
}

output "user_name" {
  description = "SQL username"
  value       = google_sql_user.main.name
}

output "password_secret" {
  description = "Name of the secret storing the database password"
  value       = google_secret_manager_secret.sql_password.secret_id
}

output "password" {
  description = "SQL password"
  value       = random_password.sql_password.result
}

Voici le contenu du fichier versions.tf :



terraform {
  required_version = ">= 0.13"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.65"
    }
    random = {
      source  = "hashicorp/random"
      version = "~> 3.4"
    }
  }
}

Ce fichier dĂ©finit les versions minimales requises de Terraform et des fournisseurs google et random, garantissant ainsi que notre configuration utilise des versions compatibles pour dĂ©ployer l’infrastructure.

Ça y est, notre module database est prĂȘt Ă  faire des Ă©tincelles ! 🎉✹ Maintenant, en route pour le module networking ! 🚀

đŸ•žïž Module Networking : Tissons notre toile de connexions ! 🌐

Ok, dans notre rĂ©pertoire networking, on va crĂ©er un fichier nommĂ© main.tf. Une fois prĂȘt, on y met le contenu suivant :


resource "google_compute_network" "main" {
  name                    = "cloud-deployment-gce-java"
  project                 = var.project_id
  auto_create_subnetworks = true
}

resource "google_compute_firewall" "cloud_deployment" {
  name      = "cloud-deployment-gce-java-health-check"
  network   = google_compute_network.main.name
  direction = "INGRESS"
  source_ranges = [
    "130.211.0.0/22", //health check ip
    "35.191.0.0/16"   //health check ip
  ]
  allow {
    protocol = "tcp"
    ports    = var.health_check_allow_ports
  }
}

# need to know what is google_compute_router and its nat.
resource "google_compute_router" "main" {
  name    = "cloud-deployment-gce-java-router"
  region  = var.region
  network = google_compute_network.main.name
}

resource "google_compute_router_nat" "main" {
  name                               = "cloud-deployment-gce-java-router-nat"
  router                             = google_compute_router.main.name
  region                             = google_compute_router.main.region
  nat_ip_allocate_option             = "AUTO_ONLY"
  source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
}

Analisons de plus prĂšs le contenu de ce fichier :

Ressource google_compute_network:

Ici, nous crĂ©ons un rĂ©seau virtuel nommĂ© « cloud-deployment-gce-java » dans le projet spĂ©cifiĂ©. đŸ—ïž L’option auto_create_subnetworks est activĂ©e, ce qui signifie que Google Cloud gĂ©nĂ©rera automatiquement des sous-rĂ©seaux pour nous. Pas besoin de se prendre la tĂȘte avec ça !

Ressource google_compute_firewall:

Ce bloc dĂ©finit une rĂšgle de pare-feu pour permettre le trafic entrant (INGRESS) sur le rĂ©seau créé. đŸ”„ Il est configurĂ© pour accepter les connexions provenant de certaines plages IP, principalement pour les vĂ©rifications de santĂ© (health checks). On autorise ici le protocole TCP sur les ports spĂ©cifiĂ©s par la variable health_check_allow_ports. đŸšȘ

Ressource google_compute_router:

Ce bloc crĂ©e un routeur qui permettra de gĂ©rer le trafic entre les diffĂ©rentes ressources du rĂ©seau. 📡 Il est associĂ© au rĂ©seau que nous avons prĂ©cĂ©demment créé et est dĂ©ployĂ© dans la rĂ©gion spĂ©cifiĂ©e par la variable region.

Ressource google_compute_router_nat:

Enfin, nous configurons une NAT (Network Address Translation) pour permettre aux instances du rĂ©seau de se connecter Ă  Internet tout en restant invisibles depuis l’extĂ©rieur. 🌍 L’option nat_ip_allocate_option est rĂ©glĂ©e sur « AUTO_ONLY », ce qui signifie que l’IP NAT sera attribuĂ©e automatiquement. 🌟

En rĂ©sumĂ©, ce fichier met en place l’infrastructure rĂ©seau qui soutiendra le bon fonctionnement de nos services cloud, en s’assurant que tout le trafic est gĂ©rĂ© correctement et en toute sĂ©curitĂ© ! .

Ajoutez les éléments suivants au fichier variables.tf, qui contient les variables de notre module :


variable "project_id" {
  description = "Google Cloud project ID."
  type        = string
}

variable "region" {
  description = "The region chosen to be used."
  type        = string
}

variable "health_check_allow_ports" {
  description = "The ports to allow for health check."
  type        = list(number)
}

Ensuite, ajoutez les éléments suivants au fichier outputs.tf :

output "vpc_network_self_link" {
  description = "VPC network"
  value       = google_compute_network.main.self_link
}

Enfin, ajoutez les éléments suivants dans un fichier versions.tf :

terraform {
  required_version = ">= 0.13"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.65"
    }
  }
}

🎉 Maintenant que nous avons terminĂ© avec ce module, passons au module Load-balancer! 🚀

Ce module est constitué de deux sous-modules :

  1. Backend-config đŸ› ïž : ChargĂ© de la configuration des load-balancers des VM.
  2. Frontend-config 🌐 : Responsable de la configuration des load-balancers pour rediriger le trafic vers nos diffĂ©rentes VM (jette un Ɠil au schĂ©ma pour plus de clartĂ©). đŸ–„ïžâœš

Nous allons débuter avec le sous-module Frontend-config. Alors crée ce sous-répertoire dans le répertoire Load-balancer et dans son fichier main.tf (que vous allez créer), ajoutez-y le contenu ci-dessous :



resource "google_compute_url_map" "cloud_deployment" {
  name            = "cloud-deployment-gce-java-${var.name_suffix}"
  default_service = var.backend_bucket_id
  host_rule {
    path_matcher = "app"
    hosts = [
      "*",
    ]
  }
  path_matcher {
    name            = "app"
    default_service = var.backend_bucket_id
    path_rule {
      paths = [
        "/${var.resource_path}/*",
      ]
      service = var.backend_bucket_id
    }
    path_rule {
      paths = [
        "*",
      ]
      route_action {
        dynamic "weighted_backend_services" {
          for_each = var.weighted_backend_services
          iterator = entry
          content {
            backend_service = entry.value["backend_service_id"]
            weight          = lookup(entry.value, "weight", floor(1000 / length(var.weighted_backend_services)))
          }
        }
      }
    }
  }
}

resource "google_compute_target_http_proxy" "cloud_deployment" {
  name    = "cloud-deployment-gce-java-${var.name_suffix}"
  url_map = google_compute_url_map.cloud_deployment.self_link
}

resource "google_compute_global_forwarding_rule" "cloud_deployment" {
  name                  = "cloud-deployment-gce-java-${var.name_suffix}"
  target                = google_compute_target_http_proxy.cloud_deployment.self_link
  ip_address            = var.global_address
  port_range            = "80"
  load_balancing_scheme = "EXTERNAL_MANAGED"
  labels                = var.labels
}

Ressource: google_compute_url_map

Je vais dĂ©tailler ce fichier au maximum, car il peut ĂȘtre un peu difficile Ă  apprĂ©hender. Du moins, c’est ce que je pense. 😊📄

Cette ressource dĂ©finit une map d’URL qui gĂšre comment les requĂȘtes HTTP sont dirigĂ©es vers les services backend en fonction des rĂšgles spĂ©cifiĂ©es.

name: Attribue un nom unique Ă  la map d’URL, en utilisant un suffixe variable pour l’unicitĂ©.

default_service: Définit le service backend par défaut (identifié par var.backend_bucket_id) qui sera utilisé si aucune autre rÚgle ne correspond.

host_rule:

  • path_matcher: DĂ©finit quel matcher de chemin utiliser, ici « app ».
  • hosts: Indique les hĂŽtes qui peuvent utiliser cette rĂšgle, ici tous les hĂŽtes ("*").

path_matcher:

  • name: Identifie ce matcher comme « app ».
  • default_service: Renvoie au service backend par dĂ©faut.
  • path_rule (premiĂšre rĂšgle):
    • paths: SpĂ©cifie le chemin qui doit correspondre, avec ${var.resource_path} pour une ressource dynamique.
    • service: Indique que le service correspondant est le backend dĂ©fini par var.backend_bucket_id.
  • path_rule (deuxiĂšme rĂšgle):
    • paths: Utilise un wildcard ("*") pour capturer toutes les autres requĂȘtes.
    • route_action: SpĂ©cifie l’action Ă  entreprendre pour cette rĂšgle, incluant la configuration de services backend pondĂ©rĂ©s.
    • dynamic "weighted_backend_services":
      • Permet d’ajouter dynamiquement plusieurs services backend avec des poids.
      • for_each: ItĂšre sur une variable de services backend pondĂ©rĂ©s, var.weighted_backend_services.
      • entry: ReprĂ©sente l’Ă©lĂ©ment courant dans l’itĂ©ration.
      • content:
        • backend_service: Indique le service backend Ă  utiliser.
        • weight: DĂ©finit le poids du service, permettant d’Ă©quilibrer la charge de maniĂšre dynamique.

Ressource: google_compute_target_http_proxy

Cette ressource crĂ©e un proxy HTTP cible qui sert de point d’entrĂ©e pour le trafic HTTP vers les services backend.

name: Comme précédemment, il utilise un nom unique basé sur le suffixe.

url_map: Lie le proxy Ă  la carte d’URL dĂ©finie prĂ©cĂ©demment, lui permettant de savoir oĂč diriger le trafic entrant.

Ressource: google_compute_global_forwarding_rule

Cette ressource configure une rĂšgle de redirection globale qui achemine le trafic vers le proxy HTTP cible.

name: Nomme la rĂšgle de redirection de maniĂšre unique.

target: Fait rĂ©fĂ©rence au proxy HTTP cible, indiquant vers oĂč diriger le trafic.

ip_address: SpĂ©cifie l’adresse IP publique Ă  utiliser pour la rĂšgle, stockĂ©e dans une variable (var.global_address).

port_range: Indique le port sur lequel la rÚgle écoute, ici le port 80 pour le HTTP.

load_balancing_scheme: Définit que cette rÚgle utilise un équilibrage de charge externe, géré par Google.

labels: Permet d’ajouter des Ă©tiquettes pour une identification et une gestion faciles.

😊 J’espĂšre que cette façon de dĂ©tailler est beaucoup plus claire. 📄✹ Maintenant, ajoutez les Ă©lĂ©ments suivants dans le fichier variables.tf :


variable "name_suffix" {
  description = "Suffix to append to the name of each resource."
  type        = string
}

variable "backend_bucket_id" {
  description = "backend bucket id"
  type        = string
}

variable "resource_path" {
  description = "Resource folder path"
  type        = string
}

variable "labels" {
  description = "A map of key/value label pairs to assign to the bucket."
  type        = map(string)
}

variable "weighted_backend_services" {
  description = "A list of weighted backend services."
  type = list(object(
    {
      backend_service_id = string
    }
  ))
}

variable "global_address" {
  description = "LB global address"
  type        = string
}

Ensuite, ajoutez les Ă©lĂ©ments suivants dans un fichier outputs.tf : 📄✹


output "lb_external_ip" {
  description = "Frontend IP address of the load balancer"
  value       = google_compute_global_forwarding_rule.cloud_deployment.ip_address
}

Ajoutez enfin les Ă©lĂ©ments suivants dans un fichier versions.tf : 📄✹


terraform {
  required_version = ">= 0.13"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.65"
    }
  }
}

👉 Maintenant, attaquons-nous au deuxiĂšme sous-module : backend-config ! CrĂ©ez ce sous-rĂ©pertoire đŸ—‚ïž et, Ă  l’intĂ©rieur, crĂ©ez un fichier main.tf 📝 en y ajoutant le contenu suivant :

resource "google_compute_health_check" "cloud_deployment" {
  name                = "cloud-deployment-gce-java-${var.name_suffix}"
  timeout_sec         = 10
  check_interval_sec  = 20
  unhealthy_threshold = 2
  tcp_health_check {
    port = var.health_check_port
  }
  log_config {
    enable = true
  }
}

resource "google_compute_backend_service" "cloud_deployment" {
  name                  = "cloud-deployment-gce-java-${var.name_suffix}"
  load_balancing_scheme = "EXTERNAL_MANAGED"
  port_name             = var.port_name
  dynamic "backend" {
    for_each = var.backends
    iterator = entry
    content {
      group = entry.value["group_url"]
    }
  }
  health_checks = [google_compute_health_check.cloud_deployment.self_link]
}

Nous allons briùvement expliquer cette ressource : 📄✹

Ressource google_compute_health_check

google_compute_health_check : Crée un contrÎle de santé pour le déploiement cloud.

  • name : Nom du contrĂŽle de santĂ©, avec un suffixe dynamique basĂ© sur une variable.
  • timeout_sec : DĂ©lai d’attente avant qu’un contrĂŽle de santĂ© soit considĂ©rĂ© comme Ă©chouĂ©.
  • check_interval_sec : Intervalle de temps entre les contrĂŽles de santĂ© successifs.
  • unhealthy_threshold : Nombre d’Ă©checs consĂ©cutifs nĂ©cessaires avant que l’instance soit considĂ©rĂ©e comme non saine.
  • tcp_health_check : VĂ©rifie la connectivitĂ© TCP sur un port spĂ©cifiĂ©.
    • port : Le port utilisĂ© pour le contrĂŽle de santĂ©.
  • log_config : Configuration des journaux.
    • enable : Active la journalisation pour ce contrĂŽle de santĂ©.

Ressource google_compute_backend_service

google_compute_backend_service : Définit un service backend pour le load balancer.

  • name : Nom du service backend, avec un suffixe dynamique basĂ© sur une variable.
  • load_balancing_scheme : SchĂ©ma de rĂ©partition de charge (EXTERNAL_MANAGED pour un load balancer externe).
  • port_name : Nom du port utilisĂ© par le backend.
  • dynamic « backend » : ItĂšre sur les backends spĂ©cifiĂ©s dans les variables et configure chaque groupe de backends.
    • for_each : ItĂ©ration sur la liste des backends.
    • iterator : Nom de l’itĂ©rateur pour chaque Ă©lĂ©ment.
    • content : Contenu pour chaque backend, spĂ©cifiant le groupe de backends.
      • group : URL du groupe backend.
  • health_checks : Associe le contrĂŽle de santĂ© dĂ©fini prĂ©cĂ©demment au service backend.

Maintenant que ce fichier est plus clair, ajoutez les Ă©lĂ©ments suivants dans un fichier variables.tf : 📄✹

variable "name_suffix" {
  description = "Suffix to append to the name of each resource."
  type        = string
}

variable "backends" {
  description = "A map of key/value pairs to assign load balancer group."
  type = list(object(
    {
      group_url = string
    }
  ))
}

variable "port_name" {
  description = "port name"
  type        = string
}

variable "health_check_port" {
  description = "health check port"
  type        = number
}

CrĂ©ez un fichier outputs.tf et ajoutez-y les Ă©lĂ©ments suivants : 📄✹


output "backend_service_id" {
  description = "Backend service ID"
  value       = google_compute_backend_service.cloud_deployment.id
}

Enfin, dans un fichier versions.tf, ajoutez le contenu suivant : 📄✹


terraform {
  required_version = ">= 0.13"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.65"
    }
  }
}

🎉 Maintenant que nous avons terminĂ© ce module, passons au module storage. CrĂ©ez un sous-rĂ©pertoire 📁 storage dans le mĂȘme rĂ©pertoire modules. Allons-y sans plus tarder avec le fichier main.tf que vous allez crĂ©er ✍ et ajoutez-y ceci :


resource "google_storage_bucket" "main" {
  project       = var.project_id
  name          = var.name
  location      = var.location
  labels        = var.labels
  force_destroy = true
}

resource "google_storage_default_object_acl" "policy" {
  bucket = google_storage_bucket.main.name
  role_entity = [
    "READER:allUsers",
  ]
}

google_storage_bucket.main : crĂ©e un bucket Google Storage. Le bucket est configurĂ© avec des variables pour le projet, le nom, la localisation et les labels. L’option force_destroy est dĂ©finie sur true pour garantir que le bucket soit supprimĂ©, mĂȘme s’il contient encore des objets.

google_storage_default_object_acl.policy : attribue une liste de contrĂŽle d’accĂšs (ACL) par dĂ©faut pour les objets du bucket. Dans ce cas, elle donne aux utilisateurs publics le rĂŽle de lecteur (READERS).

Ensuite, crĂ©ez un fichier nommĂ© variables.tf et insĂ©rez-y le contenu suivant. 📂👇

variable "project_id" {
  description = "Google Cloud project ID."
  type        = string
}

variable "name" {
  description = "Bucket name"
  type        = string
}

variable "location" {
  description = "Bucket location"
  type        = string
}

variable "labels" {
  type        = map(string)
  description = "A map of key/value label pairs to assign to the bucket."
  default     = {}
}

CrĂ©ez le fichier outputs.tf et insĂ©rez-y le contenu suivant. 📂👇

output "bucket_name" {
  description = "Bucket name"
  value       = google_storage_bucket.main.name
}

CrĂ©ez enfin le fichier versions.tf et insĂ©rez-y le contenu suivant. 📂👇

terraform {
  required_version = ">= 0.13"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.57"
    }
  }
}

Passons a present au dernier module , creez un repertoire vm-container dans notre repertoire de module . Ce module va crĂ©er đŸ› ïž un modĂšle d’instance Google Cloud 🌐 et un groupe d’instances managĂ©es (MIG) 📊 Ă  l’aide de Terraform. Il configurera aussi les paramĂštres de santĂ© đŸ©ș et de compte de service 📋 pour une gestion optimisĂ©e.

CrĂ©ez un fichier main.tf et insĂ©rez-y le contenu suivant. 📂👇


module "instance_template" {
  source  = "terraform-google-modules/vm/google//modules/instance_template"
  version = "~> 7.9"

  name_prefix  = "cloud-deployment-gce-java-temp-"
  source_image = var.source_image
  network      = var.vpc_network_self_link
  service_account = {
    email  = var.service_account.email
    scopes = var.service_account.scopes
  }
  tags           = var.tags
  metadata       = var.metadata
  startup_script = var.startup_script
  labels         = var.labels
}

module "mig" {
  source  = "terraform-google-modules/vm/google//modules/mig"
  version = "~> 8.0.1"

  project_id        = var.project_id
  region            = var.region
  mig_name          = var.mig_name
  hostname          = var.hostname == "" ? var.mig_name : var.hostname
  instance_template = module.instance_template.self_link
  target_size       = 1
  named_ports       = var.named_ports

  health_check = {
    type                = "tcp"
    port                = var.health_check_port
    proxy_header        = "NONE"
    request             = ""
    response            = ""
    check_interval_sec  = 5
    timeout_sec         = 5
    healthy_threshold   = 2
    unhealthy_threshold = 2
    host                = ""
    initial_delay_sec   = 200
    request_path        = ""
    enable_logging      = true
  }
}

Module Instance Template :

Ce module dĂ©finit un modĂšle d’instance Google Compute Engine avec un prĂ©fixe pour le nom des instances, une image source pour les lancer, un rĂ©seau VPC de dĂ©ploiement, un compte de service (avec email et permissions), des Ă©tiquettes pour la gestion, des mĂ©tadonnĂ©es pour la personnalisation, un script de dĂ©marrage pour automatiser des configurations, et des labels pour une organisation simplifiĂ©e. 🎉🚀

Module Managed Instance Group (MIG)

Ce module dĂ©finit un groupe d’instances managĂ©es (MIG) en utilisant le module source spĂ©cifiĂ©. Il configure des paramĂštres tels que l’ID du projet Google Cloud, la rĂ©gion de dĂ©ploiement, et le nom du MIG. Le module utilise un modĂšle d’instance prĂ©alablement dĂ©fini et dĂ©termine la taille cible du groupe d’instances et les ports nommĂ©s. Le contrĂŽle de santĂ© des instances est configurĂ© avec des paramĂštres spĂ©cifiques comme le type de contrĂŽle (TCP), le port Ă  vĂ©rifier, les intervalles de vĂ©rification, les dĂ©lais, et les seuils pour considĂ©rer une instance comme saine ou non. La journalisation des vĂ©rifications est Ă©galement activĂ©e. 🚀

Ensuite, crĂ©ez un fichier nommĂ© variables.tf et insĂ©rez-y le contenu suivant. 📂👇


variable "source_image" {
  description = "The source image used to create the instance."
  type        = string
}

variable "vpc_network_self_link" {
  description = "VPC network self link to deploy the instance in"
  type        = string
}

variable "service_account" {
  description = "Google recommends custom service accounts that have cloud-platform scope and permissions granted via IAM Roles."
  type = object({
    email  = string
    scopes = set(string)
  })
}

variable "metadata" {
  type        = map(any)
  description = "metadata to attach to the instance"
  default     = {}
}

variable "startup_script" {
  type        = string
  description = "The startup script to be used on the instance."
  default     = ""
}

variable "labels" {
  type        = map(string)
  description = "A map of key/value label pairs to assign to the resources."
}

variable "project_id" {
  description = "Google Cloud project ID."
  type        = string
}

variable "region" {
  description = "The region chosen to be used."
  type        = string
}

variable "mig_name" {
  description = "managed instance group name"
  type        = string
}

variable "hostname" {
  description = "hostname for the instance"
  type        = string
  default     = ""
}

variable "named_ports" {
  description = "named ports"
  type = list(object(
    {
      name = string
      port = number
    }
  ))
}

variable "health_check_port" {
  description = "health check port"
  type        = number
}

variable "tags" {
  description = "network tags for the instance"
  type        = list(string)
}

CrĂ©ez le fichier outputs.tf et insĂ©rez-y le contenu suivant. 📂👇

output "instance_group_url" {
  description = "The URL of the instance group."
  value       = module.mig.instance_group
}

output "mig_self_link" {
  description = "The self link of the instance group manager."
  value       = module.mig.self_link
}

CrĂ©ez enfin le fichier versions.tf et insĂ©rez-y le contenu suivant. 📂👇

terraform {
  required_version = ">= 0.13"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.65"
    }
  }
}

🎉 Bravo Ă  nous! Nous avons terminĂ© avec les modules. À prĂ©sent, notre arborescence devrait ressembler Ă  ceci. 🌳✹

A present , à la racine du répertoire terraform-deployment, créez un sous-répertoire template. Dans ce nouveau dossier, créez un fichier nommé startup-script.tftpl, et ajoutez-y le contenu suivant :

#! /bin/bash

export LDS_REST_PORT=${LDS_REST_PORT}
export LDS_PROJECT=$(gcloud config get-value project)
export LDS_BUCKET=${LDS_BUCKET}
export LDS_RESOURCE_PATH=/resource
export LDS_REST_URL=http://host.docker.internal:$${LDS_REST_PORT}
export LDS_MYSQL_INSTANCE=${LDS_MYSQL_INSTANCE}
export LDS_MYSQL_DATABASE=${LDS_MYSQL_DATABASE}
export LDS_MYSQL_USERNAME=${LDS_MYSQL_USERNAME}
export FRONTEND_VERSION_TAG=${FRONTEND_VERSION_TAG}
export DATA_INIT_BUCKET_NAME=${DATA_INIT_BUCKET_NAME}
export DATA_INIT_FILE_NAME=${DATA_INIT_FILE_NAME}

MYSQL_PASSWORD="$(gcloud secrets versions access --secret ${MYSQL_DB_PASSWORD_SECRET} latest --project $${LDS_PROJECT})"
export LDS_MYSQL_PASSWORD=$${MYSQL_PASSWORD}

docker-compose up -d api web

C’est un script qui s’exĂ©cute lors du dĂ©marrage de la VM. 🚀 Si le contenu te semble flou, n’hĂ©site pas Ă  jeter un Ɠil au fichier docker-compose.yml que nous avons intĂ©grĂ© dans l’image de la VM lors de la construction avec Packer. Cela devrait t’Ă©clairer ! 🔍

Maintenant, passons concrĂštement au dĂ©ploiement Blue/Green ! Avant de plonger dans le vif du sujet, faisons une petite mise au point : d’aprĂšs le schĂ©ma de l’architecture, on pourrait penser qu’il y a un point d’entrĂ©e unique pour le trafic utilisateur, qui redirige ensuite vers les instances compute-engine. Mais en rĂ©alitĂ©, chaque backend (Blue et Green) a son propre point d’entrĂ©e ! 🔄 C’Ă©tait essentiel de le prĂ©ciser pour la clartĂ©. Maintenant, dans le rĂ©pertoire terraform-deployment, crĂ©ez un fichier blue.tf et ajoutez-y le contenu suivant : 📝


module "vm_container_blue" {
  depends_on = [
    module.project_services,
  ]
  source = "./modules/vm-container"

  source_image          = var.source_image
  vpc_network_self_link = module.networking.vpc_network_self_link
  service_account = {
    email = google_service_account.cloud_deployment.email
    scopes = [
      "https://www.googleapis.com/auth/cloud-platform",
    ]
  }
  project_id = data.google_project.project.project_id
  region     = var.region
  mig_name   = "cloud-deployment-gce-java-mig-blue"
  metadata = {
    google-logging-enabled    = "true"
    google-monitoring-enabled = "true"
  }
  startup_script = templatefile(
    "${path.module}/templates/startup-script.tftpl",
    {
      LDS_REST_PORT            = lookup(local.named_ports_map, "server")["port"],
      LDS_PROJECT              = data.google_project.project.project_id,
      LDS_BUCKET               = module.storage.bucket_name,
      LDS_RESOURCE_PATH        = "/${local.resource_path}",
      LDS_MYSQL_INSTANCE       = module.database.connection_name,
      LDS_MYSQL_DATABASE       = module.database.database_name,
      LDS_MYSQL_USERNAME       = module.database.user_name,
      MYSQL_DB_PASSWORD_SECRET = module.database.password_secret,
      FRONTEND_VERSION_TAG     = "blue",
      DATA_INIT_BUCKET_NAME    = var.data_init_bucket_name,
      DATA_INIT_FILE_NAME      = var.data_init_archive_file_name,
    }
  )
  tags = [
    "service",
  ]
  named_ports       = local.named_ports
  health_check_port = lookup(local.named_ports_map, "server")["port"]
  labels            = var.labels
}

module "load_balancer_backend_config_blue" {
  depends_on = [
    module.project_services,
  ]
  source = "./modules/load-balancer/backend-config"

  name_suffix = "blue-v1"
  backends = [
    {
      group_url = module.vm_container_blue.instance_group_url,
    },
  ]
  port_name         = lookup(local.named_ports_map, "client")["name"]
  health_check_port = lookup(local.named_ports_map, "client")["port"]
}

module "load_balancer_frontend_config_blue" {
  depends_on = [
    module.project_services,
  ]
  source = "./modules/load-balancer/frontend-config"

  name_suffix       = "blue-v1"
  backend_bucket_id = google_compute_backend_bucket.cdn.id
  resource_path     = local.resource_path
  global_address    = module.global_addresses.addresses[0]
  weighted_backend_services = [
    {
      backend_service_id = module.load_balancer_backend_config_blue.backend_service_id
    }
  ]
  labels = var.labels
}

Module vm_container_blue :

Ce module configure l’instance VM de l’environnement “blue”. Il utilise le module vm-container, spĂ©cifie une image source, des informations de rĂ©seau et le compte de service pour autoriser l’accĂšs aux ressources GCP. Le script de dĂ©marrage, dĂ©fini dans le fichier startup-script.tftpl, initialise les paramĂštres spĂ©cifiques, tels que les dĂ©tails MySQL et les identifiants de stockage. Ce module crĂ©e Ă©galement des balises et des mĂ©tadonnĂ©es qui activent la journalisation et la surveillance pour la VM.

Module load_balancer_backend_config_blue :

Ce module configure le backend du load balancer pour l’environnement “blue”. Il Ă©tablit le service backend et associe un groupe d’instances, en utilisant le port spĂ©cifiĂ© pour la communication entre le client et le backend. Un contrĂŽle de santĂ© est appliquĂ© pour vĂ©rifier la disponibilitĂ© des instances dans le backend, ce qui permet une redirection dynamique du trafic en cas de panne ou d’indisponibilitĂ©.

Module load_balancer_frontend_config_blue :

Ce module gĂšre la configuration frontend du load balancer pour l’environnement “blue”. Il associe le bucket backend CDN, l’adresse IP globale et le service backend, assurant ainsi que les requĂȘtes clients sont redirigĂ©es correctement vers le bon environnement. GrĂące Ă  la pondĂ©ration, il est possible de basculer facilement le trafic entre diffĂ©rents services backend, ce qui est essentiel pour les dĂ©ploiements Blue/Green, permettant des mises Ă  jour sans interruption du service.

Ensuite, créez un fichier nommé green.tf et insérez-y le contenu suivant :



module "vm_container_green" {
  depends_on = [
    module.project_services,
  ]
  source = "./modules/vm-container"

  source_image          = var.source_image
  vpc_network_self_link = module.networking.vpc_network_self_link
  service_account = {
    email = google_service_account.cloud_deployment.email
    scopes = [
      "https://www.googleapis.com/auth/cloud-platform",
    ]
  }
  project_id = data.google_project.project.project_id
  region     = var.region
  mig_name   = "cloud-deployment-gce-java-mig-green"
  metadata = {
    google-logging-enabled    = "true"
    google-monitoring-enabled = "true"
  }
  startup_script = templatefile(
    "${path.module}/templates/startup-script.tftpl",
    {
      LDS_REST_PORT            = lookup(local.named_ports_map, "server")["port"]
      LDS_PROJECT              = data.google_project.project.project_id,
      LDS_BUCKET               = module.storage.bucket_name,
      LDS_RESOURCE_PATH        = "/${local.resource_path}",
      LDS_MYSQL_INSTANCE       = module.database.connection_name,
      LDS_MYSQL_DATABASE       = module.database.database_name,
      LDS_MYSQL_USERNAME       = module.database.user_name,
      MYSQL_DB_PASSWORD_SECRET = module.database.password_secret,
      FRONTEND_VERSION_TAG     = "green",
      DATA_INIT_BUCKET_NAME    = var.data_init_bucket_name,
      DATA_INIT_FILE_NAME      = var.data_init_archive_file_name,
    }
  )
  tags = [
    "service",
  ]
  named_ports       = local.named_ports
  health_check_port = lookup(local.named_ports_map, "server")["port"]
  labels            = var.labels
}

module "load_balancer_backend_config_green" {
  depends_on = [
    module.project_services,
  ]
  source = "./modules/load-balancer/backend-config"

  name_suffix = "green-v2"
  backends = [
    {
      group_url = module.vm_container_green.instance_group_url,
    },
  ]
  port_name         = lookup(local.named_ports_map, "client")["name"]
  health_check_port = lookup(local.named_ports_map, "client")["port"]
}

module "load_balancer_frontend_config_green" {
  depends_on = [
    module.project_services,
  ]
  source = "./modules/load-balancer/frontend-config"

  name_suffix       = "green-v2"
  backend_bucket_id = google_compute_backend_bucket.cdn.id
  resource_path     = local.resource_path
  global_address    = module.global_addresses.addresses[1]
  weighted_backend_services = [
    {
      backend_service_id = module.load_balancer_backend_config_green.backend_service_id
    }
  ]
  labels = var.labels
}

Ce module fonctionne de la mĂȘme maniĂšre que le prĂ©cĂ©dent, mais il est spĂ©cifiquement destinĂ© Ă  l’environnement green.

Créez ensuite un fichier variables.tf pour définir les variables globales du projet, puis ajoutez-y le contenu suivant :


variable "project_id" {
  description = "Google Cloud project ID."
  type        = string
  validation {
    condition     = var.project_id != ""
    error_message = "Error: project_id is required"
  }
}

variable "region" {
  description = "Google Cloud region where the cloud resource will be created."
  type        = string
  default     = "us-central1"
}

variable "zone" {
  description = "Google cloud zone where the resources will be created."
  type        = string
  default     = "us-central1-a"
}

variable "bucket_location" {
  description = "Bucket location where the uploaded files will be stored."
  type        = string
  default     = "US"

  validation {
    condition     = contains(["ASIA", "EU", "US"], var.bucket_location)
    error_message = "Allowed values for type are \"ASIA\", \"EU\", \"US\"."
  }
}

variable "source_image" {
  description = "The source image used to create the instance."
  type        = string
  default     = "projects/your-project-id/global/images/your-image-name"
}

variable "labels" {
  type        = map(string)
  description = "A map of key/value label pairs to assign to the cloud resources."
  default = {
    app = "cloud-deployment-gce-java"
  }
}

variable "data_init_bucket_name" {
  description = "Name of the Cloud Storage bucket that store the archive file for initialization."
  type        = string
  default     = "jss-resources"
}

variable "data_init_archive_file_name" {
  description = "Name of the archive file that contains the initialization data."
  type        = string
  default     = "initialization.tar.gz"
}

n’obliez pas de remplacer la variable source_image par la source de votre image packer .

Créez un fichier provider.tf et ajoutez-y le contenu suivant :

terraform {
  required_version = ">= 0.13"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.57"
    }
  }
}

provider "google" {
  project = var.project_id
  region  = var.region
}

Créez maintenant notre fichier principal main.tf et ajoutez-y le contenu suivant :


module "project_services" {
  source  = "terraform-google-modules/project-factory/google//modules/project_services"
  version = "~> 15.0"

  project_id                  = var.project_id
  disable_services_on_destroy = false
  activate_apis = [
    "compute.googleapis.com",
    "iam.googleapis.com",
    "cloudresourcemanager.googleapis.com",
    "sqladmin.googleapis.com",
    "secretmanager.googleapis.com",
    "servicenetworking.googleapis.com",
  ]
}

data "google_project" "project" {
  depends_on = [
    module.project_services
  ]

  project_id = var.project_id
}

locals {
  resource_path = "resource"
  named_ports_map = {
    "client" = {
      name = "lds-client-port"
      port = 80
    },
    "server" = {
      name = "lds-server-port"
      port = 8000
    },
  }
  named_ports = [
    for key, value in local.named_ports_map : {
      name = value.name
      port = value.port
    }
  ]
}

resource "google_service_account" "cloud_deployment" {
  depends_on = [
    module.project_services,
  ]

  account_id = "cloud-deployment-gce-java"
}

resource "google_project_iam_member" "cloud_deployment" {
  for_each = toset([
    "roles/storage.objectAdmin",
    "roles/cloudsql.editor",
    "roles/logging.logWriter",
    "roles/resourcemanager.projectIamAdmin",
    "roles/secretmanager.secretAccessor",
    "roles/secretmanager.admin",
    "roles/servicenetworking.networkAdmin",
  ])

  project = data.google_project.project.project_id
  role    = each.key
  member  = "serviceAccount:${google_service_account.cloud_deployment.email}"
}

module "storage" {
  source = "./modules/storage"

  project_id = data.google_project.project.project_id
  location   = var.bucket_location
  labels     = var.labels
  name       = "cloud-deployment-gce-resource-${data.google_project.project.number}"
}

module "networking" {
  source = "./modules/networking"

  project_id = data.google_project.project.project_id
  region     = var.region
  health_check_allow_ports = [
    for key, value in local.named_ports_map : value.port
  ]
}

module "database" {
  depends_on = [
    module.project_services,
  ]
  source = "./modules/database"

  project_id            = data.google_project.project.project_id
  region                = var.region
  zone                  = var.zone
  vpc_network_self_link = module.networking.vpc_network_self_link
  availability_type     = "ZONAL"
  service_account       = google_service_account.cloud_deployment.email
}

resource "google_compute_backend_bucket" "cdn" {
  project     = data.google_project.project.project_id
  name        = "cloud-deployment-gce-java-cdn"
  bucket_name = module.storage.bucket_name
  enable_cdn  = true
  cdn_policy {
    cache_mode        = "CACHE_ALL_STATIC"
    client_ttl        = 3600
    default_ttl       = 3600
    max_ttl           = 86400
    negative_caching  = true
    serve_while_stale = 86400
  }
  custom_response_headers = [
    "X-Cache-ID: {cdn_cache_id}",
    "X-Cache-Hit: {cdn_cache_status}",
    "X-Client-Location: {client_region_subdivision}, {client_city}",
    "X-Client-IP-Address: {client_ip_address}"
  ]
}

resource "google_compute_firewall" "cloud_deployment_client" {
  name    = "cloud-deployment-gce-java-client"
  network = module.networking.vpc_network_self_link
  allow {
    protocol = "tcp"
    ports = [
      lookup(local.named_ports_map, "client")["port"],
    ]
  }
  target_tags   = ["service"]
  source_ranges = module.global_addresses.addresses
}

resource "google_compute_firewall" "cloud_deployment_ssh" {
  name    = "cloud-deployment-gce-java-ssh"
  network = module.networking.vpc_network_self_link
  allow {
    protocol = "tcp"
    ports = [
      22,
    ]
  }
  # IAP TCP forwarding IP range.
  source_ranges = [
    "35.235.240.0/20",
  ]
}

module "global_addresses" {
  depends_on = [
    module.project_services
  ]
  source  = "terraform-google-modules/address/google"
  version = "3.2.0"

  project_id   = data.google_project.project.project_id
  region       = var.region
  address_type = "EXTERNAL"
  global       = true
  names = [
    "cloud-deployment-gce-java-blue",
    "cloud-deployment-gce-java-green",
  ]
}

Module project_services : Ce module active divers services Google Cloud nĂ©cessaires au projet, tels que Compute Engine, IAM, Cloud SQL, et Secret Manager. Il dĂ©pend d’un ID de projet passĂ© en variable et gĂšre l’activation des API spĂ©cifiĂ©es, assurant que les services requis sont disponibles pour l’ensemble des opĂ©rations de l’infrastructure.

Data google_project : Cette ressource rĂ©cupĂšre des informations sur le projet spĂ©cifiĂ©, garantissant que toutes les autres ressources et modules peuvent interagir avec le bon projet. Elle dĂ©pend du module project_services pour s’assurer que les services sont dĂ©jĂ  activĂ©s.

Variables locales : Ces variables dĂ©finissent des chemins de ressources et des ports nommĂ©s utilisĂ©s dans d’autres ressources. Par exemple, le port 80 est dĂ©fini pour le client et le port 8000 pour le serveur, facilitant la gestion des configurations des ports dans l’ensemble du projet.

Ressource google_service_account : Ce bloc crĂ©e un compte de service pour le dĂ©ploiement, ce qui permet d’exĂ©cuter des opĂ©rations sur les ressources Google Cloud sous un compte sĂ©curisĂ©, assurant ainsi une gestion appropriĂ©e des autorisations.

Ressource google_project_iam_member : Cette ressource assigne plusieurs rĂŽles IAM au compte de service créé, comme l’accĂšs Ă  Cloud Storage et Ă  Cloud SQL. Cela garantit que le compte de service a les permissions nĂ©cessaires pour interagir avec les ressources dans le projet.

Module storage : Ce module configure un bucket Cloud Storage pour stocker des ressources, utilisant des variables pour dĂ©finir l’emplacement et le nom. Il crĂ©e un espace de stockage pour les donnĂ©es nĂ©cessaires au projet, en intĂ©grant des Ă©tiquettes pour la gestion.

Ressource google_compute_backend_bucket : Cette ressource configure un bucket de backend pour un CDN (Content Delivery Network), optimisant la distribution de contenu statique. Elle dĂ©finit des politiques de mise en cache et des en-tĂȘtes de rĂ©ponse personnalisĂ©s pour gĂ©rer les rĂ©ponses aux requĂȘtes clients.

Ressources google_compute_firewall : Ces ressources dĂ©finissent des rĂšgles de pare-feu pour contrĂŽler le trafic entrant vers les instances. La premiĂšre rĂšgle autorise l’accĂšs au port client, tandis que la seconde permet l’accĂšs SSH, sĂ©curisant ainsi les communications avec les instances de calcul.

Module global_addresses : Ce module crĂ©e des adresses globales externes pour le projet, utilisĂ©es pour les services accessibles Ă  partir d’Internet. Cela permet d’exposer les applications dĂ©ployĂ©es Ă  l’extĂ©rieur du rĂ©seau, facilitant les communications avec les utilisateurs finaux.

✹ Passons maintenant Ă  la crĂ©ation d’un fichier outputs.tf ! Ajoutez-y le contenu suivant pour obtenir des valeurs importantes de notre infrastructure 🌐 :

output "backend_bucket_name" {
  description = "The name of the backend bucket used for Cloud CDN"
  value       = google_compute_backend_bucket.cdn.name
}

output "bucket_name" {
  description = "Bucket name"
  value       = module.storage.bucket_name
}

output "blue_mig_self_link" {
  description = "Self link of the blue version instance group"
  value       = module.vm_container_blue.mig_self_link
}

output "green_mig_self_link" {
  description = "Self link of the green version instance group"
  value       = module.vm_container_green.mig_self_link
}

output "blue_mig_load_balancer_ip" {
  description = "IP address of the blue version instance group"
  value       = "http://${module.load_balancer_frontend_config_blue.lb_external_ip}"
}

output "green_mig_load_balancer_ip" {
  description = "IP address of the green version instance group"
  value       = "http://${module.load_balancer_frontend_config_green.lb_external_ip}"
}

output "user_mig_load_balancer_ip" {
  description = "IP address of the user instance group"
  value       = "http://${module.load_balancer_frontend_config_user.lb_external_ip}"
}

🌟 Maintenant, crĂ©ez un bucket dans GCP ! Ensuite, crĂ©ez un fichier backend.tf et remplacez le nom du bucket par le vĂŽtre .

terraform {
  backend "gcs" {
    bucket = "teckworld-bucket"
    prefix = "terraform/state"
  }
}

🔧 Optionnel : Vous pouvez ajouter un fichier terraform.tfvars pour dĂ©finir les valeurs des variables de configuration. Cela reste facultatif, car chaque variable dans variables.tf a dĂ©jĂ  une valeur par dĂ©faut.

✹ Bravo ! AprĂšs ce long chemin, nous arrivons enfin Ă  la partie cruciale : la crĂ©ation de l’infrastructure ! Sur votre terminal, accĂ©dez au rĂ©pertoire terraform-deployment, puis exĂ©cutez les commandes suivantes pour initialiser votre environnement :

terraform init

🚀 Ensuite, vous pouvez exĂ©cuter un terraform plan pour visualiser les modifications prĂ©vues, ou bien passer directement Ă  l’Ă©tape suivante avec la commande :

terraform apply --auto-aprove

☕ À ce stade, vous pouvez vous accorder une pause cafĂ© ! Votre infrastructure mettra un certain temps Ă  ĂȘtre créée. Une fois que tout sera prĂȘt, vous verrez un message de sortie similaire :

🚀 Et voilĂ  ! Maintenant que tout est en place, rendez-vous sur la console Google Cloud pour explorer et admirer votre nouvelle infrastructure. Profitez de votre travail ! 🎉. voici quelques capture d’ecran du mien :

Groupes d’instances :

Instances de VM:

Equilibreurs de charge :

Cloud CDN :

Blue deployment :

Green deployment :

🌐 Explorez votre infrastructure : Prenez le temps de parcourir chaque ressource et profitez de cette infrastructure que vous avez mise en place avec succĂšs. Une fois votre exploration terminĂ©e, vous pourrez la nettoyer facilement en exĂ©cutant la commande terraform destroy. Cela supprimera l’ensemble des ressources, libĂ©rant ainsi vos ressources cloud. vous aurez la sortie suivante :

Passons maintenant Ă  la derniĂšre Ă©tape. 🚀✹

Etape 4 : Automatisation avec Github action

🎉 FĂ©licitations ! Si vous ĂȘtes arrivĂ© jusqu’ici, bravo pour votre persĂ©vĂ©rance et vos efforts ! Il ne reste plus qu’un dernier coup de pouce pour finaliser votre projet. Donnez votre meilleur, on est presque au bout du chemin ! đŸ’Ș🚀

Création du compte de service pour GitHub :

Pour créer un compte de service pour GitHub afin de faciliter les actions CI/CD, suivez ces étapes :

✹ Je pars du principe que vous ĂȘtes dĂ©jĂ  bien Ă  l’aise avec Google Cloud, donc il y aura trĂšs peu de captures d’écran pour cette Ă©tape. D’autant plus que ces Ă©tapes ont Ă©tĂ© abordĂ©es plus haut

Accédez à Google Cloud Console et allez dans IAM & Admin > Comptes de service.

Créez un compte de service avec un nom descriptif, par exemple « github-ci-cd ».

Attribuez les rĂŽles d’Éditeur et d’Administrateur Compute. Bien que ces rĂŽles soient assez Ă©levĂ©s, ils nous permettront d’Ă©viter des complications Ă  l’avenir.

Générez une clé JSON pour ce compte de service, que vous pourrez utiliser comme secret dans GitHub. suivez ce mini guide

Ajoutez la clĂ© JSON dans GitHub sous Settings > Secrets pour sĂ©curiser vos workflows : Ouvrez le fichier rĂ©cemment tĂ©lĂ©chargĂ© dans un Ă©diteur et copiez soigneusement son contenu. Ensuite, suivez les Ă©tapes dĂ©crites dans cette petite vidĂ©o đŸ“č.

Comme vous l’avez vu dans la vidĂ©o, ce secret doit ĂȘtre nommĂ© GCP_CREDENTIALS. CrĂ©ez ensuite deux autres secrets : GCP_PROJECT_ID, qui contiendra l’ID de votre projet GCP, et GCP_SA_KEY, qui stockera Ă©galement la clĂ© JSON. Ne me demandez pas pourquoi cette redondance existe ! 😂🔑

Création des workflows backend et frontend :

Dans la racine de votre projet, crĂ©ez un dossier .github, puis un sous-dossier workflows. À l’intĂ©rieur de ce dernier, commencez par crĂ©er un fichier backend.yml et ajoutez-y le contenu suivant : 📂✹

name: Backend Tests , Cloud Build and terraform deploy

on:
  push:
    branches:
      - main
    paths:
      - 'api/pom.xml'
      - 'api/src/**'
      - '.github/workflows/backend.yml'
  pull_request:
    branches:
      - main
    paths:
      - 'api/pom.xml'
      - 'api/src/**'
      - '.github/workflows/backend.yml'
env:
  GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
  GCP_CREDENTIALS: ${{ secrets.GCP_CREDENTIALS }}
  GCP_SA_KEY: ${{secrets.GCP_SA_KEY }}


jobs:  
  test:
    runs-on: ubuntu-latest
    environment: Backend
    strategy:
      matrix:
        java-version: ["17"]
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Java ${{ matrix.java-version }}
        uses: actions/setup-java@v4
        with:
          java-version: ${{ matrix.java-version }}
          distribution: 'temurin'
          cache: 'maven'
          cache-dependency-path: 'api/pom.xml'

      - name: Install dependencies
        run: mvn -B install -DskipTests
        working-directory: api

      - name: Run tests
        run: mvn -B test
        working-directory: api

      - name: 'Authenticating to Google Cloud'
        uses: 'google-github-actions/auth@v1'
        with:
          credentials_json: '${{ secrets.GCP_CREDENTIALS }}'

      - name: 'Build docker image with Cloud Build'
        run: |
          cd api
          gcloud builds submit --config cloudbuild.yaml


  packer-build:
    needs: test  
    runs-on: ubuntu-latest
    environment: Backend

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: 'Authenticating to Google Cloud'
        uses: 'google-github-actions/auth@v1'
        with:
          credentials_json: '${{ secrets.GCP_CREDENTIALS }}'

      # Set up Google Cloud SDK
      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v1
        with:
          project_id: ${{ secrets.GCP_PROJECT_ID }}
          service_account_key: ${{ secrets.GCP_SA_KEY }}
          export_default_credentials: true

      # Authenticate Docker with Google Cloud Registry
      - name: Authenticate Docker to Google Container Registry
        run: gcloud auth configure-docker

      # Trigger Cloud Build to run the Packer script
      - name: Run Cloud Build for Packer
        run: |
          cd cloudbuild-packer-image
          gcloud builds submit --config cloudbuild_packer_vm_img.yaml
  terraform-deploy:
    needs: packer-build 
    runs-on: ubuntu-latest
    environment: Backend

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: 'Authenticating to Google Cloud'
        uses: 'google-github-actions/auth@v1'
        with:
          credentials_json: '${{ secrets.GCP_CREDENTIALS }}'

      # Set up Google Cloud SDK
      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v1
        with:
          project_id: ${{ secrets.GCP_PROJECT_ID }}
          service_account_key: ${{ secrets.GCP_SA_KEY }}
          export_default_credentials: true

      
      - name: Authenticate Docker to Google Container Registry
        run: gcloud auth configure-docker

      # run terraform to deploy infrastructure
      - name: deploy with terraform
        run: |
          cd terraform-deployment
          terraform init
          terraform apply -auto-approve -var "project_id=${{ secrets.GCP_PROJECT_ID }}"

          

Nom et Déclencheurs

  • name : SpĂ©cifie le nom du workflow.
  • on : DĂ©finit quand le workflow s’exĂ©cute :
  • push : DĂ©clenche lors des push sur la branche principale, spĂ©cifiquement pour les modifications dans api/pom.xml, api/src/**, et .github/workflows/backend.yml.
  • pull_request : DĂ©clenche pour les demandes de tirage visant la branche principale avec les mĂȘmes chemins.

Variables d’Environnement

  • env : Configure les variables d’environnement Ă  partir des secrets GitHub.

Jobs

test :

  • runs-on : SpĂ©cifie que la tĂąche s’exĂ©cute dans l’environnement Ubuntu le plus rĂ©cent.
  • environment : Nomme l’environnement comme « Backend ».
  • strategy : Utilise une matrice pour dĂ©finir la version de Java (17).
  • steps :
    • Checkout repository : Clone le dĂ©pĂŽt.
    • Set up Java : Configure la version de Java spĂ©cifiĂ©e en utilisant la distribution Temurin et met en cache les dĂ©pendances Maven.
    • Install dependencies : ExĂ©cute Maven pour installer les dĂ©pendances sans exĂ©cuter les tests.
    • Run tests : ExĂ©cute les tests avec Maven.
    • Authenticate to Google Cloud : Authentifie avec Google Cloud en utilisant les informations d’identification stockĂ©es dans les secrets.
    • Build Docker image with Cloud Build : Soumet une demande de construction Ă  Google Cloud Build en utilisant un fichier de configuration spĂ©cifiĂ©.

packer-build:

  • needs : DĂ©pend de l’achĂšvement de la tĂąche de test.
  • steps :
  • Checkout repository : Clone Ă  nouveau le dĂ©pĂŽt.
  • Authenticate to Google Cloud : Étape d’authentification similaire.
  • Set up Cloud SDK : Configure le SDK Google Cloud avec l’ID du projet et la clĂ© du compte de service.
  • Authenticate Docker : Configure Docker pour utiliser le Google Container Registry.
  • Run Cloud Build for Packer : Soumet une demande de construction pour crĂ©er une image VM en utilisant une configuration Packer.

terraform-deploy :

  • needs : DĂ©pend de l’achĂšvement de la tĂąche de construction Packer.
  • steps :
  • Checkout repository : Clone le dĂ©pĂŽt une troisiĂšme fois.
  • Authenticate to Google Cloud : RĂ©pĂšte le processus d’authentification.
  • Set up Cloud SDK : Configure le SDK avec les mĂȘmes paramĂštres.
  • Authenticate Docker : Configure Docker pour le Google Container Registry.
  • Deploy with Terraform : Initialise Terraform et exĂ©cute des commandes pour construire toute infrastructure existante en utilisant l’ID de projet spĂ©cifiĂ©.

CrĂ©ez ensuite un fichier frontend.yml dans le mĂȘme rĂ©pertoire workflows et ajoutez-y le contenu suivant : ✹📁

name: Frontend tests

on:
  push:
    branches:
      - main
    paths:
      - 'frontend/**'
      - '.github/workflows/frontend.yml'
  pull_request:
    branches:
      - main
    paths:
      - 'frontend/**'
      - '.github/workflows/frontend.yml'
env:
  GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
  GCP_CREDENTIALS: ${{ secrets.GCP_CREDENTIALS }}
  GCP_SA_KEY: ${{secrets.GCP_SA_KEY }}

jobs:
  frontend-unit-tests:
    runs-on: ubuntu-latest
    environment: Frontend

    strategy:
      matrix:
        node-version: [16.x]
        # See supported Node.js release schedule at https://nodejs.org/en/about/releases/

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      - name: Cache dependencies
        uses: actions/cache@v4
        with:
          path: src/frontend/node_modules
          key: ${{ runner.os }}-node-modules-${{ hashFiles('frontend/package-lock.json') }}

      - name: Install dependencies
        run: npm ci
        working-directory: frontend

      - name: Execute unit tests
        run: npm test
        working-directory: frontend

  CloudBuild-docker-img:
    needs: frontend-unit-tests # Ensure tests pass before building
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: 'Authenticating to Google Cloud'
        uses: 'google-github-actions/auth@v1'
        with:
          credentials_json: '${{ secrets.GCP_CREDENTIALS }}'

      # Set up Google Cloud SDK
      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v1
        with:
          project_id: ${{ secrets.GCP_PROJECT_ID }}
          service_account_key: ${{ secrets.GCP_SA_KEY }}
          export_default_credentials: true

      # Trigger Cloud Build to run the Packer script
      - name: Build docker image
        run:  gcloud builds submit --config cloudbuild.yaml
        working-directory: frontend
          

     # Job for Google Cloud Build and Packer
  CloudBuild-packer-img:
    needs: CloudBuild-docker-img  # Ensure tests pass before building
    runs-on: ubuntu-latest
    environment: Frontend

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: 'Authenticating to Google Cloud'
        uses: 'google-github-actions/auth@v1'
        with:
          credentials_json: '${{ secrets.GCP_CREDENTIALS }}'

      # Set up Google Cloud SDK
      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v1
        with:
          project_id: ${{ secrets.GCP_PROJECT_ID }}
          service_account_key: ${{ secrets.GCP_SA_KEY }}
          export_default_credentials: true

      # Authenticate Docker with Google Cloud Registry
      - name: Authenticate Docker to Google Container Registry
        run: gcloud auth configure-docker

      # Trigger Cloud Build to run the Packer script
      - name: Run Cloud Build for Packer
        run: |
          gcloud builds submit --config packer-image/cloudbuild_packer_vm_img.yaml


  terraform-deploy:
    needs: CloudBuild-docker-img  # Ensure tests pass before building
    runs-on: ubuntu-latest
    environment: Frontend

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: 'Authenticating to Google Cloud'
        uses: 'google-github-actions/auth@v1'
        with:
          credentials_json: '${{ secrets.GCP_CREDENTIALS }}'

      # Set up Google Cloud SDK
      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v1
        with:
          project_id: ${{ secrets.GCP_PROJECT_ID }}
          service_account_key: ${{ secrets.GCP_SA_KEY }}
          export_default_credentials: true

      # Authenticate Docker with Google Cloud Registry
      - name: Authenticate Docker to Google Container Registry
        run: gcloud auth configure-docker

      # Trigger Cloud Build to run the Packer script
      - name: deploy with terraform
        run: |
          cd terraform-deployments
          terraform init && terraform destroy -auto-approve -var "project_id=${{ secrets.GCP_PROJECT_ID }}"
          

Si vous avez bien compris l’explication du workflow du backend, celui-ci n’a plus de secrets pour vous. Je n’entrerai donc pas dans les dĂ©tails Ă  nouveau.

Dans votre dépÎt github , créez deux environnements : backend et frontend. Voici comment procéder :

Cliquez sur l’onglet « Settings » en haut de la page.

Sélectionnez « Environments » : Dans la barre latérale gauche, trouvez et cliquez sur « Environments ».

Ajoutez un nouvel environnement : Cliquez sur le bouton « New environment ».

Nommez votre environnement : Donnez un nom Ă  votre nouvel environnement (par exemple, backend ou frontend).

Je crois que j’ai omis une Ă©tape cruciale : vous devez crĂ©er un dĂ©pĂŽt Git pour votre projet sur GitHub ! đŸ€Šâ€â™‚ïž Toute cette partie suppose que vous l’avez dĂ©jĂ  fait. Alors, j’espĂšre que vous l’avez fait ! 😅

CrĂ©ez ensuite un fichier .gitignore Ă  la racine de votre projet et ajoutez-y les Ă©lĂ©ments suivants : .terraform, .terraform.lock.hcl, et *.tfvars (si vous avez créé un fichier .tfvars). Assurez-vous que ces fichiers soient exclus du suivi par Git pour garder votre dĂ©pĂŽt propre ! đŸš«đŸ“‚

Maintenant, poussez tout votre code vers votre dĂ©pĂŽt distant et regardez vos pipelines s’exĂ©cuter ! Profitez de ce moment, c’est le rĂ©sultat de tout votre travail ! 🎉🚀

si tout ce passe bien vous aurez une sortie similaire :

Summary :

test job :

Packer-build job :

terraform-deploy job :

Vous pouvez ensuite allez verifier la creation des ressources dans google cloud.

Conclusion

Et voilĂ , nous avons atteint la fin de ce projet ! 🎉 Si vous ĂȘtes arrivĂ© jusqu’ici, fĂ©licitations, vous faites partie des rares qui terminent ce qu’ils commencent. J’espĂšre que ce projet vous a permis d’apprendre de nouvelles compĂ©tences. N’hĂ©sitez pas Ă  laisser un commentaire pour partager vos rĂ©flexions, et restez Ă  l’affĂ»t pour notre prochain projet ensemble ! 🚀

Categorized in:

Google CloudPlatform, Terraform,