Calico on EKS is your network policy enforcement engine, but it’s not just about blocking traffic; it’s about defining intent for how your pods communicate.

Here’s Calico in action on an EKS cluster. We’ll start with a simple setup: two namespaces, app-a and app-b, and deploy identical pods in each.

# deploy-app-a.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-a-deployment
  namespace: app-a
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app-a
  template:
    metadata:
      labels:
        app: app-a
    spec:
      containers:
      - name: app-a-container
        image: nginx:latest
        ports:
        - containerPort: 80

---
# deploy-app-b.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-b-deployment
  namespace: app-b
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app-b
  template:
    metadata:
      labels:
        app: app-b
    spec:
      containers:
      - name: app-b-container
        image: nginx:latest
        ports:
        - containerPort: 80

Apply these:

kubectl apply -f deploy-app-a.yaml
kubectl apply -f deploy-app-b.yaml

By default, with Calico, all pods can talk to each other. Let’s test this. Get the IP of a pod in app-b:

POD_B_IP=$(kubectl get pods -n app-b -o jsonpath='{.items[0].status.podIP}')

Now, try to curl it from a pod in app-a:

kubectl exec -n app-a -it $(kubectl get pods -n app-a -o jsonpath='{.items[0].metadata.name}') -- curl http://$POD_B_IP

You’ll get an HTML response, confirming open communication.

Now, let’s enforce a policy: app-a should only be allowed to talk to app-b.

# allow-app-a-to-app-b.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-app-a-to-app-b
  namespace: app-b # This policy applies to pods in the app-b namespace
spec:
  podSelector: {} # Applies to all pods in app-b
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: app-a # Selects namespaces labeled with name: app-a
    ports:
    - protocol: TCP
      port: 80

Apply this policy:

kubectl apply -f allow-app-a-to-app-b.yaml

With this policy in place, let’s re-run our curl test from app-a to app-b. It will still succeed because we explicitly allowed it.

However, if you try to curl a pod in app-a from app-b (without a corresponding policy allowing it), it will fail. The default is deny when a policy is present.

The core problem Calico solves is providing granular network segmentation within your Kubernetes cluster. Without it, your pods operate in a flat network, meaning any pod can reach any other pod. This is a significant security risk, especially in multi-tenant environments or when running sensitive applications. Calico implements Kubernetes NetworkPolicy objects, translating them into its own rich policy model and enforcing them at the network layer using iptables or eBPF.

The mental model is this: Kubernetes NetworkPolicy objects are the declarative API. Calico is the engine that reads these declarations, compiles them into concrete network rules, and applies them to the network interfaces of your pods. It operates on the principle of least privilege: by default, traffic is denied unless explicitly allowed by a policy.

The podSelector in a NetworkPolicy determines which pods the policy applies to within its namespace. An empty podSelector: {} means the policy applies to all pods in that namespace. The from and to fields, along with namespaceSelector and podSelector, define the source and destination of the allowed traffic.

The most surprising thing most people don’t realize is that a NetworkPolicy that specifies policyTypes: [Ingress] implicitly denies all Egress traffic from the selected pods. If you want to allow pods to send traffic out, you must also define an Egress section in your policy, even if it’s just to allow all egress.

For example, to allow app-a pods to talk to anything else (e.g., external services or pods in other namespaces not explicitly allowed), you’d add an Egress section:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-app-a-egress
  namespace: app-a # This policy applies to pods in the app-a namespace
spec:
  podSelector:
    matchLabels:
      app: app-a # Applies only to pods with label app: app-a
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0 # Allow all egress traffic

Applying this policy to app-a would permit app-a pods to initiate connections to any destination.

The next concept to grapple with is how Calico handles IP address management (IPAM) and its own Custom Resource Definitions (CRDs) for more advanced policies beyond standard Kubernetes NetworkPolicy, such as GlobalNetworkPolicies and Calico-specific NetworkPolicy fields.

Want structured learning?

Take the full Eks course →