Skip to content

Calico How-to Guides

Scope

Task recipes for Calico Open Source 3.32 (v3.32.2): install, switch data planes, enable flow logs, set up the ingress gateway, encrypt traffic, peer with BGP, write policies, upgrade, and troubleshoot. Background is in Explanation; defaults, ports and keys are in Reference.

Version-specific commands

Commands below pin v3.32.2. From 3.32 the operator manifest no longer bundles the CRDs, so you apply v1_crd_projectcalico_org.yaml first. For 3.31 the CRD file is operator-crds.yaml. Use kubectl create (or kubectl apply --server-side) because the CRD bundle is too large for client-side kubectl apply.

Deployment

Install with the Tigera Operator (manifests)

This is the recommended path. The default custom-resources.yaml creates Installation, APIServer, Goldmane and Whisker, with a 192.168.0.0/16 pool using VXLANCrossSubnet.

# 1. Calico CRDs (3.32+), 2. operator, 3. custom resources
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.2/manifests/v1_crd_projectcalico_org.yaml
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.2/manifests/tigera-operator.yaml
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.2/manifests/custom-resources.yaml

# Wait until every component reports AVAILABLE=True
watch kubectl get tigerastatus

To change the pod CIDR or encapsulation, download custom-resources.yaml and edit the Installation before creating it:

apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
  name: default
spec:
  calicoNetwork:
    ipPools:
    - name: default-ipv4-ippool
      blockSize: 26
      cidr: 10.244.0.0/16
      encapsulation: VXLANCrossSubnet
      natOutgoing: Enabled
      nodeSelector: all()

Install with Helm

helm repo add projectcalico https://docs.tigera.io/calico/charts
kubectl create namespace tigera-operator

# CRDs ship in a companion chart since 3.32
helm template calico-crds projectcalico/crd.projectcalico.org.v1 --version v3.32.2 \
  | kubectl apply --server-side -f -

helm install calico projectcalico/tigera-operator --version v3.32.2 --namespace tigera-operator
watch kubectl get pods -n calico-system

On EKS, GKE, AKS or MKE, pass a values.yaml that sets installation.kubernetesProvider (for example echo '{ installation: {kubernetesProvider: EKS }}' > values.yaml) and add -f values.yaml. Charts are also pushed to the OCI registry quay.io/calico/charts since 3.32.

Install with native v3 CRDs (tech preview)

Native v3 CRDs replace the aggregated calico-apiserver. They need the Kubernetes MutatingAdmissionPolicy API.

helm template calico-crds projectcalico/projectcalico.org.v3 --version v3.32.2 \
  | kubectl apply --server-side -f -
helm install calico projectcalico/tigera-operator --version v3.32.2 --namespace tigera-operator

Existing clusters migrate with a DatastoreMigration resource named v1-to-v3 (CRD manifest manifests/migration.projectcalico.org_datastoremigrations.yaml since 3.32.2). Follow the official "Migrate from API server to native CRDs" guide, because the datastore is briefly locked during the migration.

Install calicoctl

curl -L https://github.com/projectcalico/calico/releases/download/v3.32.2/calicoctl-linux-amd64 -o calicoctl
chmod +x calicoctl && sudo mv calicoctl /usr/local/bin/
calicoctl version

Since 3.32.2 calicoctl is also packaged as .deb and .rpm in the Calico package repositories. With the API server or native v3 CRDs, kubectl can manage projectcalico.org/v3 resources directly.

Switch Data Planes

Enable eBPF mode (operator, automatic)

For kubeadm-style clusters whose kube-proxy is not managed by Helm or Argo CD, the operator configures API server access and disables kube-proxy itself (3.31+):

kubectl patch installation.operator.tigera.io default --type merge \
  -p '{"spec":{"calicoNetwork":{"linuxDataplane":"BPF","bpfNetworkBootstrap":"Enabled","kubeProxyManagement":"Enabled"}}}'

For a new cluster, create custom-resources-bpf.yaml instead of custom-resources.yaml:

kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.2/manifests/custom-resources-bpf.yaml

Node port disruption

Nodes switch one at a time, so traffic through NodePorts can be disrupted briefly. On kernels with BTF, VXLAN connections do not survive the switch, because Felix recreates vxlan.calico in flow mode.

Enable eBPF mode (manual path)

On other clusters, first point Calico at the API server directly (the kubernetes-services-endpoint ConfigMap in the operator namespace, per the official guide), then:

# Operator installs: switch the data plane
kubectl patch installation.operator.tigera.io default --type merge \
  --patch='{"spec":{"calicoNetwork":{"linuxDataplane":"BPF"}}}'

