Thématiques principales

Affichage des articles dont le libellé est Docker. Afficher tous les articles
Affichage des articles dont le libellé est Docker. Afficher tous les articles

mardi 28 avril 2020

Network, VM et IaC avec Vagrant

Introduction

Qui n'a jamais eut besoin de faire des tests dans un environnement sain et isolé du poste de développement? Personne je crois en tout cas si ce n'est pas le cas, vous y viendrais un jour, c'est sur car a un moment et si vous utiliser pas Docker, il va falloir confronter vos exe a l'environnement d’exécution de la Prod!

Deux approches s'offrent généralement:
  • bénéficier d'un environnement de test dédié, c'est a dire avoir une machine physique [bare-metal], pré-installé comme en prod sur laquelle il est possible de déployer votre application et exécuter des tests dessus. on se doute que cette approche est coûteuse en matériel et en maintenance (temps de nettoyage/ réinstallation des machine etc...)
  • utiliser des machines virtuelles [machine-virtuelle].

La virtualisation

Cette deuxième approche [machine-virtuelle], que nous allons un peu plus détailler, offre de nombreux avantages car elle virtualise d'une part le matériel, donc pas de ressources physique a gérer mais elle permet de disposer d'environnement iso, réutilisable, ajustable et jetable rapidement. On pourra du coup évoquer bien sur le cas extrême de cette démarche par l'utilisation de Docker [Docker]. Pourtant il est important de ne pas confondre les deux outils et leurs objectifs respectifs. Le container isole un processus, une machine virtuelle isole un environnement. Cette dichotomie est importante car elle pousse a réfléchir a ce que l'on souhaite mettre en œuvre et pas faire un choix dogmatiquement.

Ainsi pour en revenir aux machines virtuelles, leur intérêt est donc par l'abstraction de l’environnement d’exécution de permettre de disposer a la demande de cet environnement dans un état de configuration maîtrisé.

Jusque ici l'utilisation des machines passe généralement par le choix d'un hyperviseur, en gros une couche logicielle permettant d’allouer sur la stack physique de l'OS de la machine des ressources simulées. Cette démarche permet de créer des CPU virtuelle, de la mémoire virtuelle, du stockage virtuel ou même encore du réseau virtuel (nous reviendrons sur cette partie dans un autre article car c'est un sujet en soit).

L’inconvenant de l'utilisation d'un superviseur, c'est qu'il faut faire un choix. Selon le système d'exploitation qui est utiliser, on ne disposera pas des mêmes. De façon général, on notera malgré tout que l'offre est riche: qemu-KVM/libvirt [qemu], [virtualbox], hyper-v [hyper-v] ou encore vmware sont les plus connu. Pourtant et malgré des années d’existences, ces derniers n'offrent toujours pas d'API homogènes rendant leur utilisation au sein d'un même contexte compliqué car il faudra alors prendre en considération les spécificités de chacun d'eux.

Ainsi non seulement, souvent ces machines sont utilisé a la main mais lorsqu'il s'agit de gérer logiciellement le cycle de vie des VM et l'automatiser dans une démarche IaC (infrastructure as a code [IaC]), alors il faudra se limiter. Le quoi? l'IaC? Ce n'est pas important, nous y reviendrons!

Vagrant

Une solution est pourtant possible! Vagrant [vagrant]! Bon ce n'est pas une solution miracle, mais en terme de provisionning de machine virtuel, cet outil permet d'abstraire bon nombre de caracteristique propres aux hyperviseurs (en dehors de son choix propre mais cela reste variabilisable).

Vagrant a surtout pour interet d'etre extremement simple d'emploi pour des actions basiques sur le cycle de vie d'une VM. En gros construire la VM, la lancer, l'arreter, ou encore la detruire se realise en juste quelques commandes. Bien sur qu'il permet d'ee faire plus mais globalement ca sera pour des utilisations en marge de celle utile au quotidien.

