Introduction
Kubernetes networking is most useful when it fits both the cloud network and the application operating model. On Oracle Cloud Infrastructure (OCI), Oracle Kubernetes Engine (OKE) supports OCI VCN-native pod networking, where pod IP addresses are allocated from a Virtual Cloud Network (VCN) subnet and are directly routable inside the VCN. This model gives platform teams familiar OCI controls for routing, security rules, and VCN Flow Logs.
At the same time, many Kubernetes teams want Cilium capabilities such as endpoint identity, Kubernetes NetworkPolicy enforcement, Hubble flow visibility, and rich eBPF-based observability. A practical way to combine these capabilities is to keep OCI VCN-native pod networking as the base CNI and run Cilium in CNI chaining mode.
This post walks through a working model for OKE with OCI VCN-native pod networking and Cilium generic-veth chaining. The goal is not to replace OCI pod IPAM. Instead, OCI CNI remains responsible for pod IP allocation and the base network path, while Cilium attaches policy and visibility to the pod networking lifecycle.
Core concepts
OCI VCN-native pod networking assigns pod IP addresses from a private pod subnet in the VCN. This allows pods to be reached through normal VCN routing, peering, and on-premises connectivity patterns when the required route and security rules are in place.
Cilium chaining mode is different from a full Cilium primary CNI deployment. In the chaining model, an existing CNI plugin creates the base pod network interface and address. Cilium then runs as a chained CNI plugin and adds Cilium endpoint identity, policy, and observability around that interface.
For this implementation, the validated posture is:
Base pod networking: OCI VCN-native CNI
Cilium mode: CNI chaining
Chaining plugin: generic-veth
Cilium IPAM: delegated-plugin
Routing mode: native
CNI exclusive: false
Kube-proxy: retained in the conservative model
Prerequisites
Before you run the sample, prepare the following:
- An OCI tenancy with permissions to create or manage OKE, VCN, subnets, route tables, security lists or NSGs, load balancers, and IAM policies.
- An OCI compartment and either an existing VCN with endpoint, worker, private pod, and optional load balancer subnets, or permission to create the optional disposable reference network included with the sample.
- A private pod subnet sized for the expected pod density. In VCN-native mode, pods consume IP addresses from the OCI pod subnet.
- A workstation or private runner with
bash,oci,terraform,kubectl, andjqinstalled and authenticated. Terraform installs Cilium through the Helm provider, so the orchestration scripts do not require the Helm CLI. - Network access from the workstation or runner to the OKE API endpoint after cluster creation. The deployment section generates the
oke_cilium_chainingkubeconfig context. - OCI network rules that allow worker-to-pod, pod-to-pod, DNS, Kubernetes API, and required OCI service access.
- DNS and outbound access from worker nodes to the required container registries, or equivalent private mirrors. The sample pulls Cilium images plus test images from external registries.
- A Cilium version validated with the target OKE and Kubernetes version. The publication baseline in this post uses Kubernetes
v1.35.2and Cilium1.18.11; select versions currently available and supported in your OCI region rather than copying them blindly.
For the optional Gateway API extension, also validate Gateway API CRDs, Cilium kube-proxy replacement, OCI Load Balancer posture, source IP behavior, and rollback steps in a separate environment before using it in production.
The optional network stack is intended only to make the article reproducible from an empty compartment. Enterprise deployments should normally use an approved landing-zone VCN and keep network ownership outside this sample.
Architecture overview
The architecture uses an OKE enhanced cluster with VCN-native pod networking enabled. The VCN contains an endpoint subnet, a worker subnet, a private pod subnet, and an optional load balancer subnet. The pod subnet is the address pool used by OCI CNI for pod IP allocation.

Figure 1: OCI architecture for OKE VCN-native pod networking with Cilium generic-veth chaining
The numbered flows in Figure 1 show the deployment and validation sequence:
- Operators use the controlled application path to the optional OCI Load Balancer; private OKE API administration is shown in flow 2.
- A private runner or CI/CD pipeline uses OCI APIs to apply the Terraform stacks, then uses the private OKE API to apply the CNI chain ConfigMap, Cilium Helm release, and validation workloads.
- When kubelet creates a pod sandbox, OCI VCN-native CNI allocates the pod IP from the private pod subnet.
- Cilium runs after OCI CNI in
generic-vethchaining mode and creates endpoint identity state without taking over pod IPAM. - The validation workloads prove allowed and denied traffic while Cilium enforces Kubernetes NetworkPolicy and Hubble records the verdicts.
- The optional Gateway API path is a separate extension that requires kube-proxy replacement and its own validation results.
The CNI chain itself is the most important part of the design. Figure 2 separates ownership across the private OKE API, worker nodes, VCN-native pod addresses, and the Cilium policy and observability layer. The chained configuration calls the OCI networking plugins first and Cilium after them.

