Aller au contenu
Edouard Topin's Blog
FinOps cloud native / Série 01/03

OpenCost : voir avant d'agir sur les coûts K8s

OpenCost rend le coût d'un cluster lisible par namespace. On regarde son modèle d'allocation, ce que son prix par défaut vaut vraiment, et où s'arrête l'open source.

Edouard Topin
17 min de lecture
Illustration éditoriale abstraite d'une loupe survolant un histogramme.

La direction financière ne demande pas un tableau de bord. Elle demande combien coûte l’application webshop sur le cluster vks-fret-01, et elle attend un chiffre qui tienne devant un comité. Le cluster tourne depuis dix-huit mois, la plateforme est refacturée en interne au workload domain wld-fret-prod en une ligne globale, et personne ne sait dire, sans reconstruire un tableur, ce que consomme webshop-db par rapport à webshop-web.

Cet article ne vend pas un tableau de bord : il vend la lecture d’un modèle de calcul. OpenCost n’ouvre aucune facture. Il interroge Prometheus, en tire des quantités de ressource moyennées sur une durée, et les multiplie par une fiche de prix. Tout l’enjeu est dans le second facteur — d’où vient ce prix, ce qu’il vaut, et ce qui se passe quand personne ne l’a choisi. Précision de méthode : aucun lab n’a été exécuté ici, aucune facture n’a été lue. Ce qui suit est un cadrage adossé à des pages de documentation, avec ses trous nommés à voix haute.

OpenCost — CNCF IncubatingZéro montant publiéLe prix par défaut n’est pas le vôtre

TL;DR

  • La décision — la première étape n’est pas l’installation, c’est le choix de la source de prix. OpenCost applique par défaut, dans le cloud, « the on-demand list pricing » ; sur site, une fiche livrée dont le projet écrit lui-même la provenance : "description": "Default prices based on GCP us-central1".
  • L’arbitrage qui coûte — la réconciliation avec votre facture remisée, les alertes, la gouvernance, la fédération multi-cluster et le support relèvent de Kubecost, publié par Apptio, an IBM Company. Sur une plateforme privée, cette réconciliation n’a même pas d’objet : il n’y a pas de facture amont à mettre en face.
  • L’action de lundi matin — écrire une note de modèle d’une page, versionnée à côté du fichier de valeurs Helm, qui dit d’où vient chaque prix unitaire, à quelle date il a été posé, et quels réglages d’oisiveté et de résolution ont produit les chiffres publiés en interne.

Le contrat de chiffre de cet article

Cet article publie zéro montant, zéro pourcentage d’économie, zéro statistique de parc. Ce n’est pas une lacune de collecte, c’est une règle : un chiffre de coût a l’apparence d’un fait, il se recopie dans un tableur, puis dans une slide, puis dans un arbitrage budgétaire — et plus personne ne remonte à sa source.

Ce que l’article publie, en revanche : des versions et des états de mécanisme avec la page qui les porte, des noms de champs, de paramètres, d’endpoints et de ports, des valeurs par défaut de paramètres logiciels non monétaires, des dates de gouvernance, et des formules citées depuis la spécification plutôt que reformulées.

Ce qu’il refuse de chiffrer : tout prix unitaire de ressource, cloud public ou privé ; tout coût de namespace, d’application ou d’équipe ; tout écart, tout gain, tout ratio de gaspillage.

Ce qu’OpenCost calcule : une quantité, multipliée par un prix

La documentation d’accueil du projet publie sa définition : « OpenCost is a vendor-neutral open source project for measuring and allocating cloud infrastructure and container costs. » La FAQ du même site en publie une autre, qui ne recouvre pas tout à fait le même objet : « … for measuring and allocating Kubernetes and cloud infrastructure costs. » Les deux sont publiées ; on les cite avec leur page, on n’en fabrique pas une troisième.

