MÓDULO 09 · CONCEITO 07 DE 14

Service Mesh

sidecar · mTLS · traffic management · Istio · Linkerd · eBPF

Tempo de leitura ~22 min Pré-requisito Conceito 06 — API Gateway Próximo 08 · Message Queues

Em arquiteturas de microsserviços, cada serviço precisa resolver os mesmos problemas de comunicação: retry, timeout, circuit breaking, mTLS, tracing, rate limiting, canary deploys. Implementar isso em cada serviço — em Go, C#, Python, Java — cria duplicação de código difícil de manter e atualizar. Service mesh resolve isso extraindo toda essa lógica para proxies sidecar que interceptam o tráfego transparentemente, sem que o código da aplicação precise saber da sua existência.

O problema que service mesh resolve

Sem service mesh, um time que precisa adicionar timeout de 5s em todas as chamadas inter-serviço precisa: encontrar todos os clientes HTTP em todos os serviços, modificar cada um, testar, fazer deploy coordenado. Com service mesh, a mudança é uma linha de configuração no plano de controle — aplicada imediatamente a todos os serviços sem toque no código.

As responsabilidades que o mesh centraliza:

Data plane vs Control plane

Todo service mesh tem dois planos distintos:

Data plane — os proxies sidecar que ficam junto a cada instância do serviço e interceptam todo o tráfego de entrada e saída. O Envoy é o data plane padrão de quase todos os meshes modernos (Istio, Consul Connect, Kuma). Cada sidecar conhece as políticas de tráfego para aquele serviço e as aplica em tempo real.

Control plane — o cérebro do mesh. Distribui configuração para todos os sidecars via xDS API, gerencia certificados (rotação automática), e provê a interface administrativa. No Istio é o Istiod; no Linkerd é o Linkerd Control Plane; no Consul Connect é o Consul Server.

# Fluxo de uma chamada com service mesh
Serviço A (Pod)
  ↓ tráfego sai pela porta local
Sidecar Envoy de A (container no mesmo Pod)
  ↓ aplica políticas: mTLS, retry, timeout, tracing
Rede Kubernetes
  ↓
Sidecar Envoy de B (container no mesmo Pod)
  ↓ verifica certificado de A, aplica autorização
Serviço B (Pod)

# O Serviço A e o Serviço B chamam localhost normalmente
# Os sidecars são transparentes — sem mudança de código

Sidecar injection

Em Kubernetes, o sidecar é injetado automaticamente via Mutating Admission Webhook — quando um Pod é criado em um namespace com label istio-injection: enabled, o webhook do Istiod modifica o PodSpec para adicionar o container Envoy e o init container que configura as regras de iptables para redirecionar o tráfego.

# Habilitar sidecar injection no namespace
kubectl label namespace production istio-injection=enabled

# Verificar que o sidecar foi injetado
kubectl get pod orders-7d8b9f-xkl2p -n production -o jsonpath='{.spec.containers[*].name}'
# orders envoy-proxy (dois containers no Pod)

# Ver logs do sidecar — todo tráfego do serviço
kubectl logs orders-7d8b9f-xkl2p -c envoy-proxy -n production | head -20

# Forçar injeção em um Pod específico (sem label no namespace)
# Adicionar annotation ao PodSpec:
# sidecar.istio.io/inject: "true"
nota eBPF como alternativa ao sidecar: Cilium Service Mesh usa eBPF no kernel Linux para interceptar tráfego sem injetar containers sidecar — eliminando o overhead de CPU/memória do Envoy por Pod (tipicamente 50-100MB RAM e 0.1-0.5 vCPU por sidecar). Cilium ainda é menos maduro em funcionalidades de L7 que Istio, mas é significativamente mais eficiente em clusters com centenas de serviços.

mTLS — identidade de serviço

mTLS (mutual TLS) é a funcionalidade de segurança mais importante do service mesh. Em vez de confiar em IPs (facilmente falsificáveis em ambientes de container), cada serviço recebe uma identidade criptográfica baseada no padrão SPIFFE (Secure Production Identity Framework For Everyone).

SPIFFE e SVID

SPIFFE define uma identidade de serviço como um URI: spiffe://cluster.local/ns/production/sa/orders-service. Essa identidade é embutida em um certificado X.509 chamado SVID (SPIFFE Verifiable Identity Document). O mesh emite e rotaciona esses certificados automaticamente — sem gestão manual de PKI.

