Kaherecode

(3-5) From simple nodeJs app to the cloud. DOCKER COMPOSE/SWARM

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:

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:

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.

  1. 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 .
  2. 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

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.

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 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.

First Page after login

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

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:

https://hub.docker.com/_/postgres

https://www.fastify.io/

https://node-postgres.com/

https://reactjs.org/

  1. NodeJs app with fastify

Merci à

Mamadou Yaya DIALLO

Continue de lire