Le projet est sous licence Apache 2.0 et gratuit — « No. OpenCost is open source (Apache 2.0) and free to use. » Côté gouvernance, la CNCF publie une maturité Incubating, un projet accepté le 2022-06-17 et passé Incubating le 2024-10-25. Il ne s’agit donc pas d’un « standard CNCF » : la CNCF ne publie pas de standard de mesure de coût.

Le point de mécanique qui fonde tout le reste tient en deux formes publiées par la spécification :

  • coûts d’allocation de ressource = Amount × Duration × HourlyRate ;
  • coûts d’usage = Amount × UnitRate.

Le chiffre affiché est donc le produit d’une mesure et d’une hypothèse. La mesure vient du cluster ; l’hypothèse vient d’ailleurs.

Ressource Quantité publiée Prix publié Coût publié
CPU cores = avg_over_time(cpu) by (node) [cores] fiche fournisseur ou personnalisée [$/core-hr] total cost = cores * duration * price
RAM ram bytes = avg_over_time(GB) by (node) [ram GBs] fiche fournisseur ou personnalisée [$/GB-hr] total cost = ram bytes * duration * price
GPU « The greater of requested resources and used resources measured in cores » fiche de prix même forme quantité × durée × prix
Volume persistant Disk Size = avg_over_time(GB) by (pv) [disk GBs] « typically a function of disk class, IOPS, backup size » même forme
Réseau ingress/egress « across zones, regions, or the wide internet », en octets ou gigaoctets prix par octet coût d’usage, pas d’allocation
Load balancer octets entrants et nombre de forwarding rules fiche de prix usage et allocation

Les formules portent des symboles de prix, jamais des valeurs. La durée publiée est duration = end running - start running [hrs].

Deux conséquences de lecture, rarement dites. D’abord, avg_over_time est une moyenne, pas un relevé : le chiffre d’un namespace est une intégrale approchée. Ensuite, le réseau et le load balancer ne suivent pas la même règle que le CPU — traiter « le coût d’un namespace » comme une somme homogène rate cette asymétrie.

La règle qui gouverne l’allocation est publiée telle quelle : « Workload Costs should be understood as max(request, usage) ». Côté Kubernetes, la page Resource Management for Pods and Containers publie la sémantique correspondante — le request sert au kube-scheduler pour placer le pod, et « it’s possible (and allowed) for a container to use more resource than its request for that resource specifies ». En clair : une charge qui réserve beaucoup et consomme peu porte le coût de sa réservation. C’est exactement ce que l’article suivant de la série ira déplacer.

Reste le résidu, et il porte un nom : Cluster Idle Cost = ( Cluster Asset Costs - Workload Costs ), avec Cluster Idle % = Idle Cost / Resource Allocation Costs. La spécification précise que les coûts d’usage n’en font pas partie — « they are always used, the corresponding resource never “sits idle.” » Trois paramètres d’API gouvernent son traitement, tous à false par défaut : includeIdle, shareIdle, idleByNode. Activer shareIdle augmente le coût affiché de tous les namespaces sans qu’un centime de dépense ait bougé. Ce n’est pas un réglage technique, c’est un arbitrage d’allocation — il se décide avec les équipes.

D’où vient le prix : trois chemins, dont un qu’on prend sans le savoir

Chemin Ce qu’il fournit Ce qu’il suppose
Intégration de facturation du fournisseur les coûts du fournisseur, exposés par /cloudCost un compte cloud, un export de facturation, des droits de lecture
Fiche de prix personnalisée vos prix unitaires appliqués aux quantités échantillonnées que quelqu’un ait calculé ces prix et sache dire d’où ils viennent
Valeurs par défaut une fiche pré-remplie, d’origine GCP us-central1 rien — c’est ce qu’on obtient en ne choisissant pas

