Integrating a service mesh into the FluxCD GitOps flow: installing and configuring Istio, Linkerd, and AWS App Mesh, automating traffic management, and observability in the form of tracing and service graphs to support canary analysis.

In episode 17 you learned blue/green and A/B testing with Flagger — traffic split to two application versions, tested, then promoted. At that point Flagger handled the routing, but a question remained: where is the traffic routed and how are service-to-service connection observation, retry, timeout, up to mTLS managed? The answer is a service mesh.
This episode covers service mesh integration with FluxCD. The key point: a mesh is an ordinary Kubernetes application — operators, CRDs, and a control plane — so it can be installed and configured entirely through Git. That means all routing and telemetry policies are versioned like any other manifest.
A service mesh is an infrastructure layer for inter-service communication. Every pod is given a sidecar proxy that intercepts incoming and outgoing traffic, so applications don't need to know how to retry, load balance, handle TLS, or do observability. Its two main components:
| Aspect | Istio | Linkerd | AWS App Mesh |
|---|---|---|---|
| Model | Open source, self-hosted | Open source, lightweight | AWS managed |
| Features | Rich (traffic, mTLS, observability) | Simple and focused | Native AWS, X-Ray |
| Installation | Several charts | Two manifest files | AWS CLI + controller |
| Metrics | Prometheus | Prometheus (viz addon) | CloudWatch |
| Weight routing | VirtualService | TrafficSplit (SMI) | VirtualRouter |
The most GitOps-friendly Istio installation uses the official istio-base, istiod, and gateway charts, declared as a HelmRepository and HelmRelease. With enablePrometheusMerge: true, mesh metrics automatically flow into the Prometheus pipeline.
Tip
The installation order matters: istio-base (CRDs) first, then istiod, then the gateway. Arrange it with dependsOn on the Kustomization as in episode 10, or use dependsOn on the HelmRelease.
Once the charts are in, enable sidecar injection with the istio-injection: enabled label on the namespace, and declare routing through VirtualService (where traffic per host is routed) and DestinationRule (policies like mTLS and load balancing per subset):
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api
namespace: apps
spec:
hosts:
- api.apps.svc.cluster.local
http:
- route:
- destination:
host: api
subset: stable
weight: 90
- destination:
host: api
subset: canary
weight: 10
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: api
namespace: apps
spec:
host: api
subsets:
- name: stable
labels:
version: stable
- name: canary
labels:
version: canary
trafficPolicy:
tls:
mode: ISTIO_MUTUALImportant
Flagger (episodes 15-17) supports Istio and actually manages these VirtualServices and DestinationRules automatically for canaries. The 90/10 split above is an example Flagger can produce. Enable enablePrometheusMerge: true so mesh metrics enter the observability pipeline.
Quick installation verification with the CLI:
istioctl version
istioctl proxy-status
istioctl analyze --namespace appsLinkerd is much lighter. Its installation manifests can be generated and committed directly to Git:
linkerd install --crds > install/linkerd/crds.yaml
linkerd install > install/linkerd/control-plane.yamlFluxCD only needs to point one Kustomization at the install/linkerd folder. This is the most direct example of "mesh as code" — pure YAML from the repo, without an interactive installer.
Linkerd uses ServiceProfile to describe a service's API and TrafficSplit to split traffic between versions at the service level:
apiVersion: split.smi-spec.io/v1alpha2
kind: TrafficSplit
metadata:
name: api
namespace: apps
spec:
service: api
backends:
- service: api-stable
weight: 900m
- service: api-canary
weight: 100mFor observability, add the linkerd-viz addon as a HelmRelease. The dashboard can be accessed with linkerd viz dashboard, and linkerd viz top deploy/api shows real-time latency per deployment.
On AWS, App Mesh offers a managed mesh: the control plane is held by AWS, while the proxy remains Envoy inside the cluster. Setup starts from the mesh and virtual node deployed by FluxCD:
apiVersion: appmesh.k8s.aws/v1beta2
kind: Mesh
metadata:
name: dev-null-mesh
spec:
namespaceSelector:
matchLabels:
mesh: dev-null
---
apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualNode
metadata:
name: api
namespace: apps
spec:
meshName: dev-null-mesh
listeners:
- portMapping:
port: 8080
protocol: http
backends:
- virtualService:
virtualServiceName: orders.apps.svc.cluster.localThe router and route configuration determine where traffic goes:
apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualRouter
metadata:
name: api-router
namespace: apps
spec:
meshName: dev-null-mesh
listeners:
- portMapping:
port: 8080
protocol: http
routes:
- name: api-route
httpRoute:
match:
prefix: /
action:
weightedTargets:
- virtualNodeName: api
weight: 100Tip
App Mesh suits organizations already living in the AWS ecosystem: metrics automatically go to CloudWatch with the meshName and virtualNodeName dimensions, tracing integrates with AWS X-Ray, and IAM controls who can modify the mesh.
A mesh without observability only adds complexity. Four layers to think about:
This episode showed that a service mesh isn't something separate from GitOps — a mesh is just another Kubernetes application installed and configured through Git. We covered Istio installation with HelmRelease, VirtualService and DestinationRule automation, lightweight Linkerd with TrafficSplit, AWS App Mesh with virtual nodes and routers, and observability in the form of tracing, service graphs, and dashboards.
The key takeaways:
With a mesh, traffic can now be finely managed — but this opens a new attack surface. In the next episode, episode 19, we'll discuss securing Flux: authentication, RBAC, network security, and supply chain security with Cosign and admission controllers. See you in episode 19!