Kubernetes on Amazon EKS
A cloud-native platform with Cilium, Gateway API, Karpenter, Argo CD, cert-manager and ExternalDNS.
Three slides that explain one platform at three zoom levels: what runs where, how a request is routed, and how a change ships. Together they show most of what spinagram does: nested AWS and Kubernetes zones, icons from four packs (AWS, Kubernetes, CNCF and SVG Logos), a flowchart inside an architecture, a swimlane activity and one shared dark stylesheet.
| Project | Role on the slides | Icon |
|---|---|---|
| Amazon EKS, NLB, Route 53, Aurora, ECR | Managed AWS services | aws:nlb, aws:route53, aws:amazon-aurora, … |
| Cilium | eBPF networking, kube-proxy replacement, Gateway API implementation, network policy | cncf:cilium |
| Gateway API | GatewayClass, Gateway (Envoy) and HTTPRoute resources | cncf:envoy, k8s:crd |
| Karpenter | Adds right-sized nodes when pods are pending | aws:amazon-ec2-auto-scaling |
| Argo CD | GitOps: syncs the cluster to Git | cncf:argo |
| cert-manager, ExternalDNS | TLS certificates and DNS records | cncf:cert-manager, k8s:crd |
| Hubble, Prometheus, Grafana | Flow visibility and metrics | cncf:cilium, cncf:prometheus |
The platform
The cluster is a dark canvas inside the AWS region. Its three lanes (networking, the app namespace and platform add-ons) are groups with a direction, and the managed services sit in their own column so the arrows to them stay short. Dashed purple arrows are control loops (sync, certificates, DNS records), not traffic.
"""A Kubernetes platform on Amazon EKS: Cilium as the eBPF CNI and Gateway API implementation, Karpenter for
nodes, Argo CD for GitOps, cert-manager and ExternalDNS for TLS and DNS, Hubble and Prometheus for visibility."""
from spinagram import Diagram
d = Diagram("EKS01", "Kubernetes platform on Amazon EKS", node_size=(230, 84),
subtitle="Cilium · Gateway API · Karpenter · Argo CD · cert-manager · ExternalDNS",
layout={"engine": "flex", "direction": "right", "gap": 28, "route": "ortho",
"area": [56, 176, 1808, 840]})
d.stylesheet("../showcase/showcase.css")
d.stylesheet("eks.css")
with d.group(id="outside", class_="bare", direction="down", gap=220):
users = d.box("Users", text="browsers, apps", icon="aws:users", class_="external", w=200, h=84)
git = d.box("GitHub", text="platform repo", icon="logos:github", class_="external", w=200, h=84)
with d.group("AWS · eu-west-1", id="region", style="aws-region", direction="right", gap=24, padding=16):
with d.group("EKS cluster · shop-prod", id="cluster", class_="cluster", direction="down", gap=12, padding=14):
with d.group("Networking", id="net", class_="lane", direction="right", gap=10):
gw = d.box("Gateway", text="Gateway API on Envoy", icon="cncf:envoy", class_="gateway")
certs = d.box("cert-manager", text="Let's Encrypt TLS", icon="cncf:cert-manager",
class_="addon")
cilium = d.box("Cilium", text="eBPF, no kube-proxy", icon="cncf:cilium", class_="cilium")
xdns = d.box("ExternalDNS", text="writes records", icon="k8s:crd", class_="addon")
with d.group("namespace: shop", id="ns-shop", class_="lane ns", direction="right", gap=10):
route = d.box("HTTPRoute", text="/api and /web", icon="k8s:crd", class_="gateway")
svc = d.box("Service", text="ClusterIP", icon="k8s:svc", class_="workload")
pods = d.box("Shop", text="6 pods", icon="k8s:pod", class_="workload")
with d.group("Platform", id="platform", class_="lane", direction="right", gap=10):
argo = d.box("Argo CD", text="GitOps sync", icon="cncf:argo", class_="addon")
karp = d.box("Karpenter", text="right-sized nodes", icon="aws:amazon-ec2-auto-scaling",
class_="addon")
hubble = d.box("Hubble", text="flows, service map", icon="cncf:cilium", class_="cilium")
prom = d.box("Prometheus", text="and Grafana", icon="cncf:prometheus", class_="addon")
with d.group("Managed services", id="aws", class_="services", direction="down", gap=40):
dns = d.box("Route 53", text="public zone", icon="aws:route53", class_="aws-svc")
db = d.box("Aurora", text="PostgreSQL", icon="aws:amazon-aurora", class_="aws-svc")
ecr = d.box("ECR", text="signed images", icon="aws:elastic-container-registry", class_="aws-svc")
users.to(gw, "HTTPS via NLB")
gw.to(route, "routes")
route >> svc >> pods
pods.to(db, "SQL")
certs.to(gw, class_="control")
xdns.to(dns, "records", class_="control")
git.to(argo, "sync", class_="control")
d.add_legend({".gateway": "Gateway API", ".cilium": "Cilium", ".addon": "Platform add-on",
".workload": "Workload", ".aws-svc": "AWS service", "edge.control": "Control or sync"},
position="bottom")
How Gateway API routes a request
An architecture and a flowchart on one slide: the HTTPRoute's rules are drawn as a decision flow
(/api/*, then a canary header), and each outcome points at the Service it selects. The flowchart is a region
inside the cluster group: with d.flowchart("HTTPRoute rules", direction="right") as fc:.
"""Gateway API on EKS with Cilium: the AWS NLB hands traffic to Cilium's Gateway, an HTTPRoute decides the
backend (shown as a flowchart region), and a Cilium network policy lets only the Gateway reach the pods."""
from spinagram import Diagram
d = Diagram("EKS02", "Gateway API routing with Cilium", node_size=(230, 84),
subtitle="Architecture and the HTTPRoute's rules as a flowchart, on one slide",
layout={"engine": "flex", "direction": "down", "gap": 30, "route": "ortho",
"area": [56, 176, 1808, 840]})
d.stylesheet("../showcase/showcase.css")
d.stylesheet("eks.css")
with d.group(id="edge", class_="bare", direction="right", gap=60):
nlb = d.box("NLB", text="AWS LB Controller", icon="aws:nlb", class_="aws-svc")
dns = d.box("Route 53", text="shop.example.com", icon="aws:route53", class_="aws-svc")
with d.group("EKS cluster · shop-prod", id="cluster", class_="cluster", direction="right", gap=26, padding=16):
with d.group("Gateway API", id="gwapi", class_="lane", direction="down", gap=18):
gwc = d.box("GatewayClass", text="io.cilium/gateway", icon="cncf:cilium", class_="cilium")
gw = d.box("Gateway", text=":443, TLS by cert-manager", icon="cncf:envoy", class_="gateway", id="gateway")
route = d.box("HTTPRoute", text="shop-route", icon="k8s:crd", class_="gateway")
with d.flowchart("HTTPRoute rules", id="rules", class_="flow", direction="right") as fc:
req = fc.start("Request")
api = fc.decision("/api/*?")
canary = fc.decision("x-canary?")
v2 = fc.process("api v2")
v1 = fc.process("api v1")
web = fc.process("web")
req >> api
api.yes(canary)
api.no(web)
canary.yes(v2)
canary.no(v1)
with d.group("namespace: shop", id="ns-shop", class_="lane ns", direction="down", gap=18):
svc2 = d.box("api-v2", text="Service · 10 %", icon="k8s:svc", class_="workload")
svc1 = d.box("api-v1", text="Service · 90 %", icon="k8s:svc", class_="workload")
svcw = d.box("web", text="Service", icon="k8s:svc", class_="workload")
pol = d.box("Policy", text="Gateway only; Hubble logs drops", icon="k8s:netpol", class_="cilium")
dns.to(nlb, "alias", class_="control")
nlb.to(gw, "TCP :443")
gwc >> gw >> route
route.to(req, "evaluates", class_="link")
v2.to(svc2, class_="link")
v1.to(svc1, class_="link")
web.to(svcw, class_="link")
How a change ships
GitOps as an activity with swimlanes: GitHub Actions builds, signs and pushes; Argo CD detects the new tag and syncs; Karpenter and the rolling update run in parallel; Prometheus gates the result and Argo CD rolls back on errors.
"""GitOps delivery to EKS as an activity with swimlanes: GitHub Actions builds and signs the image, Argo CD
syncs the change, Karpenter adds capacity while the rollout runs, and Prometheus gates the result."""
from spinagram import Activity
act = Activity("EKS03", "Shipping a change with GitOps", node_size=(168, 62),
subtitle="GitHub Actions · ECR · Argo CD · Karpenter · Prometheus, as swimlanes",
direction="right", category="Kubernetes", tags=["aws", "kubernetes", "gitops"],
layout={"engine": "layered", "direction": "right", "rank_gap": 34, "gap": 22})
act.stylesheet("gitops.css")
with act.sequence():
with act.lane("GitHub Actions"):
act.start()
act.action("Build and sign image")
act.send("Push to ECR")
with act.lane("Argo CD"):
act.receive("Detect new tag")
act.action("Sync manifests")
with act.lane("EKS cluster"):
with act.parallel() as par:
with par.branch():
act.action("Karpenter adds nodes")
with par.branch():
act.action("Rolling update")
with act.lane("Prometheus"):
check = act.decision("Error rate OK?")
with act.lane("Prometheus"):
done = act.end()
with act.lane("Argo CD"):
rollback = act.action("Roll back", id="rollback")
check.to(done, "yes")
check.to(rollback, "no")
The shared stylesheet
One file styles both architecture slides: Cilium yellow, Gateway API teal, add-ons violet, AWS orange.
/* Kubernetes on EKS: a dark cluster inside the AWS region, Cilium yellow, Gateway API teal. */
:root { --night: #0B1220; --panel: #131C2E; --rim: #2A3954; --frost: #F8FAFC; --mist: #94A3B8;
--cilium: #F8C517; --gateway: #2DD4BF; --addon: #A78BFA; --aws: #FF9900; --k8s: #326CE5; }
group.cluster { fill: var(--night); fill-opacity: 1; opacity: 1; stroke: var(--k8s); stroke-width: 2;
border-radius: 20px; box-shadow: 0 18px 36px rgba(11, 18, 32, 0.35); }
group.cluster::label { color: #93C5FD; }
group.lane { fill: #0F1A30; fill-opacity: 1; stroke: var(--rim); border-radius: 14px; }
group.lane::label { color: var(--mist); }
group.ns { stroke: var(--k8s); stroke-dasharray: 8 6; }
group.bare { fill-opacity: 0; stroke-width: 0; box-shadow: none; }
group.cluster node { fill: var(--panel); stroke: var(--rim); box-shadow: 0 8px 18px rgba(0, 0, 0, 0.45); }
group.cluster node::title { color: var(--frost); font-size: 17px; }
group.cluster node::body { color: var(--mist); }
.cilium { stroke: var(--cilium); stroke-width: 2.5; }
.gateway { stroke: var(--gateway); stroke-width: 2.5; }
.addon { stroke: var(--addon); }
.workload { stroke: var(--k8s); stroke-width: 2.5; }
.aws-svc { stroke: var(--aws); }
#gateway { box-shadow: 0 0 24px rgba(45, 212, 191, 0.45); }
edge { stroke: #64748B; stroke-width: 2; }
edge.control { stroke: var(--addon); stroke-dasharray: 6 5; }
edge::label { color: #475569; }
group.services { fill: #FFF7ED; fill-opacity: 1; stroke: var(--aws); border-radius: 16px; }
group.services::label { color: #C2410C; }
legend { fill: var(--night); stroke: var(--k8s); stroke-width: 2; }
legend::title { color: #93C5FD; }
legend::body { color: var(--frost); }
/* Flowchart regions inside the cluster */
group.flow { fill: #0F1A30; fill-opacity: 1; stroke: var(--gateway); stroke-dasharray: 4 4; border-radius: 14px; }
group.flow::label { color: var(--gateway); }
group.flow node { stroke: var(--gateway); }
edge.link { stroke: var(--gateway); stroke-width: 2.5; }
Make it yours
- Swap projects, keep the layout. Istio instead of Cilium is
icon="cncf:istio"; Flux instead of Argo CD isicon="cncf:flux"; KEDA, Kyverno, Falco, Velero and OpenTelemetry are incncftoo. - No logo for a project? Use the Kubernetes object it creates (
k8s:crd,k8s:ing,k8s:netpol) or a local SVG:icon="logos/karpenter.svg". - Ids must be unique across groups and boxes. A group with
id="shop"and a box titled "Shop" collide, so give namespace groups ids likens-shop.
spinagram serve examples/kubernetes/