DevOps

Service Mesh без Istio: построение межсервисной сети на Go с Envoy и Kubernetes

Ruslan Ismailov Опубликовано 12 мин чтения
S

Введение: когда Istio становится обузой

Istio — мощный инструмент, но его сложность часто обратно пропорциональна размеру команды. Control plane из нескольких компонентов (istiod, ingress gateway, egress gateway), overhead на ресурсы кластера, нетривиальный debugging и крутая кривая обучения делают его избыточным для многих production-систем. По данным CNCF Survey 2024, значительная часть команд, попробовавших Istio, отказывается от него в течение первого года именно из-за операционной сложности.

Если ваша команда работает с Go-микросервисами в Kubernetes и нуждается в mTLS, трейсинге, health checks и балансировке трафика — всё это можно получить без Istio, вручную настроив Envoy как sidecar-прокси. Именно этому посвящена данная статья.

Что такое service mesh и зачем он нужен в Go-архитектуре

Service mesh — это инфраструктурный слой, который управляет межсервисным взаимодействием: маршрутизацией трафика, балансировкой нагрузки, аутентификацией, шифрованием и observability. В микросервисной архитектуре на Go эти задачи традиционно решались на уровне приложения через библиотеки (например, go-kit, grpc-go с interceptors). Однако такой подход имеет недостатки:

  • Логика сети размазана по коду каждого сервиса
  • Обновление политик требует пересборки и передеплоя
  • Разнородность реализаций между командами
  • Сложность единообразного enforcement mTLS

Service mesh выносит эту логику в отдельный прокси-процесс (sidecar), работающий рядом с каждым pod. Ваш Go-сервис ничего не знает о шифровании или circuit breaking — всё это делает Envoy прозрачно.

Envoy как data plane: ключевые возможности

Envoy Proxy — высокопроизводительный L4/L7-прокси, написанный на C++. Именно он используется как data plane в Istio, Linkerd v2 (частично) и других mesh-решениях. Ключевые возможности Envoy, релевантные для нашей задачи:

  • Dynamic configuration через xDS API — конфигурация обновляется без перезапуска
  • HTTP/gRPC load balancing — round-robin, least-request, ring-hash
  • mTLS termination и origination — шифрование между сервисами
  • Distributed tracing — интеграция с Zipkin, Jaeger, OpenTelemetry
  • Prometheus metrics — встроенная экспозиция метрик
  • Circuit breaking и retries — отказоустойчивость на уровне прокси
  • Health checking — активные проверки upstream-сервисов

В режиме «без Istio» мы управляем Envoy через статические YAML-конфигурации или собственный xDS control plane. Для большинства команд статической конфигурации вполне достаточно.

Настройка Envoy sidecar вручную в Kubernetes

Рассмотрим пошаговый пример развёртывания Go-сервиса с Envoy sidecar в Kubernetes. Предположим, у нас есть сервис order-service, который общается с payment-service.

Шаг 1: ConfigMap с конфигурацией Envoy

apiVersion: v1
kind: ConfigMap
metadata:
  name: envoy-config
  namespace: production
data:
  envoy.yaml: |
    static_resources:
      listeners:
        - name: ingress_listener
          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
                    access_log:
                      - name: envoy.access_loggers.stdout
                        typed_config:
                          "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
                    http_filters:
                      - name: envoy.filters.http.router
                        typed_config:
                          "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                    route_config:
                      name: local_route
                      virtual_hosts:
                        - name: local_service
                          domains: ["*"]
                          routes:
                            - match:
                                prefix: "/"
                              route:
                                cluster: local_app
      clusters:
        - name: local_app
          connect_timeout: 0.25s
          type: STATIC
          load_assignment:
            cluster_name: local_app
            endpoints:
              - lb_endpoints:
                  - endpoint:
                      address:
                        socket_address:
                          address: 127.0.0.1
                          port_value: 8080
        - name: payment_service
          connect_timeout: 0.5s
          type: STRICT_DNS
          lb_policy: ROUND_ROBIN
          load_assignment:
            cluster_name: payment_service
            endpoints:
              - lb_endpoints:
                  - endpoint:
                      address:
                        socket_address:
                          address: payment-service.production.svc.cluster.local
                          port_value: 10000
    admin:
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 9901

