Le Blog Amazon Web Services

Guide pour tester les Conteneurs localement avec l’Endpoint d’Amazon ECS Local et Docker Compose

Alors que de plus en plus d’entreprises adoptent les conteneurs, les développeurs ont besoin de moyens simples leur permettant de tester localement leurs applications conteneurisées avant de les déployer sur AWS. L’équipe AWS en charge des conteneurs a publié le premier outil dédié au développement local : Amazon ECS Local Container Endpoints. Il fait partie d’un projet open source conçu pour améliorer le processus de développement local pour Amazon Elastic Container Service (ECS) et AWS Fargate. Cet outil vous permet de simuler les endpoints V2 et V3 pour ECS Task Metadata et les rôles IAM pour les Tasks.

Dans ce post, nous allons vous montrer les scénarios de test suivants supportés par l’endpoint d’Amazon ECS local et Docker Compose :

  • Tester un conteneur qui a besoin des identifiants pour interagir avec les Services d’AWS,
  • Tester un conteneur qui utilise l’API Task Metadata,
  • Tester une application multi-conteneur utilisant le mode réseau awsvpc ou host sur Docker pour Mac et Docker pour Windows (dans un environnement Linux),
  • Tester plusieurs applications conteneurisées à l’aide de la découverte de services locaux.

Installation et prérequis

Votre environnement local consiste en Docker, Docker Compose, et awslabs/amazon-ecs-local-container-endpoints. Vous avez besoin d’une installation locale de Docker Daemon, le Docker Command Line et Docker Compose, afin de suivre le scénario décrit dans ce post.

Une fois les prérequis installés, créez un fichier Docker Compose appelé docker-compose.yml. Le fichier Compose définit les paramètres nécessaires pour l’exécution de votre application. Si vous n’avez jamais utilisé Docker Compose, consultez le tutoriel Getting Started with Compose tutorial. Cet exemple de fichier définit une application Web :

version: "2"
services:
  app:
    build:
      # Build an image from the Dockerfile in the current directory
      context: .
    ports:
      - 8080:80
    environment:
      PORT: "80"

Assurez d’enregistrer votre ficher docker-compose.yml : il est utilisé dans l’ensemble des scénarios.

Scénario 1 : Tester un conteneur qui a besoin des identifiants AWS pour interagir avec les Services d’AWS

Supposons que nous souhaitons tester localement un conteneur qui nécessite des identifiants d’AWS. Nous pouvons fournir ces identifiants en tant que variables d’environnement dans le conteneur, mais ce serait une mauvaise pratique en terme de sécurité. Au lieu de cela, nous pouvons utiliser l’endpoint d’Amazon ECS local en toute sécurité pour avoir accès aux identifiants dans un conteneur local.

Le fichier suivant de Docker Compose définit un conteneur qui utilisera les identifiants d’AWS. Ceci doit être utilisé avec le fichier docker-compose.yml que vous avez créé dans la section prérequis. Nommez ce fichier docker-compose.override.yml, (Docker Compose saura utiliser automatiquement les deux fichiers).

Votre fichier docker-compose.override.yml devrait ressembler à ceci :

version: "2"
networks:
    # Ce réseau particulier est défini afin que le service local de metadata
    # puisse mapper l'addresse IP spécifique qu'utilise ECS en procuction
    credentials_network:
        driver: bridge
        ipam:
            config:
                - subnet: "169.254.170.0/24"
                  gateway: 169.254.170.1