# Istio — ver certificado SVID de um serviço
istioctl proxy-config secret orders-7d8b9f-xkl2p -n production

# Output mostra:
# - Certificate chain com a identidade SPIFFE
# - Tempo de expiração (tipicamente 24h, rotacionado automaticamente)
# - CA que assinou (Istio CA / cert-manager)

# PeerAuthentication — exigir mTLS em um namespace
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: production
spec:
  mtls:
    mode: STRICT  # rejeitar qualquer tráfego sem mTLS válido
    # PERMISSIVE: aceita mTLS e plain HTTP (útil durante migração)

# AuthorizationPolicy — controle de acesso por serviço chamador
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: orders-authz
  namespace: production
spec:
  selector:
    matchLabels:
      app: orders-service
  action: ALLOW
  rules:
  - from:
    - source:
        principals:
        # Apenas payments-service e api-gateway podem chamar orders
        - "cluster.local/ns/production/sa/payments-service"
        - "cluster.local/ns/production/sa/api-gateway"
    to:
    - operation:
        methods: ["GET", "POST"]
        paths: ["/api/orders*"]
atenção mTLS e debug: com mTLS STRICT, chamadas sem certificado são rejeitadas com Connection reset by peer ou upstream connect error. Durante debugging, é tentador mudar para PERMISSIVE — mas isso abre o serviço para tráfego não autenticado. Prefira usar istioctl proxy-config e kubectl exec para investigar sem relaxar a política.

Traffic Management

O service mesh separa deployment de release: você pode ter duas versões do serviço rodando simultaneamente e controlar exatamente qual percentual do tráfego vai para cada uma, sem modificar os clientes.

VirtualService e DestinationRule no Istio

# DestinationRule — define subsets (versões) do serviço
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: orders-destination
  namespace: production
spec:
  host: orders-service
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        http2MaxRequests: 1000
        maxRequestsPerConnection: 10
    outlierDetection:
      consecutive5xxErrors: 5
      interval: 10s
      baseEjectionTime: 30s
      maxEjectionPercent: 50
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2

---
# VirtualService — controla roteamento entre subsets
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: orders-routing
  namespace: production
spec:
  hosts:
  - orders-service
  http:
  # Canary: 10% do tráfego vai para v2
  - match:
    - headers:
        x-canary-user:
          exact: "true"  # tráfego de usuários canary vai sempre para v2
    route:
    - destination:
        host: orders-service
        subset: v2
  - route:
    - destination:
        host: orders-service
        subset: v1
      weight: 90
    - destination:
        host: orders-service
        subset: v2
      weight: 10
    # Retry policy — 3 tentativas em 5xx com backoff
    retries:
      attempts: 3
      perTryTimeout: 5s
      retryOn: "5xx,reset,connect-failure"
    # Timeout global para esta rota
    timeout: 30s
    # Fault injection para testes de resiliência
    # fault:
    #   delay:
    #     percentage:
    #       value: 10
    #     fixedDelay: 2s

Traffic mirroring

Traffic mirroring (ou shadowing) envia uma cópia do tráfego de produção para uma nova versão — em paralelo, sem afetar a resposta ao cliente. A v2 processa as requisições mas as respostas são descartadas. Permite testar com carga real antes de migrar tráfego.

# VirtualService com mirror
spec:
  http:
  - route:
    - destination:
        host: orders-service
        subset: v1
      weight: 100
    mirror:
      host: orders-service
      subset: v2
    mirrorPercentage:
      value: 100.0  # espelhar 100% do tráfego para v2

Observabilidade integrada

O service mesh coleta métricas, traces e logs de cada chamada inter-serviço sem instrumentação no código — mas requer que os serviços propaguem headers de tracing para manter o contexto de trace entre chamadas.

Métricas geradas automaticamente

# Métricas Istio expostas via Prometheus (por sidecar)
# Formato: istio_[direction]_[metric]_[unit]

# Requisições recebidas pelo serviço
istio_requests_total{
  reporter="destination",
  source_workload="api-gateway",
  destination_workload="orders-service",
  request_protocol="http",
  response_code="200",
  response_flags="-"
} 15234

# Distribuição de latência (histogram)
istio_request_duration_milliseconds_bucket{
  reporter="destination",
  destination_workload="orders-service",
  le="25"  # requests abaixo de 25ms
} 12100

# Bytes enviados/recebidos
istio_request_bytes_sum{...}
istio_response_bytes_sum{...}

Distributed tracing — propagação de headers

