Introducktion
The other day, I shared a concern in a WhatsApp group comprised of some super advanced DevOps Engineers as well as aspiring Linux Administrators. There I was, pouring my heart out around the topic of Service Mesh, (Istio, and Linkerd). How all these subjects were all intertwined and downright confusing. In the course of the debate, I’d later realised I would have to throw in a bit of Deployment Strategies just to fully grasp their concepts.
I am glad I started the debate in the whatsapp group, because as thirsty as I was for knowledge, my cry did not fall on deaf ears. Some of the greatest minds in DevOps weighed in on their thoughts and opinion on that matter. We had a healthy debate going on.
Then Matt Saunders, an esteemed member of the WhatsApp group, who is also one of the founders and organisers of the London DevOps meetup group, He’s also a well known author of many DevOps Articles, just like my favourite about Docker Bake, outright suggested that I stick with Linkerd for now.
Matt, in his own words, advised that I would be complicating things should I wish to attempt to study everything all at once. He also debated that not all seasoned Engineers are fond of Istio. so I could learn the ropes by starting with Linkerd till I get the hang of it.
He further suggested that I use various versions of my WebApps to depict the actual release metamorphosis, while demonstrating the evolution from legacy “brownfield” deployments, changing-over to the modern “greenfield” release.
His advice was brilliant. Served as a beacon of hope. The light in the dark tunnel, and honestly, a refreshing icy pool dip, in the middle of a hot Sahara desert. I am truly grateful for his suggestion and the clarity it provided.
But in a true DevOps style, suggestions are useless if you don’t act on them. Which has given birth to this article. This series documents my journey in implementing various deployment strategies using Linkerd.
Canary Deployment in Kubernetes
To kick things off, we are diving straight into Canary Deployments. This strategy allows us to shift a small portion of traffic to a new version of an application, minimizing risk before a full-scale rollout.
My Architecture and honourable mentions
As usual, no prices for guesses but the entire infrastructure would be run and tested in MiniKube. However, should I deviate from it, or also test my theory in another enviroment, other infrasture that I work with and their explanations can be found here - My Set Up
If you need a refresher on the foundational concepts before we jump into the commands, I highly recommend checking out these resources:
The Magic of Version Switching
Before we proceed, lets start with this topic. I’ve often found myself wondering: how exactly does Kubernetes switch traffic from one application version to another? What is the real mechanism behind this “magic”? If I’m going to explain different deployment strategies properly, I need to fully understand what happens behind the scenes — through all the pods, deployments, services, and routing rules.
It turns out the answer lies in traffic routing configuration, specifically with resources like HTTPRoute when using Linkerd. Here is how it works:
- Each version of your application is deployed as a separate Deployment, and exposed via its own Service.
- Both services are then listed as backendRefs inside the HTTPRoute resource.
- Depending on the deployment strategy you want to use, you assign a weight value to each backend. This weight determines what percentage of traffic is sent to each service.
- Finally, the route directs traffic accordingly, and this flow is managed and exposed through your Ingress or Gateway.
In short: the “magic” is nothing more than controlled, rule‑based traffic splitting — and Linkerd makes managing these rules simple and transparent.

So lets proceed.
Step 1 : Install Linkerd in MiniKube
- Download the Linkerd CLI
First, install the Linkerd command-line tool onto your local machine.
curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install | sh
Use code with caution.
Add the linkerd binary to your system’s path:
export PATH=$PATH:$HOME/.linkerd2/bin
Use code with caution.
- Verify Your Minikube Cluster
Make sure your Minikube cluster is running and properly configured for Linkerd by performing a pre-check:
#start minikube
minikube start
#test Linkerd
linkerd check --pre
Use code with caution.
- Install the Control Plane
Install the Custom Resource Definitions (CRDs) and the Linkerd control plane onto your active Kubernetes context:
# Install Linkerd CRDs
linkerd install --crds | kubectl apply -f -
# Install Linkerd control plane
linkerd install | kubectl apply -f -
linkerd install --set proxyInit.runAsRoot=true | kubectl apply -f -
# Install Linkerd Viz (optional but helpful for visualization)
linkerd viz install | kubectl apply -f -
linkerd viz check
# Install Gateway API CRDs
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml
Use code with caution.
- Validate the InstallationConfirm that all Linkerd control plane pods are spinning up correctly:
linkerd check
Use code with caution.
the head of linkerd check

and the tail of it