services:
    # Ce conteneur fournit les credentials à vos conteneurs
    ecs-local-endpoints:
        # Image Docker de l'Amazon ECS Local Container Endpoints
        image: amazon/amazon-ecs-local-container-endpoints
        volumes:
          # Mount de /var/run afin de pouvoir accéder à docker.sock pour s'adresser à Docker
          - /var/run:/var/run
          # Mont du répertoire partager de configurations, utilisé par le CLI AWS CLI 
          # et les SDKs AWS
          # Sous Windows, le répertoire est accessible via "%UserProfile%\.aws"
          - $HOME/.aws/:/home/.aws/
        environment:
          # Définition du folder home; les credentials seront élus sous $HOME/.aws
          HOME: "/home"
          # Vous pouvez modifier du profil AWS CLI utilisé
          AWS_PROFILE: "default"
        networks:
            credentials_network:
                # Cette adresse IP spéciale est réconnue par les SDKs AWS et le CLI AWS 
                ipv4_address: "169.254.170.2"
                
    # Ci-dessous nous referençons le conteneur de l'application que nous testons
    # Vous pouvez tester plusieurs conteneurs à la fois, il suffit de dupliquer cette section
    # et de la modifier pour chaque conteneur afin de lui donner une IP unique
    # dans la section 'credentials_network'.
    app:
        depends_on:
            - ecs-local-endpoints
        networks:
            credentials_network:
                ipv4_address: "169.254.170.3"
        environment:
          AWS_DEFAULT_REGION: "us-east-1"
          AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: "/creds"

Afin de tester votre conteneur localement, exécute la commande: docker-compose up

Votre conteneur sera alors exécuté et utilisera des identifiants temporaires obtenues à partir de votre profil AWS par défaut pour votre AWS Command Line Interface.

REMARQUE: vous ne devez pas utiliser vos identifiants de production localement. Si vous fournissez à ecs-local-endpoints un profil AWS d’accès à votre compte de production, votre application pourra alors accéder aux ressources de production / les modifier à partir de votre environnement local de test. Nous vous recommandons de créer des comptes distincts pour le développement et la production.

Comment ça marche ?

Dans cet exemple, nous avons créé un User Defined Docker Bridge Network qui permet à l’endpoint d’Amazon ECS local d’écouter sur l’adresse IP 169.254.170.2. Nous avons également défini la variable d’environnement AWS_CONTAINER_CREDENTIALS_RELATIVE_URI dans notre conteneur d’applications. Les AWS SDK et AWS CLI sont tous conçus pour extraire les identifiants d’AWS par les requêtes HTTP à 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI. Lorsque les conteneurs sont exécutés en production sur ECS, l’agent ECS transmet les identifiants aux conteneurs via cet endpoint; c’est ainsi que les rôles IAM pour les tâches sont implémentés.

Gotchas : Points importants à prendre en compte lors de l’utilisation de l’endpoint d’Amazon ECS local et de Docker Compose

  • Assurez-vous que chaque conteneur dans le réseau credentials_network a une adresse IP unique. Si vous ne le faites pas, Docker Compose peut à tort tenter d’attribuer 169.254.170.2 (adresse IP du conteneur ecs-local-endpoints) à l’un des conteneurs de l’application. Cela empêchera votre projet Compose de démarrer,
  • Sous Windows, remplacez $HOME/.aws/ dans la déclaration de volumes du conteneur d’endpoint par l’emplacement correct du répertoire de configuration de l’AWS CLI, comme indiqué dans la documentation,
  • Notez que le conteneur d’application est nommé ‘app’ dans les deux fichiers d’exemple. Vous devez vous assurer que les noms des conteneurs correspondent à votre docker-compose.yml et docker-compose.override.yml. Lorsque vous exécutez docker-compos up, les fichiers sont fusionnés. Lorsque les paramètres de chaque fichier pour chaque conteneur seront fusionnés, il est donc important d’utiliser des noms de conteneurs cohérents dans les deux fichiers.

Scénario 2 : Vérification des identifiants du rôle IAM de Task ECS

Le conteneur endpoint peut également fournir des identifiants à partir d’un rôle IAM; ceci vous permet de tester votre application localement à l’aide d’un rôle IAM pour son utilisation par les ‘Tasks’ au niveau d’ECS.

REMARQUE: vous ne devez pas utiliser votre rôle IAM de Task de production localement. Créez un rôle de test distinct, avec des permissions équivalentes pour tester vos ressources developpement.

Afin d’utiliser un rôle IAM de Task localement, vous devez modifier sa stratégie d’approbation. Commencez par obtenir l’ARN de l’utilisateur IAM défini par votre profil AWS CLI par défaut (remplacez-le par un nom de profil différent si nécessaire) :

