Initial version of KubeDiagrams

This commit is contained in:
Philippe Merle
2024-12-27 04:23:43 +01:00
parent 33cdf7ef78
commit 43d2dab65b
21 changed files with 40404 additions and 0 deletions
+51
View File
@@ -1,2 +1,53 @@
# KubeDiagrams
Generate Kubernetes architecture diagrams from Kubernetes manifest files.
There are several tools to generate Kubernetes architecture diagrams (see **[here](https://github.com/philippemerle/Awesome-Kubernetes-Architecture-Diagrams)**).
The main originality of **KubeDiagrams** is its **[configurability](bin/kube-diagrams.yaml)** allowing for instance to deal with custom Kubernetes resources.
## Prerequisites
* [Python 3.x](https://www.python.org/)
* [Diagrams](https://diagrams.mingrammer.com/)
```sh
$ pip install diagrams
```
* `dot` command ([Graphviz](https://www.graphviz.org/))
## Usage
```sh
$ kube-diagrams -h
usage: kube-diagrams [-h] [-o OUTPUT] [-f FORMAT] [-v] filename
Generate Kubernetes architecture diagrams from Kubernetes manifest files
positional arguments:
filename the Kubernetes manifest filename to process
options:
-h, --help show this help message and exit
-o OUTPUT, --output OUTPUT
output diagram filename
-f FORMAT, --format FORMAT
output format, allowed formats are png (default), jpg, svg, pdf, and dot
-c CONFIG, --config CONFIG
custom kube-diagrams configuration file
-v, --verbose verbosity, set to false by default
```
## Examples
* [official Kubernetes Cassandra example](examples/cassandra/)
* [minikube architecture diagrams](examples/minikube/)
## License
This project is licensed under the GPL-3.0 license - see the [LICENSE](LICENSE) file for details.
## Todo List
* Add more examples
* Add missed Kubernetes resource types: NetworkPolicy and Endpoint
* Make `nodes/*/edges` more declarative
* Add diagram node icon for IngressClass
+405
View File
@@ -0,0 +1,405 @@
#!/usr/bin/env python3
import argparse
import copy
from diagrams import Diagram, Edge, Cluster
from diagrams.aws.enablement import ManagedServices
import os
import yaml
from pprint import pprint
# Query YAML data.
def query_path(data, path, default=None):
paths = path.split(".")
tmp = data
for p in paths[:-1]:
tmp = tmp.get(p)
if tmp is None:
return default
return tmp.get(paths[-1], default)
# Load configuration.
config = {}
with open(os.path.dirname(__file__) + "/kube-diagrams.yaml") as f:
config = yaml.safe_load(f) # load YAML config file
# Undefined edge
UNDEFINED_EDGE = query_path(config, "edges.UNDEFINED", {})
# Get edge config.
def get_edge_config(edge_name):
return config.get("edges", {}).get(edge_name, {})
# Get config associated to a resource.
def get_node_config(resource):
return config.get("nodes", {}).get(get_type(resource), {})
# Get resource type, i.e., "kind/apiVersion".
def get_type(resource):
return resource["kind"] + "/" + resource["apiVersion"]
# Get the namespace of a resource.
# If not defined then returns default namespace.
def get_namespace(resource):
return query_path(resource, "metadata.namespace", config.get("default_namespace", "default"))
# Create diagrams node.
def create_diagram_node(resource, fontcolor="black"):
diagram_node_class = ManagedServices # default diagram node class
node_config = get_node_config(resource)
if node_config != None: # config found
# Get diagram node class name
diagram_node_classname = node_config.get("diagram_node_classname")
if diagram_node_classname != None: # classname defined
# Import Diagrams node class module
idx = diagram_node_classname.rfind('.')
if idx != -1:
exec("import " + diagram_node_classname[:idx])
# Get diagram node class
diagram_node_class = eval(diagram_node_classname)
# Create a diagram node
return diagram_node_class(resource["metadata"]["name"], fontcolor=fontcolor)
class ResourceCluster(object):
def __init__(self):
self.resources = {} # dict<str, resource>
self.clusters = {} # dict<str, Cluster>
def get_or_create_cluster(self, name):
cluster = self.clusters.get(name)
if cluster is None:
cluster = ResourceCluster()
self.clusters[name] = cluster
return cluster
class EdgesContext(list):
def __init__(self, resource):
list.__init__(self)
self.resource = resource
self.namespace = get_namespace(resource)
def add_resource(self, path):
target = query_path(self.resource, path)
self.append([
"%s/%s/%s/%s" % (
target["name"],
self.namespace,
target["kind"],
target["apiVersion"],
),
"REFERENCE"
])
def add_resources(self, path, name_path, namespace_path, kind, apiVersion):
resources = query_path(self.resource, path)
if resources is None:
return
for resource in resources:
self.append([
"%s/%s/%s/%s" % (
resource[name_path],
resource.get(namespace_path, self.namespace),
kind,
apiVersion
),
"REFERENCE"
])
def add_all_resources_matching_labels(self, kind, path, data=None):
if data is None:
data = self.resource
matchLabels = query_path(data, path)
if matchLabels is None:
return False
resourceFound = False
for rid, resource in resources.items():
if resource["kind"] != kind:
continue
labels = query_path(resource, "metadata.labels", {})
found = True
for sk, sv in matchLabels.items():
if labels.get(sk) != sv:
found = False
break
if found:
self.append([rid, "SELECTOR"])
resourceFound = True
return resourceFound
def add_all_volume_resources(self, path):
def add_volumes(volumes):
for volume in volumes:
if "configMap" in volume:
self.append([
"%s/%s/ConfigMap/v1" % (
volume["configMap"]["name"],
self.namespace
),
"REFERENCE"
])
elif "secret" in volume:
self.append([
"%s/%s/Secret/v1" % (
volume["secret"]["secretName"],
self.namespace
),
"REFERENCE"
])
elif "persistentVolumeClaim" in volume:
self.append([
"%s/%s/PersistentVolumeClaim/v1" % (
volume["persistentVolumeClaim"]["claimName"],
self.namespace
),
"REFERENCE"
])
elif "projected" in volume:
add_volumes(query_path(volume, "projected.sources"))
volumes = query_path(resource, path)
if volumes != None:
add_volumes(volumes)
return
def add_volume_claim_templates(self, path):
volumeClaimTemplates = query_path(self.resource, path)
if volumeClaimTemplates is None:
return
for volumeClaimTemplate in volumeClaimTemplates:
self.add_storage_class("spec.storageClassName", volumeClaimTemplate)
def add_storage_class(self, path, data=None):
if data is None:
data = self.resource
storageClassName = query_path(data, path)
if storageClassName != None:
self.append([
"%s/StorageClass/storage.k8s.io/v1" % storageClassName,
"REFERENCE"
])
def add_volume(self, path):
volumeName = query_path(self.resource, path)
if volumeName != None:
self.append([
"%s/PersistentVolume/v1" % volumeName,
"REFERENCE"
])
def add_wait_for_services(self, path):
initContainers = query_path(self.resource, path)
if initContainers is None:
return
for ic in initContainers:
if ic.get("name") == "wait-for-services":
for arg in ic.get("args", []):
if arg.startswith("-service="):
sn = arg[len("-service="):]
self.append([
"%s/%s/Service/v1" % (
sn,
self.namespace
),
"DEPENDENCE"
])
def add_service_account(self, path):
serviceAccountName = query_path(self.resource, path)
if serviceAccountName != None:
self.append([
"%s/%s/ServiceAccount/v1" % (
serviceAccountName,
self.namespace
),
"REFERENCE"
])
def add_role(self, path):
roleRef = self.resource[path]
if self.resource["kind"] == "RoleBinding":
namespace = "/%s" % self.namespace
else:
namespace = ""
self.append([
"%s%s/%s/%s/v1" % (
roleRef["name"],
namespace,
roleRef["kind"],
roleRef.get("apiGroup", "rbac.authorization.k8s.io")
),
"REFERENCE"
])
def add_subjects(self):
for subject in self.resource.get("subjects", []):
if subject.get("namespace"):
rid = "%s/%s/%s/%s" % (
subject["name"],
subject["namespace"],
subject["kind"],
subject.get("apiGroup", "v1")
)
else:
rid = "%s/%s/%s/v1" % (
subject["name"],
subject["kind"],
subject.get("apiGroup", "rbac.authorization.k8s.io")
)
self.append([rid,"REFERENCE"])
def add_service(self, path, data=None):
if data is None:
data = self.resource
name = query_path(data, path)
if name != None:
self.append([
"%s/%s/Service/v1" % (
name,
self.namespace
),
"REFERENCE"
])
# Parse arguments
parser = argparse.ArgumentParser(
prog="kube-diagrams",
description="Generate Kubernetes architecture diagrams from Kubernetes manifest files")
parser.add_argument("filename",
help="the Kubernetes manifest filename to process")
parser.add_argument("-o", "--output", type=str,
help="output diagram filename")
parser.add_argument("-f", "--format", type=str,
help="output format, allowed formats are png (default), jpg, svg, pdf, and dot",
default="png")
parser.add_argument("-c", "--config", type=str,
help="custom kube-diagrams configuration file")
parser.add_argument("-v", "--verbose",
help="verbosity, set to false by default",
action="store_true", default=False)
args = parser.parse_args()
# Process arguments.
if args.output is None:
args.output = args.filename[:args.filename.rfind('.')]
if args.config != None:
with open(args.config) as f:
custom_config = yaml.safe_load(f) # load custom config file
if "default_namespace" in custom_config:
config["default_namespace"] = custom_config["default_namespace"]
if "edges" in custom_config:
config["edges"].update(custom_config["edges"])
if "clusters" in custom_config:
config["clusters"].extend(custom_config["clusters"])
if "nodes" in custom_config:
config["nodes"].update(custom_config["nodes"])
# Load the Kubernetes manifest file.
resources = {} # a map of all resources
resource_cluster = ResourceCluster() # Clustering resources
print("Load %s..." % args.filename)
with open(args.filename) as f:
data = yaml.safe_load_all(f) # load YAML file
for resource in data:
def process_resource(resource):
name = resource["metadata"]["name"]
if get_node_config(resource).get("scope") == "Cluster":
rid = name + "/" + get_type(resource)
resources[rid] = resource
if resource["kind"] == "Namespace":
cluster = resource_cluster.get_or_create_cluster("Namespace: %s" % name)
else:
cluster = resource_cluster
cluster.resources[rid] = resource
else: # scope = Namespaced
rid = name + "/" + get_namespace(resource) + "/" + get_type(resource)
resources[rid] = resource
cluster = resource_cluster.get_or_create_cluster("Namespace: %s" % get_namespace(resource))
def process_clusters(cluster, resource, clusters):
for cluster_config in clusters:
data = query_path(resource, cluster_config["path"])
if data != None:
cluster = cluster.get_or_create_cluster("%s: %s" % (cluster_config["label"], data))
clusters.remove(cluster_config)
return process_clusters(cluster, resource, clusters)
return cluster
cluster = process_clusters(
cluster,
resource,
copy.deepcopy(config.get("clusters", []))
)
cluster.resources[rid] = resource
if resource["kind"] == "List":
for r in resource["items"]:
process_resource(r)
else:
process_resource(resource)
# Print loaded Kubernetes resources
if args.verbose:
def process_cluster(cluster, ident=0):
for rid, resource in cluster.resources.items():
print(" " * ident, "- Resource %s" % rid)
for cid, sub_cluster in cluster.clusters.items():
print(" " * ident, "- %s" % cid)
process_cluster(sub_cluster, ident + 1)
print("Loaded Kubernetes resources:")
process_cluster(resource_cluster)
# Generate diagram
with Diagram("", filename=args.output, show=False, direction="TB", outformat=args.format):
# Generate diagram nodes
diagram_nodes = {}
def create_nodes(cluster):
for rid, resource in cluster.resources.items():
diagram_nodes[rid] = create_diagram_node(resource)
for cid, sub_cluster in cluster.clusters.items():
with Cluster(cid):
create_nodes(sub_cluster)
create_nodes(resource_cluster)
# Generate diagram edges
undefined_nodes = {}
for resource_id, resource in resources.items():
edges = EdgesContext(resource)
# execute config code to generate edges
code_to_exec = get_node_config(resource).get("edges")
if code_to_exec:
try:
exec(code_to_exec)
except Exception as exc:
print("Error:", type(exc), ":", exc.args)
print("Edges script:\n", code_to_exec)
print("Resource:")
pprint(resource)
# generate diagram edges
for edge in edges:
edge_rid = edge[0]
edge_name = edge[1]
try:
diagram_nodes[resource_id] >> Edge(**get_edge_config(edge_name)) >> diagram_nodes[edge_rid]
except KeyError as ke:
print("Error: %s not found!" % ke)
tmp = str(edge_rid).split("/")
if tmp[2] in ["ServiceAccount", "Service", "ConfigMap"]: #TODO: avoid specific cases
rsrc = {
"apiVersion": tmp[3],
"kind": tmp[2],
"metadata": {
"name": tmp[1] + "/" + tmp[0]
}
}
else:
rsrc = {
"apiVersion": tmp[2] + "/" + tmp[3],
"kind": tmp[1],
"metadata": {
"name": tmp[0]
}
}
diagram_node = undefined_nodes.get(edge_rid)
if diagram_node is None:
diagram_node = create_diagram_node(rsrc, UNDEFINED_EDGE.get("color"))
undefined_nodes[edge_rid] = diagram_node
diagram_nodes[resource_id] >> Edge(**UNDEFINED_EDGE) >> diagram_node
print("%s.%s generated." % (args.output, args.format))
+165
View File
@@ -0,0 +1,165 @@
# Configuration of kube-diagrams
default_namespace: default
edges:
REFERENCE:
color: black
SELECTOR:
color: black
style: dashed
DEPENDENCE:
color: darkgrey
UNDEFINED:
color: red
clusters:
- label: K8s Application
path: metadata.labels.k8s-app
- label: Application
path: metadata.labels.app
- label: Microservice
path: metadata.labels.service
nodes:
ClusterRole/rbac.authorization.k8s.io/v1:
scope: Cluster
diagram_node_classname: diagrams.k8s.rbac.CRole
edges: |
for clusterRoleSelector in query_path(resource, "aggregationRule.clusterRoleSelectors", []):
edges.add_all_resources_matching_labels("ClusterRole", "matchLabels", clusterRoleSelector)
ClusterRoleBinding/rbac.authorization.k8s.io/v1:
scope: Cluster
diagram_node_classname: diagrams.k8s.rbac.CRB
edges: |
edges.add_role("roleRef")
edges.add_subjects()
CustomResourceDefinition/apiextensions.k8s.io/v1:
scope: Cluster
diagram_node_classname: diagrams.k8s.others.CRD
# no edges
ConfigMap/v1:
scope: Namespaced
diagram_node_classname: diagrams.k8s.podconfig.CM
# no edges
DaemonSet/apps/v1:
scope: Namespaced
diagram_node_classname: diagrams.k8s.compute.DS
edges: |
edges.add_all_resources_matching_labels("Pod", "spec.selector.matchLabels")
edges.add_service_account("spec.template.spec.serviceAccountName")
edges.add_all_volume_resources("spec.template.spec.volumes")
Deployment/apps/v1:
scope: Namespaced
diagram_node_classname: diagrams.k8s.compute.Deploy
edges: |
edges.add_all_resources_matching_labels("ReplicaSet", "spec.selector.matchLabels")
edges.add_service_account("spec.template.spec.serviceAccountName")
edges.add_wait_for_services("spec.template.spec.initContainers")
edges.add_all_volume_resources("spec.template.spec.volumes")
Group/rbac.authorization.k8s.io/v1:
scope: Cluster
diagram_node_classname: diagrams.k8s.rbac.Group
# no edges
HorizontalPodAutoscaler/autoscaling/v2:
scope: Namespaced
diagram_node_classname: diagrams.k8s.clusterconfig.HPA
edges: |
edges.add_resource("spec.scaleTargetRef")
Ingress/networking.k8s.io/v1:
scope: Namespaced
diagram_node_classname: diagrams.k8s.network.Ing
edges: |
for rule in query_path(resource, "spec.rules", []):
for path in query_path(rule, "http.paths", []):
edges.add_service("backend.service.name", path)
IngressClass/networking.k8s.io/v1:
scope: Cluster
diagram_node_classname: #TODO: to implement
# no edges
Job/batch/v1:
scope: Namespaced
diagram_node_classname: diagrams.k8s.compute.Job
edges: |
edges.add_all_resources_matching_labels("Pod", "spec.selector.matchLabels")
edges.add_service_account("spec.template.spec.serviceAccountName")
edges.add_all_volume_resources("spec.template.spec.volumes")
Namespace/v1:
scope: Cluster
diagram_node_classname: diagrams.k8s.group.NS
# no edges
Node/v1:
scope: Cluster
diagram_node_classname: diagrams.k8s.infra.Node
# no edges
PersistentVolume/v1:
scope: Cluster
diagram_node_classname: diagrams.k8s.storage.PV
edges: |
edges.add_storage_class("spec.storageClassName")
PersistentVolumeClaim/v1:
scope: Namespaced
diagram_node_classname: diagrams.k8s.storage.PVC
edges: |
edges.add_storage_class("spec.storageClassName")
edges.add_volume("spec.volumeName")
Pod/v1:
scope: Namespaced
diagram_node_classname: diagrams.k8s.compute.Pod
edges: |
edges.add_service_account("spec.serviceAccountName")
edges.add_wait_for_services("spec.initContainers")
edges.add_all_volume_resources("spec.volumes")
ReplicaSet/apps/v1:
scope: Namespaced
diagram_node_classname: diagrams.k8s.compute.RS
edges: |
edges.add_all_resources_matching_labels("Pod", "spec.selector.matchLabels")
edges.add_service_account("spec.template.spec.serviceAccountName")
edges.add_wait_for_services("spec.template.spec.initContainers")
edges.add_all_volume_resources("spec.template.spec.volumes")
Role/rbac.authorization.k8s.io/v1:
scope: Namespaced
diagram_node_classname: diagrams.k8s.rbac.Role
# no edges
RoleBinding/rbac.authorization.k8s.io/v1:
scope: Namespaced
diagram_node_classname: diagrams.k8s.rbac.RB
edges: |
edges.add_role("roleRef")
edges.add_subjects()
Secret/v1:
scope: Namespaced
diagram_node_classname: diagrams.k8s.podconfig.Secret
# no edges
Service/v1:
scope: Namespaced
diagram_node_classname: diagrams.k8s.network.SVC
edges: |
if not edges.add_all_resources_matching_labels("Pod", "spec.selector"):
if not edges.add_all_resources_matching_labels("Deployment", "spec.selector"):
edges.add_all_resources_matching_labels("StatefulSet", "spec.selector")
ServiceAccount/v1:
scope: Namespaced
diagram_node_classname: diagrams.k8s.rbac.SA
# no edges
StatefulSet/apps/v1:
scoped: Namespaced
diagram_node_classname: diagrams.k8s.compute.STS
edges: |
edges.add_all_resources_matching_labels("Pod", "spec.selector.matchLabels")
edges.add_service_account("spec.template.spec.serviceAccountName")
edges.add_service("spec.serviceName")
edges.add_wait_for_services("spec.template.spec.initContainers")
edges.add_all_volume_resources("spec.template.spec.volumes")
edges.add_volume_claim_templates("spec.volumeClaimTemplates")
StorageClass/storage.k8s.io/v1:
scope: Cluster
diagram_node_classname: diagrams.k8s.storage.SC
# no edges
User/rbac.authorization.k8s.io/v1:
scope: Cluster
diagram_node_classname: diagrams.k8s.rbac.User
# no edges
#TODO: add other Kubernetes resource types.
# Ep diagrams.k8s.network.Ep, Endpoint (alias)
# Netpol diagrams.k8s.network.Netpol, NetworkPolicy (alias)
# PSP diagrams.k8s.others.PSP
# Vol diagrams.k8s.storage.Vol, Volume (alias)
+45
View File
@@ -0,0 +1,45 @@
# Cassandra Example
This is based on the [official Kubernetes Cassandra example](https://kubernetes.io/docs/tutorials/stateful-application/cassandra/).
## Instructions
Generate the Kubernetes architecture diagram for [cassandra.yml](cassandra.yml):
```sh
$ kube-diagrams cassandra.yml
```
Start a minikube cluster:
```sh
$ minikube start --memory 5120 --cpus=4
```
Deploy the Cassandra application:
```sh
$ kubectl apply -f cassandra.yml
```
Wait a few minutes for the Cassandra application to be deployed.
Get all Kubernetes resources in the `default` namespace:
```sh
$ kubectl get all,pvc,serviceaccount,configmap,pv,storageclass -o=yaml > default.yml
```
Generate a Kubernetes architecture diagram for the `default` namespace:
```sh
$ kube-diagrams default.yml
```
Delete Cassandra application.
```sh
$ kubectl delete -f cassandra.yml
```
## Generated architecture diagrams
Architecture diagram for [cassandra.yml](cassandra.yml):
![cassandra.png](cassandra.png)
Architecture diagram for a deployed Cassandra instance:
![default namespace](default.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

+113
View File
@@ -0,0 +1,113 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: cassandra
name: cassandra
spec:
clusterIP: None
ports:
- port: 9042
selector:
app: cassandra
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: cassandra
labels:
app: cassandra
spec:
serviceName: cassandra
replicas: 3
selector:
matchLabels:
app: cassandra
template:
metadata:
labels:
app: cassandra
spec:
terminationGracePeriodSeconds: 500
containers:
- name: cassandra
image: gcr.io/google-samples/cassandra:v13
imagePullPolicy: Always
ports:
- containerPort: 7000
name: intra-node
- containerPort: 7001
name: tls-intra-node
- containerPort: 7199
name: jmx
- containerPort: 9042
name: cql
resources:
limits:
cpu: "500m"
memory: 1Gi
requests:
cpu: "500m"
memory: 512M #CHANGED: instead of 1Gi
securityContext:
capabilities:
add:
- IPC_LOCK
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- nodetool drain
env:
- name: MAX_HEAP_SIZE
value: 512M
- name: HEAP_NEWSIZE
value: 100M
- name: CASSANDRA_SEEDS
value: "cassandra-0.cassandra.default.svc.cluster.local"
- name: CASSANDRA_CLUSTER_NAME
value: "K8Demo"
- name: CASSANDRA_DC
value: "DC1-K8Demo"
- name: CASSANDRA_RACK
value: "Rack1-K8Demo"
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
readinessProbe:
exec:
command:
- /bin/bash
- -c
- echo OK #ERROR: if /ready-probe.sh
initialDelaySeconds: 15
timeoutSeconds: 5
# These volume mounts are persistent. They are like inline claims,
# but not exactly because the names need to match exactly one of
# the stateful pod volumes.
volumeMounts:
- name: cassandra-data
mountPath: /cassandra_data
# These are converted to volume claims by the controller
# and mounted at the paths mentioned above.
# do not use these in production until ssd GCEPersistentDisk or other ssd pd
volumeClaimTemplates:
- metadata:
name: cassandra-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: fast
resources:
requests:
storage: 512M #CHANGED: instead of 1Gi
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: fast
provisioner: k8s.io/minikube-hostpath
parameters:
type: pd-ssd
Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
#! /bin/sh
BIN=../../bin
# Generate the Kubernetes architecture diagram for cassandra.yml.
$BIN/kube-diagrams cassandra.yml
# Deploy the Cassandra application.
kubectl apply -f cassandra.yml
# Wait a few minutes for the Cassandra application to be deployed.
sleep 120
# Get all Kubernetes resources in the default namespace.
kubectl get all,pvc,serviceaccount,configmap,pv,storageclass -o=yaml > default.yml
# Generate a Kubernetes architecture diagram for the default namespace.
$BIN/kube-diagrams default.yml
# Delete Cassandra application.
kubectl delete -f cassandra.yml
+38
View File
@@ -0,0 +1,38 @@
# Minikube Example
## Instructions
Start a minikube cluster with `metrics-server`, `dashboard`, and `ingress` addons:
```sh
$ minikube start
$ minikube addons enable metrics-server
$ minikube addons enable dashboard
$ minikube addons enable ingress
```
Generate some Kubernetes architecture diagrams:
```sh
$ ./generate.sh
```
## Generated architecture diagrams
Architecture diagram of Kubernetes resources in the minikube `default` namespace:
![minikube default namespace](minikube-default.png)
Architecture diagram of Kubernetes resources in the minikube `kubernetes-dashboard` namespace:
![minikube kubernetes-dashboard namespace](minikube-dashboard.png)
Architecture diagram of all minikube Kubernetes resources except for RBAC ones:
![minikube](minikube-without-rbac.png)
**Note**: [minikube-without-rbac-corrected.yml](minikube-without-rbac-corrected.yml) was manually corrected to better group some Kubernetes resources (see `#ADDED:` and `#CHANGED:` comments).
Corrected architecture diagram of all minikube Kubernetes resources except for RBAC ones:
![minikube](minikube-without-rbac-corrected.png)
Architecture diagram of all minikube Kubernetes resources:
![Minikube](minikube.png)
**Note**: In the previous architecture diagram, both red labels and red arrows indicate unfound Kubernetes resources.
+27
View File
@@ -0,0 +1,27 @@
#! /bin/sh
BIN=../../bin
cluster_name=minikube
# Get all Kubernetes resources.
kubectl get clusterrole,clusterrolebinding,crd,configmap,ds,deployment,hpa,ingress,ingressclass,job,node,pod,replicaset,role,rolebinding,secret,service,serviceaccount,storageclass,namespace -A -o=yaml > ${cluster_name}.yml
# Generate Kubernetes architecture diagram.
$BIN/kube-diagrams -vv ${cluster_name}.yml
# Get all Kubernetes resources except for rbac ones.
kubectl get crd,configmap,ds,deployment,hpa,ingress,ingressclass,job,node,pod,replicaset,secret,service,serviceaccount,storageclass,namespace -A -o=yaml > ${cluster_name}-without-rbac.yml
# Generate Kubernetes architecture diagram.
$BIN/kube-diagrams -v ${cluster_name}-without-rbac.yml
# Generate a Kubernetes architecture diagram from a corrected manifest.
$BIN/kube-diagrams -v ${cluster_name}-without-rbac-corrected.yml
# Get all Kubernetes resources in the default namespace.
kubectl get configmap,ds,deployment,hpa,ingress,job,pod,replicaset,role,rolebinding,secret,service,serviceaccount -o=yaml > ${cluster_name}-default.yml
# Generate Kubernetes architecture diagram.
$BIN/kube-diagrams -v ${cluster_name}-default.yml
# Get all Kubernetes resources in the kubernetes-dashboard namespace.
kubectl get configmap,ds,deployment,hpa,ingress,job,pod,replicaset,role,rolebinding,secret,service,serviceaccount -n kubernetes-dashboard -o=yaml > ${cluster_name}-dashboard.yml
# Generate Kubernetes architecture diagram.
$BIN/kube-diagrams -v ${cluster_name}-dashboard.yml
Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+116
View File
@@ -0,0 +1,116 @@
apiVersion: v1
items:
- apiVersion: v1
data:
ca.crt: |
-----BEGIN CERTIFICATE-----
MIIDBjCCAe6gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p
a3ViZUNBMB4XDTIzMDIwNDA3NTMzNloXDTMzMDIwMjA3NTMzNlowFTETMBEGA1UE
AxMKbWluaWt1YmVDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM3l
n8dtEkPntkBRSF7NBlwCbwI3BRxHL5LUSROCfr/tFyuJ8XiZa8yv/DbiCKDFWeBr
lJwWOJ3HjrjQc+nBIjbG3spMaOr9Kgut7UKyYEeIZxBSz5GBd7rlRuBwOkRqEXtS
/H/TxVzgrugfKbI+ALtzbtHTzKKLDGaYjAC5t2jQ/PtzQ3s4cB8Z/fk03n9woBC8
6makLFQXXPj65y09hb/slxIbi4hyRe4hdQYnxeY+PKmuEZIaed5gFrN/EDDtrtwn
bcPY3Pwc0/b1+S44TcB+eWMq28zPLeO/Kjziw8q+1wldKWIXJJSUAUU5dBOtlRol
yfr7t0ibsft1OTVCisECAwEAAaNhMF8wDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQW
MBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
BBS1Nak8XxDdSOq3r1qdAXNmQRttCzANBgkqhkiG9w0BAQsFAAOCAQEAxrabqVKs
u9hJTiUNhLEQTOVR7gMuonymonQ73488pQHKKIyAM8It+ClgP+YdZtRi+uxGgQ3m
kwbJbf1ybysY++tIKbUXfsal/wcrt2hbqk6HJxca/YaYL7qja6N/BElct07RQhLf
DpMtfqokyBh/yOlobBkrrA+RylGVi1ebWgFM7xtzmuPpeIYurNIcXGpp0d21RuRz
0Oxd9g/HHAzZihXbHfPeJJdiQBtPvQ83TZ+T2E0MCkLDUC1okRztR8R5YPG6nZli
zVRBh41095iSNFKJhFQyu5Qzme98wdBBQGoxcMOoXk6ioeDQUywHaJMjdCANqubf
I700WTtGVr503w==
-----END CERTIFICATE-----
kind: ConfigMap
metadata:
annotations:
kubernetes.io/description: Contains a CA bundle that can be used to verify the
kube-apiserver when using internal endpoints such as the internal service
IP or kubernetes.default.svc. No other usage is guaranteed across distributions
of Kubernetes clusters.
creationTimestamp: "2024-12-26T23:24:31Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:ca.crt: {}
f:metadata:
f:annotations:
.: {}
f:kubernetes.io/description: {}
manager: kube-controller-manager
operation: Update
time: "2024-12-26T23:24:31Z"
name: kube-root-ca.crt
namespace: default
resourceVersion: "305"
uid: 2cb982e4-6045-41b4-9cf1-c00ac57e8162
- apiVersion: v1
kind: Service
metadata:
creationTimestamp: "2024-12-26T23:24:17Z"
labels:
component: apiserver
provider: kubernetes
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:labels:
.: {}
f:component: {}
f:provider: {}
f:spec:
f:clusterIP: {}
f:internalTrafficPolicy: {}
f:ipFamilyPolicy: {}
f:ports:
.: {}
k:{"port":443,"protocol":"TCP"}:
.: {}
f:name: {}
f:port: {}
f:protocol: {}
f:targetPort: {}
f:sessionAffinity: {}
f:type: {}
manager: kube-apiserver
operation: Update
time: "2024-12-26T23:24:17Z"
name: kubernetes
namespace: default
resourceVersion: "195"
uid: b0a63e0d-7e92-4d91-b328-b2f9d73425ed
spec:
clusterIP: 10.96.0.1
clusterIPs:
- 10.96.0.1
internalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- name: https
port: 443
protocol: TCP
targetPort: 8443
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
- apiVersion: v1
kind: ServiceAccount
metadata:
creationTimestamp: "2024-12-26T23:24:31Z"
name: default
namespace: default
resourceVersion: "314"
uid: 56ce5b99-fb97-40b6-a220-0eff6fd067e5
kind: List
metadata:
resourceVersion: ""
selfLink: ""
Binary file not shown.

After

Width:  |  Height:  |  Size: 671 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 681 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

File diff suppressed because it is too large Load Diff