Bonjour/Bonsoir, super content de vous retrouver dans cette aventure 'from Simple NodeJS app to the cloud '. Dans cette partie 3.1, il seras question de Docker Compose
N° 3.1 DOCKER COMPOSE
Prérequis:
- Lire la partie 1 Developper une RESTAPI avec #nodeJS fastify et 2 Containerizationpour bien comprendre le contexte de la série de tutoriels.
- docker (downloads) si vous êtes sur Windows ou mac, choisissez la version desktop qui a
compose
etkubernetes
intégré.
Docker Compose est un outil distinct qui s'installe avec docker. C'est une CLI (command line interface) distincte développée en Python. Docker Compose nous permet de démarrer très facilement plusieurs conteneurs Docker en même temps et de les connecter automatiquement avec une forme de mise en réseau, partage de volume de données etc.
L'objectif principal de docker-compose est de vous permettre de centralisation la gestion de plusieurs microservices. Dans notre cas, comme le montre la photo du cover de ce tutoriel, nous allons ajouter à notre application nodeJS, une DB postgresql et application ReactJSpour le frontend.
Pour le choix, encore une fois vous êtes libre de choisir votre STACK, ça aurait pu être (nodejs-mongodb-angular) ou encore une application plus complexe avec plusieurs microserviceset base de données.
Création du fichier de configuration :
Alors, comme pour docker, docker-compose se base sur un fichier (manifest) de config (**appelé compose file).**Par contre, en lieu et place d'un fichier de type clé/valeur, cette fois-ci nous allons utiliser le yaml.
Le YAMLest un format de représentation de données par sérialisation tout comme le JSONet le XML. C'est l'un des formats les plus utilisés dans le domaine DEVOPScomme on le verra plus loin dans cette série de tutos.
Structure du fichier docker-compose.yml :
version: "3.8"
services:
webapp:
build:
context: ./dir
dockerfile: Dockerfile-alternate
args:
buildno: 1
Explications de quelques mot clés:
- versions: comme son nom l'indique nous permet de choisir la version de compose qu'on cible, ici la version 3.8
- services: permet de donner un nom à notre module d'application ou microservice, c'est un tableau dans lequel on peut définir plusieurs microservices.
- webapp: ici le nom de notre application
- build: permet de définir comment notre module sera lancé
- context: permet d'indiquer le répertoire où se trouve notre fameux fichier Dockerfile
- dockerfile:**optionnel au cas où notre **Dockerfile(ne respecterait pas la convention de naming), celas nous donne ainsi la possibilité d'avoir plusieurs environnements avec leur propres dockerfiles (dev, staging, qa, prod)
- args: permet d'envoyer des arguments au Dockerfile lors du build time.
- il y a plusieurs autres options/mots clé qu'on verra en fonction du besoin. Pour plus d'infos, visitez le site officiel de docker qui traite largement ce sujet ici.
Maintenant qu'on a une id ée de ce qu'est un fichier docker-compose, créons-en un pour notre simple node app. Rendez-vous dans le répertoire principal du projet et exécuter la commande suivante :
$ touch docker-compose.yml
puis ouvrez le fichier avec votre éditeur de choix et ajoutez ce code ci-dessous.
version: '3.7'
services:
node_app:
build:
context: .
ports:
- 8001:3000
Alors j'explique les trois dernières lignes qui ne figuraient pas dans notre dernier exemple.
- context: . étant donné qu'on a scrupuleusement respecté la convention de nommage et de position (à la racine du projet) de notre fichier dockerfile, en mettant juste context: ., docker-compose sera capable d'aller indexer le fichier Dockerfile .
- ports: **c'est un tableau qui nous permet de mapper le port du conteneur à un port de notre machine local, ici on mappe le port **3000 sur lequel écoute notre application nodeJS au port 8001 notre OS local.
C'est bien tout ça, mais notre application ne vas pas démarrer toute seule. Voyons comment d émarrer notre application avec docker-compose CLI:
$ docker-compose build
Resultat:
Building node_app
Step 1/8 : FROM node:12.18.3-alpine3.9
---> 65acba1f58ea
Step 2/8 : MAINTAINER yayamombe090@gmail.com
---> Using cache
---> cf9a2120faa4
....
....
Successfully built 36735a94494a
Successfully tagged tutos_node_app:latest
Sur ces résultats, on vois clairement les différents steps qu' on avait définis dans le Dockerfile
FROM node:12.18.3-alpine3.9
MAINTAINER yayamombe090@gmail.com
WORKDIR /app
COPY package.json .
RUN yarn
COPY . .
EXPOSE 3000
CMD ["yarn", "start"]
Une fois que le build est terminé, nous pouvons à présent lancer notre application
$ docker-compose up -d
Creating network "tutos_default" with the default driver
Creating tutos_node_app_1 ... done
#Vous pouvez également utiliser la commande suivante pour buildrun en même temps:
# docker-compose up --build -d
Maintenant essayons d'envoyer une requête HTTP à l'adresse suivante :
$ curl http://localhost:8001/
StatusCode : 200
StatusDescription : OK
Content : {
"name":"Simple NodeJs App",
"deploy_type":"Cloud Native",
"deploy_on":"compose",
"Author":"mombe090"
}
Bravo !
Vous venez de démarrer votre application avec docker-compose, mais jusque là pas de miracle car docker nous avait permis de faire la même chose. Ca ne va pas tarder, soyez un peu patient, voyons d'abord comment arrêter l'application.
$ docker-compose stop/down/kill
- stoparrêt douce des différents services
- downarrête puis supprime tous le réseaux isolé crée par docker-compose
- kill arrêt brutal de tous les différents services
Ajoutons une BD POSTGRESQL à notre application:
Commençons par l'ajout du service de dans notre docker-compose file
….
postgres_db:
image: postgres:11
ports:
- 5433:5432
restart: always
environment:
POSTGRES_USER: devscom
POSTGRES_PASSWORD: guinee
volumes:
- ./db:/var/lib/postgresql/data
volumes:
- ./db:/var/lib/postgresql/data (permet de persister les données de la BD dans le dossier db qu'on vient de créer à la racine du projet).
Pour tester la connexion à la BD, tout client postgrespeut être utiliser bien sûr, mais dans ce tutoriel, nous allons choisir le légendaire PgAdmin.**Il y a plusieurs façons d'installer pgAdmin, allant du simple exe, dmg, deb en fonction de votre OS aux solutions de conteneurs ou encore du code source à compiler à la main avec des outils comme **make.
Ci-dessous le service qu'il faut ajouter pour la conteneurisation de l'application web/client de pgADMIN
…
pg_admin:
image: dpage/pgadmin4:4
ports:
- 9090:80
restart: on-failure
environment:
PGADMIN_DEFAULT_EMAIL: devscom@gmail.com
PGADMIN_DEFAULT_PASSWORD: 123456
Après les différents ajouts, notre fichier docker-compose ressemblera à ça
version: '3.7'
services:
node_app:
depends_on:
- postgres_db
build:
context: .
restart: always
ports:
- 8001:3000
postgres_db:
image: postgres:11
ports:
- 5433:5432
restart: always
environment:
POSTGRES_USER: devscom
POSTGRES_PASSWORD: guinee
pg_admin:
image: dpage/pgadmin4:4
ports:
- 9090:80
restart: on-failure
environment:
PGADMIN_DEFAULT_EMAIL: devscom@gmail.com
PGADMIN_DEFAULT_PASSWORD: 123456
Enregistrez le fichier et exécuter le commande suivante:
$ docker-compose up -d --build
Cette commande permet de relancer les services existants et d émarrer les nouveaux tout en se rassurant que le build est fait avant.
Creating network "simple-node-app_default" with the default driver
Building node_app
Step 1/8 : FROM node:12.18.3-alpine3.9
---> 65acba1f58ea
…
Done in 11.73s.
Removing intermediate container 64f730a35df9
---> 52ae729dcf1a
…
Pulling pg_admin (dpage/pgadmin4:4)…
4: Pulling from dpage/pgadmin4
cbdbe7a5bc2a: Already exists
26ebcd19a4e3: Pull complete
7221f8962952: Pull complete
d934a4db0b37: Pull complete
025ef52453eb: Pull complete
Digest: sha256:f287c00312226dbe812e3e27fc4b8c1e1e0752e0305516a7346ece1d85cbf9a6
Status: Downloaded newer image for dpage/pgadmin4:4
Creating simple-node-app_pg_admin_1 … done
Creating simple-node-app_postgres_db_1 … done
Creating simple-node-app_node_app_1 … done
Sur la sortie du terminal ci-haut, on voit que docker-compose a bel et bien rajouté nos deux nouveaux services.
- le premier postgres_dba été juste démarré sur la machine hôte, car il y avait déj à une image de postgres 11
- le second, pg_admin l'image du service n'étant pas localement sur la machine, elle a été téléchargée d'abord avant de lancer le service
Maintenant que vous avez votre base de donn ées lanc ée, utilisez l'instance de pgAdmin pour vous connecter. Ouvrez votre navigateur essayer l'URL suivante: http://localhost:9090
PgAdmin4 Login page
Renseignez le user e-mail et le mot de pass par d éfaut que vous avez dans la section environnement de pgAdmin (dans le fichier docker-compose). Une fois que vos carentiels sont accept és , vous devriez voir la page suivante.
Clique-droit sur serveurs puis create et en fin server, puis connexion et renseignez les infos suivantes, le paswordest **:**guinee
Bravo !
Vous devriez avoir accès maintenant à la page d'administration de votre base donn ée postgres 11. Maintenant que vous avez accès a la BD, cr éez une table users avec les noms de colonnes suivants : id (bigInt primary key), name: (character varying) et age (integer).
Actualisons le code de notre application **#nodeJS,**avec les actions suivantes
- Connexion à la DB
- Quelques opérations CRUD
- test
Pour la connexion à la BD, nous allons utiliser node-postgres qui est collection de modules et utilitaires #nodeJSpour postgreSql. Utilisons notre gestionnaire de package yarn pour l'ajout a notre projet.
$ yarn add pg
Créer un fichier query.jsdans le dossier src et coller le code source ci-dessous.
const { Client } = require('pg');
const client = new Client({
user: 'devscom',
host: 'postgres_db',
database: 'devscom',
password: 'guinee',
port: 5432,
});
client.connect();
const getUsers = async (request, response) => {
client.query("SELECT * FROM users ORDER BY id ASC", (err, results) => {
if (err) {
throw err;
}
response.status(200).send(results.rows);
})
};
const getUserById = async (request, response) => {
const id = parseInt(request.params.id);
client.query("SELECT * FROM users WHERE id=$1", [id], (err, results) => {
if (err) {
throw err;
}
response.status(200).send(results.rows[0]);
})
};
const createUser = async (request, response) => {
const {name, age} = request.body;
client.query("INSERT INTO users(name , age) VALUES($1, $2)", [name, age], (err, results) => {
if (err) {
throw err;
}
response.status(200).send(`inserted, id = ${results.id}`);
})
};
const updateUser = async (request, response) => {
const {name, age} = request.body;
const id = parseInt(request.params.id);
client.query("UPDATE users SET name = $1, age = $2 WHERE id = $3", [name, age, id], (err, results) => {
if (err) {
throw err;
}
response.status(200).send(`update, id = ${results.id}`);
})
};
const deleteUser = async (request, response) => {
const id = parseInt(request.params.id);
client.query("DELETE FROM users WHERE id = $1", [id], (err, results) => {
if (err) {
throw err;
}
response.status(200).send(`deleted, id = ${results.id}`);
})
};
module.exports = {
getUsers, getUserById, createUser, updateUser, deleteUser
};
Ensuite mettez à jour le fichier app.js
//Importer fastifer
const fastify = require('fastify')({
logger: true
});
const pgDB = require("./query.js");
//Creation du enpoint rest qui renvoie l'objet json ci-dessous
fastify.get('/', async (request, reply) => {
reply.type('application/json').code(200);
return {"name":"Simple NodeJs App","deploy_type":"Cloud Native","deploy_on":"compose","Author":"mombe090"}
});
fastify.get('/users', pgDB.getUsers);
fastify.get('/users/:id', pgDB.getUserById);
fastify.post('/users', pgDB.createUser);
fastify.put('/users/:id', pgDB.updateUser);
fastify.delete('/users/:id', pgDB.deleteUser);
//Demarrer le serveur fastify.
fastify.listen(3000, '0.0.0.0', (err, address) => {
if (err) throw err;
fastify.log.info(`Server a bien demarrer sur l'adresse suivante ${address}`)
});
Enregistrez le projet et redéployer les services en exécutant la commande suivante :
$ docker-compose up -d --build
Attendez la fin du build et essayez les opérations CRUD avec votre client HTTP (postman, insomnia ou la CLI)
$ http localhost:8001/users
HTTP/1.1 200 OK
Connection: keep-alive
Date: Sun, 06 Dec 2020 23:40:04 GMT
content-length: 2
content-type: application/json; charset=utf-8
[]
$ http POST localhost:8001/users name=Yaya, age=99
HTTP/1.1 200 OK
Connection: keep-alive
Date: Sun, 06 Dec 2020 23:40:34 GMT
content-length: 24
content-type: text/plain; charset=utf-8
inserted
$ http localhost:8001/users
HTTP/1.1 200 OK
Connection: keep-alive
Date: Sun, 06 Dec 2020 23:40:50 GMT
content-length: 36
content-type: application/json; charset=utf-8
[
{
"age": 99,
"id": "2",
"name": "Yaya,"
}
]
C'est bien jolie d'avoir toutes ces requêtes HTTP via la # CLI, quand pensez d'ajouter une application frontend pour le faire pour nous ?
Yes, nous allons choisir # REACTJS pour la partie front. Pour avoir le code source de l'application # REACT, clonez le projet et switcher sur la branche compose, dans le dossier ui se trouve tout le code source (react js avec google material).
$ git clone https://github.com/mombe090/cloudnative-nodeapp.git
$ git checkout compose
$ cd ui && npm i
Rajoutons à présent la partie front dans notre fichier docker-compose.
…
ui:
build:
context: ui
dockerfile: Dockerfile
restart: always
ports:
- 8002:80
Une dernière chose, avant de build le projet avec la docker-compose # CLI, ouvrez le fichier ui/src/service/ApiService.js et remplacez l'adresse ci-dessous par le votre.
import axios from 'axios';
const USER_API_BASE_URL = `http://192.168.1.163:8001/users`;
//remplacez par l'adresse de votre machine pas localhost/127.0.0.1
class ApiService {
fetchUsers() {
console.log(process.env.REACT_APP_EXTERNAL_IP);
return axios.get(USER_API_BASE_URL);
}
fetchUserById(userId) {
return axios.get(USER_API_BASE_URL + '/' + userId);
}
deleteUser(userId) {
return axios.delete(USER_API_BASE_URL + '/' + userId);
}
addUser(user) {
return axios.post(""+USER_API_BASE_URL, user);
}
editUser(user) {
return axios.put(USER_API_BASE_URL + '/' + user.id, user);
}
}
export default new ApiService();
Pour finir ce tutoriel en beauté, revenez sur la racine du projet et exec cette commande
$ docker-compose up -d --build
Sur votre navigateur: http://locahost:8002 et amusez-vous à créer des users, les modifier, supprimer et afficher les logs de docker-compose (docker-compose logs -f )
Bravo, vous avez avec succès réussi a déployer votre applications (microservices) avec docker-compose.
Le tutoriel suivant de la série seras le 3.1 dédié à l'orchestration de containers avec docker stack. (Bientôt)
Refs: