Contexte
Apache Camel est un framework open-source connu et reconnu pour la transmission des messages entre systèmes. Camel se structure autour de “routes”. Il s’agit de médiations formées des éléments suivants :
- Un point d’entrée. Il peut d’agit d’un fichier, d’une route HTTP ou encore d’un lien interne.
- Une suite de transformations. Typiquement une récupération complémentaires d’informations, ou un de changement de format ou de structure.
- Une ou plusieurs sorties, internes, en réponse à l’appelant, ou vers d’autres systèmes.
Camel est un framework agile et complet avec de très nombreux composants, ce qui en fait un excellent couteau suisse.
À la demande d’un client qui souhaitait un système souple, robuste et open source, nous l’avons mis en œuvre dans un contexte Cloud, sur Azure. Une version spécifique Kubernetes, Camel-K, permet de mettre à profit Kubernetes pour le run et Camel pour le dev. Ce framework tout en 1 embarque les déclarations Kubernetes, ses dépendances, et ses librairies ; ce qui le rend facile et rapide d’utilisation.
Après quelques mois d’utilisation de Camel-K en version 1.1 sur le service Azure Kubernetes Service, nous entamons sa migration. Nous vous partageons cette expérience.
Pourquoi faire la mise à jour de Camel-K ?
La mise à jour de Camel-K est nécessaire pour plusieurs raisons:
- Bénéficier des correctifs de sécurité et de performances. En effet, la version 2.0 de Camel-K propose de larges gains de performances. C’est aussi le cas de tous les composants utilisés, comme Camel-Quarkus ou les Kamelets embarqués.
- Réduire la dette technique. C’est d’autant plus vrai sur un projet open-source, où les mises à jours se succèdent rapidement.
- Utiliser de nouvelles fonctionnalités. Par exemple, Camel 4.0 apporte le contrôle de beans dans le DSL XML, améliorant la maintenabilité du code. Encore une fois, les composants aussi profitent des mises à jour, et JSONata permet désormais d’utiliser plus de fonctions complexes.
Comment effectuer la mise à jour
Pour mettre à jour Camel-K, il convient de télécharger le client 2.1 et d’exécuter une commande ci-dessous. Elle va mettre à jour l’opérateur Camel-K sur le cluster Kubernetes. La voici :
kamel install \
--olm=false \
--registry $AZ_REGISTRY_ADDRESS \
--registry-auth-username $AZ_REGISTRY_ACCOUNT \
--registry-auth-password $AZ_REGISTRY_PWD \
--monitoring=false \
--force \
--waitEn faisan
Conséquences et solutions
Cependant, l’upgrade vers la 2.1 a un coût. Toutes les fonctionnalités anciennement dépréciées ont été supprimées, et certains comportements ont complètement changés. Or, la documentation de Camel-K est très légère et il peut être difficile de trouver la réponse à certaines interrogations. Voici donc une une liste non exhaustive des problèmes rencontrés durant cette upgrade. Des conseils et des solutions vous seront procurés.
Disparition du management automatique des ressources
Le modeline est une fonctionnalité permettant à un fichier de route de déclarer des dépendances ou des ressources. En 1.10, on pouvait s’en servir pour déclarer un fichier de ressource, pouvant être ensuite utilisé via le classpath lors d’un traitement. Camel-K, de son côté, était chargé de créer une configmap Kubernetes à partir de tous les fichiers déclarés. Ces fichiers étaient ensuite accessibles depuis le classpath.
// ligne de déclaration de dépendance avec modeline
// camel-k: dependency=mvn:org.my:application:1.0
// ligne de chargement d'un fichier
// camel-k: config=file:resources-data.txt
import org.apache.camel.builder.RouteBuilder;
public class Hello extends RouteBuilder {...}
Si les modelines existent toujours en 2.1, elles ne permettent plus de déclarer un nouveau fichier de ressource. En effet, Camel-K ne peut plus accepter que des configmaps, et non pus des fichiers. Autrement dit, il faut générer nous même la configmap avec les fichiers requis pour le bon fonctionnement du traitement. Ensuite, il faut préciser à Camel-K si il s’agit d’une “ressource” (pour les fichiers binaires) ou d’une “configuration” (fichiers textuels).
Suite à ce changement, nous vous déconseillons de servir des modelines comme déclaration de ressources. En effet, les routes ne peuvent pas créer ces dernières, mais seulement les utiliser. Ainsi, autant déclarer l’usage de configmap via la CLI, ce juste après les avoir créées.
Voici un exemple d’utilisation en deux fichier : un script bash lançant la route, ainsi que cette dernière.
# [ dev.sh ]
# Lecture des fichiers dans des config-map
kubectl create configmap text-cm --from-file=foo.json=./foo.json
kubectl create configmap binary-cm --from-file=bar.png=./bar.png
# Utilisation dans le CLI
kamel run \
--config configmap:text-cm \
--ressource configmap:binary-cm \
someRoute.xml
Leur utilisation s’effectuera alors comme il suit :
<?xml version="1.0" encoding="UTF-8"?>
<!-- camel-k: language=xml -->
<!-- NE FONCTIONNE PLUS: camel-k: resource=file:foo.json -->
<!-- camel-k: dependency=camel-azure-servicebus --> <!-- fonctionne encore -->
<routes xmlns="http://camel.apache.org/schema/spring">
<route id="example">
<from uri="direct:example" />
<!-- Exemple avec JSONata. On peut récupérer foo.json depuis le classpath. -->
<to uri="jsonata:classpath:foo.json?inputType=JsonString&outputType=JsonString"/>
</route>
</routes>
Ainsi, ce changement rajoute un sur-coût en configuration de la part du développeur, qui devra manuellement créer et mettre à jour les configmaps avant de les donner à Camel-K.
Disparition de certains composants
Comme mentionné précédemment, Camel-K embarque des composants, afin de réduire à zéro les dépendances de runtime. Il embarque aussi une version de camel-quarkus, qui est la colonne vertébrale applicative. Or, Camel-Quarkus embarque lui-même plusieurs composants, formant ainsi un arbre de dépendances. Cependant, lors de l’upgrade, Camel-Quarkus a retiré certaines d’entre elles.
Par exemple, le composant AtlasMap a disparu. Il s’agit d’une extension permettant de convertir du JSON en XML selon une suite de spécifications. Le composant utilisait un format particulier, le .adm, qui contient les transformations. Il s’agissait cependant d’un format spécifique à l’application et non utilisable par autre chose. Lors de sa disparition, plusieurs actions ont dues être effectuée afin de le remplacer. Pour résoudre ce problème, il suffit évidemment de procéder en deux temps :
- restructurer le JSON avec JSONata ou JSTL,
- puis de le transformer en XML. C’est sur ce dernier point que nous avons rencontré des problèmes, notamment dus aux tableaux anonymes. Nous avons pris le soin de détailler cette solution technique sur un repo git public.
Pour faire simple, les solutions de type Jackson ont des limitations lors du nommage des éléments d’un tableau. Pour un tableau quelconque JSON, la conversion se fera comme il suit :
{
"elements": [
{ "name": "foo" },
{ "name": "bar" }
]
}
<elements>
<name>foo</name>
</elements>
<elements>
<name>bar</name>
</elements>
Comme on peut le constater, ce n’est pas ce que nous voulons. Ainsi, le convertisseur que nous avons créé permet de prendre en charge ces tableaux et de les transformer de façon cohérente :
<elements>
<element>
<name>foo</name>
</element>
<element>
<name>bar</name>
</element>
</elements>
Par défaut, ce convertisseur va garder le même nom que le parent, mais sans le ‘s’ à la fin. C’est très basique, mais permet d’obtenir le résultat voulu.
On notera bien sûr que c’est tout à fait configurable au lancement du module Camel-K, via les propriétés.
Ainsi, lors de votre mise à jour, pensez à prendre en compte de potentielles disparitions de composants ou de Kamelets. Pour ce faire, j’ai créé des comparatifs du catalogue Quarkus et des Kamelets entre les versions. Si l’un des composants que vous utilisiez a été supprimé, il faudra le remplacer.
Trait Open-API
Camel-K propose aussi les traits. Il s’agit de fonctionnalités pouvant être optionnellement configurées au lancement du programme en ligne de commande. Ils permettent soit de surcharger certains comportement par défauts (comme le PATH de la JVM) soit d’en rajouter entièrement, en fonction du trait.
Avant, en 1.10, il était possible de charger un fichier de configuration OpenAPI simplement avec
kamel <args...> --open-api path/to/file.json <args...>
La nouvelle syntaxe est moins évidente. La documentation de Camel-K étant peu bavarde. Il convient de:
- ajouter la configuration en tant que “config” et
- ensuite de référencer uniquement le nom de la config-map, sans préfixer avec “configmap:” comme pour les autres références de ressource.
Voici la solution finale :
# Création de la configmap (pensez à la supprimer si elle existe déjà)
kubectl create configmap my-config-openapi --from-file=path/to/file.json
# Utilisation
kamel <args...> \
--config configmap:my-config-openapi \
--trait openapi.configmaps=my-config-openapi \
<args...>
Faites cependant bien attention à la configuration ingress utilisée. Il peut être utile de la surcharger avec de la réécriture de path notamment. De plus, contrairement à avant, toutes les routes du fichier de spécification vont se charger (sans considérer la présence ou nom de la route de destination interne). Cela peut éventuellement rendre le debugging plus complexe.
Prêtez donc attention aux fonctionnalités utilisées dans la CLI. La nouvelle version majeure a tout standardisé, au prix de la complexité d’utilisation.
Notes additionnelles
Il semble important de rappeler que la mise à jour de Camel-K est définitive. Une fois la mise à jour effectuée, vous pourrez exécuter des routes dans un runtime Camel différent, de version >= 1.17.0. Cependant, il faut se souvenir que si le code de la route n’a pas besoin de changer, il faut tout de même reconstruire les routes par le CLI kamel
.
Stratégie de migration
Pour ce qui est de la stratégie à adopter, nous vous recommandons d’effectuer et de valider l’upgrade sur un environnement hors production. Effectuer la migration au complet sur un environnement isolé permet de corriger les écarts liés à des changements de méthode de déploiement (modeline et trait). La documentation d’un upgrade de Camel-K est bien légère à ce sujet.
Conclusion
Ainsi, l’upgrade de Camel-K est rapide à effectuer en tant que telle, mais demandera des efforts pour l’adaptation de votre existant. Tous les scripts de lancement de module devront être mis à jour. De plus, il faudra un peu de temps pour mettre en place une génération de configmap maintenable au long terme. Enfin, à moins que vous n’utilisiez des composants ou Kamelets obsolètes, aucun changement n’est à prévoir dans votre code 😀 .