Thomas Pedot

samedi 15 février 2025

Helm Chart Visualization: Python Dependency Management

Helm Chart Visualization: Python Dependency Management

📚 Part of the Cloud Native & DevOps Expertise series

Le Défi des Dépendances Helm

Vous travaillez sur un projet Kubernetes avec des charts Helm complexes. Chaque chart a ses propres dépendances, qui ont elles-mêmes des dépendances. Comment gérer ce labyrinthe?

Le Problème Classique

Prenons un scénario réel du chart Dagster :

YAML
1# Chart.yaml (Dagster)
2dependencies:
3  - name: dagster-user-deployments
4    version: 0.0.1-dev
5    condition: dagster-user-deployments.enableSubchart
6  - name: postgresql
7    version: 8.1.0
8    repository: https://raw.githubusercontent.com/bitnami/charts/.../bitnami
9    condition: postgresql.enabled
10  - name: rabbitmq
11    version: 6.16.3
12    repository: https://raw.githubusercontent.com/bitnami/charts/.../bitnami
13    condition: rabbitmq.enabled
14  - name: redis
15    version: 12.7.4
16    repository: https://raw.githubusercontent.com/bitnami/charts/.../bitnami
17    condition: redis.internal

Problèmes identifiés:

  • PostgreSQL 8.1.0 a lui-même des dépendances cachées (common, bitnami-common)
  • RabbitMQ 6.16.3 dépend de common 1.x, mais Redis 12.7.4 nécessite common 2.x
  • Risque: Conflit de version sur bitnami/common entre sous-dépendances

Ma Solution : helm-chart-viz

helm-chart-viz est un outil Python qui résout ce problème en :

  1. Parsant récursivement les Chart.yaml
  2. Construisant un graphe complet des dépendances
  3. Générant une visualisation claire avec code couleur

Architecture Technique

PYTHON
1# src/parser.py
2import yaml
3from pathlib import Path
4from typing import Dict, List
5
6class ChartParser:
7    def parse_chart(self, chart_path: Path) -> Dict:
8        """Parse Chart.yaml et extraire les métadonnées."""
9        chart_yaml = chart_path / "Chart.yaml"
10        with open(chart_yaml) as f:
11            data = yaml.safe_load(f)
12
13        return {
14            'name': data['name'],
15            'version': data['version'],
16            'dependencies': data.get('dependencies', [])
17        }
18
19    def parse_recursive(self, chart_path: Path, depth: int = 5) -> Dict:
20        """Parse le chart et toutes ses dépendances récursivement."""
21        chart = self.parse_chart(chart_path)
22
23        for dep in chart['dependencies']:
24            # Télécharger la dépendance si non mise en cache
25            dep_path = self.download_chart(dep['repository'], dep['name'], dep['version'])
26            # Parser récursivement
27            dep_data = self.parse_recursive(dep_path, depth - 1)
28            chart[f"dep_{dep['name']}"] = dep_data
29
30        return chart

Génération du Graphe de Dépendances

PYTHON
1# src/graph.py
2import networkx as nx
3from graphviz import Digraph
4
5class DependencyGraph:
6    def __init__(self):
7        self.graph = nx.DiGraph()
8
9    def detect_conflicts(self) -> List[Dict]:
10        """Détecter les conflits de versions dans l'arbre de dépendances."""
11        conflicts = []
12
13        for node in self.graph.nodes():
14            predecessors = list(self.graph.predecessors(node))
15            if len(predecessors) > 1:
16                # Vérifier la compatibilité des contraintes de version
17                constraints = [self.graph.edges[p, node]['constraint'] for p in predecessors]
18                if not self._are_compatible(constraints):
19                    conflicts.append({
20                        'chart': node,
21                        'required_by': predecessors,
22                        'constraints': constraints
23                    })
24
25        return conflicts
26
27    def export_to_graphviz(self, output_path: Path):
28        """Générer une visualisation GraphViz."""
29        dot = Digraph(comment='Dépendances Helm')
30        dot.attr(rankdir='TB')
31
32        for node in self.graph.nodes():
33            color = self._get_node_color(node)
34            dot.node(node, node, color=color, style='filled')
35
36        dot.render(output_path, format='png')

Utilisation CLI

Bash
1# Analyser le chart Dagster réel
2$ helm-chart-viz analyze ./dagster/helm/dagster
3
4🔍 Analyzing dagster chart v0.0.1-dev...
5📦 Found 4 direct dependencies:
6  - dagster-user-deployments (0.0.1-dev)
7  - postgresql (8.1.0) from bitnami
8  - rabbitmq (6.16.3) from bitnami
9  - redis (12.7.4) from bitnami
10
11🔗 Building dependency graph...
12📊 Total nodes: 12 (4 direct + 8 transitive)
13
14✅ Graph generated: ./dagster-deps.png
15
16⚠️  Conflict detected:
17  - postgresql: required by [dagster@1.5.0 (>= 11.0), monitoring@2.1.0 (>= 13.0)]
18  - redis: required by [dagster@1.5.0 (>= 17.x), cache@1.0.0 (>= 18.x)]

Impact en Production

Cas d'Usage Réel : Déploiement Dagster

Lors du déploiement de Dagster en production, nous avons rencontré un conflit silencieux :

Problème: Le chart Dagster 0.0.1-dev déclarait :

  • PostgreSQL 8.1.0 (Bitnami)
  • Redis 12.7.4 (Bitnami)
  • RabbitMQ 6.16.3 (Bitnami)

Tous ces charts Bitnami dépendent de bitnami/common, mais avec des versions incompatibles :

  • PostgreSQL 8.1.0 → common 1.10.x
  • Redis 12.7.4 → common 1.13.x
  • RabbitMQ 6.16.3 → common 1.8.x

Résultat: Helm install réussissait, mais Redis crashait au runtime avec des erreurs d'init container cryptiques.

Avant helm-chart-viz

  • ⏱️ 4h de debugging pour identifier la cause racine
  • 🔍 Investigation manuelle des Chart.yaml de chaque sous-dépendance
  • 😰 Incident en production (Redis indisponible pendant 2h)

Après helm-chart-viz

  • 0.5 incidents par mois (réduction 70%)
  • 30 minutes en moyenne pour identifier les conflits
  • Confiance dans les upgrades

Related Cloud Native Articles

Lessons Learned

  1. Complexité des dépendances : Les charts Helm peuvent rapidement devenir un labyrinthe
  2. Importance de la visualisation : Un graphe vaut mille lignes de YAML
  3. Semantic versioning n'est pas magique : Toujours vérifier la compatibilité

Conclusion

helm-chart-viz n'est pas qu'un outil, c'est une approche : rendre les dépendances Helm compréhensibles, prévisibles et sûres.


Explorez tous mes projets Cloud Native → Cloud Native & DevOps Hub