O sidecar cria spans automaticamente para cada requisição de entrada e saída. Para que os spans sejam correlacionados em um trace único, o serviço deve propagar os headers B3/W3C do request de entrada para os requests de saída. Essa é a única coisa que o código da aplicação precisa fazer explicitamente.

# Headers de tracing que devem ser propagados
# (do request de entrada para todos os requests de saída)
x-request-id
x-b3-traceid
x-b3-spanid
x-b3-parentspanid
x-b3-sampled
x-b3-flags
# Ou formato W3C:
traceparent
tracestate

Istio em profundidade

Istio é o service mesh mais completo e mais complexo. O Istiod (control plane unificado desde a versão 1.5) implementa xDS para configurar os sidecars Envoy, gerencia a CA interna (Citadel), e traduz os CRDs Kubernetes (VirtualService, DestinationRule, etc.) em configuração Envoy.

# Instalação com istioctl
istioctl install --set profile=default -y
# Profiles: minimal, default, demo, external

# Verificar status
istioctl verify-install
kubectl get pods -n istio-system

# Ferramentas de observabilidade bundled com profile=demo
kubectl apply -f samples/addons/prometheus.yaml
kubectl apply -f samples/addons/grafana.yaml
kubectl apply -f samples/addons/jaeger.yaml
kubectl apply -f samples/addons/kiali.yaml

# Kiali — visualização do service graph
istioctl dashboard kiali

# Analisar configuração de um serviço
istioctl analyze -n production

# Debug de conectividade entre dois serviços
istioctl proxy-config routes orders-7d8b9f-xkl2p -n production
istioctl proxy-config clusters orders-7d8b9f-xkl2p -n production | grep payments
atenção Overhead de Istio: Istio adiciona ~50-100ms de latência de cold start por sidecar injection, ~100MB de RAM por Pod (Envoy), e latência de ~1-3ms por hop de rede. Em serviços com latência alvo abaixo de 10ms, esse overhead é significativo. Meça antes de adotar — Linkerd tem overhead substancialmente menor.

Linkerd — simplicidade como filosofia

Linkerd (v2, reescrito em Rust/Go) tem como objetivo ser o service mesh mais simples de operar. Em vez de Envoy (100k+ linhas de C++), usa um proxy em Rust (linkerd2-proxy) com footprint muito menor: ~10MB RAM e latência adicionada de sub-milissegundo.

# Instalação do Linkerd
curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install | sh
linkerd check --pre   # pré-requisitos
linkerd install --crds | kubectl apply -f -
linkerd install | kubectl apply -f -
linkerd check         # verificar instalação

# Injetar sidecar em um deployment existente
kubectl get deploy orders-service -n production -o yaml \
  | linkerd inject - \
  | kubectl apply -f -

# Dashboard
linkerd viz install | kubectl apply -f -
linkerd viz dashboard

# Tap — ver tráfego em tempo real (como tcpdump para L7)
linkerd viz tap deploy/orders-service -n production \
  --to deploy/payments-service \
  --method POST

O que Linkerd não tem comparado ao Istio: sem VirtualService/DestinationRule (traffic splitting usa SMI — Service Mesh Interface), sem fault injection nativa, e sem suporte a traffic mirroring tão completo. Para a maioria dos casos — mTLS, métricas, retry, timeout — Linkerd é suficiente e muito mais simples de operar.

Quando usar — e quando não usar

dica Use service mesh quando: você tem 10+ serviços com comunicação inter-serviço frequente, precisa de mTLS entre todos os serviços sem modificar código, quer canary deploys e traffic management sem alterar os serviços, e tem equipe com experiência em Kubernetes para operar o mesh.

Não use service mesh quando:

Comparação por linguagem

A única coisa que o código da aplicação precisa fazer para service mesh é propagar headers de tracing. Veja como isso se implementa em cada linguagem.

C# — ASP.NET Core middleware
// Middleware que propaga headers de tracing entre requisições
// Registrar como middleware no pipeline HTTP

public class TracingPropagationMiddleware
{
    // Headers que o Istio/Linkerd esperam propagados
    private static readonly string[] TracingHeaders =
    [
        "x-request-id",
        "x-b3-traceid", "x-b3-spanid", "x-b3-parentspanid",
        "x-b3-sampled", "x-b3-flags",
        "traceparent", "tracestate",  // W3C Trace Context
    ];

    private readonly RequestDelegate _next;