aws --profile default sts get-caller-identity

Ensuite modifiez votre rôle IAM de Task afin que sa stratégie d’approbation comprenne l’instruction suivante. Vous pouvez trouver des instructions pour modifier les rôles IAM dans la documentation IAM.

   {
      "Effect": "Allow",
      "Principal": {
        "AWS": <ARN du user retourne par with get-caller-identity>
      },
      "Action": "sts:AssumeRole"
    }

Pour utiliser votre rôle IAM de Task dans votre fichier de Docker Compose pour les tests locaux, modifiez simplement la valeur de la variable d’environnement URI AWS_CONTAINER_CREDENTIALS_RELATIVE_URI dans votre conteneur d’applications:

AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: "/role/<nom de votre role>"

Par exemple, si votre rôle IAM s’appelle ecs_task_role, la variable d’environnement doit être "/role/ecs_task_role". C’est tout ce qu’il faut faire; le conteneur ecs-local-endpoints fournira maintenant les identifiants obtenues en assumant le rôle de Task. Vous pouvez l’utiliser pour vérifier si les permissions de votre rôle IAM de Task sont suffisantes pour exécuter votre application.

Scénario 3 : Tester un conteneur qui utilise l’API Task Metadata

Les endpoints de Task Metadata sont très utiles. Ils permettent en effet à un conteneur d’ECS d’obtenir des informations sur lui-même au moment de l’exécution. Cela est utile pour de nombreux cas d’utilisation. Par exemple, il vous permet d’obtenir des mesures d’utilisation des ressources du conteneur, comme montré dans ce projet.

Avec l’endpoint d’Amazon ECS local, vous pouvez tester localement les applications qui utilisent les endpoints V2 ou V3 de l’API Task Metadata. Si vous souhaitez utiliser l’endpoint V2, le template Docker Compose affiché au début de cet article suffit. Si vous souhaitez utiliser la V3, ajoutez simplement une autre variable d’environnement à chacun conteneur d’application :

ECS_CONTAINER_METADATA_URI: "http://169.254.170.2/v3"

Cette variable d’environnement est définit par le V3 metadata spec.

Scénario 4 : Tester une application multi-conteneur en utilisant le mode réseau awsvpc

Jusqu’ici, tous nos exemples consistent à tester des conteneurs dans un mode réseau bridge. Mais que se passe-t-il si vous avez une application qui utilise le mode réseau awsvpc. Pouvez-vous tester ces applications localement ?

Votre environnement de développement local n’aura pas d’Elastic Network Interfaces. Si votre Tâche Amazon ECS consiste en un seul conteneur, le mode réseau bridge utilisé dans les exemples précédents suffira. Cependant, si votre application consiste en plusieurs conteneurs qui doivent communiquer entre elles, le mode réseau awsvpc est considérablement différent par rapport au mode Bridge. Comme indiqué dans la documentation d’AWS :

“les conteneurs qui appartiennent à la même tâche peuvent communiquer via l’interface localhost.“

Ceci est l’un des bénéfices de l’awsvpc, il facilite la communication inter-conteneurs. Afin de la simuler localement, nous avons besoin d’un approche différente :

  • Si votre environnement de développement local est Linux, c’est plus simple. Vous pouvez tester vos conteneurs en utilisant le mode réseau host, qui permet aux conteneurs de communiquer via localhost. Consultez la documentation ECS Local Container Endpoints Project README pour les instructions de configurer les iptables pour que vos conteneurs puissent récupérer les identifiants et les meta-données,
  • Si vous développez dans un environnement de Windows ou Mac, l’option précédente ne fonctionnera pas. Docker ne supporte le mode réseau host que sous Linux. La section suivante décrit une solution permettant de simuler localement awsvpc dans Docker pour Mac ou Docker pour Windows. Il se sert partiellement la simulation du mode réseau host, dans le sens où tous vos conteneurs peuvent communiquer entre eux via localhost (d’un point de vue des tests locaux, host et awsvpc sont fonctionnellement identiques, l’essentiel étant que tous les conteneurs partagent une seule interface réseau).

