ContainerDays 2026 Hamburg — Trip Report
soulTec AG Representatives
- Tobias Wälti — tobias.waelti@soultec.ch
- Louis Baumann — louis.baumann@soultec.ch
Table of Contents
- Executive Summary
- Keynote Highlights
- Featured Projects & Technologies
- Technical Deep Dives
- Reliability & Operations
- Glossary
- References & Links
Executive Summary
| Event | ContainerDays 2026, Hamburg |
| Attendees from soulTec | 2 (Tobias Wälti, Louis Baumann) |
| Core themes | GitOps at enterprise scale, multi-tenant SDN networking, autonomous control planes, RBAC/OIDC hardening, observability maturity, Go internals, Gateway API |
| Headline moment | Wide-ranging live Q&A with Kelsey Hightower |
Key takeaways for the business:
- GitOps is now a change-management problem, not just a tooling problem. The clearest signal from both the Infrahub and the Flux adoption talks: the technical reconciliation loop is the easy part — getting an enterprise to trust and adopt it is the hard part, and needs to be sold with numbers and narratives, not just YAML.
- Kubernetes networking is absorbing SDN. Kube-OVN’s approach — bringing VPCs, distributed/centralized gateways, security groups, and elastic IPs natively into the cluster — shows the CNI layer catching up to what enterprises already expect from traditional data-center networking.
- Control-plane operations are becoming a product of their own. The “AutoPiloting Kubernetes” talk and the regulated-environments talk both point the same direction: treat the control plane itself as a reconciled, disposable object (via Cluster API), not a pet you SSH into.
- Operational maturity (RBAC, OIDC, observability, operator lifecycle) is the recurring pain point at scale, reinforcing that platform engineering investment pays off primarily in maintainability, not in day-one velocity.
- Kelsey Hightower’s read on Kubernetes is refreshingly unsentimental: it’s the best tool we have, not the best tool we’ll ever have — and its declarative API is simultaneously its greatest strength and its biggest weakness.
Keynote Highlights
Kelsey Hightower – Chat & Interactive Q&A
- Location: Room 1: Main Track
- Speaker: Kelsey Hightower
An open, interactive Q&A rather than a scripted keynote. It ranged widely — a distillation of the topics covered:
Main weakness of Kubernetes. “We should have told you not to use it for everything — don’t tattoo Kubernetes on your body.” The core idea is control loops reconciling declarative input, but not everything is declarative in practice: it’s a wish, and someone can always log in and manually manipulate live state. That manual interaction breaks the control loop, and it stays broken. In his words: Kubernetes is strong because it has a declarative API, and weak for exactly the same reason.
Flux or Argo? “I’d choose whichever has the bigger user group — but in the end it doesn’t really matter.”
On the Gateway API. He’s happy with where the standard landed. In his view, the Ingress object was a design mistake from the start: it never captured everything an ingress controller actually needs to express, so everything got crammed into annotations, and every controller ended up interpreting them differently. You will never fit every production requirement into a single Kubernetes API spec.
What will replace Kubernetes? No one is seriously working on a genuinely new approach to managing workloads yet. Kubernetes isn’t the best we’ll ever do here — we need someone, somewhere, thinking hard about what comes after it, folding the lessons learned from running Kubernetes into a new design.
Virtualization on top of Kubernetes. “Kubernetes is the hammer, virtualization is just another nail we hammer into the brick.” Virtualization isn’t a data-plane concern, it’s a control-plane concern — so using the Kubernetes API to manage the virtualization plane is reasonable. Just don’t expect it to be painless.
Zero token architecture. A pointed joke at current AI usage patterns: people now brag about burning tokens on tasks that accomplish nothing. We used to cache expensive queries — why did that discipline disappear? A “zero token architecture” means thinking hard up front: spending 10k tokens to stand up an architecture or generate passwords repeatedly is simply wasteful. Burn the tokens once, cache the result, and run cheaply forever. Use AI as a tool — don’t let it think for you.
What’s the point in becoming a really good programmer? AI is just a checkpoint on prior human work, so it will only ever be about as good as an average programmer. If we stop trying to improve ourselves, we never will — that’s the actual stakes.
Is etcd the right key-value store for Kubernetes? Probably a mistake, but not for the obvious reason: there are far more reads against it than anything else, and those queries would be dramatically faster with proper indexing. etcd may well be the right KV store, but maybe not the right datastore overall — something like PostgreSQL might have served Kubernetes better in the long run.
Featured Projects & Technologies
From “No Time For GitOps” To Enterprise Adoption: Selling Flux The Human Way
- Location: Room 3: Cloud Native Experience & Golang Track
- Speakers: Lucas Hornung, Christian Matthaei
- Target audience: Introductory and overview
This talk wasn’t about Flux’s mechanics — it was about the organizational work of getting a GitOps tool adopted inside a large, risk-averse enterprise. Five lessons stood out:
| # | Lesson | Why it matters |
|---|---|---|
| 1 | Be visible | A 2–3 minute pitch is often enough to move acceptance meaningfully — lead with why, not how. |
| 2 | Talk to people — and listen | Asking for advice surfaces an outside-in perspective you’d otherwise miss, and builds relationships along the way. |
| 3 | Make your value measurable | Business cares about numbers — quantify the financial impact, not just the technical elegance. |
| 4 | Create narratives | Emotional buy-in comes from making people part of the solution, not just recipients of it. |
| 5 | Make people feel the (hidden) pain | Use relatable examples — abstract risk doesn’t move people, felt pain does. |
“Don’t show people music notes — play the music.”
Technical Deep Dives
Cloud Native Network Observability: Treating Routers like Kubernetes Resources
- Location: Room 3: Cloud Native Experience & Golang Track
- Speaker: Florian Löhden
- Target audience: Intermediate
The core idea: treat network devices (routers) as Kubernetes-style resources, reconciled continuously by a controller loop rather than pushed to imperatively.
Key principles from the talk:
- Use Kubernetes-style controller reconciliation to keep on-cluster resources continuously up to date, rather than one-shot provisioning.
- Use secrets, not hard-coded passwords in YAML — a basic but frequently-skipped security hygiene point.
- Use Flux to drive the GitOps reconciliation loop.
- Shift the scope of thinking from “GitOps for the cluster” to Infrahub composition for the network itself.
The data model that emerged:
- Interfaces are unique per device.
- The Infrahub relationship between a device and its interfaces must be mandatory.
- Making that link mandatory is what makes the model immutable (and therefore safe to reconcile).
- Interfaces are modelled as components on the device, not standalone objects.
Two design decisions worth calling out:
- Declarative rendering over imperative deletion. If a resource is removed from the desired state, it’s simply not re-applied rather than explicitly deleted — this guarantees idempotency.
- No event sourcing. The speaker explicitly warned against an event-sourcing approach here: if events or webhooks fire multiple times (which they will, in practice), you end up reconciling a mess. Declarative state beats event replay for this use case.
Pipeline architecture:
Infrahub holds the source of truth for network intent; a relay pushes that through a CI rendering step, which commits the rendered config to Git. Flux then reconciles that Git state onto both the cluster and the underlying physical machines — a nice illustration of GitOps extending past the cluster boundary into infrastructure it doesn’t natively manage.
VPCs for Everyone: Multi-Tenancy and Advanced Networking with Kube-OVN
-
- Location: MS Bleichen: Mixed Track
- Speaker: Jan Bruder
- Target audience: Intermediate
The most technically dense networking session of the conference. Covers how Kube-OVN brings traditional SDN concepts — VPCs, distributed/centralized gateways, ACLs, elastic IPs — natively into Kubernetes networking.
Why this matters now
Kubernetes is evolving along three converging axes:
- Universal control plane → becoming the universal platform for hosting data, not just stateless apps.
- Cloud-native virtualization → running VMs natively alongside containers.
- Enterprise multi-tenancy → multiple tenants/customers sharing one cluster with hard isolation guarantees.
That evolution runs straight into a structural mismatch:
- Enterprise multi-tenancy and legacy virtualization assumptions collide with Kubernetes’ traditionally flat network model.
- Overlapping IP ranges and requirements for fixed IPs clash with core Kubernetes networking concepts.
- Bolting an existing SDN solution onto a cluster from the outside has proven difficult in practice — the conclusion is that SDN capability needs to be integrated into Kubernetes itself, not layered on top.
What Kube-OVN is
Kube-OVN is a CNI that combines the capabilities of a traditional SDN with cloud-native infrastructure, built on:
- OVS (Open vSwitch) — a programmable, multi-layer virtual switch (originally from OpenStack) that operates directly in the Linux kernel, replacing basic Linux bridges and slow iptables rule chains.
- OVN (Open Virtual Network) — a higher-level network abstraction on top of OVS: logical switches and logical routers that decouple the virtual network topology from the physical network.
Subnet models
Default subnet — the out-of-the-box CNI behavior. All pods are automatically added to this subnet and share the same gateway configuration, using distributed NAT for external traffic. Migrating from another CNI, you won’t notice much difference here.
Join subnet — enables hosts to have subnet configurations of their own; it configures a distributed gateway and enables SNAT. The Subnet custom resource (kubeovn.io/v1) is the object backing this:
apiVersion: kubeovn.io/v1
kind: Subnet
metadata:
name: join
spec:
vpc: ovn-cluster
protocol: IPv4
cidrBlock: 100.64.0.0/16
gateway: 100.64.0.1
excludeIps:
- 100.64.0.1
natOutgoing: false
private: false
gatewayType: distributed
Illustrative, reconstructed from session notes — verify exact field names/defaults against the Kube-OVN version you deploy.
Distributed gateway — each node acts as a gateway for the pods running on it. Packets flow from ovn0 into the host network stack, and behavior mirrors a classic CNI: all external connections are NAT’d and traffic appears as node traffic by default.
apiVersion: kubeovn.io/v1
kind: Subnet
metadata:
name: distributed-subnet
spec:
vpc: ovn-cluster
cidrBlock: 10.16.0.0/16
gateway: 10.16.0.1
gatewayType: distributed
natOutgoing: true
Centralized gateway — all traffic is routed through a selected set of gateway nodes instead of being distributed across every node; traffic from other nodes is directed to those gateway nodes. You can choose which nodes are externally visible — conceptually similar to designated external hosts in OpenShift.
apiVersion: kubeovn.io/v1
kind: Subnet
metadata:
name: centralized-subnet
spec:
vpc: ovn-cluster
cidrBlock: 10.20.0.0/16
gateway: 10.20.0.1
gatewayType: centralized
gatewayNode: "node-a,node-b"
natOutgoing: true
Access control mechanisms
Kube-OVN layers three distinct access-control mechanisms, each with a different scope:
| Mechanism | Scope | Default behavior | Notes |
|---|---|---|---|
| Kubernetes NetworkPolicy | Namespace | Default deny, strictly additive | Standard k8s-native policy object |
| Subnet ACL | Namespace (via subnet spec) | Configured directly on the Subnet resource |
Fine-grained allow/deny rules using the well-known OVN ACL ruleset |
| Security Group | Cluster-wide | Communication unrestricted by default | Match-based allow/drop policies (port, protocol, address, or group); pods opt in via the security_groups annotation; a “lazy man’s” private: true flag simply blocks all pod communication |
Elastic IP
Elastic IP lets you bind an external IP directly to a specific pod, making that pod individually addressable and exposed on that IP. This requires cluster nodes to have a dedicated physical interface connected to the underlay network.
apiVersion: v1
kind: Pod
metadata:
name: public-facing-app
annotations:
ovn.kubernetes.io/eip: "203.0.113.10"
ovn.kubernetes.io/eip-external-subnet: "external-net"
spec:
containers:
- name: app
image: nginx:latest
Illustrative — Kube-OVN also exposes elastic IPs as first-class
OvnEip/IptablesEIPcustom resources for use cases beyond a single pod annotation; confirm the right primitive for your Kube-OVN version.
Multiple VPCs
When multiple VPCs are in play, Kube-OVN can enforce strict multi-tenancy: hard network separation between customers or tenants, with the option to bridge specific VPCs together when needed, and QoS support across them.
Building a VPC — end-to-end flow:
Step 1 — Initialize tenant. Create the Vpc object that scopes everything that follows:
apiVersion: kubeovn.io/v1
kind: Vpc
metadata:
name: tenant-a
spec:
namespaces:
- tenant-a-ns
Step 2 — Map subnets to VPC. Attach one or more Subnet resources to that VPC:
apiVersion: kubeovn.io/v1
kind: Subnet
metadata:
name: tenant-a-subnet
spec:
vpc: tenant-a
cidrBlock: 10.30.0.0/16
gateway: 10.30.0.1
Step 3 — Deploy VPC NAT gateway. Give the tenant VPC outbound/inbound connectivity via a dedicated NAT gateway:
apiVersion: kubeovn.io/v1
kind: VpcNatGateway
metadata:
name: tenant-a-nat-gw
spec:
vpc: tenant-a
subnet: tenant-a-subnet
lanIp: 10.30.0.254
Micro-segmentation workflow (completed from the session’s whiteboard sketch):
apiVersion: kubeovn.io/v1
kind: SecurityGroup
metadata:
name: web-tier-sg
spec:
ingressRules:
- ipVersion: 4
protocol: tcp
priority: 100
remoteType: address
remoteAddress: 10.30.0.0/16
port: 443
policy: allow
egressRules:
- ipVersion: 4
protocol: tcp
priority: 100
remoteType: address
remoteAddress: 0.0.0.0/0
policy: allow
---
# attach to the workload
metadata:
annotations:
ovn.kubernetes.io/security_groups: web-tier-sg
Go’s Concurrency and Parallelism Inside Containers
- Location: Room 3: Cloud Native Experience & Golang Track
- Speaker: Rick Rackow
- Target audience: Intermediate
“Concurrency is not parallelism. […] Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once.” — Rob Pike, “Concurrency is not Parallelism” (2012)
That distinction was the spine of the talk: do goroutines automatically mean faster code? Not necessarily.
Concurrency is about structure: the Go runtime schedules goroutines onto whatever CPU slots are available. With enough CPUs, concurrent work also happens to run in parallel — but concurrency itself is just the ability to deal with multiple things being in flight at once, not a guarantee they run simultaneously. Limiting the CPUs available to a program can actually produce more meaningful, comparable benchmark output, since it removes accidental parallelism from the picture. A sync.WaitGroup is essential here — without one, the main goroutine can exit before the goroutines it launched have finished.
Parallelism is the stronger claim: multiple goroutines genuinely executing at the same time, on multiple CPUs. GOMAXPROCS controls how many OS threads Go will use to run goroutines in parallel, and by default it’s set to the number of CPUs available to the process — which, in a container, can be passed through as an environment variable from docker run/the pod spec down into the Go runtime.
Example — respecting GOMAXPROCS explicitly and waiting on goroutines:
package main
import (
"fmt"
"os"
"runtime"
"strconv"
"sync"
)
func main() {
if v := os.Getenv("GOMAXPROCS"); v != "" {
if n, err := strconv.Atoi(v); err == nil {
runtime.GOMAXPROCS(n)
}
}
fmt.Printf("running with GOMAXPROCS=%d\n", runtime.GOMAXPROCS(0))
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("goroutine %d running\n", id)
}(i)
}
wg.Wait()
}
docker run --cpus="2" -e GOMAXPROCS=2 my-go-app
Aside: the speaker ran into genuinely funny live trouble with minikube, image tags, and Docker images mid-demo — chaotic, but a fun watch.
Gateway API: The Illustrated Guide to the Future of Kubernetes Networking
- Location: MS Bleichen: Mixed Track
- Speaker: Aleksandr Rybolovlev
- Target audience: Introductory and overview
Gateway API is positioned as the successor to Ingress — but not a drop-in replacement, and not because Ingress was “wrong,” rather because it solves a fundamentally different problem: separating the concerns of administrators and application developers.
Why Ingress fell short
Ingress gives you HTTPS routing behind a single primary resource, but it requires an ingress controller to actually implement the API contract — and that contract turned out to be too thin. As application requirements grew, teams reached for annotations to express anything Ingress itself couldn’t, and every controller ended up with its own annotation dialect. The result: annotations that were never portable between controllers, and an API object with implicit ownership and controller-specific configuration bolted on from the outside.
What Gateway API changes
| Ingress | Gateway API | |
|---|---|---|
| Routing resources | One primary resource | Multiple purpose-built resources |
| Ownership model | Implicit | Explicit ownership and attachment |
| Extensibility | Per-controller annotations | Role-oriented, structured, portable |
| Audience | Blurs admin/app-developer concerns | Splits them cleanly by resource |
Both share the same general purpose: route traffic to the right backend. The real shift is that networking is now explicitly a shared responsibility:
- Infrastructure provider — decides which implementation runs.
- Cluster operator — decides where and how it’s deployed.
- Application developer — decides how traffic reaches their application.
Core components
- GatewayClass — cluster-scoped; references a controller by name. A cluster can have multiple GatewayClasses backed by multiple controllers.
- Gateway — references a GatewayClass and defines listeners: where to listen, and on what hostname. A Gateway can define up to 64 listeners and acts as the actual entrypoint; it can also restrict which routes are allowed to attach to it.
- Routes (e.g.
HTTPRoute) — belong to applications and attach to a Gateway viaparentRef. Routes carry routing rules much like Ingress did, but critically don’t define the entrypoint themselves — that stays owned by the Gateway. Mismatched protocols will break routing: aTCPRouteattached to a Gateway listener configured for HTTPS simply won’t work.
By default, a route cannot reach a backend Service in a different namespace. Cross-namespace access requires an explicit ReferenceGrant, which must live in the target (referenced) namespace:
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-route-to-backend
namespace: backend-ns
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: frontend-ns
to:
- group: ""
kind: Service
Status reporting
One of the practical upgrades over Ingress: Gateway API objects carry rich, structured status — Accepted, ResolvedRefs, and a Reason for each condition.
Route status example:
status:
parents:
- parentRef:
name: public-gateway
controllerName: example.com/gateway-controller
conditions:
- type: Accepted
status: "True"
reason: Accepted
message: "Route accepted by parent Gateway"
- type: ResolvedRefs
status: "True"
reason: ResolvedRefs
message: "All references resolved successfully"
Gateway status example:
status:
addresses:
- type: IPAddress
value: 203.0.113.10
conditions:
- type: Accepted
status: "True"
reason: Accepted
- type: Programmed
status: "True"
reason: Programmed
listeners:
- name: https
attachedRoutes: 3
conditions:
- type: Accepted
status: "True"
reason: Accepted
- type: ResolvedRefs
status: "True"
reason: ResolvedRefs
Object relationships

