Skip to main content
This guide demonstrates Cilium’s L2 announcement feature and how it enables nodes to respond to ARP requests for LoadBalancer external IPs in a local Kubernetes cluster (Kind is used in this demo). The goal is to show how to assign external IPs to LoadBalancer services and have nodes announce those IPs on the host subnet so clients can reach services via ARP.
A presentation slide showing the word "Demo" on the left and a turquoise curved shape on the right with the text "L2 Announcement." Small copyright text "© Copyright KodeKloud" appears in the bottom-left corner.

Overview

  • Technologies: Cilium (L2 announcements), Kind (local cluster), Helm, kubectl.
  • Goal: Assign external IPs from a node subnet to LoadBalancer services and have selected nodes respond to ARP for those IPs using Cilium L2 announcements.
  • Key CRDs used:
    • CiliumLoadBalancerIPPool: allocate external IPs for LoadBalancer services.
    • CiliumL2AnnouncementPolicy: control which nodes/interfaces respond to ARP for which services.

Prerequisites

  • A running Kind cluster and kubectl configured to that cluster.
  • Helm installed and access to the Cilium Helm chart (cilium/cilium).
  • Docker available if using Kind node containers for debugging.
Useful references:

1. Check cluster state and prepare Cilium values

Before installing Cilium, the cluster nodes may appear NotReady because the CNI is missing:
To enable L2 announcements, enable both the L2 announcement feature and kube-proxy replacement in Cilium. A typical Helm upgrade/install invocation looks like this:
When enabling kube-proxy replacement, provide the API server host and port (k8sServiceHost and k8sServicePort) so Cilium can reach the Kubernetes API. You can also edit the chart values file and set these keys before installing via Helm.
A good workflow is to fetch the default chart values, edit them, and then install:
Example installation output (abbreviated):
Verify Cilium pods are running:

2. Deploy two simple applications with LoadBalancer services

Create a manifest named apps-and-svcs.yaml which deploys two HTTP echo applications and exposes each with a Service of type LoadBalancer. apps-and-svcs.yaml:
Apply the manifest:
In a local Kind cluster, LoadBalancer services initially show EXTERNAL-IP as <pending>:

3. Provide external IPs to LoadBalancer services with Cilium IPAM

Create a CiliumLoadBalancerIPPool so Cilium can allocate external IPs for your services from an address block on your node subnet. In this demo the node subnet is 172.19.0.0/16 and we pick a small range: ipam.yaml:
Apply the pool:
After creating the pool, services receive external IPs from that pool:
Because these external IPs are on the same subnet as the host nodes, clients on that subnet will attempt to reach them via ARP. If no node responds to ARP for those IPs, traffic will not reach your services (curl will time out). Example failing request before L2 announcement is configured:

4. Configure the Cilium L2 Announcement policy

Create a CiliumL2AnnouncementPolicy to control which nodes and interfaces respond to ARP for which services. The example below:
  • Matches services labeled app: myapp
  • Excludes the control-plane node so only worker nodes respond
  • Restricts interfaces with a regex ‘^eth[0-9]+’ (matches eth0, eth1, …)
  • Enables both externalIPs and loadBalancerIPs
l2announce.yaml:
Apply the policy:
Describe the policy to confirm settings:

5. Which node responds for each service IP? (Leases)

Cilium coordinates which node will answer ARP for a given external IP using Lease objects. You can list the leases in the kube-system namespace:
The HOLDER field shows which node currently holds the lease and will therefore respond to ARP for the service IP.

6. Test connectivity and validate ARP

After the L2 announcement policy is active, clients on the same subnet can reach the LoadBalancer external IPs:
Verify the local ARP table shows the service IP entries mapped to the node MAC addresses:
In Kind-based setups the MAC addresses correspond to the Docker/Kind bridge interfaces for node containers. You can inspect the node container interfaces to confirm which node IP/MAC answered ARP. Example:
The link/ether value should match the ARP mapping for the external IP owned by that node.

7. Summary and quick references

  • Enable l2announcements and kube-proxy replacement in Cilium (via Helm values or values.yaml).
  • Create a CiliumLoadBalancerIPPool to allocate external IPs on the node subnet for LoadBalancer services.
  • Create a CiliumL2AnnouncementPolicy to specify which services, nodes, and interfaces should respond to ARP (externalIPs and/or loadBalancerIPs).
  • Inspect Leases to determine which node is announcing each external IP, and validate connectivity with curl and arp.
Key resources and commands: Further reading and docs: This completes the demonstration of Cilium’s L2 announcement feature for local clusters.

Watch Video