Container as a Service avec Amazon EC2 Container Service (ECS)

Les containers, bien que ne datant pas d'hier, sont revenus "à la mode" grâce à Docker.

Sorti en "preview" à la fin de l'année dernière, nous allons voir rapidement ce que Amazon EC2 Container Service (ECS) nous permet de faire et surtout ses limites.

Le jargon ECS

ECS utilise quelques termes qui lui sont propres, certains sont même trompeurs quant à leur rôle. ECS nécessite un cluster pour fonctionner. Ce cluster n'induit aucune notion de haute disponibilité (nous y reviendrons) et ne constitue en réalité qu'un groupe d'instances EC2.

Sur ces instances il est nécessaire d'y installer un agent fourni par Amazon permettant la communication entre ECS et les instances. Une fois une communication établie, les instances peuvent recevoir deux types d'objets, des tasks et des services.

Les tasks permettent d'instancier une ou plusieurs fois les containers enregistrés dans une task definition l'aide d'une commande docker run. Une task definition peut contenir plusieurs containers.

Amazon ECS


Les services permettent aussi d'instancier une task definition. A la différence d'une task, ECS nous assure que le nombre d'instanciation spécifiée sera constamment maintenu, le scheduler relançant les tasks en erreur. Il est aussi possible d'enregistrer les containers d'un service au sein d'un load balancer ELB.

Il parait ainsi assez clair que les tasks sont prévues pour lancer des services à faible durée de vie tandis que les services seront privilégiés pour des application stateless durable dans le temps.


Contactez des Experts AWS certifiés !

Mise en place

On commence tout d'abord par créer le cluster ECS :

$ aws ecs create-cluster --cluster OsonesTest
{
    "cluster": {
        "status": "ACTIVE",
        "clusterName": "OsonesTest",
        "registeredContainerInstancesCount": 0,
        "pendingTasksCount": 0,
        "runningTasksCount": 0,
        "clusterArn": "arn:aws:ecs:eu-west-1:515432163751:cluster/OsonesTest"
    }
}

Notre cluster est crée, maintenant il faut le remplir d'instances EC2 pour pouvoir déployer des containers.

NB : Il faudra penser par la suite à bien préciser notre cluster dans nos commndes le concernant (--cluster OsonesTest). Par défaut, ECS utilise le cluster Default.

Ma première envie fut des déployer des instances CoreOS au lieu des instances EC2 optimisées pour ECS. CoreOS est une distribution minimaliste, sans gestionnaire de packages qui vise à massivement utiliser docker. Je comptais particulièrement utiliser Fleet et Etcd qui permettent à CoreOS d'intégrer nativement un système de haute disponibilité. Néanmoins ECS ne permet la communication qu'avec le daemon docker, il n'est donc pas possible de lancer des containers grâce à Fleet.

Pour bénéficier d'un système de cluster en dehors de ce que proposent les services ECS, il faudrait se tourner vers Swarm, Kubernetes ou Mesos.

On va donc se contenter de lancer deux Ubuntu Server.

Déployez deux instances EC2 Ubuntu 14.04 LTS en pensant à créer un rôle IAM leur permettant d'accéder à ECS.

Installer la dernière version de docker : $ wget -qO- https://get.docker.com/ | sh

Nous devons ensuite installer l'ecs-agent afin de rendre nos instances disponibles pour notre cluster ECS.

\# docker run -d --name ecs-agent \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /var/log/ecs/:/log  \
-v /var/lib/ecs/data:/data \
-p 127.0.0.1:51678:51678 \
-e ECS_LOGFILE=/log/ecs-agent.log \
-e ECS_LOGLEVEL=info \
-e ECS_DATADIR=/data \
-e ECS_CLUSTER=OsonesTest \
amazon/amazon-ecs-agent:latest

Sur vos instances EC2, vous devriez voir vos ecs-agent :

