Networking and Cilium
Cilium runs the whole network in a Syself Autopilot cluster. It gives each pod an address, spreads traffic across every Service, enforces your network policy, and runs a default-deny firewall on each node's own services. Cilium uses eBPF to run its networking logic inside the Linux kernel.
Most platforms split this work across separate tools: a CNI to connect pods across nodes (the container network interface, in Kubernetes terms), a load balancer to spread traffic across the pods behind a Service, a cloud firewall to guard the nodes, and often a separate policy engine on top. Cilium does all of it in one layer. Each separate tool has its own configuration, so when two of them disagree about a packet you have to check each one to find where it dropped.
Syself ships and runs Cilium as part of the tested cluster component stack. What Cilium can do depends on how Kubernetes handles networking, which kernel modules the node loads, and what the OS underneath provides, so tuning Cilium settings alone is not enough. Syself integrates and tests them together, pins the version, and upgrades Cilium with the rest of the cluster stack. You write policy on top and never install or upgrade Cilium yourself.
The network jobs Cilium handles#
Cilium does four network jobs in one kernel layer. The diagram and table below name each one.
flowchart TB
client["Internet client"] --> lb["Hetzner Cloud load balancer<br/>public IPv4 and IPv6 at the edge"]
lb -- "reaches nodes over IPv4" --> cilium
subgraph node["Every node"]
cilium["Cilium: one layer in the kernel"]
cilium --> cni["Pod network: one flat network<br/>across all nodes"]
cilium --> kpr["Service load balancing in the kernel,<br/>no separate component"]
cilium --> hf["Host firewall: default-deny,<br/>cluster members only"]
cilium --> pol["Policy engine: address and port rules<br/>in the kernel; request-level rules rejected today"]
end| Job | What Cilium does | Why it matters |
|---|---|---|
| Pod network | Gives each pod an address and connects pods across nodes, wrapping pod traffic so it can travel between machines | Every pod can reach every other pod without you setting up routes between nodes |
| Service load balancing | Spreads all Service traffic across the right pods in the kernel, so traffic spreads evenly and existing connections stay on the same pod when pods are added or removed | You run no separate load balancer for traffic inside the cluster |
| Host firewall | Default-deny allow-list on each node's own services, enforced in the kernel | The nodes are protected without a cloud firewall in front of them |
| Policy engine | Enforces network policy on workload identity (labels and selectors), not on addresses | Your rules keep working when pods move, because they select by label, not address |
| Bandwidth manager | Smooths the rate at which a pod sends traffic out | You can cap a pod's outbound rate without changing the pod |
| Egress gateway | Sends all outbound traffic from a namespace out through one chosen node | Outbound traffic leaves from one known address, which some external services require |
| Hubble | Records network flows, policy drops, and connection metrics | You can see traffic, drops, and metrics across the cluster |
Important
The Hubble UI has no authentication of its own. Reach it with kubectl port-forward, or put it behind an authenticated ingress. Do not expose it with a plain type: LoadBalancer Service. The safe path is shown in See Network Flows with Hubble .
The host firewall#
Syself Autopilot nodes have public addresses and no private network in front of them, so each node runs its own firewall. Cilium enforces a default-deny allow-list on the node's own services, in the kernel, driven by a clusterwide policy that Syself manages. Anything not on the list is dropped before any process sees the packet, including a scanner probing the node from the internet.
A few node services are open. Each one accepts traffic only from specific cluster members, matched by identity rather than by address:
- The kubelet, the reverse tunnel that every node dials out to the control plane, and the cluster's database accept traffic from the other nodes in the same cluster and from the node itself.
- The metrics collector may reach the kubelet.
- Hubble may read flow data.
No other pods may reach a node service.
Everything else listens only on the node's loopback address, reachable from the node itself. The full allow-list, service by service, is in Ports and Listeners .
The host firewall protects the node, not your workloads. Your pods can talk to each other freely until you write a network policy. The platform does not put a default-deny between your services, because only you know which of them should talk. Segmenting your namespaces is your job, and Segment with Network Policies walks through it.
The address plan#
The cluster hands out two kinds of internal address, one to every pod and one to every Service, from ranges fixed when the cluster is created. Each node gets its own block of pod addresses, and that split is what caps how many nodes a cluster can hold: a bigger per-node block means fewer nodes fit in the same total. The per-node pod limit is hard, and system pods (one Cilium agent per node, the storage plugin, the GPU plugin) count against it, so real workload capacity is a little lower. The exact bounds are in node resources and limits .
Both ranges come from a block of addresses reserved for carrier-grade NAT, which almost nothing on the internet uses, so the cluster's internal addresses will not collide with the private ranges your company already runs. Cilium treats every address in these ranges as in-cluster, so if a real external host you need to reach uses one of them, traffic to it never leaves the node.
Warning
The pod and Service address ranges are fixed at cluster creation and cannot be changed on a running cluster. If your network plan needs different ranges, decide before you create the cluster; afterwards the only path is a new cluster. The cluster variables reference has the fixed network settings.
Load balancers#
A Service with type: LoadBalancer gets a Hetzner Cloud load balancer with a public IPv4 and a public IPv6 address, pointed at your nodes. Both cloud and bare-metal nodes are valid targets: the load balancer reaches each one on its public IPv4 address, so a bare-metal-only cluster needs no cloud pool to use load balancers.
Inside the cluster it is IPv4 only. The IPv6 address works inbound at the edge: IPv6 clients can reach your Services, but a pod cannot open a connection to an IPv6-only destination, and full dual-stack pod networking is not available today.
By default the path runs load balancer, then any node, then SNAT, then the pod, so the pod sees a node address instead of the client's. If you need the real client IP, set externalTrafficPolicy: Local. The client's real IP is then preserved, but traffic only reaches nodes that run a matching pod, so spreading is less even. Check it against your pod topology before you rely on it.
The load balancer's annotations and its hairpinning behavior are covered in Configure a Load Balancer . When a path through the load balancer or the overlay breaks, Debug Node Networking walks through finding where the packet stops.
Limits on request-level policy#
Request-level rules are rejected today. A request-level rule looks inside a request, for example at the web address it asks for. The kernel cannot read inside a request, so these rules need a helper proxy on the node, and the platform rejects them when you submit one.
Rules about addresses and ports are the other kind, and they work. They match on workload identity plus a port or address range, run entirely in the kernel, and are never rejected. Letting pods reach the DNS service on its port is one of these rules. Naming allowed domains by hostname (toFQDN), and HTTP, gRPC, or Kafka rules, are request-level and rejected. Control egress with FQDN policy covers which rule families apply where, why the DNS and HTTP gates exist, and how to spot a policy that trips this limit.
For now, write policy as workload identity plus port. For traffic leaving the cluster, allow it by address range.
When two rules overlap, the result is deny. There is no priority field to order them, so you cannot write a broad deny and then allow one thing above it. Write the exception as its own selector instead.
If you need request-level HTTP control (allow only GET /api, for example), that belongs in a service mesh. Istio in sidecar mode is the supported path, and ambient mode is not; see Choose a service mesh .
Encrypting pod traffic#
The cluster is secure without encrypting pod-to-pod traffic on the wire. The traffic that matters most already uses TLS at the connection level: the reverse tunnel, the kubelet, and the cluster's database all use mTLS. Between your own pods, traffic on Hetzner runs across the provider's network between servers you already own.
If you still want pod-to-pod encryption, the recommended path is mTLS through a service mesh. It is less invasive than encrypting at the node, and it gives you auditability, L7 metrics, and reliability.
WireGuard stays available as a node-level option. Cilium can encrypt pod-to-pod traffic between nodes, with a separate key per node. It does not cover traffic between pods on the same node, which never leaves the host and so is not encrypted. Cilium also has a separate host-to-host encryption feature, which is not the same thing. Encrypt Pod Traffic with WireGuard shows how to turn it on.
Related#
This network runs on an immutable OS .
Minimal provider dependency explains the decision to run without a private network. The security model makes the case for why a node is safe on the public internet. The storage model covers where your data lives on top of this network.
Platform Components: KubeGate, syself-agent, and the Tunnel
The Syself-built pieces that let a Kubernetes node run safely on the public internet: KubeGate in front of a loopback-only API server, the one syself-agent binary, and a reverse mTLS tunnel that nodes dial outward.
The Storage Model
Syself Autopilot ships one storage backend, network-attached Hetzner Cloud volumes; local disks through TopoLVM are fast but pinned to one machine, so a database on local disk cannot move off that machine.