FinOps EKS : 10 astuces pour réduire la facture de 90% sur son cluster Kubernetes managé AWS
Le Cloud permet de rationaliser les coûts d'infrastructure. Ceci étant dit, en démarrant, la facture peut très vite grimper. EKS, le service managé Kubernetes de AWS, n'échappe pas à la règle. Voici quelques astuces pour vous permettre de réduire vos coûts jusqu'à 90%, sans baisser le niveau de service.
Un premier coût, humain, à éviter : les modifications d'infrastructure manuelles. Ainsi, avant toute chose, automatisez avec Terraform, sous peine de ne jamais récupérer la perte de temps, traduite en euros. Avec Terraform, l'installation de toute l'infrastructure associée à EKS, VPC compris, dure environ 15 minutes. Chaque mise à jour préconisées dans cet article, si elle concerne l'infrastructure, ne prendrait ensuite que quelques minutes.
La plupart de ces aspects, génériques, peuvent s'appliquer, au moins dans la philosophie, à d'autres Cloud providers.
1. Installer le Cluster Autoscaler
Un des moyens les plus évidents de s'assurer de ne provisionner que les ressources nécessaires, c'est d'installer le Cluster Autoscaler.
Il s'agit principalement d'un pod dans le cluster qui va surveiller les ressources demandées et provisionnera ou supprimera les nœuds (VMs de calcul) suivant le besoin.
La configuration du chart Helm est a régler suivant le contexte, ci-dessous un exemple. A noter l'éviction forcée des pods système et ayant du disque local, pour du downscaling vraiment efficace :
extraArgs: # scale-down-utilization-threshold: 0.5 # default scan-interval: 30s # default 10s # scale-down-delay-after-add: 10 # default scale-down-unneeded-time: 20m # default 10 scale-down-unready-time: 5m # default 20m skip-nodes-with-local-storage: false skip-nodes-with-system-pods: false
2. Définir des nœuds en instance spot dans un Launch Template
Les instances spot sont des EC2s (VMs AWS) moins chères (jusqu'à 90%). C'est AWS qui brade ses ressources inutilisées, mais elles sont préemptibles, l'utilisateur court le risque de perdre l'EC2 en question si AWS est à court de ressources sur les réservations à la demande (garanties). Ce n'est plus un prix au plus offrant depuis plusieurs années : C'est un tarif évoluant faiblement, en fonction de la demande long-terme.
Un cluster Kubernetes est tout indiqué pour utiliser ce genre de ressource préemptible : il est capable de gérer les erreurs et s'auto-réparer en provisionnant d'autres VMs pour ses nœuds.
Le risque de perdre ses EC2s est faible mais réel : sur 4 mois d'utilisation de T3 sur eu-west-1 (Irlande), on a pu observer 2 jours d'indisponibilité de ce type sur une zone. Comment mitiger ce problème ? En créant le pool d'EC2s sous forme de Launch Template : déclarer exactement 2 types d'instance parmi un ensemble de catégories, que AWS ordonne par prix. Si un type peu cher est indisponible, AWS provisionnera automatiquement un type un peu plus cher.
Exemple d'un worker_groups_launch_template zonal sous Terraform :
worker_groups_launch_template = [ { name = "spot-az-a" subnets = [module.vpc.private_subnets[0]] # only one subnet to simplify PV usage on_demand_base_capacity = "0" # on_demand_percentage_above_base_capacity = 0 # If not set, all new nodes will be spot instances override_instance_types = ["t3a.xlarge", "t3.xlarge", "t2.xlarge", "m4.xlarge", "m5.xlarge", "m5a.xlarge"] root_volume_size = "20" spot_allocation_strategy = "lowest-price" spot_instance_pools = 2 # "Number of Spot pools per availability zone to allocate capacity. EC2 Auto Scaling selects the cheapest Spot pools and evenly allocates Spot capacity across the number of Spot pools that you specify." asg_desired_capacity = "1" asg_min_size = "0" asg_max_size = "10" key_name = var.cluster_name kubelet_extra_args = "--node-labels=lifecycle=spot" } ]
3. Éteindre automatiquement hors heures ouvrées
Autant l'environnement de production est probablement utilisé 24/7, autant l'environnement de développement n'est utilisé qu'en heures ouvrées, soit 1/3 du temps. Par défaut, sauf s'il y a une forte activité en journée, le coût du cluster sera sensiblement le même tout le temps, car la RAM est consommée même sans activité.
Pour drastiquement réduire le nombre de nœuds en heures non ouvrées, il suffit d'installer dans le cluster le Kube Downscaler. Le principe est simple : aux heures indiquées il réduit les deployments et statefulsets à 0 pod, sauf dans certains namespaces configurables. La réduction drastique du nombre de pods poussera le Cluster Autoscaler à supprimer automatiquement les nœuds inutilisés.
Autre avantage : les nœuds étant à durée de vie de quelques heures, l'espace disque réservé peut être réduit de 100Go à 20Go, permettant une très légère économie supplémentaire.
4. Réduire le nombre de zones dans la région
Chez AWS, le trafic réseau au sein d'une zone est gratuit, et il est payant entre zones, donc entre data centers.
Il a en moyenne 3 zones par région. Dans la pratique, c'est un surplus de haute disponibilité qui n'est pas nécessaire. D'autant que s'il y a 2 zones de non disponibles en même temps, il y a de grandes chances que le problème soit plus large et que la troisième le soit également à ce moment là...
Pour des structures de taille modeste, Il est possible de réduire à 2 zones en développement voir en production, suivant la haute disponibilité nécessaire. Avec une économie substantielle sur les transferts réseau.
5. Utiliser les EC2s avec le meilleur rapport performance/prix
Quelques informations sur les EC2s (VMs AWS) :
- T2, T3, M4, M5, etc. Les chiffres désignent les générations. Les génération 5 sont globalement plus performantes en benchmark
- Les instances Tn ont la technologie AWS Nitro, qui est censée apporter jusqu'à 60% de performance à caractéristiques égales, mais dans la pratique les benchmarks ne sont pas si convaincants
- Les instances Tn fonctionnent avec des crédits. Par défaut, consommer du CPU sur une longue durée fait grimper la facture. Et il n'est pas possible de dépasser 100% du CPU alloué, c'est bien l'utilisation prolongée qui est soumise à crédit.
Voici un comparatif de prix en mars 2021 pour eu-west-3 (Paris), sur des instances pouvant contenir 58 pods, avec le type, le prix spot et la réduction :
4 CPU / 32 GO RAM r5a.xlarge 0,07$/h (-74% sur 0,27$/h) r5ad.xlarge 0,07$/h (-77% sur 0.31$/h) r5d.xlarge 0,07$/h (-79% sur 0.34$/h) r5.xlarge 0,09$/h (-70% sur 0,30$/h) 8 CPU / 32 GO RAM t3a.2xlarge 0,10$/h (-71% sur 0,34$/h) t3.2xlarge 0,11$/h (-71% sur 0,38$/h) m5a.2xlarge 0,13$/h (-67% sur 0,40$/h) t2.2xlarge 0,13$/h (-69% sur 0,42$/h) m5.2xlarge 0,13$/h (-71% sur 0,45$/h) m5ad.2xlarge 0,13$/h (-73% sur 0,48$/h) m5d.2xlarge 0,13$/h (-75% sur 0,53$/h) 8 CPU / 16 GO RAM c5.2xlarge 0,12$/h (-70% sur 0,40$/h) c5d.2xlarge 0,12$/h (-74% sur 0,46$/h)
Quelques observations qui en découlent :
- Toutes ces instances spot ont plus ou moins le même prix, sauf les r5, moins chères
- Le CPU est une variable qui fait sensiblement monter la facture
- Si le besoin en CPU est faible (ce qui est souvent le cas en phase de développement) mieux vaut se tourner vers des r5x
A noter que si l'infrastructure est provisionnée par Terraform, le changement de type de serveur est totalement indolore : l'application du changement ne supprimera pas les EC2s en place, ce sont les nouvelles provisionnées qui auront bien le nouveau type.
6. Regrouper des environnements dans un même cluster
Si le delivery est organisé en gitflow, plusieurs niveaux d'environnement seront nécessaires : éventuellement un environnement par branche de feature, un environnement pour la branche develop, un environnement par branche release, un environnement de production représenté par la branche master.
Si le delivery est plus mature et organisé en trunk-based-development (évoqué dans mon article 10 pratiques essentielles vers le déploiement continu), seront comptabilisés un environnement par branche de feature, un environnement de production, et éventuellement un environnement de staging (pré-production / recette / iso-production).
Dans un scénario ou l'autre, il est possible de s'organiser pour avoir seulement deux clusters (dev & prod), ou trois (dev, staging & prod).
Regrouper plusieurs environnements dans un même cluster partage les outils de supervision tout en séparant l'applicatif en namespaces. Côté gestionnaires de données (bases de données, gestionnaires de messages), il vaut mieux qu'ils soient distincts entre les environnements. Pour chacun, par exemple, on pourra provisionner une BDD managée hors cluster pour les environnements les plus avancés (staging/production), et par contre l'intégrer dans le cluster Kubernetes pour les environnements éphémères des branches de feature. Il y a maintenant des charts Helm disponibles pour la plupart des BDD, faciles à installer et rapides à instancier.
Attention ! C'est une source évidente d'économie Cloud, mais si cet aspect induit une perte de temps au quotidien du fait de la centralisation, c'est au final une fausse bonne idée, l'humain étant bien plus coûteux que la machine aujourd'hui.
7. Utiliser la scalabilité horizontale des pods
L'Horizontal Pod Autoscaler (HPA) est un objet standard Kubernetes permettant de gérer automatiquement le nombre de pods (identiques) d'une application suivant l'activité réelle (CPU, RAM ou custom), entre n et m pods.
Il sera donc plus économique de définir un HPA entre 1 et 10 pods sachant que l'activité maximum requiert 10 pods, plutôt que de définir le nombre de replicas systématiquement à 10. Ce qui réduira drastiquement la sur-réservation sur les environnements, spécialement hors production.
8. Utiliser la scalabilité verticale des pods
Dans certains cas, la scalabilité horizontale des pods n'est pas envisageable. Particulièrement pour les statefulsets de base de donnée, moteur de recherche, gestionnaire de messages ou de cache. Pourquoi ne pas laisser simplement les pods avec une réservation basse, qui consommeraient ensuite les ressources en surplis dans une limite excessivement haute ? Car ceci met une concurrence incontrôlée entre les pods d'un même nœud, et surtout n’entraîne pas de réorganisation.
Plutôt que de définir des besoins CPU/RAM correspondant aux pics de charges, il est plus judicieux de se tourner vers le Vertical Pod Autoscaler. Ainsi plus besoin de réserver des ressources importantes pour chaque pod de notre BDD, l'augmentation et la diminution de réservation se feront suivant l'activité. Ce qui réduira drastiquement la sur-réservation sur les environnements, spécialement hors production.
Lire à ce sujet l'excellent article Vertical Pod Autoscaling : The Definitive Guide.
9. Un seul équilibreur de charge grâce à l'ingress controller
En créant un service Kubernetes, le type peut être ClusterIP, NodePort, LoadBalancer ou ExternalName. S'il est de type LoadBalancer, un équipement sera réservé pour assurer l'équilibrage de charge.
Pour éviter ce coûteux (et luxueux) fonctionnement, il vaut mieux définir les services en ClusterIP, et définir des ingress, gérés par un ingress controller comme celui basé sur NGINX, qui provisionnera un seul équilibreur de charge pour tous les ingress, et donc tous les services.
10. Maintenir environ 5-6 nodes en régime de croisière
Plus il y a de nœuds dans un cluster, plus on s'assure de la haute disponibilité, mais plus il y a de ressources systèmes consommées (incompressibles ou liées à des daemonsets), et plus il y a de chance d'avoir des disponibilités de ressource insuffisante pour mettre les prochains pods ; ressources disponibles qui, regroupées, seraient pourtant suffisantes. Le CPU sera également moins largement partagé, CPU qui est plus sujet aux pics de consommation que la mémoire.
Moins il y a de nœuds dans un cluster, plus il est possible d'éviter les problèmes précédents, mais moins la haute disponibilité est garantie, et plus on risque d'avoir un (nouveau) nœud sous utilisé et qui représente un grand pourcentage de perte.
Le juste milieu entre haute disponibilité et efficacité des ressources se situerait, d'expérience, autour de 5-6 nœuds. Donc avec 12 nœuds sur des EC2 xlarge, opter pour des EC2 2xlarge, ce qui poussera l'Autoscaler à réduire mécaniquement à 6 nœuds, voir 5 si la répartition des ressources disponibles était défavorable.
DevSecOps Engineer @ SoKube
3 ansÇa peut-être intéressant pour nous Yann ALBOU