{"id":34599242,"url":"https://github.com/niradler/k8s-graph","last_synced_at":"2026-05-24T20:03:49.166Z","repository":{"id":322912840,"uuid":"1091364235","full_name":"niradler/k8s-graph","owner":"niradler","description":null,"archived":false,"fork":false,"pushed_at":"2025-11-07T01:26:51.000Z","size":182,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-07T03:21:15.676Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/niradler.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"agents.md","dco":null,"cla":null}},"created_at":"2025-11-06T23:16:46.000Z","updated_at":"2025-11-07T01:26:54.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/niradler/k8s-graph","commit_stats":null,"previous_names":["niradler/k8s-graph"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/niradler/k8s-graph","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niradler%2Fk8s-graph","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niradler%2Fk8s-graph/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niradler%2Fk8s-graph/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niradler%2Fk8s-graph/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/niradler","download_url":"https://codeload.github.com/niradler/k8s-graph/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niradler%2Fk8s-graph/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28002250,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-24T02:00:07.193Z","response_time":83,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-24T12:07:51.471Z","updated_at":"2025-12-24T12:08:10.966Z","avatar_url":"https://github.com/niradler.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# k8s-graph\n\n\u003e Protocol-based Python library for building NetworkX graphs from Kubernetes resources\n\n[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Tests](https://img.shields.io/badge/tests-139%20passing-brightgreen.svg)](tests/)\n[![Coverage](https://img.shields.io/badge/coverage-70%25-green.svg)](htmlcov/)\n[![Type Checked](https://img.shields.io/badge/type--checked-mypy-blue.svg)](k8s_graph/)\n\n## Overview\n\n**k8s-graph** is a flexible, extensible Python library that builds NetworkX graphs from Kubernetes cluster resources. It provides intelligent relationship discovery, stable node identity, and a powerful plugin system for custom resource types.\n\n### Key Features\n\n- **Protocol-Based Design**: Easy to integrate with any K8s client (add caching, proxying, mocking)\n- **Strong Defaults**: Works out-of-the-box with kubernetes-python\n- **Extensible Architecture**: Runtime plugin system for custom CRD handlers\n- **Stable Node Identity**: Consistent node IDs even when pods recreate\n- **Stateless Library**: No built-in caching - you control the strategy\n- **Type-Safe**: Comprehensive type hints and Pydantic models\n- **Production Ready**: Async/await throughout, graceful error handling\n\n## Visualizations\n\n### Complete Namespace Graph\n\nFull namespace visualization showing all resources and their relationships:\n\n![Complete Namespace](docs/images/showcase_full_namespace.png)\n\n### Deployment with Dependencies\n\nFocused view of a single deployment with its dependencies (ReplicaSet, Pods, ConfigMaps, Secrets):\n\n![Deployment Dependencies](docs/images/showcase_deployment_with_dependencies.png)\n\n### Service Mesh Connections\n\nService-to-service connections and network topology:\n\n![Service Connections](docs/images/showcase_service_connections.png)\n\n## Installation\n\n```bash\n# Using uv (recommended)\nuv pip install k8s-graph\n\n# Using pip\npip install k8s-graph\n```\n\n## Quick Start\n\n```python\nimport asyncio\nfrom k8s_graph import GraphBuilder, KubernetesAdapter, ResourceIdentifier, BuildOptions\n\nasync def main():\n    # Create K8s client adapter\n    client = KubernetesAdapter()\n    \n    # Create graph builder\n    builder = GraphBuilder(client)\n    \n    # Build graph from a Deployment\n    graph = await builder.build_from_resource(\n        resource_id=ResourceIdentifier(\n            kind=\"Deployment\",\n            name=\"nginx\",\n            namespace=\"default\"\n        ),\n        depth=2,\n        options=BuildOptions()\n    )\n    \n    # Explore the graph\n    print(f\"Nodes: {graph.number_of_nodes()}\")\n    print(f\"Edges: {graph.number_of_edges()}\")\n    \n    # Query relationships\n    for node_id, attrs in graph.nodes(data=True):\n        print(f\"{attrs['kind']}: {attrs['name']}\")\n\nasyncio.run(main())\n```\n\n## Core Concepts\n\n### Protocol-Based Design\n\nk8s-graph uses protocols to define extension points, making it easy to customize:\n\n```python\nfrom k8s_graph import K8sClientProtocol\n\nclass CachedK8sClient:\n    \"\"\"Custom client with caching\"\"\"\n    \n    async def get_resource(self, resource_id):\n        # Your caching logic\n        pass\n    \n    async def list_resources(self, kind, namespace=None, label_selector=None):\n        # Your caching logic\n        pass\n\n# Use your custom client\nbuilder = GraphBuilder(CachedK8sClient())\n```\n\n### Extensible Discovery\n\nRegister custom handlers for CRDs or override built-in behavior:\n\n```python\nfrom k8s_graph import BaseDiscoverer, DiscovererRegistry\n\nclass MyCustomHandler(BaseDiscoverer):\n    def supports(self, resource):\n        return resource.get(\"kind\") == \"MyCustomResource\"\n    \n    async def discover(self, resource):\n        # Your relationship discovery logic\n        return relationships\n\n# Register globally\nDiscovererRegistry.get_global().register(MyCustomHandler(client))\n```\n\n### Stable Node Identity\n\nPods and ReplicaSets get stable IDs based on their template hash, not their name:\n\n```python\n# Pod names change: nginx-abc123-xyz -\u003e nginx-abc123-def\n# Node ID stays same: Pod:default:Deployment-nginx:abc123\n\n# Graph remains consistent across pod recreations\n```\n\n## Architecture\n\n```\nk8s-graph/\n├── k8s_graph/\n│   ├── models.py           # Pydantic models (ResourceIdentifier, etc.)\n│   ├── protocols.py        # K8sClientProtocol, DiscovererProtocol\n│   ├── builder.py          # GraphBuilder (main orchestration)\n│   ├── node_identity.py    # Stable node ID generation\n│   ├── validator.py        # Graph validation\n│   ├── formatter.py        # Output formatting\n│   ├── discoverers/\n│   │   ├── base.py         # BaseDiscoverer\n│   │   ├── registry.py     # DiscovererRegistry\n│   │   ├── unified.py      # UnifiedDiscoverer\n│   │   ├── native.py       # Core K8s resources\n│   │   ├── rbac.py         # RBAC relationships\n│   │   └── network.py      # NetworkPolicy relationships\n│   └── adapters/\n│       └── kubernetes.py   # Default K8s adapter\n```\n\n## Examples\n\nSee the [examples/](examples/) directory for:\n\n- **basic_usage.py** - Simple graph building and exploration\n- **namespace_graph.py** - Building complete namespace graphs\n- **cached_client.py** - Custom client with TTL-based caching\n- **custom_client.py** - Custom client with rate limiting\n- **query_graph.py** - Query API demonstrations (dependencies, paths, filtering)\n- **visualize_cluster.py** - Graph visualization with multiple layouts\n\n## Supported Resources\n\n### Native Kubernetes Resources\n\n**Workloads:**\n- Pod, Deployment, StatefulSet, DaemonSet, ReplicaSet, Job, CronJob\n\n**Networking:**\n- Service, Ingress, NetworkPolicy, Endpoints\n\n**Storage:**\n- PersistentVolumeClaim, ConfigMap, Secret\n\n**RBAC:**\n- ServiceAccount, Role, RoleBinding, ClusterRole, ClusterRoleBinding\n\n**Policy \u0026 Scaling:**\n- HorizontalPodAutoscaler, PodDisruptionBudget, ResourceQuota, LimitRange\n\n**Infrastructure:**\n- Namespace\n\n### Relationship Discovery\n\nk8s-graph automatically discovers relationships:\n\n- **namespace**: Resource → Namespace\n- **owner**: Deployment → ReplicaSet → Pod\n- **label_selector**: Service → Pods (via label matching)\n- **volume**: Pod → ConfigMap/Secret/PVC (volume mounts)\n- **env_var**: Pod → ConfigMap/Secret (environment variables)\n- **env_from**: Pod → ConfigMap/Secret (envFrom)\n- **service_account**: Workload → ServiceAccount\n- **role_binding**: RoleBinding → Role/ServiceAccount\n- **network_policy**: NetworkPolicy → Pods\n- **ingress_backend**: Ingress → Service\n- **pvc**: Pod → PersistentVolumeClaim\n\n## Use Cases\n\n### Troubleshooting \u0026 Investigation\n\n**Find why a pod is failing:**\n```python\n# Build graph from deployment\ngraph = await builder.build_from_resource(\n    ResourceIdentifier(kind=\"Deployment\", name=\"my-app\", namespace=\"production\"),\n    depth=3,\n    options=BuildOptions()\n)\n\n# Find all secrets and configmaps\nfor node_id, attrs in graph.nodes(data=True):\n    if attrs['kind'] in ['Secret', 'ConfigMap']:\n        print(f\"{attrs['kind']}: {attrs['name']}\")\n```\n\n**Trace service dependencies:**\n```python\n# Build from service\ngraph = await builder.build_from_resource(\n    ResourceIdentifier(kind=\"Service\", name=\"api-gateway\", namespace=\"default\"),\n    depth=2,\n    options=BuildOptions()\n)\n\n# Find all connected pods\npods = [attrs for _, attrs in graph.nodes(data=True) if attrs['kind'] == 'Pod']\nprint(f\"Service connects to {len(pods)} pods\")\n```\n\n**Audit secret usage across namespace:**\n```python\n# Build complete namespace\ngraph = await builder.build_namespace_graph(\"production\", depth=5, options=BuildOptions())\n\n# Find all resources using secrets\nimport networkx as nx\nsecret_users = {}\nfor node_id, attrs in graph.nodes(data=True):\n    if attrs['kind'] == 'Secret':\n        secret_name = attrs['name']\n        # Find predecessors (resources using this secret)\n        users = list(graph.predecessors(node_id))\n        secret_users[secret_name] = len(users)\n\nfor secret, count in sorted(secret_users.items(), key=lambda x: x[1], reverse=True):\n    print(f\"{secret}: used by {count} resources\")\n```\n\n## Advanced NetworkX Operations\n\nSince k8s-graph returns standard NetworkX graphs, you can leverage all NetworkX capabilities:\n\n### Path Analysis\n\n**Find dependency path between resources:**\n```python\nimport networkx as nx\n\n# Find path from deployment to secret\ntry:\n    path = nx.shortest_path(\n        graph,\n        source=\"Deployment:production:api-gateway\",\n        target=\"Secret:production:db-credentials\"\n    )\n    print(\"Dependency chain:\", \" → \".join([graph.nodes[n]['kind'] for n in path]))\nexcept nx.NetworkXNoPath:\n    print(\"No direct dependency path found\")\n```\n\n### Subgraph Extraction\n\n**Extract workloads only:**\n```python\n# Filter to workload resources\nworkload_kinds = ['Deployment', 'StatefulSet', 'DaemonSet', 'Job']\nworkload_nodes = [\n    n for n, attrs in graph.nodes(data=True) \n    if attrs.get('kind') in workload_kinds\n]\nworkload_graph = graph.subgraph(workload_nodes)\n```\n\n**Extract configuration layer:**\n```python\n# Get all ConfigMaps, Secrets, and what uses them\nconfig_kinds = ['ConfigMap', 'Secret']\nconfig_nodes = [n for n, attrs in graph.nodes(data=True) if attrs.get('kind') in config_kinds]\n\n# Include resources that use them\nextended_nodes = set(config_nodes)\nfor node in config_nodes:\n    extended_nodes.update(graph.predecessors(node))\n\nconfig_graph = graph.subgraph(extended_nodes)\n```\n\n### Graph Analysis\n\n**Find most connected resources (hubs):**\n```python\n# Calculate degree centrality\ncentrality = nx.degree_centrality(graph)\ntop_resources = sorted(centrality.items(), key=lambda x: x[1], reverse=True)[:10]\n\nfor node_id, score in top_resources:\n    attrs = graph.nodes[node_id]\n    print(f\"{attrs['kind']}/{attrs['name']}: {score:.3f}\")\n```\n\n**Detect isolated resources:**\n```python\n# Find resources with no connections\nundirected = graph.to_undirected()\nisolated = list(nx.isolates(undirected))\n\nprint(f\"Found {len(isolated)} isolated resources:\")\nfor node_id in isolated:\n    attrs = graph.nodes[node_id]\n    print(f\"  {attrs['kind']}/{attrs['name']}\")\n```\n\n**Analyze connectivity:**\n```python\n# Check graph connectivity\nundirected = graph.to_undirected()\ncomponents = list(nx.connected_components(undirected))\n\nprint(f\"Graph has {len(components)} connected components\")\nprint(f\"Largest component: {len(max(components, key=len))} nodes\")\n```\n\n### Export \u0026 Visualization\n\n**Export to different formats:**\n```python\nfrom k8s_graph import export_json, export_png, export_html\n\n# JSON for programmatic use\nexport_json(graph, \"cluster.json\")\n\n# PNG for documentation\nexport_png(graph, \"cluster.png\", title=\"Production Cluster\", aggregate=True)\n\n# Interactive HTML\nexport_html(graph, \"cluster.html\", title=\"Production Cluster\", aggregate=True)\n```\n\n**Custom NetworkX exports:**\n```python\nimport networkx as nx\n\n# GraphML for Gephi/Cytoscape\nnx.write_graphml(graph, \"cluster.graphml\")\n\n# GML format\nnx.write_gml(graph, \"cluster.gml\")\n\n# Edge list\nnx.write_edgelist(graph, \"cluster.edgelist\")\n```\n\n## Development\n\n```bash\n# Setup\ngit clone https://github.com/k8s-graph/k8s-graph\ncd k8s-graph\nuv venv\nsource .venv/bin/activate\nmake install-dev\n\n# Run tests\nmake test\n\n# Run checks\nmake check\n\n# Build package\nmake build\n```\n\nSee [agents.md](agents.md) for detailed development guide.\n\n## License\n\nMIT License - see [LICENSE](LICENSE) for details.\n\n## Documentation\n\n- **Architecture Guide**: [agents.md](agents.md) - Comprehensive guide for developers\n- **Examples**: [examples/](examples/) - Working code examples\n- **Tests**: [tests/](tests/) - Full test suite showcasing capabilities\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fniradler%2Fk8s-graph","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fniradler%2Fk8s-graph","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fniradler%2Fk8s-graph/lists"}