Traefik est un reverse proxy que nous avons déjà évoqué sur ce blog par le passé. Très puissant couplé avec des containers, il permet une gestion fine et légère de son trafic.

Il y a quelques jours, Containous, l’éditeur de Traefik, a annoncé la sortie de Traefik 2.3.0-rc2. Cette nouvelle version apporte quelques changements, et notamment :

  • L’ajout d’un nouveau service : Traefik Pilot
  • La possibilité d’ajouter des plugins à Traefik
  • L’ajout du provider ECS

J'ai déjà abordé les deux premiers points sur mon blog personnel et je vais m'intéresser ici au support du backend ECS (Elastic Container Service) sur AWS via un nouveau provider Traefik.

Traefik au pays des providers

Un provider, c’est quoi ?

A l’instar de Terraform, Traefik utilise une notion de provider pour définir les services sur lesquels il va se connecter.

Chaque provider a un vocabulaire et une configuration qui lui est propre. L’idée de base étant bien sûr d’avoir un noyau léger, Traefik, et de charger uniquement les providers que l’on utilise.

Il existe aujourd’hui une quinzaine de providers disponibles pour Traefik, comme par exemple Docker, Kubernetes, Rancher, Etcd, Consul etc…

Le provider est la source de données que Traefik va exploiter pour découvrir les backends auxquels il va se connecter.

Pourquoi l’ajout du provider ECS change la donne

Un peu de contexte : ECS est l’orchestrateur managé d’AWS, il permet de piloter des containers sur des EC2 ou sur un autre service, Fargate, qui permet d’exécuter ses containers en mode serverless.

Dans Fargate, on réserve simplement des ressources, et Amazon se charge de l’infrastructure sous-jacente pour nous (parce que le serverless ce n’est pas magique).

L’ajout du provider ECS permet donc à Traefik de découvrir dynamiquement des ressources pilotées par ECS, afin de les rattacher directement à lui même, ce qui donne plus de dynamisme dans vos déploiements. Cette découverte repose sur Traefik lui-même en utilisant du polling via les APIs d’AWS.

De plus, cela permet de ne plus avoir un AWS ALB par ressource, ou avec beaucoup de règles, mais d’en avoir simplement un qui renvoie vers Traefik, et c’est ce dernier qui s’occupe de tout le routage. De quoi interconnecter Traefik avec ECS peu importe où il est déployé. L’exemple ici réutilise ECS par simplicité, ce n’est pas indispensable, vous pouvez l’héberger où vous voulez.

Le provider en action

Disclaimer

Je vous propose donc un petit hands-on en exploitant ce provider, avec toutefois un petit disclaimer :

  • Ce hands-on est fait sur une version “Release candidate”, la version finale peut donc être légèrement différente
  • Le déploiement est fait à l'aide de Terraform, qui n’est absolument pas un prérequis, vous pouvez arriver au même résultat avec CloudFormation par exemple
  • Un bug est actuellement présent dans la gestion des métadonnées sur ECS, d’où le fait que nous passons par une API Key, ce qui n’est pas une bonne pratique de sécurité dans AWS
  • Ce hands-on est une démonstration, dans un environnement productif, on mettra en place du hardening plus fort sur certains paramètres

Ce que nous allons déployer

Description du déploiement

Le code complet du déploiement Terraform est disponible ici.

Je ne vais pas décrire ici l’ensemble du déploiement Terraform, car ce n’est pas le coeur de cet article, je vais plutôt m’attarder sur les points qui nous intéressent pour Traefik, et les nuances à prendre en compte.