Ainsi pour realiser une VM, il va falloir en premier lieu la definir. Pour cela, on va realiser un fichier vagrantfile. Ce fichier s'appuie sur le language Ruby. sans etre un grand expert de Ruby, en fait ce fichier va etre essentiellement descriptif: Il va contenir un bloc "do" permettant de specifier la version vagrant utilisé. Dans ce bloc il va ensuite falloir decrire la VM en specifiant:
  • son image de base qui sera issu d'une bibliotheque d'image hebergé par vagrant [vagrant-images].
  • un nom qui servira de hostname,
  • une config reseau selon 3 modes possibles:
    • en port mapping permettant de router les paquet arrivant sur un port de la machine hote sur un port de la machine virtuelle
    • avec un reseau privé, c'est a dire qu'un reseau virtuel sera construit dans laquelle la machine virtuelle aura une adressse IP. Ce reseau sera accessible alors par la machine hote et seulement celle ci (la machine host disposera alors d'un nouvelle interface avec une IP dans le reseau virtuel)
    • avec un reseau publique, c'est a dire qu'une interface sera creer dans la machine virtuelle mais celle ci sera bridgé sur la machine hote dans le meme reseau que celle ci.
  • un provider c'est a dire l'hyperviseur employé, et on pourra alors preciser le nombre de CPU virtuel et de memoire que l'on souhaite lui allouer.

Pour exemple, voici une allocation de VM possible:


1
2
3
4
5
6
7
8
9
Vagrant.configure("2") do |config|
  config.vm.box = "generic/ubuntu1804"
  config.vm.hostname = "myvmtest"
  config.vm.network "public_network", :dev => "eno2", :mode => 'bridge', mac: "52:54:00:B2:14:8E", use_dhcp_assigned_default_route: true
  config.vm.provider "libvirt" do |vb|
    vb.memory = "1000"
    vb.cpus = "1"
  end
end

Pour lancer cette VM, rien de plus simple:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
$ vagrant up
Bringing machine 'default' up with 'libvirt' provider...
==> default: Checking if box 'generic/ubuntu1804' is up to date...
==> default: Creating image (snapshot of base box volume).
==> default: Creating domain with the following settings...
==> default:  -- Name:              vagrant-template_default
==> default:  -- Domain type:       kvm
==> default:  -- Cpus:              1
==> default: 
==> default:  -- Feature:           acpi
==> default:  -- Feature:           apic
==> default:  -- Feature:           pae
==> default:  -- Memory:            1000M
==> default:  -- Management MAC:    
==> default:  -- Loader:            
==> default:  -- Base box:          generic/ubuntu1804
==> default:  -- Storage pool:      default
==> default:  -- Image:             /var/lib/libvirt/images/vagrant-template_default.img (32G)
==> default:  -- Volume Cache:      default
==> default:  -- Kernel:            
==> default:  -- Initrd:            
==> default:  -- Graphics Type:     vnc
==> default:  -- Graphics Port:     -1
==> default:  -- Graphics IP:       127.0.0.1
==> default:  -- Graphics Password: Not defined
==> default:  -- Video Type:        cirrus
==> default:  -- Video VRAM:        256
==> default:  -- Sound Type:
==> default:  -- Keymap:            en-us
==> default:  -- TPM Path:          
==> default:  -- INPUT:             type=mouse, bus=ps2
==> default: Creating shared folders metadata...
==> default: Starting domain.
==> default: Waiting for domain to get an IP address...
==> default: Waiting for SSH to become available...
    default: 
    default: Vagrant insecure key detected. Vagrant will automatically replace
    default: this with a newly generated keypair for better security.
    default: 
    default: Inserting generated public key within guest...
    default: Removing insecure key from the guest if it's present...
    default: Key inserted! Disconnecting and reconnecting using new SSH key...
==> default: Setting hostname...
==> default: Configuring and enabling network interfaces...

Cela va avoir pour consequence de provisionner la VM les ressources, faire l'allocation de l'IP via le DHCP et de fournir une interface en plus pour la maintenance.

Networking

Aisni, du coté host, on a un certain nombre d'interfaces reseaux "mysterieuse" qui ont ete crée, virbr0, vnet, macvtap0@eno2 on va revenir dessus:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ ip a
1: lo: [...]
2: eno2: <broadcast> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 04:92:26:1d:b2:e8 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.57/24 brd 192.168.0.255 scope global dynamic noprefixroute eno2
       valid_lft 36031sec preferred_lft 36031sec
3: wlo1: [...]
41: virbr0: <broadcast> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 52:54:00:d7:c3:3c brd ff:ff:ff:ff:ff:ff
    inet 192.168.121.1/24 brd 192.168.121.255 scope global virbr0
       valid_lft forever preferred_lft forever
42: virbr0-nic: <broadcast> mtu 1500 qdisc fq_codel master virbr0 state DOWN group default qlen 1000
    link/ether 52:54:00:d7:c3:3c brd ff:ff:ff:ff:ff:ff
71: vnet0: <broadcast> mtu 1500 qdisc fq_codel master virbr0 state UNKNOWN group default qlen 1000
    link/ether fe:54:00:51:d8:29 brd ff:ff:ff:ff:ff:ff
72: macvtap0@eno2: <broadcast> mtu 1500 qdisc fq_codel state UP group default qlen 500
    link/ether 52:54:00:b2:14:8e brd ff:ff:ff:ff:ff:ff

Mais dans la VM, on a que deux interfaces:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ vagrant ssh 
$ ip a
1: lo: [...]
2: eth0: <broadcast> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 52:54:00:51:d8:29 brd ff:ff:ff:ff:ff:ff
    inet 192.168.121.245/24 brd 192.168.121.255 scope global dynamic eth0
       valid_lft 1943sec preferred_lft 1943sec
3: eth1: <broadcast> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 52:54:00:b2:14:8e brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.110/24 brd 192.168.0.255 scope global dynamic eth1
       valid_lft 41543sec preferred_lft 41543sec

Pour expliquer un peu tout cela. Lors de la creation de la VM, vagrant va construire 2 interfaces, l'une pour la maintenance est utilisée lorsque l'on realise "vagrant ssh" (eth0) et l'autre est celle qu'on lui a demandé de creer dans le vagrantfile (eth0).

Du coté du host c'est un peu plus compliqué. On sait que l'interface que nous avons demandé de constuire a vagrant est une interface public mais par defaut, celle qu'il construit pour lui, ou celle de maintenance, est une interface privé.
On a donc deux approches differentes et forcement deux solutions (avant cela, je vous invite a aller relire l'article sur l'adressage [addressage], ca peut aider).

Reseau Privé


Pour les reseau privé, vagrant va constuire un network virtuel. Pour cela il va creer une interface associé present dans la VM (ici vnet0), de meme, il va creer une interface pour le host (ici virbr0-nic). Enfin, vagrant va construire un bridge [bridge] (ici virbr0) ou pont permettant d'alluer une ip au host, dans un plan d'addressage specifique.

C'est cette manipualtion qui va permettre la creation de ce reseau privé entre le host et la machien virtuelle permettant aux deux de communiquer ensemble. On peu le verifier en utilisant les commande suivante:


1
2
3
4
$ brctl show 
bridge name bridge id  STP enabled interfaces
virbr0  8000.525400d7c33c yes  virbr0-nic
       vnet0

La commande brctl n'etant plus officiellement supporté sous certains OS, l'alternative est:


1
2
3
4
5
6
7
8
$ ip link show type bridge_slave
42: virbr0-nic: <broadcast> mtu 1500 qdisc fq_codel master virbr0 state DOWN mode DEFAULT group default qlen 1000
    link/ether 52:54:00:d7:c3:3c brd ff:ff:ff:ff:ff:ff
73: vnet0: <broadcast> mtu 1500 qdisc fq_codel master virbr0 state UNKNOWN mode DEFAULT group default qlen 1000
    link/ether fe:54:00:20:a2:a1 brd ff:ff:ff:ff:ff:ff
$ ip link show type bridge
41: virbr0: <broadcast> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:d7:c3:3c brd ff:ff:ff:ff:ff:ff

Reseau Publique


Concernant l'interface publique, c'est plus simple car il existe deja une interface dans le reseau public, c'est eno2. Du coup au lieu de construire un bridge comme precedement, ici dans le host, vagrant va en construire une autre sorte, le macvtap, entre l'interface reseau de la VM identifié par son adresse mac (qui sera alors transposé sur le macvtap) et l'interface en02 de la machine host.

On peut le voir grace a la commande suivante:


1
2
3
ip link show type macvtap
74: macvtap0@eno2: <broadcast> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 500
    link/ether 52:54:00:b2:14:8e brd ff:ff:ff:ff:ff:ff

L'interet ici contrairement a l'interface public, c'est que l'adresse mac de la VM est visible depuis le reseau publique et donc qu'elle peut etre gerer par un service DHCP.

En resumé, tout cela donne le reseau suivant:



Il y aurait encore beaucoup a dire sur les aspects reseaux (le vlan par exemple, etc..) ou sur vagrant qui par le biais de plugin va permettre le provision automatique du contenu logiciel de la VM ainsi creer (via Ansible par exemple).

Ceci est un autre probleme que nous traiterons dans d'autres articles.

Références

  • [bare-metal] https://www.ionos.fr/digitalguide/serveur/know-how/serveur-bare-metal-definition-et-structure/
  • [machine-virtuelle] https://fr.wikipedia.org/wiki/Machine_virtuelle
  • [Docker] https://www.docker.com/
  • [qemu] https://www.qemu.org/
  • [virtualbox] https://www.virtualbox.org/
  • [hyper-v] https://docs.microsoft.com/fr-fr/virtualization/hyper-v-on-windows/about/
  • [vmware] https://www.vmware.com/fr.html
  • [IaC] https://www.lebigdata.fr/infrastructure-as-code-definition
  • [vagrant] https://www.vagrantup.com
  • [vagrant-images] https://app.vagrantup.com/boxes/search
  • [ip-addr] https://memo-linux.com/ip-la-commande-linux-pour-gerer-son-interface-reseau/
  • [addressage] https://un-est-tout-et-tout-est-un.blogspot.com/2020/04/networking-adressage.html
  • [bridge] https://seravo.fi/2012/virtualized-bridged-networking-with-macvtap

samedi 4 avril 2020

Docker : Swarm

Introduction

Problématique Microservices

Dans le contexte des infrastructures microservices, docker [docker] est la reference! Jusqu’a maintenant nous avions surotut vu son utilisation dans le cadre du processus de developpement voir d’integration, mais nous ne sommes pas allé sur le terrain de la mise en production.

Qu’est ce que je veux dire? Ce que je veux dire, c’est qu’une architecture microservice necessite forcement plusieurs micorservice et c’est pour cela d’ailleur que nous avons vite utilisé [docker-compose] afin de nous faciliter la vie en lancant l’ensemble de ces microservices ensemble, pour ainsi dire "orchestrer".
Pourtant docker-compose n’est pas suffisant, en effet celui ci nous permet de mettre en place une deployement cible de nos microservices mais ces derniers n’evolent alors que dans la machine dans laquelle docker-compose a ete invoqué!
Dans le cas ou nous souhaiterions deployer l’ensemble de nos microservice selon des regles specifique de deploiement, (par exemple les composants front ensemble, les back ensemble etc…​) alors il nous faut d’une part etre capable de deploier dans un ensemble de machine de facon transparente mais en plus de pouvoir facilement administrer ce deploiement!
La solution a cette problematique c’est le sujet de cet article, docker-swarm [docker-swarm]

Architecture

Docker swarm [docker-swarm-concepts] est un outil d’orchestration inclu dans docker engine. Un Swarm est un ensemble machine contenant docker et fonctionnant en mode swarm Danse ce cluster, on trouvera alors un manager (ou n selon la redondance souhaité) et des worker (le manager pouvant aussi cumuler le rôle de worker).




Comme dans docker-compose, on déclare des services correspondant a un etat descriptif de la configuration souhaitée auprès du manager qui va produire des taches de déploiement dans les workers.

Fonctionnalités

Docker Swarm permet:
  • de gérer un cluster de nœud définit dans un réseau de machine potentiellement distribué et heterogene.
  • de fournir une description du déploiement orienté service comme docker compose
  • de scaler les instances pour ajouter de la redondance logique
  • de faire du monitoring et de la réconciliation: en cas de crash, swarm sera capable de détecter les instances éventuellement manquante et se chargera de les redémarrer
  • de faire des mise a jour a chaud pour realiser des modifications sur la configuration de déploiement comme des mise a jour applicative et docker se chargera de réconcilier l’état reel avec l’etat voulu.
  • de loadbalancer la charge des flux sur les n instances (les replicas que nous verrons plus loin)

Creation du cluster swarm

Architecture cible





Dans notre archi, on va jouer avec 1 manager principal redondé une fois et 5 workers (incluant les 2 managers).

On va vouloir faire un server de fichier via http (avec [nginx]) dont les données de configuration sont partagées via un partage [nfs] global au cluster. Par contre dans notre exemple nous considererons que les données a exposer ne sont disponible que sur manager et node1.

Nos instances nginx devront donc se trouver sur l’une ou ces deux workers.
Process

Pour creer le swarm, on va suivre la doc! [create-swarm]

On va sur la machine qui sera le manager et on execute la commande:

1
$docker swarm init --advertise-addr 192.168.0.60

On obtient une reponse de la forme:

1
$docker swarm join --token SWMTKN-1- 192.168.0.60:2377

Cette commande il faudra aller l’exécuter sur chaque machine ou est installé docker-engine. Avec la commande suivante il est possible de verifier que les noeuds ont bien eté ajouté:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
  $ docker info
  Client:
  Debug Mode: false

  Server:
  Containers: 5
    Running: 0
    Paused: 0
    Stopped: 5
  Images: 21
  Server Version: 19.03.6
  [...]
  Swarm: active
    NodeID: tntgi3hb72z9ly8rgir8p1j9p
    Is Manager: true
    ClusterID: wr0n081vcbdyxaabz40y3k3n8
    Managers: 1
    Nodes: 4
    Default Address Pool: 10.0.0.0/8  
    SubnetSize: 24
    Data Path Port: 4789
    [...]

Pour redonder le manager il faut s’ajouter dans cluster comme noeud manager secondaire pour rendre possible l’execution des commandes en local (le poste de dev en fait) et non devoir les executer sur le manger en remote.

Sur le poste de dev, on execute la commande d’ajout d’un noeud classique:

1
$ docker swarm join --token SWMTKN-1-<token-value> 192.168.0.60:2377

Et sur la machine manager on realise une promotion a la machine de dev (dev etant le nom hostname de la machine):

1
$docker node promote dev

Pour obtenir des informations sur les noeuds:

1
2
3
4
5
6
7
  $ docker node ls
  ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
  tntgi3hb72z9ly8rgir8p1j9p *   manager            Ready               Active              Leader              19.03.6
  xbaxr3icsfc08a3liv4dr3v7p     node1              Ready               Active                                  19.03.8
  fyvyl153mrly3jz12xt0bblb3     node2              Ready               Active                                  19.03.8
  vgqfp4qxuguma7u11ghyw847i     node3              Ready               Active                                  19.03.8
  s4h7oyar5tnothrwp0p6ukews *   dev                Ready               Active              Reachable           19.03.5

En preparation et pour respecter la typologie a venir, on va ajouter un label a nos noeuds [add-label]. Nous verons plus tard pourquoi et comment cela va impacter le deploiement.

1
2
$docker node update --label-add http=active manager
$docker node update --label-add http=active node1

Deploiement d’un service dans le swarm

Process

Pour deploier nos service nginx, on va definir un ficheir docker-compose un peu booster et integrant quelques specifications supplementaire [deploy-swarm].

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  tc-ngnix:
    image: nginx:1.17.2-alpine
    volumes:
      - "/media/nfs_storage/tc-nginx/conf:/etc/nginx:ro"
      - "/mnt/raid/data:/usr/share/nginx/html/data:ro"
    ports:
      - 80:80
    deploy:
      replicas: 2
      placement:
        constraints:
          - "node.labels.http==active"

Ce fichier va s’appuyer sur une image nginx evidemement, declarer les points de montage de conf et de data et definir :
  • le nombre de replicas c’est a dire le nombre d’instance souhaité du container
  • la strategie de placement via une contrainte permetant de n’utiliser que les nodes sur lesquels on a mis le label http
Pour deployer le service http-services (son petit nom a nous):

1
$docker stack deploy --compose-file docker-compose.yml http-services

Pour consulter le service

1
2
3
  $ docker stack services http-services
  ID                  NAME                        MODE                REPLICAS            IMAGE                               PORTS
  d0g78u03joll        tc-infra-base_tc-ngnix      replicated          2/2                 nginx:1.17.2-alpine   *:80->80/tcp

Nous dit que nous avons bien deployer nos instances. Pour savoir ou? nous allons demander directement a docker avec un docker ps sur le manager:

1
2
3
  $ docker ps -a
  CONTAINER ID        IMAGE                          COMMAND                  CREATED              STATUS                      PORTS               NAMES
  e7e989c8b5f5        nginx:1.17.2-alpine            "nginx -g 'daemon of…"   About a minute ago   Up About a minute           80/tcp              http-services_tc-ngnix.1.xjww2x4g3ic3fs0h156vxneg0

Il en manque un! mais non il est sur le node1:


1
2
3
  $ docker ps -a
  CONTAINER ID        IMAGE                          COMMAND                  CREATED              STATUS                      PORTS               NAMES
  a1e06407c418        nginx:1.17.2-alpine            "nginx -g 'daemon of…"   About a minute ago   Up About a minute           80/tcp              http-services_tc-ngnix.2.ej3w74okhms3ir3gufbnockz7

Le plus simpa avec ca du coup c’est que notre server est accessible que ce soit:
  • http://node1
  • http://manager
mais aussi en:
  • http://dev
  • http://node2
  • http://node3
Parfait nous voila avec un server nginx up en double instance, loadbalancé et accessible via tous les noeuds du cluster!

Point de vigilance

Je n’entre pas dans le detail mais sur certains points quelques interrogation peuvent etre soulevé, concernant le partage de la conf et des datas a nginx via un point de montage [nfs]. Ce n’est pas une solution ideale mais elle a le merite d’etre rapide et simple a mettre en oeuvre mais attention alors a la securité…​.

Pour aller un peu plus loin…​

Pour voir un exemple similaire avec quelques manip en plus genre la mise a jour a chaud ou le scaling horizontal aller voir cet article [swarm-fun]

Ensuite si vous voulez entrer dans la partie monitoring de vos containeurs et service avec des outils comme Prometheus ou Grafana, vous pouvez consulter [swarm-monitoring]

References

  • [docker-swarm-concepts] https://docs.docker.com/engine/swarm/key-concepts/
  • [docker-swarm] https://docs.docker.com/engine/swarm/
  • [create-swarm] https://docs.docker.com/engine/swarm/swarm-tutorial/create-swarm/
  • [deploy-swarm] https://docs.docker.com/engine/swarm/stack-deploy/
  • [swarm-mode] https://docs.docker.com/engine/swarm/how-swarm-mode-works/services/#replicated-and-global-services
  • [add-label] https://docs.docker.com/engine/reference/commandline/node_update/#add-label-metadata-to-a-node
  • [docker] https://docs.docker.com/
  • [docker-compose] https://docs.docker.com/compose/
  • [nfs] https://doc.ubuntu-fr.org/nfs
  • [nginx] https://www.nginx.com/
  • [swarm-fun] https://dzone.com/articles/fun-with-docker-swarm?edition=334876&utm_source=Daily%20Digest&utm_medium=email&utm_campaign=Daily%20Digest%202017-11-15
  • [swarm-monitoring] https://dzone.com/articles/monitoring-docker-swarm?edition=451233&utm_source=Daily%20Digest&utm_medium=email&utm_campaign=Daily%20Digest%202019-02-11

jeudi 20 février 2020

RabbitMQ : Configuration

Dans l’article précédent nous avions vu rapidement le fonctionnement de RabbitMQ [1]. Nous y avions vu comment le lancer dans un container Docker et de choisir quel type de binding et d’exchange utiliser selon nos besoins.

Par contre nous n’avions pas vu comment configurer de nouveaux exchanges, de nouvelles queues ou définir de nouveaux binding. Remédions à cela!

Pour réaliser une configuration, on peut utiliser l’interface web permettant de “jouer” et faire une config et des tests rapide. Par contre, il est évident que cela manque de reproductibilité. Il faut donc une approche et quelques outils.

mardi 18 février 2020

RabbitMQ

Il y a deux ans nous avions parlé de JMS [1], et des principes et enjeux liés aux échanges de messages entre systèmes logiciels. Aujourd’hui je profite d'être sur un sujet très proche de JMS  pour écrire un petit article ce coup ci sur RabbitMQ [2].

Un message Broker

Qu’est ce que RabbitMQ? Il s’agit d’un message broker [4] qui à l'instar de JMS permet la réception et le dispatch de message au sein d’un système d’information.

De façon logique, on va donc retrouver comme dans JMS une logique de files auxquelles les applications vont devoir s’abonner. La différence dans RabbitMQ est que la gestion des messages n’est pas limitée autour de l’utilisation de files ou de topic mais autour de files configurable [5] selon différentes configurations auxquelles vont être associées des clefs de routages permettant la gestion de la diffusion des messages.

samedi 8 février 2020

Postgres : Cluster Citus

Dans le logiciel, la sauvegarde des données métiers et applicatives est une problématique incontournable. Tôt où tard il faudra stocker et choisir une stratégie pérenne dans le temps afin de garantir que les données ne puissent être perdu tout en garantissant un accès facile et rapide à ces dernières

Pour cela deux techniques peuvent être mise en place, la mise en haute disponibilité [HD] qui permet de garantir que les données sont conservés intégré dans le temps et accessible malgré une panne éventuelle et le load balancing [LB] qui lui va permettre de garantir l’accessibilité dans un temps limité de ces données qu’elle qu’en soit le contexte d’exploitation

vendredi 31 janvier 2020

Docker, par ou commencer

Docker est une technologie que l'on croise de plus en plus aujourd'hui, on en a parler de nombreuses fois dans différents articles de ce blog [1].

Il est évident qu'il est aujourd'hui indispensable de monter en compétence sur le sujet car autant c'est un outil indispensable au déploiement en production mais c'est aussi un outil facilitant la vie du développeur évoluant dans un contexte DevOps [2].

Mais par ou commencer? Bien sur il y a la documentation en ligne [3] mais elle est conséquente et parfois il faut se poser dans un document prenant les choses dans l'ordre!

Du coup je vous propose le livre que j'ai lu a l’époque. Je ne dis pas qu'il s'agit de la référence mais il explique bien les fondamentaux, le socle de docker ainsi que les outils annexes comme docker-compose [4] et docker-swarm [5]. En prime différents exemple sont données permettant de bien se projeter dans l'utilisation de l'outil au quotidien.     

Le voici donc et bonne lecture [6].


Références:

[1] https://un-est-tout-et-tout-est-un.blogspot.com/search/label/Docker
[2] https://un-est-tout-et-tout-est-un.blogspot.com/2019/01/lecture-devops-et-agilite.html
[3] https://docs.docker.com/
[4] https://docs.docker.com/compose/
[5] https://docs.docker.com/swarm/
[6] https://www.amazon.fr/dp/2100789708/ref=cm_sw_em_r_mt_dp_U_bVbnEb2F7NDDC

samedi 23 novembre 2019

Reseau : Iptables et réseaux docker

Maintenant que nous avons vu le principe de réécriture d’adresse [1] nous allons maintenant nous intéresser à la vraie finalité de cette suite d’article sur Iptables: comment docker réalise ses réseaux virtuels?

À coup sûr vous l’aurez compris, Iptables va avoir un rôle. Mais avant d’en arriver à cela, reprenons la construction d’un nouveau réseau virtuel docker.

Par défaut, les réseaux docker sont au nombre de 3 [2]:

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
ff147d84a29e        bridge              bridge              local
8932db1d0751        host                host                local
9a54dab98d45        none                null                local  

Ces trois types de réseau sont gérés par trois drivers spécifiques:
  • le driver null permettant d’isoler complètement les conteneurs qui serait associé à ce réseau en ne lui ne leur associant aucune adresse ip.
  • le drivers host qui permet de partager l’interface réseau de la machine hôte avec les conteneurs associé à ce réseau.
  • le driver bridge qui permet ici de construire une nouvelle interface réseau sur la machine hôte (un bridge entre autre! [3])
C’est ce dernier driver qui va nous intéresser. En effet en construisant un bridge, docker va construire une interface réseau dédiée au réseau construit spécifiquement pour l’ensemble des conteneurs qui seront déployés dans celui ci. Et afin de permettre à ces conteneurs de communiquer avec le monde extérieur, alors le bridge va servir de gateway et ce grâce …. à IpTable!

lundi 19 août 2019

Docker Compose

Nous rebondissons une nouvelle fois! Dans l’article précédent nous avions traité d’un exemple de déploiement de python dans docker [1] . Mais nous avions constaté que nous devions spécifier dans nos images des informations concernant le contexte d'exécution, entre autre le hostname de la machine hôte… et franchement c’est un peu dégueu...

Alors jusqu’à maintenant nous avons utilisé docker dans différents exemples ou soit nous voulions expliquer certains aspects de son fonctionnement soit parce que tout simplement c'était plus simple pour  traiter le sujet en question.

À chaque fois nous n’avions eu besoin que d’un container ce qui ne posait pas de problème sauf dans ce dernier exemple!

Du coup on va voir comment s’en défaire et ça va justement être l’occasion de parler un peu de docker-compose [2].

Docker compose est justement la pour faciliter l’orchestration et le lancement de container docker dans un contexte commun cohérent [24].

vendredi 16 août 2019

Python dans docker

Nous avons dans un article précédent [1] proposer un exemple de mise en oeuvre  de deux applications python exploitant ou exposant une interface ReST.

Dans un second article [2],  nous avons également vu qu’il était possible de de packager et livrer des applications python avec divers outils comme setuptools[3] ou pyinstaller [4].

Nous avions cependant laisser une question en suspend concernant une dernière façon de déployer nos appli python. Ainsi, comme vous l’avez deviné, (le titre à tout spoiler…) nous allons dans ce présent article nous intéresser au packaging python dans des container docker.

Alors du coup après avoir lu l’article précédent, vous allez surement demander, mais pourquoi? quel est l'intérêt? et bien pour deux raisons:

  • la première est la devise de docker [5]: build once, run everywhere ce qui permet d’aller bien au delà des plateformes et des machines virtuelles
  • la seconde est que comme à la place de livrer un package ou des sources, on va livrer une image docker, alors peu importe ce que nous mettrons dans le conteneur, cela restera dans le conteneur sous notre responsabilité en minimisant complètement les interventions d’installations d’un utilisateur

La limitation est évidemment que nous ne nous intéresserons qu’aux applications basées sur le réseaux…. (WEB, CLI, etc)

Du coup pour illustrer cet article, je vous propose de reprendre l’exemple déjà traité précédemment [1] avec lequel nous proposons de déployer dans un ou plusieurs conteneur docker les différentes parties applicatives.

Pour cela nous allons donc utiliser une image de base de type alpine dans laquelle nous allons ensuite déployer ce que nous aurons décidé d’etre notre livrable. Ici, le plus simple, et de directement pousser nos sources dans l’image en s’assurant préalablement que python est bien installé ainsi que les dépendances de notre application.

On va donc définir deux Dockerfile en s’inspirant de [6]: Dockerfile_people et Dockerfile_stat respectivement pour chaque applications:


#Dockerfile_people:

FROM alpine:latest
RUN apk add --no-cache python3
RUN pip3 install setuptools Wheel
RUN pip3 install flask
COPY . /opt/
CMD cd /opt;python3 -m people



#Dockerfile_stat:

FROM alpine:latest
RUN apk add --no-cache python3
RUN pip3 install setuptools Wheel
RUN pip3 install flask python_http_client
COPY . /opt/
CMD cd /opt;python3 -m people_stat


Puis on construit nos images:


$ docker build -t people -f ./Dockerfile_people .
$ docker build -t people_stat -f ./Dockerfile_stat .


Et on les lances (en oubliant pas d’exposer leur ports d'écoutes respectifs 5001 et 5002 avec le paramètre -p)


$ docker run -it -p 5001:5001 people
$ docker run -it -p 5002:5002 people_stat


Et la ca marche! euh oui mais pas complètement… autant si on essaye d'accéder à l’interface http://localhost:5001/humans, cela fonctionne mais…. si ont tente http://localhost:5002/recall, la rien ne va plus!

Qu’est ce qui se passe?

C’est simple dans la configuration applicative réalisant les requêtes sur “recall”, nous avons dit à notre application d’aller chercher les informations sur localhost:5001/humans…. mais ce localhost…. c’est lui même! Ce que l’on voulait c'était qu'il aille sur l’autre container! Alors l’erreur semble évidente mais souvent on la fait car on développe sur un poste en local sans se préoccuper de l'aspect distribué du système que l’on est en train de faire… et donc je vous assure tout le monde tombera dans cette erreur.

Donc du coup on fait quoi?

C’est simple on ne met pas localhost mais le hostname de la machine qui expose réellement ce port! C’est à dire dans notre situation, la machine physique. (je vous laisse essayer ).

Alors bien sur généralement une information comme celle ci ne devra pas être mis en dur dans le code, on préférera s’appuyer sur des fichiers de configuration, accessibles depuis un volume ou définis par des variables d’environnement alimenté au moment du lancement du container. L’idée ici n’est pas d’aller jusque la mais déjà de voir ce que permet docker basiquement et certaines des limitations que cela induit, ici entre autre une adhérence résiduelle à la machine physique.

Nous verrons dans un prochain article comme avec docker toujours s’en affranchir.

Références:

[1] https://un-est-tout-et-tout-est-un.blogspot.com/2019/08/rest-avec-python.html
[2] https://un-est-tout-et-tout-est-un.blogspot.com/2019/08/deployer-du-python.html
[3] https://setuptools.readthedocs.io/en/latest/setuptools.html
[4] https://www.pyinstaller.org/
[5] https://uwm.edu/business/event/docker/
[6] https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xix-deployment-on-docker-containers

jeudi 8 août 2019

Docker registry

Quand on utilise docker, on en vient forcément un jour à se dire (et en fait ca arrive même très très vite), je veux publier les images que j’ai construit. Dans l’utilisation que l’on en fait quotidiennement, c’est à dire le cadre privé, l’utilisateur utilisera le docker hub.

Pourtant le docker hub est publique, c’est à dire que toutes les images que vous allez y déployer seront disponibles aux autres utilisateurs de docker.

C’est évidemment une approche qui se tient de favoriser par défaut la diffusion de images et de vouloir en faire profiter tout le monde dans un esprit de libre échange et bien sur il est quand même possible moyennant un loyer de disposer d’un espace privé dans le docker hub afin de rendre nos images accessibles qu’à un ensemble d’utilisateurs spécifiques ayant les permissions adéquates.

Pourtant, tout cela implique donc de laisser la responsabilité à docker hub de gérer l’accessibilité à nos données alors qu’en fait si le besoin de réutilisation des images est local et interne à l’entreprise, alors il devient légitime de vouloir avoir votre propre “docker hub” à la maison.

Alors sans aller sur un docker-hub avec toute l’interface graphique, ce qui importe c’est de disposer du logiciel qui va gérer nos images. Pour cela, comme le docker hub, il faut utiliser l’image registry [1] fourni par docker [2] [3].

Pour l’utiliser rien de compliqué: il suffit de lire la doc:

$ docker pull registry

Ensuite on lance un conteneur instance de cette image en exposant le port de l’API pour y accéder en prévoyant son redémarrage en l'état en montant un volume:


$ docker run -d -p 5000:5000 -v “/media/storage/registry-volume:/var/lib/registry:rslave” registry:2.7.1


Une fois le container lancer, il reste à tester la registry:

$ docker pull ubuntu
$ docker tag ubuntu localhost:5000/ubuntu
$ docker push localhost:5000/ubuntu

et c’est tout? oui pour déployer des images mais, comment les récupérer? et surtout savoir qu’elles existent?

C’est la que l’on va utiliser l’API [4] de la registry. Par exemple pour connaitre le contenu de notre registry on peut exécuter la requête ReST GET suivant:

$ curl -X GET http://localhost:5000/v2/_catalog

{"repositories":["artifactory-oss","hello-world","hw","jenkins","nginx","registry","sonarqube"]}

On obtient alors au format Json l’ensemble des images contenu dans la registry.

Pour en connaître ensuite le détail des tags existant pour une image, il va falloir élaborer une autre requête:


$ curl -X GET http://localhost:5000/v2/hello-world/tags/list

{"name":"hello-world","tags":["latest"]}


Il existe via cette API bien d’autres types de requêtes permettant de manipuler les informations contenues dans la registry et entre autre de les manipuler manuellement comment en faisant les push ou les pull avec la commande docker.

Pour en savoir plus la doc est la référence [4].

Références

[1] https://github.com/docker-library/docs/tree/master/registry
[2] https://docs.docker.com/registry/
[3] https://hub.docker.com/_/registry
[4] https://docs.docker.com/registry/spec/api/

mercredi 27 février 2019

OSGI : Karaf ServiceMix

On est presque à la fin du mois et je n’ai écrit qu’un seul article! Heureusement j’ai un sujet au chaud pour combler le vide.

On avait parlé du framework OSGI Felix [osgi-felix] la dernière fois et nous avions vu dans un précédent article qu’il en existe d’autres packages sous la forme de serveur d’application (comme le serait un Jboss [jboss] ou un Weblogic [weblogic]).

Nous allons justement nous intéresser à deux de ses serveurs:
  • Karaf [karaf]
  • ServiceMix [servicemix]

Bien sur ceux les connaissant diront que c’est grosso merdo la même chose, et effectivement, ServiceMix s’appuie sur Karaf mais en apportant un ensemble de bundle dédié à la communication et aux services, c’est à dire les WebServices et les services de messageries (SMTP, JMS, etc…). Il était donc important de le connaître également.

dimanche 3 février 2019

OSGI : Felix

Nous revoilà avec OSGI mais cette fois ci pour aller dans le concret, plus de blabla, maintenant on fait chauffer la JVM.

Le problème du choix

Bien tout d’abord il nous faut choisir un framework, c’est à dire une implémentation. Nous en avons vu plusieurs dans l’article précédent [osgi-fwk] et il nous faudra faire un choix. À mon sens il y avait plusieurs possibilité:

  • Knoplerfish [knoplerfish] est trop compliqué bien qu’à jour dans l'implémentation des versions OSGI
  • Karaf [karaf] est un peu trop lourd pour ce que nous voulons montrer ici (mais nous ferons un article dessus)
  • Equinox [equinox] est sympas je l’aurais choisi si je ne prévoyais pas de présenter mon framework equinox-loader [eq-loader] qui l’utilise
  • Concierge [concierge] est hors propos, on ne va pas faire de Iot
  • Donc en toute logique, on va essayer l’utilisation de OSGI avec Felix [felix]

Tout d’abord il faut télécharger la version courante: c’est ici [felix-down]

Dockerisation

Pour simplifier son utilisation, nous allons préalablement le dockeriser pour faciliter son exploitation, on construit donc un fichier Dockerfile dans un répertoire (moi je l’ai nommé felix-framework) contenant le processus de construction suivant:

FROM openjdk:8-jre-alpine

RUN wget http://mirror.ibcp.fr/pub/apache//felix/org.apache.felix.main.distribution-6.0.1.tar.gz \
&& tar zxvpf org.apache.felix.main.distribution-6.0.1.tar.gz && rm org.apache.felix.main.distribution-6.0.1.tar.gz

COPY . /felix-framework-6.0.1/
CMD cd /felix-framework-6.0.1; java -jar bin/felix.jar


lundi 14 janvier 2019

Server Web leger : Spark

Petit article pour parler rapidement d’un framework permettant de faire des webservice REST. Il s'appelle Spark [spark-rest] et non il n'a rien à voir avec le framework Big Data Spark [spark-bd] dont on parlera aussi prochainement.

Non ici on parle de Spark [spark-rest], un framework complet mais très simple à utiliser et très rapide à mettre en oeuvre. Il intègre un serveur web permettant l'écoute d’un port de connexion.

Le plus simple est de mettre en oeuvre. Commençons par importer l'artefact maven associé.


<dependency>
    <groupId>com.sparkjava</groupId>
    <artifactId>spark-core</artifactId>
    <version>2.8.0</version>
</dependency>

Ensuite tout passe par l’utilisation de la classe statique “spark.Spark”. Celle ci fourni les moyens d'écoute sur un port (reconfigurable)


Spark.port(port);

Ensuite il reste à définir le mapping entre url et path avec des méthodes à exécuter lors de l'utilisation de ces urls. Par exemple on peut faire à un appel d’une méthode sans paramètres:


Spark.get("/bundles", (request, response) -> this.bundleList(response));

Lorsqu'il y a des paramètres à manipuler alors on utilise la notation :variable, par exemple:


Spark.get("/bundle/:bundleName/:version", (request, response) -> this.bundleInfo(response,request.params(":bundleName"),request.params(":version")));

Il est bien sur possible d’utiliser d’autres primitives http que GET, comme POST, CREATE, DELETE. Je vous renvoie sur la doc pour ces points, n’en ayant pas eut besoin.

Ensuite il ne faut pas oublier que Spark est un serveur web et que du coup on peut aussi le configurer de façon à rendre accessible des ressources statiques. Pour cela on utilise la commande


Spark.staticFiles.externalLocation("/var/www/resources");

Voila j'avais parlé d'un article court, et avec ces quelques fonctions on peut déjà faire pas mal de choses. L'implémentation dont est extrait les exemples est dans le bundle OSGI tc-osgi-bundle-manager du depot Github [github]

Il est par contre à noter que ce code est voué à être déplacé dans un bundle dédié et qu’à terme il ne sera plus accessible à cette url. j’ajouterai une note à ce propos à ce moment là… ou pas… lol

En fait depuis il est dispo dans [gh-misc] et utilisé dans la composition docker associée [doc-spark]

Références

[spark-rest] http://sparkjava.com/
[spark-bd] http://spark.apache.org/
[github] https://github.com/collonville-tom/tc-equinox-loader/tree/master/tc-osgi-bundle-manager
[gh-misc] https://github.com/collonville-tom/tc-misc-osgi
[doc-spark] https://cloud.docker.com/u/collonvtom/repository/docker/collonvtom/tc-osgi-bundle-spark

jeudi 3 janvier 2019

2019 une nouvelle année

Nous voilà en 2019, depuis septembre 2017 que je tiens ce blog, je tiens un rythme de croisière de 4 à 5 articles par mois arrivant pour l’année 2018 à environ 70 articles écrits. Certes la qualité n’est pas toujours au top que ce soit orthographiquement ou en terme de présentation mais sur le fond je pense que les choses posées sont à minima intéressantes.

Du coup on aura vu un peu de docker (surtout dernièrement) du Java, du python, et de l’IA. On se sera aussi intéressé au processus d’apprentissage et de veille technologique avec l’utilisation de AsciiDoc comme moyen de mise en forme (changeant assez avantageusement de latex et Beamer il faut l’avouer).

En 2018 j’avais également changé de boulot quittant Thales pour Capgemini et son client Grdf où il avait fallu faire une montée de version de son SI. Aujourd’hui, la tâche étant en passe de se finaliser et n’ayant pas, au vue des discussions que j’ai pu entretenir avec mes managers de perspectives autres que ce projet décidé de quitter de nouveau ce poste pour aller vers d’autres aventures.

Ainsi en 2019, je rejoins Norsys qui m'ont semblé être une boite responsable, voulant tirer ses collaborateurs vers le haut. Cela me va parfaitement, moi qui ne voulait clairement pas passer mon temps sur les mêmes activités je pense que chez eux j’aurais largement l’opportunité de diversifier mes tâches. Nous verrons cela.

Du coup en terme de blog qu’est ce qui s’annonce pour 2019? En l'état un peu la même chose que 2018, continuer sur l’IA et parfaire les sujets autour du machine learning afin de pouvoir passer au Deep Learning. En parallèle, il sera aussi question de terminer les dossiers autour de OSGI et Docker (aborder les outils compose, machine, swarm). Essayer également de traiter un peu plus finement la question de la modélisation (du MDE, du DDD des ontologies) et parler de Spark le framework BigData en oubliant pas Scala, R, python etc....

Voila, ca fait encore de belles perspectives et beaucoup de travail surtout que j’aimerai de nouveau également aborder la question de la publication scientifique (en tentant de publier en démarrant déjà sur mon travail de thèse et celui sur l’IA de ces derniers mois), les approches de validation (fréquentiste, bayésienne) et parler un peu peut être de scepticisme avec les approches zététiques (on verra que c’est un sujet intéressant surtout en lien avec l’IA)

Enfin voila, en vous souhaitant une bonne année 2019, je vous dis à bientôt pour le prochain article qui ne devrait pas arriver dans très longtemps.

lundi 31 décembre 2018

Docker - Plugin Maven

Un petit écart dans la liste des articles qui devaient être publiés prochainement en abordant le sujet du packaging de nos builds maven dans des images Docker.

On parle d'écart mais en fait pas tant que ca car nous verrons que OSGI se prête bien à une intégration dans docker. Bref ca c’est une autre histoire.

Tout le monde connaît Docker, au moins de nom. Il s’agit d’une solution de conteneurisation [docker-pub], assimilable à de la “virtualisation allégé” (ok le raccourci est rapide) permettant de construire des architectures scalables [scala] de type [microservice] (nous en reparlerons).

L’approche classique pour construire une image docker et d’utiliser la commande “docker build”, si vous êtes un habitué de l’outil, sera de construire un Dockerfile. Je vous laisse pour cela aller consulter l’article [docker-base].

Cependant cette approche en ligne de commande, bien que pratique car universelle n’est pas forcément immédiate dans le cadre d’une industrialisation comme d’un build maven qui aurait pour résultat non pas un artifact java classique [integ-java] mais une image docker prête à l’emploi pour les tests ou la production (vous le voyez poindre le concept de Déploiement Continu?).

Heureusement pour répondre à ce problème il existe deux plugins maven [compar]:
  • spotify/maven-docker-plugin [spotify], et sa nouvelle version [spotify-2] spotify/dokerfile-maven-plugin
  • fabric8io/maven-docker-plugin [fabric8io]

Spotify

Ce plugin maven est celui ci qui collera le plus avec la logique d’utilisation de docker.

En effet, ce plugin s’utilise de façon assez simple soit par configuration xml sur les éléments basiques qu’un dockerfile pourrait contenir où en spécifiant directement un répertoire où se trouvera un dockerfile.

Ainsi ce plugin va permettre de réaliser la phase de build de la commande docker mais aussi en complément la phase de push pour livrer l’image ainsi construite dans un repository comme le docker hub [DocHub].

On pourra regretter que ce plugin ne permet pas de lancer lui même des conteneurs, chose qui aurait pu être intéressant pour la réalisation de phase de tests.

Dans le cadre de cet article, comme dans celui de [compar], nous ne nous attarderons pas sur ce plugin qui finalement fait bien son taff mais ne fournira pas en dehors du cadre de l'intégration à maven beaucoup plus de fonctionnalité que de faire son Dockerfile nous même.

Fabric8io

Le plugin fabric8io sort quand à lui plus des sentier battu en ne s’appuyant pas du tout sur un dockerfile mais sur une approche plus classique dans un environnement maven. En effet, ce plugin propose d’utiliser un descriptif xml du Dockerfile [doc-fabric] ainsi que le mécanisme des assembly [assembly-maven].

A cela, il permet aussi de gérer le cycle de vie des conteneurs en activité (start/stop/kill etc...) ainsi que de publier les images sur un repository (comme le DockerHub [DocHub])

Le plus simple pour présenter le plugin est d’en faire la démonstration.

Ainsi considérons un projet java construit forcément avec un process maven. L’idée d’un build est de généralement construire jar. Dans une approche standard de packaging, le process maven appliquera une phase d’assemblage dans laquelle, le jar, sa conf et ses éventuellement dépendances seront mis dans un targz, un paquet debian où tout autre format de livraison.

Avec docker, le concept permettant d'exécuter un conteneur à partir d’une image quelque soit la plateforme permet d’imaginer une rationalisation du moyen de livraison et d'exécution.

Ainsi, parce que nous utilisons maven, nous allons enrichir un pom parent avec une configuration pluginManagement du plugin fabric8io. L’idée est alors de permettre à tout pom héritant de celui-ci de construire une image docker de packaging et d'exécution.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<profiles>
    <profile>
        <id>DOCKER</id>
        <build>
            <pluginManagement>
                <plugins>
                    <plugin>
                        <groupId>io.fabric8</groupId>
                        <artifactId>docker-maven-plugin</artifactId>
                        <version>0.27.2</version>
                        <configuration>
                            <images>
                                <image>
                                    <name>${docker.login}/${project.artifactId}:%l</name>
                                    <alias>${project.artifactId}</alias>
                                    <build>
                                        <maintainer>Collonville Thomas collonville.thomas@gmail.com</maintainer>
                                        <!-- <dockerFile>Dockerfile</dockerFile> -->
                                        <assembly>
                                            <mode>dir</mode>
                                            <permissions>auto</permissions>
                                            <targetDir>/</targetDir>
                                            <descriptor>../assembly/assembly-docker.xml</descriptor>
                                        </assembly>
                                    </build>
                                </image>
                            </images>
                        </configuration>
                        <executions>
                            <execution>
                                <id>start</id>
                                <phase>verify</phase>
                                <goals>
                                    <goal>build</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </pluginManagement>
        </build>
    </profile>
    <profile>
        <id>PUSH</id>
        <build>
            <pluginManagement>
                <plugins>
                    <plugin>
                        <groupId>io.fabric8</groupId>
                        <artifactId>docker-maven-plugin</artifactId>
                        <version>0.27.2</version>
                        <executions>
                            <execution>
                                <id>start</id>
                                <phase>verify</phase>
                                <goals>
                                    <goal>build</goal>
                                    <goal>push</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </pluginManagement>
        </build>
    </profile>
</profiles>

Ainsi la pré configuration maven du plugin permet de façon générale de donner un nom à l’image et spécifier ses informations générales (nom, mainteneur) dans la section “image”.

Ensuite dans une section build on va préciser le contenu de l’image en utilisant les mécanismes d’assembly maven que l’on devra préciser dans le fichier “assembly/assembly-docker.xml”

Enfin en coherence avec la logique des plugins maven se constituant d’une partie configuration et d’une partie exécution, dans cette dernière, on va préciser quelle commande docker nous allons appliquer.

Ici on notera qu’il y a deux profiles maven, l’un DOCKER de base qui apporte la configuration et la construction de l’image et un second PUSH dans lequel on appliquera la commande “push” de docker (cela nécessitera l’utilisation de la propriété docker.login précisant le compte du DockerHub associé et présent dans le nom de l’image)

L’utilisation de ce pom parent passe ensuite par la surcharge du profile DOCKER dans une section plugin (le profile PUSH étant directement hérité).

La surcharge est rapide bien qu’un peu verbeuse à cause du profile maven. (à noter que dans un process complet et générique on pourra in fine s’en passer)


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<profiles>
    <profile>
        <id>DOCKER</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>io.fabric8</groupId>
                    <artifactId>docker-maven-plugin</artifactId>
                    <configuration>
                        <images>
                            <image>
                                <build>
                                    <from>openjdk:8-jre-alpine</from>
                                    <cmd>/opt/equinox-loader/docker.sh</cmd>
                                    <ports>
                                        <port>${jmx.port}</port>
                                    </ports>
                                </build>
                            </image>
                        </images>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Dans ce pom, il s’agira juste de completer les parties manquantes c’est à dire:

  • l’image source utilisé
  • une commande de lancement
  • les ports et les volumes à déclarer
  • pour finir le contenu de l’assembly, faisant office de commande ADD et COPY d’un Dockerfile

Ici nous utiliserons l’image openjdk:8-jre-alpine comme image source que nous allons configurer le port JMX de la JVM et que nous lancerons l’application via un script sh.

Pour ce qui concerne l’assembly, il s’agit à ce niveau d’une assembly standard utilisant des section “dependencySets” et “fileSets”. Je vous invite à consulter la documentation du plugin [plugass] et/ou de lire l’article [assem].

Voilà avec ce plugin, du coup il est possible de construire des images docker contenant directement notre build prêt à l’emploi nous facilitant d’une part la diffusion du livrable mais aussi l’exploitation dans des tests et surtout l’exploitation en production.

Références

[docker-pub] https://un-est-tout-et-tout-est-un.blogspot.com/2017/10/docker-publier.html
[docker-base] https://un-est-tout-et-tout-est-un.blogspot.com/2017/09/docker-construire-son-image.html
[scala] https://fr.wikipedia.org/wiki/Scalability
[integ-java] https://runnable.com/docker/java/dockerize-your-java-application
[microservice] https://fr.wikipedia.org/wiki/Microservices
[spotify] https://github.com/spotify/docker-maven-plugin
[spotify-2] https://github.com/spotify/dockerfile-maven
[fabric8io] https://github.com/fabric8io/docker-maven-plugin
[doc-fabric] http://dmp.fabric8.io/
[compar] https://dzone.com/articles/meet-the-docker-maven-plugin
[assembly-maven] http://maven.apache.org/plugins/maven-assembly-plugin/
[DocHub] https://hub.docker.com/
[assem] https://un-est-tout-et-tout-est-un.blogspot.com/2018/03/maven-assembly-targz-et-jdeb.html
[plugass] https://maven.apache.org/plugins/maven-assembly-plugin/single-mojo.html

mercredi 28 mars 2018

Architectures types

Lorsque nous nous sommes intéressé au pattern DAO [1], nous avons pu voir que les systèmes logiciels se conçoivent en définissant une architecture répondant au mieux aux besoins clients en usant de différentes stratégies en terme de solutions techniques [2] (les patterns de conceptions, les frameworks, etc…) et de méthodes [3] (IDM, Agilité, Scrum, etc...).

Ainsi, au fils des articles nous avons croisé un certain nombre de patterns ou de framework qui clairement nous simplifie la vie pour produire notre application. Cependant ce genre de concepts se focalisent exclusivement sur le logiciel lui même sans fournir de réponses à des besoins environnementaux qui peuvent être nombreux et contraignants

Ainsi par exemple, que faire lorsque le logiciel doit être capable de gérer un taux de connexion élevé? Comment garantir la monté en charge? Comment s’assurer de l'intégrité des données ? de leur disponibilités ? de leur sauvegarde? Comment garantir qu’il n’y aura pas d'interruption de service? A toutes ces questions, le logiciel seul ne peut y répondre en plus du besoin pour lequel il est produit. En effet, si techniquement on peut imaginer répondre à ces problématiques, cela nécessiterait des développement spécifiques très lourd en plus du métier pour lequel le logiciel doit être fait et comme nous l’avons vu précédemment, toutes les approches purement logiciel sont beaucoup trop auto-centré.

A cette problématique existe heureusement un solution et celle-ci relève en fait du bon sens. Ainsi, si un logiciel doit être conçu et architecturé, son environnement doit l'être aussi! Et les choix d’architectures (interne et externe au logiciel) doivent permettre à ces deux systèmes de cohabiter.

Nous allons donc prendre un peu de recul sur la conception logiciel elle même et nous placer au dessus des patterns de conception que nous avons vu jusque ici pour regarder le logiciel de façon plus global. L’objectif est toujours de considérer le logiciel avec ses interfaces mais aussi de regarder au delà de celles ci pour penser l’architecture dans son ensemble.

Reprenons pour commencer l’exemple très simple de notre article sur le DAO [1]. Nous avions vu l’une des architectures les plus typiques, l’architecture en 3 couches permettant de dissocier les préoccupations métiers des préoccupations de représentations (la couche presentation) des préoccupations de stockage (la couches de données).

Il est évident que cette approche a de nombreux avantages, dans la conception d’une part car les spécificités des besoins des différentes couches seront isolés et confiés aux experts des différents domaines associés à ses couches. Dans la maintenance également car l'indépendance permet de substituer n’importe quelle couche par d’autres implémentations équivalentes ou même d’autres solutions techniques, tant que les interfaces entre couches sont respectées. Enfin ce genre d’approche faisant consensus parmis les développeurs, elle permet la mise en place de standard tant au niveau méthodologique qu’au niveau technique et aujourd’hui, parler par exemple de l’utilisation d’une architecture JEE en 3 couches fait tout de suite référence aux technologies JSP, JSF, Servlet pour la couche présentation, JPA, Hibernate pour les couches données tout en exploitant des EJB pour les couches métiers. Bien sur le modèle en 3 couches a ses limites et souvent il est necessaire de considerer quelques couches supplémentaires en ajoutant une couche de sécurité entre la présentation et le métier et une couche de cache entre le métier et la couche de données. Nous en obtenons alors le modèle en 5 couches [5].


Bien sur on peut en déduire des modèles à n couches mais cette approche a elle même des limites car en augmentant le nombre de couches, on augmente également le temps de traversé des flux de contrôle et de données, occasionnant une chute des performances.

Pour répondre à cette problématique, heureusement il existe également des solutions. Le problème de l’architecture en couche est d'être vertical. Ainsi, une solution est de fournir des fonctionnalités transverses déployées de manière horizontale.

De façon à concevoir des architectures plus horizontale, une approche dite orientée service est possible ou SOA [7]. Celle ci est apparue peu avant 2000 et a largement bénéficié de l'émergence des webservices. Le principe de SOA est simple, il se base sur le principe du producteur consommateur avec comme produit le service.

Pour orchestrer ces communications [8], un annuaire (UDDI) permet la définition d’un référentiel des services accessibles sur un bus de communication servant de middleware (souvent un ESB mais pouvant reposer plus simplement sur le Web si la sécurisation est complètement pris en compte). Enfin tous ces éléments vont reposer sur le standard xml pour faciliter l'interopérabilité de la communication et l'échange des données en restant conforme aux contrats définis pour les services en exploitant des support de communication comme SOAP ou JMS.

L'intérêt de ce genre d’architecture est clairement la mise en place d’un découplage entre les fonctions fourni par les services permettant à des équipes différentes de les développer indépendamment, sur des cibles potentiellement hétérogènes (OS, frameworks, language). de plus cela permet de faire vivre dans le même environnement des applications requièrant les même services ou partiellement. De même faire évoluer ce type de système se résume alors à l’identification des services à exploiter et des nouveaux à déployer.

La réside la difficulté de cette approche. Il faut être capable de dimensionner correctement les services de façon à ce qu’ils soient ni trop simple au risque d'être trop exploité avec des risques notable de perte de performances (à noter qu’un service est censé être unique donc c’est à lui de gérer l’exploitation de la demande et la monté en charge) soit trop complexe et donc trop peu réutilisable. Les architectures orienté service sont donc souvent confronté aux problèmes de performances qui sont directement relié aux capacités réseaux et aux capacités en temps de réponse des plus fragiles des services du système ( qui peuvent en ralentir ou d'encombrer drastiquement le fonctionnement) A noter que ce n’est pas le seul point noir car, si ls SOA favorise la modularité et le reuse des services, cela ne favorise pas son evoluabilité. Toutes transformations dans la chaîne des processus EBP (Enterprise Business Process) utilisant les services entraînera de forte perturbation sur l’ESB.

Pour palier à ces défauts les microservices proposent de descendre encore en granularité dans la décomposition fonctionnelle. Ainsi un microservice va se réduire à ne répondre qu'à une et une unique fonction tout en simplifiant les protocoles de communication par l’utilisation de REST par exemple et en supprimant le principe de bus. L’idée principale est de produire une fournir un plus grand nombre de services (en comparaison à SOA) mais plus simple, élémentaire et donc plus indépendant. Cela va alors faciliter leur maintenance et leur interchangeabilité.
Les microservices ont, au delà des aspects techniques, des avantages même au niveaux processus car grâce à leur indépendance, il est possible de morceler plus le système et donc de le rendre plus facilement testable. Cela conduit même au rapprochement du développeur dans cette phase de test qui aura la possibilité d’isoler complètement le service qu’il doit produire (on le voit arriver la le docker? ). Finalement, grâce à cela, l’utilisation des microservices va déplacer nos problématiques précédentes de performance et de maintenance en une problématique d’interconnexion et de logique de définition de processus métier. Attention alors à ne pas tomber dans des architectures plat de spaghetti…[6]

Jusque là nous nous sommes projetés dans des architectures sans parler de la structure réseau sous-jacente. Bien sur aujourd’hui une application ne se pense plus de façon stand alone sur un serveur, elles se pensent embarquées dans un ou plusieurs serveurs d’applications répartis sur plusieurs serveurs physiques et les approches de types SOA ou Microservices se prêtent parfaitement à cela mais si l’on ne considère pas forcément ce type de solutions, pourquoi malgré tout choisir une architecture distribuée et/ou décentralisé?

En réalité la réponse est simple, et c’est toujours la même, une architecture bien pensé doit permettre d'éviter de construire des monolithes immaintenables (remarque, si ca nous fait évoluer...) et la décomposition est souvent la première des bonnes idées à avoir.

Nous avons vu que ces principes font parti intégrante des architectures précédentes. En fait si les architectures distribuées et/ou décentralisées sont intéressantes c’est parce que non seulement elle poussent à décomposer le problème et favoriser ainsi la réutilisabilité, la maintenabilité (le problème étant localement plus simple du coup) mais elles permettent aussi d'intégrer des solutions techniques répondant à des problèmes plus spécifiques et techniques exigés par le client comme des exigences de disponibilités, de montées en charges, de vitesse de traitement, etc...

source: Architecture fédérée

Par contre, il ne faut pas croire que parce que l’on va considérer une architecture distribuée et ou décentralisé, les problèmes sont terminées car cela peut aussi être une fausse bonne idées dans certains cas. En effet, souvent ce genre d'architecture nécessite d'incorporer au mieux des outils spécifiques permettant la gestion du problème considéré mais selon les cas, il faudra penser à des mécanismes spécifiquement développées par l'équipe de développement. Dans ce genre de cas, on s’engage souvent sur un chemin complexe car ce genre d'architecture ne sont pas simple à gérer soit même. Par exemple, si nous considérons le cas d’une architecture orienté données qu’est l’architecture fédérée, elle a pour objet de permettre la mise en collaboration de différentes bases de données pour donner l’illusion qu’elle n’en forme qu’une tout en leur permettant de rester indépendantes. Si l’idée est pertinente, dans la pratique (dans le cas de fédérations faiblement couplé), elle nécessite de constituer des vue spécifiques dans chacunes des bases, des schémas supplémentaires (privé : base initiale , export : données présenté aux autres bases, import : données reconstitué des autres base) et des mécanismes de reconstruction. Ce genre d’approche est très complexe à mener et doit être refait pour à chaque intégration d’une nouvelle base dans la fédération. Il sera probablement plus simple de réaliser une migration des données vers des bases repensées plus globalement.

Alors bien sur le cas des architectures fédérées n’est pas à généraliser et elle peuvent aussi trouver leur applications dans certains cas bien étudié. Il ne faut pas oblitérer que ce genre d’architecture permettent aussi de gérer des problématiques de redondances ou de répartition de charge pour obtenir des services en haute disponibilité.

Pour conclure cet article déjà assez long, on statuera sur le fait que toutes les architectures décrites jusqu'ici sont plutôt classiques alors aujourd’hui, de nouvelles architectures font leur apparition pour intégrer de nouveaux types de besoins comme l'intégration de service dans le cloud, le traitement de données massives, le besoin de procéder à des prévisions ou des profils de ventes grâce à du Big Data ou du Machine learning. Nous ne rentrerons pas dans le détails de celles ci maintenant mais nous y reviendrons.

Références:

[1] https://un-est-tout-et-tout-est-un.blogspot.fr/2018/03/design-pattern-dao.html
[2] https://un-est-tout-et-tout-est-un.blogspot.fr/2017/10/la-conception-logicielle.html
[3] https://un-est-tout-et-tout-est-un.blogspot.fr/2017/12/la-modelisation.html
[4] https://un-est-tout-et-tout-est-un.blogspot.fr/p/java.html
[5] https://jobprod.com/quelques-principes-darchitecture-dapplis-web-javajee/
[6] https://dzone.com/articles/microservices-vs-soa-is-there-any-difference-at-al
[7] https://www.piloter.org/techno/support/SOA.htm
[8] https://openclassrooms.com/courses/implementez-une-architecture-orientee-services-soa-en-java/mprendrecequecestunesoa