Note
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de changer d’annuaire.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de changer d’annuaire.
Il est inévitable que les fonctions soient ajoutées, supprimées et modifiées au cours de la durée de vie d’une application. Durable Functions permet de chaîner des fonctions de manière qui n'était pas possible auparavant, et ce chaînage affecte la façon dont vous pouvez gérer la gestion des versions.
Types de changements cassants
Il existe plusieurs exemples de modifications drastiques à prendre en compte. Cet article décrit les plus courants. Le thème principal derrière tous ces éléments est que les orchestrations de fonctions nouvelles et existantes sont affectées par les modifications apportées au code de fonction.
Modification des signatures de fonction d’activité ou d’entité
Une modification de signature fait référence à une modification du nom, de l’entrée ou de la sortie d’une fonction. Si ce type de modification est apporté à une fonction d’activité ou d’entité, elle peut interrompre toute fonction d’orchestrateur qui dépend de celle-ci. Cela est particulièrement vrai pour les langages à typage sécurisé. Si vous mettez à jour la fonction d’orchestrateur de manière à intégrer cette modification, vous risquez de décomposer les instances en cours.
Par exemple, supposons que nous disposions de la fonction d’orchestrateur suivante.
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
bool result = await context.CallActivityAsync<bool>("Foo");
await context.CallActivityAsync("Bar", result);
}
Cette fonction simpliste prend les résultats de Foo et la transmet à Bar. Supposons que nous devons modifier la valeur de retour de Foo d’une valeur booléenne en chaîne pour prendre en charge une plus grande variété de valeurs de résultat. Le résultat ressemble à ceci :
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
string result = await context.CallActivityAsync<string>("Foo");
await context.CallActivityAsync("Bar", result);
}
Cette modification fonctionne pour toutes les nouvelles instances de la fonction d’orchestrateur, mais peut décomposer toutes les instances en cours. Par exemple, considérez le cas où une instance d’orchestration appelle une fonction nommée Foo, récupère une valeur booléenne, puis des points de contrôle. Si la modification de signature est déployée à ce stade, l’instance faisant l’objet de points de contrôle échoue immédiatement, puis reprend et exécute à nouveau l’appel à Foo. Cet échec se produit, car le résultat de la table d’historique est une valeur booléenne, mais le nouveau code tente de le désérialiser dans une valeur string, ce qui entraîne un comportement inattendu ou même une exception runtime pour les langages de type sécurisé.
Cet exemple n’est qu’une des nombreuses façons dont une modification de signature de fonction peut interrompre les instances existantes. En général, si un orchestrateur doit modifier la façon dont il appelle une fonction, il est probable que la modification soit problématique.
Modification de la logique d’orchestrateur
Les autres classes de problèmes de contrôle de version proviennent de la modification du code de fonction d’orchestrateur d’une manière qui modifie le chemin d’exécution des instances en cours d’exécution.
Tenez compte de la fonction d’orchestrateur suivante :
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
bool result = await context.CallActivityAsync<bool>("Foo");
await context.CallActivityAsync("Bar", result);
}
Supposons maintenant que vous souhaitez apporter une modification pour ajouter un nouvel appel de fonction entre les deux appels de fonction existants.
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
bool result = await context.CallActivityAsync<bool>("Foo");
if (result)
{
await context.CallActivityAsync("SendNotification");
}
await context.CallActivityAsync("Bar", result);
}
Cette modification ajoute un nouvel appel de fonction à SendNotification entre Foo et Bar. Aucune modification de signature n’est apportée. Le problème survient lorsqu’une instance existante reprend à partir de l’appel à bar. Lors de la réexécution, si l’appel d’origine à Foo renvoie true, la réexécution de l’orchestrateur effectue l’appel dans SendNotification, qui n’est pas dans son historique d’exécution. Le runtime détecte cette incohérence et déclenche une erreur d’orchestration non déterministe , car il a rencontré un appel à SendNotification lorsqu’il s’attendait à voir un appel à Barre. Le même type de problème peut se produire lors de l’ajout d’appels d’API à d’autres opérations durables, comme la création de minuteurs durables, l’attente d’événements externes, l’appel de sous-orchestrations, etc.
Stratégies d’atténuation
Voici quelques-unes des stratégies permettant de relever les défis liés au contrôle de version :
- Ne rien faire (non recommandé)
- Versionnage d'orchestration (recommandé dans la plupart des cas)
- Arrêter toutes les instances en cours
- Effectuer des déploiements côte à côte
Ne rien faire
L’approche naïve du contrôle de version consiste à ne rien faire et à laisser les instances d’orchestration en cours échouer. Selon le type de modification, les types d’échecs suivants peuvent se produire.
- Les orchestrations peuvent échouer avec une erreur d’orchestration non déterministe .
- Les orchestrations peuvent être bloquées indéfiniment, en affichant un statut
Running. - Si une fonction est supprimée, toute fonction qui tente d’appeler peut échouer avec une erreur.
- Si une fonction est supprimée une fois qu’elle a été planifiée pour s’exécuter, l’application peut rencontrer des échecs d’exécution de bas niveau dans le moteur Durable Task Framework, ce qui peut entraîner une dégradation grave des performances.
En raison de ces défaillances potentielles, la stratégie « ne rien faire » n’est pas recommandée.
Gestion des versions d’orchestration
Remarque
Le contrôle de version d’orchestration est actuellement en préversion publique.
La fonctionnalité de gestion de version d’orchestration permet à différentes versions d’orchestrations de coexister et de s’exécuter concurremment sans conflits ni problèmes de non-déterminisme, ce qui autorise le déploiement de mises à jour tout en permettant aux instances d’orchestration en cours d’exécution de se terminer sans intervention manuelle.
Avec le contrôle de version d’orchestration :
- Chaque instance d’orchestration obtient une version associée définitivement à celle-ci lors de sa création
- Les fonctions Orchestrator peuvent examiner leur version et leur exécution de branche en conséquence
- Les workers exécutant des versions plus récentes d’orchestrateur peuvent continuer à exécuter des instances d’orchestration créées par des versions antérieures
- Le runtime empêche les workers d’exécuter des versions antérieures de fonction orchestrator d’exécuter des orchestrations de versions plus récentes
Cette stratégie est recommandée pour les applications qui doivent prendre en charge des modifications majeures tout en conservant des déploiements sans temps d’arrêt.
Pour obtenir des conseils détaillés sur la configuration et la mise en œuvre, consultez la section Gestion des versions d'orchestration dans Fonctions durables.
Arrêter toutes les instances en cours
Une autre option consiste à arrêter toutes les instances en cours. Si vous utilisez le fournisseur de stockage Azure par défaut pour l’extension Durable Functions, vous pouvez arrêter toutes les instances en effaçant le contenu des files d’attente control-queue et workitem-queue. Vous pouvez également arrêter l’application de fonction, supprimer ces files d’attente et redémarrer l’application à nouveau. Les files d’attente sont recréées automatiquement une fois l’application redémarrée. Les instances d’orchestration précédentes peuvent rester à l’état « En cours d’exécution » indéfiniment, mais elles n’encombrent pas vos journaux d’activité avec des messages d’échec ou provoquent des dommages à votre application. Cette approche est idéale dans le développement rapide de prototypes, y compris le développement local.
Remarque
Cette approche nécessite un accès direct aux ressources de stockage sous-jacentes et peut ne pas convenir à tous les fournisseurs de stockage pris en charge par Durable Functions.
Effectuer des déploiements côte à côte
La méthode la plus fiable pour vous assurer que les modifications majeures sont déployées en toute sûreté consiste à les déployer parallèlement à vos versions antérieures. Vous pouvez effectuer cette opération à l’aide de l’une des techniques suivantes :
- Déployez toutes les mises à jour en tant que nouvelles fonctions, laissant des fonctions existantes as-is. Cela n’est généralement pas recommandé en raison de la complexité liée à la mise à jour récursive des appelants des nouvelles versions de fonction.
- Déployez toutes les mises à jour en tant qu’application de fonction avec un autre compte de stockage.
- Déployez une nouvelle copie de l’application de fonction avec le même compte de stockage, mais avec un nom de hub de tâches mis à jour. Cela entraîne la création de nouveaux artefacts de stockage qui peuvent être utilisés par la nouvelle version de votre application. L’ancienne version de votre application continuera à s’exécuter à l’aide de l’ensemble précédent d’artefacts de stockage.
Le déploiement côte à côte est la technique recommandée pour déployer de nouvelles versions de vos applications de fonction.
Remarque
Cette aide pour la stratégie de déploiement côte à côte utilise des termes spécifiques au stockage Azure, mais s’applique généralement à tous les fournisseurs de stockage Durable Functions pris en charge.
Emplacements de déploiement
Lorsque vous effectuez des déploiements côte à côte dans Azure Functions ou Azure App Service, nous vous recommandons de déployer la nouvelle version de l’application de fonction sur un nouvel emplacement de déploiement. Les emplacements de déploiement vous permettent d’exécuter plusieurs copies de votre application de fonction côte à côte avec une seule d’entre elles comme emplacement de production actif. Lorsque vous êtes prêt à exposer la nouvelle logique d’orchestration dans l’infrastructure existante, cette opération peut s’avérer aussi simple que l’échange d’une nouvelle version dans l’emplacement de production.
Remarque
Cette stratégie fonctionne mieux lorsque vous utilisez des déclencheurs HTTP et webhook pour les fonctions d’orchestrateur. Pour les déclencheurs non HTTP, tels que les files d’attente ou Event Hubs, la définition du déclencheur doit dériver d’un paramètre d’application qui est mis à jour dans le cadre de l’opération d’échange.