Tuning Guide
This guide helps you optimize a Cilium installation for optimal performance.
Recommendation
The default out of the box deployment of Cilium is focused on maximum compatibility rather than most optimal performance. If you are a performance-conscious user, here are the recommended settings for operating Cilium to get the best out of your setup.
Note
In-place upgrade by just enabling the config settings on an existing cluster is not possible since these tunings change the underlying datapath fundamentals and therefore require Pod or even node restarts.
The best way to consume this for an existing cluster is to utilize per-node configuration for enabling the tunings only on newly spawned nodes which join the cluster. See the Per-node configuration page for more details.
Each of the settings for the recommended performance profile are described in more detail on this page and in this KubeCon talk:
netkit device mode
eBPF host-routing
BIG TCP for IPv4/IPv6
Bandwidth Manager (optional, for BBR congestion control)
Per-CPU distributed LRU and increased map size ratio
eBPF clock probe to use jiffies for CT map
Requirements:
Kernel >= 6.8
Supported NICs for BIG TCP: mlx4, mlx5, ice
To enable the main settings:
helm install cilium ./cilium \ --namespace kube-system \ --set routingMode=native \ --set bpf.datapathMode=netkit \ --set bpf.masquerade=true \ --set bpf.distributedLRU.enabled=true \ --set bpf.mapDynamicSizeRatio=0.08 \ --set ipv6.enabled=true \ --set enableIPv6BIGTCP=true \ --set ipv4.enabled=true \ --set enableIPv4BIGTCP=true \ --set kubeProxyReplacement=true \ --set bpfClockProbe=true
For enabling BBR congestion control in addition, consider adding the following settings to the above Helm install:
--set bandwidthManager.enabled=true \ --set bandwidthManager.bbr=true
netkit device mode
netkit devices provide connectivity for Pods with the goal to improve throughput and latency for applications as if they would have resided directly in the host namespace, meaning, it reduces the datapath overhead for network namespaces down to zero. The netkit driver in the kernel has been specifically designed for Cilium’s needs and replaces the old-style veth device type. See also the KubeCon talk on netkit for more details.
Cilium utilizes netkit in L3 device mode with blackholing traffic from the Pods when there is no BPF program attached. The Pod specific BPF programs are attached inside the netkit peer device, and can only be managed from the host namespace through Cilium. netkit in combination with eBPF-based host-routing achieves a fast network namespace switch for off-node traffic ingressing into the Pod or leaving the Pod. When netkit is enabled, Cilium also utilizes tcx for all attachments to non-netkit devices. This is done for higher efficiency as well as utilizing BPF links for all Cilium attachments. netkit is available for kernel 6.8 and onwards and it also supports BIG TCP. Once the base kernels become more ubiquitous, the veth device mode of Cilium will be deprecated.
To validate whether your installation is running with netkit, run cilium status
in any of the Cilium Pods and look for the line reporting the status for
“Device Mode” which should state “netkit”. Also, ensure to have eBPF host
routing enabled - the reporting status under “Host Routing” must state “BPF”.
Warning
This is a beta feature. Please provide feedback and file a GitHub issue if you experience any problems. Known issues with this feature are tracked here.
Note
In-place upgrade by just enabling netkit on an existing cluster is not possible since the CNI plugin cannot simply replace veth with netkit after Pod creation. Also, running both flavors in parallel is currently not supported.
The best way to consume this for an existing cluster is to utilize per-node configuration for enabling netkit on newly spawned nodes which join the cluster. See the Per-node configuration page for more details.
Requirements:
Kernel >= 6.8
eBPF host-routing
To enable netkit device mode with eBPF host-routing:
helm install cilium ./cilium \ --namespace kube-system \ --set routingMode=native \ --set bpf.datapathMode=netkit \ --set bpf.masquerade=true \ --set kubeProxyReplacement=true
eBPF Host-Routing
Even when network routing is performed by Cilium using eBPF, by default network packets still traverse some parts of the regular network stack of the node. This ensures that all packets still traverse through all of the iptables hooks in case you depend on them. However, they add significant overhead. For exact numbers from our test environment, see TCP Throughput (TCP_STREAM) and compare the results for “Cilium” and “Cilium (legacy host-routing)”.
We introduced eBPF-based host-routing
in Cilium 1.9 to fully bypass iptables and the upper host stack, and to achieve
a faster network namespace switch compared to regular veth device operation.
This option is automatically enabled if your kernel supports it. To validate
whether your installation is running with eBPF host-routing, run cilium status
in any of the Cilium pods and look for the line reporting the status for
“Host Routing” which should state “BPF”.
Note
BPF host routing is incompatible with Istio (see GitHub issue 36022 for details).
Requirements:
Kernel >= 5.10
eBPF-based kube-proxy replacement
eBPF-based masquerading
To enable eBPF Host-Routing:
helm install cilium ./cilium \ --namespace kube-system \ --set bpf.masquerade=true \ --set kubeProxyReplacement=true
Known limitations:
eBPF host routing optimizes the host-internal packet routing, and packets no
longer hit the netfilter tables in the host namespace. Therefore, it is incompatible
with features relying on netfilter hooks (for example, GKE Workload Identities).
Configure bpf.hostLegacyRouting=true
or leverage Local Redirect Policy
to work around this limitation.
IPv6 BIG TCP
IPv6 BIG TCP allows the network stack to prepare larger GSO (transmit) and GRO (receive) packets to reduce the number of times the stack is traversed which improves performance and latency. It reduces the CPU load and helps achieve higher speeds (i.e. 100Gbit/s and beyond).
To pass such packets through the stack BIG TCP adds a temporary Hop-By-Hop header after the IPv6 one which is stripped before transmitting the packet over the wire.
BIG TCP can operate in a DualStack setup, IPv4 packets will use the old lower limits (64k) if IPv4 BIG TCP is not enabled, and IPv6 packets will use the new larger ones (192k). Both IPv4 BIG TCP and IPv6 BIG TCP can be enabled so that both use the larger one (192k).
Note that Cilium assumes the default kernel values for GSO and GRO maximum sizes are 64k and adjusts them only when necessary, i.e. if BIG TCP is enabled and the current GSO/GRO maximum sizes are less than 192k it will try to increase them, respectively when BIG TCP is disabled and the current maximum values are more than 64k it will try to decrease them.
BIG TCP doesn’t require network interface MTU changes.
Note
In-place upgrade by just enabling BIG TCP on an existing cluster is currently not possible since Cilium does not have access into Pods after they have been created.
The best way to consume this for an existing cluster is to either restart Pods or to utilize per-node configuration for enabling BIG TCP on newly spawned nodes which join the cluster. See the Per-node configuration page for more details.
Requirements:
Kernel >= 5.19
eBPF Host-Routing
eBPF-based kube-proxy replacement
eBPF-based masquerading
Tunneling and encryption disabled
Supported NICs: mlx4, mlx5, ice
To enable IPv6 BIG TCP:
helm install cilium ./cilium \ --namespace kube-system \ --set routingMode=native \ --set bpf.masquerade=true \ --set ipv6.enabled=true \ --set enableIPv6BIGTCP=true \ --set kubeProxyReplacement=true
Note that after toggling the IPv6 BIG TCP option the Kubernetes Pods must be restarted for the changes to take effect.
To validate whether your installation is running with IPv6 BIG TCP,
run cilium status
in any of the Cilium pods and look for the line
reporting the status for “IPv6 BIG TCP” which should state “enabled”.
IPv4 BIG TCP
Similar to IPv6 BIG TCP, IPv4 BIG TCP allows the network stack to prepare larger GSO (transmit) and GRO (receive) packets to reduce the number of times the stack is traversed which improves performance and latency. It reduces the CPU load and helps achieve higher speeds (i.e. 100Gbit/s and beyond).
To pass such packets through the stack BIG TCP sets IPv4 tot_len to 0 and uses skb->len as the real IPv4 total length. The proper IPv4 tot_len is set before transmitting the packet over the wire.
BIG TCP can operate in a DualStack setup, IPv6 packets will use the old lower limits (64k) if IPv6 BIG TCP is not enabled, and IPv4 packets will use the new larger ones (192k). Both IPv4 BIG TCP and IPv6 BIG TCP can be enabled so that both use the larger one (192k).
Note that Cilium assumes the default kernel values for GSO and GRO maximum sizes are 64k and adjusts them only when necessary, i.e. if BIG TCP is enabled and the current GSO/GRO maximum sizes are less than 192k it will try to increase them, respectively when BIG TCP is disabled and the current maximum values are more than 64k it will try to decrease them.
BIG TCP doesn’t require network interface MTU changes.
Note
In-place upgrade by just enabling BIG TCP on an existing cluster is currently not possible since Cilium does not have access into Pods after they have been created.
The best way to consume this for an existing cluster is to either restart Pods or to utilize per-node configuration for enabling BIG TCP on newly spawned nodes which join the cluster. See the Per-node configuration page for more details.
Requirements:
Kernel >= 6.3
eBPF Host-Routing
eBPF-based kube-proxy replacement
eBPF-based masquerading
Tunneling and encryption disabled
Supported NICs: mlx4, mlx5, ice
To enable IPv4 BIG TCP:
helm install cilium ./cilium \ --namespace kube-system \ --set routingMode=native \ --set bpf.masquerade=true \ --set ipv4.enabled=true \ --set enableIPv4BIGTCP=true \ --set kubeProxyReplacement=true
Note that after toggling the IPv4 BIG TCP option the Kubernetes Pods must be restarted for the changes to take effect.
To validate whether your installation is running with IPv4 BIG TCP,
run cilium status
in any of the Cilium pods and look for the line
reporting the status for “IPv4 BIG TCP” which should state “enabled”.
Bypass iptables Connection Tracking
For the case when eBPF Host-Routing cannot be used and thus network packets still need to traverse the regular network stack in the host namespace, iptables can add a significant cost. This traversal cost can be minimized by disabling the connection tracking requirement for all Pod traffic, thus bypassing the iptables connection tracker.
Requirements:
Kernel >= 4.19.57, >= 5.1.16, >= 5.2
Direct-routing configuration
eBPF-based kube-proxy replacement
eBPF-based masquerading or no masquerading
To enable the iptables connection-tracking bypass:
cilium install --chart-directory ./install/kubernetes/cilium \ --set installNoConntrackIptablesRules=true \ --set kubeProxyReplacement=true
helm install cilium ./cilium \ --namespace kube-system \ --set installNoConntrackIptablesRules=true \ --set kubeProxyReplacement=true
Hubble
Running with Hubble observability enabled can come at the expense of performance. The overhead of Hubble is somewhere between 1-15% depending on your network traffic patterns and Hubble aggregation settings.
In clusters with a huge amount of network traffic, cilium-agent might spend a significant portion of CPU time on processing monitored events and Hubble may even lose some events. There are multiple ways to tune Hubble to avoid this.
Increase Hubble Event Queue Size
The Hubble Event Queue buffers events after they have been emitted from datapath and before they are processed by the Hubble subsystem. If this queue is full, because Hubble can’t keep up with the amount of emitted events, Cilium will start dropping events. This does not impact traffic, but the events won’t be processed by Hubble and won’t show up in Hubble flows or metrics.
When this happens you will see log lines similar to the following.
level=info msg="hubble events queue is processing messages again: NN messages were lost" subsys=hubble
level=warning msg="hubble events queue is full: dropping messages; consider increasing the queue size (hubble-event-queue-size) or provisioning more CPU" subsys=hubble
By default the Hubble event queue size is #CPU * 1024
, or 16384
if your nodes have
more than 16 CPU cores. If you encounter event bursts that result in dropped events,
increasing this queue size might help. We recommend gradually doubling the queue length
until the drops disappear. If you don’t see any improvements after increasing the queue
length to 128k, further increasing the event queue size is unlikely to help.
Be aware that increasing the Hubble event queue size will result in increased memory
usage. Depending on your traffic pattern, increasing the queue size by 10,000
may
increase the memory usage by up to five Megabytes.
cilium install --chart-directory ./install/kubernetes/cilium \ --set hubble.eventQueueSize=32768
helm install cilium ./cilium \ --namespace kube-system \ --set hubble.eventQueueSize=32768
If only certain nodes are effected you may also set the queue length on a per-node basis using a CiliumNodeConfig object.
apiVersion: cilium.io/v2
kind: CiliumNodeConfig
metadata:
namespace: kube-system
name: set-hubble-event-queue
spec:
nodeSelector:
matchLabels:
# Update selector to match your nodes
io.cilium.update-hubble-event-queue: "true"
defaults:
hubble-event-queue-size: "32768"
Increasing the Hubble event queue size can’t mitigate a consistently high rate of events being emitted by Cilium datapath and it does not reduce CPU utilization. For this you should consider increasing the aggregation interval or rate limiting events.
Increase Aggregation Interval
By default Cilium generates a tracing event on every new connection, any time a packet
contains TCP flags that have not been previously seen for the packet direction, and on
average once per monitor-aggregation-interval
, which defaults to 5 seconds.
Depending on your network traffic patterns, the re-emitting of trace events per aggregation interval can make up a large part of the total events. Increasing the aggregation interval may decrease CPU utilization and can prevent lost events.
The following will set the aggregation interval to 10 seconds.
cilium install --chart-directory ./install/kubernetes/cilium \ --set bpf.events.monitorInterval="10s"
helm install cilium ./cilium \ --namespace kube-system \ --set bpf.events.monitorInterval="10s"
Rate Limit Events
To further prevent high CPU utilization caused by Hubble, you can also set limits on how many events can be generated by datapath code. Two limits are possible to configure:
Rate limit - limits how many events on average can be generated
Burst limit - limits the number of events that can be generated in a span of 1 second
When both limits are set to 0, no BPF events rate limiting is imposed.
Note
Helm configuration for BPF events map rate limiting is experimental and might change in upcoming releases.
Warning
When BPF events map rate limiting is enabled, Cilium monitor, Hubble observability, Hubble metrics reliability, and Hubble export functionalities might be impacted due to dropped events.
To enable eBPF Event Rate Limiting with a rate limit of 10,000 and a burst limit of 50,000:
cilium install --chart-directory ./install/kubernetes/cilium \ --set bpf.events.default.rateLimit=10000 \ --set bpf.events.default.burstLimit=50000
helm install cilium ./cilium \ --namespace kube-system \ --set bpf.events.default.rateLimit=10000 \ --set bpf.events.default.burstLimit=50000
You can also choose to stop exposing event types in which you are not interested. For instance if you are mainly interested in dropped traffic, you can disable “trace” events which will likely reduce the overall CPU consumption of the agent.
cilium config set bpf-events-trace-enabled false
helm install cilium ./cilium \ --namespace kube-system \ --set bpf.events.trace.enabled=false
Warning
Suppressing one or more event types will impact cilium monitor
as well as Hubble observability capabilities, metrics and exports.
Disable Hubble
If all this is not sufficient, in order to optimize for maximum performance, you can disable Hubble:
cilium hubble disable
helm install cilium ./cilium \ --namespace kube-system \ --set hubble.enabled=false
MTU
The maximum transfer unit (MTU) can have a significant impact on the network throughput of a configuration. Cilium will automatically detect the MTU of the underlying network devices. Therefore, if your system is configured to use jumbo frames, Cilium will automatically make use of it.
To benefit from this, make sure that your system is configured to use jumbo frames if your network allows for it.
Bandwidth Manager
Cilium’s Bandwidth Manager is responsible for managing network traffic more efficiently with the goal of improving overall application latency and throughput.
Aside from natively supporting Kubernetes Pod bandwidth annotations, the Bandwidth Manager, first introduced in Cilium 1.9, is also setting up Fair Queue (FQ) queueing disciplines to support TCP stack pacing (e.g. from EDT/BBR) on all external-facing network devices as well as setting optimal server-grade sysctl settings for the networking stack.
Requirements:
eBPF-based kube-proxy replacement
To enable the Bandwidth Manager:
helm install cilium ./cilium \ --namespace kube-system \ --set bandwidthManager.enabled=true \ --set kubeProxyReplacement=true
To validate whether your installation is running with Bandwidth Manager,
run cilium status
in any of the Cilium pods and look for the line
reporting the status for “BandwidthManager” which should state “EDT with BPF”.
BBR congestion control for Pods
The base infrastructure around MQ/FQ setup provided by Cilium’s Bandwidth Manager also allows for use of TCP BBR congestion control for Pods. BBR is in particular suitable when Pods are exposed behind Kubernetes Services which face external clients from the Internet. BBR achieves higher bandwidths and lower latencies for Internet traffic, for example, it has been shown that BBR’s throughput can reach as much as 2,700x higher than today’s best loss-based congestion control and queueing delays can be 25x lower.
In order for BBR to work reliably for Pods, it requires a 5.18 or higher kernel. As outlined in our Linux Plumbers 2021 talk, this is needed since older kernels do not retain timestamps of network packets when switching from Pod to host network namespace. Due to the latter, the kernel’s pacing infrastructure does not function properly in general (not specific to Cilium). We helped fixing this issue for recent kernels to retain timestamps and therefore to get BBR for Pods working.
BBR also needs eBPF Host-Routing in order to retain the network packet’s socket association all the way until the packet hits the FQ queueing discipline on the physical device in the host namespace.
Note
In-place upgrade by just enabling BBR on an existing cluster is not possible since Cilium cannot migrate existing sockets over to BBR congestion control.
The best way to consume this is to either only enable it on newly built clusters, to restart Pods on existing clusters, or to utilize per-node configuration for enabling BBR on newly spawned nodes which join the cluster. See the Per-node configuration page for more details.
Note that the use of BBR could lead to a higher amount of TCP retransmissions and more aggressive behavior towards TCP CUBIC connections.
Requirements:
Kernel >= 5.18
Bandwidth Manager
eBPF Host-Routing
To enable the Bandwidth Manager with BBR for Pods:
helm install cilium ./cilium \ --namespace kube-system \ --set bandwidthManager.enabled=true \ --set bandwidthManager.bbr=true \ --set kubeProxyReplacement=true
To validate whether your installation is running with BBR for Pods,
run cilium status
in any of the Cilium pods and look for the line
reporting the status for “BandwidthManager” which should then state
EDT with BPF
as well as [BBR]
.
XDP Acceleration
Cilium has built-in support for accelerating NodePort, LoadBalancer services and services with externalIPs for the case where the arriving request needs to be pushed back out of the node when the backend is located on a remote node.
In that case, the network packets do not need to be pushed all the way to the upper networking stack, but with the help of XDP, Cilium is able to process those requests right out of the network driver layer. This helps to reduce latency and scale-out of services given a single node’s forwarding capacity is dramatically increased. The kube-proxy replacement at the XDP layer is available from Cilium 1.8.
Requirements:
Kernel >= 4.19.57, >= 5.1.16, >= 5.2
Native XDP supported driver, check our driver list
eBPF-based kube-proxy replacement
To enable the XDP Acceleration, check out our getting started guide which also contains instructions for setting it up on public cloud providers.
To validate whether your installation is running with XDP Acceleration,
run cilium status
in any of the Cilium pods and look for the line
reporting the status for “XDP Acceleration” which should say “Native”.
eBPF Map Backend Memory
Changing Cilium’s core BPF map memory configuration from a node-global LRU memory pool to a distributed per-CPU memory pool helps to avoid spinlock contention in the kernel under stress (many CT/NAT element allocation and free operations).
The trade-off is higher memory usage given the per-CPU pools cannot be
shared anymore, so if a given CPU pool depletes it needs to recycle
elements via LRU mechanism. It is therefore recommended to not only
enable bpf.distributedLRU.enabled
but to also increase the map
sizing which can be done via bpf.mapDynamicSizeRatio
:
helm install cilium ./cilium \ --namespace kube-system \ --set kubeProxyReplacement=true \ --set bpf.distributedLRU.enabled=true \ --set bpf.mapDynamicSizeRatio=0.08
Note that bpf.distributedLRU.enabled
is off by default in Cilium for
legacy reasons given enabling this setting on-the-fly is disruptive for
in-flight traffic since the BPF maps have to be recreated.
It is recommended to use the per-node configuration to gradually phase in this setting for new nodes joining the cluster. Alternatively, upon initial cluster creation it is recommended to consider enablement.
Also, bpf.distributedLRU.enabled
is currently only supported in combination
with bpf.mapDynamicSizeRatio
as opposed to statically sized map configuration.
eBPF Map Sizing
All eBPF maps are created with upper capacity limits. Insertion beyond the limit would fail or constrain the scalability of the datapath. Cilium is using auto-derived defaults based on the given ratio of the total system memory.
However, the upper capacity limits used by the Cilium agent can be overridden for advanced users. Please refer to the eBPF Maps guide.
eBPF Clock Probe
Cilium can probe the underlying kernel to determine whether BPF supports
retrieving jiffies instead of ktime. Given Cilium’s CT map does not require
high resolution, jiffies is more efficient and the preferred clock source.
To enable probing and possibly using jiffies, bpfClockProbe=true
can
be set:
helm install cilium ./cilium \ --namespace kube-system \ --set kubeProxyReplacement=true \ --set bpfClockProbe=true
Note that bpfClockProbe
is off by default in Cilium for legacy reasons
given enabling this setting on-the-fly means that previous stored CT map
entries with ktime as clock source for timestamps would now be interpreted
as jiffies.
It is therefore recommended to use the per-node configuration to gradually phase in this setting for new nodes joining the cluster. Alternatively, upon initial cluster creation it is recommended to consider enablement.
To validate whether jiffies is now used run cilium status --verbose
in
any of the Cilium Pods and look for the line Clock Source for BPF
.
Linux Kernel
In general, we highly recommend using the most recent LTS stable kernel (such as >= 5.10) provided by the kernel community or by a downstream distribution of your choice. The newer the kernel, the more likely it is that various datapath optimizations can be used.
In our Cilium release blogs, we also regularly highlight some of the eBPF based kernel work we conduct which implicitly helps Cilium’s datapath performance such as replacing retpolines with direct jumps in the eBPF JIT.
Moreover, the kernel allows to configure several options which will help maximize network performance.
CONFIG_PREEMPT_NONE
Run a kernel version with CONFIG_PREEMPT_NONE=y
set. Some Linux
distributions offer kernel images with this option set or you can re-compile
the Linux kernel. CONFIG_PREEMPT_NONE=y
is the recommended setting for
server workloads.
Kubernetes
Set scheduling mode
By default, the cilium daemonset is configured with an inter-pod anti-affinity rule. Inter-pod anti-affinity is not recommended for clusters larger than several hundred nodes as it reduces scheduling throughput of kube-scheduler.
If your cilium daemonset uses a host port (e.g. if prometheus metrics are enabled),
kube-scheduler
guarantees that only a single pod with that port/protocol is
scheduled to a node – effectively offering the same guarantee provided by the
inter-pod anti-affinity rule.
To leverage this, consider using --set scheduling.mode=kube-scheduler
when
installing or upgrading cilium.
Note
Use caution when changing changing host port numbers. Changing the host port
number removes the kube-scheduler
guarantee. When a host port number
must change, ensure at least one host port number is shared across the upgrade,
or consider using --set scheduling.mode=anti-affinity
.
Further Considerations
Various additional settings that we recommend help to tune the system for specific workloads and to reduce jitter:
tuned network-* profiles
The tuned project offers various profiles to
optimize for deterministic performance at the cost of increased power consumption,
that is, network-latency
and network-throughput
, for example. To enable
the former, run:
tuned-adm profile network-latency
Set CPU governor to performance
The CPU scaling up and down can impact latency tests and lead to sub-optimal
performance. To achieve maximum consistent performance. Set the CPU governor
to performance
:
for CPU in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
echo performance > $CPU
done
Stop irqbalance
and pin the NIC interrupts to specific CPUs
In case you are running irqbalance
, consider disabling it as it might
migrate the NIC’s IRQ handling among CPUs and can therefore cause non-deterministic
performance:
killall irqbalance
We highly recommend to pin the NIC interrupts to specific CPUs in order to allow for maximum workload isolation!
See this script for details and initial pointers on how to achieve this. Note that pinning the queues can potentially vary in setup between different drivers.
We generally also recommend to check various documentation and performance tuning guides from NIC vendors on this matter such as from Mellanox, Intel or others for more information.