Skip to content

Blogs de Développeurs: Aggrégateur de Blogs d'Informatique sur .NET, Java, PHP, Ruby, Agile, Gestion de Projet

Forum Logiciel

Forum Logiciel : diffusion de connaissance et d’informations sur toutes les activités liées au développement d’applications informatiques en entreprise.

Le blog d'OCTO Technology, cabinet d'architectes en systèmes d'information
Syndiquer le contenu
Le blog d'OCTO Technology, cabinet d'architectes en systèmes d'information
Mis à jour : il y a 9 heures 41 min

Petit-déjeuner : Un éléphant qui se balançait … – jeudi 5 octobre 2017

il y a 13 heures 39 min
Comment mettre en musique les big data et valoriser les données avec de nouveaux services.

Petit déjeuner - un éléphant qui se balançait

BNP Paribas viendra témoigner de sa démarche avec un retour sur la mise en œuvre de ces nouvelles architectures de données.

Un menu copieux pour cette rentrée des petits-déjeuner OCTO avec un focus sur les architectures de données, un témoignage de BNP Paribas, un retour sur la mise en œuvre de ces nouvelles architectures de données et, cerise sur le gâteau, une mise en perspective de la tendance vers des architectures de flux à l’occasion de la publication du livre blanc Digital Studies Vol.02 : La question du temps dans les architectures digitales.

Les données sont là, initialement éclatées dans différents silos applicatifs. Mais maintenant qu’elles commencent à alimenter un Data Lake sous Hadoop, que va-t-on en faire ? Comment les valoriser ? Comment créer de nouveaux services à valeur ajoutée ?

BNP Paribas viendra tĂ©moigner de sa dĂ©marche – initiĂ©e par des expĂ©rimentations autour des data – pour proposer dès Ă  prĂ©sent de nouveaux services (trois projets seront Ă©voquĂ©s).

OCTO vous prĂ©sentera le retour d’expĂ©rience sur la mise en Ĺ“uvre de ces nouvelles architectures de donnĂ©es, incluant les technologies Hadoop, Spark, Cassandra, Solr ainsi que des expĂ©rimentations sur le Machine Learning, tout en soulignant les mĂ©thodes de travail utilisĂ©es avec des Ă©quipes mixtes BNP Paribas / OCTO.

Ce petit-déjeuner sera aussi l’occasion de vous présenter et de vous remettre une version imprimée du livre blanc Digital Studies Vol.02, consacré aux questions d’architecture, notamment aux nouvelles architectures de flux.

 

Cliquez ici pour vous inscrire au petit-déjeuner « Un éléphant qui se balançait »

 

Articles suggested :

  1. Formation Data Science : Paris – Genève
  2. USI dans VOTRE entreprise avec MyUSI
  3. Formations OCTO : Novembre – DĂ©cembre

Catégories: Blog Société

Le demi-cercle (épisode 3 — Communication Breakdown)

ven, 09/15/2017 - 05:47

The conclusion seems inescapable that at least with certain kinds of large programs, the continued adaption, modification, and correction of errors in them, is essentially dependent on a certain kind of knowledge possessed by a group of programmers who are closely and continuously connected with them.
Peter Naur – Programming as Theory Building

Tu regardes cette partie du code, Ă  laquelle il faut ajouter une nouvelle fonctionnalitĂ©, et tu t’entends dire tout haut « Je n’aurais jamais Ă©crit ça de cette façon… Comment peut-on faire un design aussi tordu ? » JĂ©rĂ©mie, l’auteur de ce design se trouve Ă  dix pas.  Il est actuellement en train de travailler sur une autre partie du programme, son casque sur les oreilles. Tu pourrais te lever, l’interrompre dans son travail et lui poser des questions, lui faire un retour Ă  propos de ce design. Mais tu te dis « Ă  quoi bon ? » Ce sera au mieux un dĂ©saccord de plus, une occasion supplĂ©mentaire de mesurer la distance qui vous sĂ©pare sur le plan des connaissances, prĂ©fĂ©rences et expĂ©rience en matière de design logiciel. Tu te rappelles qu’il y a dix jours il est venu te voir pour t’annoncer qu’il avait dĂ©cidĂ© de rĂ©Ă©crire entièrement le module de communication Front / Back.

– Ah bon !?
– Je ne comprends rien Ă  ce qui est fait dans ce module.
– Et donc tu veux le rĂ©Ă©crire pour mieux comprendre ?
– Exactement.
– …
– Ce sera plus simple.
– C’est Ă  tes risques et pĂ©rils.
– C’est OK avec la P.O. J’ai fait une U.S. technique.
– Alors c’est OK pour moi. Je ne suis pas en charge de la coordination dans ce projet.

Tu reviens mentalement sur cette conversation. Tu aurais pu insister un peu. Mentionner, par exemple, que tu es ici depuis dix mois, mais qu’il t’en a fallu six avant de pouvoir coder une demande d’Ă©volution sans avoir Ă  demander de l’aide Ă  quatre personnes diffĂ©rentes. Tu te demandes quelles sont les chances que JĂ©rĂ©mie reste encore un an sur le projet.

Tu marches dans une pièce obscure encombrée d’un fatras indescriptible. Tout ton travail consiste à tenter de décrire ces obstacles que tu rencontres. Ce n’est certes pas une description très fiable, puisque tu vois principalement avec les mains, les genoux, parfois la tête. Décrire correctement demande de la pondération. Du calme. Comment rester calme quand on se prend continuellement en pleine face des objets non identifiés ? Mais tu crois qu’en les décrivant, tu ménages le temps de tes coéquipiers, puisque tu leur prépares la voie. En quelque sorte.

– J’ai constatĂ© que lorsque l’utilisateur passe par le menu Administration pour Ă©tablir des autorisations, le template de service n’est pas le mĂŞme que lorsqu’il saisit l’autorisation au vol pendant la transaction.
– Tu veux parler de la transaction 2L ?
– Pas la 2L, la 2LB.
– Tu m’en diras tant. Bienvenue dans le b… Bon courage!
– OK.

Tu pourrais ouvrir ton cahier à n’importe quelle page et intituler cette page : découverte inattendue numéro …

Tu marches dans une pièce obscure, qui n’est pas la même pièce obscure que celle qu’explore Jérémie en ce moment; ni la même que celle de n’importe lequel de tes coéquipiers en fait. Les objets ne sont pas les mêmes; ils n’ont pas la même forme; ils ne sont pas de même nature. Quelles sont nos chances de produire une application cohérente, intègre, robuste dans ces conditions ? Déployés chacun dans sa pièce (est-ce seulement le même bâtiment ?), chacun sur une partie du système, avec quelques outils de survie en milieu obscur, et une bande passante tellement petite que ça en devient ridicule.

Jérémie ne comprend pas cette histoire de bande passante.  Il affirme qu’on n’a jamais eu une fibre aussi puissante.
– Je te parle de la bande passante entre nous, pas du rĂ©seau. C’est une mĂ©taphore.
– Je pige pas tes mĂ©taphores. Sois plus clair.
– VoilĂ .

Maria, qui est PO/Coordinatrice/Chef de Projet est bien d’accord avec le constat, mais elle dit qu’il faut s’y faire. Ce qui compte, c’est ce que voit le client. Tu te demandes comment le client pourrait voir dans ce système un assemblage cohérent d’éléments conçus pour bien fonctionner ensemble, là où tu ne vois qu’un tas d’objets disparates, un agrégat de trucs disséminés, cristallisés, pour certains fossilisés, et que les vagues technologiques successives ont progressivement accumulé en couches. Tu songes à ce que diraient les clients à propos du temps que demande chaque ticket, s’ils pouvaient voir cette coupe archéologique. Où passe leur budget. Quand on voit le prix des choses. Peut-être bien qu’ils nous diraient :

Pourquoi un champ aussi peu fertile, avec un sous-sol aussi riche ?

« Je ne suis pas en charge de la coordination dans ce projet ». Gna gna gna gna. Mais quelle réponse débile. Bien sûr, que Maria va porter la coordination fonctionnelle et technique de ce projet, assurer sa cohérence interne et externe, et en plus elle va tenir ses délais, économiser, veiller à la bonne humeur sur le plateau, et faire grandir son équipe. C’est certain.

Dix heures trente. Tu passes voir si Audrey et Farid prendraient un café.
– C’est pas ma journĂ©e, dis-tu.
– Qu’est-ce qu’il t’arrive ? demande Audrey
– Panne de communication.

Farid sourit et dit:
– Oh. Quand c’est comme ça, je me cale dans mon siège pour la journĂ©e et je me concentre sur un petit truc bien technique.
– En parlant de communication, dit Audrey, je suis allĂ©e au Dojo de programmation jeudi dernier.
– Comment c’Ă©tait ?
– Pas mal. Un des participants a proposĂ© de faire du Mob Programming.
– Connais pas.
– C’est comme le Dojo habituel, avec rĂ©tro-projecteur et tout, Ă  une diffĂ©rence près.
– Laquelle ?
– La personne qui est au clavier attend que les participants lui indiquent ce qu’elle doit coder.

JĂ©rĂ©mie, qui s’est joint Ă  la conversation, dit en vidant une vieille boule Ă  thĂ© dans la poubelle:
– Argh. Je pourrais attendre longtemps..

Farid s’esclaffe.
– Sans rire, dit Audrey, il y a mĂŞme un principe, pour toi, qui aimes bien les principes..

Tu dis:
– Ça m’intrigue. Dis m’en plus.
– C’est le principe Driver/Navigator. Je ne me souviens plus de la phrase exactement. Mais je l’ai notĂ©e.

Vous retournez Ă  vos postes. Audrey ouvre un cahier qui semble aussi rempli que le tien de notes, de listes, et de gros points d’exclamation et de BdMs.

VoilĂ :
Pour aller de la tĂŞte de quelqu’un jusqu’Ă  l’ordinateur, toute idĂ©e doit passer entre les mains de quelqu’un d’autre.

– Llewellyn Falco.

Et on appelle ça, le modèle Driver-Navigator. Le driver est celui qui est au clavier, les navigateurs sont ceux qui réfléchissent, décident, font le design. Tout le monde travaille sur la même Story, devant un écran.

Hmm.

Driver/Navigator.
Driver/Navigator.

Tu reviens sur ton bout de code. De toute évidence, ça changerait un peu la vie dans les labyrinthes obscurs. Tu imagines le groupe en action.
– Pas par lĂ ; il y a une classe entièrement copiĂ©e-collĂ©e sur la classe TransactionBL. J’ai vu ça la semaine dernière.
– Alors contourne, en renommant la classe et en ne gardant que ce qui compte.
– Comment savoir ce qui compte ?
– Moi je sais ! Va dans le module Livraison. Je peux te montrer.
– TransactionBL ? Vraiment ? Il serait temps qu’on se mette d’accord sur les règles de nommage..

Tu fais mentalement la liste des inconvĂ©nients possibles d’un tel choix d’organisation. Maria aurait immĂ©diatement un problème de budget, et viendrait vous faire remarquer que c’est très bien de communiquer Ă  propos du code, mais qu’il ne faut pas rĂŞver. Elle qui accuse dĂ©jĂ  nos rĂ©trospectives de « cramer du gaz »…

En parlant du loup, voilà Maria qui débarque sur le plateau. Et elle vient te voir directement.

– Comment ça se passe ?
– Comme ci, comme ça. Je ne suis pas sĂ»r de finir ce ticket d’ici vendredi.
– Ça n’arrange pas mes affaires. J’ai besoin de montrer quelque chose qui marche.
– Je pense bien…
– Est-ce que tu crois qu’il faut ajouter des ressources au projet ?
– Je ne ferais pas ça Ă  ta place… Ajouter des ressources Ă  un projet…
– … En retard le met encore plus en retard. Je sais. Brooks. C’est quoi le problème en l’occurrence ? Qu’est-ce qu’il faudrait pour que ce ticket passe comme une lettre Ă  la poste ?
– Hum. Revoir l’ensemble du design du produit, et recommencer sur des bases plus saines, avec un standard d’Ă©quipe ?
– Quand tu seras revenu sur terre, rĂ©ponds Ă  ma question s’il te plaĂ®t, dit Maria en souriant.
– Alors, joker. J’ai des problèmes systĂ©miques.
– C’est-Ă -dire ?
– Comme quand tu te rends Ă  la confĂ©rence sur le rĂ©chauffement climatique, et que tu y vas en voiture, tu vois ?
– Pas du tout.
– Ă€ court terme, je peux rĂ©soudre ce ticket, mais en crĂ©ant des problèmes supplĂ©mentaires…
– RĂ©sous le ticket. Pour le reste, on verra plus tard.

De retour dans le code. Dans le code jusqu’au cou. Tu rĂ©flĂ©chis Ă  ce que vous pouvez faire. Vous ne pouvez pas rĂ©Ă©crire intĂ©gralement le système, en dĂ©cidant et en appliquant des principes agrĂ©Ă©s par toute l’Ă©quipe, cela prendrait des semaines de rĂ©unions laborieuses, houleuses, infructueuses.

Englués, chacun dans son dédale obscur, à se cogner dans des trucs.
Impossible de produire un travail d’Ă©quipe, qui se verrait dans le produit.

Tu notes le principe dans ton cahier.
Pour aller de la tĂŞte de quelqu’un jusqu’Ă  l’ordinateur, toute idĂ©e doit passer entre les mains de quelqu’un d’autre.

Tu notes deux conséquences que tu trouves intéressantes:
– De cette manière, vous n’implĂ©menteriez jamais une idĂ©e sans l’avoir d’abord communiquĂ©e.
– De cette manière, vous ne perdriez pas de temps Ă  discuter sur ce qui ne sera pas implĂ©mentĂ©.

Tu décides que jeudi prochain, tu iras au Dojo de programmation.
Tu retournes Ă  ton Ă©cran. Tu enfiles tes Ă©couteurs et tu remets Led Zep’, trop fort.

Catégories: Blog Société

Meetup PerfUG : Détecter et résoudre vos problèmes de performance applicative avec AppDynamics

mar, 09/12/2017 - 10:45

AppDynamics vous aide Ă  monitorer vos applications mĂ©tier critiques en Ă©tant plus proactif, en rĂ©duisant votre MTTR, en diminuant le nombre d’incidents en production et en augmentant la visibilitĂ© sur le mĂ©tier et ainsi le revenu online.

AppDynamics dĂ©tecte automatiquement et surveille les performances des transactions mĂ©tier de bout en bout, depuis l’utilisateur web ou mobile jusqu’Ă  la base de donnĂ©es, identifie les lignes de code ralentissant les applications et gère intelligemment vos alertes en apprenant le comportement normal de vos mĂ©triques et en alertant quand elles dĂ©vient de la normale.

Venez assister Ă  une dĂ©monstration interactive de la solution et devenez une rock-star de l’APM en quelques heures.

JB Marzolf est consultant avant-vente depuis 3 ans chez AppDynamics et spĂ©cialiste dans les solutions de gestion de l’IT depuis 15 ans.

Inscriptions et informations sur Meetup. Cette session sera suivie d’un pot dans les locaux d’OCTO.

logo

Le PerfUG est un meetup parisien qui a pour objectif d’offrir un lieu d’échanges informels oĂą toutes les personnes intĂ©ressĂ©es par l’optimisation et la performance sont les bienvenues quel que soit leur niveau. Nous sommes convaincus que la performance est une feature implicite et non nĂ©gociable d’une application et pourtant bien souvent oubliĂ©e. Le PerfUG permet d’Ă©changer idĂ©es et pratiques sur ces sujets pour obtenir plus simplement des systèmes performants. Le PerfUG souhaite faciliter la diffusion des derniers outils et des meilleures techniques pour maĂ®triser au plus tĂ´t la performance d’un système informatique.

imgres

Pour en apprendre davantage sur la Performance, retrouvez notre formation OCTO Academy : Performance des applications et du SI Ă  l’ère du digital

Articles suggested :

  1. PerfUG : Programmation Lock-Free, les techniques des pros
  2. Second anniversaire du PerfUG : Nginx et JVM Off-Heap (Architecture NUMA)
  3. Meetup PerfUG : Couchbase Performance

Catégories: Blog Société

Ethereum : trucs et astuces concernant la machine virtuelle

mar, 09/12/2017 - 08:46

Les diffĂ©rentes technologies de Blockchain (Bitcoin, Ethereum, etc.) consistent Ă  produire un consensus, entre de nombreuses parties, sur un Ă©tat stable. Pour Bitcoin, il s’agit de se mettre d’accord sur l’état d’un livre de compte. Ethereum ajoute la notion de consensus sur l’état de contrats dits intelligents (smart contracts).Alors qu’un contrat est usuellement contrĂ´lĂ© par une entitĂ© centralisĂ©e, Ethereum permet lui l’exĂ©cution de contrats numĂ©riques sous le contrĂ´le du rĂ©seau distribuĂ©. Des fonds (en monnaie Ă©lectronique Ether) peuvent ĂŞtre sous la responsabilitĂ© du contrat, et le code dĂ©cide ensuite de leur distribution. Typiquement, le contrat peut servir de notaire, en gardant des fonds jusqu’Ă  ce qu’une condition nĂ©cessaire soit validĂ©e (dĂ©lai, preuve de livraison, etc.).

Pour arriver Ă  un consensus sur l’état de la blockchain, des serveurs (que l’on appelle des mineurs) valident chaque nouvel Ă©tat, constituĂ© d’un nouveau bloc, avant son ajout Ă  la suite des prĂ©cĂ©dents. Cette validation passe par la rĂ©solution d’un problème mathĂ©matique complexe consistant Ă  trouver une valeur particulière Ă  associer au bloc, pour rĂ©pondre Ă  certaines caractĂ©ristiques. Le premier serveur Ă  valider un bloc est habilitĂ© Ă  l’ajouter Ă  la chaĂ®ne de bloc. Il est rĂ©munĂ©rĂ© pour cela.

Les développeurs de contrats utilisent généralement le langage de développement Solidity : le code Solidity est compilé dans un byte-code destiné à la machine virtuelle Ethereum (EVM).

Il existe de nombreuses implémentations de la machine virtuelle Ethereum. Certaines simulent les instructions localement dans un navigateur, avec un simple code JavaScript. D’autres convertissent le code des contrats en code assembleur, afin de réduire le temps d’exécution du programme.

Nous vous proposons d’étudier quelques spécificités de l’EVM et du langage Solidity, afin de pouvoir les utiliser judicieusement. L’objectif de cet article est de parcourir les spécificités de cet environnement d’exécution. Des choix ont été faits par les concepteurs d’Ethereum qui sont inhabituels par rapport aux approches utilisées par d’autres langages de programmation. Par exemple, contrairement à Java, la machine virtuelle n’a pas connaissance de notions complexes comme l’héritage, le polymorphisme, les constructeurs, la gestion mémoire, etc. Nous pensons que connaître ces spécificités permet de proposer des smart contrats plus efficaces. Dans un prochain article, nous mettrons à profit ces particularités pour contourner une limite d’Ethereum : l’immuabilité des contrats.

1 Ethereum VM (EVM)

L’Ethereum Virtual Machine propose des calculs sur des données de 256 bits (32 bytes) à l’aide d’une pile d’exécution. Les valeurs sont déposées sur une pile et les instructions consomment les données au-dessus de la pile pour y déposer le résultat.

Par exemple, la suite d’instructions suivante effectue une addition et place le résultat sur le sommet de la pile.

PUSH1 0x42
PUSH1 0x24
ADD

Il y a également des zones mémoires spéciales. La RAM, dédiée à l’exécution de la transaction, commence à l’adresse zéro et augmente au fur et à mesure des besoins, par pas de 32 bytes. Comme toutes les instructions de la machine virtuelle, cela a un coût (1 gaz par 32 bytes écrit).

Les données persistantes dans le contrat (c’est à dire les attributs) sont mémorisées dans une table d’association utilisant 32 bytes pour la clé et 32 bytes pour la valeur. Les modifications sont sauvegardées dans le prochain bloc de la blockchain, après validation par la communauté.