La phrase publiée par la FAQ est sans ambiguïté : « OpenCost provides real-time monitoring of your Kubernetes cluster and provides the on-demand list pricing for the cloud resources in use by your cluster. » Tarif public, à la demande. Un lecteur sous contrat remisé, sous engagement, ou consommant de la capacité spot, regarde donc un chiffre qui n’est pas le sien tant qu’il n’a pas branché l’intégration de facturation de son fournisseur.

Sur site, c’est le même piège en pire : la fiche par défaut porte elle-même sa provenance, "description": "Default prices based on GCP us-central1". Dans le cloud, le prix par défaut est au moins le prix affiché de la ressource réellement employée ; sur un cluster VKS, c’est le prix affiché d’une autre plateforme, dans une autre région.

La page Configuration publie la raison d’être du deuxième chemin — « In some cases users may need to override the pricing provided by their cloud service provider. » — et l’application se fait par le fichier de valeurs Helm : « Custom pricing may be applied by updating your local OpenCost Helm values file. »

{
  "provider": "custom",
  "description": "REPLACE_WITH_PRICE_SHEET_PROVENANCE",
  "CPU": "REPLACE_WITH_CPU_HOURLY_RATE",
  "spotCPU": "REPLACE_WITH_SPOT_CPU_HOURLY_RATE",
  "RAM": "REPLACE_WITH_RAM_GB_HOURLY_RATE",
  "spotRAM": "REPLACE_WITH_SPOT_RAM_GB_HOURLY_RATE",
  "GPU": "REPLACE_WITH_GPU_HOURLY_RATE",
  "storage": "REPLACE_WITH_STORAGE_GB_HOURLY_RATE",
  "zoneNetworkEgress": "REPLACE_WITH_ZONE_EGRESS_RATE",
  "regionNetworkEgress": "REPLACE_WITH_REGION_EGRESS_RATE",
  "internetNetworkEgress": "REPLACE_WITH_INTERNET_EGRESS_RATE"
}

Les noms de clés et le champ provider: "custom" sont publiés. Les REPLACE_WITH_* restent vides, et c’est volontaire : ils dépendent d’un relevé que seul l’exploitant peut faire, à sa date et sur sa région. La meilleure idée à retenir de la fiche livrée est ailleurs : le projet écrit lui-même sa provenance dans le champ description. Un exploitant qui y met sa source et sa date rend son modèle auditable par la personne suivante.

Dernier point de cadrage, et il change l’arbitrage pour un exploitant VCF : « Cloud Costs are not currently supported for on-prem, but you may configure OpenCost to view costs associated with other cloud service providers. » Sur vks-fret-01, le plan /cloudCost ne portera donc rien de la plateforme privée. Et il n’y a rien à réconcilier : il n’existe pas de facture amont. Or la réconciliation est précisément l’argument principal du produit commercial. Ce que le projet énumère lui-même comme relevant de Kubecost : « more accurate cost numbers for your cluster after reconciling the differences between your published bill with any negotiated discounts », puis « recommendations, governance, alerting, federated clusters, a SaaS platform, and support ». Kubecost est publié par Apptio, an IBM Company ; son palier Foundations est gratuit — « Unlimited clusters up to 250 cores », « 15-day metric retention », support communautaire — et aucun montant n’est affiché sur la page de tarification. Cette rétention de quinze jours borne l’analyse de tendance, pas la mesure : un besoin de comparaison trimestrielle sort du palier gratuit, ou reste dans Prometheus, dont la rétention est votre affaire.

Lire dans le bon ordre : les actifs avant l’allocation

Le prérequis publié tient en une phrase — « OpenCost requires Prometheus for scraping metrics and data storage » — et aucune version minimale de Prometheus n’est publiée. Ce n’est pas un oubli de collecte : la page de configuration Prometheus dédiée n’existe pas dans cet arbre de documentation, elle répond 404. On l’écrit tel quel plutôt que d’inventer une borne.