Dans ECS, awsvpc est implémenté en créant un conteneur, appelé « pause container« . On attache une ‘Elastic Network Interface (ENI)’ à ce conteneur, puis tous les autres conteneurs de votre Tâche Amazon ECS sont lancés dans le même namespace du réseau du « pause container ». Pour la simulation locale d’awsvpc, une approche similaire sera utilisée.

Tout d’abord, créez un Dockerfile avec le contenu suivant pour le “pause container” local.

FROM amazonlinux:latest 
RUN yum install -y iptables 
CMD iptables -t nat -A PREROUTING -p tcp -d 169.254.170.2 --dport 80 -j DNAT --to-destination 127.0.0.1:51679 \ 
&& iptables -t nat -A OUTPUT -d 169.254.170.2 -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 51679 \ 
&& iptables-save \ 
&& /bin/bash -c 'while true; do sleep 30; done;'

Ce Dockerfile définie une image de conteneur qui configure les iptables et se met en veille pour toujours. Les règles de routage transfère toutes les requêtes d’identifiants ou de metadata à la destination de 169.254.170.2:80 vers localhost:51679, 169.254.170.2:80 est le port de l’endpoint d’Amazon ECS local.

Construire l’image:

docker build -t local-pause:latest .

Maintenant, modifiez votre fichier docker-compose.override.yml comme ci-dessous :

version: "2"
services:
    ecs-local-endpoints:
        image: amazon/amazon-ecs-local-container-endpoints
        volumes:
          - /var/run:/var/run
          - $HOME/.aws/:/home/.aws/
        environment:
          ECS_LOCAL_METADATA_PORT: "51679"
          HOME: "/home"
        network_mode: container:local-pause

    app:
        depends_on:
            - ecs-local-endpoints
        network_mode: container:local-pause
        environment:
          ECS_CONTAINER_METADATA_URI: "http://169.254.170.2/v3/containers/app"
          AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: "/creds"

Quelques points importants à noter:

  • ECS_LOCAL_METADATA_PORT est 51679; c’est le port qui a été utilisé dans les règles iptables du “pause container »,
  • network_mode est défini ainsi container:local-pause pour tous les conteneurs, ce qui signifie qu’ils utiliseront la stack réseau du conteneur ‘local-pause’,
  • ECS_CONTAINER_METADATA_URI est http://169.254.170.2/v3/containers/app. C’est important. En mode bridge, le conteneur d’endpoints local peut identifier le conteneur qui envoie la requête grace à son adresse IP dans la requête. Dans l’awsvpc simulé, il ne fonctionnera pas car tous les conteneurs partagent la même adresse IP. Ainsi, le conteneur d’endpoint prend en compte le nom du conteneur dans l’URI de la demande afin qu’il puisse identifier le conteneur qui envoie la requête. Dans notre cas, le conteneur est nommé app, app est donc ajoutée à la variable d’environnement. Si vous copiez la configuration du conteneur app pour ajouter d’autres conteneurs à ce fichier de Docker compose, rassurez de mettre à jour la valeur de ECS_CONTAINER_METADATA_URI pour chaque nouveau conteneur,
  • Supprimez toutes les déclarations de port dans le fichier docker-compose.yml. Elles ne fonctionnent pas avec la configuration de network_mode que vous utiliserez. Le texte ci-dessous explique comment exposer les ports dans ce mode réseau awsvpc simulé.

Avant de lancer le docker-compose, vous avez besoin de lancer le conteneur local-pause. Nous ne pouvons pas déclarer ce conteneur dans le fichier Docker Compose, car Compose ne supporte pas l’ordre de lancement de conteneurs. Le conteneur local-pause doit être lancé avant les autres. Vous pensez peut-être au paramètre depends_on, mais ce paramètre détermine l’ordre de démarrage des conteneurs, ce n’est pas une solution robuste.

