Serviços Mesh com Go e Envoy: Controle e Observabilidade de Tráfego

Em arquitetruas cloud-native modernas, o Service Mesh é fundamental para garantir a comunicação robusta, segura e eficiente entre microsserviços. Este artigo explora a criação de um protótipo de service mesh leve, utilizando Go e o proxy Envoy, focando em como a injeção customizada de Sidecar, políticas de controle de tráfego e coleta de métricas podem aprimorar a observabilidade do sistema.

Por que Go + Envoy?

  • Go: Conhecido por seu alto desempenho e facilidade de concorrência, é ideal para desenvolver o plano de controle do Sidecar.
  • Envoy: Um proxy de alto desempenho, originado no Lyft, que oferece recursos avançados como atualização dinâmica de configuração, mTLS, circuit breaking e retries.

A combinação desses dois possibilita a construção de uma solução de service mesh flexível e controlável, especialmente adequada para equipes que buscam uma implementação rápida.

Exemplo: Implementaremos uma aplicação em Go no Kubernetes com um injector de Sidecar, garantindo que cada Pod utilize o Envoy como proxy local automaticamante.

Arquitetura Simplificada

O tráfego do cliente é direcionado através do Envoy para o serviço de destino. O Envoy obtém regras de roteamento e políticas de health check do plano de controle. Toda a comunicação passa por um ponto de entrada unificado, facilitando o monitoramento e a limitação de taxa.


┌─────────────┐    ┌──────────────┐    ┌──────────────┐
│   Cliente   │    │   Pod App    │    │ Envoy Sidecar│
│             │◄──►│              │◄──►│              │
└─────────────┘    └──────────────┘    └──────────────┘
    ▲                    ▲
             │                    │
                (HTTP/HTTPS)        (Atualizações xDS)
   

Implementação Prática: Construindo um Service Mesh Mínimo

1. Modelo de Configuração Estática do Envoy

Crie um arquivo envoy.yaml com a configuração inicial. Note que, para cenários dinâmicos, esta configuração estática precisará ser complementada com o mecanismo de atualização dinâmica xDS.


static_resources:
 listeners:
 - name: listener_0
   address:
     socket_address:
       address: 0.0.0.0
       port_value: 10000
   filter_chains:
   - filters:
     - name: envoy.filters.network.http_connection_manager
       typed_config:
         "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
         stat_prefix: ingress_http
         route_config:
           name: local_route
           virtual_hosts:
           - name: backend
             domains: ["*"]
             routes:
             - match:
                 prefix: "/"
               route:
                 cluster: service_a
         http_filters:
         - name: envoy.filters.http.router
 clusters:
 - name: service_a
   connect_timeout: 0.25s
   type: STRICT_DNS
   lb_policy: ROUND_ROBIN
   load_assignment:
     cluster_name: service_a
     endpoints:
     - lb_endpoints:
       - endpoint:
           address:
             socket_address:
               address: service-a.default.svc.cluster.local
               port_value: 8080
   

2. Controlador de Injeção de Sidecar em Go

Este servidor HTTP atua como um Admission Controller no Kubernetes, interceptando a criação de Pods para injetar automaticamente o contêiner Envoy.


package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

type PodSpecPatch struct {
	Op    string      `json:"op"`
	Path  string      `json:"path"`
	Value interface{} `json:"value"`
}

type AdmissionResponse struct {
	Allowed bool             `json:"allowed"`
	Patch   []PodSpecPatch   `json:"patch"`
	PatchType string         `json:"patchType"`
}

type AdmissionReviewResponse struct {
	APIVersion string `json:"apiVersion"`
	Kind       string `json:"kind"`
	Response   AdmissionResponse `json:"response"`
}

func injectSidecarHandler(w http.ResponseWriter, r *http.Request) {
	var admissionReviewRequest map[string]interface{}
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Failed to read request body", http.StatusBadRequest)
		return
	}
	if err := json.Unmarshal(body, &admissionReviewRequest); err != nil {
		http.Error(w, "Failed to unmarshal request JSON", http.StatusBadRequest)
		return
	}

	admissionReviewRequest["response"] = AdmissionReviewResponse{
		Allowed: true,
		PatchType: "JSONPatch",
		Patch: []PodSpecPatch{
			{
				Op:   "add",
				Path: "/spec/containers/-",
				Value: map[string]interface{}{
					"name":  "envoy-sidecar",
					"image": "envoyproxy/envoy:v1.25-latest",
					"ports": []map[string]int{
						{"containerPort": 10000},
					},
					"volumeMounts": []map[string]string{
						{"name": "envoy-config", "mountPath": "/etc/envoy"},
					},
				},
			},
		},
	}

	w.Header().Set("Content-Type", "application/json")
	json.Encoder := json.NewEncoder(w)
	if err := jsonEncoder.Encode(admissionReviewRequest); err != nil {
		log.Printf("Error encoding response: %v", err)
		http.Error(w, "Failed to encode response", http.StatusInternalServerError)
	}
}

func main() {
	http.HandleFunc("/mutate", injectSidecarHandler)
	log.Println("Starting sidecar injector on :8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatalf("Failed to start server: %v", err)
	}
}
   

Validação e Testes

1. Implantação


# Implantar o injector de Sidecar no K8s
kubectl apply -f sidecar-injector-deployment.yaml

# Criar um serviço de exemplo em Go
kubectl create deployment test-app --image=golang:alpine --port=8080
   

2. Verificação da Injeção


kubectl get pod -o wide
# Exemplo de saída:
# NAME                         READY   STATUS    RESTARTS   AGE
# test-app-7b9d5f4c8f-xyzabc   2/2     Running   0          3m
   

Um Pod com um contêiner de aplicação e um contêiner Envoy (2/2 em READY) indica sucesso.

3. Teste de Requisição


# Obter o IP do Cluster do serviço
SERVICE_IP=$(kubectl get svc test-app -o jsonpath='{.spec.clusterIP}')

# Enviar requisição para a porta do Envoy (10000), que encaminhará para o serviço real (8080)
curl -H "Host: example.com" http://$SERVICE_IP:10000
   

Após o teste, observe os logs do Envoy para detalhes de requisição e tempo de resposta. Métricas de QPS, latência e taxa de erro podem ser coletadas pelo Prometheus (a partir da interface /stats do Envoy) e visualizadas no Grafana.

Sugestões para Funcionalidades Avançadas

Explore a expansão do service mesh com funcionalidades como:

  • Comunicação mTLS: Habilite TLS bidirecional para segurança contra ataques man-in-the-middle, configurando os campos tls\_context no Envoy.
  • Circuit Breaking: Implemente limites de requisição (max\_requests) e erros consecutivos (consecutive\_errors) para proteger serviços contra falhas.
  • Exportação de Métricas para Prometheus: Configure stats\_tags no Envoy para adicionar rótulos customizados às métricas expostas na interface /stats.

# Configuração do Envoy para métricas:
stats_config:
 stats_matcher:
   inclusion_list:
     prefixes: ["upstream_cx_total", "cluster.upstream_rq"]
   

Esta abordagem oferece uma alternativa customizável, compreensível e depurável em comparação a soluções mais complexas como Istio ou Linkerd. É particularmente valiosa para quem deseja entender os princípios fundamentais ou para fins educacionais.

Recomenda-se a integração com OpenTelemetry para coletar dados de rastreamento distribuído, aprimorando ainda mais a observabilidade. Lembre-se de realizar testes de carga e implantação gradual em ambientes de produção.

Tags: service mesh envoy go kubernetes controle de microsserviços

Publicado em 7-17 22:59