Part 4 of 616 min read · 12 diagramsAI-assisted

Service Mesh, etcd & Operators

Table of Contents#

  1. The Problem a Service Mesh Actually Solves
  2. The Sidecar Pattern, Fully Explained
  3. Service Mesh Architecture: Data Plane vs Control Plane
  4. mTLS — Automatic, Zero-Code Encryption Everywhere
  5. Traffic Management — Canary Releases Without Application Changes
  6. Observability for Free — Revisited
  7. The Real Cost of a Service Mesh
  8. etcd — Operational Depth
  9. etcd Compaction and Defragmentation
  10. A Real etcd Disaster Recovery Drill
  11. Custom Resource Definitions (CRDs) — Extending the API
  12. The Operator Pattern
  13. A Worked Operator Example
  14. Helm — Packaging Kubernetes Applications
  15. Common Mistakes
  16. Worked Practice Problems
  17. Summary and What's Next

The Problem a Service Mesh Actually Solves#

Part 3 covered Services and how basic request routing works inside a cluster. But real production microservice architectures need more than "can this reach that" — they need encryption, retries, fine-grained traffic control, and observability, consistently, across every single service, without every single application team having to reimplement the same logic in their own code.

Diagram

The Sidecar Pattern, Fully Explained#

This is the exact mechanical foundation a service mesh is built on — and it's a direct, concrete application of the "containers in the same pod share a network namespace" fact from Part 3.

Diagram

The core trick, worth stating explicitly: iptables rules are configured (usually automatically, via an init container) inside the pod's network namespace to transparently redirect ALL inbound and outbound traffic through the sidecar proxy — the application code doesn't need to know this is happening at all; it just makes what looks like a completely normal network call, and the sidecar silently intercepts it, applies mTLS/retries/metrics, and forwards it on.


Service Mesh Architecture: Data Plane vs Control Plane#

Exactly the same conceptual split as Kubernetes's own architecture from Part 1, applied one layer up.

Diagram

Simple analogy: the control plane is like a company's central policy office, writing the actual rules ("all deliveries must be insured, all packages logged"). The data plane is every individual delivery driver (sidecar) actually following those rules on every single delivery, in real time — the office doesn't personally handle any package; it just tells every driver what the rules are.

A commonly-cited real name worth knowing: Envoy is the dominant sidecar proxy implementation, used as the actual data plane by both Istio and (in some configurations) other mesh products — genuinely worth recognizing by name, since it comes up frequently.


mTLS — Automatic, Zero-Code Encryption Everywhere#

mTLS (mutual TLS) means both sides of a connection prove their identity to each other via certificates, not just the server proving its identity to the client (as in standard, one-way TLS from the Linux & Networking Fundamentals series).

Diagram

Why this is such a genuinely strong, concrete "what does a service mesh give you for free" answer, directly extending the STRIDE threat modeling from the DevSecOps series: mTLS between every service directly defends against Spoofing (a rogue pod can't successfully impersonate a legitimate service without a valid certificate) and Information Disclosure (all inter-service traffic is encrypted in transit, even inside the cluster's own internal network) — and it's applied automatically, uniformly, to every single service in the mesh, with zero application code ever needing to implement TLS logic itself.


Traffic Management — Canary Releases Without Application Changes#

A genuinely powerful, concrete capability worth demonstrating with an actual example — this directly ties into deployment strategies covered in the Automation/CI-CD topic.

# An Istio VirtualService splitting traffic between two versions
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: checkout
spec:
  hosts:
    - checkout
  http:
    - route:
        - destination:
            host: checkout
            subset: v1
          weight: 90
        - destination:
            host: checkout
            subset: v2
          weight: 10
Diagram

Why this is such a strong capability, worth stating explicitly: this canary rollout happens entirely at the infrastructure/mesh layer, with ZERO changes to the application code itself — the application has no idea it's v1 or v2 of a canary split; the mesh is silently, transparently splitting traffic based purely on this declarative configuration, which can be adjusted live (10% -> 50% -> 100%) without redeploying anything.


Observability for Free — Revisited#