Deux planchers Kubernetes cohabitent chez le même projet : « Kubernetes 1.21+ and Kubernetes 1.28 is officially supported as of v1.105 » sur la page Setup, « OpenCost supports a Kubernetes environment above 1.8. » dans la FAQ. La règle de lecture, réutilisable bien au-delà d’OpenCost : quand deux pages d’un même projet publient deux bornes, on retient la plus restrictive et on nomme la page qui la porte. Ici 1.21+, page Setup — très en dessous du plancher de cette série, Kubernetes 1.35, cible 1.36. Ce n’est pas un procès du projet, c’est une méthode de citation.

L’installation se fait dans un namespace nommé opencost, l’UI est incluse par défaut, et l’accès local passe par un port-forward. Là encore, deux commandes sont publiées pour le même besoin, sur deux pages différentes — celle de la page Installation et celle de la page UI :

# page Installation — API sur 9003, UI sur 9090
kubectl port-forward --namespace opencost service/opencost 9003 9090

# page UI — même besoin, commande différente
kubectl port-forward --namespace opencost service/opencost 9090 9090

Vient ensuite l’argument central de cette section : on lit les actifs avant l’allocation. La ventilation n’a de sens que si on connaît d’abord le total qu’elle ventile. Un tableau de bord qui ouvre sur les namespaces met le lecteur devant un résultat sans son dénominateur.

# 1 — le total à ventiler : nœuds, disques, avec cpuCost, ramCost, totalCost
curl -G http://localhost:9003/assets -d window=7d

# 2 — la ventilation, exemple publié par la documentation
curl -G http://localhost:9003/allocation \
  -d window=7d \
  -d aggregate=namespace \
  -d resolution=1m

# 3 — voir l'oisiveté et la répartir : deux réglages, deux résultats
curl -G http://localhost:9003/allocation \
  -d window=1d \
  -d shareIdle=true \
  -d idleByNode=true

Le paramètre aggregate accepte cluster, node, namespace, controllerKind, controller, service, pod, container, label:LABEL_NAME et annotation:name. Le paramètre window accepte des mots (today, week, lastmonth), des durées (30m, 12h, 7d), des paires de dates RFC3339 et des timestamps Unix. Quant à resolution, sa description publiée est un aveu utile : « Smaller values (i.e. higher resolutions) will provide better accuracy, but worse performance […] Larger values will perform better, but at the expense of lower accuracy for short-running workloads. Default is 1m. » La même page porte une section intitulée « Theoretical error bounds ». Son tableau de valeurs n’a pas été obtenu en citation exacte lors de la vérification : cet article dit donc que le projet publie des bornes d’erreur, et n’en cite aucune valeur. Le fait suffit — un coût affiché à deux décimales sur un CronJob de trente secondes est une illusion de précision.

L’attribution par équipe passe par l’agrégation par étiquette, et elle suppose une convention antérieure au déploiement :

curl -G http://localhost:9003/allocation \
  -d window=7d \
  -d aggregate=label:REPLACE_WITH_TEAM_LABEL_KEY \
  -d resolution=1m

Attention : l’API accepte cette agrégation, elle ne crée pas la donnée. Si trois Deployment sur quinze portent l’étiquette, la réponse arrivera quand même — et elle sera fausse au sens de la question posée. Aucune page chargée ne publie ce que devient une charge dépourvue de l’étiquette demandée : il n’y a pas lieu d’affirmer qu’elle atterrit dans un seau « non attribué ». La question se pose, elle se renvoie au relevé.

Un contrôle vaut d’être fait ailleurs qu’à travers OpenCost : vérifier que Prometheus détient bien l’historique de la fenêtre demandée. Une fenêtre plus longue que la rétention produit un chiffre silencieusement partiel.

# Reconstitution à partir des familles de métriques nommées par la spécification.
# Le nom exact des séries dépend de la pile en place — à valider sur votre plateforme.
count_over_time(
  container_cpu_usage_seconds_total{namespace=~"webshop-.*"}[7d]
)

Sur VKS : ce que VCF Operations 9.1 expose, et ce qu’il ne publie pas