    public TracingPropagationMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context)
    {
        // Guardar headers do request de entrada no contexto
        var tracingHeaders = TracingHeaders
            .Where(h => context.Request.Headers.ContainsKey(h))
            .ToDictionary(h => h, h => context.Request.Headers[h].ToString());

        context.Items["TracingHeaders"] = tracingHeaders;
        await _next(context);
    }
}

// Extension para HttpClient — propagar headers automaticamente
public class TracingPropagationHandler : DelegatingHandler
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public TracingPropagationHandler(IHttpContextAccessor accessor)
        => _httpContextAccessor = accessor;

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken ct)
    {
        var ctx = _httpContextAccessor.HttpContext;
        if (ctx?.Items["TracingHeaders"] is Dictionary<string, string> headers)
        {
            foreach (var (key, value) in headers)
                request.Headers.TryAddWithoutValidation(key, value);
        }
        return base.SendAsync(request, ct);
    }
}

// Program.cs
builder.Services.AddHttpContextAccessor();
builder.Services.AddTransient<TracingPropagationHandler>();
builder.Services.AddHttpClient("payments")
    .AddHttpMessageHandler<TracingPropagationHandler>();

app.UseMiddleware<TracingPropagationMiddleware>();

// Health check para o mesh detectar readiness
builder.Services.AddHealthChecks();
app.MapHealthChecks("/ready");
app.MapHealthChecks("/health");

O ASP.NET Core injeta OpenTelemetry automaticamente se o pacote OpenTelemetry.Instrumentation.AspNetCore estiver configurado — nesse caso a propagação de W3C TraceContext é automática e o middleware manual não é necessário.

Python — FastAPI middleware
from fastapi import FastAPI, Request
import httpx
from contextvars import ContextVar

app = FastAPI()

# ContextVar — armazena headers de tracing por coroutine (thread-safe)
_tracing_headers: ContextVar[dict] = ContextVar('tracing_headers', default={})

TRACING_HEADERS = [
    "x-request-id",
    "x-b3-traceid", "x-b3-spanid", "x-b3-parentspanid",
    "x-b3-sampled", "x-b3-flags",
    "traceparent", "tracestate",
]

@app.middleware("http")
async def propagate_tracing_headers(request: Request, call_next):
    # Extrair headers de tracing do request de entrada
    headers = {
        h: request.headers[h]
        for h in TRACING_HEADERS
        if h in request.headers
    }
    _tracing_headers.set(headers)
    return await call_next(request)

# Cliente HTTP que propaga headers automaticamente
class TracingClient:
    def __init__(self, base_url: str):
        self._client = httpx.AsyncClient(base_url=base_url)

    async def get(self, path: str, **kwargs) -> httpx.Response:
        headers = {**_tracing_headers.get(), **kwargs.pop("headers", {})}
        return await self._client.get(path, headers=headers, **kwargs)

    async def post(self, path: str, **kwargs) -> httpx.Response:
        headers = {**_tracing_headers.get(), **kwargs.pop("headers", {})}
        return await self._client.post(path, headers=headers, **kwargs)

    async def aclose(self):
        await self._client.aclose()

# Health checks para readiness/liveness
@app.get("/ready")
async def ready():
    # Verificar dependências necessárias
    return {"status": "ready"}

@app.get("/health")
async def health():
    return {"status": "ok"}

# Uso
payments_client = TracingClient("http://payments-service")

@app.post("/api/orders")
async def create_order(request: Request):
    # Headers de tracing são propagados automaticamente pelo cliente
    payment = await payments_client.post(
        "/api/payments",
        json={"amount": 100, "currency": "BRL"}
    )

Python usa ContextVar para armazenar headers de tracing de forma segura entre coroutines assíncronas — cada request tem seu próprio contexto isolado, sem risco de vazamento entre requisições concorrentes.

Go — middleware com context
package mesh

import (
    "context"
    "net/http"
)

// Headers de tracing que devem ser propagados
var tracingHeaders = []string{
    "x-request-id",
    "x-b3-traceid", "x-b3-spanid", "x-b3-parentspanid",
    "x-b3-sampled", "x-b3-flags",
    "traceparent", "tracestate",
}

type contextKey struct{}

// TracingMiddleware extrai headers de tracing e guarda no contexto
func TracingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        headers := make(map[string]string)
        for _, h := range tracingHeaders {
            if v := r.Header.Get(h); v != "" {
                headers[h] = v
            }
        }
        ctx := context.WithValue(r.Context(), contextKey{}, headers)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// PropagateHeaders injeta os headers de tracing em um request de saída