# Manifest installs: set it on Felix instead
calicoctl patch felixconfiguration default --type='merge' -p '{"spec":{"bpfEnabled":true}}'

# Stop kube-proxy by giving it a node selector that matches no nodes
kubectl patch ds -n kube-system kube-proxy \
  -p '{"spec":{"template":{"spec":{"nodeSelector":{"non-calico":"true"}}}}}'

For better external-traffic performance, turn on direct server return:

kubectl patch felixconfiguration default --type='merge' -p '{"spec":{"bpfExternalServiceMode":"DSR"}}'

Enable Maglev load balancing for a Service (eBPF, 3.32+)

Requires eBPF in DSR mode; applies to external traffic only (not pod-to-Service, NodePort or Services with External Traffic Policy set).

kubectl annotate service my-svc lb.projectcalico.org/external-traffic-strategy=maglev

Use the nftables data plane

kube-proxy must also run in nftables mode (Kubernetes 1.31+; mode: nftables in KubeProxyConfiguration). Then set the data plane in the Installation:

apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
  name: default
spec:
  calicoNetwork:
    linuxDataplane: Nftables
    ipPools:
    - name: default-ipv4-ippool
      cidr: 192.168.0.0/16
      encapsulation: VXLANCrossSubnet
      natOutgoing: Enabled

Enable Flow Logs (Goldmane and Whisker)

New 3.30+ installs include both. Clusters upgraded from 3.29 or earlier need the custom resources (operator or Helm installs only):

kubectl apply -f - <<EOF
apiVersion: operator.tigera.io/v1
kind: Goldmane
metadata:
  name: default
---
apiVersion: operator.tigera.io/v1
kind: Whisker
metadata:
  name: default
EOF

# Open the console at http://localhost:8081
kubectl port-forward -n calico-system service/whisker 8081:8081

Do not expose Whisker publicly without authentication. Calico installs policies that deny other ingress to it.

Set Up Calico Ingress Gateway

# Deploy Envoy Gateway and the tigera-gateway-class GatewayClass
kubectl apply -f - <<EOF
apiVersion: operator.tigera.io/v1
kind: GatewayAPI
metadata:
  name: default
EOF
kubectl get gatewayclass
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: web
  namespace: demo
spec:
  gatewayClassName: tigera-gateway-class
  listeners:
  - name: http
    protocol: HTTP
    port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: web
  namespace: demo
spec:
  parentRefs:
  - name: web
  rules:
  - backendRefs:
    - name: frontend
      port: 8080

Gateway API CRD versions

3.31.6+ and 3.32.1+ bundle Envoy Gateway 1.8, which needs Gateway API v1.5.1 CRDs. If the operator manages them (the default), set crdManagement: Reconcile on the GatewayAPI resource before upgrading; if you manage them yourself, install v1.5.1 first.

Encrypt Traffic with WireGuard

# IPv4 and IPv6 inter-node encryption
kubectl patch felixconfiguration default --type='merge' \
  -p '{"spec":{"wireguardEnabled":true,"wireguardEnabledV6":true}}'

# Verify: each node publishes a WireGuard public key
calicoctl get node <node-name> -o yaml | grep -i wireguardPublicKey

# EKS/AKS with the cloud CNI only: also encrypt host-network traffic
calicoctl patch felixconfiguration default --type='merge' -p '{"spec":{"wireguardHostEncryptionEnabled":true}}'

Review the MTU afterwards; WireGuard adds overhead per packet.

BGP Peering

Peer a rack of nodes with a top-of-rack router:

apiVersion: projectcalico.org/v3
kind: BGPPeer
metadata:
  name: rack-peer
spec:
  peerIP: 10.0.0.1
  asNumber: 64512
  nodeSelector: rack == 'rack1'

Above about 100 nodes, disable the full mesh and use route reflectors:

apiVersion: projectcalico.org/v3
kind: BGPConfiguration
metadata:
  name: default
spec:
  nodeToNodeMeshEnabled: false
  asNumber: 64512

Since 3.31 a BGPPeer can set localASNumber to use a different local ASN for one external peer.

Network Policies

Default deny

# Calico GlobalNetworkPolicy: deny all ingress by default
apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
  name: default-deny
spec:
  selector: all()
  types:
    - Ingress

Exclude system namespaces

selector: all() also selects kube-system and calico-system pods. In production use a selector such as projectcalico.org/namespace not in {'kube-system', 'calico-system'} or add explicit allow rules first.

Tiered allow policy

