u/Express_Text_8165

backendRefs is a list, but not the kind of list you think — the Gateway API trap that fails silently

If you're writing an HTTPRoute and need two paths served by different services, backendRefs looks like the obvious place to add a second entry.

That's a super easy mistake to make, and it fails silently.

Here's the baseline shape from the official docs:

rules:
- matches:
  - path:
      type: PathPrefix
      value: /api
  backendRefs:
  - name: video-svc
    port: 8080

Now you need /api/video and /api/eat handled by two different services. Under time pressure, you just append a second entry:

backendRefs:
  - name: video-svc
    port: 8080
  - name: eat-svc
    port: 8080

This does not route paths independently. It creates a weighted traffic split. One request goes to exactly one backend, chosen via round-robin. If you curl that endpoint 20 times, video-svc gets about 10 requests and eat-svc gets the other 10.

What backendRefs list is actually for

Traffic splitting: canaries, blue/green cutovers, and progressive rollouts.

backendRefs:
  - name: api-v1
    port: 8080
    weight: 90
  - name: api-v2
    port: 8080
    weight: 10

Changing those numbers handles your rollout without touching pods:

  • 100 / 0 -> all traffic to v1, v2 idle
  • 90 / 10 -> canary testing
  • 50 / 50 -> halfway through cutover
  • 0 / 100 -> v2 live, v1 kept as your instant rollback switch

Two quirks worth knowing about weights:

  1. They're relative, not percentages. They don't need to sum to 100. weight: 3 and weight: 1 means a 75/25 split.
  2. The split is per-request, not sticky per-user. The same client can hit v1 then v2 on the very next call. Fine for canary, but weights alone aren't real A/B testing (which needs header matching for sticky sessions).

Why this bug bites so hard

Three things stack up to make this brutal to debug:

  1. Nothing in the YAML signals a split. Two entries under backendRefs looks normal whether you intended path routing or traffic splitting.
  2. weight defaults to 1. Omitting weight doesn't opt you out of a split, it creates an even 50/50 split.
  3. Nothing errors out. No CrashLoopBackOff, no 503, no warning in kubectl describe. Route status literally reports Accepted: True and ResolvedRefs: True because both backends actually exist. Requests just land on the wrong service half the time. That's the exact profile of a bug you spend a whole day blaming on DNS or caching.

The correct way to handle multiple paths

Use separate rules, not multiple backendRefs inside one rule:

rules:
- matches:
  - path: {type: PathPrefix, value: /api/video}
  backendRefs:
  - name: video-svc
    port: 8080
- matches:
  - path: {type: PathPrefix, value: /api/eat}
  backendRefs:
  - name: eat-svc
    port: 8080

Reusing a Service across rules is totally fine video-svc can back both /api/video and /see/video.

Two match behaviors worth remembering:

  • rules are not evaluated top-to-bottom. Ingress was first-match-wins, so people order them defensively. Gateway API scores matches by specificity: exact matches beat prefixes, and longer prefixes beat shorter ones. A bare /api rule sitting above /api/video won't shadow it.
  • matches logic: Multiple entries under matches act as an OR condition. But multiple conditions inside a single match entry (e.g., path + method + headers) act as an AND. One dash is the difference.

CKA / Exam Takeaways

  • Separate paths mean separate rules**:** CKA path-routing tasks will ask you to direct distinct paths to distinct services (e.g., /api to svc-a and /web to svc-b). Never add multiple backendRefs under a single rule for this — that creates a traffic split instead of path routing.
  • Don't quick-copy individual refs: Under time pressure, appending a second backendRef to an existing block is a fast way to lose points. Always copy and adjust the full rule block.
  • Rule order doesn't matter: Unlike classic Ingress (where first match wins), Gateway API matches by specificity. Exact matches beat prefixes, and longer prefixes beat shorter ones. Placing a / rule at the top won't shadow /api below it. Don't waste time reordering YAML entries.
  • Verify Service ports: port in a backendRef must reference the Service port — not the targetPort, container port, or Gateway listener port. Always verify with kubectl get svc.
  • Verify with curl**, not** kubectl**:** Route status will show Accepted: True even if your path logic sends traffic to the wrong service. Run a quick curl loop to confirm requests land where they belong.