Figure 2: Baseline CNI ownership, pod IP allocation, Cilium chaining, and validation flow
The baseline architecture keeps kube-proxy in place. That is the conservative starting point for proving OCI VCN-native pod IPAM with Cilium chaining. After validating that baseline, you can evaluate Cilium Gateway API as a separate north-south traffic design. OCI CNI continues to own pod addressing, while the optional Cilium configuration introduces kube-proxy replacement and an Envoy-based Gateway API datapath.
What you build
The sample repository contains Terraform and validation scripts that build and test the pattern end to end. Its README deployment guide is the operational source of truth for the current prerequisites, configuration, deployment, validation, and cleanup commands. Clone the repository and run the remaining commands in this post from the repository root:
git clone https://github.com/oracle-devrel/oke-cilium-chaining-sample.git
cd oke-cilium-chaining-sample
cp envs/oke-vcn-native-cilium-chaining.tfvars.example \
envs/oke-vcn-native-cilium-chaining.tfvars
Edit envs/oke-vcn-native-cilium-chaining.tfvars with the compartment, VCN, subnet, NSG, compute shape, and region values for your tenancy. Unless a section explicitly says otherwise, keep your shell in the repository root so that the relative paths used by the Terraform and validation commands resolve correctly.
For a disposable from-scratch environment, also copy and edit the optional network configuration. Its VCN and subnet names must match the names in the OKE tfvars file:
cp envs/oke-vcn-native-cilium-network.tfvars.example \
envs/oke-vcn-native-cilium-network.tfvars
The sample uses staged infrastructure:
stacks/00-network Optional disposable VCN, subnets, Internet/NAT/Service Gateways, and routes
stacks/01-oke OKE cluster, node pool, and related security-list resources
stacks/03-cilium Cilium Helm release after kubeconfig access works
scripts/ Validation scripts for OCI CNI, Cilium, smoke tests, and policy tests
Run the end-to-end workflow
The recommended entry point preserves those stages but orchestrates them as one operation:
scripts/deploy-and-validate.sh
The command above is the enterprise path and expects the VCN and subnets referenced by the OKE tfvars to exist. To create a disposable reference network and run the complete workflow from an empty compartment, use:
scripts/deploy-and-validate.sh --provision-network
Disposable-network mode applies stacks/00-network, creates OKE, automatically attaches the generated role-specific OKE security lists to the sample-owned subnets, installs Cilium, and runs every validation stage. The network Terraform state remains separate so cleanup can prove ownership before deleting it.
Existing-network mode assumes the referenced subnets already have equivalent OKE network rules. The example tfvars enables generated security lists. When those generated lists are required and the repository is authorized to update the existing subnet associations, run:
scripts/deploy-and-validate.sh --attach-security-lists
The generated script requires an explicit APPLY. Without the flag, OKE resource creation can succeed while Kubernetes API access or pod networking remains blocked by the existing subnet rules.
The runner performs the following sequence:
- When requested, initializes, validates, plans, and applies
stacks/00-networkto create the disposable VCN, subnets, Internet Gateway, NAT Gateway, Service Gateway, and route tables. Private subnets use the Service Gateway for regional Oracle Services Network traffic and NAT for other outbound endpoints. - Initializes, validates, plans, and applies
stacks/01-oketo create the OKE enhanced cluster and VCN-native managed node pool. - For a sample-owned network, attaches the generated role-specific security lists; existing-network mode changes subnet associations only when explicitly requested.
- Reads the cluster OCID and OCI region from Terraform outputs, generates kubeconfig, and waits for every worker node to report
Ready. - Calls
scripts/cilium-install-verify.sh, which proves base OCI CNI connectivity, applies the generic-veth chaining configuration, and appliesstacks/03-cilium. - Runs the cross-node endpoint, ClusterIP, service DNS, Cilium service-table, Hubble flow, and Kubernetes NetworkPolicy proofs, writes complete evidence to
validation-results/, and prints one end-to-end summary.
For a private Kubernetes API endpoint, execute the runner from a connected private runner and select the private endpoint:
scripts/deploy-and-validate.sh \
--kube-endpoint PRIVATE_ENDPOINT
If oke_cilium_chaining is already a kubeconfig context for another API server, choose a unique context name without changing the tfvars cluster key:
scripts/deploy-and-validate.sh \
--kube-context oke_cilium_chaining_live
A successful run ends with a summary similar to the following:
===== END-TO-END SUMMARY =====
Prerequisite network: PASS (sample owned)
OKE cluster and VCN-native node pool: PASS
Subnet security-list attachment: PASS (sample-owned network)
Kubeconfig and Kubernetes API access: PASS
Worker nodes Ready: PASS
OCI VCN-native cross-node connectivity: PASS
Cilium installation and agent health: PASS
Cilium chained endpoint smoke test: PASS
Cross-node ClusterIP and service DNS connectivity: PASS
Cilium service-table visibility: PASS
Hubble forwarded and policy-denied flow visibility: PASS
Kubernetes NetworkPolicy enforcement: PASS
OVERALL RESULT: PASS
Detailed log: validation-results/deploy-and-validate-<timestamp>.log
The following sections show the same workflow stage by stage so that readers can inspect, rerun, or troubleshoot each layer independently.
The important OKE cluster setting is the pod network type:
cluster = {
type = "ENHANCED_CLUSTER"
cni_type = "OCI_VCN_IP_NATIVE"
pod_cidr = null
service_cidr = "10.97.0.0/16"
public_endpoint = true
image_policy_enabled = false
}
The node pool must include a private pod subnet when the cluster uses OCI VCN-native pod networking:
node_pool_pod_network_option_details {
cni_type = "OCI_VCN_IP_NATIVE"
max_pods_per_node = 31
pod_subnet_ids = var.pod_subnet_ids
pod_nsg_ids = var.pod_nsg_ids
}
Deploy the OKE cluster
By default, the sample assumes that the target compartment, VCN, endpoint subnet, worker subnet, private pod subnet, and optional load balancer subnet already exist. The stacks/01-oke Terraform stack resolves those existing network resources by OCID or name and creates the following resources:
- An OKE enhanced cluster with
OCI_VCN_IP_NATIVEpod networking. - A managed node pool whose worker nodes use the worker subnet and whose pods use the private pod subnet.
- Optional OKE security lists and generated scripts for attaching those lists to existing subnets.
If you need to prepare the network or want to create the cluster manually instead, see Network Resource Configuration for Cluster Creation and Deployment, Creating an Enhanced Cluster, and Using the OCI VCN-Native Pod Networking CNI plugin.
For an isolated evaluation, --provision-network applies the included stacks/00-network before this OKE stack. Treat that network as disposable reference infrastructure, not as a substitute for an enterprise landing zone.
From the sample repository root, initialize, validate, plan, and apply the OKE stack:
terraform -chdir=stacks/01-oke init
terraform -chdir=stacks/01-oke validate
terraform -chdir=stacks/01-oke plan \
-var-file=../../envs/oke-vcn-native-cilium-chaining.tfvars \
-out=tfplan
terraform -chdir=stacks/01-oke apply tfplan
Confirm that Terraform created the cluster and node pool with OCI VCN-native pod networking and resolved the intended subnets:
terraform -chdir=stacks/01-oke output cluster_ids
terraform -chdir=stacks/01-oke output cluster_cni_types
terraform -chdir=stacks/01-oke output node_pool_cni_types
terraform -chdir=stacks/01-oke output resolved_network
When security_lists.enabled = true, Terraform creates the security lists and writes generated/attach-security-lists.sh. Terraform does not silently replace the security lists on existing subnets. Review the generated commands and run the script only if this repository is authorized to update those subnet associations:
terraform -chdir=stacks/01-oke output attach_instructions
sed -n '1,240p' generated/attach-security-lists.sh
generated/attach-security-lists.sh
The generated script preserves existing security-list OCIDs and asks you to type APPLY before changing a subnet. If another Terraform state owns the subnets, coordinate the change to avoid configuration drift.
The OKE stack also snapshots the original subnet security-list associations in Terraform state. Later Terraform refreshes therefore keep generated/detach-security-lists.sh pointed at the pre-attachment state instead of treating the attached lists as the new baseline.
Create the kubeconfig after the OKE control plane is active. Set OCI_REGION to the region in your tfvars file:
export OCI_REGION="us-phoenix-1"
export CLUSTER_ID="$(terraform -chdir=stacks/01-oke output -json cluster_ids | \
jq -r '.oke_cilium_chaining')"
oci ce cluster create-kubeconfig \
--cluster-id "$CLUSTER_ID" \
--file "$HOME/.kube/config" \
--region "$OCI_REGION" \
--token-version 2.0.0 \
--kube-endpoint PUBLIC_ENDPOINT
if [[ "$(kubectl config current-context)" != "oke_cilium_chaining" ]]; then
kubectl config rename-context \
"$(kubectl config current-context)" oke_cilium_chaining
fi
kubectl --context oke_cilium_chaining get nodes -o wide
For a private Kubernetes API endpoint, run the command from a connected private runner and use --kube-endpoint PRIVATE_ENDPOINT. See Setting Up Cluster Access for the supported access patterns.
Continue only after the worker nodes report Ready. The next two stages create the chaining ConfigMap and install Cilium.
Configure the CNI chain
Create the CNI chaining ConfigMap before installing Cilium. The ConfigMap defines the plugin chain that runs for pod networking:
apiVersion: v1
kind: ConfigMap
metadata:
name: cilium-chaining-config
namespace: kube-system
data:
cni-config: |-
{
"name": "oci",
"cniVersion": "0.3.1",
"plugins": [
{
"cniVersion": "0.3.1",
"type": "oci-ipvlan",
"mode": "l2",
"ipam": {
"type": "oci-ipam"
}
},
{
"cniVersion": "0.3.1",
"type": "oci-ptp",
"containerInterface": "ptp-veth0",
"mtu": 9000
},
{
"type": "cilium-cni",
"chaining-mode": "generic-veth",
"enable-debug": false,
"log-file": "/var/run/cilium/cilium-cni.log"
}
]
}
The sample repository includes a helper:
scripts/apply-oci-cilium-chaining-config.sh oke_cilium_chaining
Helper output:
Applied kube-system/cilium-chaining-config for OCI CNI + Cilium generic-veth chaining.
Install Cilium with Helm
The Cilium values are intentionally conservative for the VCN-native chaining model:
cilium = {
enabled = true
version = "1.18.11"
install_mode = "chaining"
namespace = "kube-system"
repository = "https://helm.cilium.io/"
ipam_mode = "delegated-plugin"
routing_mode = "native"
kube_proxy_replacement = false
chaining_mode = "generic-veth"
cni_custom_conf = true
cni_config_map = "cilium-chaining-config"
cni_config_map_key = "cni-config"
cni_exclusive = false
cni_external_routing = true
enable_ipv4_masquerade = false
bpf_masquerade = false
socket_lb_enabled = false
k8s_network_policy_enabled = true
enable_non_default_deny_policies = true
hubble_enabled = true
hubble_relay_enabled = true
hubble_ui_enabled = true
}
For only the Cilium installation and network-validation workflow after the OKE nodes are Ready, run:
scripts/cilium-install-verify.sh oke_cilium_chaining
The wrapper uses only files included in the sample repository. On its first run it performs the base cross-node OCI CNI test, applies the chaining ConfigMap, installs Cilium through stacks/03-cilium, verifies the OCI CNI and Cilium DaemonSets, tests cross-node pod-IP and ClusterIP Service connectivity, inspects Cilium endpoint and service state, captures Hubble forwarded and denied flows, and runs the Kubernetes NetworkPolicy test. It writes the complete command output to validation-results/cilium-chaining-<timestamp>.log. The top-level deploy-and-validate.sh calls this same wrapper after provisioning OKE and configuring cluster access.
If the kubeconfig context is an alias rather than the cluster key in tfvars, provide both values:
TARGET_CLUSTER_NAME=oke_cilium_chaining \
scripts/cilium-install-verify.sh oke_cilium_chaining_live
To inspect an existing installation without applying Terraform again, use:
scripts/cilium-install-verify.sh oke_cilium_chaining --verify-only
To run only the Cilium installation stage manually, apply the sample Terraform stack directly:
terraform -chdir=stacks/03-cilium init
terraform -chdir=stacks/03-cilium workspace select oke_cilium_chaining || \
terraform -chdir=stacks/03-cilium workspace new oke_cilium_chaining
terraform -chdir=stacks/03-cilium apply \
-var-file=../../envs/oke-vcn-native-cilium-chaining.tfvars \
-var="target_cluster_name=oke_cilium_chaining"
Validate the cluster
First, confirm that the worker nodes are Ready:
kubectl --context oke_cilium_chaining get nodes -o wide
Validation output from the environment used for this article:
NAME STATUS ROLES VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE CONTAINER-RUNTIME
10.61.4.70 Ready node v1.35.2 10.61.4.70 <none> Oracle Linux Server 8.10 cri-o://1.35.2-13.975e05318cb.el8
10.61.7.184 Ready node v1.35.2 10.61.7.184 <none> Oracle Linux Server 8.10 cri-o://1.35.2-13.975e05318cb.el8
Next, confirm that both OCI VCN-native CNI and Cilium are running. In this model, seeing both DaemonSets is expected:
kubectl --context oke_cilium_chaining -n kube-system get ds cilium vcn-native-ip-cni kube-proxy
Validation output:
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
cilium 2 2 2 2 2 kubernetes.io/os=linux
vcn-native-ip-cni 2 2 2 2 2 <none>
kube-proxy 2 2 2 2 2 beta.kubernetes.io/os=linux
Inspect the generated Cilium configuration:
kubectl --context oke_cilium_chaining -n kube-system get cm cilium-config -o yaml | \
egrep 'cluster-name|ipam|routing-mode|cni-chaining|custom-cni-conf|kube-proxy-replacement|enable-hubble|enable-k8s-networkpolicy'
Validation output:
cluster-name: oke-cilium-chaining
cni-chaining-mode: generic-veth
custom-cni-conf: "true"
enable-hubble: "true"
enable-k8s-networkpolicy: "true"
ipam: delegated-plugin
kube-proxy-replacement: "false"
routing-mode: native
Now check Cilium status:
kubectl --context oke_cilium_chaining -n kube-system exec ds/cilium -- cilium-dbg status --verbose
Key output:
Kubernetes: Ok 1.35 (v1.35.2) [linux/amd64]
CNI Chaining: generic-veth
Cilium: Ok 1.18.11
Cilium health daemon: Ok
IPAM: IPv4: delegated to plugin
Routing: Network: Native Host: Legacy
Masquerading: Disabled
Hubble: Ok
KubeProxyReplacement: False
BPF Maps: dynamic sizing: on
Check that Cilium has active service backends:
kubectl --context oke_cilium_chaining -n kube-system exec ds/cilium -- cilium-dbg service list
Validation output:
ID Frontend Service Type Backend
3 10.97.5.5:53/TCP ClusterIP 1 => 10.61.10.76:53/TCP (active)
2 => 10.61.10.253:53/TCP (active)
6 10.97.0.1:443/TCP ClusterIP 1 => 10.61.0.3:6443/TCP (active)
8 10.97.139.177:80/TCP ClusterIP 1 => 10.61.7.184:4244/TCP (active)
Prove the datapath with smoke tests
The validation is intentionally split into four proof areas. Each one demonstrates a different part of the chaining model:
| Check | What it proves | Why it matters |
|---|---|---|
| Base pod connectivity | OCI VCN-native CNI can allocate pod IPs and route pod-to-pod traffic across worker nodes. | This confirms the cloud-native pod network works before Cilium is added. |
| Cilium smoke test | The same cross-node pod traffic works after Cilium is chained, and Cilium sees the test pods as endpoints. | This proves Cilium did not replace OCI IPAM and did attach to the pod lifecycle. |
| Service and Hubble test | Cross-node ClusterIP and service-DNS requests succeed, Cilium learns the Service, and Hubble records a forwarded flow. | This proves the composed service path is usable and observable after chaining. |
| NetworkPolicy test | Cilium enforces Kubernetes NetworkPolicy while pods keep VCN-native IP addresses. | This proves the value-add layer: identity-aware policy on top of OCI pod networking. |
The validation scripts accept the kubeconfig context as the first argument and an optional namespace as the second argument. If you do not pass a namespace, the scripts create a timestamped namespace and keep it for inspection.
| Script | Optional inputs | What it creates | What pass means |
|---|---|---|---|
scripts/pod-connectivity-test.sh <context> [namespace] |
SERVER_IMAGE, CLIENT_IMAGE environment overrides |
A server pod and client pod pinned to different worker nodes | OCI VCN-native CNI allocated pod IPs and routed cross-node pod traffic before Cilium policy was introduced. |
scripts/cilium-smoke-test.sh <context> [namespace] |
SERVER_IMAGE, CLIENT_IMAGE, CILIUM_NS environment overrides |
A server and client on different workers, a ClusterIP Service, endpoint/service inspection, and Hubble flow query | Pod-IP, ClusterIP, and service-DNS traffic work across nodes; Cilium sees the endpoints and Service; Hubble records forwarding. |
scripts/cilium-policy-test.sh <context> [namespace] |
SERVER_IMAGE, CLIENT_IMAGE, CILIUM_NS environment overrides |
A policy server, allowed client, blocked client, Kubernetes NetworkPolicy, and Hubble denial query |
Cilium allows the selected identity, blocks the other identity, and Hubble records the denial while pod IPs remain OCI VCN-native. |
This makes the validation stronger than a simple pod ping. The tests cover the base OCI datapath, the Cilium chained endpoint lifecycle, the Kubernetes Service path, Cilium’s service view, Hubble observability, and policy enforcement behavior.
When all stages in the combined wrapper pass, it prints a concise summary suitable for attaching to a review or change record:
===== VALIDATION SUMMARY =====
OCI VCN-native cross-node connectivity: PASS
Cilium DaemonSet and agent health: PASS
Cilium chained endpoint smoke test: PASS
Cross-node ClusterIP and service DNS connectivity: PASS
Cilium service-table visibility: PASS
Hubble forwarded and policy-denied flow visibility: PASS
Kubernetes NetworkPolicy enforcement: PASS
OVERALL RESULT: PASS
Detailed log: validation-results/cilium-chaining-<timestamp>.log
First, validate that the base OCI CNI can move traffic:
scripts/pod-connectivity-test.sh oke_cilium_chaining
The script schedules a client pod and a server pod on different worker nodes and performs an HTTP request from the client to the server pod IP. A passing result means the OKE VCN-native datapath can allocate pod IPs from the pod subnet and route cross-node pod traffic before Cilium policy is introduced.
Representative base-CNI output:
NAME READY STATUS IP NODE
pod-test-client 1/1 Running 10.61.11.109 10.61.4.70
pod-test-server 1/1 Running 10.61.8.205 10.61.7.184
===== cross-node pod HTTP test =====
POD CONNECTIVITY TEST RESULT: PASS
After Cilium is installed, run the Cilium smoke test:
scripts/cilium-smoke-test.sh oke_cilium_chaining
This test repeats the cross-node HTTP check after Cilium is in the CNI chain, creates a ClusterIP Service for the server, and reaches that Service by both ClusterIP and Kubernetes DNS name. It then queries Cilium endpoint and service state and obtains the forwarded-flow evidence from the Hubble CLI included in the Cilium agent pod.
The baseline deliberately retains kube-proxy and sets kubeProxyReplacement=false. Therefore, the Service test proves that the composed OKE, OCI CNI, kube-proxy, and Cilium deployment provides working Service connectivity. The Cilium service-table check proves that Cilium learned the Service, but it does not claim that Cilium exclusively owns Service forwarding.
The smoke test covers these Cilium-specific signals:
- Cilium remains healthy after installation in
generic-vethchaining mode. - The client and server pods are visible in Cilium endpoint state.
- The endpoints keep VCN-native pod IPs, proving Cilium is not doing pod IPAM.
- Cross-node HTTP works after Cilium has attached endpoint state.
- ClusterIP and service-DNS requests reach the backend on the other worker.
cilium-dbg service listcontains the test Service frontend and backend.- Hubble is healthy and reports a forwarded flow involving the smoke client.
Representative truncated output from the enhanced script follows. The full service table and Hubble flow set remain in the timestamped validation log; addresses, pod names, and timestamps vary by run.
NAME READY STATUS IP NODE
cilium-smoke-client 1/1 Running 10.61.11.181 10.61.4.70
cilium-smoke-server 1/1 Running 10.61.8.222 10.61.7.184
===== cross-node pod-IP HTTP test =====
NOW: 2026-06-29 15:17:19.363811836 +0000 UTC
CROSS-NODE POD-IP CONNECTIVITY RESULT: PASS
===== cross-node ClusterIP service test =====
NOW: <timestamp>
CLUSTERIP SERVICE CONNECTIVITY RESULT: PASS
===== cross-node service DNS test =====
NOW: <timestamp>
SERVICE DNS CONNECTIVITY RESULT: PASS
===== cilium endpoints for smoke pods =====
552 Disabled Disabled 132591 k8s:app=cilium-smoke-test 10.61.8.222 ready
2775 Disabled Disabled 141292 k8s:app=cilium-smoke-test 10.61.11.181 ready
===== Cilium service table for <cluster-ip>:80 =====
ID Frontend Service Type Backend
... <cluster-ip>:80/TCP ClusterIP 1 => 10.61.8.222:8080/TCP (active)
CILIUM SERVICE TABLE RESULT: PASS
===== Hubble forwarded flows for smoke client =====
<timestamp>: cilium-smoke-<id>/cilium-smoke-client ->
cilium-smoke-<id>/cilium-smoke-server:8080 to-endpoint FORWARDED (TCP)
HUBBLE FORWARDED FLOW RESULT: PASS
Namespace kept for inspection: cilium-smoke-1782746218
SMOKE TEST RESULT: PASS
The important signals are:
- The client and server pods are on different worker nodes.
- Both pod IPs are from the VCN-native pod address space.
- The HTTP request succeeds across nodes.
- Cilium endpoint output includes the smoke-test pods.
- The same backend is reachable through the ClusterIP and service DNS name.
- Cilium’s service table contains the test frontend and backend.
- Hubble records a forwarded flow for the test client.
Together, these signals prove that OCI CNI continues to own pod IP allocation and routing, while Cilium is active as the chained policy and observability layer.
Validate Kubernetes NetworkPolicy
Cilium should see Kubernetes endpoints and enforce Kubernetes NetworkPolicy while OCI CNI remains the base pod networking layer.
Run:
scripts/cilium-policy-test.sh oke_cilium_chaining
The script creates:
policy-server
allowed-client
blocked-client
Then it applies a Kubernetes NetworkPolicy that allows only pods labeled access=allowed to reach the policy-server on TCP port 8080:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-only-approved-client
spec:
podSelector:
matchLabels:
app: policy-server
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
access: allowed
ports:
- protocol: TCP
port: 8080
Validation output:
===== allowed client should succeed =====
allowed client: PASS
===== blocked client should fail =====
blocked client: PASS
===== Hubble denied flows for blocked client =====
<timestamp>: cilium-policy-<id>/blocked-client <>
cilium-policy-<id>/policy-server:8080 Policy denied DROPPED (TCP Flags: SYN)
HUBBLE POLICY DENIAL RESULT: PASS
Namespace kept for inspection: cilium-policy-1782746250
CILIUM POLICY TEST RESULT: PASS
This is the policy proof point. The allowed-client succeeds, the blocked-client fails, Hubble records the denial, and the policy server appears as a Cilium endpoint with ingress policy enabled. The pod IPs remain VCN-native; the enforcement decision comes from Cilium policy state attached to those endpoints.
You can also verify that the policy server is a Cilium endpoint with ingress policy enabled:
kubectl --context oke_cilium_chaining -n kube-system exec ds/cilium -- cilium-dbg endpoint list
Validation output:
ENDPOINT POLICY (ingress) POLICY (egress) LABELS IPv4 STATUS
468 Enabled Disabled k8s:app=policy-server 10.61.8.32 ready
552 Disabled Disabled k8s:app=cilium-smoke-test 10.61.8.222 ready
2775 Disabled Disabled k8s:app=cilium-smoke-test 10.61.11.181 ready
How Cilium features are used
The solution uses Cilium features in layers. The first layer is required for the OCI CNI chaining design. The second layer is optional and becomes useful when you want Cilium to own more of the application traffic management path.
| Cilium capability | How it is used in this design | Evidence or acceptance check |
|---|---|---|
| CNI chaining | Cilium runs after the OCI CNI plugins and attaches policy and visibility to the pod interface that OCI CNI created. | cilium-dbg status --verbose shows CNI Chaining: generic-veth. |
| Delegated IPAM | Cilium does not allocate pod IP addresses. OCI CNI allocates VCN-native pod IPs from the pod subnet. | cilium-config shows ipam: delegated-plugin. |
| Kubernetes NetworkPolicy | Cilium enforces Kubernetes NetworkPolicy for selected pods while the pod IPs remain OCI VCN-native. | The policy test allows allowed-client and blocks blocked-client. |
| Endpoint identity | Cilium assigns identities to Kubernetes endpoints and applies policy by identity instead of hard-coded pod IPs. | cilium-dbg endpoint list shows labels such as k8s:app=policy-server. |
| Service awareness | Cilium learns Kubernetes Service frontends and backends while the conservative baseline retains kube-proxy. | The smoke test reaches the ClusterIP and DNS name, then finds the test frontend in cilium-dbg service list. |
| Hubble observability | Hubble observes pod flows and policy verdicts from the Cilium-managed endpoints. | The scripts require a forwarded smoke flow and a dropped policy flow from hubble observe. |
| Gateway API extension | In a separate evaluation, Cilium can implement Gateway and HTTPRoute resources for north-south HTTP routing. |
Require accepted and programmed Gateway and HTTPRoute conditions before treating the extension as usable. |
| CiliumNetworkPolicy L7 rules | In that separate evaluation, CiliumNetworkPolicy can express HTTP-aware policy for traffic entering through the Cilium Gateway API datapath. | Prove allowed and rejected methods or paths from the ingress entity and capture Hubble evidence. |
Optional extension: Cilium Gateway API
Gateway API gives platform teams ownership of GatewayClass and shared Gateway resources while application teams attach HTTPRoute resources. It can extend this design with Kubernetes-native north-south routing, but it changes the validated baseline in an important way: Cilium Gateway API requires kube-proxy replacement, while the baseline in this article intentionally uses kubeProxyReplacement=false.
Validation boundary: Gateway API was not enabled in the environment that produced the baseline results in this article. Treat this section and the included manifests as an evaluation path, not as validated output. Test it on a separate cluster or through a controlled change to the Terraform state that already owns the Cilium release. Cilium also documents limitations for some advanced Layer 7 capabilities in generic-veth chaining mode, so validate the exact OKE, Kubernetes, OCI CNI, and Cilium versions you plan to run.
The optional evaluation has five steps:
- Install the Gateway API CRDs supported by the selected Cilium version.
- Enable Cilium kube-proxy replacement and Gateway API through the existing
stacks/03-ciliumTerraform state. - Create a
GatewaywithgatewayClassName: ciliumand attach anHTTPRoute. - Confirm that the resulting OCI Load Balancer or Network Load Balancer posture matches the intended subnet, NSG, and exposure model.
- Validate routing, CiliumNetworkPolicy behavior, Hubble evidence, and rollback before adoption.
Copy the baseline tfvars file and change these fields inside its existing cilium object:
kube_proxy_replacement = true
extra_values_yaml = <<-YAML
gatewayAPI:
enabled: true
YAML
Install the matching Gateway API CRDs, then apply the change through the same Terraform workspace that manages Cilium:
GATEWAY_API_VERSION=v1.4.1 # Use the version supported by the Cilium release you validate.
cp envs/oke-vcn-native-cilium-chaining.tfvars \
envs/oke-vcn-native-cilium-gateway-api.tfvars
# Edit the copied file with the two Cilium settings shown above.
kubectl apply --server-side -f \
"https://github.com/kubernetes-sigs/gateway-api/releases/download/${GATEWAY_API_VERSION}/standard-install.yaml"
terraform -chdir=stacks/03-cilium apply \
-var-file=../../envs/oke-vcn-native-cilium-gateway-api.tfvars \
-var="target_cluster_name=oke_cilium_chaining"
The sample repository includes a small web workload, Gateway, HTTPRoute, and CiliumNetworkPolicy. Deploy the workload and manifests only in the separate evaluation environment:
kubectl create namespace gateway-demo
kubectl label namespace gateway-demo \
oke-cilium-sample=true oke-cilium-test=gateway-api
kubectl -n gateway-demo create deployment web \
--image=nginxinc/nginx-unprivileged:1.27-alpine
kubectl -n gateway-demo expose deployment web \
--port=80 --target-port=8080
kubectl apply -f examples/gateway-api/web-gateway.yaml
kubectl apply -f examples/gateway-api/allow-gateway-to-web.yaml
The policy selects the web endpoints and permits only HTTP GET / from Cilium’s special ingress entity to backend port 8080. That rule protects the second leg of the request, from the Cilium Gateway datapath to the backend pod. A broader default-deny design may also need an explicit rule for the first leg from the external world identity to the ingress identity.
Use the following checks as acceptance criteria. Do not copy expected values from another environment:
| Check | Acceptance criterion |
|---|---|
| Gateway controller | The cilium GatewayClass reports Accepted=True. |
| Gateway programming | The Gateway reports Programmed=True and receives the intended address. |
| Route attachment | The HTTPRoute reports Accepted=True and ResolvedRefs=True. |
| OCI exposure | The provisioned load balancer type, subnet placement, listener, and NSGs match the intended public or private posture. |
| HTTP routing | An allowed GET / reaches the backend through the Gateway address. |
| L7 policy | A method or path not allowed by the CiliumNetworkPolicy is rejected. |
| Observability | Hubble records the allowed request and the policy verdict for rejected traffic. |
| Rollback | Disabling Gateway API and restoring the baseline Cilium values returns the cluster to the validated kube-proxy model. |
This keeps the article’s evidence boundary explicit: the OCI CNI and Cilium chaining baseline is proven by the included smoke tests, while Gateway API remains an optional extension that requires its own results before publication as a validated pattern.
Operational guidance
Use this model when you want OCI-native pod addressing and cloud-network visibility while adding Cilium policy and Hubble observability. The operational boundary is important:
- OCI CNI remains the source of truth for pod IP allocation.
- Pod subnet sizing matters because pods consume VCN IP addresses.
- Security rules must allow the worker, pod, API endpoint, and optional load balancer traffic paths that are used by this deployment.
- Cilium must run with
cni.exclusive=falseso it does not take over as the only CNI. ipam.mode=delegated-plugintells Cilium that the base CNI handles IPAM.- Keep kube-proxy in the conservative model unless you explicitly validate kube-proxy replacement for your exact OKE, OCI CNI, and Cilium versions.
- If you enable Cilium Gateway API, treat it as a separate architecture decision because it requires kube-proxy replacement and introduces an Envoy-based ingress datapath.
- For Gateway API, validate Gateway and HTTPRoute status, OCI Load Balancer or Network Load Balancer provisioning, listener security, source IP handling, Hubble flows, and CiliumNetworkPolicy behavior.
- Validate Cilium upgrades together with OKE worker image updates and OCI CNI plugin updates.
Clean up the sample
Use the guarded cleanup orchestrator to preview and then destroy the sample in dependency order:
scripts/destroy-all.sh oke_cilium_chaining --plan-only
scripts/destroy-all.sh oke_cilium_chaining --confirm DESTROY
The script removes validation namespaces, destroys Cilium, deletes the chaining ConfigMap, restores the original subnet security-list associations, and then destroys OKE. It deliberately leaves the prerequisite VCN untouched because the sample normally uses an existing network.
When the deployment used --provision-network, preview and explicitly delete the sample-owned network with the same cleanup entry point:
scripts/destroy-all.sh oke_cilium_chaining \
--plan-only \
--destroy-network
scripts/destroy-all.sh oke_cilium_chaining \
--confirm DESTROY \
--destroy-network
Network deletion is guarded by Terraform ownership: cleanup refuses to proceed unless stacks/00-network contains state for the VCN.
After deleting a sample-owned disposable network, rerun scripts/deploy-and-validate.sh --provision-network to recreate it. For enterprise mode, provide another compatible existing VCN and update the OKE tfvars before rerunning the deployment.
The orchestrator is reusable across environments: pass the kubeconfig context, set TARGET_CLUSTER_NAME only when that context differs from the cluster key in the tfvars file, and use network stack or tfvars overrides only for advanced layouts.
The following commands show the equivalent manual sequence.
The validation scripts intentionally keep their timestamped namespaces for inspection. They label every test namespace with oke-cilium-sample=true, so remove all sample workloads with:
kubectl --context oke_cilium_chaining delete namespace \
-l oke-cilium-sample=true
Destroy the Cilium Helm release before destroying the OKE cluster:
terraform -chdir=stacks/03-cilium destroy \
-var-file=../../envs/oke-vcn-native-cilium-chaining.tfvars \
-var="target_cluster_name=oke_cilium_chaining"
kubectl --context oke_cilium_chaining -n kube-system delete configmap \
cilium-chaining-config --ignore-not-found
If you ran generated/attach-security-lists.sh, review and run the generated restore script before destroying the OKE stack. This detaches Terraform-created security lists from the existing subnets:
sed -n '1,240p' generated/detach-security-lists.sh
generated/detach-security-lists.sh
terraform -chdir=stacks/01-oke destroy \
-var-file=../../envs/oke-vcn-native-cilium-chaining.tfvars
Do not destroy the OKE stack first when its security lists are still attached to existing subnets, because OCI can reject deletion of in-use security lists.
Key benefits
Cloud-native pod addressing
Pods receive IP addresses from OCI VCN subnets, so network teams can reason about pod traffic with familiar VCN constructs.
Kubernetes policy enforcement
Cilium can enforce Kubernetes NetworkPolicy on top of the OCI VCN-native datapath.
Hubble visibility
Hubble gives platform teams flow visibility and endpoint-level context from inside the Kubernetes network layer.
Optional Gateway API evaluation path
After validating the baseline chaining model, teams can separately evaluate Cilium Gateway API for Kubernetes-native HTTP routing and L7 policy.
OCI network-control alignment
VCN subnets, NSGs or security lists, OKE networking, and Cilium/Hubble visibility complement each other rather than replacing one another.
Incremental adoption
This model lets teams keep the OCI-supported VCN-native CNI path while validating Cilium features in a controlled way.
Summary
OKE VCN-native pod networking and Cilium chaining solve different parts of the Kubernetes networking problem. OCI CNI provides pod IP allocation from VCN subnets and keeps pod traffic aligned with OCI routing and security controls. Cilium chaining adds Kubernetes-aware policy, identity, service visibility, and Hubble observability.
The result is a practical design for teams that want OCI-native pod networking without giving up Cilium-based policy and troubleshooting workflows. Start by validating the base OCI CNI, apply the chained CNI ConfigMap, install Cilium with delegated IPAM and native routing, and then prove the result with node readiness, Cilium status, service table, smoke, and NetworkPolicy tests. If you need north-south HTTP routing, add Cilium Gateway API as a separately validated extension and prove it with Gateway status, route status, Hubble flows, and CiliumNetworkPolicy results.
To try the pattern, run the sample’s one-command workflow in an isolated OCI compartment, review the generated PASS or FAIL evidence, and use the guarded cleanup command when validation is complete. After the disposable baseline works, adapt the subnet, NSG, routing, and private-runner choices to your approved OCI landing zone.
References and resources
- Network Resource Configuration for Cluster Creation and Deployment
- Creating an Enhanced Cluster
- Setting Up Cluster Access
- Using the OCI VCN-Native Pod Networking CNI plugin for pod networking
- Cilium Generic Veth Chaining
- Cilium Gateway API Support
- Gateway API getting started
- Cilium Network Policy
- OKE Cilium chaining sample repository
- Sample deployment and validation guide
