Visualize Kubernetes and container orchestration with Mermaid
Kubernetes manifests are declarative — they say what you want, not how traffic flows. A .yaml file tells the cluster to run a pod, but it doesn't show why, or how it connects to three other services, or where the bottleneck is under load. Mermaid diagrams bridge that gap by visualizing cluster topology, networking, and deployment patterns that YAML hides.
Why diagram Kubernetes
Kubernetes abstracts infrastructure. That's powerful, but it makes the system invisible:
- A developer sees a service name, not the 5 pods behind it.
- A network diagram shows ingress rules, but not the path a request takes.
- A deployment topology lives in three separate manifests (deployment, service, ingress).
- Troubleshooting performance means tracing traffic across namespaces and nodes.
A Mermaid diagram makes the cluster topology explicit — showing pods, services, ingress, and external traffic in one place. That clarity helps onboard new engineers and prevents deployment mistakes.
Basic pod and service architecture
Here's a typical microservices deployment:
graph TD
User["👤 User"] -->|HTTPS| Ingress["Ingress Controller<br/>LoadBalancer"]
Ingress -->|HTTP :80| WebSvc["Service: web<br/>ClusterIP: 10.0.0.10"]
Ingress -->|HTTP :8080| ApiSvc["Service: api<br/>ClusterIP: 10.0.0.11"]
WebSvc --> WebP1["Pod: web-1<br/>nginx:latest<br/>0.2 CPU / 128Mi"]
WebSvc --> WebP2["Pod: web-2<br/>nginx:latest<br/>0.2 CPU / 128Mi"]
ApiSvc --> ApiP1["Pod: api-1<br/>app:v2<br/>0.5 CPU / 512Mi"]
ApiSvc --> ApiP2["Pod: api-2<br/>app:v2<br/>0.5 CPU / 512Mi"]
ApiP1 -->|TCP :5432| DbSvc["Service: postgres<br/>ClusterIP: 10.0.0.12<br/>Headless"]
ApiP2 -->|TCP :5432| DbSvc
DbSvc --> DbP["Pod: postgres-0<br/>postgres:15<br/>1 CPU / 2Gi<br/>StatefulSet"]
style Ingress fill:#ff9800
style WebSvc fill:#4db8ff
style ApiSvc fill:#4db8ff
style DbSvc fill:#ff6b6b
style DbP fill:#ff6b6b
This shows:
- Ingress receives external traffic and routes by path/host.
- Each service is a stable DNS name backed by multiple pods.
- Pods are ephemeral; the service hides that from clients.
- The database uses a headless service for direct pod access (stateful).
- Resource requests per pod are documented (CPU, memory).
Multi-namespace architecture
Large clusters spread services across namespaces. Show the boundaries:
graph TD
subgraph Internet["External"]
User["👤 Users"]
end
subgraph Cluster["Kubernetes Cluster"]
subgraph Ingress["ingress-nginx namespace"]
IGW["Ingress Controller<br/>type: LoadBalancer<br/>port: 80, 443"]
end
subgraph Production["production namespace"]
subgraph Web["Web Tier"]
WebSvc["Service: web<br/>port: 80"]
WP1["Pod: web-1"] & WP2["Pod: web-2"] & WP3["Pod: web-3"]
WebSvc --> WP1 & WP2 & WP3
end
subgraph API["API Tier"]
ApiSvc["Service: api<br/>port: 8080"]
AP1["Pod: api-1"] & AP2["Pod: api-2"]
ApiSvc --> AP1 & AP2
end
WP1 & WP2 & WP3 -->|http://api:8080| ApiSvc
AP1 & AP2 -->|http://postgres:5432| DB
DB["Service: postgres<br/>StatefulSet"]
DBP["Pod: postgres-0"]
DB --> DBP
end
subgraph Monitoring["monitoring namespace"]
Prometheus["Prometheus<br/>Scrapes metrics<br/>/metrics"]
Grafana["Grafana<br/>Visualizes data"]
Prometheus --> Grafana
end
end
User -->|HTTPS| IGW
IGW -->|http://web| WebSvc
Prometheus -->|scrape_interval: 30s| Production
style IGW fill:#ff9800
style WebSvc fill:#4db8ff
style ApiSvc fill:#4db8ff
style DB fill:#ff6b6b
style Prometheus fill:#66bb6a
style Grafana fill:#66bb6a
This shows:
- Ingress and monitoring live in separate namespaces (common practice).
- Production services are isolated.
- Prometheus scrapes all pods in production without coupling them.
- Traffic flow is clear: external → ingress → web → api → database.
Rolling deployment and service discovery
When you deploy a new version, how do old and new pods coexist?
sequenceDiagram
participant User
participant LB["Load Balancer"]
participant ServiceV1["Service: api v1"]
participant ServiceV2["Service: api v2"]
rect rgb(240, 248, 255)
Note over User,ServiceV2: Initial state (v1)
User->>LB: GET /api/data
LB->>ServiceV1: route
ServiceV1->>ServiceV1: Pod 1v1, Pod 2v1, Pod 3v1
ServiceV1-->>User: response (v1)
end
rect rgb(255, 240, 240)
Note over LB,ServiceV2: Deployment: scale up v2 (maxSurge: 1)
ServiceV2->>ServiceV2: Pod 1v2 starting
LB->>LB: Traffic: 66% v1, 33% v2
LB->>ServiceV1: 2/3 requests
LB->>ServiceV2: 1/3 requests
end
rect rgb(255, 248, 240)
Note over LB,ServiceV2: Deployment: scale down v1 (maxUnavailable: 1)
ServiceV1->>ServiceV1: Pod 3v1 terminating (drain connections)
LB->>LB: Traffic: 33% v1, 66% v2
end
rect rgb(240, 255, 240)
Note over User,ServiceV2: All v2 ready, v1 gone
User->>LB: GET /api/data
LB->>ServiceV2: route
ServiceV2->>ServiceV2: Pod 1v2, Pod 2v2, Pod 3v2
ServiceV2-->>User: response (v2)
end
This shows:
- Old and new versions run simultaneously during rollout.
- Traffic shifts gradually (controlled by maxSurge and maxUnavailable).
- Clients don't see downtime; they're routed to healthy pods.
- Connections drain before a pod shuts down.
Data persistence and storage
How do pods access persistent data?
graph TD
subgraph Pod["Pod: postgres-0<br/>StatefulSet"]
PVC["PersistentVolumeClaim<br/>name: data<br/>size: 10Gi<br/>accessMode: ReadWriteOnce"]
Container["postgres container<br/>mountPath: /var/lib/postgresql"]
PVC <--> Container
end
subgraph Cluster["Kubernetes Cluster"]
PV["PersistentVolume<br/>size: 10Gi<br/>phase: Bound<br/>reclaimPolicy: Retain"]
PVC -.->|bound to| PV
end
subgraph Storage["Storage Backend"]
EBS["AWS EBS Volume<br/>vol-abc123def<br/>gp3 500 IOPS<br/>200ms latency"]
end
PV -->|backed by| EBS
note["⚠️ PVC is 1:1 to Pod (StatefulSet)<br/>If pod restarts on same node, data persists<br/>If pod moves to new node, volume follows<br/>"]
style PVC fill:#81c784
style PV fill:#81c784
style EBS fill:#ffb74d
This shows:
- A pod requests storage via a PersistentVolumeClaim.
- Kubernetes binds it to a PersistentVolume.
- The volume is backed by real storage (EBS, NFS, local disk).
- Stateful workloads stay bound even if the pod reschedules.
Network policy and security boundaries
Kubernetes defaults to allow-all networking. Network policies lock it down:
graph TD
Ingress["External Traffic<br/>Ingress Controller"]
subgraph Allowed["Allowed flows"]
A1["Ingress → web<br/>Port 80 only"]
A2["web → api<br/>Port 8080, label: tier=api"]
A3["api → postgres<br/>Port 5432, label: tier=data"]
end
subgraph Denied["Denied flows"]
D1["❌ web ↛ postgres<br/>(direct DB access blocked)"]
D2["❌ postgres ↛ api<br/>(reverse traffic blocked)"]
D3["❌ api → external<br/>(egress controlled)"]
end
Ingress --> A1 --> Web["Service: web<br/>Pods: tier=web"]
Web --> A2 --> API["Service: api<br/>Pods: tier=api"]
API --> A3 --> DB["Service: postgres<br/>Pods: tier=data"]
Web -.->|denied| D1 --> DB
DB -.->|denied| D2 --> API
API -.->|denied| D3
style A1 fill:#c8e6c9
style A2 fill:#c8e6c9
style A3 fill:#c8e6c9
style D1 fill:#ffcdd2
style D2 fill:#ffcdd2
style D3 fill:#ffcdd2
This shows:
- Each tier only talks to the next tier (defense in depth).
- Pod-to-pod traffic is restricted by labels (selector-based).
- Direct web→database access is forbidden.
- Egress is controlled (no external calls unless explicitly allowed).
Tips for Kubernetes diagrams
- Use consistent pod icons: Represent pods the same way across all diagrams (e.g., small boxes in a service).
- Label services and endpoints: Include the service name, ClusterIP, and port — that's how developers reference them.
- Show namespaces as subgraphs: Cluster structure is invisible in YAML; diagrams make it clear.
- Color-code by tier: Web = blue, API = green, database = red, monitoring = orange (helps readers scan quickly).
- Include resource requests: CPU and memory limits are in the manifests but hard to reason about; add them to diagrams.
- Diagram the traffic path: From external user to pod to database — show where latency lives.
- Separate deploy-time from runtime: Manifests describe desired state; diagrams show how it behaves in production (rollouts, failures, recovery).
FAQ
Q: Should I diagram my production cluster as-is, or the ideal state?
A: Both. Diagram the intended architecture, then diff it against reality. That gap often reveals skew.
Q: Can I auto-generate these from kubectl?
A: Not yet, but you can parse manifests and template the diagram. Consider a script that reads kubeconfig and generates the Mermaid source.
Q: How do I show a pod that crashed and restarted?
A: Use a sequence diagram with swim lanes. Show time on the X-axis and pod lifecycle (running → crash → pending → running) on the Y-axis.
Build your K8s diagrams in the playground and share them with your team — they'll catch networking and deployment issues before they hit prod.
Related posts
Mermaid packet diagrams: visualize network topology and data flows
Document network architectures and packet routing with Mermaid packet diagrams. Learn syntax, real-world examples, and when to use for infrastructure visualization.
Mermaid swimlanes for microservices: document service interactions
Use Mermaid flowchart swimlanes to visualize microservices architecture, inter-service communication, and request flows. Clear diagrams for team alignment.