Un point clé à noter; tous les ports utilisés par vos conteneurs d’applications doivent être déclaré dans le conteneur de local-pause. Vous ne pouvez pas définir des ports directement dans vos conteneurs d’applications car leur mode réseau est conteneur: local-pause. C’est une limitation imposée par Docker.

Imaginez que vos conteneurs d’applications doivent exposer les ports 8080 et 3306 (remplacez-les par les ports réels utilisés par vos applications), exécutez le conteneur local-pause avec la commande suivante : docker run -d -p 8080:8080 -p 3306:3306 --name local-pause --cap-add=NET_ADMIN local-pause .

Ensuite, il suffit d’exécuter les fichiers de docker compose et vous aurez des conteneurs qui partagent une seule interface réseau et ont accès aux identifiants et aux metadata !

Scénario 5 : Tester plusieurs applications conteneurisées à l’aide de la découverte de services locaux

Jusqu’a présent, tous les exemples ont porté sur l’exécution d’une seule application conteneurisée localement. Mais que se passe-t-il si vous souhaitez tester plusieurs applications exécutées en tant que Tâches ECS (Task) distinctes en production ?

Docker Compose vous permet de configurer des alias DNS pour vos conteneurs. Ceci leur permet de communiquer entre eux utilisant un hostname.

Dans cet exemple, retournez au fichier docker-compose.override.yml avec le mode de réseau bridge des scénarios 1 à 3. Voici un exemple de fichier docker-compose.override.yml qui implémente un scénario simple. Il y a deux applications: frontend et backend. Frontend envoie des requêtes au backend.

version: "2"
networks:
    credentials_network:
        driver: bridge
        ipam:
            config:
                - subnet: "169.254.170.0/24"
                  gateway: 169.254.170.1
services:
    # Ce conteneur fournit des credentials à vos conteneurs
    ecs-local-endpoints:
        # Docker Image pour Amazon ECS Local Container Endpoints
        image: amazon/amazon-ecs-local-container-endpoints
        volumes:
          - /var/run:/var/run
          - $HOME/.aws/:/home/.aws/
        environment:
          HOME: "/home"
          AWS_PROFILE: "default"
        networks:
            credentials_network:
                ipv4_address: "169.254.170.2"
                aliases:
                    - endpoints # configurations pour les conteneurs que vous testez
    frontend:
        image: amazonlinux:latest
        command: /bin/bash -c 'while true; do sleep 30; done;'
        depends_on:
            - ecs-local-endpoints
        networks:
            credentials_network:
                ipv4_address: "169.254.170.3"
        environment:
          AWS_DEFAULT_REGION: "us-east-1"
          AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: "/creds"
    backend:
        image: nginx
        networks:
            credentials_network:
                # definissez un alias pour la découverte du service
                aliases:
                    - backend
                ipv4_address: "169.254.170.4"

Avec cette configuration, le conteneur frontend peut trouver le conteneur backend en adressant une requête à http://backend.

Conclusion

Dans ce post, nous avons parcouru comment utiliser Docker Compose et awslabs/amazon-ecs-local-container-endpoints pour tester vos applications Amazon ECS et AWS Fargate localement avant leur déploiement.

Vous avez appris à :

  • Créer les fichiers docker-compose.yml et docker-compose.override.yml.
  • Tester un conteneur localement avec des identifiants temporaires à partir d’un profil AWS CLI local.
  • Tester un conteneur localement à l’aide des identifiants d’un rôle IAM de ECS Task.
  • Tester un conteneur localement qui utilise le Task Metadata Endpoints,
  • Simuler localement le mode réseau awsvpc,
  • Utiliser la découverte du service Docker Compose pour tester localement plusieurs applications dépendantes.

Pour suivre les nouveautés du projet de développement local, vous pouvez vous rendre sur le site publique AWS Containers Roadmap sur GitHub. Informez l’équipe sur Github si vous avez des questions ou des commentaires !

Ce post a été rédigé par Wesley Pettit, Développeur Logiciel chez AWS, et traduit en français par Kun Song, Architecte de Solutions chez AWS France aidant les clients français à adopter le cloud AWS, LinkedIn.