This tutorial series already introduced this idea in the Monitoring Methodologies series (Part 1): because every single request flows through a sidecar proxy, RED metrics (Rate, Errors, Duration) can be emitted automatically for every service, with zero application instrumentation required.

Diagram

Why this is worth restating here, one more time, tied to the full architecture just explained: this isn't magic — it's a direct, mechanical consequence of the sidecar pattern: since literally 100% of network traffic to and from a pod already passes through its sidecar, that sidecar is perfectly positioned to observe and report on every single request, uniformly, across every language and framework a team might use, without a single line of application-level instrumentation code.


The Real Cost of a Service Mesh#

A balanced, senior-level answer names the tradeoffs, not just the benefits — a genuinely common, important interview follow-up.

Diagram

A genuinely strong, balanced interview answer: "I'd adopt a service mesh when the organization has enough services that consistent mTLS, retries, and observability genuinely can't be reasonably maintained per-team in application code anymore — but I wouldn't reach for it by default on a small handful of services, where the added latency, resource overhead, and operational complexity of running the mesh itself can outweigh the benefit."


etcd — Operational Depth#

Part 1 introduced etcd as Kubernetes's source of truth, using Raft consensus and requiring a quorum. This section goes deeper into the operational realities of actually running it well.

Diagram

Why the watch mechanism deserves its own callout: it's the actual, concrete technical reason Kubernetes controllers can react to changes near-instantly rather than on a slow polling cycle — every controller and kubelet maintains an open watch connection (via the API Server, which itself watches etcd) for exactly the objects it cares about, and gets pushed an update the moment anything changes.


etcd Compaction and Defragmentation#

A genuinely practical, sometimes-overlooked operational concern — a real interview differentiator if you know it.

Diagram
# Manually compact etcd's history (usually automated on a schedule
# in a well-configured cluster, but worth knowing the raw command)
etcdctl compact $(etcdctl endpoint status --write-out="json" | \
  python3 -c "import sys,json; print(json.load(sys.stdin)[0]['Status']['header']['revision'])")

# Defragment to actually reclaim disk space
etcdctl defrag --endpoints=https://127.0.0.1:2379

Why an unmaintained etcd database is a genuinely real operational risk worth knowing about, not just trivia: without regular compaction/defragmentation, etcd's on-disk database file can grow until it hits etcd's own configured storage quota (--quota-backend-bytes), at which point etcd stops accepting writes entirely — meaning the entire cluster loses the ability to create or update any object at all, a genuinely severe, cluster-wide outage caused purely by operational neglect of a component most teams rarely think about directly.


A Real etcd Disaster Recovery Drill#

Extending the backup command from Part 1 into a full, practiced recovery procedure — directly connecting to the Disaster Recovery topic (topic 11) in this course.

# 1. Take a regular, scheduled snapshot (as shown in Part 1)
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-snapshot.db

# 2. In a real disaster, RESTORE from that snapshot to a fresh
#    data directory
ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd-snapshot.db \
  --data-dir /var/lib/etcd-restored \
  --name etcd-restored \
  --initial-cluster etcd-restored=https://127.0.0.1:2380 \
  --initial-advertise-peer-urls https://127.0.0.1:2380

# 3. Update the etcd service/static pod manifest to point at the
#    NEW data directory, then restart etcd

The single most important, most-often-skipped practice worth stating explicitly, directly reusing the chaos engineering philosophy from the Incident Management series: a backup that's never been test-restored is not a verified backup — it's a hypothesis. Just like the "chaos engineering" principle of deliberately testing assumptions before reality tests them, teams should periodically practice a full etcd restore drill in a non-production environment, confirming the backup actually works and the team actually knows the procedure, rather than discovering a broken backup or an unfamiliar procedure for the first time during a genuine control-plane disaster.


Custom Resource Definitions (CRDs) — Extending the API#

Everything covered so far (Pods, Deployments, Services) are built-in Kubernetes API objects. A CRD lets you define your own, entirely custom object type, which then behaves exactly like a native Kubernetes object — kubectl get, kubectl apply, RBAC, everything, works on it identically.

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: postgresqlclusters.db.example.com
spec:
  group: db.example.com
  scope: Namespaced
  names:
    plural: postgresqlclusters
    singular: postgresqlcluster
    kind: PostgreSQLCluster
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                replicas:
                  type: integer
                version:
                  type: string