Шаг 2: Deployment с Envoy sidecar

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      initContainers:
        - name: iptables-init
          image: busybox:1.36
          securityContext:
            capabilities:
              add: ["NET_ADMIN"]
          command:
            - sh
            - -c
            - |
              iptables -t nat -A OUTPUT -p tcp --dport 8080 -j RETURN
              iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-port 10000
      containers:
        - name: order-service
          image: myregistry/order-service:1.4.2
          ports:
            - containerPort: 8080
          env:
            - name: PAYMENT_SERVICE_URL
              value: "http://127.0.0.1:10001"
        - name: envoy
          image: envoyproxy/envoy:v1.29-latest
          args: ["-c", "/etc/envoy/envoy.yaml", "--log-level", "info"]
          ports:
            - containerPort: 10000
              name: proxy
            - containerPort: 9901
              name: admin
          volumeMounts:
            - name: envoy-config
              mountPath: /etc/envoy
          resources:
            requests:
              memory: "64Mi"
              cpu: "50m"
            limits:
              memory: "128Mi"
              cpu: "200m"
      volumes:
        - name: envoy-config
          configMap:
            name: envoy-config

Обратите внимание на initContainer с iptables: он перехватывает исходящий трафик и перенаправляет его через Envoy. Это стандартная техника, используемая и в Istio.

Интеграция с Go-сервисами: health checks, метрики, трейсинг

Health checks

Go-сервис должен предоставлять endpoint для health check. Минимальный пример:

package main

import (
    "net/http"
    "encoding/json"
)

func healthHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/health", healthHandler)
    mux.HandleFunc("/ready", healthHandler)
    http.ListenAndServe(":8080", mux)
}

В конфигурации Envoy добавьте активный health check для upstream-кластера:

clusters:
  - name: payment_service
    health_checks:
      - timeout: 1s
        interval: 10s
        unhealthy_threshold: 3
        healthy_threshold: 2
        http_health_check:
          path: "/health"

Метрики через Prometheus

Envoy автоматически экспортирует метрики на admin-порту (по умолчанию 9901) по пути /stats/prometheus. Добавьте ServiceMonitor для Prometheus Operator:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: envoy-sidecar-metrics
  namespace: production
spec:
  selector:
    matchLabels:
      app: order-service
  endpoints:
    - port: admin
      path: /stats/prometheus
      interval: 15s

Distributed Tracing с OpenTelemetry

Добавьте tracing-конфигурацию в Envoy для отправки span'ов в Jaeger:

tracing:
  http:
    name: envoy.tracers.zipkin
    typed_config:
      "@type": type.googleapis.com/envoy.config.trace.v3.ZipkinConfig
      collector_cluster: jaeger
      collector_endpoint: "/api/v2/spans"
      shared_span_context: false
      collector_endpoint_version: HTTP_JSON

В Go-сервисе используйте go.opentelemetry.io/otel для создания span'ов и пробрасывания контекста через HTTP-заголовки (traceparent, b3). Envoy автоматически подхватит и продолжит trace.

mTLS между сервисами без service mesh оператора

Настройка mTLS вручную — наиболее трудоёмкая часть, но вполне выполнимая. Нам нужны: корневой CA, сертификаты для каждого сервиса и настройка Envoy на обоих концах соединения.

Генерация сертификатов (cert-manager)

Используйте cert-manager для автоматической выдачи и ротации сертификатов:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: order-service-cert
  namespace: production
spec:
  secretName: order-service-tls
  duration: 24h
  renewBefore: 1h
  subject:
    organizations:
      - mycompany
  commonName: order-service.production.svc.cluster.local
  dnsNames:
    - order-service.production.svc.cluster.local
  issuerRef:
    name: internal-ca
    kind: ClusterIssuer

Конфигурация mTLS в Envoy (upstream — origination)

clusters:
  - name: payment_service
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
        common_tls_context:
          tls_certificates:
            - certificate_chain:
                filename: /etc/certs/tls.crt
              private_key:
                filename: /etc/certs/tls.key
          validation_context:
            trusted_ca:
              filename: /etc/certs/ca.crt

Конфигурация mTLS (downstream — termination)