Un attribut coûte 20.000 gaz lors de sa création (passage à une valeur différente de zéro), puis 5000 gaz lors de chaque modification. Il rapporte 15.000 gaz lors de sa remise à zéro.

Pour invoquer une méthode d’un contrat, il faut envoyer une transaction au réseau Ethereum. Charge à lui de trouver le mineur qui sera en capacité d’exécuter la méthode. Cela se traduit techniquement par l’envoi d’une transaction au réseau, avec la description de l’invocation (contrat, méthode, paramètres).

Dans une transaction, on trouve :

  • msg.sender : l’Ă©metteur du message (current call)
  • msg.gas : Le gaz maximum restant pour la suite du traitement
  • msg.data : Les paramètres de l’invocation du contrat
  • msg.sig : La signature de la mĂ©thode invoquĂ©e, sur 4 bytes. Il s’agit en fait d’un calcul de hash sur le nom de la mĂ©thode, intĂ©grant la liste des paramètres. Cela correspond aux quatre premiers octets de msg.data
  • msg.value : Ă©ventuellement, une quantitĂ© de wei (sous unitĂ© d’éther) pour transfĂ©rer des fonds au contrat

Pour construire un contrat depuis l’extérieur de la blockchain, il faut invoquer le contrat zéro, avec en paramètre, le code et la description du nouveau contrat. Le code du contrat zéro se charge d’ajouter le nouveau contrat dans la chaîne de bloc, d’exécuter le constructeur et de retourner l’adresse du contrat.

Pour construire un contrat depuis un autre contrat, il est possible d’utiliser l’instruction CREATE de l’EVM. Cela est moins coûteux que depuis l’extérieur (100 gaz contre 53.000, cf les spécification de l’EVM).

Des instructions permettent à un contrat d’écrire des logs. Les logs sont des tableaux de bytes, associés à chaque contrat. Cela permet aux API externes de récupérer des informations lorsqu’une transaction est validée par la communauté. C’est le canal privilégié par Solidity pour les communications asynchrones entre les contrats et l’extérieur d’Ethereum. Ecrire dans un log permet de communiquer le résultat d’un appel asynchrone.

Parmi les instructions avancées de la machine virtuelle Ethereum, il y a trois instructions spéciales :

  • call pour invoquer un autre contrat et le modifier
  • callcode pour utiliser le code d’un autre contrat, sur l’état du contrat appelant, en indiquant le contrat actuel comme Ă  l’origine de l’invocation.
  • delegatecall pour utiliser le code d’un autre contrat, sur l’état du contrat appelant, en gardant la valeur du msg.sender (l’identitĂ© de l’invocation de la mĂ©thode d’origine).

callcode n’est pas très utile Ă  cause du changement de msg.sender. Pour ne pas casser la compatibilitĂ©, ils ont ajoutĂ©s une nouvelle instruction : delegatecall.

2 Solidity

Au-dessus de la machine virtuelle, le langage Solidity propose de nombreux concepts de plus haut niveau, pouvant être compilés en instructions de la machine virtuelle. Souvent, le code rédigé en Solidity est très éloigné du code compilé pour l’EVM. Il est parfois important de comprendre la relation entre les deux, pour exploiter au mieux les avantages des deux modèles de programmation.

Petit rappel sur les concepts portés par Solidity :

  • GĂ©nère du code EVM
  • propose une sorte d’hĂ©ritage avec la notion de constructeur
  • propose un modèle particulier de polymorphisme

• Comme les instructions de la EVM fonctionnent en 256 bits, cela n’Ă©conomise pas le coĂ»t de sauvegarde des informations. Pour optimiser cela, Solidity se charge d’effectuer des opĂ©rations de masque binaire, pour pouvoir manipuler quelques bytes Ă  la fois sur chaque zone mĂ©moire de 32 bytes. Par exemple, la mĂ©thode f() suivante :

contract Test {
  byte b;
  function f() {
    b=0xAA;
  }
}

est compilée en cette longue suite d’instructions. Tout cela pour ne modifier QUE le premier octet des 32 bytes.

// b = b AND NOT(0xFF) OR 0xAA
PUSH 0 // Offset de l’attribut
DUP1
SLOAD // Lecture de l’attribut de position 0
PUSH FF // Masque binaire pour un octet
NOT // Inversion du masque (0xFFFFF....FF00)
AND // Masque entre la donnée de l’attribut et le masque
PUSH AA // Valeur Ă  Ă©crire
OR // Or entre 0xAA et l’attribut moins le premier octet
SWAP1
SSTORE // Ecriture de l’attribut

Cela fait beaucoup d’instructions, mais reste bien moins coûteux en éther que de mémoriser chaque octet dans un espace mémoire de 32 bytes.