L’intuition « VCF ne dit rien du coût Kubernetes » est fausse depuis VCF 9.1. Les notes de version publient : « VCF Operations now delivers comprehensive costing capabilities across your entire Kubernetes ecosystem, including nodes, clusters, vSphere namespaces, projects, and organizations. » Et aussi : « VCF Operations now offers powerful tools to implement transparent, granular chargeback models specifically for VMware Kubernetes Service (VKS) nodes in addition to the traditional VM pricing. » Le Management Pack for Kubernetes livre par ailleurs un tableau de bord qui suit « the costs of the clusters, nodes, and namespaces in a single place ».

Côté origine du prix, la différence est structurelle : VCF Operations ne consulte aucun tarif, il distribue un coût saisi. Dix cost drivers sont publiés pour l’infrastructure vCenter — Server Hardware: Traditional, Server Hardware: Hyper-Converged, Storage, License, Applications, Maintenance, Labor, Network, Facilities, Additional Cost — et la mécanique tient en une phrase : « The total cost set by you is distributed across resources in the data center. » Le matériel s’amortit selon deux méthodes publiées, Straight Line et Max of Double or Straight, sur une période paramétrable de deux à cinq ans, avec une remise dont « The default discount % value is zero. »

Axe OpenCost VCF Operations
Objet de coût le plus fin publié jusqu’au conteneur, agrégeable par étiquette nœud, cluster, vSphere namespace, projet, organisation
Origine du prix fiche de prix appliquée aux quantités échantillonnées coût total saisi, puis distribué
Méthode de calcul publiée oui, dans la spécification non trouvée sur les pages chargées
Facture amont à réconcilier existe dans le cloud public n’existe pas sur la plateforme privée

Aucun des deux n’est « meilleur » : ils ne répondent pas à la même question. Et il faut assumer trois trous plutôt que les habiller. Un : aucune page chargée ne publie la méthode de calcul du costing Kubernetes de VCF Operations — l’article décrit ce qui est exposé, pas comment c’est obtenu. Deux : la granularité publiée s’arrête au nœud et au vSphere namespace, jamais au pod ni au conteneur. Trois : aucune page Broadcom chargée ne nomme OpenCost, Kubecost ou un outil FinOps Kubernetes tiers sur VKS — déployer OpenCost sur un cluster VKS est une reconstitution raisonnable, pas un chemin documenté par l’éditeur. À noter aussi : les pages de configuration du coût côté 9.1 — pricing cards, chargeback VCF Automation, cost drivers — ne nomment aucun objet Kubernetes ; seules les notes de version annoncent la capacité. On cite donc la capacité telle qu’annoncée, sans décrire une procédure de configuration qu’aucune page ne publie.

Reste la question ouverte, et elle se raconte comme telle. Il est mécaniquement possible de dériver un prix $/core-hr et $/GB-hr du modèle financier de VCF Operations et de le poser dans la fiche personnalisée d’OpenCost — mais aucune page ne publie cette dérivation ni ne l’autorise, et elle dépend du taux d’occupation de la plateforme. Ce sera le sujet du troisième article. Sur un cluster VKS, l’exploitant qui veut un chiffre au grain du conteneur doit fournir lui-même le prix unitaire. C’est précisément la ligne que cet article lui demande d’écrire.