func PropagateHeaders(ctx context.Context, req *http.Request) {
    if headers, ok := ctx.Value(contextKey{}).(map[string]string); ok {
        for k, v := range headers {
            req.Header.Set(k, v)
        }
    }
}

// TracingTransport — RoundTripper que propaga headers automaticamente
type TracingTransport struct {
    Base    http.RoundTripper
    Context func() context.Context  // função que retorna o contexto atual
}

func (t *TracingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
    if t.Context != nil {
        PropagateHeaders(t.Context(), req)
    }
    return t.Base.RoundTrip(req)
}

// Uso no serviço
func NewPaymentsClient(ctx func() context.Context) *http.Client {
    return &http.Client{
        Transport: &TracingTransport{
            Base:    http.DefaultTransport,
            Context: ctx,
        },
    }
}

// Health check handlers para o mesh
func ReadinessHandler(w http.ResponseWriter, r *http.Request) {
    // Verificar conexão com dependências críticas
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(`{"status":"ready"}`))
}

func LivenessHandler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(`{"status":"ok"}`))
}

Go passa o contexto explicitamente em cada chamada — o TracingTransport implementa http.RoundTripper para injetar headers em todas as requisições HTTP saintes sem modificar os handlers.

Decisões de engenharia

Istio vs Linkerd
Istio oferece o conjunto mais completo de funcionalidades: VirtualService para roteamento fino, fault injection para chaos testing, traffic mirroring, políticas de autorização por método/path, e extensibilidade via filtros Wasm. O custo é complexidade operacional alta (centenas de CRDs, plano de controle com múltiplos componentes antes da v1.5, ~100MB de RAM por sidecar) e curva de aprendizado íngreme. Linkerd foca em simplicidade: proxy em Rust com ~10MB de RAM, instalação em dois comandos, métricas automáticas sem configuração. O que Linkerd não tem nativamente (fault injection, traffic splitting avançado) requer SMI ou extensões. Para equipes que precisam de canary automático, mTLS e métricas, Linkerd é suficiente e muito mais simples de operar. Istio é a escolha quando você precisa de roteamento por header, fault injection, protocolo transcoding, ou quando já tem experiência com Envoy.
Sidecar proxy vs eBPF (Cilium)
Sidecars (modelo Istio/Linkerd) injetam um container por Pod — isolamento forte, mas overhead proporcional ao número de Pods: cluster com 500 Pods = 500 sidecars × 50-100MB = 25-50GB de RAM dedicado a proxies. Cilium Service Mesh usa eBPF no kernel Linux: um único programa de eBPF por node intercepta tráfego para todos os Pods sem containers adicionais — eliminando o overhead por Pod. O trade-off é que eBPF opera em L3/L4 nativamente; políticas L7 (por HTTP method/path) requerem proxies leves opcionais por node (não por Pod). Cilium é a escolha para clusters grandes onde o overhead de sidecar é proibitivo; sidecar é mais maduro em funcionalidades L7 e mais compatível com distribuições Kubernetes sem suporte a eBPF avançado.
mTLS STRICT vs PERMISSIVE
STRICT rejeita qualquer tráfego sem certificado mTLS válido — segurança máxima, mas quebra serviços que ainda não têm sidecar injetado (legacy services, jobs, operadores sem injeção). PERMISSIVE aceita mTLS e plain HTTP — permite migração gradual onde novos serviços já usam mTLS enquanto legados ainda falam HTTP. A estratégia correta de migração: começar com PERMISSIVE em todo o cluster, injetar sidecars serviço por serviço, verificar via istioctl proxy-config que a comunicação está usando mTLS, e migrar para STRICT namespace por namespace quando todos os serviços do namespace estiverem com sidecar. Nunca deixar PERMISSIVE como estado permanente — é uma janela de segurança aberta.
Service mesh vs resiliência na aplicação
Service mesh tira retry, timeout e circuit breaking do código da aplicação — mas o mesh não sabe nada sobre semântica de negócio. Retry de uma mutation idempotente (debit com idempotency key) é seguro; retry de uma criação sem idempotency key duplica dados. O mesh vai fazer retry se configurado, independente do método HTTP. A solução correta é: mesh para políticas gerais (timeout de 30s para todos os endpoints, retry em 503 de connection failure), aplicação para lógica específica (não fazer retry em POST sem idempotency key, circuit breaker com fallback que retorna cache). Não elimine toda a resiliência da aplicação — elimine a duplicação de políticas genéricas que pertencem à infraestrutura.