Gateway references its GatewayClass; HTTPRoute attaches to the Gateway via parentRef; and the route points at a backend Service, crossing namespaces only when a ReferenceGrant authorizes it.
Support levels
Gateway API defines three conformance tiers so implementations can differ without breaking portability:
| Tier | Meaning |
|---|---|
| Core | Every controller must implement this |
| Extended | May be implemented, but must behave in a well-defined way if it is |
| Implementation-specific | Every implementation is free to behave differently |
The bigger picture
As the speaker put it: the controller interprets a graph, not a flat list of objects — GatewayClass, Gateway, Route, Service, and Secret are all inputs it reconciles together into both the running data plane and the status written back onto each object.
Reliability & Operations
AutoPiloting Kubernetes: How We Built Autonomous Control Planes
- Location: Room 2: Platform Engineering Track
- Speakers: Dario Tranchitella, Gianluca Mardente
- Target audience: Expert
How cluster provisioning evolved
- Bash scripts (fully manual)
- Automation via kops and kubespray
- kubeadm
The burden of creating a cluster from scratch
- Infrastructure management
- Certificate management
- Bootstrap process
- etcd and API server monitoring
- HA and load balancing
The burden of operating thousands of clusters
At that scale, provisioning tooling alone isn’t enough — you need the control plane itself declared and reconciled, via Cluster API: use ClusterClass and ClusterTopology objects so fleets of clusters can be templated and reconciled the same declarative way as any other Kubernetes resource.
apiVersion: cluster.x-k8s.io/v1beta1
kind: ClusterClass
metadata:
name: standard-cluster-class
spec:
controlPlane:
ref:
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlaneTemplate
name: standard-control-plane
workers:
machineDeployments:
- class: default-worker
template:
bootstrap:
ref:
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
name: default-worker-bootstrap
infrastructure:
ref:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: ExampleMachineTemplate
name: default-worker-machine
---
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: workload-cluster-01
namespace: tenant-a
spec:
topology:
class: standard-cluster-class
version: v1.29.4
controlPlane:
replicas: 3
workers:
machineDeployments:
- class: default-worker
name: md-0
replicas: 3
Illustrative — simplified to show the
ClusterClass→Clustertemplating relationship discussed in the talk; verify exact fields against the Cluster API version in use.
Challenges
- Cluster lifecycle management is genuinely hard.
- Workers are cattle; the control plane historically has not been.
The fix: treat the control plane as cattle too
The control plane is just an API that reads and writes to a persistence layer (etcd). Defining it as a CRD — with an operator maintaining that CP object, and the API server exposed as a Service running inside an infrastructure cluster — turns “create a new control plane” into the same reconciliation problem as anything else on the platform.
Benefits:
- Control planes become first-class citizens of the platform.
- Fast onboarding for new clusters.
- More secure by construction (fewer bespoke, hand-run components).
- Cheaper to run at fleet scale.
Sveltos
Sveltos came up as the add-on controller used to manage what runs on those fleets of clusters:
- Supports Helm, plain YAML, Kustomize, and Carvel.
- Template-based add-on delivery.
- Agent-driven, pull-based model — deliberately chosen to sidestep firewall/connectivity problems with push-based delivery.
Worth noting: this pull-based add-on model is conceptually the same pattern soulTec already runs with ArgoCD + GitLab-based bootstrapping across several existing use cases — good validation that we’re already on the right architectural track here.
Taking Back Control: Building Cloud-Agnostic Container Platforms for Regulated Environments
- Location: Room 1: Main Track
- Speakers: Jinhong Brejnholt, Per Hedegaard Christiansen
- Target audience: Intermediate
What’s driving this
- Cost pressure
- Global politics
- AI
What “control” means in a regulated industry
- Continuity — the platform has to keep running regardless of a single vendor’s fate.
- Management — it has to be operable by your own team, not just the vendor’s.
- Exit (independence) — you need a credible path off any given vendor.
Five building blocks
| # | Building block | Why |
|---|---|---|
| 1 | Standardize the workload | A consistent workload shape is what makes portability possible in the first place. |
| 2 | Abstract the infrastructure | Network, storage, and compute all need an abstraction layer between the workload and the underlying vendor. |
| 3 | Choose portable primitives | E.g. logs written in a vendor-specific format are painful to migrate later — pick portable formats from day one. |
| 4 | Reproduce the environment periodically | Regularly rebuilding the environment from scratch proves your portability story actually works, rather than just existing on paper. |
| 5 | Reconcile the dependencies | Ongoing reconciliation, not one-off migration — dependencies drift, so the process has to be continuous. |
Case study: Saxo Bank
The Saxo Bank use case illustrated these principles in practice, organized around three pillars: control, portability, and continuity.
Cluster API is used here too, to reconcile the underlying Kubernetes clusters themselves — the same pattern as the “AutoPiloting Kubernetes” talk above, applied specifically to a regulated-industry context. The stated target architecture is hybrid — deliberately not fully cloud, not fully on-prem.
Kubernetes RBAC at Scale Without Losing Your Sanity
- Location: Room 3: Cloud Native Experience & Golang Track
- Speaker: Kevin Gimbel
- Target audience: Advanced
RBAC fundamentals (ramp-up)
- System access is based on user-assigned roles.
- Roles are managed in a central place.
- Zero trust and least privilege are design defaults, not bolted-on afterward.
- The model is application-agnostic — it doesn’t care what the resource actually does.
Role/RoleBinding are namespace-scoped; ClusterRole/ClusterRoleBinding are cluster-scoped, spanning all namespaces:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: team-a
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: team-a
subjects:
- kind: User
name: jane
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-reader
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: read-nodes-global
subjects:
- kind: Group
name: platform-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: node-reader
apiGroup: rbac.authorization.k8s.io
Managing RBAC at scale
Aggregation rules are a standard ClusterRole feature that allows dynamic aggregation of roles: you define a set of small, reusable roles, and they get merged (“shared”) into a larger aggregate role automatically. New access can be granted without touching any existing role definition — a new CRD just means a new aggregated rule, picked up automatically. This only works for ClusterRole; normal namespaced Role objects can’t aggregate.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-view-metrics
labels:
rbac.example.com/aggregate-to-monitoring: "true"
rules:
- apiGroups: ["metrics.k8s.io"]
resources: ["pods", "nodes"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.example.com/aggregate-to-monitoring: "true"
rules: [] # populated automatically by the aggregation controller
Kubernetes’ own default roles use this same mechanism. Every cluster ships with cluster-admin, admin, edit, and view, all labeled kubernetes.io/bootstrapping: rbac-defaults:
Default ClusterRole |
Purpose | Aggregation label to extend it |
|---|---|---|
cluster-admin |
Full control over every resource (superuser) | n/a |
admin |
Full admin rights within a namespace | rbac.authorization.k8s.io/aggregate-to-admin: "true" |
edit |
Read/write access to most namespaced objects | rbac.authorization.k8s.io/aggregate-to-edit: "true" |
view |
Read-only access to most namespaced objects | rbac.authorization.k8s.io/aggregate-to-view: "true" |
Add the matching label to any custom ClusterRole and it’s automatically folded into admin/edit/view cluster-wide, no direct edits required.
Federating identity with OIDC
Rather than managing users directly in Kubernetes, delegate to an identity provider (Keycloak, Zitadel, etc.): the IdP owns role/group assignment, and the cluster reads roles from the authenticated identity, mapping IdP roles/groups onto RBAC bindings.
A decoded ID token including the group claim RBAC bindings key off:
{
"iss": "https://idp.example.com/realms/soultec",
"sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"aud": "kubernetes",
"exp": 1735689600,
"iat": 1735686000,
"email": "louis.baumann@soultec.ch",
"groups": [
"platform-team",
"cluster-viewers"
]
}
Connecting the API server to OIDC
Newer Kubernetes versions configure this via a structured AuthenticationConfiguration file, pointed to with --authentication-config on kube-apiserver:
apiVersion: apiserver.config.k8s.io/v1beta1
kind: AuthenticationConfiguration
jwt:
- issuer:
url: https://idp.example.com/realms/soultec
audiences:
- kubernetes
claimMappings:
username:
claim: email
prefix: "oidc:"
groups:
claim: groups
prefix: "oidc:"
kube-apiserver \
--authentication-config=/etc/kubernetes/authentication-config.yaml \
...
On the client side, a kubeconfig entry using the kubelogin (oidc-login) plugin drives the browser-based login flow above on demand:
apiVersion: v1
kind: Config
users:
- name: oidc-user
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: kubectl
args:
- oidc-login
- get-token
- --oidc-issuer-url=https://idp.example.com/realms/soultec
- --oidc-client-id=kubernetes
- --oidc-client-secret=$(OIDC_CLIENT_SECRET)
- --oidc-extra-scope=groups
All OIDC/JWT examples above are illustrative, reconstructed from the well-established Kubernetes OIDC authentication pattern discussed in the session — verify exact fields against your cluster’s Kubernetes version and identity provider.
Glossary
| Term | Definition |
|---|---|
| Aggregated ClusterRole | A ClusterRole whose rules are automatically composed from other ClusterRole objects matched by a label selector. |
| Cluster API (CAPI) | A Kubernetes subproject for declaratively creating, upgrading, and managing the lifecycle of Kubernetes clusters themselves. |
| ClusterClass / ClusterTopology | Cluster API primitives that template a cluster’s shape so fleets of clusters can be created and reconciled consistently. |
| CNI (Container Network Interface) | The plugin standard Kubernetes uses to configure pod networking. |
| Distributed Gateway | A Kube-OVN gateway mode where every node acts as an egress gateway for the pods running on it. |
| Centralized Gateway | A Kube-OVN gateway mode where all egress traffic is routed through a designated subset of nodes. |
| Elastic IP | A Kube-OVN feature binding an external, routable IP directly to a single pod. |
| Flux | A GitOps toolkit that continuously reconciles a Kubernetes cluster’s state against a Git repository. |
| Gateway API | The Kubernetes SIG-Networking project defining the next-generation, more expressive successor to Ingress. |
| GitOps | An operational model where Git is the single source of truth and a controller continuously reconciles live state to match it. |
| GOMAXPROCS | A Go runtime setting controlling how many OS threads can execute goroutines simultaneously. |
| Idempotency | The property that applying an operation multiple times produces the same result as applying it once. |
| Infrahub | A source-of-truth and automation platform for network infrastructure, using a graph data model with typed relationships. |
| Kube-OVN | A CNI that integrates OVN/OVS-based SDN capability natively into Kubernetes. |
| NAT (Network Address Translation) | Rewriting source/destination IP addresses as traffic crosses a network boundary. |
| OIDC (OpenID Connect) | An identity layer on top of OAuth 2.0, used here to federate cluster authentication to an external identity provider. |
| Operator | A Kubernetes controller pattern that encodes operational knowledge for managing a specific application or resource. |
| OTel (OpenTelemetry) | A vendor-neutral standard and toolset for generating, collecting, and exporting traces, metrics, and logs. |
| OVN (Open Virtual Network) | A higher-level SDN control plane on top of OVS providing logical switches/routers decoupled from physical topology. |
| OVS (Open vSwitch) | A programmable, multi-layer virtual switch operating in the Linux kernel; originally developed for OpenStack. |
| RBAC (Role-Based Access Control) | The Kubernetes authorization model that grants permissions based on roles bound to users or service accounts. |
| ReferenceGrant | A Gateway API object that authorizes a route in one namespace to reference a backend Service in another. |
| Reconciliation Loop | A controller pattern that continuously compares desired vs. actual state and converges the two. |
| Security Group | A cluster-scoped Kube-OVN access-control object applied to pods via annotation, independent of subnet/namespace boundaries. |
| SDN (Software-Defined Networking) | Decoupling network control logic from the underlying physical hardware, enabling programmatic network management. |
| Sveltos | A Kubernetes add-on controller that installs and manages Helm/YAML/Kustomize/Carvel resources across fleets of clusters, pull-based. |
| Subnet ACL | Namespace-scoped, fine-grained allow/deny rules configured directly on a Kube-OVN Subnet resource. |
| Talos Linux | An immutable, API-managed Linux distribution purpose-built for running Kubernetes. |
| VPC (Virtual Private Cloud) | An isolated, tenant-scoped virtual network within a shared cluster, used here to enforce multi-tenancy. |
| WaitGroup | A Go synchronization primitive (sync.WaitGroup) used to block until a collection of goroutines finish. |
| Zero Token Architecture | Kelsey Hightower’s term for caching the result of expensive AI/token-driven setup work instead of repeatedly paying its cost. |
References & Links
Conference
Projects & Tools mentioned
- Kube-OVN (GitHub)
- Flux (fluxcd.io)
- Talos Linux
- Kubernetes Gateway API
- Cluster API (cluster-api.sigs.k8s.io)
Report prepared by soulTec AG attendees Tobias Wälti and Louis Baumann, covering ContainerDays 2026, Hamburg, 2–4 September 2026.







Leave a Reply