La policy IAM, utilisée aussi en tant qu’utilisateur IAM dans l’exemple, en raison du bug décrit précédemment :

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "TraefikECSReadAccess",
            "Effect": "Allow",
            "Action": [
                "ecs:ListClusters",
                "ecs:DescribeClusters",
                "ecs:ListTasks",
                "ecs:DescribeTasks",
                "ecs:DescribeContainerInstances",
                "ecs:DescribeTaskDefinition",
                "ec2:DescribeInstances"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

Comme on peut le noter, il s’agit de droits uniquement en lecture sur les services ECS et EC2, car ECS peut exploiter EC2 pour ses noeuds d’exécution.

A noter qu’il est tout à fait possible de restreindre cette policy, dans un modèle least privilege, comme préconisé par AWS, en réduisant par exemple le scope des ECS visibles par Traefik, ou en autorisant uniquement les EC2 qui porteraient un tag spécifique.

La policy indiquée ici est tirée de la documentation officielle.

Prérequis de déploiement

En raison du bug de récupération des informations liées aux rôles, il est nécessaire de créer un utilisateur et de lui assigner une clé d’API AWS et la policy décrite ci dessus. Cet utilisateur n’est pas fourni dans le code disponible sur GitLab car Terraform ne permet pas le stockage de la secret access key de manière sécurisée. Toutefois, si vous souhaitez tout de même créer cet utilisateur via Terraform, le code nécessaire est le suivant :

/*
Workaround for issue Traefik#7096 : https://github.com/containous/traefik/issues/7096
*/
 
resource "aws_iam_user" "traefik" {
  name = "traefik"
  path = "/system/"
}
 
resource "aws_iam_access_key" "traefik" {
  user = aws_iam_user.traefik.name
}
 
data "aws_iam_policy_document" "traefik_user" {
  statement {
    sid = "main"
 
    actions = [
      "ecs:ListClusters",
      "ecs:DescribeClusters",
      "ecs:ListTasks",
      "ecs:DescribeTasks",
      "ecs:DescribeContainerInstances",
      "ecs:DescribeTaskDefinition",
      "ec2:DescribeInstances"
    ]
 
    resources = [
      "*",
    ]
  }
}
 
resource "aws_iam_user_policy" "traefik_user" {
  name   = "traefik_user"
  user   = aws_iam_user.traefik.name
  policy = data.aws_iam_policy_document.traefik_user.json
}
 
/*Store access keys in Secret manager to retrieve it with Fargate*/
resource "aws_secretsmanager_secret" "traefik_secret_access_key" {
  name        = "traefik-secret_access_key_value"
  description = "contains traefik secret access key"
}
 
resource "aws_secretsmanager_secret_version" "key" {
  secret_id     = aws_secretsmanager_secret.traefik_secret_access_key.id
  secret_string = aws_iam_access_key.traefik.secret
}
 
output "access_key" {
  value = aws_iam_access_key.traefik.id
}
 
output "secret_id" {
  value = aws_secretsmanager_secret.traefik_secret_access_key.id
}

Les paramétrages des tâches ECS

La taskdefinition de Traefik est assez simple :

[
    {
      "name": "traefik",
      "image": "traefik:v2.3.0-rc2",
      "entryPoint": ["traefik", "--providers.ecs.clusters", "${ecs_cluster_name}", "--log.level", "DEBUG", "--providers.ecs.region", "${region}", "--api.insecure"],
      "essential": true,
      "logConfiguration":{
        "logDriver": "awslogs",
        "options": {
            "awslogs-group": "${loggroup}",
            "awslogs-region": "${region}",
            "awslogs-stream-prefix": "traefik"
        }
      },
      "Environment" : [{
        "name": "AWS_ACCESS_KEY_ID",
        "value": "${aws_access_key}"
      }],
      "Secrets" :[{
        "name": "AWS_SECRET_ACCESS_KEY",
        "valuefrom": "${secret_arn}"
      }],
      "portMappings": [
        {
          "containerPort": 80,
          "hostPort": 80
        },
        {
          "containerPort": 8080,
          "hostPort": 8080
        }
      ]
    }
  ]

L’information importante ici est bien entendu l’entrypoint. L’entrypoint est la commande qui sera exécutée par ECS au lancement du container.

traefik --providers.ecs.clusters ${ecs_cluster_name} --providers.ecs.region ${region} --api.insecure

Quand on regarde les paramètres de la ligne de commande nous voyons donc :

  • traefik : Le nom du binaire à lancer, obligatoire, vu que je remplace l’entrypoint existant dans l’image.
  • --providers.ecs.clusters=${ecs_cluster_name} : Il s’agit là de nommer le nom du cluster ECS sur lequel Traefik doit chercher les ressources, il est possible d’invoquer ce paramètre de manière multiple, ou de dire à Traefik de chercher dans tous les clusters avec “--providers.ecs.autoDiscoverClusters=true”
  • --providers.ecs.region=${region} : Bien que ce ne soit pas indiqué dans la documentation, ce paramètre est obligatoire pour pouvoir exploiter l’authentification par access key.
  • --api.insecure : Paramètre purement optionnel, il permet l’accès au dashboard Traefik sans avoir besoin d’authentification, sur un environnement productif, ce paramètre n’est bien entendu pas activé.

De plus, on peut remarquer, en variables d’environnement :

  • L’access key est chargée en clair, cette information n’est pas sensible, elle n’a donc pas besoin d’être chargée depuis secrets manager
  • La secret access key est par contre chargée depuis secrets manager, afin qu’elle ne soit pas visible, bien que dans une section “secret”, elle est tout de même chargée en tant que variable d’environnement, mais invisible depuis la définition de la tâche ECS.

J’ai choisi de faire la configuration via la ligne de commande, mais il est tout à fait possible de faire cette dernière via les fichiers de configurations de Traefik, pour plus d’informations à ce sujet, je vous invite à regarder la documentation officielle.


La taskdefinition de Whoami, le backend est prévu pour exposer les informations utilisée par Traefik :

[
    {
      "name": "whoami",
      "image": "containous/whoami:v1.5.0",
      "essential": true,
      "portMappings": [
        {
          "containerPort": 80,
          "hostPort": 80
        }
      ],
      "dockerLabels": 
        {
          "traefik.http.routers.whoami.rule": "Host(`${alb_endpoint}`)",
          "traefik.enable": "true"
        }
      
    }
  ]

Whoami est une image minimaliste, créée par Containous à des fins de démonstration. Elle ne nécessite pas de paramètres particuliers.

On peut voir que j’ajoute deux paramètres, via des labels Docker sur mon container :

  • “traefik.enable : true” indique que je demande à Traefik de référencer ce service
  • “traefik.http.routers.whoami.rule” indique que je veux créer un “routeur” Traefik, appelé whoami ayant comme règle de faire suivre tout le trafic qui passe par Traefik. Ces règles sont dynamiques et peuvent être plus complexes, une fois de plus, n’hésitez pas à voir la documentation officielle.

Déployons notre infrastructure

Il est maintenant possible de déployer le code Terraform, qui va nous mettre en place notre cluster ECS, nos containers, la gestion de l’IAM et un load balancer frontal.

Au bout de quelques minutes, notre serveur Traefik est maintenant disponible.

Vous pouvez accéder au tableau de bord de Traefik via l’url de votre load balancer (renvoyée par Terraform), sur le port 8080.

En allant voir dans les services, vous voyez normalement un service whoami, qui correspond à notre déploiement.

Comme vous pouvez le voir sur la capture d’écran ci dessous :

  • Le provider est ECS
  • Nous voyons notre “rule” que nous avions définie sur le container
  • Les 3 containers déployés sont bien visibles en backend.

Un accès à l’url du load balancer nous renvoie le container whoami, en rafraîchissant la page, vous devriez voir l’IP changer, ce qui signifie bien que nous avons du load balancing sur notre service.

En conclusion

Traefik ajoute une nouvelle corde à son arc en permettant la découverte native de services déployés dans ECS.

Actuellement, on peut sentir que le vernis n’est pas encore sec, de part le bug que j’ai remonté, mais aussi le fait que la documentation manque de clarté sur certains aspects. Ainsi, je ne vous recommande pas d’utiliser cette fonctionnalité dans son état actuel sur un environnement de production, encore une fois il s’agit d’une release candidate, on préférera attendre la version stable pour cet usage.

Néanmoins, ce produit de la french tech nous prouve une fois de plus qu’il est capable d’évoluer et de s’adapter aux besoins de ses utilisateurs. Aucun doute sur le fait qu’ECS sera bientôt un provider parmis les autres pour Traefik.