Exercícios práticos

  1. Instale o Linkerd em um cluster local (kind ou minikube). Injete o sidecar em dois serviços que se comunicam. Use linkerd viz tap para observar o tráfego em tempo real e linkerd viz stat para ver métricas de sucesso e latência por rota. Critério: linkerd viz stat deploy/orders-service mostra SUCCESS ≥99% para tráfego normal; ao derrubar o pod do backend, tap mostra erros sendo retornados pelo sidecar em <1s; latência P99 visível no dashboard Linkerd Viz.
  2. Configure mTLS STRICT em um namespace Istio e verifique que chamadas sem certificado são rejeitadas. Adicione uma AuthorizationPolicy que permita apenas o serviço A chamar o serviço B. Critério: kubectl exec em um pod sem service account autorizada e tentativa de chamar B retorna RBAC: access denied ou upstream connect error or disconnect/reset before headers; pod com service account correta retorna 200; istioctl analyze -n production não reporta warnings.
  3. Implemente um canary deploy com Istio VirtualService: 90% do tráfego para v1, 10% para v2. Aumente progressivamente o peso monitorando a taxa de erro. Critério: query Prometheus rate(istio_requests_total{destination_version="v2"}[1m]) / rate(istio_requests_total[1m]) mostra ≈0.10 (10%); ao injetar fault delay de 500ms em v2, o Kiali mostra latência P99 de v2 claramente acima de v1; aplicar VirtualService com 100% v1 elimina os erros em ≤30s.
  4. Implemente propagação de headers de tracing na linguagem de sua preferência. Configure Jaeger (disponível no profile demo do Istio). Critério: uma requisição que passa por 3 serviços (A → B → C) gera um trace único no Jaeger com 3 spans e o mesmo trace_id; ao remover o middleware de propagação do serviço B, o trace se quebra em dois: um com A→B e outro sem parent para C.
  5. Compare o overhead do Istio vs Linkerd: deploy do mesmo serviço em dois namespaces (um com Istio, outro com Linkerd) e meça com kubectl top pod e hey ou wrk. Critério: documentar RAM do sidecar Linkerd (esperado: ≤10-15MB) vs Istio (esperado: 80-100MB); latência P99 de Linkerd deve ser ≤2ms de overhead adicional; latência P99 de Istio tipicamente 3-8ms de overhead. Compilar tabela comparativa com os valores reais medidos.

Perguntas de entrevista

Como o service mesh intercepta tráfego sem modificar o código da aplicação?

Em Kubernetes, o sidecar é injetado via Mutating Admission Webhook: quando um Pod é criado em um namespace com istio-injection: enabled, o webhook do Istiod modifica o PodSpec antes de o Pod ser criado — adiciona o container Envoy e um init container. O init container configura regras de iptables que redirecionam todo o tráfego TCP de entrada e saída para as portas do Envoy (por padrão 15001 para saída, 15006 para entrada). O processo da aplicação tenta se conectar ao IP de destino normalmente, mas o kernel intercepta o pacote e o entrega ao Envoy, que aplica as políticas e o encaminha.

O resultado é transparência total: a aplicação usa sockets TCP/HTTP normais, não sabe que o Envoy existe, e não precisa de nenhuma mudança de código. A única exceção é propagação de headers de tracing (x-b3-traceid, traceparent) — o sidecar não pode propagar automaticamente headers de um request de entrada para um request de saída porque eles estão em fluxos TCP separados. O serviço precisa copiar esses headers explicitamente.

A alternativa eBPF (Cilium) usa programas no kernel Linux que são executados em pontos específicos do processamento de pacotes — sem iptables, sem container adicional, sem overhead de context switch para espaço de usuário em cada pacote.

Por que identidade de serviço baseada em certificado SPIFFE é superior à baseada em IP?

Em ambientes de container, IPs são efêmeros e não identificam serviços de forma confiável: um Pod reinicia e ganha um IP diferente; múltiplos Pods do mesmo serviço têm IPs diferentes; um IP que era do serviço de pagamentos pode ser reatribuído para outro serviço após um restart. Políticas de rede baseadas em IP requerem atualizações constantes e são difíceis de auditar — "IP 10.0.1.15 pode chamar IP 10.0.2.30" não tem significado semântico claro.