apiVersion: projectcalico.org/v3
kind: Tier
metadata:
  name: application
spec:
  order: 500
---
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
  name: allow-frontend
  namespace: default
spec:
  tier: application
  selector: app == 'backend'
  ingress:
    - action: Allow
      source:
        selector: app == 'frontend'
      destination:
        ports:
          - 8080

Since 3.32 the policy name no longer needs the application. tier prefix.

Test a policy before enforcing it

Create the policy as StagedNetworkPolicy (same spec), watch its verdicts in Whisker, then apply it as NetworkPolicy.

Cluster-wide guardrail with ClusterNetworkPolicy (3.32+)

Use the upstream policy.networking.k8s.io/v1alpha2 ClusterNetworkPolicy with tier: Admin (cannot be overridden by namespaces) or tier: Baseline (default that namespaces can override). Remove any AdminNetworkPolicy or BaselineAdminNetworkPolicy objects before upgrading to 3.32, which no longer enforces them.

Upgrade Calico

Operator installs:

curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.32.2/manifests/v1_crd_projectcalico_org.yaml
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.32.2/manifests/tigera-operator.yaml
kubectl apply --server-side --force-conflicts -f v1_crd_projectcalico_org.yaml
kubectl apply --server-side --force-conflicts -f tigera-operator.yaml
watch kubectl get tigerastatus

Helm installs: apply the new CRDs first (Helm does not upgrade CRDs), then upgrade the chart:

helm template calico-crds projectcalico/crd.projectcalico.org.v1 --version v3.32.2 \
  | kubectl apply --server-side --force-conflicts -f -
helm upgrade calico projectcalico/tigera-operator --version v3.32.2 --namespace tigera-operator

Upgrade checklist for 3.32

Replace AdminNetworkPolicy/BaselineAdminNetworkPolicy with ClusterNetworkPolicy; update anything that references the allow-tigera tier (now calico-system); upgrade Gateway API CRDs to v1.5.1 if you use the ingress gateway; move Helm-managed CRDs to the companion CRD chart.

Common Issues

Issue Diagnosis Fix
Pod connectivity fails calicoctl node status, kubectl get tigerastatus Check Felix health, BGP sessions, IP pool encapsulation
Policy not applied calicoctl get workloadendpoints -A Verify label selectors, tier order and Pass actions
Traffic unexpectedly denied Whisker flow logs filtered by verdict deny Read the policy trace; add an allow rule or Pass in the right tier
IP exhaustion calicoctl ipam show --show-blocks Add an IP pool or enlarge the CIDR; check for leaked IPs with calicoctl ipam check
eBPF conntrack map full calico-node -bpf conntrack dump (JSON output since 3.32) Increase bpfMapSizeConntrack, or rely on bpfMapSizeConntrackScaling: DoubleIfFull
Interfaces unmanaged on the node NetworkManager owns cali* / vxlan.calico Configure NetworkManager to ignore Calico interfaces
Ingress Gateway crash-loops on OpenShift or GKE Missing ListenerSet, TLSRoute or BackendTLSPolicy CRDs Upgrade to 3.31.7 / 3.32.2 or install the missing CRDs

Commands & Recipes

Diagnostics

# Node status and BGP peers
calicoctl node status

# Component health (operator installs)
kubectl get tigerastatus

# Policies, endpoints and pools
calicoctl get networkpolicy -A -o wide
calicoctl get globalnetworkpolicy -o yaml
calicoctl get workloadendpoint -A
calicoctl get ippool -o wide

# IPAM usage and consistency
calicoctl ipam show --show-blocks
calicoctl ipam check

# Collect a diagnostics bundle for the whole cluster
calicoctl cluster diags

eBPF inspection

# Run inside a calico-node pod in eBPF mode
kubectl exec -n calico-system ds/calico-node -c calico-node -- calico-node -bpf nat dump
kubectl exec -n calico-system ds/calico-node -c calico-node -- calico-node -bpf conntrack dump
kubectl exec -n calico-system ds/calico-node -c calico-node -- calico-node -bpf policy dump eth0 all

Goldmane API access

# Goldmane requires mTLS; fetch client credentials from calico-node's certs
kubectl get secret -n calico-system node-certs --template='{{index .data "tls.key"}}' | base64 -d > tls.key
kubectl get secret -n calico-system node-certs --template='{{index .data "tls.crt"}}' | base64 -d > tls.crt
kubectl get secret -n calico-system goldmane-key-pair --template='{{index .data "tls.crt"}}' | base64 -d > ca.crt
kubectl port-forward -n calico-system svc/goldmane 7443:7443

Sources