Blog

ElasticSearch ILM et répartition des données

Description

La gestion d’une centaine de milliards de documents (données issues de logs applicatifs, équipements réseau, middlewares, etc.) s’avère coûteuse (machines, disques, espace de sauvegarde, etc.).
Pour alléger ce coût d’infrastructure nous devons distinguer les données brûlantes des données tièdes et froides (voir gelées). C’est ce que propose ElasticSearch avec l’ILM (Index Lifecycle Management). Nous vous proposons ainsi d’analyser sa mise en œuvre sur un cluster de production d’un de nos clients.
Les données considérées brûlantes doivent être exploitables dans des temps de réponse très courts, nous devons donc les placer sur des machines dimensionnées correctement. Les requêtes sur les données tièdes et froides n’ont pas besoin d’être aussi performantes, ces données peuvent donc automatiquement être déplacées vers des machines plus modestes grâce aux règles de rétention offertes par ILM.

Limitations des écrans dans Kibana et Cerebro

Kibana, l’interface graphique de la suite Elastic ne propose pas une vue synthétique de cette répartition. Nous pouvons obtenir cette information de répartition grâce à plusieurs requêtes, mais nous devons alors croiser les résultats, ce qui est fastidieux.
Cerebro, une interface graphique issue de la communauté open source, est excellent dans la visualisation de la répartition de la charge, mais ne propose pas cette visualisation au niveau du cycle de vie.
Même constat pour Elasticvue, qu’il s’agisse de sa version desktop, extension Chrome ou webapp.
Un moyen d’obtenir les informations souhaitées est donc de passer par l’API d’ElasticSearch.
Avec une première API nous pouvons obtenir les rôles des machines :

GET _nodes/settings?filter_path=nodes.*.roles,nodes.*.name

{
  "nodes": {
    "ECNRRDP2SUKmcu3s9qJgnA": {
      "name": "es3",
      "roles": [
        "data_cold",
        "ingest",
        "master"
      ]
    },
    "NXZNCa_BQ_SE613oSnDf-g": {
      "name": "es6",
      "roles": [
        "data_cold",
        "ingest",
        "master"
      ]
    },
    "jqmdwzdeQHG84601oQjpGw": {
      "name": "es1",
      "roles": [
        "data",
        "data_hot",
        "ingest",
        "master"
      ]
    },
    "4z2k13n8SZS4t2JmLielaQ": {
      "name": "es4",
      "roles": [
        "data",
        "data_hot",
        "ingest",
        "master"
      ]
    },
    "AFRMIIagRWK4Cu-ZhbLH2A": {
      "name": "es5",
      "roles": [
        "data_warm",
        "ingest",
        "master"
      ]
    },
    "sxljzKF-Q1app0VSkVgTxg": {
      "name": "es2",
      "roles": [
        "data_warm",
        "ingest",
        "master"
      ]
    }
  }
}

Puis avec une deuxième API nous pouvons récupérer le cycle de vie des indices (index au pluriel).