# Once the CRD exists, you can create objects of this NEW type,
# just like any built-in Kubernetes object:
apiVersion: db.example.com/v1
kind: PostgreSQLCluster
metadata:
  name: my-database
spec:
  replicas: 3
  version: "15.2"

Why this matters, and it's a genuinely important architectural insight worth stating explicitly: by itself, a CRD just defines a shape of data Kubernetes will accept and store — creating a PostgreSQLCluster object above, with no more, does absolutely nothing on its own. Something still has to actually watch for these objects and take real action — which is exactly the job of an Operator, covered next.


The Operator Pattern#

An Operator is simply a custom controller, following the exact same reconciliation loop pattern from Part 1, but built specifically to manage a CRD instead of a built-in Kubernetes object.

Diagram

Why Operators are such a powerful, genuinely important pattern, worth explaining the "why" clearly: a plain Deployment or StatefulSet only knows generic Kubernetes concepts (pods, replicas, storage) — it has zero built-in understanding of, say, "how do I safely fail over a PostgreSQL primary to a replica" or "how do I take an application-consistent database backup." An Operator encodes exactly that kind of domain-specific operational knowledge as actual, executable code, running continuously inside the cluster — effectively automating what would otherwise be a human database administrator's runbook (directly connecting to the runbook automation discussion from the Incident Management series), but triggered automatically and continuously rather than manually, by a human, after being paged.


A Worked Operator Example#

A concrete, narrated example showing an Operator handling something a generic StatefulSet fundamentally cannot.

Diagram

This is precisely why widely-used, real-world Operators exist for complex stateful systems — the Prometheus Operator (managing Prometheus/Alertmanager configuration declaratively), various PostgreSQL/MySQL Operators, the etcd Operator, and many more — each one encoding deep, product-specific operational expertise as automated, continuously-running code rather than a manual runbook a human has to execute under pressure.


Helm — Packaging Kubernetes Applications#

A real, deployed application is rarely just one YAML file — it's typically a Deployment, a Service, a ConfigMap, an Ingress, maybe a PVC, all needing to work together and often needing slightly different values per environment (staging vs. production). Helm is the most widely used Kubernetes package manager, solving exactly this problem.

Diagram
# Install an application from a Helm chart
helm install my-checkout ./checkout-chart --values production-values.yaml

# Upgrade to a new version/configuration
helm upgrade my-checkout ./checkout-chart --values production-values.yaml

# Roll back to the previous release, if something goes wrong
helm rollback my-checkout

Why this matters practically, tying it back to the reproducibility principle from the DevSecOps series: Helm turns "apply these 8 YAML files, in the right order, with the right environment-specific substitutions, remembered correctly every time" into one versioned, repeatable, auditable command — directly reducing the exact kind of manual, error-prone toil the SRE Fundamentals series identifies as worth automating away.


Common Mistakes#

MistakeWhy It's WrongFix
Adopting a service mesh for a small handful of services "because it's best practice"Adds real latency, resource overhead, and operational complexity that may outweigh the benefit at small scaleAdopt a mesh when consistent cross-cutting concerns (mTLS, retries, observability) genuinely can't be reasonably maintained per-team anymore
Never running etcd compaction/defragmentationThe database file grows unbounded, eventually hitting the storage quota and causing a cluster-wide write outageSchedule regular compaction and defragmentation as standard operational practice
Treating an untested etcd backup as a real safety netA backup that's never been restored is a hypothesis, not a verified recovery pathPeriodically practice a full restore drill in a non-production environment
Creating a CRD and assuming something automatically happensA CRD alone just defines a data shape — nothing acts on it without a corresponding controller/OperatorEnsure a controller (an Operator) is actually deployed and watching the CRD before relying on it
Manually applying many interdependent YAML files by hand, across environmentsError-prone, hard to reproduce consistently, no built-in versioning/rollbackPackage the application as a Helm chart for repeatable, versioned, auditable deployments
Assuming a generic StatefulSet can handle stateful-system-specific operations like failoverStatefulSets only understand generic Kubernetes concepts — they have zero built-in knowledge of a specific database's failover procedureUse a purpose-built Operator that encodes the actual domain-specific operational logic

