Préparer une application pour Cloud Service Mesh


Cloud Service Mesh est un outil puissant de gestion et de surveillance des applications distribuées. Pour tirer le meilleur parti de Cloud Service Mesh, il est utile de comprendre ses abstractions sous-jacentes, y compris les conteneurs et Kubernetes. Ce tutoriel explique comment préparer une application pour Cloud Service Mesh du code source à un conteneur s'exécutant sur GKE, jusqu'au point juste avant d'installer Cloud Service Mesh.

Si vous connaissez déjà les concepts de Kubernetes et du maillage de services, vous pouvez ignorer ce tutoriel et passer directement au guide d'installation de Cloud Service Mesh.

Objectifs

  1. découvrir une application "Hello World" simple et multiservice ;
  2. exécuter l'application à partir de la source ;
  3. conteneuriser l'application ;
  4. créer un cluster Kubernetes ;
  5. déployer les conteneurs sur le cluster.

Avant de commencer

Pour activer l'API Cloud Service Mesh, procédez comme suit :
  1. Accédez à la page Kubernetes Engine dans la console Google Cloud.
  2. Créez ou sélectionnez un projet.
  3. Patientez le temps de l'activation de l'API et des services associés. Cette opération peut prendre plusieurs minutes.
  4. Make sure that billing is enabled for your Google Cloud project.

Ce tutoriel utilise Cloud Shell, qui provisionne une machine virtuelle (VM) g1-small Compute Engine exécutant un système d'exploitation Linux basé sur Debian.

Préparer Cloud Shell

Les avantages liés à l'utilisation de Cloud Shell sont les suivants :

  • Les environnements de développement Python 2 et Python 3 (y compris virtualenv) sont tous configurés.
  • Les outils de ligne de commande gcloud, docker, git et kubectl utilisés dans ce tutoriel sont déjà installés.
  • Nous vous proposons plusieurs éditeurs de texte :

    • Éditeur de code, auquel vous accédez en cliquant sur en haut de la fenêtre Cloud Shell.

    • Emacs, Vim ou Nano, auxquels vous accédez depuis la ligne de commande dans Cloud Shell.

In the Google Cloud console, activate Cloud Shell.

Activate Cloud Shell

At the bottom of the Google Cloud console, a Cloud Shell session starts and displays a command-line prompt. Cloud Shell is a shell environment with the Google Cloud CLI already installed and with values already set for your current project. It can take a few seconds for the session to initialize.

Télécharger l'exemple de code

  1. Téléchargez le code source helloserver :

    git clone https://github.com/GoogleCloudPlatform/anthos-service-mesh-samples
    
  2. Accédez au répertoire de l'exemple de code :

    cd anthos-service-mesh-samples/docs/helloserver
    

Explorer l'application multiservice

L'exemple d'application est écrit dans Python et comporte deux composants qui communiquent à l'aide de REST :

  • server : un serveur simple avec un point de terminaison GET, / , qui affiche "hello world" dans la console.
  • loadgen : script qui envoie le trafic vers server, avec un nombre configurable de requêtes par seconde (RPS).

exemple d'application

Exécuter l'application à partir de la source