\# docker ps
CONTAINER ID        IMAGE                            COMMAND             CREATED             STATUS              PORTS                        NAMES
4907e62abb2b        amazon/amazon-ecs-agent:latest   "/agent"            3 minutes ago       Up 3 minutes        127.0.0.1:51678->51678/tcp   ecs-agent

Si votre container ecs-agent ne s'est pas lancé, il faut vérifier plusieurs points :

  • Votre instance doit avoir un rôle IAM lui permettant d'accéder à ECS
  • Votre version de Docker (docker version | grep "Server version") doit être supérieur à 1.5
  • Si vous lancez votre docker run via systemd, vous devez ommettre le paramètre -d

Et sur votre cluster ECS :

$ aws ecs describe-clusters --cluster OsonesTest
{
    "clusters": [
        {
            "status": "ACTIVE",
            "clusterName": "OsonesTest",
            "registeredContainerInstancesCount": 2,
            "pendingTasksCount": 0,
            "runningTasksCount": 0,
            "clusterArn": "arn:aws:ecs:eu-west-1:515432163751:cluster/OsonesTest"
        }
    ],
    "failures": []
}

Nous voyons bien nos deux instances enregisrées dans le cluster.

Définition des tâches et services

Nous allons maintenant définir des tasks dans un fichier json :

[
  {
    "image": "osones/nginx",
    "name": "nginx",
    "cpu": 2,
    "memory": 300,
    "essential": true,
    "environment": [],
    "portMappings": [
      {
        "containerPort": 80,
        "hostPort": 80
      }
    ]
  }
]

Et les importer dans ECS :

aws ecs register-task-definition --family nginx --container-definitions file:///home/rguichard/aws/taskfile.json

Et on lance notre task :

aws ecs run-task --task-definition nginx:1 --count 1 --cluster OsonesTest

Alors maintenant pour savoir sur quelle instance tourne notre container c'est un peu laborieux...

Il faut dans l'ordre :

  • Afficher la description de la task describe-tasks et récupérer l'ID de l'instance sur laquelle elle tourne
  • Se servir de l'ID pour afficher les informations de l'instance describe-container-instances et récupérer l'ID EC2 de l'instance
  • Enfin utiliser cet ID pour afficher l'IP publique de l'instance ec2 describe-instances

Ou alors vous passez par le portail Amazon, ça va plus vite pour le coup :p

Pensez à vérifier que votre SecurityGroup autorise le port 80 et rendez vous à l'adresse de votre IP dans votre navigateur et vous devriez voir le message d'accueil de Nginx.

Qu'est ce que j'en pense ?

Tout ça pour un container. Même pas résilient en plus.

En soit cela n'a pour moi que très peu d'intêret. Docker fourni un produit appelé Docker-Machine qui permet de se connecter à différents cloud providers (dont AWS évidemment) et d'y déployer des containers. Autre produit Docker, Docker-Compose (anciennement Fig) nous permettra de définir nos containers et de les scaler, de façon manuelle. Et en plus les définitions de containers se font en yaml et non en json. Plus lisible, moins d'erreurs.

Là où ECS peut être intéressant c'est grâce aux services et à leurs promesses de haute disponibilité. Vérifions.

De la HA avec les services

On va faire quelque chose de simple encore une fois mais qui nous permettra de vérifier les fonctions des services ECS. Partons sur deux serveurs web (un par instance) load balancés par un ELB. Et on verra ce qu'il se passe quand un container tombe.

Commençons par créer une nouvelle définition de task qui contiendra un serveur web préconfiguré pour afficher un petit "hello world" suivi du hostname du container.

Je vous donne le json :

{
    "taskDefinition": {
        "volumes": [],
        "taskDefinitionArn": "arn:aws:ecs:eu-west-1:515432163751:task-definition/helloworld:1",
        "containerDefinitions": [
            {
                "environment": [],
                "name": "helloworld",
                "links": [],
                "mountPoints": [],
                "image": "tutum/hello-world",
                "essential": true,
                "portMappings": [
                    {
                        "containerPort": 80,
                        "hostPort": 80
                    }
                ],
                "entryPoint": [],
                "memory": 100,
                "command": [],
                "cpu": 1,
                "volumesFrom": []
            }
        ],
        "family": "helloworld",
        "revision": 1
    }
}

