Introduction
Qu'allons-nous faire ?
Lors du développement d'une application, il est toujours nécessaire d'avoir des environnements de recette et de production indépendants l'un de l'autre, pour tester de nouvelles features en recette et ensuite être capable de les déployer en production en toute sérénité.
Problématique :
Ces applications nécessitent parfois de faire appel à des services externes, comme des API (Rest // Graphql).
Si nous prenons l'exemple d'un appel vers une API Rest ou GraphQL, l'URL appelée par notre application de recette devra être différente de l'URL appelée par l'application de production.
Solution :
Afin de gérer cette problématique, il est nécessaire d'utiliser ce que l'on appelle des variables d'environnement pour dissocier la configuration de nos deux applications.
Il est assez facile de gérer ces variables d'environnement dans un projet perso, mais qu'en est-il dans un environnement tel que Google Cloud Platform pour des projets professionnels ?
Dans ce CodeLabs, nous allons donc voir comment créer et déployer, via Gitlab CI, une application React sur différents environnements d'exécution (recette // production) dans l'écosystème Google Cloud Platform (App Engine) et comment gérer ses variables d'environnement.
Liens utiles
Créer une application React Obtenir un compte Google Cloud Platform Créer un compte Gitlab En savoir plus à propos de Gitlab CI
Pré-requis
Nous aurons besoin d'une installation de Npm & Nodejs Le code complet du projet est disponible ici mais je vous conseille de le récupérer seulement si besoin.
Creation de l'application React
Pré-requis
Pour commencer, vous devez installer Npm & Nodejs
Création de l'application REACT
À l'heure où je rédige ce CodeLabs, le framework React permet facilement de créer des interfaces utilisateurs interactives.
Nous allons donc créer une application très simple avec React. Si vous désirez plus d'informations sur ce framework, rendez-vous ici.
Pour créer votre application, suivez les instructions suivantes. Dans un dossier react-app, lancez la commande suivante :
npx create-react-app react-app cd react-app npm start
Nous avons utilisé create-react-app qui génère automatiquement les fichiers nécessaires au bon fonctionnement d'une application React et ses dépendances. Nous pouvons consulter maintenant tous ces fichiers à la racine de notre projet.
Ensuite en nous plaçant dans le dossier du projet, nous avons lancer l'application avec la dernière ligne de commande.
En quelques instants, notre application est fonctionnelle dans notre environnement local.
Rendez-vous à cette adresse : http://localhost:3000 pour visualiser le résultat.
Modification en vue du déploiement sur Google Cloud Platform
Notre objectif est de déployer notre application dans l'écosystème Google Cloud Platform et plus précisément via le service App Engine dont nous parlerons un peu plus loin dans ce CodeLabs.
Il est important de savoir que toutes les applications déployées sur App Engine sont servies via le port 8080.
Vous l'aurez sûrement compris, notre application est disponible via le port 3000 par défaut. Nous devons donc préventivement faire en sorte que notre application utilise le port 8080 afin d'éviter des surprises dans un futur proche.
Pour ce faire, nous allons éditer le fichier package.json, qui gère une grande partie de la configuration de notre application.
Nous allons remplacer la ligne suivante :
"start": "react-scripts start"
par :
"start": "PORT=8080 react-scripts start"
Relancez ensuite votre application :
Ctrl + C npm start
Rendez-vous ensuite ici pour vérifier que votre application est disponible sur ce port.
Ce simple changement permettra de forcer l'application React à utiliser le port 8080 et sera utilisé pour servir notre application par le service App Engine.
Utiliser des variables d'environnement
Un des moyens simples de gérer les variables d'environnement pour une application React est d'utiliser un fichier .env dans lequel on stockera toutes les variables relatives à l'environnement.
Pour éviter que personne ne puisse accéder à ce fichier sensible, nous allons ajouter ce dernier dans un fichier .gitignore, qui se trouve à la racine du projet.
Exemple de .gitignore :
# Exemple de .gitignore
# ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
#configuration
.env
# misc
.DS_Store
npm-debug.log*
yarn-debug.log*
yarn-error.log*
Notre application pourra faire référence à la même variable d'environnement (process.env.REACT_APP_API_URL par exemple) pour nos deux environnements d'exécution (toujours recette et production), mais lors de la compilation, cette variable aura une valeur différente selon l'environnement.
Créons maintenant ce fichier .env à la racine du projet. Puis ajoutons une variable :
REACT_APP_API_URL=http://api-url.com
Affichons cette variable dans notre application. Pour ce faire, éditons le fichier App.js de notre application en remplaçant le code par celui-ci :
import React from 'react'; import './App.css'; const apiUrl = process.env.REACT_APP_API_URL; const App = () => apiUrl; export default App;
Avec ces modifications, nous avons supprimé le logo et le texte par défaut de React. Nous avons défini une variable qui récupère la valeur de notre variable d'environnement. Nous demandons ensuite à notre component App d'afficher cette variable sur la home page de notre application.
Après sauvegarde du fichier, relancez ensuite votre application :
Ctrl + C npm start
nous pouvons voir apparaître la valeur de notre variable.
Nous verrons plus tard comment faire pour que cette variable change de valeur selon l'environnement d'exécution.
Notre application est fin prête ! Nous allons pouvoir la déployer sur Google Cloud Platform via le service App Engine. Pour cela, nous allons voir dans la prochaine étape comment créer un projet sur Google Cloud Platform.
Déploiement du projet sur App Engine
Nous allons survoler cette étape assez rapidement, car ce point n'est pas le sujet du CodeLabs mais reste essentiel pour la suite.
Nous utiliserons le service App Engine qui permet de déployer des applications web très facilement. Si vous voulez en savoir sur AppEngine rendez-vous ici : https://cloud.google.com/appengine/ Vous devriez également lire l'article suivant si vous n'avez aucune connaissance sur le service App Engine.
Dans l'ordre, nous allons créer un projet sur GCP, installer le SDK et enfin déployer une application de recette et de production.
Création du projet GCP
Je vous invite tout simplement à lire l'article suivant, il vous permettra de créer un projet GCP : /fr/google-cloud-platform-pour-les-nuls/
Installation du SDK GCP en vue du déploiement
Création d'un compte de service
Avant toute chose, il est nécessaire de créer un compte de service pour utiliser le SDK.
Je vous invite à aller dans l'onglet IAM et administration de la console GCP et de créer un compte de service :
Nom du compte de service :
react-app
Description du compte de service :
react-app
Ensuite il vous demandera de définir les autorisations du compte :
Nous choisirons dans notre cas : Projet >> propriétaire (pour plus de simplicité). Enfin, créez une clé de sécurité au format JSON et enregistrez cette clé sur votre machine à l'extérieur de votre projet. Si vous désirez plus d'informations sur les comptes de services, rendez-vous ici.
Installation du SDK GCP
Je vous invite à suivre les instructions disponibles ici. Une fois l’installation terminée vous devriez pouvoir lancer la commande suivante dans votre terminal :
gcloud auth activate-service-account --key-file chemin/vers/key.json
Ensuite il nous faut configurer votre SDK avec la commande suivante :
gcloud init
Suivez les instructions du prompteur en choisissant le compte de service que vous avez créé et le projet que nous avons créé précédemment. Nous sommes fin prêts à déployer notre application sur AppEngine.
Déploiement sur App Engine
Afin de déployer notre application via le SDK, nous allons utiliser un fichier au format YAML reconnu par App Engine. Nous allons donc ajouter un fichier app.yaml qui permet de configurer votre service App Engine. Vous trouverez la documentation complète ici.
Dans le fichier, nous allons mettre en place la configuration de base pour un environnement node.
#app.yml service: default runtime: nodejs10 instance_class: F1 handlers: - url: / static_files: build/index.html upload: build/index.html - url: /(.*)/ static_files: build/\1/index.html upload: build/(.*)/index.html - url: /static static_dir: build/static - url: /(.*) static_files: build/index.html upload: build/index.html
Le premier service aura toujours pour nom : default. Le paramètre runtime permet de définir l'environnement d'exécution. instance_class définit le type d'instance que l'on va utiliser. Et le paramètre handlers permet de lister les formats d'une URL de notre application React.
Déploiement de l'application de production
La mise en production est maintenant simple, il nous suffit de lancer la commande suivante à la racine du projet :
gcloud app deploy ./app.yml --version version1
L’option –version vous permet de donner un nom à votre version. App Engine permet de gérer différentes versions pour un même service. Ceci peut être utile en cas de rollback ou de tests.
Allons vérifier que notre application est bien déployée. Rendez-vous dans la console Cloud dans l’onglet App Engine, puis dans SERVICES >> VERSIONS. Vous devriez voir la version de votre application default apparaître.
Quand le déploiement sera terminé, nous pourrons accéder à notre front React en cliquant sur le nom de la version.
Déploiement de l'application de production
Pour obtenir une version de recette de notre application, nous allons déployer un second service de la même manière que précédemment. Il nous suffit de créer un second fichier app.recette.yml et d'y ajouter la configuration suivante :
#app.recette.yml service: react-app-recette runtime: nodejs10 instance_class: F1 handlers: - url: / static_files: build/index.html upload: build/index.html - url: /(.*)/ static_files: build/\1/index.html upload: build/(.*)/index.html - url: /static static_dir: build/static - url: /(.*) static_files: build/index.html upload: build/index.html
Le seul changement ici se fait au niveau du nom de notre service. Nous aurons ainsi deux services distincts que nous pourrons déployer indépendamment l'un de l'autre.
Dans la prochaine étape, nous allons maintenant industrialiser ce process en utilisant les fonctionnalités offertes par GitlabCI. Ce qui nous évitera de déployer à la main nos versions.
Déploiement de l'application React (recette et production) via Gitlab CI
Nous allons survoler également cette étape assez rapidement. Pour commencer, je vous invite à lire ceci
Pré-requis
Vous devez posséder un compte gitlab. Créez un projet et un repository dans lequel vous aurez déposé votre code source.
Mise en place de la CI (Continuous integration)
Le service CI/CD va nous permettre de déployer notre application. Pour ce faire, il nous faut d'abord créer un fichier gitlabci.yml à la racine de notre projet et ajouter les instructions suivantes :
image: node:10 cache: paths: - node_modules/ stages: - deploy_recette - deploy_production before_script: - echo "deb http://packages.cloud.google.com/apt cloud-sdk-jessie main" | tee /etc/apt/sources.list.d/google-cloud-sdk.list - curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - - apt-get update && apt-get install -y google-cloud-sdk - echo $DEPLOY_KEY_JSON_PRODUCTION > /tmp/$CI_PIPELINE_ID.json - gcloud auth activate-service-account --key-file /tmp/$CI_PIPELINE_ID.json - npm install after_script: - rm /tmp/$CI_PIPELINE_ID.json deploy_recette: environment: recette script: - npm run build - gcloud app deploy ./app.recette.yml --version=$CI_PIPELINE_ID --promote --stop-previous-version deploy_production: environment: production script: - npm run build - gcloud app deploy ./app.yml --version=$CI_PIPELINE_ID --promote --stop-previous-version
Cet exemple est une version simplifiée, mais elle contient les éléments nécessaires au déploiement de nos deux applications.
Les principaux éléments ici sont : before_script : cette partie nous permet d'installer le SDK GCP nécessaire au déploiement et d'initialiser ce dernier avec notre compte de service créé dans l'étape précédente via la variable d'environnement Gitlab (DEPLOY_KEY_JSON_PRODUCTION).
D'ailleurs, rendons-nous dans notre projet Gitlab, dans l'onglet Settings du repository. Et allons insérer ce compte de service dans CI/CD >> Variables.
Indiquez DEPLOY_KEY_JSON_PRODUCTION dans le champ KEY. Et dans le champ VALUE, ajoutez le contenu de notre fichier key.json. Ceci permettra à notre script de récupérer notre clé secrète sans qu'elle puisse être accessible par des tiers.
Enfin si nous jetons un oeil aux deux parties qui concernent les déploiements, le script va tout d'abord ajouter nos dépendances et ensuite lancer la commande que nous avons lancé à la main dans le step précédent.
Pour rappel, l'option version (--version=$CI_PIPELINE_ID) va utiliser l'ID de la pipeline, et permettra d'avoir des URLs différentes selon les versions.
Une fois ce fichier créé, je vous invite à pusher vos modifications sur votre repository. Gitlab va détecter automatiquement notre fichier de CI et va l'exécuter.
À la fin du script de CI, si tout s'est bien passé, nous pourrons voir le résultat dans votre console GCP et vérifier que de nouvelles versions de nos services sont maintenant fonctionnelles.
Script de récupération des fichiers de configuration sur Google Cloud Storage
Nous avons maintenant deux applications distinctes mais nous utilisons toujours le même fichier d'environnement .env. Souvenez-vous, notre application pourra faire référence à la même variable d'environnement (process.env.REACT_APP_API_URL) pour nos deux applications (recette et production). Ceci permet également de sécuriser l'application et de ne pas divulger les secret keys par exemple.
Nous devons donc créer deux fichiers .env.recette et .env.production et les déposer dans le bucket de notre projet GCP. Dans le fichier .env.recette, vous devez renseigner :
REACT_APP_API_URL=http://api-url-recette.com
Et dans le fichier .env.production :
REACT_APP_API_URL=http://api-url-prod.com
En se rendant dans notre console GCP et dans l'onglet Google Cloud Storage (En savoir plus), déposons dans le bucket id_de_votre_projet.appspot.com nos fichiers d'environnement.
Nous allons maintenant ajouter un script qui permettra lors du déploiement de récupérer le fichier de configuration nécessaire à l'application en fonction de l'environenent (recette ou production).
Vous devez maintenant créer un dossier commands à la racine du projet et ajouter un fichier : loadEnvCloudStorage.js. Ajoutez le code suivant dans votre fichier :
const { Storage } = require('@google-cloud/storage'); const fs = require('fs'); const projectId = 'id_de_votre_projet'; const bucketName = 'id_de_votre_projet.appspot.com'; const keyFile = 'key.json'; const gcloudKey = process.argv[2]; const env = process.argv[3]; async function createKeyFile() { await fs.writeFile(keyFile, gcloudKey, (resp, err) => { if (err) throw err; return resp; }); } // write to a new key file async function getConfigFile() { console.log(`Downloading config .env.${env} from bucket "${bucketName}"`); await createKeyFile(keyFile, gcloudKey); const storage = new Storage({ projectId, keyFilename: keyFile }); const directory = `.env.${env}`; await storage .bucket(bucketName) .file(directory) .download({ destination: '.env' }) .then(() => console.info(`Config .env.${env} file downloaded successfully`)) .catch(error => error); } getConfigFile();
Le script utilise deux librairies :
-
fs, pour la manipulation de fichier
-
@google-cloud/storage pour interagir avec le service Google Cloud Storage
Dans notre fonction, nous allons créer un fichier contenant notre secret key. Ce fichier est nécessaire lors de l'implémentation du service Storage. Ensuite, récupérons le bon fichier pour le sauvegarder dans le code source de l'application en le renommant en .env.
Mise en place du script via Gitlab CI
Il nous suffit maintenant d'ajouter la commande qui exécutera ce script lors de la CI.
Exemple :
npm run start:config '$DEPLOY_KEY_JSON_PRODUCTION' production
Modifier le fichier .gitlab-ci.yml comme suit (version finale) :
image: node:10 cache: paths: - node_modules/ stages: - deploy_recette - deploy_production before_script: - echo "deb http://packages.cloud.google.com/apt cloud-sdk-jessie main" | tee /etc/apt/sources.list.d/google-cloud-sdk.list - curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - - apt-get update && apt-get install -y google-cloud-sdk - echo $DEPLOY_KEY_JSON_PRODUCTION > /tmp/$CI_PIPELINE_ID.json - gcloud auth activate-service-account --key-file /tmp/$CI_PIPELINE_ID.json - npm install after_script: - rm /tmp/$CI_PIPELINE_ID.json deploy_recette: environment: recette script: - npm run build - npm run start:config '$DEPLOY_KEY_JSON_PRODUCTION' recette - gcloud app deploy ./app.recette.yml --version=$CI_PIPELINE_ID --promote --stop-previous-version deploy_production: environment: production script: - npm run build - npm run start:config '$DEPLOY_KEY_JSON_PRODUCTION' production - gcloud app deploy ./app.yml --version=$CI_PIPELINE_ID --promote --stop-previous-version
Comme pour le step précédente, je vous invite à pusher vos modifications sur votre repository.
Gitlab CI devra faire le reste. Nous pouvons observer l'exécution du script dans les logs de la CI.
Vous pouvez ainsi vérifier le résultat en allant sur les deux URLS suivantes : https://id_de_votre_projet-recette.react-app.appspot.com et https://id_de_votre_projet.appspot.com
Si tout s'est bien passé, la valeur affichée doit être différente selon l'environnement !