SPIFFE (Secure Production Identity Framework for Everyone) define uma identidade baseada em URI: spiffe://cluster.local/ns/production/sa/payments-service. Essa identidade é embutida em um certificado X.509 (SVID — SPIFFE Verifiable Identity Document) assinado pela CA do mesh. O mesh emite e rotaciona esses certificados automaticamente — sem gestão manual. O resultado: quando o serviço de orders estabelece mTLS com payments, o Envoy de pagamentos verifica o certificado de orders e sabe exatamente quem está chamando, independente de qual IP o Pod tem.

AuthorizationPolicy no Istio usa essa identidade para controle de acesso: "apenas spiffe://cluster.local/ns/production/sa/api-gateway pode chamar /api/orders com método POST". Isso é auditável, semântico, e resistente a ataques de spoofing de IP.

Qual a diferença entre VirtualService e DestinationRule no Istio, e por que as duas são necessárias?

DestinationRule define o que existe: divide um serviço em subsets (versões) identificados por labels, e configura políticas de conexão que se aplicam independente de como o tráfego é roteado — connection pooling, outlier detection (circuit breaking passivo), e configuração TLS para o upstream. É o "quem" e "como se conectar".

VirtualService define o que fazer com o tráfego: roteamento baseado em peso, headers, URI, método HTTP — entre os subsets definidos pelo DestinationRule. Também configura retry, timeout e fault injection por rota. É o "para onde enviar e com que políticas".

A separação existe porque as responsabilidades são ortogonais: você pode ter uma DestinationRule definindo subsets v1/v2 e connection pooling sem nenhum VirtualService (tráfego vai para o subset default com round robin). Ou pode ter um VirtualService para canary sem DestinationRule (mas sem subsets definidos, o canary não tem como diferenciar as versões). Ambos são necessários para canary deploy completo: DestinationRule mapeia version: v1 e version: v2 labels para subsets, e VirtualService roteia 90%/10% entre eles.

Como automatizar rollback em canary deploy baseado em métricas do mesh?

O fluxo manual com Istio: criar DestinationRule com subsets v1 e v2, criar VirtualService com 90/10, monitorar métricas, ajustar pesos. O problema é que requer intervenção humana constante e o rollback sob pressão de incidente é lento.

A abordagem automatizada usa ferramentas de progressive delivery que consomem métricas do mesh: Flagger (CNCF project) observa as métricas do Istio/Linkerd, incrementa automaticamente o peso do canary (ex: +10% a cada 5 minutos) se as métricas estão dentro dos thresholds, e faz rollback automático se: taxa de erro do canary > 5%, latência P99 do canary > 2× do baseline, ou métricas de negócio (conversão, receita) degradam. Argo Rollouts tem funcionalidade similar e integra com GitOps.

O critério de rollback deve ser definido com antecedência: "taxa de erro 5xx > 1% por 2 minutos consecutivos" é um critério de rollback claro. Configurar alertas no Prometheus que disparam a redução de peso é mais confiável do que depender de alguém monitorando ativamente. A métrica mais importante para rollback é taxa de erro — latência pode ser aceitavelmente maior na nova versão se os erros são zero.

Quais são as alternativas ao service mesh e quando cada uma é mais apropriada?