Worked Practice Problems#

Problem 1: A team is deciding whether to adopt a service mesh for their 6-service application. What questions would you ask before recommending for or against it?

Answer: I'd ask whether they currently have inconsistent or duplicated implementations of retries/circuit breakers/mTLS across their 6 services (a sign a mesh would genuinely help), whether they're already struggling with cross-service observability that RED-metric auto-instrumentation would solve, and whether the team has the operational capacity to run and maintain an entirely separate control-plane system. At only 6 services, I'd lean toward recommending against a full mesh initially — the added latency, sidecar resource overhead, and operational complexity are real costs that may not be justified yet, and I'd suggest revisiting the decision as the service count and cross-cutting-concern pain genuinely grow.

Problem 2: A cluster's control plane suddenly stops accepting any writes — kubectl apply and kubectl create all fail, though reads still work. What's a plausible root cause specific to etcd operations, and how would you confirm it?

Answer: A plausible cause is etcd hitting its configured storage quota (--quota-backend-bytes) due to a database file that's grown unbounded from a lack of regular compaction/defragmentation — etcd deliberately stops accepting new writes once its backend database exceeds this quota, while still being able to serve existing reads. I'd confirm by checking etcd's own logs/metrics for quota-related alarm messages (etcdctl alarm list shows an active NOSPACE alarm in exactly this scenario), and the fix would involve compacting old history and defragmenting to reclaim space, then clearing the alarm — while also fixing the underlying gap by scheduling regular compaction going forward.

Problem 3: A team builds a CRD for PostgreSQLCluster objects but doesn't deploy any accompanying controller. They create a PostgreSQLCluster object and are confused when no actual database appears. What's the misunderstanding?

Answer: A CRD only teaches the Kubernetes API server to accept and store a new shape of object — it defines the schema, nothing more. It has no inherent behavior of its own; creating a PostgreSQLCluster object just stores that data in etcd, exactly like creating any object type. For anything to actually happen as a result (creating StatefulSets, PVCs, Services, running database-specific logic), a separate controller — an Operator — needs to be deployed, watching for PostgreSQLCluster objects and reconciling real Kubernetes resources to match. Without that Operator running, the CRD is just an inert data definition with nothing acting on it.


Summary and What's Next#

  • A service mesh solves the problem of consistent cross-cutting concerns (mTLS, retries, observability) across many services without duplicating logic in every team's application code — built entirely on the sidecar pattern, which works because containers in a pod share a network namespace (Part 3).
  • The mesh's control plane (central policy) and data plane (every sidecar proxy actually enforcing it) mirror Kubernetes's own control-plane/worker-node split from Part 1.
  • mTLS provides automatic, zero-code mutual authentication and encryption for every service-to-service call — a direct, concrete defense against Spoofing and Information Disclosure from the DevSecOps series' STRIDE framework.
  • Traffic splitting for canary releases happens entirely at the mesh layer, with zero application code changes — and RED-metric observability comes "for free" for exactly the same mechanical reason.
  • A service mesh has real costs (latency, resource overhead, operational complexity) — it's a deliberate tradeoff, not a default best practice for every scale.
  • etcd needs ongoing operational care — compaction and defragmentation prevent its database file from growing unbounded and eventually blocking all writes cluster-wide — and a backup that's never been test-restored is not a verified recovery path.
  • CRDs extend the Kubernetes API with custom object types, but by themselves do nothing — an Operator (a custom controller following the same reconciliation loop pattern as every built-in controller) is what actually encodes domain-specific operational knowledge and takes real action.
  • Helm packages multi-file, multi-environment Kubernetes applications into versioned, repeatable, auditable installs — directly reducing manual deployment toil.

Continue to Part 5 (05-managed-kubernetes-eks-aks-gke.md) to see how everything covered so far actually gets deployed in practice — the major managed Kubernetes platforms (EKS, AKS, GKE) and how much of this architecture each one manages on your behalf.