Avant de créer le service associé, il faut créer l'ELB dont nous aurons besoin. Créez un ELB HTTP standard, pensez juste à configurer des health checks assez aggresifs pour ne pas à attendre trop longtemps quand nous ferons tomber un container.

On peut ensuite crée le service, préciser que nous voulons deux fois la task "helloworld" et que nous souhaitons utiliser un ELB.

Voici le json utilisé :

{
    "serviceName": "ecs-simple-service-elb",
    "taskDefinition": "helloworld:1",
    "loadBalancers": [
        {
            "loadBalancerName": "helloworld-lb",
            "containerName": "helloworld",
            "containerPort": 80
        }
    ],
    "desiredCount": 2,
    "role": "ecsServiceRole"
}

Pour le charger : aws ecs create-service --service-name svc-helloworld --cli-input-json file:///home/rguichard/aws/service.json --cluster OsonesTest

On attend un peu que nos deux containers se lancent et on peut ensuite aller voir sur la page web de notre ELB et en rafraichissant la page on voit que notre ELB fonctionne :

ECS Hello World

On peut maintenant supprimer une de nos tâches. aws ecs stop-task --task ID-TASK --cluster OsonesTest

Notre service svc-helloworld va se charger de recréer un container et l'ajouter à ELB. En rafraichissant notre page web, on constate bien qu'un des containers a changé d'hostname.

A quoi ça sert ?

Finalement l'intérêt est limité. Cela commence à être intéressant quand cette HA est aussi assurée pour les instances elles mêmes avec de l'autoscale par exemple. Il faudrait pour cela préparer des fichiers cloud-init qui permettraient de remonter automatiquement des instances qui iraient s'enregistrer auprès du cluster ECS. Elles seraient ensuite peuplées par notre service.

Les fonctions assurées par ECS existent déjà dans l'écosystème Docker notamment avec les services de discovery comme Consul et son outil consul-template permettant de mettre à jour n'importe quel système en fonction de l'instance sur laquelle tourne le container et en fonction du port d'écoute qui peut ainsi être rendu aléatoire.

L'interface fournie par ECS est simple mais permet de créer des containers rapidement. Malheureusement, cela est encore en dessous de ce qu'on peut voir chez Mesosphere, Kubernetes ou même récemment chez Rancher.

Le dernier point faible sur lequel il est important de s'attarder c'est le scheduler, quasiment inexistant. Il est néanmoins possible de s'en affranchir et d'utiliser ceux fournis par les OS des instances. CoreOS dont je parlais plus haut permet grâce à Fleet un placement plus précis des containers avec un système d'affinités/antiaffinités par exemple. Il existe aussi des proof of concept sur l'intégration d'Apache Mesos avec ECS permettant d'utiliser le scheduler Mesos pour lancer des workloads sur ECS.

Néanmoins, Amazon EC2 Container Service est toujours en preview et on espère voir les améliorations fleurir rapidement.

Romain GUICHARD



C'est à vous de jouer !

Questions, remarques, suggestions... Contactez-nous directement sur Twitter sur @osones !

Pour discuter avec nous de vos projets, nous restons disponibles directement via contact@osones.com ou via le chat !

- Encore un peu de temps ? Parcourez nos dossiers :

A la découverte d'AWS Lambda

AWS Lambda


A la découverte d'Amazon DynamoDB Streams

Amazon DynamoDB


Container as a Service avec Amazon EC2 Container Service (ECS)

Amazon ECS


On a testé Amazon Elastic File System (EFS)

Amazon EFS


Rejoignez VOTRE groupe LinkedIn dès maintenant : Utilisateurs Francophones d'Amazon Web Services (AWS).

AWS user group FR

La discussion continue !

Nous attendons vos questions, remarques & mots doux sur notre Twitter :