• Comme il n’y a qu’un seul point d’entrée pour un contrat EVM, Solidity a décidé de consacrer les quatre premiers octets des paramètres de la transaction à l’identification de la méthode à invoquer. La valeur des octets correspondent à un bytes4(sha3("f(uint)") sur le nom de la méthode, complété par le type des différents paramètres.

Le code du contrat commence par un aiguillage semblable à un switch, basé sur la signature de chaque méthode. Si aucune méthode ne correspond, alors la méthode par défaut est utilisée. C’est une méthode particulière, n’ayant pas de nom ni de paramètre. Cette approche permet de gérer le polymorphisme.

switch (msg.sig) {
  case e1c7392a : // function init()
  …
  case 2db12ac4 : // function changeToV2()
  …
  case 82692679 : // function doSomething()
  …
  default : // function ()
  …
}

• La machine virtuelle n’a pas de notion de constructeur. Pour construire une instance, il faut envoyer une transaction vers le contrat zéro. Les datas sont alors considérées comme du code à exécuter.

Solidity génère automatiquement le code du constructeur, qui se charge de fournir le code du contrat à générer sous forme de data.

• Solidity propose une instruction throw, pour signaler une erreur dans un contrat. Comme il n’existe pas d’équivalent dans la machine virtuelle, le code généré invoque un saut vers une adresse mémoire invalide. Cela implique une erreur lors de l’exécution, ce qui est le but recherché. Ne vous étonnez pas alors, de recevoir une erreur de type “invalid jump destination”.

• Solidity propose la notion d’event, qui correspond aux logs de la machine virtuelle. Ils permettent d’informer un code Ă  l’écoute de la chaĂ®ne lorsqu’une mĂ©thode d’un contrat est invoquĂ©e. Des filtres permettent d’attendre l’acquittement d’un traitement particulier dans la blockchain. Concrètement, Solidity utilise toujours la première valeur des logs de l’EVM pour y placer le hash de la signature de l’event. Les autres valeurs correspondent aux paramètres de l’event. Le hash de la signature de l’évènement est utilisĂ© comme nom de topic, pour l’indexation des Ă©vĂ©nements.

• Un dernier point à savoir. Pour maîtriser la mémoire nécessaire à l’invocation des méthodes, l’attribut de clé 0x40 est utilisé par Solidity. La valeur stockée indique la plus haute adresse mémoire en mémoire RAM utilisée par les méthodes du contrat.

Dans un prochain article, nous utiliserons toutes ces particularités pour répondre à la question suivante : comment modifier un contrat immuable ?

Articles suggested :

  1. Introduction Ă  la technologie Blockchain
  2. Une cagnotte dans la blockchain Ethereum
  3. Raiden, une réponse à la confidentialité des échanges sur Ethereum ?

Catégories: Blog Société

La dette technique dans un SI

lun, 09/11/2017 - 14:50

La dette technique peut être définie de plusieurs façons, selon le point de vue : code, logiciel, infrastructure ou le SI en général. Dans cet article, nous allons faire un focus sur la dette technique dans le SI, ses impacts et comment la traiter.

Une définition plus détaillée avec les typologies de dette est bien expliquée dans cet article du blog octo.

La dette technique dans le SI

Dans un SI, la dette technique constitue l’écart entre l’existant et l’état de l’art des composants du SI (code, logiciel, infrastructure…)

Le remboursement de la dette d’un SI constitue l’investissement mis en oeuvre pour permettre au système d’information d’être dans l’état de l’art. Traiter la dette technique d’un SI revient à :

  • Faire Ă©voluer les composants du SI vers une cible plus rĂ©cente: traiter l’obsolescence, mise Ă  niveau des composants, etc.
  • La revue du code incompatible avec les standards et les pratiques de dĂ©veloppement (coĂ»t de non qualitĂ©)
  • Mettre Ă  niveau les Ă©quipes techniques tout le long du cycle de vie du SI
La dette est inĂ©vitable…

Fred Brooks a introduit dans son papier “No Silver Bullet” les notions de complexité essentielle et de complexité accidentelle. La première est celle liée au problème à résoudre et elle ne peut pas être évitée. La seconde n’est pas liée au problème, mais introduite par effet de bord à cause d’une analyse non approfondie du problème : sa conséquence est que le logiciel, et par conséquent le SI, devient plus compliqué à maintenir. Les deux situations engendrent une augmentation de la dette.

Qu’elle soit volontaire ou non, la dette technique augmente avec le temps.

Sur plan organisationnel, la perte de connaissance technique constitue une source de l’augmentation de la complexitĂ©. Lorsqu’on veut faire Ă©voluer un composant, des fois on peut se confronter Ă  une mĂ©connaissance des règles mĂ©tier ou du besoin originel. Sur un plan technique, un composant applicatif doit Ă©voluer, et son Ă©volution est moins rapide que les frameworks, les outils et l’infrastructure qui l’hĂ©berge, et ce, indĂ©pendamment de la qualitĂ© du code et des logiciels qui forment le système d’information.

Ce qui nous conduit au constat suivant : Même un composant applicatif de qualité génère de la dette s’il n’est pas maintenu pour être en tout temps en phase avec les standards du marché et les bonnes pratiques de développement.

Le remboursement de la dette constitue un effort, donc un budget. Si cet effort n’est pas prévu en amont, l’écart augmente et le SI deviendra de plus en plus endetté.

La dette est inévitable.

Pour donner quelques exemples concrets…

Quand on utilise un framework, celui-ci évolue avec le temps, des versions supérieures sortent. Le code en place va générer de la dette qui devra être traitée un jour. Plus le temps passe, plus la dette augmente et sera difficile à rembourser.

Quand on met en place un outil ou framework en version X, le fait que l’outil Ă©volue en version supĂ©rieure X+1, X+2,… et qu’il est, en gĂ©nĂ©ral, impossible de suivre les mises Ă  niveau au fur Ă  mesure, fait que l’on cumule de la dette qu’il faut un jour payer, Ă  travers des mises Ă  niveaux. Et si ce n’est pas fait dans les temps, le prix sera le paiement du support Ă©tendu, le recrutement de l’expertise…

Si le code n’est pas implĂ©mentĂ© en suivants les bonnes pratiques de dĂ©veloppement : couplage fort entre composants, nommage incomprĂ©hensible, code non homogène,… cela engendre un cumul de la dette au fil du temps.

Et si on parlait impacts de la dette non traitée sur le SI…

Sur l’humain : quand on cumule de la dette, on obtient un SI contenant des composants applicatifs ou infrastructure d’un autre temps. Un des impacts concret de ce type de dette est la perte de l’expertise. Les experts deviennent plus rares, et donc plus chers (exemple des experts Cobol, Mainframe…).

Sur l’environnement technique : on maintient dans le SI des infrastructures “anciennes” avec du support étendu voire obsolète des fois. On continue à utiliser des solutions ou des frameworks non supportés par des éditeurs ou la communauté (si open source). On continue à exécuter un code difficilement maintenable.

Sur le process : quand on hérite d’un SI dont les composants générent des dysfonctionnements, on rentre dans des cycles maintenance assez fastidieux, du code non testable, voire des cas extrêmes tels que l’impossibilité de déploiement de nouvelles versions.

Sur l’organisation : dans un monde qui se veut agile, avec des composants de plus en plus interconnectés, une organisation en silo est peu adaptée. Une telle organisation ne favorise pas la communication, l’échange entre équipes… et donc constitue un frein lorsqu’on veut faire évoluer le SI. Ce qui peut avoir un impact sur le traitement de la dette.

Mais quels actions pourrait-on mettre en place ? Actions correctives

Si nous sommes dans le cas où notre SI présente une dette technique à gérer, comment faire ?

Voici quelques pistes pour résorber la dette d’un SI:

  • Analyser l’ensemble des composants : selon la complexitĂ© du SI, considĂ©rer l’intĂ©rĂŞt d’utiliser un outil de type APM (Application Portfolio Management) pour assurer Ă  la fois la cohĂ©rence du SI et le suivi de la dette technique de l’ensemble des composants du SI.
  • Analyser l’impact fonctionnel (s’il existe) sur les composants Ă  modifier : la rĂ©solution de la dette peut engendrer un impact fonctionnel, certain outils ou frameworks apportent de nouvelles fonctionnalitĂ©s ou des modifications impactants le mĂ©tier.
  • Analyser l’impact sur les liens avec des composants externes.
  • Etudier le traitement de la dette en implĂ©mentant un des trois scĂ©narios : mise Ă  niveau, migration et refonte (les trois scĂ©narios sont explicitĂ©s en bas).
  • Planifier une stratĂ©gie de gestion de la dette, et commencer Ă  la traiter. Comme dit le proverbe chinois : “Le meilleur moment pour planter un arbre, c’était il y a 20 ans. Le second meilleur moment, c’est maintenant.” Il n’y a pas de temps Ă  perdre !

Revenons aux trois scénarios cités plus haut.

La mise à niveau d’un composant applicatif ou d’infrastructure consiste à le faire évoluer d’une version à une autre supérieure, avec d’éventuels impacts sur le code. Dans certains cas, la mise à niveau, surtout si elle arrive au bout de plusieurs années, peut avoir des impacts importants sur le SI, du simple fait qu’une version récente peut apporter des nouvelles fonctionnalités, voire casser des fonctionnalités existantes de la version en cours.

La migration d’un composant consiste à son remplacement par un autre, du même ou d’un autre éditeur. L’étude de la migration doit contenir une analyse détaillée des fonctionnalités utilisées, leurs équivalents dans la nouvelle solution, les éventuels impacts techniques mais aussi sur le quotidien des utilisateurs de la solution.

La refonte est essentiellement applicable sur le code d’un composant applicatif ou les composants auxquels la brique est connectĂ©e. La refonte peut ĂŞtre complète ou partielle. Dans tous les cas, il faut assurer la non-rĂ©gression, l’intĂ©gration du nouveau code et la mise en oeuvre des bonnes pratiques liĂ©es au processus de dĂ©veloppement et au code. Chez OCTO Technology, nous savons auditer les pratiques de dĂ©veloppement, qualifier un code Ă  travers certaines mĂ©triques telles quel : clartĂ©/simplicitĂ©, rĂ©utilisabilitĂ©, maintenabilitĂ©, application des principes SOLID,…

Les actions préventives

La dette étant inévitable l’objectif est d’avoir deux axes pour la traiter en amont et dans le temps.

En amont, Ă  travers:

  • La mise en place des bonnes pratiques de dĂ©veloppement dans le code et dans les processus (principes SOLID, revue de code, TDD, DDD, pair programming…)
  • L’utilisation des outils Ă©prouvĂ©s et dans l’état de l’art du marchĂ©

Dans le temps, en réservant du budget pour le traitement de la dette : Ce qui consiste à estimer les coûts de mises à niveau, migration et/ou refonte dans le temps. Comme les composants du SI évoluent et au lieu de subir cette évolution, la solution la plus adéquate est de réserver systématiquement un budget pour le traitement de la dette.

Takeaways…

La dette fait partie du SI. Elle a des impacts sur l’humain, l’environnement technique, le process et l’organisation. Le traitement de la dette doit être prévu et budgétisé au risque de voir son coût augmenter avec le temps.

Deux points importants Ă  retenir :

  • La dette est inĂ©vitable, elle fait partie du SI.
  • La dette se traite en amont mais aussi tout au long du cycle de vie d’un logiciel.

 

Articles suggested :

  1. L’architecture d’entreprise : vision mĂ©tier ou technologique?
  2. Services REST : ne jetez pas la SOA avec l’eau du bain

Catégories: Blog Société

Blockchain : Modifier un contrat immuable

lun, 09/11/2017 - 07:00

Pourquoi et comment modifier un contrat immuable ? C’est ce que nous allons Ă©tudier.

Les différentes technologies de Blockchain (bitcoin, ethereum, etc.) consistent à produire un consensus, entre de nombreuses parties, sur un état stable. Pour Bitcoin, il s’agit de se mettre d’accord sur l’état d’un livre de compte ; pour Ethereum, de se mettre d’accord sur l’état d’un ordinateur virtuel mondial.

Ethereum a la prĂ©tention de permettre l’exĂ©cution de contrats numĂ©riques sous le contrĂ´le du rĂ©seau distribuĂ© et non d’une entitĂ© centralisĂ©e. Des fonds (en monnaie Ă©lectronique Ether) peuvent ĂŞtre sous la responsabilitĂ© du contrat, et le code dĂ©cide ensuite leur distribution. Typiquement, le contrat peut servir de notaire, en gardant des fonds jusqu’Ă  ce qu’une condition nĂ©cessaire soit validĂ©e (dĂ©lai, preuve de livraison, etc.).

Chaque contrat peut posséder une quantité de crypto-monnaie appelée éther. Les ethers permettent, entre autre, de financer la communauté pour l’exécution de l’ordinateur mondial.

Le principe de base d’un contrat Ethereum est d’avoir un code immuable, impossible Ă  modifier ou Ă  supprimer. Si le dĂ©veloppeur ne l’a pas prĂ©vu, il n’est pas possible d’arrĂŞter un contrat qui s’exĂ©cute dès lors qu’il est sollicitĂ©.

En effet, lorsqu’un contrat est déposé dans la chaîne de blocs, il y est pour toujours. Les instructions qui le compose peuvent être invoquées à tout moment, entraînant éventuellement une modification d’état du contrat, d’autres contrats liés, voire le transfert des Ethers associés au contrat vers un autre compte ou un autre contrat.

  • Un contrat dĂ©posĂ© dans la blockchain ne peut pas ĂŞtre supprimĂ©
  • Il peut ĂŞtre invoquĂ© par n’importe quel utilisateur ou autre contrat
  • Le code d’un contrat ne peut pas Ă©voluer
  • Seules ses donnĂ©es peuvent Ă©voluer

Modifier l’état d’un contrat, c’est écrire les différences dans le prochain bloc.

Cette immutabilité est une excellente chose, car elle permet de garantir qu’aucune des parties prenantes du contrat ne pourra revenir sur ses engagements. C’est un élément essentiel de la sécurité d’Ethereum et cela contribue à sa valeur.

Si j’accepte de signer un contrat où je m’engage à rembourser telle somme en Ether si une condition ne s’est pas produite à telle date, il m’est impossible de répudier cela. Lorsque le délai sera passé, l’autre partie pourra déclencher le versement des Ethers présents en caution dans le contrat.

Personne, mĂŞme pas un pirate, ne pourra apporter des modifications au contrat. A tel point que si le contrat est mal Ă©crit, des Ethers peuvent devenir inaccessibles.

1 Amender un contrat ?

Un contrat est immuable, mais parfois, on aimerait bien pouvoir le modifier, avec l’accord de toutes les parties. On aimerait pouvoir publier une nouvelle version du contrat.

Par exemple, il devrait être possible d’ajouter un avenant à un contrat. Ou de le modifier, car ce qui était permis ne l’est plus, des paramètres extérieurs ont évolués, de nouvelles règles sont imposées par la loi, etc.

Du point de vue des scénarios d’usage, on peut imaginer la situation suivante : deux personnes signent un contrat numérique qui est régi par la loi. La loi évolue, remettant en cause le contrat : il faut donc le modifier, avec l’accord des deux personnes.

Il est également possible d’ajouter un utilisateur représentant une puissance étatique : l’État, une instance territoriale, une institution publique, etc.

Le contrat est alors construit avec trois propriétaires : les deux personnes et, par exemple, l’État. Il est paramétré pour que deux propriétaires seulement soient nécessaires pour modifier le contrat.

Avec un scénario d’habilitation “deux sur trois”, deux partenaires parmis les trois peuvent se mettre d’accord pour modifier le contrat. Dans les faits, soit les deux personnes trouvent un nouveau terrain d’entente, soit l’une d’elles, avec l’accord de l’état, peut modifier le contrat.

Modèle d’exĂ©cution du contrat

Nous souhaitons donc pouvoir apporter toutes les modifications possibles à un contrat. Nous vous proposons ci-dessous l’approche retenue par OCTO, et permettant de minimiser les impacts sur le reste de l’écosystème :

  • Un autre contrat ou une application externe Ă  la blockchain ne doit pas ĂŞtre modifiĂ©e lors de la publication d’une nouvelle version du contrat
  • Il ne faudrait pas avoir Ă  transfĂ©rer l’état d’une version d’un contrat vers la suivante, car cela peut consommer plus d’ether que le maximum autorisĂ© par l’utilisateur du contrat et peut compromettre la sĂ©curitĂ©, en ouvrant une brèche permettant le transfert des ethers appartenants au contrat :
    • La nouvelle version doit avoir accès Ă  tous les attributs du contrat d’origine.
    • Il faut Ă©galement pouvoir transfĂ©rer les fonds de la version 1 vers la version 2. Mais le contrat version 1 ne peut avoir connaissance de la version 2.
  • Il doit ĂŞtre possible d’ajouter de nouveaux attributs et de nouvelles mĂ©thodes dans la nouvelle version du contrat : La nouvelle version du contrat ne doit pas avoir de contraintes
  • Les Ă©vĂ©nements publiĂ©s par les diffĂ©rentes versions doivent ĂŞtre Ă©mis par un seul et unique contrat
  • Les applications externes Ă  l’écoute du contrat (JavaScript ou autres) ne doivent pas ĂŞtre modifiĂ©es suite Ă  un changement de version
  • Le solde d’Ethers portĂ© par le contrat une version doit ĂŞtre disponible pour la nouvelle version

Pour cela, inspiré des travaux de Martin Swende, avec le langage Solidity, nous allons utiliser un contrat Proxy qui hérite d’un contrat Versionable. La première version du contrat devra également hériter de Versionable.

Le Proxy va exploiter la fonction fallback de Solidity, sans nom ni paramètre, qui est invoquée lorsqu’aucune méthode présente dans le contrat ne correspond. Le code de cette méthode va déléguer les data de la transaction au contrat référencé par le Proxy, mais en demandant à la machine virtuelle Ethereum (EVM) d’utiliser le contexte du contrat Proxy pour stocker les attributs (pour les détails techniques, référez-vous à cet article).

Afin de posséder l’intégralité des attributs de la première version, ContractV2 hérite de ContractV1. Cette nouvelle version peut alors ajouter de nouveaux attributs et proposer d’autres implémentations des méthodes. Il est également possible d’ajouter de nouvelles méthodes.

Avec cette approche, seul le code des versions du contrat est utilisé par le Proxy. Les données entre les versions sont mutualisées dans le Proxy. Ainsi, il n’est pas nécessaire de migrer les données du contrat V1 vers le contrat V2. Elles sont déjà présentes !

Entrons dans la technique

Pour implémenter cela, il faut écrire un peu d’assembleur EVM.

contract Versionable {
  event VersionChanged(Versionable version);

  /** The current version. */
  Versionable internal currentVersion;
  }

  contract Proxy is Versionable {

  /**
   * Create a proxy to delegate call to the current version of contract.
   * @param _currentVersion The first version to use.
   */
  function Proxy(Versionable _currentVersion) {
    currentVersion = _currentVersion;
  }

  /**
   * Change the current version.
   * @param _newVersion The new version.
   */
  function changeVersion(Versionable _newVersion) { // TODO: Add privilege
    currentVersion.kill(this);
    currentVersion = _newVersion;
    VersionChanged(_newVersion);
  }

  /**
   * Propagate the current call to another contract.
   * Use TARGET code with THIS storage, but also keep caller and callvalue.
   * Invoke delegateCall().
   * In order for this to work with callcode,
   * the data-members needs to be identical to the target contract.
   *
   * @param target The target contract.
   * @param returnSize The maximum return size of all methods invoked.
   */
  function propagateDelegateCall(address target, int returnSize) internal {
    assembly {
      let brk := mload(0x40) // Special solidity slot with top memory
      calldatacopy(brk, 0, calldatasize) // Copy data to memory at offset brk
      let retval := delegatecall(sub(gas,150)
        ,target //address
        ,brk // memory in
        ,calldatasize // input size
        ,brk // reuse mem
        ,returnSize) // arbitrary return size
      // 0 == it threw, by jumping to bad destination (00)
      jumpi(0x00, iszero(retval)) // Throw (access invalid code)
      return(brk, returnSize) // Return returnSize from memory to the caller
    }
  }

  function () payable {
    /* 32 is the maximum return size for all methods in all versions. */
    propagateDelegateCall(currentVersion,32);
  }
}

/**
 * The version 1 of the contract.
 * The attr is initialized to 1000.
 * The method doSomething() return attr + version = 1001
 */
contract ContractV1 is Versionable {
  uint constant private version=1;
  uint public attr;

  /** return attr+version (1001). */
  function doSomething() external
    returns(uint) {
   return attr+version; // 1001
  }

  /** Post-construction. */
  function init() {
    attr=1000;
    isInit=true;
  }
}

/**
 * The version 2 of the contract.
 * To preserve all the attributs from the v1 version, this version IS a ContractV1.
 * All methods can be rewrite, new one can be added and
 * some attributs can be added.
 *
 * The newAttr is initialized to 100.
 * The method doSomething() return attr + newAtttr + version = 1102
 */
contract ContractV2 is ContractV1 {
  uint constant private version=2;
  uint public newAttr;

  /** return attr + newAttr + version (1102). */
  function doSomething() external returns(uint) {
    return attr + newAttr + 2; // 1102
  }

  /** return 42. Another method in version 2. */
  function doOtherThing() external returns(uint) {
    return 42;
  }

  /** Post-construction. */
  function init() {
    newAttr = 100;
    isInit = true;
  }
}

Nous avons négligé les règles de sécurité pour simplifier le code. Il va sans dire que la méthode changeVersion() doit être protégée contre une utilisation par n’importe qui. Sinon, n’importe qui pourrait modifier un contrat.

L’intégralité des sources est disponible ici.

Le proxy ne possède pas de méthode, mais va gérer les attributs du contrat. Les méthodes sont présentes dans les versions du contrat. Si on inspecte l’instance ContractV1, aucun attribut n’est présent. De même pour ContractV2.

Le deuxième effet kisscool de ce modèle est que les events viennent du Proxy et non des contrats. Donc tant que le format des événements n’est pas modifié, les applications à l’écoute du contrat n’ont pas à savoir qu’il a été modifié. Elles écoutent toujours les mêmes événements, venant du même contrat.

Pour le développeur, il teste et conçoit les contrats comme d’habitude. C’est lors du déploiement que la localisation des attributs change.

Il reste à gérer les constructeurs des contrats. En effet, construire une instance est un traitement spécial. Il est envoyé au contrat de numéro zéro de la blockchain. Le code du constructeur n’est pas disponible avec le contrat. Il n’est donc pas possible de le réutiliser pour initialiser le proxy.

Nous devons alors utiliser une méthode init() qui jouera le rôle de constructeur. Il ne faut pas oublier de l’invoquer juste après la création de l’instance du contrat v1 et du contrat v2.

Comment utiliser ce code ? Il suffit de construire une instance du ContractV1, de l’encapsuler dans un Proxy et de caster (transtyper) le proxy en ContractV1.

ContractV1 myContract = ContractV1(new Proxy(new ContractV1()));

Ensuite, il ne faut pas oublier d’invoquer l’initialisation du contrat, en passant bien par le proxy.

myContract.init();

Pour utiliser le contrat, c’est comme d’habitude.

myContract.doSomething();

Et voilà. Pour modifier la version, on recast le contrat en Proxy, afin d’avoir accès à la méthode changeVersion().

Proxy(myContract).changeVersion(new ContractV2());

L’invocation de la nouvelle version est identique et s’effectue toujours avec myContract.

myContract.doSomething();

Ce dernier modèle répond à toutes les exigences :

  • La rĂ©fĂ©rence du contrat n’évolue pas, mĂŞme en cas de changement d’implĂ©mentation
  • Il est possible d’ajouter de nouvelles mĂ©thodes ou de nouveaux attributs dans une nouvelle version du contrat
  • Il n’est pas nĂ©cessaire de migrer les donnĂ©es entre les versions
  • Les Ă©vĂ©nements Ă©mis par le code des contrats V1 ou V2 viennent bien du proxy

Les inconvénients de cette approche sont les suivants :

  • Il n’est pas possible de supprimer un attribut dans la dĂ©finition de la classe
  • Il est nĂ©cessaire de sĂ©parer le constructeur de la mĂ©thode init()
  • Cela prĂ©sente un surcoĂ»t de 735 gaz pour chaque invocation (soit 0,00000252 € avec un Ether Ă  120 €).
  • Il faut connaĂ®tre la taille maximum acceptable des returns (cf. paramètre returnSize)

Pour résoudre ce dernier point, une proposition d’évolution de l’EVM est en discussion (EIP-5).

2 Et la sécurité ?

Pour autoriser la modification de la version du contrat, il faut, par exemple, que n owners parmi m soient d’accord sur la nouvelle implémentation du contrat (n pouvant être égal à m). Lorsqu’une méthode sensible est invoquée, elle n’est pas exécutée directement tant qu’un autre owner n’invoque pas la même méthode avec strictement les mêmes paramètres. Lorsque suffisamment d’owners sont d’accords pour modifier le contrat, alors la version est modifiée.

OCTO propose les contrats MultiOwned, Versionable et Proxy que vous pouvez utiliser pour tous vos nouveaux contrats.

Vous retrouverez ici la version protégée du Proxy, permettant à plusieurs owners de se mettre d’accord sur la nouvelle version du contrat. La liste des owners est à spécifier lors de la création du Proxy et peut ensuite être modifiée avec l’accord de tous.

Tous les sources sont disponibles ici.

Nous proposons un exemple d’utilisation de ce modèle avec les sources. Pour le tester, il faut :

  • CrĂ©er une instance du test unitaire
  • Invoquer init()
  • Invoquer doSomething() pour rĂ©cupĂ©rer 1001 (version 1 du traitement)
  • Demander le changement de version via l’utilisateur 1 ( user1_changeToV2() )
  • Confirmer la demande de changement en invoquant de mĂŞme changeVersion() avec strictement les mĂŞmes paramètres, mais via l’utilisateur 2 ( user2_changeToV2() )
  • Invoquer doSomething() pour rĂ©cupĂ©rer 1102 (version 2 du traitement)
  • et enfin doOtherthing() pour confirmer qu’il est possible d’ajouter une nouvelle mĂ©thode
3 Dans le détail du code assembleur

Cette implémentation se base sur quelques subtilités de l’Ethereum Virtual Machine et de Solidity. Si vous souhaitez plus de détails techniques, nous vous invitons à regarder cet autre article de blog.

L’invocation d’une méthode est intégralement décrite dans le paramètre data d’une transaction. Il est donc possible de proposer la même invocation à un autre contrat. Pour cela, nous devons utiliser l’instruction delegatecall. Cette dernière nécessite d’indiquer une zone en mémoire avec les paramètres de l’invocation, et une autre zone en mémoire pour récupérer le résultat de l’invocation. Pour utiliser une zone mémoire disponible, nous commençons par récupérer la valeur à l’index 0x40, utilisé par Solidity. Elle indique la dernière adresse mémoire utilisée par le programme. Nous utilisons alors cette zone vierge pour y répliquer les données de msg.data. Nous indiquons la même zone mémoire pour récupérer le résultat de l’invocation. En cas d’erreur, nous lançons un jump vers une zone de code invalide. Cela est l’équivalent à un throw sous Solidity. Enfin, nous retournons la zone mémoire valorisée par l’invocation du delegateCall.

Nous plaçons cette méthode spéciale dans la méthode de repli de Solidity, afin que toutes les méthodes absentes du contrat Proxy soient déléguées à l’instance portant la version du contrat. Toutes les modifications des attributs s’effectuent sur l’instance Proxy.

Nous vous proposons une solution générique. Elle utilise les spécificités de la machine virtuelle et des choix d’implémentations de Solidity :

  • Utiliser le fait qu’un Cast est possible vers n’importe quelle adresse de contrat. Cela permet de faire passer le Proxy comme un ContractV1 ou ContractV2
  • Utiliser la mĂ©thode par dĂ©faut, lorsqu’une mĂ©thode n’est pas identifiĂ©e par le contrat
  • Utiliser l’attribut Ă  l’adresse 0x40 pour identifier une zone mĂ©moire disponible pour dĂ©lĂ©guer le traitement
  • Utiliser l’assembleur pour invoquer une mĂ©thode d’un autre contrat, et rĂ©cupĂ©rer la valeur de retour avant de la propager Ă  l’appelant
  • Utiliser la dĂ©lĂ©gation pour que les Ă©vĂ©nements des diffĂ©rentes implĂ©mentations viennent bien du Proxy

OCTO propose une solution générique, de quelques lignes, permettant de limiter au maximum les impacts de la mise à jour d’un contrat.

Articles suggested :

  1. Introduction Ă  la technologie Blockchain
  2. Une cagnotte dans la blockchain Ethereum
  3. Raiden, une réponse à la confidentialité des échanges sur Ethereum ?

Catégories: Blog Société

Le demi-cercle (épisode 2 — Voir / Avancer)

ven, 09/08/2017 - 09:26

(RĂ©sumĂ© des Ă©pisodes prĂ©cĂ©dents : Et si c’Ă©tait le moment oĂą on pose son ouvrage et on rĂ©flĂ©chit ? Si c’Ă©tait le moment oĂą l’on commence Ă  changer un peu la manière dont on fait les choses ?)

Prenons ce bug. Tu viens d’identifier l’origine du problème, et aussitĂ´t tu t’es figurĂ© la solution. En un clin d’Ĺ“il. Tu ouvres le code, tu fais la modification qui va bien. Tu relances l’assemblage. Dans dix minutes, l’application sera livrable en recette. Il faudra remettre en place des donnĂ©es de test pour le compte du client. Il faudra attendre que le client fasse ses tests, et donne son feu vert pour le passage en production.

Tu réfléchis. Tu fais les questions et les réponses.
– Et si ça ne devait pas marcher ?
– Ça marche; j’en suis sĂ»r.
– Et si ça ne devait pas marcher ?
– Ça ne se serait pas de notre fait.
– Si ça ne devait pas marcher, combien de temps faudrait-il pour en ĂŞtre informĂ© ?
– Plusieurs heures, voire plusieurs jours.

Tu réfléchis encore.
– De deux choses l’une : soit le programme est maintenant corrigĂ©, soit il comporte (au moins) un nouveau dĂ©faut.
– S’il est corrigĂ©, alors nous faisons toutes ces dĂ©marches de test pour rien.
– S’il ne l’est pas, alors il vaut mieux attendre d’en ĂŞtre certain avant de rĂ©agir.

Tu marches Ă  tâtons dans l’obscuritĂ©. L’incertitude. Ce qui est certain, c’est que la pièce est assez vaste — on dirait qu’elle rĂ©sonne. Et aussi qu’elle est particulièrement encombrĂ©e, par un tas de trucs indĂ©finis, non identifiĂ©s, un fatras. Mais tu ne peux rien voir. Tu peux seulement tenter d’avancer, et buter dans l’obstacle qui se trouve lĂ . Ce jeu — que tu adorais Ă  quinze ans mais qui commence Ă  te fatiguer maintenant que tu en as presque trente — possède une règle bien identifiĂ©e par tous les joueurs :

« voir » = buter dans un obstacle.

Dans cette situation, quelle sera ton approche prĂ©fĂ©rĂ©e ? Est-ce que tu ralentis et tends les mains pour dĂ©tecter les obstacles, ou bien est-ce que tu t’engages prestement et traverse la pièce avec aplomb ? D’autres se mettraient tout de suite Ă  quatre pattes, pour ne pas risquer une chute. Petits joueurs. Marcher Ă  quatre pattes, c’est s’avouer vaincu d’avance.

Tu marches Ă  l’aveugle, mais debout. Tu prĂ©tends pouvoir sortir de la pièce en un seul trajet. Tu affirmes : « C’Ă©tait le dernier dĂ©faut ! » Tu dis : « Il reste juste ce tout petit problème Ă  corriger et on est bons. » Mais pas une seule de toutes les parties prĂ©cĂ©dentes que tu as jouĂ©es ne s’est dĂ©roulĂ©e sans que tu ne rencontres, en le percutant, un obstacle. Et Ă  chaque fois, tu t’es cassĂ© la figure, tu as ramassĂ© et rĂ©arrangĂ© Ă  la hâte les trucs en questions, tu t’es figurĂ© que ça devait vaguement former un bloc, une pile, ou bien au moins un tas, et tu t’es relevĂ©. VoilĂ . Ça devrait faire le job. Tu aimerais bien rentrer chez toi maintenant. Tu te dis : « demain il fera jour ». Mais demain quand tu reviendras dans la pièce, elle sera tout aussi obscure. Et Ă  nouveau, peut-ĂŞtre, tu buteras dans des nouveaux obstacles, de forme et de taille inconnues.

Bombes logiques. À mesure que tu corriges des défauts, tu en sèmes de nouveaux.

Ce que tu pourrais faire de différent ?

Tu ne peux certes pas allumer dans la pièce.
Si tu pouvais allumer, cela résoudrait une bonne partie du problème, évidemment.

Tu rĂŞves qu’on allume dans la pièce, et dans ta tĂŞte tu entends comme une foule soudain satisfaite qui soupire : « Aaaaah! », et tu regardes :

Le premier obstacle dans lequel tu butais systĂ©matiquement n’Ă©tait pas bien rangĂ©. Une fois placĂ© debout près de ce pilier, cet obstacle ne gĂŞnerait plus du tout, en fait.

Tu vois clairement maintenant certains obstacles que tu croyais insurmontables, inĂ©luctables : tu rĂ©alises qu’on peut les faire disparaĂ®tre en deux-temps, trois mouvements.

Cet obstacle dont tu te disais : « ça doit ĂŞtre le dernier », se trouve comme Ă©talĂ© sur toute la surface de la pièce, tu peux Ă  peine compter les occurrences, il n’y en a pas « juste un dernier », il en reste des douzaines.

Et dans cette lumière éclatante et cette netteté nouvelle, tu es sur le point de réaliser une deuxième règle que beaucoup moins de joueurs connaissent :

« avancer » = créer de nouveaux obstacles

Mais tu ne peux pas vraiment formuler cette règle numĂ©ro 2 parce que tu n’as ni les moyens (il faudrait savoir comment) ni le temps (il faudrait des mois) de rĂ©tablir la lumière dans ce projet aveugle.

TrĂŞve de rĂŞveries.

Que faire ?
Une pause.

Tu vas au coin cuisine/café/baby et tu te prépares une soupe Ramen.
Tu reviens et tu la manges devant ton Ă©cran, qui dĂ©place en silence des couleurs fauves autour d’une horloge qui dit : 20h45.
Avec un petit jus de mangue, et un cookie Chinois.
Tu ouvres le cookie Chinois, tu déplies le papier.

On apprend tous de nos erreurs… Vous allez beaucoup apprendre aujourd’hui.

Très drĂ´le. Tu repenses Ă  la blague de JĂ©rĂ©mie hier midi. JĂ©rĂ©mie — qui fait montre d’un esprit moqueur et d’un vif scepticisme Ă  l’Ă©gard de tout ce qui est mĂ©thodologie — ouvre un cookie Chinois et lit d’un ton docte : « Chaque ligne de code est justifiĂ©e par un test qui ne passait pas ». Ce qui a fait rire toute la tablĂ©e, mĂŞme ceux qui entendaient ce truc pour la première fois.

Cette blague. Chaque ligne de code est justifiĂ©e par un test qui ne passait pas. Et puis quoi d’autre ?

Tu déverrouilles ton écran. Pourquoi pas ? Tu peux décider maintenant, comme une sorte de principe de survie en milieu obscur, que les obstacles rencontrés ne se remettront pas dans ton chemin.

Tu reviens en arrière; tu reprends la version bugguĂ©e du programme; tu crĂ©es un module de test; tu Ă©cris un test qui ne passera pas; il ne passera pas parce qu’il y a cette erreur — ce bug. Écrire le test te prend du temps. Le code ne s’y prĂŞte pas, ne se soumet pas facilement Ă  des tests de cette sorte. C’est parce que le test vise une partie du code qu’il faudrait exĂ©cuter indĂ©pendamment de tout le reste. C’est d’autant plus idiot que cette partie est toute petite. Deux lignes.

Le code est couplĂ© Ă  ses dĂ©pendances extĂ©rieures. Tu mets l’effort que requiert la crĂ©ation de ce test en regard de l’insignifiance de la ligne de code qu’il permet de tester. Tout ça pour ça. La montagne accouche d’une souris. Frustration.

Tu es arrĂŞtĂ© dans tes pensĂ©es par le vrombissement du tĂ©lĂ©phone. C’est StĂ©phanie.
– J’ai eu ton message. J’ai dĂ®nĂ©. Tu en as pour longtemps ?
– Plus tellement. Je suis crevĂ©. Ça va ?
– J’essaie de rĂ©parer mon vĂ©lo. C’est le dĂ©railleur.
– Oh!
– C’est pĂ©nible. J’ai essayĂ© de le rĂ©gler mais je ne peux pas manĹ“uvrer le dĂ©railleur sans pĂ©daler et rouler. Et quand je roule, je ne peux pas regarder le dĂ©railleur de près. En plus il fait nuit, et la lumière de la cour est trop faible.
– Ah.
– Donc je retourne le vĂ©lo, je le mets sur sa selle et j’actionne les pĂ©dales. Mais dans ce cas lĂ , la chaĂ®ne ne s’enroule pas de la mĂŞme façon sur le dĂ©railleur parce que le vĂ©lo est dans l’autre sens.
– …
– Et toi comment ça va ?
– Pareil.
– Tu rentres Ă  quelle heure ?
– Je pars dans 10 minutes.

Tu te dis : « Ă€ quoi bon, c’est impossible ». Mais rĂ©flĂ©chis. Le dĂ©lai qu’il te faudrait pour dĂ©coupler un peu ce code et poser ce test, tu vas le passer Ă  attendre que le client fasse ses tests ?

Tout en insĂ©rant d’autres bombes logiques dans le programme ?

Tu reprends l’idĂ©e. Tu casses les dĂ©pendances. C’est assez moche. Le code semble encore un peu plus baroque qu’il ne l’Ă©tait avant ce test. Tu arrives tant bien que mal Ă  isoler le code incriminĂ©. Ce n’est pas spectaculaire, mais au moins ton test s’exĂ©cute sans faire appel Ă  la base de donnĂ©es.

Il est rouge.
Tu corriges le bug.
Le test passe au vert.

Tu as peut-ĂŞtre encore le temps d’Ă©crire un autre test, du coup. Cette partie du code est totalement (dans l’obscuritĂ©) dĂ©pourvue de tests.
Pour cette partie du code, tu ne sais mĂŞme pas ce qu’il faudrait que le test affirme. Tu exĂ©cutes le test, et le rĂ©sultat te dit ce que fait effectivement le code. Tu colles ce rĂ©sultat dans le test, tout en n’Ă©tant pas sĂ»r que ce soit la vĂ©ritĂ©. Tu te dis qu’en tout Ă©tat de cause, tu as dĂ©terminĂ© ce que fait le code, et que tu pourras toujours t’appuyer sur ce test pour poser des questions Ă  la responsable d’application. Ce sera toujours plus fiable que de lancer des hypothèses dans le noir et se casser la figure ensuite.

Tu dĂ©cides pour un temps de garder ce principe, d’Ă©crire un test pour chaque nouvel Ă©lĂ©ment de fonctionnalitĂ© que tu voudras crĂ©er, ou modifier.

C’est comme si Ă  chaque fois que tu dois explorer la pièce, tu pouvais poser Ă  l’endroit oĂą tu te trouves, une lumière, certes pas très puissante, mais qui, tout le temps que ce projet durera, ne s’Ă©teindra plus.

Tu verrouilles ton poste, et tu rentres.

Catégories: Blog Société

End to end testing depuis les tranchées, avec Protractor

mer, 09/06/2017 - 10:05

www.octo.chTrĂ´nant sur la pyramide des tests, les tests fonctionnels « de bout en bout » (end-to-end) peuvent ĂŞtre destinĂ©s Ă  automatiser les scĂ©narios d’acceptance Ă  travers la fenĂŞtre d’un navigateur. En pratique, ces tests sont souvent soit nĂ©gligĂ©s par les Ă©quipes de dĂ©veloppement, soit au contraire, utilisĂ©s comme ultime ligne de dĂ©fense pour se protĂ©ger de tous les problèmes pouvant survenir sur le projet. Si aucune de ces solutions extrĂŞmes n’est recommandĂ©e, les tests end-to-end ont sans nul doute un rĂ´le Ă  jouer dans le dĂ©veloppement d’un produit.

Par ailleurs, ces tests traĂ®nent avec eux une mauvaise rĂ©putation parmi les dĂ©veloppeurs: instables, lents, coĂ»teux, difficiles Ă  maintenir. Dans cet article, nous allons tenter d’ĂŞtre plus pragmatiques et enthousiastes sur le sujet. Nous allons dĂ©buter par une rapide introduction Ă  Protractor (le framework de tests le plus utilisĂ© dans le monde AngularJS), puis examiner des cas pratiques issus de situations rĂ©elles, faisant intervenir la technique, la mĂ©thodologie et l’Homme.

La suite en anglais…

Catégories: Blog Société

IoT world 2017 (Santa Clara)

mar, 09/05/2017 - 20:41

De Janvier Ă  Juillet, j’ai Ă©tĂ© en mission au coeur de la Silicon Valley pour une mission industrielle IOT pour un grand compte de l’Ă©nergie.

L’IOT World 2017 avait lieu Ă  Santa Clara Ă  quelques miles de nos bureaux le 17 / 18 mai.

J’y ai vu l’opportunitĂ© de couvrir l’Ă©vĂ©nement pour y dĂ©crypter les tendances de l’annĂ©e Ă  venir et peut-ĂŞtre trouver des nouvelles solutions activables dans nos projets.

A la suite de ces deux jours de confĂ©rence, j’ai pu observer que l’IoT est toujours un mot Ă  la mode, mais le buzz s’est essoufflĂ©, car les dĂ©sillusions ont Ă©tĂ© nombreuses. DiffĂ©rents acteurs du marchĂ© n’ont pas trouvĂ© de modèle de monĂ©tisation comme le dit BOB O’DONNELL

« It is hard to make money with IoT »

Nous constatons un retournement de situation avec la montée en puissance des objets domestiques dans les foyers américains comme Alexa (d’Amazon) et Google Home. Deux ans après sa commercialisation, Amazon a vendu plus de 11 millions d’exemplaires, ce qui représente 10% des foyers américains. Dans les couloirs de l’IOT world on ne parlait que de ça.

 

J’ai assisté à deux jours de conférences, je me suis concentré sur deux streams, smart home et smart cities, des marchés centrés B2C et Business to City. Je n’ai pas assisté aux présentations sur la santé, le transport et les utilities. Il y a eu, par conséquent, des annonces que je ne vais pas reporter dans cet article.

Voici 4 tendances que je vois impacter le marché sur l’année à venir.

Smart home assistant

Ce furent les stars de ces deux jours de conférence : Alexa et Google Home. Perçu au premier abord comme un gadget amusant, le smart home assistant permet surtout l’arrivée de nouveaux services, comme l’achat par simple commande vocal sur Prime Now et la réception en moins de deux heures. Il n’y a plus d’interface physique, l’ensemble des interactions s’effectuent directement par la voix avec une grande fiabilité, grâce à la maturité du NLP (Natural language processing). Au delà de l’univers d’Amazon ou Google, ce sont des plateformes de développement. Plus de 1300 projets sur Github existent et ce n’est que la partie visible de l’iceberg. J’ai assisté à une démonstration au cours de laquelle, le démonstrateur configure les appareils connectés de sa maison par la voix. Par exemple, créer un groupe salle à manger (avec des ampoules, des radiateurs et des prises de courants) et allumer ce groupe.

Dans le panel “From Cool to Crucial: Achieving Mass Market Success of Connected Industry, Enterprise and Consumer”, Nate Williams enfonce le clou : “il faut suivre les acteurs plateformes tels que Apple, Google, Amazon pour rester dans la zone de confort de l’utilisateur”

Nous constatons un engouement fort du grand public avec une croissance de 1600% des ventes entre 2016 et 2017, on peut donc s’attendre à voir ces objets envahir notre quotidien dans les 5 ans à venir. Des compagnies comme Netflix ou Spotify prennent déjà les devant pour être présents sur ce nouveau canal.

Beaucoup d’usages n’ont pas encore été découverts ! On peut faire le rapprochement avec les débuts du smartphone. Initialement, personne n’aurait imaginé qu’il allait complètement supplanter le GPS !

Les passerelles

Au travers des talks: “Edge to Core computing – Where are the analytics taking place?” et  “Secure Industrial IoT solutions with the Edge”, le modèle de l’Edge computing est venu Ă  l’ordre du jour pour relever les dĂ©fis de la sĂ©curitĂ© et de la fiabilitĂ© des systèmes IoT.

 

L’edge computing consiste Ă  ajouter une part d’intelligence dans la gateway, pour faciliter et amĂ©liorer la gestion d’un ensemble de devices IoT. On retrouve ce modèle sur les smart home assistants pour Ă©viter de saturer la bande passante et faciliter la connectivitĂ© avec d’autres devices.

Aujourd’hui, l’architecture dominante consiste à avoir une gateway transparente  dont le rôle s’applique à la couche réseau exclusivement, il n’y a aucun rôle applicatif (filtre sur les messages à envoyer). Ce qui nous amène à nous poser plusieurs questions à propos des coûts cachés de la scalabilité et du cloud.

  • Que se passe-t-il en cas de latence ?
  • Si la bande passante des rĂ©seaux est trop faible ?
  • Si le signal raw est trop riche ?
  • Comment corrĂ©ler le service rendu avec la facturation du cloud (qui est Ă  la consommation) ?
  • Est-ce que les plateformes IoT actuelles scalent vraiment de façon linĂ©aire ?

 

L’Edge computing est un axe de réflexion à étudier pour potentiellement répondre à ces problématiques.

La communication vers le cloud est prétraitée par la gateway et sollicite moins le cloud.

De cette façon, la latence est plus faible et ce fonctionnement permet de limiter les surprises de scalabilité et de coût.

On pourrait écrire des pages entière à propos du edge computing mais on s’éloignerait trop du sujet de cet article.

Connectivité et sécurité

Le choix du réseau pour connecter ses objets a été un sujet fréquemment évoqué. Les réseaux LPWAN (bas débit, basse consommation) ont été au centre de nombreuses conversations.

Pour une architecture IOT, nous ne constatons pas d’alternative aux LPWAN aujourd’hui, mais il faut rester à l’écoute du marché car de nouvelle solutions peuvent apparaître très rapidement et exploser (5G?)

 

Le LPWAN ne répond pas à des challenges, tels que la mise à jour des devices ou l’encryption de données comme nous l’avons détaillé dans l’article “IoT et sécurité : une union impossible ?”

Dans le panel: “How LPWAN fills the performance gap between wide area cellular and local area short range wireless technologies”, les intervenants nous rappellent que les réseaux LPWAN ne sont pas interopérables. Un device SigFox ne fonctionnera pas sur un réseau LoRa.

Aujourd’hui le choix du réseau est donc important. Il n’y a pas standard visible à l’horizon.

La 3G ne couvre pas les besoin de l’IoT (trop gourmande en énergie), la 5G répondra surement à ce problème.

 

Lors de la table ronde, la CTO de la ville de Washington DC (Archana Vemulapalli) nous a fait part de ces craintes de l’utilisation de réseau public telle que la 3G, qui ne garantissent pas une qualité de service et peuvent poser des problèmes lorsque le réseau est saturé. Par exemple lors de grands événements sportifs.

Smart cities

Dans le cadre de programmes d’amĂ©nagement urbain, les projets de smart cities s’insèrent parfaitement.

J’ai assisté à la table ronde “The bigger Picture: Harnessing the ioT opportunity to enhance communities, cities and environments”,

Brenna Berman, Archana Vemulapalli, Arlette Hart et Phil Quade parlent avec engouement des programmes en cours qui embarquent des solutions de smart cities. Un des projets qui m’a marqué est celui qui est en cours de réalisation dans la ville de Washington DC. La ville compte installer des capteurs dans toute la ville pour collecter des données sur les déchets dans l’objectif d’optimiser leur collecte. (lien vers le projet).

Toutes les initiatives pour washington DC (et elles sont nombreuses) sont regroupées sur le site suivant : https://smarter.dc.gov

Dans un registre plus léger j’ai assisté à la présentation d’Analog devices. Celle-ci démontre, à l’aide d’une maquette en Lego, à quoi pourrait ressembler une smart city.

 

Conclusion

Au final,il n’y a pas eu de grandes annonces, les constructeurs prĂ©fèrent le faire lors de leur propre confĂ©rence. En effet, Apple a annoncĂ© dans les jours suivant l’arrivĂ©e prochaine de son assistant domestique, le “Homepod”.

Je n’ai pas reparlé des sujets plus “classiques” comme la sécurité, les plateformes, les objets …

Nous retenons qu’une nouvelle effervescence s’est créée autour des smart home assistants (Alexa notamment) pour apporter l’IoT chez “monsieur tout le monde” et créer de nouvelles opportunités auprès du grand public.

Articles suggested :

  1. TechDays 2015: Retrouvez toute l’actualitĂ© des technologies Microsoft avec OCTO
  2. AWS re:Invent 2015 : retour sur les annonces et compte-rendus
  3. Les Octos débarquent à l’Agile Tour Lille 2016 !

Catégories: Blog Société

Découplage, découplage, découplage !

lun, 09/04/2017 - 08:00

En informatique, on adore le découplage : quel bonheur ce serait, d’avoir des morceaux de système évoluant librement chacun dans leur coin.

Un SI dĂ©couplĂ© c’est presque aussi bien qu’un SI composĂ© de chatons, photo par Pieter Lanser en CC

Il existe malheureusement deux ombres Ă  ce tableau idyllique :

  • le dĂ©couplage prĂ©sente des avantages mais aussi des inconvĂ©nients ;
  • il n’existe pas un dĂ©couplage mais des dĂ©couplages, qui ont chacun leurs caractĂ©ristiques propres, parler de dĂ©couplage sans prĂ©ciser duquel il s’agit, c’est donc prendre le risque de ne pas se comprendre et de faire de mauvais choix.
Un exemple simple de découplage

Dans l’architecture logicielle, la programmation structurée est un exemple très simple de découplage, apparue dans les années 70. Révolutionnaire à l’époque, elle consiste à encapsuler son code dans des modules — fonctions ou procédures — plutôt que d’utiliser des GOTO.

Encapsuler du code dans des sous-routines exposées par des APIs formalisées — plutôt qu’une masse de code informe — a permis d’améliorer la lisibilité des programmes et la qualité des développements.

Cet exemple intègre les trois caractéristiques qu’on retrouvera dans les cas de découplage au niveau du SI :

  • un besoin : amĂ©liorer la lisibilitĂ© et la qualitĂ© du code ;
  • un moyen : des nouvelles syntaxes spĂ©cifiques ;
  • un inconvĂ©nient : une baisse notable de performance avec les ordinateurs et les compilateurs de cette Ă©poque, au point qu’utiliser des fonctions a longtemps Ă©tĂ© une pratique controversĂ©e.
Les trois types de découplage de SI

À l’échelle d’un SI, il existe trois grands types de découplage :

Les trois types de découplages qui nous intéressent

  • le dĂ©couplage de format : quand l’exĂ©cution d’une fonctionnalitĂ© passe par l’utilisation d’un contrat d’interface stable ;
  • le dĂ©couplage de temporalitĂ© : quand l’exĂ©cution d’une fonctionnalitĂ© se fait plus tard de manière asynchrone ;
  • le dĂ©couplage de localisation : quand l’exĂ©cution d’une fonctionnalitĂ© se fait ailleurs.

Ces découplages présentent des zones de recouvrement mais sont différents.

Le découplage de format

C’est celui auquel on pense le plus souvent quand on parle de découplage dans le domaine du SI.
Il est fondamental dans une organisation d’une certaine taille car il répond à deux grands besoins :

  • maĂ®triser les consĂ©quences d’une modification d’une partie du système ;
  • permettre Ă  diffĂ©rentes Ă©quipes de travailler de manière indĂ©pendante en limitant les efforts de coordination.

Le moyen consiste à définir certains contrats d’exposition comme étant stables et utilisables à l’extérieur, à l’opposé de contrats « à usage internes » qui ne font pas l’objet de ces garanties.

L’inconvĂ©nient est le surcoĂ»t crĂ©Ă© par l’effort de formalisation, et le risque si ce dĂ©couplage est mal fait : en effet, le prĂ©requis pour rĂ©aliser ce dĂ©coupage est que les fonctionnalitĂ©s Ă  exposer soient matures et Ă©voluent peu, ou d’une manière qui ne demande pas de faire Ă©voluer le format d’exposition. Si les fonctionnalitĂ©s que vous voulez exposer ne sont pas encore bien maĂ®trisĂ©es, vous risquez d’exposer une mauvaise interface. Dans ce cas, le surcoĂ»t de mise en place et de maintien du format pourra ĂŞtre plus important que le gain qu’il apporte, car chaque changement entraĂ®ne des coĂ»ts et/ou des dĂ©lais : communication, tests d’intĂ©gration, gestion de compatibilité…

L’utilisation de bonnes pratiques et d’outils comme le DDD peut faciliter la définition d’interfaces, mais le plus difficile pour exposer des API stables relève de la connaissance métier.

Les bénéfices d’un découplage de format se font sentir même entre sous-modules d’une même application car il rend le code plus lisible et limite les besoins de refactoring lors des évolutions. C’est une des bases de la réutilisation.

Rappelez-vous cependant qu’un découplage de format n’est presque jamais total mais qu’au mieux, il s’agit d’un couplage faible : avec le temps tous les services évoluent, et donc aussi leurs contrats.

Le découplage de localisation

Il est à la base de l’informatique distribuée : il répond au besoin de pouvoir accéder à une fonctionnalité distante sans avoir à connaître précisément le ou les dispositif(s) qui en ont la charge. Cela permet de remplacer, déplacer, ajouter ou supprimer les machines qui rendent cette fonctionnalité de manière transparente lors d’un appel.

Le moyen est la mise en place d’une couche d’indirection entre les systèmes comme un DNS ou des proxys : au lieu d’accéder à un système directement par son nom, on utilise un intermédiaire.

Cela couvre les différents types de service distribué comme CORBA, SOAP ou REST, mais aussi les bases de données non locales et les systèmes de fichiers distants.

Un découplage de localisation sans découplage de format peut être utile à l’intérieur d’un même système car il permet de déployer séparément différents composants et de pouvoir gérer la résilience. C’est le cas par exemple des bases de données où une bibliothèque cliente expose une API pour requêter le serveur.

L’inconvĂ©nient du dĂ©couplage de localisation est qu’il ajoute

  • de nouveaux types d’erreurs et d’incertitudes ;
  • de la complexitĂ© pour suivre et dĂ©bugger les traitements ;
  • de la latence pour la sĂ©rialisation / dĂ©sĂ©rialisation et les transferts rĂ©seau.

Ainsi, si votre besoin est un découplage de format et pas un découplage de localisation, travailler sur des modules d’un même applicatif est une stratégie tout à fait valide.

Cependant, le cas le plus courant, qui correspond à ce qu’on entend généralement quand on parle de découplage, correspond à un découplage de localisation et de format. C’est lui qui permet à des équipes de travailler et de déployer leurs composants en encadrant et en limitant les dépendances.

La méprise la plus courante sur le découplage consiste à penser qu’un découplage de localisation entraîne nécessairement un découplage de format. Il est vrai qu’exposer des fonctionnalités via des services force à définir un contrat de service, autrement dit une interface formelle, mais rien ne garantit que ce contrat de service permette par lui-même un réel découplage. C’est l’erreur classique des SI de services mis en œuvre sans gouvernance.

Par exemple on peut penser qu’exposer des fonctionnalités en REST et fournir une documentation Swagger permet un découplage de format car il est alors possible de les appeller avec un minimum de connaissances et d’interractions avec les personnes qui en sont responsables. Mais si ce contrat d’interface évolue toutes les semaines, le fait d’avoir un contrat de service formalisé ne vous sauvera pas d’avoir à modifier votre code à chaque fois.

Le découplage de temporalité

Il s’agit d’exécuter un traitement plus tard de manière asynchrone. Il se fait souvent au moyen d’une file de messages, d’un envoi de fichier, ou d’une base de données.

Il permet de répondre à deux besoins :

  • rendre plus rapidement la main Ă  l’appelant en remettant Ă  plus tard une partie des traitements ;
  • gĂ©rer plus facilement les pics de charge, tant que l’outil en charge de gĂ©rer les demandes d’exĂ©cution en attente n’est pas submergĂ©.

Il a trois inconvénients majeurs :

  • il faut ĂŞtre certain de ne pas perdre de demande de traitements, et de ne pas en traiter en double (ou s’arranger pour que ça n’entraĂ®ne pas de consĂ©quences nĂ©fastes) ;
  • il rend plus difficile la gestion des erreurs et de la cohĂ©rence des donnĂ©es ;
  • il rend plus difficile le monitoring du système, nĂ©cessitant souvent la mise en place d’outils de monitoring de flux.

Un découplage de temporalité sans découplage de format rend plus complexe les montées de version. En effet, il faut alors gérer la compatibilité entre les versions, ou attendre que les demandes en cours soient traitées avant de migrer l’ensemble du système. Ceci-dit, lorsque l’asynchronisme est utilisé à l’intérieur d’une seule et même application, cette approche peut être la bonne car elle évite d’avoir à se préoccuper de la gestion de compatibilité.

La mise en place d’un découplage de temporalité passe, dans la plupart des cas par l’utilisation d’un outil tiers externe à l’application (file de message, base de données…). Suivant son implémentation et sa configuration, cet outil peut fournir une forme « naturelle » de découplage de localisation. Si, dans ce cas, elle est facile à mettre en œuvre du point de vue infrastructure, cela ne veut pas dire qu’elle est gratuite car le surcoût en complexité engendrée par les nouveaux comportements à prendre en compte est bien là.

Pour conclure

À travers les trois types de découplage et leurs intersections, nous avons vu que découpler n’est pas une fin en soi mais bien un moyen de répondre à certains besoins, ce moyen ayant aussi des inconvénients, notamment des effets de bords à l’endroit où le découplage prend place. Nous avons noté aussi qu’accumuler les découplages, c’est cumuler les avantages mais aussi les inconvénients.

Le plus difficile, et qui ne peut être résumé dans un article, est de déterminer comment et à quel endroit découpler en fonction des besoins à satisfaire : cela fait plus de 30 ans que la question est ouverte. Si ajouter une certaine quantité de découplage est nécessaire dans les grands systèmes, mal s’y prendre mène parfois au désastre.

P.S. : J’ai eu l’idée de cet article après avoir lu ce texte qui est une très bonne analyse de l’utilisation de middleware de message pour gérer des tâches asynchrones.

Catégories: Blog Société

Terraform et comptes multiples dans AWS

lun, 09/04/2017 - 08:00

Disclaimer : Cet article est assez technique et peut nécessiter des connaissances sur le fonctionnement de la gestion des accès et des droits dans AWS. Pour plus d’informations sur le sujet, la documentation AWS est très complète et permet d’avoir une connaissance minimale pour aborder cet article.

Que ce soit pour des raisons de sécurité ou pour de la gestion administrative, il est possible dans AWS de créer plusieurs comptes AWS (visible dans AWS organization) reliés à un même compte maître.

Un exemple simple, vous possédez 2 comptes :

  • Un compte de production qui va faire tourner votre site web de vente en ligne par exemple. Seule l’équipe d’exploitation aura le droit de crĂ©er de l’infrastructure dessus.
  • Un compte de R&D oĂą les droits seront ouverts Ă  l’ensemble des Ă©quipes pour tester les nouveaux services AWS, des technologies, des frameworks …

Chaque compte AWS existant va alors pouvoir créer sa propre base d’utilisateurs avec des droits différents. Par exemple, Bob, faisant partie de l’équipe d’exploitation est Administrateur sur les 2 comptes et possède donc un utilisateur IAM sur chacun d’eux.

Afin de sécuriser ces accès, chaque utilisateur va donc s’authentifier avec des identifiants (Login / Mot de passe, Access / Secret Key) différents par compte. Chaque compte est alors autonome dans sa gestion des utilisateurs et dans les autorisations qui leurs sont données . Rien n’empêche Bob d’ajouter Alice en tant qu’Auditrice du compte de production.

Plus le nombre d’utilisateurs augmente, plus la gestion des droits devient complexe :

D’un point de vue utilisateur, avoir un IAM User sur chaque compte oblige à avoir un mot de passe / Access Key / Secret Key par compte.

 

Dans cet article, nous allons voir comment centraliser notre base d’utilisateurs au sein d’un compte, permettant une authentification unique sur l’ensemble des comptes. Cela nécessitera d’utiliser les rôles AWS pour gérer les droits de chacun des utilisateurs sur chacun des comptes.

Piqûre de rappel

Dans un compte AWS, nous pouvons déployer des ressources et avoir une gestion des utilisateurs. Ces utilisateurs, IAM Users pour AWS, peuvent être organisés par groupe.

Un service AWS (telle qu’une instance) peut avoir des droits sur le APIs AWS via des Roles. Un rôle donne donc le droit à un service AWS d’exécuter des appels aux APIs AWS. Chaque groupe, IAM User ou Role possède une ou plusieurs IAM policy. Elles permettent de donner ou de restreindre des droits sur les appels aux APIs AWS permettant ainsi de gérer les accès aux différents services d’AWS (par exemple : le droit de créer des Instances).

Un Role qui a des Policies peut aussi être utilisé par un IAM User pour avoir des droits dans un autre compte, ce qui s’appelle assumer un rôle dans le langage AWS. Dans cet article, nous allons décrire la procédure pour qu’un utilisateur d’un compte AWS assume un rôle dans un autre compte. Ce qui est décrit par AWS comme AWS Assume Role Cross Account.

Un compte pour les gouverner tous

“Trois comptes pour les rois Clients sous le ciel,

Sept pour les Seigneurs Ops dans leurs demeures de pierre,

Neuf pour les Dev Mortels destinés au trépas,

Un pour le Seigneur Administrateur sur son sombre trône”

J.R.R. Tolkien (version Ops)

 

Pour notre exemple, nous allons avoir seulement 2 comptes. Un compte R&D qui contiendra l’ensemble des utilisateurs ainsi que la gestion de leurs droits et un compte production qui est un second compte de l’organisation.

Nous allons créer un rôle administrateur ayant tous les droits sur le compte de production. Nous allons ensuite donner les droits aux utilisateurs du compte de R&D d’utiliser le rôle Administrateur du compte de production.

Nous aurons donc à terme, le schéma de gestion d’authentifications et d’autorisations suivant :

Terraform pour automatiser

Afin d’automatiser les deux côtés (production et R&D) de la création du rôle, nous utilisons Terraform, qui avec son approche infrastructure as code, nous permet d’avoir un code maintenable et compréhensible.

Dans les exemples de code suivants, nous allons utiliser les conventions suivantes :

  • Aws Account Id du compte R&D :”${var.retd_account_id}”
  • Aws Account Id du compte Production : ”${var.production_account_id}
  • RĂ´le que les utilisateurs utiliseront dans le compte cible : Administrator
Compte Production

Nous devons crĂ©er sur ce compte un utilisateur Administrateur possĂ©dant tous les droits sur le compte. Puis nous donnerons l’autorisation au compte maĂ®tre d’assumer le rĂ´le du compte production.

Dans un premier temps, il nous définir le provider AWS avec les crédentials AWS (Access Key et Secret Key) du compte de production. Si vous ne souhaitez pas les faire apparaître dans votre code, vous pouvez aussi les mettre en variables d’environnement.

provider "aws" {  
    access_key = "XXXXXXXXXX"  
    secret_key = "XXXXXXXXXX" 
}

Il faut créer 2 ressources dans terraform et une datasource pour répondre à notre besoin :

resource "aws_iam_role" "role" {
    name               = "Administrator"
    assume_role_policy = "${data.aws_iam_policy_document.assume_role.json}"
}

data "aws_iam_policy_document" "assume_role" {
    statement {
       actions       = ["sts:AssumeRole"]
       principals {
         type        = "AWS"
         identifiers = ["arn:aws:iam::${var.retd_account_id}:root"]
       }
   }
}

resource "aws_iam_role_policy_attachment" "attach-role" {
    role       = "${aws_iam_role.role.name}"
    policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}

Dans le code ci-dessus, la ressource “role” définit le rôle que nous allons créer sur le compte production. Dans la définition du role, nous lui donnons le nom « Administrator«  et lui attachons la policy qui va permettre d’utiliser ce rôle, décrite ci-dessous.

La datasource “assume_role” décrit quels sont les utilisateurs qui ont le droit d’assumer le rôle auquel ils sont associés. Il faut décrire les permissions (statement) qui vont constituer notre policy et qui correspondent aux différents droits de celle ci. Dans notre statement, nous déclarons que la liste de compte [« arn:aws:iam::${var.retd_account_id:root »]  (compte de R&D) possède les droits [« sts:AssumeRole »].

Les deux premières ressources ont défini le rôle ainsi que sa politique d’assumation.

La troisième ressource va associer les droits d’administrateur au rôle que l’on vient de créer.

Compte R&D

Nous venons de créer le rôle sur le compte production et tous les utilisateurs du compte maître sont maintenant éligibles à l’assumation du rôle. Nous allons maintenant gérer les utilisateurs pouvant utiliser le rôle au moyen de Terraform.

Il faut noter que si les utilisateurs du compte maître ont la policy AdministratorAccess, ils possèdent le droit d’assumer le rôle du compte production.

Nous allons maintenant exécuter des actions sur le compte de R&D. Il faut donc mettre un autre provider avec d’autres crédentials (nous verrons plus tard qu’avec les assumes roles, cela ne sera plus nécessaire).

provider "aws" {
 access_key = "YYYYYYYYY"
 secret_key = "YYYYYYYYY"
}

 

data "aws_iam_policy_document" "policy_document" {
 statement {
   effect    = "Allow"
   actions   = ["sts:AssumeRole"]
   resources = ["arn:aws:iam::${var.production_account_id}:role/Administrator"]
 }
}

resource "aws_iam_group_policy" "my_policy" {
  name     = "assume-to-production-account"
  group    = "mygroup"
  policy   = "${data.aws_iam_policy_document.policy_document.json}"

}

Dans la première ressource Terraform nous déclarons une policy qui autorise d’assumer la liste de rôles [« arn:aws:iam::${var.production_account_id}:role/Administrator »] (compte production), puis nous attachons, dans la deuxième ressource, cette policy au groupe d’utilisateurs « mygroup ». Il est aussi possible d’attacher la policy à un seul utilisateur en utilisant la ressource ”aws_iam_user_policy” de Terraform.

Vous avez maintenant un rôle administrateur assumable par un groupe d’utilisateurs présents sur un seul et unique compte, vous permettant de gouverner vos utilisateurs et leurs droits plus facilement.

 

“Un anneau pour les gouverner tous. Un anneau pour les trouver,

Un anneau pour les amener tous et dans les ténèbres les lier”

J.R.R. Tolkien

Utilisation de notre Assume RĂ´le

Maintenant que tout est configuré, comment peut-on utiliser le rôle créé pour se connecter à notre compte production?

La console AWS

Dans la console web, nous pouvons voir sous le nom de l’utilisateur, le champ changer de rôle, ce lien va nous permettre d’assumer le rôle.

Deux informations sont nécessaires :

  • Compte : soit l’Id du compte, soit l’alias du compte AWS cible
  • RĂ´le : le rĂ´le Ă  assumer sur le compte cible

Une fois l’action faite, nous somme maintenant sur le compte production.

Le CLI

Avec la Command Line Interface, on peut faire un assume role via la ligne de commande :

$ aws sts assume-role --role-arn arn:aws:iam::<Production-Account-ID>:role/Administrator --role-session-name my-username

Cette commande renvoie les AccessKeyId, SecretAccessKey et SessionToken qu’on pourra exporter en variables d’environnement pour effectuer les actions sur le compte production.

Terraform

Avec Terraform, lors de la définition du provider AWS, il est possible de lui faire assumer un rôle :

# Provider Compte de Production

provider "aws" {
 access_key = "YYYYYYYYY"
 secret_key = "YYYYYYYYY"
 assume_role {
   role_arn = "arn:aws:iam::${var.production_account_id}:role/Administrator"
 }
 region     = "eu-west-1"
}

Toutes les ressources créées ensuite seront exécutées avec le rôle que nous avons créé sur le compte production, ici, administrateur.

Conclusion

Nous avons vu comment mettre en place des Assume Role Cross-Account Ă  travers Terraform et comment les utiliser via la console Web, le CLI et Terraform.

L’utilisateur n’a besoin que d’un accès au compte de R&D et passe par des Assume Role pour faire des actions sur les autres comptes.

AWS Organization vient par ailleurs de sortir avec la promesse de pouvoir créer des comptes via des APIs. Quand on vient créer un compte via Organization, on donne le nom d’un rôle à la création et AWS se charge de faire la création de la Policy, du Role … Ce rôle permet depuis le compte de R&D de faire un Assume Rôle sur ce nouveau compte. Seul la configuration du compte R&D est alors nécessaire.

 

Articles suggested :

  1. Créer des instances AWS qui ont accès à Internet sans IP publique avec Terraform
  2. Le DÉFI DES BOG’OPS : notre recette du Team Spirit
  3. Le DÉFI DES BOG’OPS : Les coulisses

Catégories: Blog Société

Le demi-cercle (Ă©pisode 1)

ven, 09/01/2017 - 14:01

Ça commence par la naĂŻvetĂ©, par une sorte d’ignorance bĂ©nie. On crĂ©e du code sans ĂŞtre conscient des consĂ©quences, et de la nĂ©cessitĂ© d’un retour d’information sur ce code. On bâtit naĂŻvement une tour, avec ce qu’on trouve ici et lĂ . Quand la tour frĂ©mit, on devient soudain extra-prudent, mais alors on prend un peu plus de temps pour chaque chose, et on subit la pression des clients, et du management.

La tour s’Ă©croule; on la remonte, Ă  la hâte. On n’a pas le temps d’assurer la qualitĂ© parce qu’on a trop de problèmes imprĂ©vus Ă  rĂ©soudre parce qu’on a un faible niveau de qualitĂ© parce qu’on n’a pas le temps d’assurer la qualitĂ©. On perd son calme, on agit de façon prĂ©cipitĂ©e, dĂ©sordonnĂ©e, irrationnelle.

On n’observe pas ce qu’il faudrait, on ne mesure pas les bons phĂ©nomènes, et on en tire par consĂ©quent des conclusions fausses, imaginaires. On recherche ce qui cloche en amont, dans les estimations, dans les prĂ©requis, au lieu d’Ă©tudier ce qui nous ralentit le plus. On dĂ©veloppe une sorte de mauvaise foi, qui sert Ă  tout expliquer. On est sur la dĂ©fensive.

Pendant ce temps, le code s’enfonce doucement mais sĂ»rement dans l’entropie. Fuite en avant.

Pour remĂ©dier Ă  ce problème, que faudrait-il faire ? Plus on attend, et plus les contre-mesures sont coĂ»teuses, l’ampleur des travaux impressionne. Observez quelque chose qui subit une dĂ©sorganisation progressive. La pelote de chargeurs et de rallonges derrière le bureau qui grossit et s’entremĂŞle un peu plus Ă  chaque branchement et dĂ©branchement. Une cuisine sale.

Le problème se complexifie avec le temps. On connaĂ®t mal les mĂ©canismes rĂ©gulateurs qui permettraient de rĂ©sorber ce dĂ©sordre. Le problème change d’Ă©tage : on passe d’un problème de qualitĂ© Ă  un problème de rĂ©sultats, Ă  un problème de crĂ©dibilitĂ©, Ă  un problème de dĂ©fensivitĂ©. Ça devient une affaire, un conflit, une bataille.

Et quelqu’un lance : « mais si le code pouvait parler, que dirait-il ? »

Alors on essaye de faire parler le code, avec des outils, qui rĂ©vèlent l’Ă©tendue des dĂ©gâts, qui calculent la dette technique. Calculer la dette technique, c’est au moins faire quelque chose. C’est comme les congrès sur le rĂ©chauffement climatique. Une rĂ©flexion sur les consĂ©quences, quand on ne sait pas empĂŞcher les causes. Et pour cause, parce que chaque cause est un micro-dĂ©tail dans le grand tableau du dĂ©sastre. Chaque cause demanderait qu’on apprenne un geste nouveau : le geste prĂ©cis qui Ă©vite les futures consĂ©quences.

Le code d’un programme, tel qu’il est prĂ©sentĂ© dans les livres qui chantent l’Art du Code, est une cathĂ©drale de logique. Mais pour le « noob » fraĂ®chement embauchĂ© dans une Ă©quipe de « TMA », le code est un champ de mines, un potentiel bourbier. C’est une cacophonie d’effets pervers, de consĂ©quences inattendues dans laquelle les effets composĂ©s de chaque dĂ©cision inconsidĂ©rĂ©e, de chaque ajout effectuĂ© Ă  la hâte, Ă  l’aveugle, Ă  la va-comme-je-te-pousse, se sont multipliĂ©s pour former une sorte de bombe logique nuclĂ©aire.

Le programme plante. EnchevĂŞtrement de causes multiples.

Le programme plante lors d’un test, ce qui empĂŞche un autre test, lequel test s’il avait lieu rĂ©vĂ©lerait la prĂ©sence d’un Ă©cart entre les rĂ©sultats actuels et attendus, lequel Ă©cart mènerait sinueusement mais sĂ»rement Ă  la dĂ©tection d’une erreur de logique dans le code qui produit les calculs, erreur qui si elle Ă©tait discutĂ©e entre l’Ă©quipe de dĂ©veloppeurs et l’Ă©quipe de test, montrerait que certaines règles de calcul ne sont pas partagĂ©es, sujettes Ă  interprĂ©tation, parce que les deux Ă©quipes ne voient pas le produit de la mĂŞme façon.

Ă€ chaque Ă©tage il y a un dĂ©tonateur. Que croyez-vous qu’une Ă©quipe de professionnels du dĂ©minage ferait ? Exactement. C’est exactement le contraire, que fait l’Ă©quipe de dĂ©veloppement. Personne ne prend le temps de revenir suffisamment en arrière et suffisamment en dĂ©tail sur ce qui se produit. Au lieu de cela, un dĂ©veloppeur est occupĂ© Ă  rĂ©parer le programme, pendant que d’autres commentent la situation, ou font du sur-place en attendant une dĂ©cision du management.

Devant ma logique, celle qui consiste à dire après un incident :

prenons le temps de rĂ©tro-analyser, avec rigueur, et sans blâmer, ni dĂ©cider Ă  la hâte, ce qui a pu se produire, et trouvons des contre-mesures efficaces afin d’Ă©viter des incidents similaires Ă  l’avenir…

…devant ma logique, mes collègues sont perplexes : ça ne ressemble pas Ă  une action qu’on peut mener sans dĂ©penser au moins quatre heures. « Il vaudrait mieux… », « De toutes façons… », « Oui, mais… », et finalement le manager tranche, parce qu’il a le client qui fulmine au bout du fil. « On rĂ©pare et on re-livre. »

Nous vivons dans un monde complexe, et il paraît que le logiciel mange le monde. Et la complexité mange le logiciel. Et le monde, du moins celui dans lequel on démarre, on reprend, on défait, et on relance des projets de développement logiciel, ce monde « surfe » sur la complexité.

Nous recrutons des dĂ©veloppeurs et dĂ©veloppeuses ninjas, multi-compĂ©tents, full-stack serait un plus, ayant le sens de l’aventure, et l’esprit d’Ă©quipe, jeunes, des geeks, pour un projet tout-terrain!

Au rythme où vont les projets, il nous faut des équipes toujours plus « tout-terrain ».

Comment pourrais-je faire du tout-terrain si je ne maĂ®trise mĂŞme pas la conduite du vĂ©hicule dans un parking ? Je fais vingt kilomètres, et bim. C’est l’accident bĂŞte.

Les gestes de base, on les Ă©voque : « il aurait fallu… », on liste les « best practices », on produit des prĂ©sentations, et on prescrit des « transformations ». Mais on sait parfaitement — puisqu’on serait incapable, comme ça, Ă  brĂ»le-pourpoint, de restaurer un fauteuil Louis XVI, d’exĂ©cuter une sonate, ou de dispenser des soins après un pontage coronarien — on sait parfaitement qu’

Il faut dix ans pour faire un expert

… c’est-Ă -dire que les gestes de base requièrent du temps pour devenir l’habitude, le savoir profondĂ©ment ancrĂ© qui distingue le professionnel de l’amateur mĂŞme passionnĂ©.

SuggĂ©rer Ă  une Ă©quipe de changer ses habitudes, alors que le projet est en crise ? Avec l’accident au carrefour, on a certes des arguments plus convaincants pour un temps; on a de bien de meilleures raisons d’envisager un tel changement, mais changer c’est dĂ©jĂ  tellement difficile lorsque tout va bien…

Ce n’est plus le moment d’apprendre les gestes de base.

Si le code pouvait parler, que dirait-il ? Donnons-lui la parole :


Si tu veux comprendre ce que je décris, accroche-toi.
Ici je dis la chose comme ci, et lĂ  je dis la chose comme ça. C’est comme ça.
Je me répète tellement que je peux tuer ton attention, ton intérêt, ton enthousiasme, à longueur de page lue.
Je sais, c’est imprononçable, compliquĂ©, tordu, mais attends ce n’est pas tout.
Comment ça, est-que ça marche ? Est-ce que tu travaillerais dans ce projet si ça ne marchait pas ? Voyons.
Vous pouvez faire une mine dĂ©goĂ»tĂ©e, c’est comme ça.
Essaie de changer cette mĂ©thode, rien que pour voir. J’en ai matĂ© des plus durs que toi.
Pour fonctionner correctement tout en restant honnĂŞtement maintenable, j’aurais besoin de vingt fois le temps que vous me consacrez actuellement, vous m’entendez, vingt fois.
Oubliez vos rĂŞves de simplicitĂ© et d’efficacitĂ©.
Au fond, tu n’es ni un artiste, ni un scientifique, ni un penseur, ni un ingĂ©nieur.
Tu es un développeur ninja tout-terrain.

Alors on va gĂ©rer le retard. On va gĂ©rer la dette technique. On va gĂ©rer la relation avec le client. Il faut gĂ©rer, parce que ça ne se fera pas tout seul. Il n’y a que de la gestion, parce qu’il n’y a pas de solution. Il n’y a pas moyen de faire en sorte qu’un projet en retard ne soit plus en retard. On le sait, mais on va le gĂ©rer. Trente-six mois de code propre et qui passe ses tests, c’est dĂ©jĂ  une construction très dĂ©licate, qui demande de l’attention, tous les jours. Demandez Ă  ceux qui savent. Alors que dire de trente-six mois de code Ă©crit Ă  la rache ? C’est une zone sinistrĂ©e pour les annĂ©es qui viennent. C’est — encore une fois — une bombe logique nuclĂ©aire.

Et si c’Ă©tait le moment oĂą l’on pose son ouvrage et l’on rĂ©flĂ©chit ? Si c’Ă©tait le moment oĂą l’on commence Ă  changer un peu la manière dont on fait les choses ?

Catégories: Blog Société

MythBuster: Apache Spark • Épisode 2: Planification et exécution d’une requête SQL

mar, 08/08/2017 - 14:02


Nous poursuivons aujourd’hui notre sĂ©rie d’articles dĂ©diĂ©e Ă  la dĂ©mystification de Spark et plus particulièrement au moteur d’exĂ©cution Tungsten.

Pour rappel, dans l’Ă©pisode prĂ©cĂ©dent, nous sommes partis d’une requĂŞte SQL sous forme de String que nous avons d’abord dĂ©coupĂ©e en une instance de Seq[Token] grâce Ă  notre classe Lexer, puis en une instance d’AST grâce Ă  notre classe Parser. L’arbre formĂ© par l’AST obtenu en sortie permet d’avoir une structure avec laquelle il est relativement simple d’intĂ©ragir au travers de notre code.

Dans cet Ă©pisode, nous allons chercher Ă  transformer cet AST en un plan d’exĂ©cution, ce qui sera l’occasion de plonger au coeur du moteur d’optimisation Catalyst, dont nous dĂ©taillerons certains mĂ©canismes utilisĂ©s dans la version 2.2 de Spark.

Nous aurons alors tout le matĂ©riel nĂ©cessaire pour mettre en place un modèle de gĂ©nĂ©ration de code similaire Ă  celui de Spark, que nous traiterons dans le troisième Ă©pisode cette sĂ©rie d’articles.

L’ensemble du code dont nous allons parler ci-dessous est disponible dans notre dĂ©pĂ´t GIT. N’hĂ©sitez-pas Ă  aller y faire un tour !

Planification Un peu de théorie…

Reprenons notre cheminement. Grâce Ă  l’Ă©pisode prĂ©cĂ©dent, nous sommes capables d’avoir une reprĂ©sentation sĂ©mantique d’une requĂŞte SQL exprimĂ©e au dĂ©part sous forme de texte. L’AST, produit en sortie du Parser, permet en effet de donner un sens Ă  la requĂŞte d’origine, et de l’exploiter plus facilement grâce Ă  sa structure d’arbre.

Maintenant que nous sommes capables de donner un sens Ă  cette requĂŞte au travers du code, nous allons devoir dĂ©terminer quelle est la meilleure façon de l’exĂ©cuter. C’est lĂ  qu’interviennent successivement le Logical Plan (ou plan d’exĂ©cution logique) et le Physical Plan (ou plan d’exĂ©cution physique).

Plan d’exĂ©cution logique

Il s’agit d’un arbre (oui, encore un) qui modĂ©lise l’ensemble des opĂ©rations ensemblistes nĂ©cessaires pour exĂ©cuter la requĂŞte SQL. La Projection, le Filtre, la Jointure sont des exemples d’opĂ©rations ensemblistes qui peuvent faire partie du plan d’exĂ©cution logique.

En clair, le plan d’exĂ©cution logique dĂ©crit l’ensemble des Ă©tapes nĂ©cessaires pour exĂ©cuter une requĂŞte.

Plan d’exĂ©cution physique

Le plan d’exĂ©cution physique dĂ©crit la manière dont ces opĂ©rations ensemblistes vont ĂŞtre rĂ©alisĂ©es. Il se distingue en cela du plan d’exĂ©cution logique qui, lui, ne dĂ©crit que de façon abstraite les opĂ©rations Ă  effectuer.

Concrètement, le plan d’exĂ©cution physique comprend l’ensemble du code qui va ĂŞtre utilisĂ© pour exĂ©cuter une requĂŞte SQL.

Un Join peut p. ex. être réalisé par :

  • Un Hash Join (on va indexer dans une map toutes les valeurs de l’une des deux tables, et itĂ©rer sur la seconde en rĂ©alisant un lookup dans la map crĂ©Ă©e auparavant) : cet algorithme est efficace lorsque l’une des deux tables est suffisamment petite pour ĂŞtre indexĂ©e en mĂ©moire. Dans Spark, on part alors plus spĂ©cifiquement de Broadcast Hash Join.
  • Ou bien un Nested Loop Join (on va rĂ©aliser deux boucles imbriquĂ©es avec une comparaison Ă  chaque sous-itĂ©ration) : cet algorithme est applicable quelle que soit la taille des tables en jeu, mais nĂ©cessite de re-parcourir les tables Ă  chaque fois (sauf si elles sont mises en cache, et l’on parle alors de Block Nested Loop)

Ces deux types de Join ne sont pas exhaustifs et sont plutĂ´t les reprĂ©sentants simplistes de deux familles : il existe en effet de nombreuses autres implĂ©mentations qui reposent sur des optimisations de l’un ou l’autre des algorithmes (p. ex. le Sort-Merge Join est semblable au Nested Loop Join mais est plus rapide sur des tables dĂ©jĂ  triĂ©es par leurs clĂ©s de jointure).

L’Ă©tape qui va permettre de passer du plan d’exĂ©cution logique au plan d’exĂ©cution physique correspond Ă  ce que l’on appelle la planification : c’est lors de cette Ă©tape que vont ĂŞtre mis en relation le modèle logique de donnĂ©es et le modèle physique. Sur la plupart des SGBDR, il est possible de rĂ©cupĂ©rer ce plan d’exĂ©cution en utilisant les commandes EXPLAIN ou EXPLAIN PLAN.

DĂ©tail d’un plan d’exĂ©cution physique sous Oracle

Durant l’Ă©tape de planification, nous avons quasiment une relation 1-pour-1 entre le Logical Plan et le Physical Plan, et c’est lors de l’Ă©tape d’optimisation que le Physical Plan sera susceptible d’Ă©voluer pour obtenir de meilleures performances lorsque notre requĂŞte sera exĂ©cutĂ©e…

Optimisations

Les optimisations correspondent Ă  un ensemble de transformations que l’on va appliquer Ă  nos plans d’exĂ©cution logiques et physiques de manière Ă  accĂ©lĂ©rer la requĂŞte. C’est en appliquant des optimisations sur nos diffĂ©rents plans d’exĂ©cution que l’on comprend tout l’intĂ©rĂŞt de distinguer l’aspect logique de l’aspect physique :

  • Dans le cas d’un plan d’exĂ©cution logique, les optimisations ne vont ĂŞtre qu’ensemblistes ou bien rĂ©alisĂ©es sur les Expressions. Il s’agit d’optimisations gĂ©nĂ©riques pour lesquels peu (voire pas) d’informations sur la donnĂ©e sont nĂ©cessaires. P. ex., sans connaissance prĂ©alable du contexte, on sait qu’il est intĂ©ressant de regrouper plusieurs filtres successifs en un seul, ou bien Ă©valuer des constantes, factoriser des termes…
  • Dans le cas du plan d’exĂ©cution physique, les optimisations vont ĂŞtre rĂ©alisĂ©es sur la manière dont on accède aux donnĂ©es (p. ex. dĂ©porter un filtre directement dans la base de donnĂ©es source quand c’est intĂ©ressant, on parle alors de Predicate Pushdown) ainsi que sur la manière dont les relations vont ĂŞtre rĂ©alisĂ©es en fonction des mĂ©tadonnĂ©es des tables en jeu (les stats d’Oracle, ça vous rappelle quelque-chose ?)
Notre implémentation ! Petit focus sur le trait TreeNode

Avant de rentrer dans le détail en tant que tel, nous allons commencer par décrire le trait TreeNode défini dans le fichier TreeNode.scala, qui va nous servir de structure de base à plusieurs reprises.

Ce trait modélise une structure en arbre habituelle : chaque instance de type TreeNode peut faire référence entre 0 et plusieurs fils eux aussi de type TreeNode. Cependant, deux propriétés rendent ce trait vraiment intéressant :

  • Chaque instance d’une classe implĂ©mentant TreeNode doit ĂŞtre immutable (et ne peut donc pas ĂŞtre altĂ©rĂ©e)
  • Tout comme une instance de Seq peut ĂŞtre transformĂ©e Ă  l’aide de la mĂ©thode map(), une instance d’une classe implĂ©mentant TreeNode peut-ĂŞtre transformĂ©e Ă  l’aide de la la mĂ©thode transformDown()pour modifier l’ensemble des enfants de l’arbre
  • Le trait TreeNode possède une annotation self-type du type qui va l’implĂ©menter (ce qui va nous permettre d’utiliser ce trait de manière totalement transparente dans notre code, sans jamais le rĂ©fĂ©rencer)

Nous pouvons p. ex. voir dans le fichier TreeSpec.scala qu’il est possible de dĂ©crire la règle de factorisation d’entier et l’appliquer sur une expression quelconque en quelques lignes seulement :

expression.transformDown {
  case Add(Multiply(a, b), Multiply(c, d)) => (a, b, c, d) match {
    case _ if a == c => Multiply(a, Add(b, d))
    case _ if a == d => Multiply(a, Add(b, c))
    case _ if b == c => Multiply(b, Add(a, d))
    case _ if b == d => Multiply(b, Add(a, c))
  }
}

On traverse notre arbre d’expressions, et chaque fois qu’un noeud correspond Ă  la forme Add(Multiply, Multiply), on applique notre factorisation. Les autres noeuds restent inchangĂ©s ! Notons que l’on s’appuie fortement sur la notion de PartialFunction de Scala.

Dans la suite de cet article, nous allons voir que de nombreux concepts reposent sur des structures de donnĂ©es en arbre. C’est pourquoi ce trait TreeNode est central dans notre implĂ©mentation, car bon nombre de nos classes vont s’appuyer dessus.

Les classes LogicalPlan, PhysicalPlan et QueryPlanner

Tout comme Spark, nous nous reposons fortement sur les case classes et la notion de Pattern Matching pour convertir notre AST de départ en une instance de LogicalPlan.

Le trait LogicalPlan

Ce trait LogicalPlan et l’ensemble de ses implĂ©mentations est dĂ©fini au sein du fichier LogicalPlan.scala. Il est intĂ©ressant de noter que ce trait Ă©tend TreeNode (et correspond donc Ă  une structure en arbre) mais n’impose l’implĂ©mentation d’aucune mĂ©thode : il ne s’agit en effet que d’une reprĂ©sentation sĂ©mantique de notre requĂŞte SQL. La structure d’arbre est quant Ă  elle utile pour effectuer facilement des transformations qui vont servir Ă  optimiser le plan logique d’exĂ©cution.

trait LogicalPlan extends TreeNode[LogicalPlan]

C’est la mĂ©thode apply de notre companion object LogicalPlan qui va ĂŞtre en charge de transformer notre instance d’AST, obtenu en sortie du parsing, en une instance de LogicalPlan. Ou plus prĂ©cisĂ©ment en un Try[LogicalPlan] car la conversion peut Ă©chouer si notre AST n’est pas valide.

C’est lui qui fait le lien entre la sĂ©mantique de notre requĂŞte SQL et sa reprĂ©sentation en termes d’opĂ©rateurs. Il devient ainsi possible de raisonner d’un point de vue purement logique au moment d’optimiser notre requĂŞte.

La classe LogicalPlanOptimizer.scala a pour but de rassembler l’ensemble de ces règles d’optimisation (qui sont encore en cours de dĂ©veloppement).

Le trait PhysicalPlan

Le trait PhysicalPlan et l’ensemble de ses implĂ©mentations sont prĂ©sents au sein du fichier PhysicalPlan.scala. Le trait est relativement semblable Ă  celui dĂ©fini ci-dessus, hormis qu’il est cette fois nĂ©cessaire d’implĂ©menter la mĂ©thode execute() qui doit retourner un Iterator[InternalRow]. Elle sera appelĂ©e lors de l’exĂ©cution effective de notre requĂŞte. Nous parlerons plus en dĂ©tail de cette mĂ©thode par la suite.

trait PhysicalPlan extends TreeNode[PhysicalPlan] {
    def execute() : Iterator[InternalRow]
}

Notons Ă  titre d’exemple la classe CSVFileScan qui va lire des lignes d’un fichier CSV et les retourner sous forme d’InternalRow : c’est grâce Ă  cette classe que nous allons pouvoir utiliser des fichiers .csv comme tables.

Les optimisations du plan d’exĂ©cution physique sont listĂ©es dans PhysicalPlanOptimizer.scala et dĂ©finies dans package.scala. C’est justement au sein de ces règles d’optimisation que l’on se rend compte de la puissance du trait TreeNode associĂ© au Pattern Matching de Scala.

La classe QueryPlanner

La conversion entre le LogicalPlan et le PhysicalPlan est rĂ©alisĂ©e Ă  l’aide du QueryPlanner Ă  cet endroit dĂ©fini dans le fichier QueryPlanner.scala.

Du côté de chez Spark

Le mĂ©canisme que nous avons mis en place est vraiment très similaire Ă  celui que Spark propose au sein de son moteur d’optimisation Catalyst. Les plans logiques et physiques sont reprĂ©sentĂ©s par des arbres sur lesquels des optimisations peuvent ĂŞtre effectuĂ©es.

De même, les Expressions sont traités comme des arbres à part entière (qui implémentent donc la classe TreeNode). Pour simplifier, les Expressions correspondent aux opérations appliquées sur les colonnes notamment pour les Projections ou les Filters.

P. ex. pour la requête : SELECT 2 * (longueur + 1) FROM rectangles, le terme 2 * (longueur + 1) correspond à une Expression qui peut être représentée sous la forme suivante Multiply(Constant(2), Add(Literal("longueur"), Constant(1))).

L’avantage, encore une fois, est que l’on peut appliquer facilement des optimisations sur les Expressions en effectuant des opĂ©rations sur les arbres.

Dans Spark, les optimisations s’appliquent donc Ă  la fois aux opĂ©rateurs et aux Expressions. C’est la classe Optimizer dĂ©finie dans Optimizer.scala qui rĂ©pertorie l’ensemble des optimisations et qui sert de classe de base du SparkOptimizer (c’est ici que l’on trouve l’ensemble des règles appliquĂ©es !).

La classe SparkPlan correspond à la classe de base des classes Exec qui correspondent à nos PhysicalPlan est définie dans le fichier SparkPlan.scala.

Enfin, le fichier SparkStrategies.scala contient l’ensemble des stratĂ©gies qui permettent de passer d’un plan d’exĂ©cution logique Ă  un plan d’exĂ©cution physique (les classes Exec) en se basant sur le QueryPlanner et (avec comme implĂ©mentation le SparkPlanner, les stratĂ©gies utilisĂ©es sont d’ailleurs dĂ©clarĂ©es au sein de ce dernier).

Pour aller plus loin… User Defined Fonctions et optimisations

Comme beaucoup de moteurs SQL, Spark offre la possibilitĂ© de dĂ©finir ses propres fonctions Ă  appliquer dans des requĂŞtes SQL : les User Defined Fonctions ou UDF. En pratique, lorsqu’on dĂ©finit une UDF cela correspond Ă  crĂ©er une nouvelle implĂ©mentation de l’interface Expression, comme le montre la classe ScalaUDF. La fonction dĂ©finie par l’utilisateur sert alors d’implĂ©mentation Ă  la mĂ©thode eval du trait Expression.

De ce fait, les UDF offrent une grande souplesse pour le dĂ©veloppeur qui peut utiliser n’importe quel code Scala au sein de sa requĂŞte SQL. NĂ©anmoins, cette libertĂ© a un coĂ»t puisque le moteur Catalyst n’est plus capable de comprendre le contenu de l’Expression et d’appliquer des optimisations dessus : une UDF est perçue par Spark comme une boĂ®te noire. Il est donc important de les utiliser avec parcimonie pour Ă©viter des problèmes de performances (voir Performance Considerations).

NĂ©anmoins, depuis Spark 2.0, il est possible d’implĂ©menter ses propres règles d’optimisation et ainsi tenir compte de ses propres UDF (dont seul l’utilisateur connait le contenu).

Apache Calcite… Le retour !

Nous avons déjà mentionné Apache Calcite dans le premier article de cette série étant donné que ce framework permet de résoudre de manière générale les problématiques inhérentes au parsing SQL…

Il se trouve que Calcite possède Ă©galement son propre moteur d’optimisation (qui est, lui aussi, utilisĂ© par Hive).

Cost Based Optimizer

Jusqu’Ă  la version 2.2 de Spark, l’optimisation des plans d’exĂ©cution se basait uniquement sur des règles indĂ©pendantes des donnĂ©es elles-mĂŞmes, et ignorait certaines informations comme :

  • La cardinalitĂ© des lignes qui vont ĂŞtre vont ĂŞtre produites par un plan d’exĂ©cution (p. ex., pour le produit cartĂ©sien va produire N Ă— M lignes si le premier fils produit N lignes et le seconds fils M lignes)
  • La manière dont les donnĂ©es sont distribuĂ©es (p. ex., combien y a-t-il d’Ă©lĂ©ments distincts)

En fonction de ces informations supplĂ©mentaires, il peut ĂŞtre intĂ©ressant d’optimiser diffĂ©remment le plan d’exĂ©cution : plus le contexte de la requĂŞte exĂ©cutĂ©e est connu, plus l’optimisation sera pertinente. On parle alors de Cost Based Optimization.

Ces notions doivent rappeler des souvenirs aux utilisateurs de SGBDR… On retrouve en effet ici les fameuses statistiques qui ont causé du fil à retordre à nos DBA préférés.

Depuis Spark 2.2, on voit apparaĂ®tre dans le code des fonctionnalitĂ©s relatives Ă  ces statistiques (on peut p. ex. voir dans Statistics.scala la manière dont elle sont stockĂ©es et elle seront a priori exploitĂ©es dans QueryExecution.scala qui pour l’instant prend le premier plan d’exĂ©cution physique).

Execution

Toujours en partant de notre requĂŞte SQL sous forme de String, nous avons Ă  prĂ©sent un PhysicalPlan qui dĂ©crit de manière exhaustive ce qui doit ĂŞtre rĂ©alisĂ© pour obtenir un rĂ©sultat… Il n’y a donc plus qu’Ă  exĂ©cuter la requĂŞte ! Et c’est pour cela que nous allons implĂ©menter la fameuse mĂ©thode execute()…

Un peu de théorie La notion de Volcano Model

Le plan d’exĂ©cution physique modĂ©lise le parcours de chaque tuple d’origine (qui est issu d’une table) pour aboutir (ou non, p. ex. s’il est filtrĂ© au niveau d’une clause WHERE) au rĂ©sultat de la requĂŞte.

Depuis plus d’une vingtaine d’annĂ©e, la manière dont les plans d’exĂ©cution physique sont executĂ©s repose sur la notion de Volcano Model, dont les bases ont Ă©tĂ© thĂ©orisĂ©es au sein de ce papier (qui date de 1994 !). Oracle, PostgreSQL, etc. reposent en grande partie sur cette notion.

Le Volcano Model se base sur trois principes :

  • Faire correspondre Ă  chacune des Ă©tapes du plan d’exĂ©cution logique une Ă©tape du plan d’exĂ©cution physique
  • Le deuxième principe repose sur le fait que toutes les Ă©tapes du plan d’exĂ©cution physique sont chaĂ®nĂ©es (chaque Ă©tape mère est associĂ©e Ă  une ou plusieurs Ă©tapes filles) : Ă  chaque Ă©tape correspond donc un opĂ©rateur
  • Enfin, Ă©tant donnĂ© que l’objectif du plan d’exĂ©cution physique est d’Ă©valuer la requĂŞte, le troisième principe spĂ©cifie que l’on va laisser le parent contrĂ´ler le flux des tuples qui vont naviguer d’un opĂ©rateur Ă  l’autre en demandant aux enfants les tuples un par un

Il est intéressant de noter que le Volcano Model repose sur le principe de pull de la donnée : le parent demande aux enfants le prochain tuple à traiter.

Principe du Volcano Model

L’exemple ci-dessus modĂ©lise donc un filtre Filter appliquĂ© sur le produit cartĂ©sien Cartesian Product de deux sources de donnĂ©es Scan : pour peu que ce filtre permette de faire le lien entre nos deux sources de donnĂ©es, nous avons ici l’exemple d’une jointure naĂŻve rĂ©alisĂ©e avec la combinaison de 3 opĂ©rateurs !

Notre implémentation ! Nos types de base

Nous allons commencer par dĂ©finir le type InternalRow qui va correspondre Ă  une ligne. Une ligne est composĂ©e de colonnes de type InternalColumn (qui peut ĂŞtre issu d’une table identifiĂ©e par son nom de type RelationName ou d’une expression) identifiĂ©e par un nom de type ColumnName et Ă  chaque colonne correspond une valeur de type Any.

De ce fait :

type Name = String
type ColumnName = Name
type RelationName = Name
type InternalColumn = (Option[RelationName], ColumnName)
type InternalRow = Map[InternalColumn, Any]
La mĂ©thode execute() L’idĂ©e gĂ©nĂ©rale

Le passage d’une instance de LogicalPlan vers une instance de PhysicalPlan a Ă©tĂ© effectuĂ© Ă  l’aide du QueryPlanner dont nous avons parlĂ© ci-dessus. Nous n’avons par contre pas parlĂ© de l’objectif de la mĂ©thode execute() de notre trait PhysicalPlan qui doit ĂŞtre dĂ©finie au sein des implĂ©mentations.

Le contrat Ă©tabli par les opĂ©rateurs du Volcano Model ressemble beaucoup Ă  celui rempli par l’interface Iterator de Scala… En effet, chaque opĂ©rateur va rĂ©cupĂ©rer 1 par 1 les tuples qu’il va traiter. Eh bien capitalisons lĂ -dessus en imposant Ă  cette mĂ©thode execute()de retourner un Iterator[InternalRow].

Pour rappel :

trait Iterator[E] {

    def next(): E

    def hasNext: Boolean

}
Implémentation du Filter

On a notre base de travail : notre trait est défini. On peut maintenant implémenter un Filter :

case class Filter(child: PhysicalPlan, predicate: Predicate) extends PhysicalPlan {

  override def execute(): Iterator[InternalRow] = {

    child.execute().filter(predicate.evaluate)

  }

}

On constate alors que Filter fait rĂ©fĂ©rence Ă  l’opĂ©rateur prĂ©cĂ©dent nommĂ© child. En effet, pour chaĂ®ner l’ensemble des opĂ©rateurs, chacun d’eux va devoir rĂ©fĂ©rencer celui qui le prĂ©cède, comme un arbre. Filter collecte ainsi 1 par 1 les tuples renvoyĂ©s par l’opĂ©rateur qui le prĂ©cède et applique sur chacun d’eux un Predicate (i.e. vĂ©rifie si une condition est respectĂ©e).

Puisque chaque opĂ©rateur appelle le prĂ©cĂ©dent, le dernier opĂ©rateur Ă  traiter un tuple est celui qui va servir de point d’entrĂ©e au traitement : on demande au dernier opĂ©rateur de lancer le traitement, qui va alors demander Ă  son (ou ses) enfants de lancer leurs propres traitements et ainsi de suite. On retrouve bien ici le mode pull dont nous parlions prĂ©cĂ©demment. Ce fonctionnement n’est en fait qu’une consĂ©quence direct de la description de notre plan physique sous forme d’arbre : c’est le noeud racine de l’arbre qui rĂ©fĂ©rence tous les autres. Lorsque l’on appelle la mĂ©thode execute() sur ce noeud, on appelle execute() sur l’ensemble de ses enfants, et ainsi de suite jusqu’Ă  arriver Ă  la source de donnĂ©es.

Chaque tuple traitĂ© doit ainsi traverser chacune des classes de chacun des opĂ©rateurs du plan physique. La modularitĂ© du modèle vient du fait qu’il est facile de chaĂ®ner de nouveaux opĂ©rateurs entre eux, et de faire des optimisations notamment en modifiant l’ordre des diffĂ©rents opĂ©rateurs.

DĂ©monstration de bout en bout

Du côté de chez Spark…

La classe SparkPlan qui correspond à la classe de base des classes Exec qui correspondent à nos PhysicalPlan est définie au sein de SparkPlan.scala

Il est intĂ©ressant de noter que, dans Spark, les RDD sont utilisĂ©s, alors notre implĂ©mentation se base sur des Iterator, et c’est ça la force de Spark SQL puisque le calcul va ĂŞtre distribuĂ© ! L’itĂ©rateur est ainsi partagĂ© entre plusieurs machines. Mais ça, ça sera pour un prochain article…

Pour aller plus loin Predicate Pushdown

Une autre optimisation intĂ©ressante que propose Spark en termes de plan d’exĂ©cution physique est le fait de pouvoir dĂ©lĂ©guer certaines opĂ©ration directement Ă  la source de donnĂ©es : c’est ce que l’on appelle le Predicate Pushdown.

Typiquement, si les donnĂ©es d’un DataFrame sont issues d’une base de donnĂ©es, il est possible d’effectuer le filtre directement au niveau de la base de donnĂ©es et de ne renvoyer Ă  Spark que les donnĂ©es qui ont pu valider ce premier filtre. La quantitĂ© de donnĂ©es Ă  transfĂ©rer est donc rĂ©duite.

RDD vs Dataframe vs Dataset

Maintenant que nous voyons Ă  quoi ressemblent les mĂ©canismes sous-jacents Ă  l’exĂ©cution d’une requĂŞte dans Spark, faisons un point sur les 3 structures proposĂ©es par le framework pour manipuler des donnĂ©es.

A noter que depuis Spark 2.0, le Dataframe n’est qu’un sous-cas non typĂ© du Dataset. La plupart des optimisations appliquĂ©es Ă  l’un sont donc applicables Ă  l’autre.

Le gros intĂ©rĂŞt d’utiliser les Dataset ou Dataframe est de profiter de toutes les optimisations dĂ©crites dans cet article. La requĂŞte SQL dĂ©crit le besoin de l’utilisateur et le rĂ´le du moteur d’optimisation Catalyst est de trouver le moyen le plus rapide d’apporter la rĂ©ponse Ă  ce besoin.

Une nouvelle problĂ©matique survient lorsque l’utilisateur dĂ©cide d’utiliser ses propres fonction en passant p. ex. par l’opĂ©rateur map() sur une instance d’un DataFrame. Dans ce cas lĂ , on sort du contexte SQL et le code est perçu comme une boĂ®te noire par Catalyst : c’est l’utilisateur qui dĂ©crit la solution technique au lieu d’exprimer un besoin.

Jusqu’Ă  la version 1.5 de Spark, la mĂ©thode map(), appliquĂ©e Ă  un DataFrame renvoyait un RDD car le contenu de l’itĂ©rateur ne correspond plus Ă  un tuple classique (une Row), mais Ă  objet Java ou Scala. L’utilisateur prenait alors la responsabilitĂ© d’apporter une solution technique performante, car Catalyst n’Ă©tait plus en mesure de fournir ses optimisations.

A partir de la version 1.6, et avec l’arrivĂ©e des Dataset, il devient possible d’avoir un itĂ©rateur d’objets Java ou Scala (le DataFrame n’est alors plus qu’un Dataset[Row]). La mĂ©thode map() permet ainsi de renvoyer un DataSet Ă  partir d’un autre DataSet. La mĂ©thode map() est alors interprĂ©tĂ©e comme un opĂ©rateur du LogicalPlan. NĂ©anmoins, son contenu reste inconnu Ă  Catalyst. Chaque tuple doit ainsi ĂŞtre dĂ©sĂ©rialisĂ© puis resĂ©rialisĂ© pour pouvoir travailler sur la donnĂ©e.

Une piste d’amĂ©lioration envisagĂ©e pour les versions futures de Spark consiste Ă  interprĂ©ter le contenu de la fonction passĂ©e en paramètre de la mĂ©thode map() en Ă©tudiant directement le bytecode qui le constitue, c’est-Ă -dire en regardant comment la fonction est compilĂ© par le compilateur Java ou Scala.

L’objectif final serait de vĂ©rifier automatiquement s’il est possible de traduire cette fonction en une succession d’opĂ©rateurs SQL classiques : on veut traduire la boĂ®te noire en opĂ©rateurs interprĂ©tables (et donc optimisables) par Catalyst.

Les travaux actuels sur le sujet s’appuient sur Javassist pour Ă©tudier le bytecode de la fonction. La logique est essentiellement prĂ©sente dans le fichier ClosureToExpressionConverter.scala.

A suivre…

Nous avons vu que le Volcano Model est une notion qui n’est pas propre Ă  Spark en tant que tel : de nombreux SGBDR tel que Postgres, MySQL ou mĂŞme Oracle s’appuient sur ce modèle.

La grosse amĂ©lioration apportĂ©e par Spark 2.x repose sur le fait qu’il est possible, dans certains cas, de sortir de ce modèle standard pour directement gĂ©nĂ©rer le code qui va permettre d’exĂ©cuter la requĂŞte SQL, et ainsi gagner en performance… Mais ça, c’est pour la suite !

Catégories: Blog Société

Meetup PerfUG : gRPC, échanges à haute fréquence !

lun, 08/07/2017 - 10:45

Ogury est la plateforme de data mobile qui permet d’accéder aux données comportementales des profils de plus de 400 millions de mobinautes répartis dans plus de 120 pays.

Une plateforme micro-services c’est cool et c’est à la mode … mais il y a aussi le revers de la médaille ! L’augmentation du traffic inter-services peut dégrader vos temps de réponses car les connexions HTTP sont lentes.

Cependant des solutions existent pour optimiser le trafic réseau, durant cette session, David et Carles vous parleront de la solution qu’ils ont expérimentée : gRPC.

David Caramelo, DĂ©veloppeur Craftsman passionnĂ© depuis 12 ans, actuellement Tech Lead full stack chez Ogury. David s’est forgĂ© son expĂ©rience essentiellement dans des startups parisiennes comme Viadeo ou Ogury et dans des cabinets conseil IT comme Xebia.

Carles Sistaré, Architecte-Développeur dans les clouds, actuellement Tech Lead de la team Delivery et co-fondateur d’Ogury. Carles a évolué dans le monde de la AdTech en passant par Ad4Screen et en tant qu’amateur de l’open-source en tant que commiteur Node-Kafka et créateur du module grpc-promise.

Inscriptions et informations sur Meetup. Cette session sera suivie d’un pot dans les locaux d’OCTO.

logo

Le PerfUG est un meetup parisien qui a pour objectif d’offrir un lieu d’échanges informels oĂą toutes les personnes intĂ©ressĂ©es par l’optimisation et la performance sont les bienvenues quel que soit leur niveau. Nous sommes convaincus que la performance est une feature implicite et non nĂ©gociable d’une application et pourtant bien souvent oubliĂ©e. Le PerfUG permet d’Ă©changer idĂ©es et pratiques sur ces sujets pour obtenir plus simplement des systèmes performants. Le PerfUG souhaite faciliter la diffusion des derniers outils et des meilleures techniques pour maĂ®triser au plus tĂ´t la performance d’un système informatique.

imgres

Pour en apprendre davantage sur la Performance, retrouvez notre formation OCTO Academy : Performance des applications et du SI Ă  l’ère du digital

Articles suggested :

  1. Compte-rendu du Performance User Group #3
  2. PerfUG : Deep into your native application
  3. High Performance Images

Catégories: Blog Société

MythBuster: Apache Spark • Épisode 1: Parsing d’une requête SQL

lun, 07/31/2017 - 13:00

Spark est en Ă©volution constante et maintient un rythme soutenu de sorties de releases, en tĂ©moigne la dernière version en date, la 2.2. Dans cette sĂ©rie d’articles, nous allons revenir ensemble sur diffĂ©rentes mĂ©caniques actuellement en place au sein de cet outil et essayer d’en comprendre le fonctionnement.

code {
background-color: #efefef;
font-family: Courier New, Courier,Lucida Sans Typewriter, Lucida Typewriter, monospace;
}
Préface Spark 2 et le projet Tungsten

Depuis la version 2.0, Spark s’appuie sur un moteur d’exĂ©cution retravaillĂ©, nommĂ© Tungsten. Celui-ci embarque avec lui son lot d’amĂ©liorations, et confirme la volontĂ© des contributeurs de rendre Spark toujours plus performant et accessible.

En particulier, le gros des efforts de dĂ©veloppement s’est concentrĂ© sur l’amĂ©lioration des DataFrames (et plus rĂ©cement les Datasets) qui permettent, entre autres, de manipuler de la donnĂ©e Ă  partir de requĂŞtes SQL classiques. L’enjeu de ces amĂ©liorations est d’offrir aux dĂ©veloppeurs une API simple et haut-niveau avec le minimum de contrepartie en termes de performances.

Dans cette optique, une des Ă©volutions du moteur Tungsten semble avoir fait faire un grand pas en avant Ă  Spark puisque, sous rĂ©serve d’utilisation des DataFrames, sans aucune modification de code entre Spark 1.6 et Spark 2.0, les performances pourraient ĂŞtre multipliĂ©es par 10. Cet exploit serait en majeur parti du Ă  une technique de gĂ©nĂ©ration du code Ă  la volĂ©e afin d’accroĂ®tre la vitesse d’exĂ©cution… Rien que ça.

DĂ©mystification

Je ne sais pas pour vous, mais pour nous, « plan d’exĂ©cution », « gĂ©nĂ©ration de code Ă  la volĂ©e », « moteur d’optimisation », etc. sont des concepts dont on a tous entendu parler sans trop vraiment savoir ce qui se cache derrière. Et quand on y pense, on pourrait mĂŞme dire la mĂŞme chose de Spark, non ?

C’est suite Ă  cette rĂ©flexion que nous avons dĂ©cidĂ© de dĂ©cortiquer le code source de Spark et d’en extraire l’essence, afin de comprendre en dĂ©tail ce qu’il se passe sous le capot… Pour cela, nous allons construire ensemble un moteur capable de partir d’une simple requĂŞte SQL sous forme de String et de l’exĂ©cuter, en s’appuyant sur le code source de Spark.

Nous chercherons à répondre aux questions suivantes :

  • Comment peut-on passer d’une requĂŞte SQL Ă  une structure exploitable directement avec du code ?
  • Comment est construit et optimisĂ© un plan d’exĂ©cution ?
  • Quel est l’intĂ©rĂŞt de gĂ©nĂ©rer du code et comment faire ?

Ce sera Ă©galement l’occasion d’apprĂ©hender ensemble certaines fonctionnalitĂ©s de Spark.

Pour cela, nous allons dĂ©couper cet article en 3 parties : la première traitera du parsing d’une requĂŞte, la deuxième de la construction d’un plan d’exĂ©cution physique, la dernière partie mettra l’accent spĂ©cifiquement sur la gĂ©nĂ©ration de code.

L’intĂ©gralitĂ© du code que nous allons rĂ©aliser est prĂ©sent sur le GitLab d’OCTO : que les plus curieux d’entres-vous n’hĂ©sitent-pas Ă  y faire un tour avoir une vision dĂ©taillĂ©e du fonctionnement de notre implĂ©mentation !

Un sous-ensemble du langage SQL

La gĂ©nĂ©ration de code sera le point d’arrivĂ©e de cette sĂ©rie d’article mais nous allons voir qu’avant cela, il y a un certain nombre d’Ă©tapes par lesquelles nous devons passer. La première consiste Ă  transformer une requĂŞte SQL sous forme de String en quelque-chose d’un peu plus pratique Ă  manipuler avec du code.

L’objectif ici n’Ă©tant pas de rĂ©implĂ©menter Spark mais uniquement de comprendre son fonctionnement, nous n’allons pas chercher Ă  supporter l’ensemble de la version 2011 de SQL. Le langage SQL que nous allons implĂ©menter va se restreindre aux contraintes suivantes :

  • Seulement des requĂŞtes de type SELECT avec des clauses FROM et JOIN seront interprĂ©tĂ©es
  • Seul deux types seront supportĂ©s, Ă  savoir NUMBER et TEXT
  • Les mots clĂ©s devront ĂŞtre en majuscules et les identifiants (nom des tables, des colonnes, etc.) en minuscules
  • Les donnĂ©es des tables seront situĂ©es dans des fichiers CSV dont le nom doit ĂŞtre le mĂŞme que celui de la table

Concernant le dernier point, notons que Spark possède une API particulière pour dĂ©crire les sources de donnĂ©es (qui seront considĂ©rĂ©es comme des tables au sein des requĂŞte SQL) et qu’il est possible d’ajouter ses propres sources, mais ce n’est pas quelque-chose que nous allons couvrir ici.

InterprĂ©tation d’une requĂŞte SQL

La première étape de notre périple va être de transformer une String en quelque chose de programmatiquement utilisable…

Un peu de théorie…

Cette transformation se déroule en deux étapes réalisées par deux composants :

  • Le Lexer : il dĂ©crit l’ensemble des mots clĂ©s qui dĂ©finit une requĂŞte SQL (SELECT, WHERE, FROM, etc.) et les localise dans une String donnĂ©e en entrĂ©e. Il transforme ainsi une String en une liste d’Ă©lĂ©ments qui composent notre lexique. Ces Ă©lĂ©ments sont ce que l’on appelle des Tokens. Les mots qui ne font pas partie de ce lexique sont identifiĂ©s par le token Literal s’il s’agit d’une valeur (un nombre ou un texte entre guillemets), et sinon, il s’agit d’un Identifier.
  • Le Parser : lĂ  oĂą le Lexer a une approche lexicale, le Parser a une approche sĂ©mantique : c’est lui qui va donner un sens Ă  la requĂŞte SQL Ă  partir des tokens identifiĂ©s par le Lexer. Pour cela, la liste de Tokens va ĂŞtre convertie en une structure un peu particulière, l’AST (ou Abstract Syntax Tree)…

L’AST est un arbre dont le rĂ´le est de reprĂ©senter une requĂŞte SQL parsĂ©e, en hiĂ©rarchisant les Token identifiĂ©s par le Lexer.

Par exemple, lorsque l’on Ă©crit SELECT column, il faut comprendre que le terme column s’applique au terme SELECT. Le premier est donc un argument du second. Dans l’AST, le SELECT se trouve au dessus du column.

Deux catĂ©gories d’AST sont Ă  distinguer :

  • La catĂ©gorie Expression qui va correspondre aux opĂ©rations que l’on rĂ©alise sur les lignes (comme par exemple les prĂ©dicats appliquĂ©s au niveau des clauses WHERE et ON de nos requĂŞtes) ;
  • La catĂ©gorie Relation qui va dĂ©terminer la manière dont les lignes sont produites (dans notre cas, via les FROM et JOIN, mais on peut aussi penser aux SELECT si l’on avait implĂ©mentĂ© la notion de sous-requĂŞte).

En deux mots : la Relation correspond Ă  l’origine de la donnĂ©e, et l’Expression Ă  ce que l’on en fait.

Notre implémentation ! Nos classes Lexer et Parser

Chacun des types de Token identifiables par le Lexer est reprĂ©sentĂ© par une case class Scala qui Ă©tend le trait Token. Le rĂ´le de la classe Lexer va donc ĂŞtre simplement d’associer certains motifs d’une String Ă  la sous classe de Token correspondante.

L’idĂ©e n’Ă©tant pas d’Ă©crire un parser qui supporte la totalitĂ© du langage SQL, nous allons utiliser les Parser Combinators qui suffisent amplement dans notre cas.

def select: Parser[Select] = positioned { "SELECT" ^^ { _ => Select() } }

Exemple d’un Parser Combinator qui permet de parser le mot clĂ© SELECT en un Token Select() (qui est une case class Scala)

Pour une explication détaillée du fonctionnement des Parsers Combinators, nous redirigeons le lecteur vers la documentation de cette librairie.

Comme leur nom l’indique, il est possible d’associer plusieurs Parser Combinators ensemble, chacun Ă©tant en charge d’un mot clĂ©. Nous sommes ainsi capables d’identifier tous les termes que l’on souhaite au sein d’une String, ce en itĂ©rant sur chacun des mots de la String et en appliquant tous nos Parser Combinators.

L’ensemble des règles ainsi implĂ©mentĂ©es dans notre code est regroupĂ© dans notre objet Lexer prĂ©sent dans Lexer.scala. Token.scala contient l’ensemble des Tokens pris en compte par notre Lexer.

Liste des case classes qui dérivent de Token

Le trait AST

L’objet Parser s’appuie Ă©galement sur les Parser Combinators pour construire notre AST.

Si l’implĂ©mentation des diffĂ©rents parsers peut sembler lĂ©gèrement absconse au premier abord, il n’est pas nĂ©cessaire d’en comprendre les dĂ©tails pour poursuivre la lecture.

L’utilisation des Parser Combinators constitue nĂ©anmoins une pratique courante pour le parsing en Scala.

Cette fois, grâce au travail du Lexer, ce ne sont plus des String que l’on identifie, mais des Token, ce qui simplifie le travail et la comprĂ©hension du programme.

Pour pouvoir construire un arbre Ă  partir de ces Tokens, on ne peut plus se contenter d’appliquer l’ensemble de nos Parser Combinators Ă  nos Ă©lĂ©ments (comme nous l’avions fait dans le Lexer). Nous allons devoir appliquer de façon rĂ©cursive, et dans un certain ordre nos diffĂ©rents Parser Combinator.

Par exemple, nous allons commencer par le parser Select, qui lui mĂŞme attend en paramètre un nom de colonne, un From, et, Ă©ventuellement, un Where. Chacun de ces paramètres correspondent Ă©galement Ă  des Parser qui attendent, eux aussi, d’autres paramètres. De façon rĂ©cursive, nous allons ainsi construire notre arbre syntaxique (le Parser Combinator correspondant aux Expressions est un peu particulier, cf. Pour aller plus loin).

Le Select est donc un type d’AST qui attend en paramètre :

  • Une liste d’expressions qui correspondent Ă  l’ensemble des projections dĂ©crites juste après le terme SELECT
  • Une liste de relations qui correspondent Ă  l’ensemble des tables sources dĂ©crites juste après le terme FROM
  • Éventuellement une dernière expression qui correspond aux filtres (ou prĂ©dicats) dĂ©crits juste après le terme WHERE (sachant qu’il est possible d’appliquer plusieurs filtres en utilisant les expressions AND ou OR)

Hiérarchie des case classes qui dérivent de AST

RĂ©capitulatif

Pour résumer, le parsing se déroule donc en deux étapes :

  • Le lexing de la String en une Seq[Token] grâce Ă  l’objet Lexer qui va valider le vocabulaire utilisĂ© au sein de la requĂŞte SQL
  • Le parsing de la Seq[Token] en un AST grâce Ă  l’objet Parser qui va valider la sĂ©mantique de la requĂŞte SQL

Les deux Ă©tapes qui constituent le parsing

Et du côté de chez Spark ?

Les auteurs de Spark ont quant à eux pris le parti de déléguer cette fastidieuse étape de parsing à un célèbre outil qui mature depuis plus de 20 ans cette problématique : ANTLR !

Je pense qu’on l’a tous dĂ©jĂ  croisĂ© quelque-part…

A l’inverse de bon nombre de projets Scala, les dĂ©veloppeurs de Spark ont dĂ©cidĂ© d’utiliser Maven en lieu et place de SBT pour gĂ©rer le build. Juste avant la compilation de Spark, c’est donc le plugin Maven d’ANTLR qui va gĂ©nĂ©rer les classes nĂ©cessaires pour rĂ©aliser le parsing (ce que nous avons fait manuellement en implĂ©mentant le Lexer et Parser ci-dessus) en se basant sur la grammaire dĂ©finie ici.

La traduction en case classes est quant-Ă -elle rĂ©alisĂ©e ici par l’AstBuilder.

Pour aller plus loin… Recursive Descent Parser

La première implémentation basée sur les Parser Combinator que nous avions réalisée était assez naïve, ce qui a eu pour conséquence des embêtantes StackOverflowException… Tiens-donc.

En effet : bien que relativement simple, la grammaire que nous avons dĂ©finie est rĂ©cursive (par exemple, un terme d’une addition peut lui-mĂŞme ĂŞtre une addition) ce qui n’est pas supportĂ© de base par les Parser Combinators. De ce fait, nous avons dĂ» revoir notre implĂ©mentation et mettre en place un Recursive Descent Parser dont le principe est très bien dĂ©crit ici. Pour nous aider dans cette tâche, nous nous sommes Ă©normĂ©ment inspirĂ©s de cette classe disponible sur GitHub.

Apache Calcite

Le parsing de requĂŞte SQL est une problĂ©matique rĂ©currente : en plus des SGBDR communs, nombre de solutions Big Data s’appuient aujourd’hui sur le langage SQL pour manipuler les donnĂ©es.

Apache Calcite est une initiative d’Apache dont l’objectif est de rĂ©soudre diffĂ©rentes problĂ©matiques liĂ©es Ă  la conception de base de donnĂ©es (SGBDR ou NoSQL), dont – entres autres – le parsing du SQL.

Par exemple, Apache Hive s’appuie sur Calcite pour le parsing du SQL (mais Ă©galement pour l’optimisation des requĂŞtes, mais ça, c’est pour une autre fois…).

A suivre…

A partir de notre String de dĂ©part qui Ă©tait difficilement utilisable, nous avons pu, Ă  l’aide de notre Parser et de notre Lexer, en tirer un AST beaucoup plus exploitable programmatiquement et qui dĂ©crit l’objectif de la requĂŞte SQL.

Nous allons voir dans le prochain article comment transformer cet AST en une nouvelle structure qui va cette fois-ci dĂ©crire la manière dont la requĂŞte va s’exĂ©cuter… Rendez-vous la semaine prochaine !

Articles suggested :

  1. Une analyse géographique des articles de Medline
  2. Compte rendu du Spark Summit 2016

Catégories: Blog Société

Créer des instances AWS qui ont accès à Internet sans IP publique avec Terraform

ven, 07/28/2017 - 15:03

Il nous est souvent demandé : Comment est-ce que je fais pour créer des instances AWS qui ont accès à internet mais sans IP publique ?

Tout d’abord, qu’est ce qu’une IP publique? Une IP publique est une adresse IP joignable sur internet. A contrario des adresses IP privées (décrites dans la RFC 1918) qui elles ne sont pas visibles de l’extérieur du réseau.

 

L’architecture Amazon à mettre en place

 

 

Schéma de l’architecture à mettre en place

 

Tout d’abord nous avons besoin d’un VPC (Virtual Private Cloud) (1) qui va héberger nos différents sous-réseaux (subnets). Un VPC est une brique AWS nous permettant ici de gérer les éléments réseaux de notre infrastructure.

Pour avoir un accès à internet, il faut poser une Internet Gateway (2). L’Internet Gateway est le composant AWS permettant de lier les éléments d’un VPC à Internet.

Ensuite nous avons un Subnet “publique”  (3) : toutes les instances / objets à l’intérieur seront routées sur internet.

Nous allons ensuite mettre en place deux Subnets dit “privés” (4) : les instances / objets à l’intérieur ne seront pas directement routées sur internet, ils auront accès à internet sans être accessible depuis l’extérieur.

Enfin, pour permettre à des instances d’accèder à internet sans avoir d’IP publiques, nous allons utiliser une NAT Gateway (http://docs.aws.amazon.com/fr_fr/AmazonVPC/latest/UserGuide/vpc-nat-gateway.html).

Le concept “Network Address Translation” (NAT), permet de transposer des adresses rĂ©seaux, souvent privĂ©es, en d’autres adresses. C’est un concept rĂ©gulièrement utilisĂ© pour des composants d’un rĂ©seau privĂ© pour avoir accès Ă  internet sans ĂŞtre accessible (adressable) depuis l’extĂ©rieur (c’est le fonctionnement des box que nous avons tous chez nous).

La NAT Gateway se pose dans le subnet publique et dispose d’une adresse publique, en l’occurrence un Elastic IP (EIP) chez AWS. Il reste Ă  s’intĂ©resser au routage de la NAT Gateway aux instances des subnets privĂ©s.

Le Subnet publique aura sa propre RouteTable (routeur chez AWS). Celle-ci aura une route qui fera sortir tout le traffic sortant (0.0.0.0/0) vers l’Internet Gateway.

Les Subnets privés auront une autre RouteTable, celle-ci aura aussi une route qui permettra de faire sortir le traffic sortant (0.0.0.0/0) mais cette fois vers la NAT Gateway et non vers l’Internet Gateway.

Pour implémenter cela, nous allons utiliser Terraform qui va déployer l’architecture sur AWS. Pour en savoir plus sur Terraform et son fonctionnement : https://blog.octo.com/deployer-son-infrastructure-google-cloud-platform-grace-a-terraform.

DĂ©ploiement de notre infrastructure

Commençons par exporter les variables d’environnements nécessaire (credentials et région) :

export AWS_ACCESS_KEY_ID=<XXXXXXXXXXXXXX>
export AWS_SECRET_ACCESS_KEY=<XXXXXXXXXXXXXX>
export AWS_DEFAULT_REGION=<XXXX>

Nous avons besoin de récupérer la région courante, pour cela nous allons utiliser un Datasource Terraform (https://www.terraform.io/docs/providers/aws/d/region.html) :

data "aws_region" "region" {
 current = true
}

Créons notre VPC :

resource "aws_vpc" "my_vpc" {
  cidr_block = "10.0.0.0/16"

 tags {
    Name = "my_vpc"
  }
}

Ensuite nous allons créer tous les composants nécessaire au Subnet publique :

resource "aws_subnet" "public_subnet" {
  vpc_id = "${aws_vpc.my_vpc.id}"
  cidr_block = "10.0.0.0/24"
  availability_zone = "${data.aws_region.current.name}a"

  map_public_ip_on_launch = true
  tags {
    Name = "public subnet"
  }
}

Dans la configuration du subnet, nous pouvons voir que pour le champs availability_zone nous utilisons le datasource de la region.

Nous spécifions aussi le champs map_public_ip_on_launch à true. Cela permet à toutes les instances lancées dans ce subnet d’avoir par défaut une IP publique.

Pour le déploiement de notre NAT Gateway nous avons besoin de deux objets : l’IP publique et la NAT Gateway en elle-même :

resource "aws_eip" "nat_eip" {
  vpc = true
}

resource "aws_nat_gateway" "nat" {
  allocation_id = "${aws_eip.nat_eip.id}"
  subnet_id     = "${aws_subnet.public_subnet.id}"
}

Nous posons donc la NAT Gateway dans le subnet publique. Petit détail, une NAT Gateway quand on la pose dans le Subnet dispose aussi d’une IP privée. Celle-ci est très rarement utilisée, nous verrons juste après dans la RouteTable que nous préférons utiliser l’ID de la NAT Gateway.

Sur cette première partie, il nous reste à mettre en place le routage :

resource "aws_route_table" "public_routetable" {
  vpc_id = "${aws_vpc.vpc.id}"
  tags {
    Name = "Public Routetable"
  }
}

resource "aws_route" "public_route" {
  route_table_id         = "${aws_route_table.public_routetable.id}"
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = "${aws_internet_gateway.internet_gateway.id}"
}

resource "aws_route_table_association" "public_subnet_a" {
  subnet_id      = "${aws_subnet.public_subnet.id}"
  route_table_id = "${aws_route_table.public_routetable.id}"
}

Passons maintenant à la deuxième partie, la création des subnets privés. Nous allons créer deux subnets, un dans l’Availability Zone A et un autre dans l’Availability Zone B.

resource "aws_subnet" "private_subnet_a" {
  vpc_id = "${aws_vpc.my_vpc.id}"
  cidr_block = "10.0.1.0/24"
  availability_zone = "${data.aws_region.current.name}a"
  tags {
    Name = "private subnet A"
  }
}

resource "aws_subnet" "private_subnet_b" {
  vpc_id = "${aws_vpc.my_vpc.id}"
  cidr_block = "10.0.2.0/24"
  availability_zone = "${data.aws_region.current.name}b"
  tags {
    Name = "private subnet B"
  }
}

Nous allons passer à la dernière partie : la configuration du routage de la partie privée. Il faut donc créer la RouteTable, les association et bien entendu la route qui dirige vers la NAT Gateway.

resource "aws_route_table" "private_routetable" {
  vpc_id = "${aws_vpc.vpc.id}"
  tags {
    Name = "private Routetable"
  }
}

resource "aws_route_table_association" "private_subnet_a" {
  subnet_id      = "${aws_subnet.private_subnet_a.id}"
  route_table_id = "${aws_route_table.private_routetable.id}"
}

resource "aws_route_table_association" "private_subnet_b" {
  subnet_id      = "${aws_subnet.private_subnet_b.id}"
  route_table_id = "${aws_route_table.private_routetable.id}"
}

resource "aws_route" "nat_route" {
  route_table_id         = "${aws_route_table.private_routetable.id}"
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = "${aws_nat_gateway.nat.id}"
}

Nous allons créer deux outputs Terraform pour permettre d’indiquer en sortie les subnets dans lesquels il faut poser les instances.

output "private_subnet_a_id" {
  value         = "${aws_subnet.private_subnet_a.id}"
}

output "private_subnet_b_id" {
  value         = "${aws_subnet.private_subnet_b.id}"
}

Le code est disponible ici : https://github.com/mathieuherbert/aws-terraform/blob/master/vpc_with_nat_gateway

Conclusion

Nous avons vu que permettre à des instances d’avoir à accès à internet sans être accessible demande plusieurs objets. Ces objets peuvent être compliqués à manipuler dans un premier temps.
Il existe une implémentation plus historique et qui n’est plus conseillée par AWS : la NAT Instance. Celle-ci demande de manager soi-même son instance et sa disponibilité (http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_NAT_Instance.html ).

Concernant la disponibilitĂ© de la NAT Gateway, celle-ci est dĂ©pendante de la disponibilitĂ© de la zone de son Subnet d’hĂ©bergement. Pour assurer la disponibilitĂ© sur une rĂ©gion au globale, une NAT Gateway par zone de disponibilitĂ© est nĂ©cessaire avec des impacts sur le routage.

Articles suggested :

  1. Terraform et comptes multiples dans AWS
  2. AWS re:Invent 2015 : retour sur les annonces et compte-rendus
  3. GĂ©rer dynamiquement l’accès Ă  ses environnements avec HAProxy et le SNI

Catégories: Blog Société

Partagez la connaissance

Partagez BlogsdeDeveloppeurs.com sur les réseaux sociaux