GET /*/_settings?filter_path=*.settings.index.routing.allocation.include,*.settings.index.uuid,*.settings.index.provided_name

{
  ".ds-my-index-1-2024.09.17-003772": {
    "settings": {
      "index": {
        "routing": {
          "allocation": {
            "include": {
              "_tier_preference": "data_hot"
            }
          }
        },
        "provided_name": ".ds-my-index-1-2024.09.17-003772",
        "uuid": "HyELPU6oTpC_EfskAxP5BQ"
      }
    }
  },
  ".ds-my-index-1-2024.09.17-003748": {
    "settings": {
      "index": {
        "routing": {
          "allocation": {
            "include": {
              "_tier_preference": "data_warm,data_hot"
            }
          }
        },
        "provided_name": ".ds-my-index-1-2024.09.17-003748",
        "uuid": "QNC-Z2loT4SCKFb2-LJKAw"
      }
    }
  },
  ".ds-my-index-1-2024.09.17-003714": {
    "settings": {
      "index": {
        "routing": {
          "allocation": {
            "include": {
              "_tier_preference": "data_cold,data_warm,data_hot"
            }
          }
        },
        "provided_name": ".ds-my-index-1-2024.09.17-003714",
        "uuid": "zIP72E-qTBSfKbZdAYmFEQ"
      }
    }
  },
  [...]
}

Et avec une troisième API nous pouvons restituer le détails des blocs de données correspondant aux indices :

GET /_cat/shards?format=json&h=index,node,state,prirep,docs

[
  {
    "index": ".ds-my-index-1-2024.09.17-003848",
    "node": "es1",
    "state": "STARTED",
    "prirep": "p",
    "docs": "120"
  },
  {
    "index": ".ds-my-index-1-2024.09.17-003848",
    "node": "es4",
    "state": "STARTED",
    "prirep": "r",
    "docs": "47"
  },
  {
    "index": ".ds-my-index-1-2024.09.17-003828",
    "node": "es5",
    "state": "STARTED",
    "prirep": "p",
    "docs": "29"
  },
  {
    "index": ".ds-my-index-1-2024.09.17-003828",
    "node": "es2",
    "state": "STARTED",
    "prirep": "r",
    "docs": "147"
  },
  [...]
]

En croisant les résultats de ces trois requêtes, nous pouvons donc savoir si un bloc de données d’un index « brûlant » est bien placé sur un noeud « brûlant ». Mais pour des milliers d’index, cela devient beaucoup plus laborieux.

Développement d’une solution de visualisation

Quelques lignes de JavaScript (64), d’HTML (80) et de CSS (118) plus tard et voici un aperçu d’un rapport réalisé sur un cluster ElasticSearch en local.

ElasticSearch ILM test

Résultat de l’analyse du cluster local

L’outil génère un fichier de rapport en format HTML, consultable depuis n’importe quel navigateur. Alors, en un coup d’œil on peut savoir si un bloc est à la bonne place.
Le code source est disponible sur Github.
Pour résumer ce que fait techniquement ce code source :

  • Lancement des 3 requêtes précédemment étudiées
  • Injection de ces données dans un moteur de template HTML
  • Sauvegarde du résultat dans un fichier HTML

Premier rapport réalisé sur une infrastructure de production et analyse

Ensuite, nous avons intégré l’outil dans une chaîne d’intégration continue de Gitlab pour automatiser la génération des rapports. Nous pouvons ainsi désormais nous interfacer avec le cluster à analyser et donc générer un premier rapport.
Quelques données sur la taille du cluster :

  • 10 machines ElasticSearch
  • 110 milliards de documents
  • 125 To de données stockées
  • 25 000 événements par seconde

Le premier rapport montre une répartition de la charge logique comme ci-dessous :

ElasticSearch ILM

Résultat de l’analyse du cluster de production

Les nœuds ayant le rôle data_warm reçoivent bien les blocs data_warm. Les nœuds ayant le rôle data (tout les rôles) reçoivent tout type de blocs.
Cependant ce n’est pas encore optimisé pour réduire les coûts car :

  • Il reste des blocs data_warm non alloués à des nœuds data_warm car ElasticSearch cherche l’équilibre en termes de nombre de blocs par machine.
  • Aucun bloc de données froides n’apparaît car les règles de rétention ne définissent pas de phase « cold ». Des nœuds data_cold doivent être ajoutés au cluster.
  • Il y a légèrement trop de données brûlantes en proportion, la durée de rétention en phase « hot » doit être revue à la baisse.

Evolution du cluster

Ainsi, après réflexions avec les équipes en charge de la maintenance du cluster, nous avons définit la cible à atteindre :

  • 3 nœuds cold (à venir)
  • 5 nœuds warm (node-3 node-9 node-10 node-1 node-2)
  • 2 nœuds hot warm master (node-4 node-5)
  • 3 nœuds hot ingest master content (node-6 node-7 node-8)

Les raisons de ces choix dépendent des caractéristiques des machines à disposition (CPU, disques, RAM).
Les mouvements de blocs vont être nombreux, et nous réfléchissons déjà à la procédure de migration afin de perturber au minimum le service. Il faut bien prendre en compte que l’espace disque pris par chaque bloc est de 50Go, et que chaque déplacement prend entre 30 minutes et 1 heure sur cette infrastructure réseau.
Nous avons ensuite planifié l’exécution de l’outil pour fournir un rapport tous les jours afin de suivre l’évolution de la répartition. Nous aurons donc un joli jeu de couleurs d’ici quelques semaines 😉

Découverte de JetPack Compose

Bannière Jetpack Compose

Description

Jetpack Compose est un framework d’interface utilisateur développé par Google, sorti en version 1.0 en août 2021. Comme SwiftUI pour iOS, il permet de faciliter et d’accélérer la création d’interfaces graphiques pour les applications Android. C’est une approche innovante et différente de ce que proposait Android en termes de développement d’UI via les XML Views.

Avantages

Un des principaux avantages de Compose est l’approche déclarative des éléments. Grâce à la gestion de la fonction remember. Lorsqu’un élément change d’état, l’interface se met à jour automatiquement.
Par exemple, lorsque nous déclarons une variable var name by remember { mutableStateOf("Jean") }, la valeur initiale de la variable est Jean, mais lorsque le nom changera, l’interface affichera automatiquement la nouvelle valeur associée.
Les composants sont appelés Composable et peuvent être imbriqués entre eux afin de créer des composants modulables, complexes et réutilisables. Cela permet ainsi de créer un code plus structuré et plus compréhensible pour les développeurs.
De plus, Compose est plus performant grâce à sa capacité à « recomposer » les bons composants au bon moment. C’est-à-dire qu’il est capable de mettre à jour les composants nécessitant un changement d’état sans avoir à tout recréer.
Voulant optimiser et faciliter au maximum le développement d’interfaces utilisateur, Google intègre des Composable favorisant les standards de l’écosystème Android, tels que Scaffold, qui permet de gérer une vue avec une TopBar, une BottomBar, un FloatingActionButton, tout en gérant automatiquement les bonnes marges pour le contenu.

Démonstration

Prenons l’exemple d’une interface affichant une liste de prénoms pouvant être alimentée par un champ de texte.

XML

activity_main.xml

Ce fichier va définir l’interface principale de l’application. Comprenant un EditText pour la saisie d’un nouveau nom, une ImageView pour ajouter l’élément à la liste et une RecyclerView pour afficher celle-ci. Comme la liste peut contenir un nombre indéterminé d’éléments, ce composant est plus adapté et optimisé.

<androidx.constraintlayout.widget.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:id="@+id/main"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">


   <EditText
       android:id="@+id/et_name"
       android:layout_width="0dp"
       android:layout_height="wrap_content"
       android:layout_margin="16dp"
       android:hint="Nouveau nom"
       android:inputType="text"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toStartOf="@+id/but_add"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       app:layout_constraintVertical_bias="0.0"
       tools:ignore="Autofill">


   <ImageView
       android:id="@+id/but_add"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_below="@id/et_name"
       android:layout_marginEnd="16dp"
       android:src="@android:drawable/ic_menu_add"
       app:layout_constraintBottom_toBottomOf="@+id/et_name"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintHorizontal_bias="1.0"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="@+id/et_name"
       app:tint="#2196F3">


   <androidx.recyclerview.widget.RecyclerView
       android:id="@+id/rv_names"
       android:layout_width="match_parent"
       android:layout_height="0dp"
       android:layout_below="@id/but_add"
       android:layout_marginTop="16dp"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@+id/et_name">


</androidx.constraintlayout.widget.ConstraintLayout>

row_item.xml

Représentant un élément de la liste, ce fichier comprendra un TextView pour afficher le nom, ainsi qu’une ImageView pour supprimer l’entrée si nécessaire.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:orientation="vertical">


   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="horizontal"
       android:padding="8dp">


       <TextView
           android:id="@+id/tv_name"
           android:layout_width="0dp"
           android:layout_height="wrap_content"
           android:layout_weight="1"
           android:textSize="16sp">


       <ImageView
           android:id="@+id/but_delete"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           app:srcCompat="@android:drawable/ic_delete">


   <LinearLayout>


   <com.google.android.material.divider.MaterialDivider
       android:layout_width="match_parent"
       android:layout_height="1dp">


<LinearLayout>

ListAdapter.kt

Maintenant que les fichiers XML ont été créés, nous devons les associer à leurs fichiers Kotlin. ListAdapter.kt va, comme son nom l’indique, associér l’élément de la liste avec le composant XML

class ListAdapter(
   private val names: MutableList,
   private val onDeleteClick: (Int) -> Unit
) : RecyclerView.Adapter() {


   inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
       val tvName: TextView = view.findViewById(R.id.tv_name)
       val butDelete: ImageView = view.findViewById(R.id.but_delete)


       init {
           butDelete.setOnClickListener {
               onDeleteClick(adapterPosition)
           }
       }
   }


   override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
       val view = LayoutInflater.from(parent.context).inflate(R.layout.row_item, parent, false)
       return ViewHolder(view)
   }


   override fun onBindViewHolder(holder: ViewHolder, position: Int) {
       holder.tvName.text = names[position]
   }


   override fun getItemCount(): Int {
       return names.size
   }
}

MainActivity.kt

Maintenant que tout est en place, nous pouvons créer le fichier principal qui gérera toute la logique de la fonctionnalité. Ce fichier associera les différents composants aux variables ainsi que ListAdapter à la RecyclerView. De plus, c’est ici que nous pourrons gérer les actions des utilisateurs, telles que l’ajout et la suppression d’un prénom avec la mise à jour de l’interface.

class MainActivity : AppCompatActivity() {


   private lateinit var etName: EditText
   private lateinit var butAdd: ImageView
   private lateinit var rvNames: RecyclerView
   private lateinit var names: MutableList
   private lateinit var adapter: ListAdapter


   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)
       etName = findViewById(R.id.et_name)
       butAdd = findViewById(R.id.but_add)
       rvNames = findViewById(R.id.rv_names)
       names = mutableListOf("Pierre", "Paul", "Jacques")
       adapter = ListAdapter(names) { position ->
           names.removeAt(position)
           adapter.notifyItemRemoved(position)
       }
       rvNames.setLayoutManager(LinearLayoutManager(this))
       rvNames.setAdapter(adapter)
       butAdd.setOnClickListener(View.OnClickListener {
           val name = etName.getText().toString()
           if (name.isNotEmpty()) {
               names.add(name)
               adapter.notifyDataSetChanged()
               etName.setText("")
           }
       })
   }
}

Comme nous pouvons le constater dans la MainActivity.kt, l’adaptateur a besoin d’être notifié d’un changement; à la fois lorsqu’on ajoute un élément adapter.notifyDataSetChanged() et lorsqu’on supprime un élément adapter.notifyItemRemoved(position).

Compose

Avec Compose, nous aurons besoin d’un seul fichier. Nous pouvons bien évidemment séparer les composants en plusieurs fichiers pour plus de lisibilité ou d’ergonomie.
La fonction remember nous permet de suivre les changements de valeurs, tant sur la liste des noms que sur la saisie de texte.
Le composable LazyColumn est l’équivalent d’une RecyclerView. Il permet d’optimiser l’affichage d’une liste de taille indéterminée.
Enfin, le composable FirstNameRow représente une entrée dans la liste. Il n’y a pas ici besoin d’adaptateur, il suffit de déclarer le composant tel quel et de l’intégrer dans la boucle de la LazyColumn.

MainActivity.kt

class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           MainView()
       }
   }
}


@Composable
fun MainView() {
   var names by remember { mutableStateOf(listOf("Pierre", "Paul", "Jacques")) }
   var entryName by remember { mutableStateOf("") }


   Column(
       modifier = Modifier.padding(16.dp),
       verticalArrangement = Arrangement.spacedBy(16.dp)
   ) {
       TextField(
           value = entryName,
           onValueChange = { entryName = it },
           label = {"Nouveau prénom"},
           modifier = Modifier.fillMaxWidth(),
           trailingIcon = {
               Icon(
                   imageVector = Icons.Default.AddCircle,
                   contentDescription = "Ajouter prénom",
                   modifier = Modifier.clickable {
                       if(entryName.isNotEmpty()) {
                           names += entryName
                           entryName = ""
                       }
                   },
                   tint = Color.Blue
               )
           }
       )


       LazyColumn(
           verticalArrangement = Arrangement.spacedBy(5.dp)
       ) {
           items(names.count()) { index ->
               FirstNameRow(firstName = names[index]) {
                   names -= names[index]
               }
           }
       }
   }
}


@Composable
fun FirstNameRow(
   firstName: String,
   onDelete: () -> Unit
) {
   Column(
       Modifier.fillMaxWidth()
   ) {
       Row(
           Modifier.fillMaxWidth(),
           verticalAlignment = Alignment.CenterVertically
       ) {
           Text(text = firstName)
          
           Spacer(modifier = Modifier.weight(1f))


           Icon(
               imageVector = Icons.Default.Close,
               contentDescription = "Supprimer prénom",
               modifier = Modifier.clickable {
                   onDelete()
               },
               tint = Color.Red
           )
       }


       Divider()
   }
}

@Composable
fun MainActivityPreview() {
   MainView()
}

Comme nous pouvons le constater ci-dessus, aucun adaptateur n’a été initié, et aucune notification envers le LazyColumn n’a besoin d’être faite. Tout se fait distinctement, simplement et rapidement.
Nous pouvons également noter qu’il existe une fonction de prévisualisation qui permet d’afficher la vue en temps réel. Nous avons aussi la possibilité d’interagir directement avec les différents composants sans qu’aucun émulateur ou appareil physique ne soit nécessaire. Cela permet d’effectuer des tests d’UI et fonctionnels beaucoup plus rapidement.

Conclusion

Tout comme SwiftUI côté iOS, Jetpack Compose modernise le développement d’applications Android en offrant une approche plus simple, plus puissante et plus concise pour la création d’interfaces utilisateur. Les développeurs bénéficient d’une productivité accrue et d’une meilleure expérience de développement. Pour ma part, je trouve cette approche de développement bien plus intéressante et innovante que le modèle XML traditionnel. De plus, lorsqu’on développe à la fois sur Android avec Kotlin/Compose et sur iOS avec Swift/SwiftUI, nous remarquons des similitudes qui facilitent et accélèrent considérablement le développement sur les deux plateformes concurrentes.

KMM : découverte de Kotlin Multiplateform Mobile

Définition

Kotlin Multiplateform Mobile, plus communément appelé KMM, est une technologie open source créée par JetBrains. Stable et en production depuis novembre 2023, elle permet de simplifier le développement multiplateforme, notamment pour Android et iOS.
Le principal langage de programmation de KMM est, comme son nom l’indique, Kotlin. Développé en 2011 par JetBrains, ce langage, moins verbeux que Java, permet une écriture de code plus rapide et concise, offrant une accélération conséquente du rythme de développement. Kotlin propose également des améliorations, notamment avec l’introduction de la null-safety, afin d’éviter les NullPointerException en déclarant des variables pouvant être nulles, assurant ainsi un code plus limpide et robuste. Depuis 2017, il est devenu le langage officiel du développement mobile Android.

Fonctionnement

KMM permet d’intégrer le code métier de l’application de manière partagée entre Android et iOS, réduisant drastiquement le temps de développement et évitant la redondance. Il est utilisé pour écrire les différents modèles de l’application, les appels réseau et aux bases de données. Pour une architecture MVVM, certaines librairies offrent la possibilité de partager également les ViewModels sur les deux supports. Seule la partie visuelle doit être écrite nativement, en XML ou Jetpack Compose pour Android, et en SwiftUI ou UIKit pour iOS, assurant ainsi des performances et une expérience utilisateur optimales.

Exemple

Prenons l’exemple de base d’un projet KMM tout nouvellement créé :

Arborescence KMM (Kotlin Multiplateform Mobile) composeApp : contient le code source de l’application Android en Compose, non partagé.
iosApp : contient le code source de l’application iOS en Swift/SwiftUI, non partagé
shared : contient le code partagé entre les 2 plateformes séparé en 3 dossiers

 

Dans le dossier shared, nous pouvons remarquer la présence des classes et des fichiers Kotlin. Le fichier Platform.kt quant à lui, dans commonMain, contient une interface qui retourne le nom de la plateforme associée au téléphone ainsi qu’une fonction expect. Déclarer une fonction expect permet d’implémenter du code spécifique natif à la plateforme, et c’est pour cela que dans androidMain et dans iosMain, nous retrouvons l’utilisation de cette interface afin de récupérer nativement le nom et le numéro de version de la plateforme. Dans la classe Greeting, nous pouvons appeler une fonction qui nous retournera le nom de la plateforme associée à la fois en Compose sur Android et en SwiftUI sur iOS.

Code KMM (Kotlin Multiplateform Mobile)

Code KMM (Kotlin Multiplateform Mobile)

Conclusion

À l’instar des autres technologies de développement hybride, comme React Native ou Flutter, Kotlin Multiplateform Mobile permet de conserver l’aspect natif de l’application. Il permet en effet d’utiliser les composants et l’expérience des écosystèmes Android et iOS tout en partageant du code métier, alliant à la fois performance de l’application et rapidité de développement. Pour ma part, je trouve que KMM est une véritable innovation dans la création d’applications mobiles natives, permettant d’éviter la redondance d’écriture de code métier lors de la création d’une application Android et iOS. KMM prend dors et déjà de plus en plus d’ampleur au sein des stacks de développement mobile, qu’il s’agissent de nouvelles application ou de refonte.

A noter que les développeurs Android auront forcément plus d’attirance pour cette approche basée sur un langage et un écosystème Kotlin. Cela ouvre également un débat plus organisationnel au sein des équipes mobiles. En effet, il faudra savoir quels développeurs sont ou seront responsables du développement du code partagé.

UIKit vs SwiftUI

UIKit

Pour développer des applications iOS en utilisant le langage Swift ou Objective-C, Apple propose un framework d’interface utilisateur impératif : UIKit. Il permet de construire la partie UI de l’application notamment via l’InterfaceBuilder, l’outil de développement d’interface graphique intégré dans Xcode.
La prise en main de cet outil peut être fastidieuse, et malgré l’habitude, ajouter un simple écran s’avère assez long. Il est tout d’abord nécessaire de glisser et déposer 1 à 1 chacun des composants, pour ensuite les lier entre eux afin de les positionner. Enfin, il faut toujours procéder de la même manière et déclarer ces derniers dans un ViewController.
Pour certains écrans, parfois complexes, cette méthode est chronophage et répétitive. De plus, les écrans créés par cet outil sont intégrés dans un fichier appelé Storyboard, pouvant contenir de multiples interfaces et donc présenter plusieurs inconvénients :

  • Un temps de chargement trop long
  • De nombreux conflits lors de merge (en utilisant Git par exemple)
  • Des composants non-dynamiques et non-adaptables
  • Une interface qui semble désordonnée

Exemple de Storyboard (Source: Swiftement)

Pour pallier ces difficultés, ainsi qu’aux nouveaux designs qui peuvent être plus exigeants, et par conséquent nécessiter un temps de développement plus important, en 2019, Apple a proposé un nouveau framework : SwiftUI.

SwiftUI

Ce nouveau framework d’UI propose une approche déclarative et n’est disponible qu’en Swift. La construction d’un écran se déroule directement dans le code, ce qui est beaucoup plus rapide et compréhensible par les développeurs. Construire une simple interface prendra ainsi peu de temps, chaque écran faisant partie d’un unique fichier (à contrario des Storyboards cités auparavant). Il propose également un canvas interactif permettant de visualiser les changements en temps réel selon plusieurs configurations. Une est notamment utilisée pour l’accessibilité, rappelant ainsi les développements web et mobiles hybrides.

Ce framework peut à la fois être intégré dans des composants UIKit pour ainsi disposer de composants plus dynamiques. De par sa jeunesse, il peut également au contraire intégrer des composants UIKit afin de compenser un certain manque d’adaptabilité. N’étant disponible qu’à partir d’iOS13, certaines applications et téléphones restent évidemment incompatibles avec SwiftUI. Cependant, les versions minimales iPhone sont souvent augmentées pour des raisons de sécurité, rendant ainsi SwiftUI potentiellement supporté par tous les iOS dans un futur proche. A contrario d’UIKit, nécessitant la création d’un nouveau d’un projet en AppKit pour développer une application MacOS, SwiftUI prend lui en charge le multi-device de tous les appareils de la pomme.

UIKit vs SwiftUI : démonstration comparative chronométrée

Prenons l’exemple d’un écran constitué simplement d’un titre et d’un texte, possédant une valeur qui s’incrémente ou se décrémente à l’aide de deux boutons. L’approche de conception est bien différente, avec UIKit, le code n’est utilisé que pour la partie logique de l’application. La partie design se fait via un outil d’interface qui permet de construire notre écran au fur et à mesure, de manière plus contrôlée, mais plus lente. Une bonne partie du temps est utilisée pour parcourir les menus des différents composants. Avec SwiftUI, nous restons sur la même page, en utilisant que très peu la souris et le pad, tout se construisant via le code. Les vidéos accélérées ci-dessous démontrent que l’approche SwiftUI est jusqu’à 2,5 fois plus rapide qu’UIKIt dans cet exemple.

UIKit Demo
SwiftUI Demo

Conclusion UIKit vs SwiftUI

Bien que l’utilisation des deux frameworks puissent s’associer, et qu’UIKit prévale de par son ancienneté, SwiftUI se démarque. En effet, son dynamisme, sa facilité de prise en main ainsi que sa rapidité d’écriture et de conception me permettent, après une réelle utilisation sur projet d’affirmer qu’il représente le futur du développement natif Swift.
Précédemment, j’avais pu développer un projet mobile natif Android en Java et iOS en Swift/UIKit. Je me sentais beaucoup plus à l’aise sur Android et retourner sur xCode m’apparaissait fastidieux et pénible. En effet, avec des temps de chargements longs, de simples écrans à construire pouvaient prendre des heures de travail, pour le même rendu final sur Android, en 3 fois moins de temps. Apprendre et utiliser SwiftUI a donc été, sans l’ombre d’un doute, une vraie bouffée d’air frais. Le framework était certes encore jeune, mais tellement puissant, fluide et dynamique qu’il m’a donné à l’époque l’envie de rouvrir xCode !

LRA Long Running Actions

Principe général

Long Running Actions (LRA) est un module de la stack Eclipse MicroProfile comme peuvent l’être « Health », « Fault Tolerance » ou « GraphQL ». Son objectif est de permettre la coordination des microservices concourant à implémenter une même activité métier. Sur le plan théorique ce pattern est référencé sous le nom de SAGA.

Bien sûr l’idée n’est pas de revenir au mode de fonctionnement centralisé et fortement couplé dont l’architecture microservices essaie de s’éloigner. Il n’est donc pas question d’opérer des transactions SQL distribuées comme on a pu le faire il y a quelques années, chaque microservice reste seul responsable de son système de stockage qui n’est pas partagé.

Le but ici est d’atteindre l’eventual consistency. Attention pour ceux qui ne maîtriseraient pas l’anglais, eventual est un faux amis, il ne s’agit pas de dire que le système sera éventuellement cohérent mais qu’il le sera forcément tôt ou tard.
Ce concept est familier aux utilisateurs des bases de données de nouvelle génération qui l’exploitent pour offrir une bonne montée en charge.

Examinons maintenant plus concrètement comment déployer cela au sein d’une architecture microservices.

Le coordinateur

L’implémentation de Long Running Actions requière la coopération d’un service additionnel à nos microservices métier. Son rôle sera d’orchestrer les transactions, c’est à dire d’informer nos services pour leur demander de compenser (défaire) leurs actions ou au contraire de les valider définitivement.

Ainsi la première étape consiste à lancer ce fameux coordinateur. Nous utiliserons ici l’implémentation issue du projet Narayana, packagée dans un microservice Quarkus:
docker run -d -p 8080:8080 quay.io/jbosstm/lra-coordinator

Pour vérifier qu’il tourne correctement:
curl -i localhost:8080/lra-coordinator
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
content-length: 2
Narayana-LRA-API-version: 1.1

[]

D’autres alternatives à Narayana existent et il est possible de les utiliser sans impact sur le reste de notre architecture car les interactions s’appuient sur un protocole HTTP REST normalisé.

Sur les microservices

Au niveau du code de nos microservices, les _endpoints_ participant à une transaction métier longue doivent être annotés @LRA, par exemple:

@LRA(value = LRA.Type.REQUIRED, end=false)
@POST
@Consumes(MediaType.TEXT_PLAIN)
@Path("/book")
public Response bookFlight(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId, String destination) {
  if (destination.equals("Toulouse") || destination.equals("Paris")) {
    System.out.println("Succès");
    return Response.ok().build();
  }
  else {
    System.out.println("Echec");
    return Response.serverError().build();
  }
}

L’annotation @LRA présente des similitudes avec une autre annotation largement utilisée : @Transactional. En effet, elles partagent toutes deux des paramètres pour spécifier le périmètre de la transaction, qui se ressemblent beaucoup. Par défaut, l’attribut value est égal à « REQUIRED », ce qui indique que si une transaction est déjà en cours, la méthode sera exécutée dans son contexte. La propagation de l’id de la LRA se fait au travers de l’entête HTTP « Long-Running-Action ». C’est ainsi la présence de cette information dans la requête entrante qui détermine s’il faut créer une LRA ou pas. Dans la réponse HTTP nous retrouverons également cette entête. Elle devra être réutilisée par le client dans les requêtes successives. Pour fixer les idées la valeur de cette entête pourrait être par exemple :

http://localhost:8080/lra-coordinator/0_ffffc0c5100f_f355_6113bebe_22

L’id de la LRA prend la forme de son URL canonique. La création de la LRA se fait sur demande par l’appel de l’API REST du coordinateur depuis notre microservice.
Nous n’avons pas à coder cette interaction, cela se fait automatiquement par la librairie LRA.
Les appels aux microservices suivants, lorsque ceux-ci sont annotés avec @LRA et que l’entête est bien fournie, se voient donc inclus dans la même transaction tant que la propriété end=true n’est pas fixée. Quand ce sera le cas, la transaction LRA sera fermée après l’exécution de la méthode et le coordinateur appellera pour chaque microservice son point de terminaison d’API annoté @Complete.

@Complete
@Path("/complete")
@PUT
public Response completeFlight(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId, String userData) {
  String message = "Flight Booking completed with LRA with id: " + lraId;
  System.out.println(message);
  return Response.ok(ParticipantStatus.Completed.name()).build();
}

Si au contraire, une des méthodes des microservices annotées @LRA avait été en erreur, la LRA dans son ensemble aurait été annulée. Les méthodes callback invoquées auraient alors été celles annotées @Compensate.

@Compensate
@Path("/compensate")
@PUT
public Response compensateFlight(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId, String userData) {
  String message = "Flight Booking compensated with LRA with id: " + lraId;
  System.out.println(message);
  return Response.ok(ParticipantStatus.Compensated.name()).build();
}

Conclusion

Comme nous venons de le voir, le module Long Running Actions fournit un cadre d’implémentation du pattern SAGA.
Si des points communs avec les transactions JPA (Java Persistence API) peuvent être relevés, le fonctionnement des LRAs est cependant largement plus découplé et repose sur un système de compensation. C’est en effet à nous d’implémenter le rollback en fonction bien sûr de la technologie sous-jacente. Même principe pour la méthode @Complete c’est à nous qu’incombe de coder la logique de « commit« . Ce terme est à considérer avec des guillemets car même si le système de stockage du microservice est construit sur une base traditionnelle, sa transaction SQL locale sera déjà véritablement commitée (il est inenvisageable de conserver des verrous base de données sur plusieurs requêtes HTTP). La plupart du temps la tâche de la méthode @Complete se résume à nettoyer les ressources ou informations conservées dans l’optique de pouvoir compenser la LRA si finalement elle devait être annulée.

Ainsi l’essentiel du framework consiste à mettre en œuvre une série de notifications pour informer les microservices des résultats des opérations des uns des autres. Cette synchronisation se fait avec un décalage qui peut être plus ou moins long, certains microservices pouvant échouer à compenser, mais le coordinateur insistera et les notifiera à nouveau. L’objectif final n’étant pas de garantir une consistence instantanée des données mais juste de permettre d’y arriver à un moment donnée.

Pour plus de détails encore dans le protocole Long Running Actions, voici sa javadoc, bien sûr n’hésitez pas à expérimenter avec Quarkus par exemple:
https://download.eclipse.org/microprofile/microprofile-lra-2.0/apidocs/

Agrément CIR renouvelé pour DocDoku jusqu’en 2027

Agrément CIR depuis 2011

Pour la cinquième fois consécutive, DocDoku obtient son agrément Crédit d’Impôt Recherche (CIR) pour quatre ans, jusqu’en 2027 donc. Nous détenons en effet cet agrément depuis 2011.
Pour rappel, cela signifie que DocDoku est reconnu comme organisme ayant la capacité à mener des travaux de Recherche et Développement (R&D) et que toutes les dépenses de réalisation d’opérations de R&D confiées à DocDoku ouvrent droit au Crédit d’Impôt en faveur de la Recherche, dans les mêmes conditions que pour des investissements internes.

Le CIR, c’est quoi ?

Pour rappel, le CIR est un dispositif d’incitation fiscale en faveur de la recherche et de l’innovation. Il permet aux entreprises de bénéficier d’un crédit d’impôt correspondant à un pourcentage du montant des dépenses investies dans l’année en recherche et développement. Cet avantage fiscal varie entre 30% et 50% de Crédit d’Impôt sur les dépenses engagées pour les projets éligibles (voir conditions sur le site du Ministère).

Qu’attendons-nous pour innover ensemble ?

Qu’attendez-vous pour innover et lancer de nouveaux projets R&D puisque ces derniers peuvent faire l’objet d’une prise en charge substantielle au travers du Crédit d’Impôt Recherche lorsque vous passez par notre intermédiaire ?
Pour toutes questions sur vos projets innovants, contactez-nous.

FNE-Formation ou comment financer vos formations à 100% en chômage partiel

Vous êtes nombreux à vous interroger sur le dispositif FNE-Formation aujourd’hui renforcé à cause de la crise sanitaire que nous traversons.
L’objectif de cet article est donc de vous apporter toutes les réponses afin de bénéficier au mieux de ce dispositif.

Qui peut y avoir droit ?

Tous les salariés du privé placés en chômage partiel (les contrats d’apprentissage et de professionnalisation étant exclus) ! La formation doit par contre avoir lieu durant les heures et jours de chômage partiel, donc pas pendant les congés, RTT ou jours fériés.

Quel montant est pris en charge ?

La totalité des coûts pédagogiques est prise en charge. Il faut cependant savoir que les formations en dessous de 1500 euros TTC par salarié sont traitées de manière plus rapide et de manière quasi automatique au niveau de votre DIRECCTE régionale.
Au-dessus de 1500 euros TTC par salarié une instruction plus avancée doit être engagée.

Quels types de formation ?

Toutes les formations sont éligibles, à condition d’avoir lieu à distance et à l’exception des formations en alternance et obligatoires à la sécurité incombant à l’employeur.

Quelle est la procédure à suivre ?

Il y a deux procédures possibles :

N’hésitez pas à nous contacter si vous souhaitez en savoir plus ou vous lancer, toutes nos formations au catalogue étant bien entendu disponibles à distance.

Sources :

Chaîne DevOps intégrant Jenkins avec Ansible ou OpenShift (2/2)

Introduction

Le but de cet article est de présenter brièvement le concept de DevOps, illustré par une implémentation concrète dans un environnement applicatif de microservices Restfull, développés dans le contexte d’une approche agile.

La première partie est consacrée au rappel des principes de DevOps et à la présentation du schéma architectural d’une chaîne DevOps avec Github, Jenkins, Artifactory et Ansible.

Cette deuxième partie présente une autre chaîne DevOps avec Github, Jenkins, Artifactory et OpenShift.

1 Exemple d’une mise en oeuvre avec OpenShift

La chaîne DevOps décrite ci-dessous permet d’assurer des déploiements dans une plateforme de containers (OpenShift) sur différents namespaces  (§. 1.1 ci-dessous) : INT, VAL et PROD.

Elle est composée des éléments suivants :

  • GitHub
  • Git local installé sur chaque poste développeur
  • OpenShift qui héberge différents namespaces. Dans chaque namespace, un pod Jenkins pilote les builds (compilation/packaging) et le déploiement
  • Artifactory : repository des artifacts

Dans ce cas d’utilisation, tout se passe dans OpenShift, plateforme de containers. La plateforme est structurée en namespaces. Chaque namespace correspond à un environnement technique d’un projet (VAL, INT, PROD, …).

Chaque namespace (§. 1.1 ci-dessous) dispose de son propre pod Jenkins. Celui-ci exécute, à chaque update du projet dans Github (déclenchement par webhook), un job de type pipeline qui comporte plusieurs phases (stages) :

    1. Git clone du repository Github
    2. Build par maven (compilation –> tests unitaires –> tests intégration –> packaging)
    3. Audit du code par sonar
    4. Déploiement exécuté en fonction d’une build configuration et d’une deployment configuration (§. 1.1 ci-dessous)
      • Création d’une image docker à partir des sources de l’application
      • Création d’un pod (host exécutant le microservice) incluant le container de cette image
      • Génération du service, interface externe correspondant aux différents pods d’une application, et joue le rôle de répartiteur de charge
      • Génération des routes qui permettent d’accéder au service d’un pod

Exemple d’un dashboard de pipeline Jenkins, illustré ci-dessous :

Par ailleurs, OpenShift assure d’autres fonctionnalités opérationnelles :

  • Il gère les variations de charge de l’application par l’intermédiaire d’un replication controller: ajuste le nombre d’instances d’un pod en fonction de la charge.
  • Il assure le monitoring des applications qu’il héberge
  • Il gère les logs : logs des builds et logs applicatifs
  • Il permet via une interface Web d’accéder et d’administrer toutes ces fonctionnalités (§. 1.3 ci-dessous)

 

1.1    Notion de namespace dans OpenShift

La notion de namespace dans OpenShift vient de Kubernetes dont il est une sur-couche. Elle permet de regrouper dans un même espace de nommage les ressources qui décrivent une application et comment elle doit être déployée. La figure ci-dessous regroupe tous les éléments qui constituent un namespace :

Contrairement à Kubernetes qui permet d’accéder à tous les namespaces d’un cluster, OpenShift encapsule chaque namespace dans un projet qui va permettre d’en contrôler les accès par un modèle d’authentification et d’autorisations basé sur les users et les groups.

Il existe 3 canaux pour interagir avec un cluster Openshift à travers son API Rest : une CLI (ligne de commande) oc, une interface Web et l’API elle-même. Plus de détails dans la documentation OpenShift.

1.2    CLI (Ligne de commande) OpenShift

L’objet de ce document n’étant pas d’exposer en détail le fonctionnement d’OpenShift, on se contentera de montrer ci-dessous quelques vues du déploiement d’un service de base de données PostgreSql en exécutant des commandes oc. Dans le paragraphe suivant, on verra les vues relatives à ce service dans la console Web.

  • Déploiement d’une base PostgreSql

  • Affichage de la description du déploiement dc/my-database, créé par la commande précédente :

1.3    Console web OpenShift

Ci-dessous, quelques vues dans la console Web relatives au déploiement décrit dans le paragraphe précédent :

  • Description du déploiement dc/my-database : on voit une représentation du pod correspondant. Les flèches haut/bas (encadrée en rouge) permettent d’augmenter/diminuer manuellement le nombre d’instances du pod.

 

  • Vue des services : Outre le service my-database, on distingue un autre service, (microservice springboot) déployé qui utilise le service my-database

  • Vue d’une route (URL) pour accéder au microservice :

  • Vue du monitoring :

 En résumé

Dans cet article ont été rappelés les concepts de DevOps en relation avec une approche agile du développement d’applications. Deux schémas architecturaux de chaînes DevOps sont présentés ayant en commun les outils de versionning et de repositories (GitHub et Artifactory) ainsi que Jenkins comme pilote de l’intégration et du déploiement continus.

Dans cette deuxième partie, la chaîne DevOps présentée utilise OpenShift. Celui-ci joue les rôles à la fois d’une plateforme de containers et d’un cloud privé. A travers la notion de namespace encapsulé dans un projet, il gère les aspects droits d’accès, cycle de vie des applications, répartition de charges, gestion des logs et monitoring.

La première partie de cet artice est consacrée à la présentation d’une autre chaîne DevOps.  Elle utilise Ansible pour exécuter les déploiements sur les différents nœuds et environnements de l’application. Ansible est installé sur un nœud unique et permet via les playbooks et son interface Ansible Tower d’automatiser le processus de déploiement sur un grand nombre de nœuds (serveurs, périphériques, …).

 Pour en savoir plus

Chaîne DevOps intégrant Jenkins avec Ansible ou OpenShift (1/2)

Introduction

Le but de cet article est de présenter brièvement le concept de DevOps, illustré par une implémentation concrète dans un environnement applicatif de microservices Restfull, développés dans le contexte d’une approche agile.

La première partie est consacrée au rappel des principes de DevOps et à la présentation du schéma architectural d’une chaîne DevOps avec Github, Jenkins, Artifactory et Ansible.

La deuxième partie présente une autre chaîne DevOps avec Github, Jenkins, Artifactory et OpenShift.

1      Agile et DevOps main dans la main

Image result for devops

Le développement d’applications dans le cadre d’une approche agile impose des livraisons fréquentes au rythme des itérations,  tout en préservant la qualité des livrables, la maîtrise des releases et l’adaptabilité à différents environnements techniques et applicatifs. Pour répondre à ces exigences, l’automatisation des processus est nécessaire ainsi qu’une synergie entre les équipes de développement et celles des opérations. C’est là que DevOps apporte la solution à ces problématiques d’intégration continue (IC) et de déploiement continu (DC).

2     Les apports de DevOps

  • Fréquence des déploiements accrue
  • Délai d’exécution réduit pour les modifications
  • Récupération plus rapide en cas de problème
  • Une sécurité plus robuste et mieux intégrée
  • Qualité accrue
  • Feedback rapide

3      Les outils de DevOps

A chacune des étapes du workflow automatisé de DevOps correspondent des outils open source ou propriétaires (ci-dessous une liste non exhaustive) :

  • Versionning-commit de code : SVN, Git, TFS, GitHub, Tuleap
  • Qualité de code : SonarQube, IntelliJ, Eclipse
  • Sécurité : SonarQube, Checkmarx, dependency-check-maven
  • Tests : JUnit, Mockito, Selenium, Postman, Gatling, Jmeter
  • Build : Maven, NPM
  • Intégration/Déploiement/Livraison continus : Jenkins, Teamcity, Bamboo, TFS
  • Gestion des releases : Nexus, Artifactory
  • Déploiement : Ansible, Docker, OpenShift, Azure, AWS, VmWare
  • Performance, monitoring : Nagios, Zabbix, Spring boot Actuator
  • Logs : Splunk, Logstash, Elasticsearch

4      Exemple d’une mise en oeuvre avec Ansible

La chaîne DevOps décrite ci-dessous permet d’assurer des déploiements sur différents environnements techniques : INT, VAL et PROD.

Elle est composée des éléments suivants :

  • GitHub
  • Git local installé sur chaque poste développeur
  • Jenkins assure les builds (compilation/packaging)
  • Artifactory : repository des artifacts
  • Ansible assure les déploiements


Dans ce cas d’utilisation, un job Jenkins pilote le build (compilation, tests unitaires automatisés, audit de code par SonarQube, packaging). L’artifact résultant (un jar, dans le cas d’un microservice springboot) est uploadé dans Artifactory.

En post-action, le job va déclencher un script Ansible chargé de déployer le microservice dans les différents nœuds d’un cluster.

4.1    Concepts utilisés dans Ansible

Ansible permet d’automatiser les tâches de configuration et de déploiement des applications. Son administration se fait par le biais d’une interface Web, Ansible Tower.

Cette administration est centralisée dans une seule machine, le Control Node, où est installé Ansible. Aucune installation d’un agent n’est faite sur les autres machines gérées par Ansible (Managed nodes). Les concepts énumérés ci-dessous permettent de comprendre le fonctionnement de Ansible :

  • Inventory (inventaire) : liste des nœuds gérés, spécifiant des informations telles que les adresses IP. Cette liste peut être organisée selon différents critères : localisation, fonction, …
  • Module: c’est une unité de code Ansible spécialisée sur un type de fonctionnalités qu’on peut invoquer pour exécuter des tâches : administration des utilisateurs sur un type spécifique de base de données, gestion des interfaces VLAN sur un type spécifique de périphérique réseau, etc …
  • Task: c’est une unité d’action Ansible qu’on peut exécuter par une commande
  • Playbook: Liste ordonnée de tâches, enregistrées à exécuter à plusieurs reprises. Les playbooks peuvent inclure des variables ainsi que des tâches. C’est des fichiers écrits en YAML.
  • Roles : les rôles sont définis pour charger automatiquement des fichiers de configuration, tâches et gestionnaires. Ils sont organisés sous forme de fichiers dans des répertoires ayant un nommage et une structure pré-définis. Les rôles sont invoqués dans les playbooks (comme les imports dans un fichier java).

Exemple de mise en œuvre d’Ansible dans   Integrating Ansible with Jenkins in a CI/CD process.

  En résumé

Dans cette première partie,  ont été rappelés les concepts de DevOps en relation avec une approche agile du développement d’applications. Un premier schéma architectural de chaîne DevOps est présenté ayant comme outil de versionning et de repositories GitHub et Artifactory,  Jenkins comme pilote de l’intégration et du déploiement continus.

Cette chaîne utilise Ansible pour exécuter les déploiements sur les différents nœuds et environnements de l’application. Ansible est installé sur un nœud unique et permet via les playbooks et son interface Ansible Tower d’automatiser le processus de déploiement sur un grand nombre de nœuds (serveurs, périphériques, …).

Dans la deuxième partie, un autre schéma architectural d’une chaîne DevOps utilisant OpenShift sera présenté.

  Pour en savoir plus

Comprendre l’approche Cloud Native…pour bien la vendre en interne.

Selon la Cloud Native Computing Foundation (CNCF), fondée en 2015 par la Linux Foundation, les technologies Cloud Native permettent aux entreprises de créer et d’exécuter des applications scalables au sein d’environnements modernes et dynamiques tels que le Cloud public, privé ou hybride.

En guise d’exemple de technologies, on peut citer les containers, le service mesh, les microservices, les infrastructures immuables, l’intégration continue ou encore les APIs déclaratives. Les applications créées avec cette approche sont généralement basées sur une infrastructure en tant que service IaaS ou une plateforme en tant que service PaaS qui sera ensuite combinée avec les outils précédemment cités.

Une approche Cloud Native présente plusieurs avantages.
Tout d’abord, elle permet aux entreprises de transformer plus rapidement leurs idées d’applications en produits disponibles sur le marché. Elle permet non seulement d’accélérer les changements, mais aussi d’en réduire les risques.

En outre, les technologies Cloud Native permettent aussi une scalabilité accrue pour les applications. À mesure que les entreprises attirent de nouveaux utilisateurs dans un plus grand nombre de régions et sur un plus grand nombre d’appareils, il est possible de préserver la réactivité de l’application et de maintenir les coûts.

Un autre avantage majeur est qu’il permet généralement aux entreprises de dépenser moins d’argent dans l’hébergement. Ainsi, il est possible de réduire efficacement les charges.

Les applications Cloud Native requièrent une architecture très différente des applications d’entreprise traditionnelles, et c’est pourquoi elles présentent de nombreuses différences avec les applications sur site.

Tout d’abord, alors que les applications sur site conçues pour être exécutées sur les serveurs de l’entreprise sont généralement écrites dans des langages traditionnels (C/C++, C#…), les applications Cloud Native sont écrites dans des langages centrés web comme le HTML, le CSS, le Java, le JavaScript, le .net, le Go, le Node.js, le PHP, le Python ou le Ruby.

Les applis Cloud Native présentent aussi la particularité d’être toujours à jour et disponibles. De leur côté, les applications sur site requièrent des mises à jour durant lesquelles elles sont indisponibles, et sont généralement proposées avec un modèle d’abonnement par le vendeur.

En cas de pic d’utilisation, les applications Cloud Native peuvent profiter de l’élasticité offerte par le Cloud en utilisant davantage de ressources. Ces dernières peuvent ensuite être désactivées lorsque l’utilisation retourne à la normale. C’est une flexibilité dont ne disposent pas les applis sur site.

Une application sur source peut aussi être exécutée au sen d’un environnement virtualisé et partager des ressources avec d’autres applications. En cas de panne de l’un des Data Centers du fournisseur de Cloud, le stack peut être immédiatement transféré vers une autre région. Le risque de downtime est donc largement réduit par rapport à une application sur site.

Grâce au Cloud, la gestion des applications Cloud Native est entièrement automatisée grâce aux outils d’automatisation et d’orchestration. De plus, les applications Cloud Native présentent un design modulaire, car un grand nombre de leurs fonctions peuvent être décomposées en microservices. Il est donc possible de désactiver certaines fonctions ou de déployer des mises à jour pour des modules spécifiques plutôt que pour l’application complète.

Besoins de compétences Cloud Native ?

Faites appel au centre de compétences Cloud Native DocDoku ou développez vos compétences grâce à nos offres de formations DevOps Cloud Native.

Source : d’après un article de Bastien L publié sur LeBigData

Quelles perspectives pour Java et le développement Mobile ?

Quelles perspectives pour Java et le développement Mobile ?

C’est la question posée aux intervenants et participants de notre dernier ApéroTech du 14 novembre dernier à Toulouse.

A la découverte de Flutter 

Pour lancer l’ApéroTech sur le sujet du futur du développement Mobile, Thibaud a présenté une alternative à React Native : Flutter, lancé par Google. Les participants ont ensuite pu bénéficier d’une démo en live de l’API Spatioport pour découvrir les capacités du framework.

Retour d’experiences croisées sur Oracle Code One 2019

L’ApéroTech s’est poursuivi avec l’intervention d’Olivier et Bertrand, heureux participants du dernier Oracle Code One à San Francisco qui ont fait le point sur l’avenir de l’écosystème Java.
JVM, GraalVM, JakartaEE, Microprofile et Kubernetes : de multiples bouleversements accompagnent les mutations des environnements d’exécution.
Les impressions à chaud de Bertrand sont à retrouver sur son article ici.

Merci aux intervenants et participants pour leurs échanges et leur belle énergie !

Nous avons hâte de vous retrouver en mars 2020 pour une nouvelle édition.

Informations et inscriptions sur la page Meetup Les Z’ApéroTech.

8 thématiques incontournables à connaître pour maîtriser HTML5

L’HTML va bientôt fêter ses 30 ans. Ce langage de balisage a beaucoup évolué depuis sa création, avec l’intégration de JavaScript et CSS (Cascading Style Sheets) qui sont venus grossir les rangs des technologies web.
Et côté utilisateurs, dynamiser dans son ensemble la navigation web.

Développer des applications web modernes comme le permet HTML5 et CSS3 ne s’improvise pas.
Voici le top 8 des sujets incontournables à connaître pour maîtriser ce langage :

  • Une brique essentielle : JavaScript
  • Les API de communication
  • Le stockage côté client
  • Graphisme et multimédia
  • Les nouveaux tags HTML5
  • La présentation avec CSS3
  • Architecture et conception
  • L’outillage et l’environnement de développement

Vous êtes Architectes, développeurs ou webmasters et certains de ces sujets vous échappent encore ?

Nous avons une piste pour vous permettre de mettre à jour rapidement vos compétences : rejoignez notre prochaine session de formation Développer des applications HTML 5.

Prochaines sessions HTML5 à Toulouse :
Du 26 au 28 février 2020 (dernières places disponibles!)
Du 18 au 20 mai 2020

Prochaines sessions HTML5 à Paris :
Du 22 au 24 janvier 2020
Du 18 au 20 mars 2020

 

Collaboration et efficacité au quotidien avec Git

Vous êtes chef de projets, développeur ou architecte et vous cherchez à gérer vos sources de façons innovante et efficace ?

Vous avez sûrement entendu parler de Git, le système de contrôle de version distribué en licence Open Source.
Mais l’avez-vous concrètement utilisé ?
Et le maîtrisez-vous ?

Devenu incontournable, Git est un gestionnaire polyvalent capable de gérer aussi bien les petits que les très gros projets informatiques.
Des équipes de développement réparties géographiquement peuvent bénéficier des fonctionnalités de Git, en permettant à chacun de travailler de manière déconnectée et de se resynchroniser au moment voulu.

Git s’impose aujourd’hui dans de nombreuses organisations mais ses fonctionnalités avancées le rendent inévitablement plus complexe que les gestionnaires de sources traditionnels.
Pour répondre à cet enjeu et maîtriser ses fonctions surpuissantes, pourquoi ne pas vous former à Git ?

Constitués de 50 % de travaux pratiques, profitez de 2 jours pour revoir les fondamentaux de Git, son utilisation efficiente au quotidien, maîtriser la gestion des branches et son usage en équipe.

Informations et inscriptions ici.

Prochaines sessions 2019 :
Toulouse, du 4 au 5 novembre
Paris, du 2 au 3 décembre

Découvrez un éclairage différent sur la Blockchain

Saviez-vous que la technologie Blockchain proposait des solutions concrètes pour :

  • Assurer la véracité et la pertinence de vos données
  • Obtenir une vision globale de votre chaîne de valeur
  • Coordonner parfaitement des process Métier hétérogènes
  • Trouver des solutions à la complexité et l’étendue de votre Supply Chain ?

La Team DocDoku vous propose de partager son éclairage sur les usages concrets de la Blockchain au cours d’un petit déjeuner le Vendredi 18 octobre 2019 à partir de 8h30. Commencez votre journée en découvrant comment la Blockchain peut se mettre au service de votre Métier, au travers de cas d’usage concrets mais également de best practices métier et techniques.

Programme
8h30-9h : Accueil
9h-9h15 : Définir la Blockchain et identifier les projets « Blockchain ready »
9h15-9h45 : Découvrir un usage de la Blockchain dans l’Aéronautique (MRO)
9h45-10h00 : Comprendre les enjeux techniques
10h00-10h15 : Réflexions et perspectives
10h15-10h30 : Echanges avec les participants

Accès libre sur inscription réservé aux acteurs de l’innovation (CTO, DSI, Responsable Métier, R&D, Data…)

Informations pratiques
Quand ? Vendredi 18 octobre de 8h30 à 10h30
Où ? Chez DocDoku, 76 allée Jean Jaurès, 31 000 Toulouse

Inscrivez-vous en ligne ici

Quarkus : le framework Cloud Native révolutionnaire

L’écosystème Java est en plein renouveau. Oracle a officiellement acté son désengagement de Java EE (renommé Jakarta EE), les spécifications et leur implémentation de référence se font désormais sous l’égide de la fondation Eclipse et non plus au JCP (Java Community Process). Si ce chamboulement a pu générer quelques craintes et appréhensions quant au futur de la plateforme, aujourd’hui les conditions de passation de témoin entre Oracle et Eclipse semblent dorénavant établies (voir ici pour plus de détails sur l’imbroglio au sujet de l’utilisation du nom de package javax).

Place donc à la technique et je dois dire, pour vivre les choses de l’intérieur (nous sommes membres du consortium Jakarta) que la dynamique Jakarta est bien réelle. Le tournant micro-service a été pris avec conviction (il était temps), l’axe Cloud Native est clairement une priorité pour le consortium.

Jakarta poursuit l’oeuvre de standardisation de Java EE et offre aux développeurs le choix de l’implémentation. Nous avons toujours la filière traditionnelle avec un modèle basé sur le serveur d’applications. Mais surtout nous avons désormais la déclinaison Microprofile du standard. L’application ne nécessite alors plus de serveur pour fonctionner mais est packagée sous la forme d’une application autonome taillée pour les containers Dockers et les clusters Kubernetes. L’approche prise par la spécification Microprofile est particulièrement intéressante : conserver tels quels les modules Jakarta EE (CDI, JAX-RS, JPA…) et y adjoindre de nouveaux composants dédiés aux nouvelles problématiques découlant des architectures microservices. Notamment sont couverts les sujets liés à la tolérance à la panne, l’authentification par token JWT, la documentation Open API, les métriques et les traces, le Health Check, la génération de clients REST, l’intégration aux services de messaging… Bien sûr pour chacun de ces thèmes, il y a d’un côté la spécification des API et de l’autres les implémentations.

Cela nous amène à Quarkus qui est la nouvelle proposition Microprofile de Red Hat. Je dis nouvelle parce qu’il y a déjà Thorntail qui est la déclinaison Microprofile du serveur d’application WildFly. Quarkus lui, est une réécriture pensée véritablement pour l’approche Cloud Native. En effet, l’objectif ultime de Quarkus est de permettre la compilation native des applications grâce à GraalVM et ainsi corriger les deux faiblesses historiques de Java que sont l’empreinte mémoire et le temps de démarrage. A une époque pas si lointaine où le modèle d’exécution était le gros serveur d’applications pas ou peu redondé qui devait être redémarré le moins souvent possible, ces deux lacunes posaient peu de problème. Aujourd’hui le paradigme a changé, les applications sont découpées en plusieurs unités d’exécution (microservices) qui sont démarrées, répliquées, déplacées sur un cluster Kubernetes de nombreuses fois par jour (potentiellement tout du moins). Il est ainsi crucial que nos processus soient faiblement gourmands en ressources et puissent être lancés juste en une fraction de seconde.

Quarkus est encore jeune, la compilation native échoue plus souvent qu’elle ne réussit, mais ce n’est pas bloquant, il suffit alors de se rabattre sur une exécution classique JVM qui reste bien plus performante que n’importe quel serveur d’applications ou que Thorntail. Quarkus nous montre le futur des applications Java d’entreprise orientées Cloud Native. Si vous êtes intéressés, n’hésitez pas à échanger avec nous ou à vous inscrire à une de nos sessions de formation.

 

AWS IoT Greengrass et NBitcoin au menu du 3ème ApéroTech DocDoku

Le 27 juin dernier avait lieu la 3ème édition des ApéroTech dans nos locaux de Toulouse.

Au programme, présentation de 2 thèmes portés par des collaborateurs passionnés, suivie par un bon moment de convivialité en ce début d’été.

AWS IoT Greengrass

Premier thème abordé : comprendre ce qu’est le Edge Computing et ses applications.
Passionné de nouvelles technologies et persuadé des applications prochaines de l’internet des objets, Pierre a donné aux participants les clés de cette notion et présenté en cas d’usage le fonctionnement de Greengrass ou comment intégrer AWS dans un contexte IoT.

A la découverte de NBitcoin

Benjamin a ensuite guidé les participants dans la découverte de #NBitcoin pour #Csharp : de la construction d’une transaction Bitcoin à son transfert, puis à sa diffusion sur le réseau.  Au delà de l’usage en tant que cryptomonnaie, les perspectives de cette technologie sont très variées et l’assemblée a pu découvrir d’autres applications innovantes du Bitcoin.

Merci à nos deux talentueux orateurs et rendez-vous à la rentrée pour un prochain ApéroTech.

Bel été à toutes et tous !

Réaliser une transaction avec NBitcoin C# .NET

Introduction

  • Par soucis de « vulgarisation » et d’accessibilité au plus grand nombre, certains concepts clés évoqués dans cet article ont été « simplifiés » ou « raccourcis ». Il conviendra à chacun d’étoffer sa recherche à l’aide des liens fournis dans la rubrique « Ressources ».
  • Généralement, les présentations autour de Bitcoin commencent par une approche théorique des mécanismes permettant de sécuriser le réseau.
  • Dans cette présentation nous allons faire abstraction de tout cela → ?.
  • Nous allons  considérer la blockchain Bitcoin comme un service de stockage décentralisé que l’on va exploiter en mode Blockchain As a Service.
  • Nous consommerons ce service à l’aide de la librairie NBitcoin.net et de l’api QBitNinja.
  • Nous auront une approche pratique, qui consistera à programmer et signer un transfert Bitcoin entre deux adresses que nous allons créer.
  • La notion de « transfert » et de « vérification de propriété » est fondamentale pour le réseau Bitcoin puisqu’il à été spécialement conçu pour cela !
  • Tout en construisant notre transfert, nous découvrirons notamment à quoi servent une adresse, une clé privé, une clé publique et une transaction …

Généralités sur Bitcoin

  • Un système de transfert et de vérification de propriété,
  • Repose sur un réseau pair à pair,
  • Pas d’autorité centrale,
  • L’application initiale et l’innovation principale de ce réseau est un système de monnaie numérique décentralisé,
  • L’unité de compte au sein du réseau Bitcoin est nommée « Bitcoin »,
  • Bitcoin fonctionne avec des logiciels et un protocole,
  • Permet à ses participant d’émettre et de gérer des transactions de façon collective et automatique,
  • Un protocole libre et ouvert dont le code source est publié sous licence MIT,
  • Bitcoin est conçu pour s’auto-réguler,
  • Son bon fonctionnement est garanti par une organisation générale que tout le monde peut examiner,
  • Tout y est public : protocoles de base, algorithmes cryptographiques, programmes opérationnels, données de comptes, débats des développeurs …

Bitcoin Transfert : Problématique

  • Des fonds initiaux ont été envoyés à Bob,
  • Bob à réalisé un transfert de Bitcoin à Alice en lui laissant un message,
  • Nous allons recréer la transaction entre Bob et Alice à l’aide de NBitcoin,

Configuration du projet (.netCore + VSCode)

Auteur de NBitcoin et QBitNinja.Client : Nicolas Dorier, METACO SA

$ mkdir btcMeetup

$ cd btcMeetup

$ dotnet new console

$ dotnet add package NBitcoin

$ dotnet add package QBitNinja.Client

$ dotnet restore

Eléments fondamentaux

Pour réaliser un transfert sur le protocole Bitcoin, on s’appuie sur 5 éléments fondamentaux :

Private Key :

  • Permet de signer une transaction,
  • Donne le droit de dépenser les fonds liés à une adresse,
  • Ne doit pas être partagé, doit être conservé en lieu sûr.

Public Key :

  • Permet de s’assurer que vous êtes le propriétaire d’une adresse pouvant recevoir des fonds,
  • Est générée à partir de la clé privée (l’inverse étant « impossible ») Permet de s’assurer que vous êtes le propriétaire d’une adresse pouvant recevoir des fonds,
  • La clé public permet de générer une adresse bitcoin et un scriptPubKey.

ScriptPubKey :

  • C’est un peu l’équivalent d’une BitcoinAddress mais au niveau du protocole.
  • Vous n’envoyez donc pas des fonds à une adresse mais à un ScriptPubKey.
  • N’est pas facilement partageable contrairement à une adresse.

Bitcoin Address :

  • Pour recevoir un transfert de fonds,
  • Information facilement encodable en QR Code, 
  • Peut être communiquée à tout le monde.

Transaction :

  • Structure de données permettant d’encoder un transfert de valeur entre un ou plusieurs participants au réseau Bitcoin.

Générer une adresse pour Bob et une adresse pour Alice

Une adresse Bitcoin est une information que vous allez partager avec les autres utilisateurs du réseau pour recevoir des fonds.

Mais pour obtenir une adresse vous devez d’abord générer une clé privée !

  • Elle est le seul moyen de dépenser les bitcoins envoyés à votre adresse.
  • Les clés privées sont personnelles et ne sont pas stockées sur le réseau.
  • Elles peuvent être générées sans être connectées à internet.

NBitcoin supporte plusieurs standards pour générer des clés privées …

Nous allons utiliser les standards BIP32 et BIP39 qui permettent de générer une clé racine à partir d’une wordlist. C’est sur ce principe que fonctionnent les cold-wallets tel que Ledger Nano ou Trezor. Cette wordlist ou seed ou mnemonic permet de re-générer indéfiniment la même suite de clé.

Ci-après le processus de génération de clés que nous allons suivre :

Etape 1 : Générer une nouvelle mnémonique :

Etape 1 (bis) : Restaurer une mnémonique existante :

Etape 2 : On génère la clé racine  qui dérive de notre « Mnémonique » et d’un mot de passe :

Etape 3 : On peut maintenant générer deux « sous-clés », une pour  Bob, une pour Alice, en dérivant de notre clé racine :

Remarque : Grâce à ce mécanisme de dérivation, il est possible de générer et régénérer des arborescences complexes de clés. On peut par exemple construire une arborescence de clés basées sur l’organigramme d’une entreprise.

Etape 4 : A partir de nos deux clés privées, nous allons pouvoir obtenir leurs clés publiques respectives :

  • On génère une clé publique à partir d’une clé privée au moyen d’une fonction à sens unique.
  • C’est à dire une fonction qui peut aisément être calculée mais difficilement inversée.
  • La clé publique permet de recevoir des fonds et d’attester que vous être le propriétaire d’une adresse.
  • En revanche, elle ne permet pas de dépenser les fonds d’une adresse.

Etape 5 : Après avoir généré les clés publiques d’Alice et Bob, nous allons pouvoir obtenir leurs adresses :

  • Il existe 2 réseaux Bitcoins : MainNet et TestNet,
  • On obtient une adresse Bitcoin à partir de sa clé publique, et en précisant le réseau sur lequel on souhaite opérer.
  • Remarque: sur le MainNet, les erreurs peuvent coûter cher ? !

Etape 6 : Consulter les comptes de Bob et d’Alice :

Adresse de Bob : 16joUFCsaVDfacYsDxCm2oF5mRyLTq6DvY

Adresse d’Alice : 13hA6HP6W4BCwmtr89CKh6fTra19Exm4bL

Si on regarde d’un peu plus près les informations fournies par BlockCypher concernant l’adresse de Bob, on peut remarquer deux choses plutôt curieuses au premier abord :

  • la différence entre le total des inputs et des outputs (0.000355 BTC) → Frais de minage.
  • Bob se renvoie à lui même une partie des fonds impliqués dans la transaction → Tous les fonds impliqués doivent être dépensés.

Etape 7 : Analyser une transaction Bitcoin

Pour envoyer des fonds de Bob vers Alice, il va donc nous falloir construire une transaction et la soumettre au réseau Bitcoin.

Les transactions sont au coeur du système Bitcoin, elles contiennent les informations relatives aux transferts de valeur entre les participants du réseau.

Pour envoyer des fonds à Alice, il va donc falloir faire référence à l’output d’une transaction qui à transféré des fonds initiaux à Bob et que ce dernier n’à pas dépensé …

En consultant la dernière transaction liées à l’adresse de Bob, on retrouve rapidement cette information :

Notre nouvelle transaction va donc « s’accrocher » à l’output non dépensé de celle-ci !

QBitNinja va nous permettre de récupérer ces informations afin de les manipuler dans notre application console.

Etape 8 : Récupérer une transaction stockée sur la blockchain avec le client QBitNinja :

Affichons l’intégralité de la transaction :

Résultat :

Etape 9 : Construire la transaction Bitcoin

Maintenant que l’ont sait quoi dépenser, on peut commencer à construire notre nouvelle transaction :

Et ajouter un nouvel input à cette nouvelle transaction :

Si on visualise notre transaction à ce stade, on obtient :

Voyons maintenant comment dépenser notre input …

On calcule la répartition des fonds entre les différents Outputs de notre future transaction en respectant le fait que :

  • tous les fonds en input de la transaction doivent être dépensés,
  • la différence entre le montant total des inputs et des outputs sera reversé au mineur,
  • des frais de minages trop faibles entrainent un traitement plus long de la transaction (voire elle n’est jamais traitée),

Pour calculer les frais de minages on peut directement chercher sur google :

  • plus vous payez, plus vite est traitée votre transaction,
  • il s’agit d’une moyenne, le prix est fixé en fonction du poids de votre transaction.
  • si vous ajoutez un message, vous devrez augmenter les frais.
  • pour calculer les frais adéquats :

https://bitcoinfees.earn.com/ (coût par byte)

La répartition des fonds entre les différents outputs est prête, on peut les créer et les ajouter à notre transaction :

Etape 10 : Joindre un message à votre transaction

Output contenant le message :

  • On peut donc stocker une information sans forcément transférer de la valeur, du moment que les frais de minage sont couverts !
  • Ce message est inaltérable et incensurable.
  • Vous payez pour écrire ce message, mais sa lecture est « gratuite », par n’importe qui ou n’importe quoi ayant accès au réseau Bitcoin.

Etape 11 : Signer la transaction avec votre clé privée :

Avant de pouvoir diffuser votre transaction, vous devez d’abord la signer à l’aide de votre clé privée :

Etape 12 : Diffuser la transaction sur le réseau Bitcoin :

Il est désormais temps de diffuser notre transaction au réseau !

Pour cela, il faut la transmettre à un noeud du réseau,

Plusieurs options s’offrent à vous :

  • Copier le code Hexadécimal de votre transaction et le pusher via un explorateur de blocks tel que BlockCypher :
  • Passer par l’API QBitNinja :

Etape 13 : Consulter l’état de la transaction diffusée :

Pour consulter l’état de notre transaction diffusée, rendez-vous sur un explorateur de Block tel que BlockCypher :

Transaction ID : a9172485a20e06b9a745f5105cd23cabd9f866e890b0bd4c25c3d29c073cce14

Si on consulte l’adresse d’Alice :

Conclusions et perspectives

L’application première du réseau Bitcoin est l’échange de la cryptomonnaie du même nom.

D’ailleurs sur ce réseau tout se paye en Bitcoins.

Mais il peut également servir à autre chose (et NBitcoin supporte tout cela):

  • stocker des données de façon indélébile et non-censurable,
  • émettre et détruire votre propre « tokens » pour représenter les parts d’une entreprise, des actions ou des votes,
  • attacher un « contrat ricardien » à un token (une sorte de smart contract lisible par l’homme et destiné au monde juridique).

Le protocole est en perpétuelle évolution et vise à devenir de plus en plus efficient (plus rapide, moins coûteux).

Il est également à noter que le coût de transaction n’est pas proportionnel au montant transféré et dépends de la « taille » en bytes de votre transaction. Ainsi, transférer 10$ ou 10M$ aura un coût de transaction équivalent !

Si aujourd’hui la lenteur et les frais de transaction élevés limite l’usage du réseaux, des mises à jour comme le « Lightning Network » ont le potentiel de changer la donne et maintenir Bitcoin sur le devant de la scène.

Ressources :

Programming the Blockchain in C# (Nicolas Dorier, Metaco SA)

NBitcoin (Nicolas Dorier, Metaco SA)

QBitNinja (Nicolas Dorier, Metaco SA)

Stratis Platform (Blockchain As A Service / BaaS, C# Smart-Contracts)

Microsoft Visual Studio Code (IDE)

IoT et Agriculture, SAFe et les secrets de JakartaEE8

Une nouvelle édition des ApéroTech a réuni il y a quelques jours une trentaine de passionnés de technologie dans les locaux de DocDoku à Toulouse.

IoT, méthodologie projet et programmation ont été les thèmes choisis et présentés par des intervenants particulièrement enthousiastes (et ce, alors que la partie Apéro n’avait pas encore commencé 😉 )

Les participants ont ainsi pu découvrir et échanger sur :

IoT : à la découverte d’une sonde connectée dernière génération.

Junior a immergé les participants dans la conception et le fonctionnement d’un device et de sa plateforme associée. Conçue en collaboration avec des agriculteurs pour connaître les besoins des sols en fonction des plantes cultivées, le projet présenté utilisait les technologies bluetooth4.0, Lorawan, Objenious, REST, PostgreSQL et Rails.

L’agilité à l’échelle de l’entreprise avec SAFe.

Bertrand a donné aux participants les clés de cette méthodologie, ses avantages, dans quels contextes l’utiliser sans toutefois oublier ses limites.

Les nouveautés de la plateforme #JavaEE ou plutôt #JakartaEE 8 .

Florent a fait le point sur les grandes familles de nouveautés à retrouver dans cette nouvelle plateforme : l’alignement entre modules, les nouveaux standards Web, les nouveaux modules…

Le prochain ApéroTech aura lieu le jeudi 18 avril à Paris.
Renseignez-vous et téléchargez votre invitation gratuite.

Mise en oeuvre de la Haute disponibilité dans PostgreSQL

Rappels de quelques notions

Avant d’aborder le sujet de la HA (High Availability), il est utile de rappeler quelques principes qui vont être utilisés dans la suite de cet exposé.

RPO et RTO

Il existe plusieurs méthodes de sauvegarde d’une base de données PostgreSQL, chacune présentant des avantages et des inconvénients eu égard à la volumétrie des données, à la granularité des sauvegardes qu’on veut réaliser, à la cohérence des données sauvegardées, au transfert des données entre différentes versions de PostgreSQL et entre machines d’architectures différentes. Par ailleurs, on distingue deux types de sauvegardes : les sauvegardes logiques et les sauvegardes physiques.

Une sauvegarde logique consiste à archiver les commandes sql qui ont servi à générer la base et à les rejouer pour la restauration de celle-ci. Pour celà, il existe 2 commandes dans PostgreSQL : pg_dump et pg_dumpall. On trouvera plus de détails sur ces commandes dans  la documentation de PostgreSQL https://www.postgresql.org/docs/10/backup.html.

Une sauvegarde physique ou sauvegarde au niveau du système de fichiers consiste à archiver les répertoires des données de la base en utilisant les commandes de copie ou d’archivage des fichiers du système d’exploitation : tar, cp, scp, rsync, … On peut aussi réaliser une “image gelée” (frozen snapshot) du volume contenant la base de données et ensuite copier entièrement le répertoire de données (pas seulement quelques parties) de l’image sur un périphérique de sauvegarde, puis de libérer l’image gelée.

Les objectifs d’une bonne procédure de sauvegarde sont exprimés par deux métriques :

  • le RPO (Recovery Point Objective) : représente la quantité maximum de données qu’on peut tolérer de perdre suite à un incident de la base de données.
  • le RTO (Recovery Time Objective) : représente la durée maximum d’une interruption de service qu’on peut tolérer, le temps de restaurer la base et la remettre en service.

L’objectif idéal étant que ces 2 paramètres soient nuls, PostgreSQL propose des approches qui permettent de s’en approcher. Elles sont basées sur la mise en oeuvre de l’archivage continu et la récupération  d’un instantané (PITR : Point In Time Recovery).

Archivage continu et PITR

Cette approche est basée sur l’utilisation des journaux WAL (Write Ahead Log), appelés aussi journaux des transactions. Ils enregistrent chaque modification effectuée sur les fichiers de données des bases. Ils sont stockés dans le sous-répertoire pg_wal/ du répertoire des données du cluster.

La sauvegarde consiste à combiner une sauvegarde de niveau système de fichiers avec la sauvegarde des fichiers WAL.

La restauration consiste à restaurer les fichiers de la base puis de rejouer les fichiers WAL sauvegardés jusqu’à emmener la base à son état actuel.

Avantages :

  • Il n’est pas nécessaire de disposer d’une sauvegarde des fichiers parfaitement cohérente comme point de départ. Toute incohérence dans la sauvegarde est corrigée par la ré-exécution des journaux
  • Puisqu’une longue séquence de fichiers WAL peut être assemblée pour être rejouée, une sauvegarde continue est obtenue en continuant simplement à archiver les fichiers WAL. C’est particulièrement intéressant pour les grosses bases de données dont une sauvegarde complète fréquente est difficilement réalisable.
  • Les entrées WAL ne doivent pas obligatoirement être rejouées intégralement. La ré-exécution peut être stoppée en tout point, tout en garantissant une image cohérente de la base de données telle qu’elle était à ce moment-là. Ainsi, cette technique autorise la récupération d’un instantané (PITR) : il est possible de restaurer l’état de la base de données telle qu’elle était en tout point dans le temps depuis la dernière sauvegarde de base.
  • Si la série de fichiers WAL est fournie en continu à une autre machine chargée avec le même fichier de sauvegarde de base, on obtient un système « de reprise intermédiaire » : à tout moment, la deuxième machine peut être montée et disposer d’une copie quasi-complète de la base de données.

Contraintes :

  • approche plus complexe à administrer
  • ne supporte que la restauration d’un cluster de bases de données complet, pas d’un sous-ensemble
  • espace d’archivage important requis : si la sauvegarde de base est volumineuse et si le système est très utilisé, ce qui génère un grand volume de WAL à archiver.

La réplication

Dans une architecture maître-esclave, le transfert des données de base et des WAL (Log shipping) peut se configurer de 3 manières légèrement différentes :

  • Warm Standby : les fichiers des transactions (WAL) archivés sont transférés du maître vers l’esclave par copie et rejoués, avec un retard d’un fichier WAL (16 MB) par rapport au maître. La perte éventuelle de données ne peut donc excéder 16 MB. Dans cette configuration, la base esclave n’est pas accessible.
  • Hot Standby : le fonctionnement est identique au Warm Standby. La différence est que la base esclave est accessible en lecture seule.
  • Streaming Replication :  dans cette configuration, chaque transaction est transférée vers la machine esclave via le réseau, et rejouée, sans attendre que le fichier WAL soit complété.  Ainsi, le RPO est quasiment nul (une transaction perdue au maximum dans une réplication asynchrone, 0 dans une réplication synchrone).

Tolérance à panne

La tolérance à panne (failover) est la capacité d’un système à maintenir un état de fonctionnement en dépit d’une défaillance logicielle ou matérielle. Elle est rendue possible par la réplication. Nous étudierons ici les moyens à mettre en oeuvre pour assurer une continuité de service en toutes circonstances.

Gestion du Failover avec repmgr

Une Architecture Maître/Esclave sert à gérer les situations de failover où la machine Esclave prend le relais en cas de défaillance de la machine Maître.

Pour ce faire, une réplication continue est instaurée entre les 2 machines de telle façon que les données des 2 machines soient quasiment identiques à chaque instant.

Pour pouvoir gérer en plus,  la capacité de déclencher la bascule (le failover) automatiquement, il existe plusieurs outils dans l’écosystème PostgreSQL tels que repmgr. Celui-ci peut-être utilisé seul ou en conjonction avec d’autres outils de backup comme BARMAN (Backup and Recovery Manager for PostgreSQL) développé par 2ndQuadrant (https://www.2ndquadrant.com/) avec une licence GPL v3, dont la dernière version est la 2.5 du 23/10/2018 (http://www.pgbarman.org/).

Présentation de repmgr

repmgr est une suite d’outils open source développés par 2dQuadrant ( https://repmgr.org/docs/4.2/index.html) qui mettent en oeuvre la réplication native de PostgreSQL et qui permettent de disposer d’un serveur dédié aux opérations de lecture/écriture (Primary ou Maître) et d’un ou plusieurs serveurs en lecture seule (Standby ou Esclave). Deux principaux outils sont fournis :

  • repmgr : outil en ligne de commandes pour les tâches d’administration :
    • configuration des serveurs standby
    • promotion d’un serveur standby en primary
    • inverser les rôles des deux serveurs (switchover)
    • afficher les statuts des serveurs
  • repmgrd : deamon qui supervise l’état des serveurs et qui permet de :
    • surveiller les performances
    • opérer un failover quand il détecte la défaillance du primary en promouvant le standby
    • émettre des notifications sur des événements qui peuvent survenir sur les serveurs à des outils qui vont déclencher des alertes (mail par exemple)

Architecture

Pré-requis système

  • Linux/Unix
  • repmgr 4.x compatible avec PostgreSQL 9.3+
  • La même version de PostgreSQL sur tous les serveurs du cluster
  • repmgr doit être installé sur tous les serveurs du cluster, au moins dans la même version majeure*
  • les connections entre serveurs doivent être ouvertes sur leurs ports respectifs
  • les accès ssh par échange de clés publiques pour le user postgres de chacun des serveurs doivent être configurés.

Installation

L’installation se fait pour :

  • RedHat/CentOS/Fedora à partir du repository yum de 2ndQuadrant
  • Debian/Ubuntu à partir des repository APT de postgreSQL (http://apt.postgresql.org/) ou du repository APT de 2ndQuadrant (https://dl.2ndquadrant.com/default/release/site/)

Pour la version 10 de PostgrSQL :

sudo apt-get install postgresql-10-repmgr

Configuration de PostgreSQL

Les exemples ci-dessous concernent une version 10.x de PostgreSQL

Ajuster les paramètres ci-dessous dans les fichiers de configuration des PostgreSQL (postgresql.conf) :

  # Enable replication connections; set this figure to at least one more
   # than the number of standbys which will connect to this server
   # (note that repmgr will execute `pg_basebackup` in WAL streaming mode,
   # which requires two free WAL senders)

 max_wal_senders = 10

   # Enable replication slots; set this figure to at least one more
   # than the number of standbys which will connect to this server.
   # Note that repmgr will only make use of replication slots if
   # « use_replication_slots » is set to « true » in repmgr.conf

max_replication_slots = 3

   # Ensure WAL files contain enough information to enable read-only queries
   # on the standby.
   #
   # PostgreSQL 9.5 and earlier: one of ‘hot_standby’ or ‘logical’
   # PostgreSQL 9.6 and later: one of ‘replica’ or ‘logical’
   # (‘hot_standby’ will still be accepted as an alias for ‘replica’)
   #
   # See: https://www.postgresql.org/docs/current/static/runtime-config-wal.html#GUC-WAL-LEVEL

wal_level = replica

   # Enable read-only queries on a standby
   # (Note: this will be ignored on a primary but we recommend including
   # it anyway)

hot_standby = on

   # Enable WAL file archiving
archive_mode = on

   # Set archive command to a script or application that will safely store
   # you WALs in a secure place. /bin/true is an example of a command that
   # ignores archiving. Use something more sensible.
 archive_command = ‘/bin/true’

Création du user repmgr et du schéma repmgr

Le schéma repmgr sert à enregistrer les données de monitoring de repmgr. On y accède avec le user repmgr. Ils doivent être créés sur chacun des instances des serveurs :

sudo -i -u postgres

psql -p 5432

create role repmgr with password ‘password123’ ; alter role repmgr login ; alter role repmgr replication ; alter role repmgr superuser;

CREATE DATABASE repmgr OWNER repmgr;

Configuration de pg_hba.conf

L’objectif de cette configuration est de permettre au user repmgr de se connecter en mode replication :

 local   replication   repmgr                       trust
 host    replication   repmgr 127.0.0.1/32            trust
 host    replication   repmgr 195.154.39.0/24      trust

  local   repmgr      repmgr                       trust
  host    repmgr      repmgr 127.0.0.1/32             trust
  host    repmgr      repmgr 195.154.39.0/24         trust

Vérifier que la base sur le serveur primaire est joignable à partir du serveur standby :

psql ‘host=195.154.39.62 user=repmgr dbname=repmgr connect_timeout=2’

Configuration de repmgr dans le noeud primaire

La configuration se fait dans le fichier /etc/repmgr.conf aux niveau du primaire (node_id=1, node_name=val) et du standby ((node_id=2, node_name=customers).

Remarques :

  • Les paramètres indiqués ci-dessus sont les paramètres de base pour un fonctionnement standard de repmgr. On trouvera d’autres paramètres disponibles dans le fichier repmgr.conf.sample  fourni dans la distribution.

Enregistrement du serveur primaire

L’enregistrement du serveur primaire auprès de repmgr va créer les métadata dans le schéma repmgr et y initialisera un enregistrement correspondant au serveur primaire :

  • Avec le user postgres , exécuter la commande suivante :
  • $ repmgr -f /etc/repmgr.conf primary register
  • Vérifier le statut du cluster avec  la commande suivante :

Les metatda sont affichés  avec la commande suivante :

Configuration du serveur standby

Créer le fichier de configuration /etc/repmgr.conf, similaire à celui du primaire. Il diffère essentiellement par le n° et le nom du noeud ainsi que la chaîne de connexion conninfo :

Clonage du serveur standby

On exécutera la commande de clonage en dry-run pour s’assurer que toutes les conditions de succès sont satisfaites :

  • postgres@customers:~$ repmgr -h 195.154.39.62 -U repmgr -d repmgr -p 5434 -f /etc/repmgr.conf -F standby clone –dry-run

Si pas de problème rapporté, exécuter la même commande sans l’option dry-run :

  • postgres@customers:~$ repmgr -h 195.154.39.62 -U repmgr -d repmgr -p 5434 -f /etc/repmgr.conf -F standby clone
  • Si le clonage s’est bien terminé , vérifier que la réplication se passe bien.
    • Dans le serveur primaire, exécuter la commande suivante :
  • Dans le serveur standby, exécuter la commande suivante :

  • Enregistrer le standby :

postgres@customers:~$  /usr/lib/postgresql/10/bin/repmgr standby register

  • Vérifier que le standby est enregistré :

Vérifier que tout fonctionne

  • Créer une table sur le maître :

CREATE TABLE rep_test (test varchar(40));

INSERT INTO rep_test VALUES (‘replique moi);

  • Vérifier qu’elle est répliquée dans le standby:

SELECT * FROM rep_test;

Failover automatique avec repmgrd

Dans le schéma ci-dessus, si val est indisponible , customers est promu en primary, et en mode read-write. Ce failover peut être exécuté manuellement avec 2 commandes de repmgr :

  • Au niveau de customers : /usr/bin/repmgr standby promote -f /etc/repmgr.conf –log-to-file
  • Au cas où il y aurait plusieurs serveurs standby, on exécute au niveau de chacun une commande de follow, qui leur indique le nouveau serveur maître :

/usr/bin/repmgr standby follow -f /etc/repmgr.conf –log-to-file –upstream-node-id=2

Le daemon repmgrd permet de surveiller l’état des serveurs du cluster et de déclencher automatiquement les 2 opérations décrites ci-dessus.

Configuration de repmgrd

  • Pour utiliser repmgrd, inclure la librairie associée via la configuration de postgresql.conf :

shared_preload_libraries = ‘repmgr’

  • ajouter dans /etc/repmgr.conf les lignes suivantes :

failover=automatic

promote_command=’/usr/lib/postgresql/10/bin/repmgr standby promote -f /etc/repmgr.conf –log-to-file’

follow_command=’/usr/lib/postgresql/10/bin/repmgr standby follow -f /etc/repmgr.conf –log-to-file –upstream-node-id=%n’

monitoring_history=yes

  • Si repmgr a été installé à partir des packages de Debian/Ubuntu, il faudra configurer pour que repmgrd tourne en daemon, via le fichier /etc/default/repmgrd , dont le contenu est le suivant :

Démarrage de repmgrd

Pour démarrer repmgrd, exécuter la commande suivante sur chacun des serveurs, en tant que postgres :

/usr/lib/postgresql/10/bin/repmgrd -f /etc/repmgr.conf

Vérification du fonctionnement du failover

En considérant l’architecture à 2 serveurs ci-dessus , effectuer les opérations suivantes :

  • Statut du départ :
  • Après le démarrage de repmgrd sur les 2 serveurs, vérifier sur leurs logs respectifs qu’ils sont bien actifs
  • Arrêter le serveur primaire :

/usr/lib/postgresql/10/bin/pg_ctl stop -D /var/lib/postgresql/10/main

  • Vérifier de nouveau le statut du cluster :

On remarque que le noeud 2 est devenu primaire.

  • Une fois, val  “réparé”, le retour à la situation initiale s’effectue en 2 temps : d’abord mettre val comme standby du primaire customers puis faire un switchover sur val pour qu’il redevienne primaire et customers redevient standby :
  • Cloner val à partir de customers et l’enregistrer comme standby :

postgres@val$ /usr/lib/postgresql/10/bin/repmgr -h 195.154.39.117 -p 5433 -U repmgr -d repmgr -f /etc/repmgr.conf standby clone –force

  • Redémarrer val :

postgres@val$ pg_ctlcluster 10 main start

  • enregistrer val en standby :

postgres@val$ /usr/lib/postgresql/10/bin/repmgr standby register –force

  • Vérifier l’état du cluster :

On constate que le cluster fonctionne avec customers comme primaire et val comme standby.

Pour inverser les rôles, on fait un switchover sur le standby :

  • Vérifier l’état du cluster

Remarque : le switchover agit sur le standby et sur les autres serveurs pour qu’ils soient avertis du nouveau primaire.

Conclusion

Ce post a permis d’exposer pas à pas les étapes pour configurer une HA sur un cluster à 2 noeuds, en mode primaire/standby. Pour ce faire, on a mis en oeuvre une streaming réplication avec repmgr et un monitoring des 2 noeuds avec le daemon associé à repmgr, ce qui permet de déclencher le failover automatique en cas de défaillance du noeud primaire. Le retour à la configuration initiale, après réparation de la défaillance, est réalisé avec un switchover. Le protocole de streamaing réplication permet de minimiser le RPO car il permet de rejouer les transactions au fil de l’eau au niveau du standby. Pour minimiser le RTO, il faut ajuster la fréquence des backup de base en fonction de la volumétrie des transactions.

La configuration qui a été présentée ici, peut être renforcée par un système de load balancing par un outil tiers dans le cas général , style pgpool-II ( http://www.pgpool.net ) ou par le driver jdbc si on est dans l’écosystème java.

Les Feature Toggles* : Activer / Désactiver dynamiquement une fonctionnalité d’un logiciel

* La désignation ne fait pas consensus et la liste des termes synonymes est très longue : Feature Flag, Feature Switch, Conditional Feature, Feature flipper, Feature Bits, Gatekeepers….

Le principe

À la livraison d’une nouvelle version d’un logiciel sur un environnement, toutes les nouvelles fonctionnalités sont rendues disponibles immédiatement. Au contraire, le modèle de design Feature Toggle* a pour but de fournir un interrupteur de fonctionnalités qui active ou désactive une partie du code sans nécessiter ni redéploiement ni redémarrage de l’application.

Les usages possibles

Cette fonctionnalité peut avoir de nombreux usages. Les cas suivants en sont un exemple et ne représentent pas l’exhaustivité des situations.

CAS 1 – Différents besoins sur différents environnements

Pratiquer des livraisons régulières, fréquentes et de petite taille est une des conditions à garantir pour l’agilité d’un projet (Continuous Delivery). L’enjeu est le maintien d’un cycle de développement court pour limiter les risques du rythme élevé de changements. Or si cette stratégie s’avère un atout en environnement de recette, où la validation de petites briques logicielles réduit la durée de la phase de tests, elle peut être difficile à réaliser en environnement de production. En effet, une fonctionnalité peut représenter plusieurs itérations de développements et il serait incongru de la mettre à disposition de l’utilisateur de façon incomplète. Le système de feature toggles rend possible la livraison de parties de fonctionnalités en mode désactivé sans que cela n’affecte la stabilité du code. L’activation pourra être réalisée au bout de quelques semaines, une fois que l’ensemble du code nécessaire aura été livré.

CAS 2 – Incident suite à une livraison

Lorsqu’un dysfonctionnement est rencontré en production par un utilisateur suite à une récente livraison, il sera possible de désactiver la fonctionnalité pour réduire l’instabilité du logiciel en attendant une résolution.

CAS 3 – Dépendances entre équipes

Il n’est pas rare que plusieurs équipes travaillent en parallèle sur des besoins différents et dans des délais qui leur sont propres tout en ayant en commun une ou plusieurs applications. Par exemple, l’interface peut être la même pour toutes les équipes. Lorsque le moment est venu pour une équipe de livrer son travail, elle se trouve gênée par les développements encore en cours ou en validation des autres équipes sur l’application partagée. Si chaque équipe a mis en place des toggles,  la livraison sera envisageable à la condition que chaque fonctionnalité non encore terminée soit désactivée.

CAS 4 – Tests comparatifs

Il existe une catégorie de Feature toggles dont l’activation se fait par utilisateur. La condition d’activation peut être déterminée par un profil de permissions ou encore l’appartenance à un échantillon. Au lieu de plusieurs environnements, un seul est nécessaire pour comparer des résultats entre différents groupe d’utilisateurs et valider une approche.

AUTRES CAS – L’activation de fonctionnalités peut aussi…

  • concerner la configuration d’un système opérationnel déjà en service afin de déterminer les meilleurs paramètres (court terme) ou bien de maintenir un service stratégique au détriment d’un autre en dégradant les performances à son profit (long terme)
  • être amenée à rester sur le long terme dans le cas du besoin de customisation de fonctionnalités par utilisateur (par exemple un mode « utilisateur premium»)

Selon Martin Fowler 1 – son blog, il existe deux axes de qualification pour un feature toggle, son dynamisme (changements au déploiement / à chaud / par requête) et sa longévité (nombre de jours / mois / années). Cette classification doit orienter vers le choix d’une implémentation.

Un rapide aperçu avec FF4J

FF4J  (Feature Flipping for Java) est un framework en licence libre apache 2.

Une fois les dépendances nécessaires ajoutées (ff4j-core, junit, ff4j-web) et la configuration paramétrée, il faudra créer un service fournissant une instance de la classe FF4J.

Étape 1. Créer un feature toggle

Cela consiste à insérer une nouvelle entrée dans un feature store (le système de stockage) précisant l’identifiant du toggle, sa description, son statut d’activation, son groupe d’appartenance, les rôles d’utilisateurs ou les stratégies associés. L’offre de système de stockage des données est large : mémoire, bases relationnelles ou no-sql.

CREATE TABLE FF4J_FEATURES (
  "FEAT_UID"            VARCHAR(100),
  "ENABLE"              INTEGER NOT NULL,
  "DESCRIPTION"         VARCHAR(1000),
  "STRATEGY"            VARCHAR(1000),
  "EXPRESSION"      VARCHAR(255),
  "GROUPNAME"           VARCHAR(100),
  PRIMARY KEY("FEAT_UID")
);

ci-dessus : Table prévue pour le stockage en base relationnelle, le champ « enable » stocke 1 pour le statut « activé » 0 pour « désactivé ».

Étape 2. Utiliser le feature toggle dans le code

L’identifiant choisi à l’étape 1 permettra de vérifier l’état d’activation.

If (ff4j.check("id.du.toggle")) {
	doBehiavorA()
}  else {
	doBehiavorB()
}

ci-dessus : si le toggle est activé alors le comportement A est joué sinon le comportement B fonctionne.

Étape 3 . Déployer le code sur un environnement

Étape 4 . Contrôler l’activation via la console d’administration

ci -dessus : Une interface graphique permet de réaliser l’opération sans nécessiter de compétences techniques.

Limites

     Ce mécanisme complexifie le code : deux cas de comportements sont implémentés en même temps donc deux fois plus de code. Il est donc conseillé de réfléchir aux bons emplacements stratégiques dans le code et de limiter le nombre de recours. Dans le même ordre d’idée, le nombre total de fonctionnalités couvertes se doit de rester faible pour rester maintenable.
     Heureusement, ce risque est diminué dans la plupart des usages puisqu’ils répondent à un besoin temporaire. Néanmoins, cette situation temporaire va induire une opération supplémentaire de nettoyage du code, une fois que la fonctionnalité est activée de manière définitive.
     Il existe également quelques cas où la sécurisation avec un toggle n’est pas envisageable. J’ai notamment déjà rencontré le cas lors d’un changement structurel important au niveau du modèle en projet .
     Il existe une controverse. En effet, les outils de gestion de versions (tel que Git) apportent une aide précieuse dans la résolution de certaines problématiques (ex. CAS 3). Si la pratique très courante du Feature Branching (séparation des fonctionnalités en branches) est jugée inadaptée face au défi de l’intégration et de la livraison continues pour les uns 1 – martin fowler blog, elle reste une réponse possible lorsque bien exécutée pour d’autres 2 – james mckay blog. Ces approches peuvent aussi être utilisées côte à côte.

     Enfin, cette approche va de pair et prendra tout son sens dans un contexte où l’automatisation des tests et des déploiements est déjà en place.

Quelques implémentations

Flaggr (Android), angular-feature-flags (Angular), rollout.io (javascript), Togglz (Java), au sein du framework Spring (Java)…