filter_chains:
  - transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
        require_client_certificate: true
        common_tls_context:
          tls_certificates:
            - certificate_chain:
                filename: /etc/certs/tls.crt
              private_key:
                filename: /etc/certs/tls.key
          validation_context:
            trusted_ca:
              filename: /etc/certs/ca.crt

Секрет order-service-tls монтируется в pod через volumeMounts. При ротации cert-manager обновит секрет, а Envoy можно настроить на динамическую перезагрузку через SDS (Secret Discovery Service).

Сравнение с Linkerd и Cilium

Linkerd

Linkerd — значительно более лёгкий service mesh, чем Istio. Его data plane написан на Rust (linkerd2-proxy), потребляет меньше памяти и CPU. Установка сводится к нескольким командам CLI. Из минусов: менее гибкая конфигурация (нет такого контроля над маршрутизацией, как в Envoy), ограниченная поддержка протоколов (в основном HTTP/1, HTTP/2, gRPC). Для команд, которым нужен быстрый старт без глубокой кастомизации — отличный выбор в 2026.

Cilium

Cilium работает на уровне eBPF в ядре Linux, что даёт минимальный overhead. Он совмещает функции CNI (сетевой плагин Kubernetes) и service mesh, исключая необходимость sidecar-контейнеров вообще. Cilium Service Mesh поддерживает mTLS, L7 observability и трафик-политики. Идеален для команд, которые хотят минимальные задержки и контроль на уровне сети. Сложнее в отладке из-за работы в ядре.

Ручной Envoy sidecar (наш подход)

Максимальная гибкость и контроль. Нет зависимости от сторонних операторов. Сложнее поддерживать при росте числа сервисов — конфигурации нужно версионировать и шаблонизировать (Helm, Kustomize). Оптимален для команд от 2 до 20 сервисов или при особых требованиях к маршрутизации.

Рекомендации по мониторингу и observability

Без централизованного control plane observability особенно важна. Рекомендуемый стек:

  • Prometheus + Grafana — сбор метрик с Envoy admin endpoint (/stats/prometheus). Используйте готовые дашборды Envoy для Grafana (ID: 6693).
  • Jaeger или Grafana Tempo — сбор distributed traces. Envoy генерирует span'ы автоматически при настроенном tracing.
  • Loki — сбор access log'ов Envoy в структурированном JSON-формате. Настройте access log format явно:
access_log:
  - name: envoy.access_loggers.stdout
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
      log_format:
        json_format:
          timestamp: "%START_TIME%"
          method: "%REQ(:METHOD)%"
          path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
          response_code: "%RESPONSE_CODE%"
          duration: "%DURATION%"
          upstream_host: "%UPSTREAM_HOST%"
          trace_id: "%REQ(X-B3-TRACEID)%"
  • Alerting: настройте алерты на envoy_cluster_upstream_rq_5xx, envoy_cluster_upstream_cx_connect_fail и P99 latency через envoy_cluster_upstream_rq_time_bucket.

Итоги: когда использовать полноценный service mesh

Подход «Envoy sidecar вручную» оправдан, когда:

  • У вас от 3 до 20 сервисов и небольшая DevOps-команда
  • Нужен полный контроль над конфигурацией без «магии» оператора
  • Istio избыточен по ресурсам и сложности
  • Есть специфические требования к маршрутизации или протоколам

Переходите на полноценный service mesh (Istio, Linkerd, Cilium), когда:

  • Количество сервисов превышает 30–50 и поддержка ручных конфигураций становится накладной
  • Нужна автоматическая ротация сертификатов и централизованное управление политиками
  • Требуется canary-деплой, traffic shifting на уровне mesh
  • Команда готова инвестировать время в изучение и эксплуатацию control plane

Построение service mesh без Istio на Go с Envoy и Kubernetes — это прагматичный выбор для зрелых команд, которые ценят прозрачность и контроль над своей инфраструктурой. Главное — не забывать про версионирование конфигураций, автоматизацию через Helm/Kustomize и своевременный мониторинг состояния прокси.

Технологии

Теги

Руслан Исмаилов

Senior Web / Backend разработчик. Senior web/backend разработчик с 9-летним опытом. Стек: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, микросервисы, CI/CD. Подробнее обо мне →