Bibliotecas de resiliência na aplicação (Polly em C#, Resilience4j em Java, tenacity em Python): implementam retry, circuit breaker e timeout no código. Vantagem: total controle da semântica (saber que um POST não deve ser retentado, ou que um circuit breaker deve retornar um valor de cache em vez de erro). Desvantagem: duplicação em cada serviço e cada linguagem, sem visibilidade centralizada. Adequado quando: poucos serviços, linguagem única, ou quando a semântica de negócio requer controle fino.

Kubernetes NetworkPolicies: controle de acesso L3/L4 nativo do Kubernetes — "Pod A pode se conectar na porta 8080 do Pod B". Sem overhead de sidecar, sem latência adicional. Mas sem mTLS (tráfego não criptografado), sem métricas L7, e sem controle por método/path. Adequado quando: rede isolada em que criptografia interna não é requisito de compliance, e controle de acesso por porta é suficiente.

gRPC interceptors / HTTP middleware chain: middleware centralizado em cada serviço que aplica retry, logging e tracing. Mais fácil de testar que service mesh, mas ainda requer deploy em cada serviço quando a política muda. Adequado quando: time pequeno, infraestrutura sem Kubernetes, ou quando o mesh seria a primeira camada de complexidade operacional do time.

A decisão deve considerar: tamanho do cluster (mesh vale a partir de ~10 serviços), maturidade da equipe com Kubernetes, requisitos de compliance (mTLS obrigatório leva ao mesh), e se a organização tem infra team para operar o mesh.

Referências

  1. docs Istio Documentation — Istio Project. istio.io/docs — Referência completa de VirtualService, DestinationRule, PeerAuthentication e AuthorizationPolicy. O guia de conceitos é leitura obrigatória antes de configurar qualquer política de tráfego ou segurança.
  2. docs Linkerd Documentation — Buoyant. linkerd.io/docs — Documentação do Linkerd v2: instalação, injeção de sidecar via linkerd inject, observabilidade com Linkerd Viz (tap, stat, top), e SMI (Service Mesh Interface) para traffic splitting compatível com múltiplos meshes.
  3. docs Cilium Service Mesh Documentation — Isovalent / CNCF. docs.cilium.io/en/stable/network/servicemesh — Documentação do Cilium Service Mesh baseado em eBPF: sidecarless architecture, Hubble para observabilidade L7 sem sidecar, e políticas de rede L3/L4/L7 via eBPF. Referência para entender a alternativa de menor overhead ao modelo sidecar.
  4. docs Envoy Proxy Documentation — Envoy Project / CNCF. envoyproxy.io/docs — Documentação do data plane usado por Istio, Consul Connect e Kuma. Seções críticas: xDS APIs (como o control plane configura os sidecars), outlier detection (circuit breaking passivo), e access logging. Entender Envoy é fundamental para debugar problemas de mesh.
  5. artigo The Service Mesh: What Every Software Engineer Needs to Know — William Morgan (Buoyant, 2017). buoyant.io/service-mesh-manifesto — O artigo que cunhou o termo "service mesh". Explica o problema dos proxies por serviço (duplicação em cada linguagem), por que a abstração de sidecar emergiu como solução, e as responsabilidades do data vs control plane.
  6. artigo Service Mesh Comparison — servicemesh.es. servicemesh.es — Comparativo atualizado de Istio, Linkerd, Consul Connect, Kuma e Cilium com tabela de funcionalidades (mTLS, traffic management, observabilidade), overhead de memória/CPU por sidecar, e maturidade de cada projeto.
  7. artigo SPIFFE: Solving the Bottom Turtle Problem — Evan Gilman & Doug Barth (O'Reilly, 2020). spiffe.io/book — Livro sobre SPIFFE e SPIRE para identidade de carga de trabalho: SVID (SPIFFE Verifiable Identity Document), SPIRE como implementação de referência, e integração com Kubernetes. Fundação conceitual para entender mTLS automático e AuthorizationPolicy baseada em identidade de serviço.
  8. standard W3C Trace Context — Level 1 — W3C (2021). w3.org/TR/trace-context — Especificação dos headers traceparent e tracestate para propagação de contexto de tracing entre serviços. A base do formato moderno de distributed tracing suportado por Istio, Linkerd e OpenTelemetry — substituto padronizado do formato B3.
  9. docs Consul Connect Documentation — HashiCorp. developer.hashicorp.com/consul/docs/connect — Documentação do service mesh do Consul: funciona em Kubernetes e VMs, com Envoy como data plane. Relevante para ambientes híbridos (Kubernetes + VMs bare metal) onde Istio não funciona nativamente.
  10. docs Flagger Documentation — Flux / CNCF. flagger.app/docs — Documentação do Flagger para progressive delivery automatizado: canary releases baseadas em métricas do Istio/Linkerd, análise automática de sucesso/erro, e rollback automático. Referência para implementar canary deploy sem intervenção manual.
  11. paper eBPF: Rethinking the Linux Kernel — Jonathan Corbet et al. (HOTOS 2021). Proceedings of the Workshop on Hot Topics in Operating Systems — Explica como eBPF permite executar programas verificados no kernel Linux para interceptar e processar pacotes de rede sem overhead de context switch. Base teórica para entender por que Cilium tem overhead menor que sidecars userspace.
  12. livro Cloud Native Patterns — Cornelia Davis (Manning, 2019). Capítulos 9-11 — "Interaction Patterns": retry, circuit breaker e service discovery em sistemas cloud native. Cobre os mesmos problemas que service mesh resolve, mas da perspectiva da aplicação — útil para entender o que o mesh abstrai e o que ainda é responsabilidade do código.