reddit.com
u/Express_Text_8165 — 19 hours ago

Don't Fail the CKA Network Policy Question (Calico Setup)

You might get a question of this during the exam:

Install a CNI plugin. Choose one of the following: Flannel (v0.26.1) or Calico (v3.28.2)

with a requirement about network policy enforcement.

Flannel doesn't do network policy. That's not something you work out during the exam, it's something you know before you sit down. You see Flannel, you pick the other one, you move on.

That's the easy part. Everybody gets that part right. Then you run the right command, you watch it say created, and you still lose the marks. So here's what to actually look out for.

Step 1 — get the podSubnet before you install anything

k get cm kubeadm-config -n kube-system -o yaml | grep podSubnet

Depends on the cluster config, but you'll get something like:

podSubnet: 10.244.0.0/16

podSubnet is what the cluster hands out. The Calico manifest ships with its own default and it has never seen your cluster. This value got set when somebody ran kubeadm init, probably months ago, probably not by you. It's the range every pod IP on this cluster comes from, and the CNI you're about to install has to agree with it. Read the number now, before you install anything.

Step 2 — install the operator

you don't need those urls memorized. the exam gives you what you need in the question itself.

k create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.2/manifests/tigera-operator.yaml

Notice I used create here, not apply. The CRDs bundled inside that operator file are massive. If you use apply, kubectl tries to shove the whole file into a last-applied-configuration annotation, blows past the 256k limit, and throws an error right in your face. Use create and keep moving.

Don't believe me? Here's the output:

root@controlplane:~$ kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.2/manifests/tigera-operator.yaml
namespace/tigera-operator created
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgpfilters.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgppeers.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/blockaffinities.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/caliconodestatuses.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/clusterinformations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworksets.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamblocks.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamconfigs.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamhandles.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipreservations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/kubecontrollersconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networksets.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/apiservers.operator.tigera.io created
customresourcedefinition.apiextensions.k8s.io/imagesets.operator.tigera.io created
customresourcedefinition.apiextensions.k8s.io/tigerastatuses.operator.tigera.io created
serviceaccount/tigera-operator created
clusterrole.rbac.authorization.k8s.io/tigera-operator created
clusterrolebinding.rbac.authorization.k8s.io/tigera-operator created
deployment.apps/tigera-operator created
The CustomResourceDefinition "installations.operator.tigera.io" is invalid: metadata.annotations: Too long: may not be more than 262144 bytes

Step 3 — the operator is not the CNI

This is the one that got me. The operator is the thing that installs the CNI. It is not the CNI. It's just a controller sitting there waiting to be told what to build, and nothing has told it anything yet. No Calico, no CNI binary on disk, and your nodes are still NotReady.

You can check it yourself:

k get pods -n tigera-operator    # No resoucrce found
k get pods -n calico-system      # No resources found

That's what a half-finished install looks like.

Step 4 — download the custom resources and check the CIDR

you don't need those urls memorized. the exam gives you what you need in the question itself.

curl -sLO https://raw.githubusercontent.com/projectcalico/calico/v3.28.2/manifests/custom-resources.yaml

This is the file that actually tells the operator what to install. Don't pipe it straight into kubectl create. Download it, because you want to look at it first.

# This section includes base Calico installation configuration.
# For more information, see: https://docs.tigera.io/calico/latest/reference/installation/api#operator.tigera.io/v1.Installation
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
  name: default
spec:
  # Configures Calico networking.
  calicoNetwork:
    ipPools:
    - name: default-ipv4-ippool
      blockSize: 26
      cidr: 192.168.0.0/16   <-------------Here is what u need to change
      encapsulation: VXLANCrossSubnet
      natOutgoing: Enabled
      nodeSelector: all()

---

# This section configures the Calico API server.
# For more information, see: https://docs.tigera.io/calico/latest/reference/installation/api#operator.tigera.io/v1.APIServer
apiVersion: operator.tigera.io/v1
kind: APIServer
metadata:
  name: default
spec: {}

Final Step:

k create -f custom-resources.yaml

And now the operator actually has its instructions.

Step 5 — verify

k get tigerastatus
k get pod test-pod -o wide

There you go that is the whole process, I hope that will help you guys

u/Express_Text_8165 — 14 days ago