Pièges & points de vigilance

  • Le chiffre suit les réservations, pas la consommation. max(request, usage). Une équipe qui optimise son code sans toucher à ses requests ne verra aucun effet sur son coût affiché.
  • L’oisiveté partagée déplace le coût sans déplacer la dépense. Un showback publié sans dire quel réglage de shareIdle et d’idleByNode l’a produit est incomparable d’un mois sur l’autre. Figer ces réglages, et les publier avec chaque chiffre.
  • Le fichier de valeurs se versionne comme un manifeste applicatif. Un changement de prix unitaire modifie rétroactivement la lecture de toutes les séries, sans laisser de trace ailleurs.
  • Deux pages du même projet peuvent publier deux bornes. Bornes Kubernetes et listes d’intégrations de facturation divergent entre la page Configuration et la FAQ. On cite chaque liste avec sa page ; on ne construit pas une synthèse qui n’existe nulle part.
  • Une fenêtre plus longue que la rétention Prometheus renvoie un résultat partiel, sans le dire. Le contrôle se fait côté Prometheus, pas côté OpenCost.
  • Le rightsizing qui suit ne créera pas d’économie tout seul. Réduire une réservation libère de la capacité. L’économie n’existe que si un nœud est retiré, non acheté, ou repris par une autre charge.

Conclusion

La bonne sortie de ce chantier n’est pas une capture de graphe : c’est un document d’une page, versionné à côté du fichier de valeurs, qui répond à six questions. D’où vient chaque prix unitaire et à quelle date il a été posé. Quelle fenêtre et quelle resolution produisent les chiffres publiés en interne. L’oisiveté est-elle partagée, et selon laquelle des trois méthodes publiées — uniforme, proportionnelle, ou métrique personnalisée. Quelle clé d’étiquette porte l’attribution par équipe. Quels coûts sont hors de ce modèle, nommément. Et qui relit ce document, à quelle échéance.

Avant d’installer

Choisir la source de prix : intégration de facturation, fiche personnalisée, ou rien — et « rien » signifie des tarifs publics d’une région Google Cloud sur votre matériel privé.

Après l’installation

Lire les actifs avant l’allocation, figer fenêtre, résolution et traitement de l’oisiveté, et inscrire la provenance des prix dans le champ description.

Ce que ça ne fait pas

OpenCost ne réduit rien et ne réconcilie rien. Il rend un modèle lisible. La suite — savoir combien de ce coût est du vide réservé — est l’objet de l’article suivant.

Ce premier étage répond à la première question de la direction financière et s’arrête à une frontière nette : le chiffre produit est un produit, une quantité multipliée par un prix posé, et la quantité vaut max(request, usage). Autrement dit, sur les trois namespaces webshop-*, une part du coût affiché n’est pas de la consommation — c’est de la réservation. Combien ? Cet article ne le dit pas ; il dit où le lire, et avec quels réglages.

Lectures voisines sur le blog : Prometheus et Grafana sur VKS en production pour le prérequis d’observabilité, Premier cluster VKS sur VCF 9 pour le prérequis de plateforme, VCF 9.1 : Kubernetes en self-service pour le modèle de projets et d’organisations que le costing 9.1 vient chiffrer, et La nouvelle architecture VCF 9 expliquée aux architectes pour le modèle de plateforme.

Suite de la série : Rightsizing Kubernetes avec VPA et KRR.

Reçois le prochain par email

Les nouveaux articles et séries, envoyés à leur publication. Aucun autre courrier.

Désabonnement en un clic, à tout moment.

Retour au blog
Partager

Articles similaires

  1. 17 min de lecture

    Modèles de coûts FinOps : ce que facturent AWS, Azure et GCP — et ce que VCF calcule

    Un cluster-heure EKS, un palier AKS, une requête de pod GKE et un matériel VCF amorti ne sont pas quatre valeurs de la même variable. Ce que chaque plateforme facture, et ce que VCF calcule.

  2. 20 min de lecture

    Rightsizing Kubernetes avec VPA et KRR

    VPA recommande et applique, KRR recommande et explique. Les six modes de VPA, et ce que chacun fait vraiment au pod depuis que le redimensionnement en place est stable.

  3. 23 min de lecture

    Network policies et Cilium : construire un default-deny défendable

    L'API NetworkPolicy est livrée avec Kubernetes ; l'appliquer est le travail du CNI. Ce que Cilium ajoute, ce qui reste standard, et comment atteindre le default-deny sans casser la production.

Suivre le blog

Nouveaux articles, réflexions et mises à jour.