Pour vous familiariser avec l'exemple d'application, exécutez-le dans Cloud Shell.

  1. À partir du répertoire sample-apps/helloserver, exécutez la commande server :

    python3 server/server.py
    

    Au démarrage, server affiche les éléments suivants :

    INFO:root:Starting server...
    
  2. Ouvrez une autre fenêtre de terminal pour pouvoir envoyer des requêtes vers server. Cliquez sur  pour ouvrir une autre session.

  3. Envoyez une requête vers server :

    curl http://localhost:8080
    

    Le server répond :

    Hello World!
    
  4. Dans le répertoire dans lequel vous avez téléchargé l'exemple de code, accédez au répertoire qui contient le fichier loadgen :

    cd YOUR_WORKING_DIRECTORY/anthos-service-mesh-samples/docs/helloserver/loadgen
  5. Créez les variables d'environnement suivantes :

    export SERVER_ADDR=http://localhost:8080
    export REQUESTS_PER_SECOND=5
    
  6. Lancez virtualenv :

    virtualenv --python python3 env
    
  7. Activez l'environnement virtuel :

    source env/bin/activate
    
  8. Installez les éléments requis pour loadgen :

    pip3 install -r requirements.txt
    
  9. Exécutez la commande loadgen :

    python3 loadgen.py
    

    Au démarrage, loadgen génère un message semblable à celui-ci :

    Starting loadgen: 2019-05-20 10:44:12.448415
    5 request(s) complete to http://localhost:8080
    

    Dans l'autre fenêtre de terminal, le server écrit des messages dans la console, comme suit :

    127.0.0.1 - - [21/Jun/2019 14:22:01] "GET / HTTP/1.1" 200 -
    INFO:root:GET request,
    Path: /
    Headers:
    Host: localhost:8080
    User-Agent: python-requests/2.22.0
    Accept-Encoding: gzip, deflate
    Accept: */*
    

    Du point de vue de la mise en réseau, l'ensemble de l'application s'exécute désormais sur le même hôte. De ce fait, vous pouvez utiliser localhost pour envoyer des requêtes vers server.

  10. Pour arrêter loadgen et server, saisissez Ctrl-c dans chaque fenêtre de terminal.

  11. Dans la fenêtre de terminal loadgen, désactivez l'environnement virtuel :

    deactivate
    

Conteneuriser l'application

Pour exécuter l'application sur GKE, vous devez empaqueter l'exemple d'application (server et loadgen) dans des conteneurs. Un conteneur permet de regrouper une application de manière à l'isoler de l'environnement sous-jacent.

Pour conteneuriser l'application, vous avez besoin d'un fichier Dockerfile. Dockerfile est un fichier texte qui définit les commandes nécessaires pour assembler le code source de l'application et ses dépendances dans un image Docker). Après avoir créé l'image, vous l'importez vers un registre de conteneurs, tel que Docker Hub ou Container Registry.

L'exemple est fourni avec Dockerfile pour server et loadgen avec toutes les commandes requises pour créer les images. Voici le fichier Dockerfile pour server :

FROM python:3.13-slim as base
FROM base as builder
RUN apt-get -qq update \
    && apt-get install -y --no-install-recommends \
        g++ \
    && rm -rf /var/lib/apt/lists/*

# Enable unbuffered logging
FROM base as final
ENV PYTHONUNBUFFERED=1

RUN apt-get -qq update \
    && apt-get install -y --no-install-recommends \
        wget

WORKDIR /helloserver

# Grab packages from builder
COPY --from=builder /usr/local/lib/python3.* /usr/local/lib/

# Add the application
COPY . .

EXPOSE 8080
ENTRYPOINT [ "python", "server.py" ]
  • La commande FROM python:3-slim as base demande à Docker d'utiliser la dernière image Python 3 comme image de base.
  • La commande COPY . . copie les fichiers sources dans le répertoire de travail actuel (dans ce cas, simplement server.py) dans le système de fichiers du conteneur.
  • Le fichier ENTRYPOINT définit la commande utilisée pour exécuter le conteneur. Dans ce cas, la commande est presque identique à celle que vous avez utilisée pour exécuter server.py à partir du code source.
  • La commande EXPOSE indique que server écoute sur le port 8080. Cette commande n'expose aucun port, mais sert de documentation pour ouvrir le port 8080 lorsque vous exécutez le conteneur.

Préparation pour la conteneurisation de l'application

  1. Définissez les variables d'environnement suivantes. Remplacez PROJECT_ID par l'ID de votre projet Google Cloud.

    export PROJECT_ID="PROJECT_ID"
    export GCR_REPO="asm-ready"

    Vous utilisez les valeurs PROJECT_ID et GCR_REPO pour baliser l'image Docker lors de la création, puis la transférer vers votre Container Registry privé.

  2. Définissez le projet Google Cloud par défaut pour la Google Cloud CLI.

    gcloud config set project $PROJECT_ID
  3. Définissez la zone par défaut pour Google Cloud CLI.

    gcloud config set compute/zone us-central1-b
    
  4. Assurez-vous que le service Container Registry est activé dans votre projet Google Cloud.

    gcloud services enable containerregistry.googleapis.com
    

Conteneurisation de server

  1. Accédez au répertoire dans lequel se trouve l'exemple server :

    cd YOUR_WORKING_DIRECTORY/anthos-service-mesh-samples/docs/helloserver/server/
  2. Créez l'image à l'aide de Dockerfile et des variables d'environnement que vous avez définies précédemment :

    docker build -t gcr.io/$PROJECT_ID/$GCR_REPO/helloserver:v0.0.1 .
    

    L'option -t représente le tag Docker. Il s'agit du nom de l'image que vous utilisez lorsque vous déployez le conteneur.

  3. Stockez l'image dans Container Registry :

    docker push gcr.io/$PROJECT_ID/$GCR_REPO/helloserver:v0.0.1
    

Conteneurisation de loadgen

  1. Accédez au répertoire où se trouve l'exemple loadgen :

    cd ../loadgen
    
  2. Créez l'image comme suit :

    docker build -t gcr.io/$PROJECT_ID/$GCR_REPO/loadgen:v0.0.1 .
    
  3. Stockez l'image dans Container Registry :

    docker push gcr.io/$PROJECT_ID/$GCR_REPO/loadgen:v0.0.1
    

Répertoriage des images

Obtenez la liste des images du dépôt pour confirmer que les images ont été envoyées :

gcloud container images list --repository gcr.io/$PROJECT_ID/asm-ready

La commande répond avec les noms d'image que vous venez de transférer :

NAME
gcr.io/PROJECT_ID/asm-ready/helloserver
gcr.io/PROJECT_ID/asm-ready/loadgen

Créer un cluster GKE

Vous pouvez exécuter ces conteneurs sur la VM de Cloud Shell à l'aide de la commande docker run. Cependant, en production, vous devez orchestrer les conteneurs de manière plus unifiée. Par exemple, vous avez besoin d'un système qui s'assure que les conteneurs sont toujours en cours d'exécution, et vous avez besoin d'un moyen pour effectuer un scaling à la hausse et démarrer les instances supplémentaires d'un conteneur pour gérer l'augmentation du trafic.

Vous pouvez utiliser GKE pour exécuter des applications en conteneur. GKE est une plate-forme d'orchestration de conteneurs qui connecte les VM pour former un cluster. Chaque VM est appelée nœud. Les clusters GKE sont basés sur le système de gestion de clusters Open Source Kubernetes. Les mécanismes de Kubernetes vous permettent d'interagir avec votre cluster.

Pour créer un cluster GKE, procédez comme suit :

  1. Créez le cluster :

    gcloud container clusters create asm-ready \
      --cluster-version latest \
      --machine-type=n1-standard-4 \
      --num-nodes 4
    

    La commande gcloud crée un cluster dans le projet Google Cloud et la zone que vous avez définis précédemment. Pour exécuter Cloud Service Mesh, nous vous recommandons d'utiliser au moins quatre nœuds et le type de machine n1-standard-4.

    La commande permettant la création d'un cluster prend quelques minutes. Lorsque le cluster est prêt, la commande affiche un message semblable à celui-ci :

    NAME        LOCATION       MASTER_VERSION  MASTER_IP      MACHINE_TYPE   NODE_VERSION   NUM_NODES  STATUS
    asm-ready  us-central1-b  1.13.5-gke.10   203.0.113.1    n1-standard-2  1.13.5-gke.10  4          RUNNING
    
  2. Fournissez les identifiants à l'outil de ligne de commande kubectl afin de pouvoir l'utiliser pour gérer le cluster :

    gcloud container clusters get-credentials asm-ready
    
  3. Vous pouvez maintenant utiliser kubectl pour communiquer avec Kubernetes. Par exemple, vous pouvez exécuter la commande suivante pour obtenir l'état des nœuds :

    kubectl get nodes
    

    La commande répond avec une liste des nœuds, semblable à la suivante :

    NAME                                       STATUS   ROLES    AGE    VERSION
    gke-asm-ready-default-pool-dbeb23dc-1vg0   Ready    <none>   99s    v1.13.6-gke.13
    gke-asm-ready-default-pool-dbeb23dc-36z5   Ready    <none>   100s   v1.13.6-gke.13
    gke-asm-ready-default-pool-dbeb23dc-fj7s   Ready    <none>   99s    v1.13.6-gke.13
    gke-asm-ready-default-pool-dbeb23dc-wbjw   Ready    <none>   99s    v1.13.6-gke.13
    

Comprendre les concepts clés de Kubernetes

Le schéma suivant illustre l'application s'exécutant sur GKE :

application conteneurisée

Avant de déployer les conteneurs sur GKE, vous pouvez passer en revue certains concepts clés de Kubernetes. La fin de ce tutoriel fournit des liens vous permettant d'en savoir plus sur chaque concept.

  • Nœuds et clusters : dans GKE, un nœud est une VM. Sur d'autres plates-formes Kubernetes, un nœud peut être une machine physique ou virtuelle. Un cluster est un ensemble de nœuds pouvant être traités ensemble, comme une seule machine, sur laquelle vous déployez une application conteneurisée.

  • Pods : dans Kubernetes, les conteneurs s'exécutent dans un pod. Un pod est l'unité atomique de Kubernetes. Un pod contient un ou plusieurs conteneurs. Vous déployez les conteneurs server et loadgen chacun dans leur propre pod. Lorsqu'un pod exécute plusieurs conteneurs (par exemple, un serveur d'applications et un serveur proxy), les conteneurs sont gérés comme une seule entité et partagent les ressources du pod.

  • Déploiements : un déploiement est un objet Kubernetes qui représente un ensemble de pods identiques. Un déploiement exécute plusieurs instances dupliquées des pods répartis entre les nœuds d'un cluster. Un déploiement remplace automatiquement tous les pods qui échouent ou ne répondent pas.

  • Service Kubernetes : l'exécution du code d'application dans GKE modifie la mise en réseau entre loadgen et server. Lorsque vous avez exécuté les services dans une VM de Cloud Shell, vous pouviez envoyer des requêtes à server à l'aide de l'adresse localhost:8080. Une fois le déploiement effectué sur GKE, les pods sont programmés pour s'exécuter sur les nœuds disponibles. Par défaut, vous ne pouvez pas contrôler le nœud sur lequel le pod est en cours d'exécution. Par conséquent, les pods ne disposent pas d'adresses IP stables.

    Pour obtenir une adresse IP pour server, vous devez définir une abstraction de mise en réseau au-dessus des pods appelés service Kubernetes. Un service Kubernetes fournit un point de terminaison de mise en réseau stable pour un ensemble de pods. Il existe plusieurs types de services. server utilise LoadBalancer, qui expose une adresse IP externe afin que vous puissiez accéder à server en dehors du cluster.

    Kubernetes dispose également d'un système DNS intégré qui attribue des noms DNS (par exemple, helloserver.default.cluster.local) aux services. Cela permet aux pods du cluster d'atteindre d'autres pods du cluster avec une adresse stable. Vous ne pouvez pas utiliser ce nom DNS en dehors du cluster, par exemple depuis Cloud Shell.

Fichiers manifestes Kubernetes

Lorsque vous avez exécuté l'application à partir du code source, vous avez utilisé une commande impérative : python3 server.py

Impérative, car elle s'appuie sur l'action : "faire cela".

En revanche, Kubernetes fonctionne sur un modèle déclaratif. Cela signifie que, plutôt que de dire exactement à Kubernetes ce qu'il faut faire, vous indiquez à Kubernetes l'état souhaité. Par exemple, Kubernetes démarre et arrête les pods selon les besoins afin que l'état réel du système corresponde à l'état souhaité.

Vous spécifiez l'état souhaité dans un ensemble de fichiers manifestes ou de fichiers YAML. Un fichier YAML contient la spécification pour un ou plusieurs objets Kubernetes.

L'exemple contient un fichier YAML pour server et loadgen. Chaque fichier YAML spécifie l'état souhaité pour l'objet et le service de déploiement de Kubernetes.

Serveur

apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloserver
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloserver
  template:
    metadata:
      labels:
        app: helloserver
    spec:
      containers:
      - image: gcr.io/google-samples/istio/helloserver:v0.0.1
        imagePullPolicy: Always
        name: main
      restartPolicy: Always
      terminationGracePeriodSeconds: 5
  • kind indique le type d'objet.
  • metadata.name spécifie le nom du déploiement.
  • Le premier champ spec contient une description de l'état souhaité.
  • spec.replicas spécifie le nombre de pods souhaités.
  • La section spec.template définit un modèle de pod. Dans la spécification pour les pods, le champ image correspond au nom de l'image à extraire de Container Registry.

Le service est défini comme suit :

apiVersion: v1
kind: Service
metadata:
  name: hellosvc
spec:
  ports:
  - name: http
    port: 80
    targetPort: 8080
  selector:
    app: helloserver
  type: LoadBalancer
  • LoadBalancer : les clients envoient des requêtes à l'adresse IP d'un équilibreur de charge réseau, qui possède une adresse IP stable et qui est accessible en dehors du cluster.
  • targetPort : rappelez-vous que la commande EXPOSE 8080 de Dockerfile n'expose pas de ports. Exposez le port 8080 afin d'atteindre le conteneur server en dehors du cluster. Dans ce cas, hellosvc.default.cluster.local:80 (nom abrégé : hellosvc ) correspond au port 8080 de l'adresse IP du pod helloserver).
  • port : numéro de port utilisé par les autres services du cluster lors de l'envoi de requêtes.

Générateur de charge

L'objet Déploiement dans loadgen.yaml est semblable à server.yaml. Une différence notable est que l'objet Déploiement contient une section appelée env. Cette section définit les variables d'environnement requises par loadgen, que vous avez définies précédemment lors de l'exécution de l'application à partir de la source.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: loadgenerator
spec:
  replicas: 1
  selector:
    matchLabels:
      app: loadgenerator
  template:
    metadata:
      labels:
        app: loadgenerator
    spec:
      containers:
      - env:
        - name: SERVER_ADDR
          value: http://hellosvc:80/
        - name: REQUESTS_PER_SECOND
          value: '10'
        image: gcr.io/google-samples/istio/loadgen:v0.0.1
        imagePullPolicy: Always
        name: main
        resources:
          limits:
            cpu: 500m
            memory: 512Mi
          requests:
            cpu: 300m
            memory: 256Mi
      restartPolicy: Always
      terminationGracePeriodSeconds: 5

Étant donné que loadgen n'accepte pas les requêtes entrantes, le champ type est défini sur ClusterIP. Ce type fournit une adresse IP stable que les services du cluster peuvent utiliser, mais qui n'est pas exposée aux clients externes.

apiVersion: v1
kind: Service
metadata:
  name: loadgensvc
spec:
  ports:
  - name: http
    port: 80
    targetPort: 8080
  selector:
    app: loadgenerator
  type: ClusterIP

Déployer les conteneurs sur GKE

  1. Accédez au répertoire où se trouve l'exemple server :

    cd YOUR_WORKING_DIRECTORY/anthos-service-mesh-samples/docs/helloserver/server/
  2. Ouvrez server.yaml dans un éditeur de texte.

  3. Remplacez le nom du champ image par le nom de votre image Docker.

    image: gcr.io/PROJECT_ID/asm-ready/helloserver:v0.0.1
    

    Remplacez PROJECT_ID par l'ID de votre projet Google Cloud.

  4. Enregistrez et fermez le fichier server.yaml.

  5. Déployez le fichier YAML sur Kubernetes :

    kubectl apply -f server.yaml
    

    En cas de réussite, la commande renvoie la réponse suivante :

    deployment.apps/helloserver created
    service/hellosvc created
    

  6. Accédez au répertoire où se trouve loadgen.

    cd ../loadgen
    
  7. Ouvrez loadgen.yaml dans un éditeur de texte.

  8. Remplacez le nom du champ image par le nom de votre image Docker.

    image: gcr.io/PROJECT_ID/asm-ready/loadgen:v0.0.1
    

    Remplacez PROJECT_ID par l'ID de votre projet Google Cloud.

  9. Enregistrez et fermez loadgen.yaml, puis fermez l'éditeur de texte.

  10. Déployez le fichier YAML sur Kubernetes :

    kubectl apply -f loadgen.yaml
    

    En cas de réussite, la commande renvoie la réponse suivante :

    deployment.apps/loadgenerator created
    service/loadgensvc created
    

  11. Vérifiez l'état des pods :

    kubectl get pods
    

    La commande renvoie un état semblable au suivant :

    NAME                             READY   STATUS    RESTARTS   AGE
    helloserver-69b9576d96-mwtcj     1/1     Running   0          58s
    loadgenerator-774dbc46fb-gpbrz   1/1     Running   0          57s
    
  12. Récupérez les journaux de l'application à partir du pod loadgen. Remplacez POD_ID par l'identifiant de la sortie précédente.

    kubectl logs loadgenerator-POD_ID
    
  13. Récupérez les adresses IP externes de hellosvc :

    kubectl get service
    

    La réponse de la commande est semblable à la suivante :

    NAME         TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
    hellosvc     LoadBalancer   10.81.15.158   192.0.2.1       80:31127/TCP   33m
    kubernetes   ClusterIP      10.81.0.1      <none>          443/TCP        93m
    loadgensvc   ClusterIP      10.81.15.155   <none>          80/TCP         4m52s
    
  14. Envoyez une requête à hellosvc. Remplacez EXTERNAL_IP par l'adresse IP externe de votre hellosvc.

    curl http://EXTERNAL_IP
    

Prêt pour Cloud Service Mesh

Vous avez maintenant déployé l'application sur GKE. loadgen peut utiliser le DNS Kubernetes (hellosvc:80) pour envoyer des requêtes à server, et vous pouvez envoyer des requêtes à server avec une adresse IP externe. Bien que Kubernetes vous fournisse de nombreuses fonctionnalités, certaines informations concernant les services sont manquantes :

  • Comment les services interagissent-ils ? Quelle est la relation entre les services ? Comment le trafic circule-t-il entre les services ? Vous savez que loadgen envoie des requêtes à server, mais imaginez que vous ne connaissez pas l'application. Vous ne pouvez pas répondre à ces questions en consultant la liste des pods en cours d'exécution sur GKE.
  • Statistiques : combien de temps faut-il pour que server réponde aux requêtes entrantes ? Combien de requêtes par seconde (RPS) sont entrantes vers server ? Existe-t-il des réponses d'erreur ?
  • Informations de sécurité : le trafic est-il compris entre loadgen et le server brut HTTP ou mTLS ?

Cloud Service Mesh peut répondre à ces questions. Cloud Service Mesh est une version gérée par Google Cloud du projet Istio Open Source. Cloud Service Mesh fonctionne en plaçant un proxy side-car Envoy dans chaque pod. Le proxy Envoy intercepte tout le trafic entrant et sortant vers les conteneurs d'applications. Cela signifie que server et loadgen disposent chacune d'un proxy side-car Envoy, et tout le trafic de loadgen vers server est diffusé par les proxys Envoy. Les connexions entre ces proxys Envoy forment le maillage de services. Cette architecture de maillage de services fournit une couche de contrôle en plus de Kubernetes.

maillage de services

Étant donné que les proxys Envoy s'exécutent dans leurs propres conteneurs, vous pouvez installer Cloud Service Mesh sur un cluster GKE sans apporter de modifications importantes à votre code d'application. Cependant, il existe plusieurs moyens clés avec lesquels vous avez préparé l'application pour être instrumentée avec Cloud Service Mesh:

  • Services pour tous les conteneurs : les déploiements server et loadgen sont associés à un service Kubernetes. Même loadgen, qui ne reçoit aucune requête entrante, dispose d'un service.
  • Les ports des services doivent être nommés: bien que GKE vous permette de définir des ports de service sans nom, Cloud Service Mesh vous demande de fournir un nom pour un port correspondant au protocole du port. Dans le fichier YAML, le port pour server est nommé http, car server utilise le protocole de communication HTTP. Si service utilise gRPC, vous devez nommer le port grpc.
  • Les déploiements sont libellés: cela vous permet d'utiliser les fonctionnalités de gestion du trafic de Cloud Service Mesh, telles que la division du trafic entre les différentes versions du même service.

Installer Cloud Service Mesh

Consultez le guide d'installation de Cloud Service Mesh et suivez les instructions pour installer Cloud Service Mesh sur votre cluster.

Effectuer un nettoyage

Pour éviter que les ressources utilisées lors de ce tutoriel soient facturées sur votre compte Google Cloud, supprimez le projet contenant les ressources, ou conservez le projet et les ressources individuelles.

Pour le nettoyage, supprimez le cluster GKE. La suppression du cluster supprime toutes les ressources qui constituent le cluster de conteneurs, telles que les instances de calcul, les disques et les ressources réseau.

gcloud container clusters delete asm-ready

Étape suivante