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\_contextno 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\_tagsno 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.