So, whats next ?
Ok, if you have made it this far, you have successfully created a Linkerd environment on Kubernetes Minikube. If you wish to install Linkerd for your Minikube setup, and you find the previous stipulated steps a bit tedious, you can just run the Setup-Linkerd-on-Minikube.sh script provided at our Halfknown Dairies GitHub repository.
You would also find the prospective Kubernetes YAML files for this canary deployment in the same repository, to save you the trouble of typing everything out.
Now that we have have Minikube up and running with Linkerd all setup, we can now proceed to running the deployment strategy.
The Files
For this demonstration and deployment, I have created : { LINK - Available on GitHub }
- Canary-deployment.yaml
- Ingress.yaml
- Service.yaml
- Stable-deploment.yaml
- Traffic-split.yaml
How the Canary process works or flows
To set up an Ingress for your Canary deployment, you follow a two-layer approach:
The Ingress Controller: Acts as the entry point (North-South traffic).
Linkerd: Manages the routing inside the cluster (East-West traffic, including your canary split).
The steps are :
User hits the URL ( http://maltina.app1/).
NGINX Ingress receives the request and forwards it to a parent Service (In my case, webapp-svc)
Linkerd Sidecar (in the ingress pod) intercepts the request and checks the HTTPRoute rules.
Linkerd routes the traffic based on your 90/10 weight split to either webapp-v4-svc or webapp-v5-svc within the parent service.
So, run the Deployment.
First, I like to keep things tidy so lets create a namespace.
dont ask me why, its the first namespace name that got into my head.
# create namespace
kubectl create ns canary-wharf
# Enable automatic Linkerd proxy injection
kubectl label namespace canary-wharf linkerd.io/inject=enabled

Then, apply all YAML files
kubectl apply -f canary-deployment.yaml -n canary-wharf
kubectl apply -f ingress.yaml -n canary-wharf
kubectl apply -f service.yaml -n canary-wharf
kubectl apply -f stable-deployment.yaml -n canary-wharf
kubectl apply -f traffic-split.yaml -n canary-wharf
# Wait for deployments to be ready
kubectl -n canary-wharf rollout status deployment/webapp-v4
kubectl -n canary-wharf rollout status deployment/webapp-v5

Check them out !!
# list all pods in the namespace
kubectl get pods -n canary-wharf
# list services
kubectl get svc -n canary-wharf
# list all pods, services and ingress
kubectl get all,ing -n canary-wharf

I initially thought of re-cycling the hostname maltina.app1 based on the past article For the Helm of it. When we populated the /etc/hosts file with a MiniKube IP address and the desired hostname maltina to demonstrate the creation of 2 applications using helm charts templates.
My new MiniKube, shows an IP address of 192.168.58.2 so to be on the safe side, we would repopulate the hosts file, with the current IP.
# Access via Minikube
echo "$(minikube ip) maltina.app1" | sudo tee -a /etc/hosts
curl http://maltina.app1
Time to Mesh them up
First, annotate each deployment then roll out
# annotate
kubectl annotate ns canary-wharf linkerd.io/inject=enabled --overwrite
kubectl annotate deployment/webapp-v4 -n canary-wharf linkerd.io/inject=enabled --overwrite
kubectl annotate deployment/webapp-v5 -n canary-wharf linkerd.io/inject=enabled --overwrite
# might not be necessary but
# Annotate all 3 services
kubectl annotate svc -n canary-wharf \
webapp-svc \
webapp-v4-svc \
webapp-v5-svc \
linkerd.io/description="webapp canary services" \
--overwrite
# Now roll out
kubectl rollout restart deployment webapp-v4 -n canary-wharf
kubectl rollout restart deployment webapp-v5 -n canary-wharf

If Linkerd injection fails, force manual injection.
# Export the deployment manifests
kubectl -n canary-wharf get deployment webapp-v4 -o yaml > webapp-v4.yaml
kubectl -n canary-wharf get deployment webapp-v5 -o yaml > webapp-v5.yaml
# Inject the proxy using linkerd
linkerd inject webapp-v4.yaml | kubectl apply -f -
linkerd inject webapp-v5.yaml | kubectl apply -f -
# Wait for the new pods
kubectl -n canary-wharf rollout status deployment/webapp-v4
kubectl -n canary-wharf rollout status deployment/webapp-v5
Now some Linkerd to verify it’s working
Once applied, you can use the Linkerd CLI to see the traffic distribution in real-time:
I got some various Linkerd viz stat to verify linkerd is up and running.
linkerd viz stat ns/canary-wharf
linkerd viz stat namespaces
linkerd viz stat deployments -n canary-wharf
linkerd viz stat pods -n canary-wharf
linkerd viz stat svc -n canary-wharf
Here are some screenshots of Linkerd stats of namespaces, deployments and pods

Use linkerd viz tap to see live requests
linkerd viz tap deployment/webapp-v4 -n canary-wharf
linkerd viz tap deployment/webapp-v5 -n canary-wharf
Using a split terminal to view live requests.

Constant refresing of the web browser querying
http://maltina.app1/, and :
Hurray… Ingress works, here is version 4 the stable version.
still refreshing the browser and viewing the live requests on the terminal, and :
Here also is the screenshot of version 5 the canary version

So it works, now what ? Run further tests
By now, we have managed to prove, the north to south network traffic :
External(User) → Ingress → webapp-svc → Linkerd HTTPRoute → 90% v4 / 10% v5
And we can test it by running this script in a terminal while either observing the split terminal showing the output of both linkerd viz tap deployment commands, or watch the output on a new terminal.
while true; do
curl -s http://maltina.app1 | grep -E "Version|v4|v5|Hello|JayJay"
sleep 0.5
done
Terminal output :

Commands to Update Weights and trafiic
To push it to a 70% v4 / 30% v5 split. They must both add up to 100%
# Example: Move to 70% stable / 30% canary
kubectl patch httproute webapp-route -n canary-wharf --type merge -p '
{
"spec": {
"rules": [
{
"backendRefs": [
{"name": "webapp-v4-svc", "port": 80, "weight": 70},
{"name": "webapp-v5-svc", "port": 80, "weight": 30}
]
}
]
}
}'

To verify the patch had taken effect. Run this to test
# Check the route definition
kubectl get httproute webapp-route -n canary-wharf -o yaml | grep -A 5 backendRefs
Part of the Screenshot

Other Weight Increments
# 50 / 50 split
kubectl patch httproute webapp-route -n canary-wharf --type merge -p '{"spec":{"rules":[{"backendRefs":[{"name":"webapp-v4-svc","port":80,"weight":50},{"name":"webapp-v5-svc","port":80,"weight":50}]}]}}'
# 100% to canary (full rollout)
kubectl patch httproute webapp-route -n canary-wharf --type merge -p '{"spec":{"rules":[{"backendRefs":[{"name":"webapp-v4-svc","port":80,"weight":0},{"name":"webapp-v5-svc","port":80,"weight":100}]}]}}'
# Rollback to original stable
kubectl patch httproute webapp-route -n canary-wharf --type merge -p '{"spec":{"rules":[{"backendRefs":[{"name":"webapp-v4-svc","port":80,"weight":90},{"name":"webapp-v5-svc","port":80,"weight":10}]}]}}'
.
My Final Thoughts and Conclusion
While this project successfully demonstrates the core functionality of Linkerd Service Mesh, there are a few observations and points that still leave me with some questions or concerns:
Areas of Uncertainty
- The
linkerd-proxysidecar container was not visible throughout the process Although the service mesh appears to be functioning correctly across the cluster, the sidecar container itself was never listed as an additional container inside any pod. This leads to two key limitations:
- When querying the number of containers in a pod, only the main application container is returned — no linkerd-proxy container is shown. For instance, the command :
kubectl get pods -n canary-wharf -o custom-columns=NAME:.metadata.name,CONTAINERS:.spec.containers[*].name

- Because the sidecar is not visible, it is currently impossible to directly retrieve or inspect metrics from it, as you would normally do with a standard sidecar deployment.
Positive Outcomes & Observations
Linkerd Canary Deployment works as expected Despite the above point, the canary deployment feature operates correctly. Linkerd is able to collect and display valid traffic and performance statistics from:
- Individual Pods
- Deployments
- Entire Namespaces
Successful Implementation Highlights On the whole, the setup is fully functional and meets all intended requirements:
- Complete, working architecture successfully deployed. Meaning :
Browser/Curl → maltina.app1 → Minikube Ingress → webapp-svc → Linkerd HTTPRoute → 90% v4 / 10% v5
- All relevant pods have been successfully “meshed”
- Traffic splitting and routing policies are fully controlled by Linkerd
- All expected metrics are visible and accessible via the Linkerd dashboard
- No additional extensions or third‑party components were required to achieve this
Overall summary: While the visibility of the linkerd-proxy remains a technical oddity that warrants further investigation, the service mesh itself has been successfully deployed and delivers all its core capabilities reliably.