Ваши PHP-микросервисы общаются шепотом? Пора включить громкую связь
Представьте: у вас 15 PHP-микросервисов. Один отвечает за авторизацию, другой — за платежи, третий — за уведомления. Они общаются по HTTP, но каждую неделю что-то ломается: таймауты, падения, потерянные запросы. Вы добавляете ретраи, circuit breaker'ы, балансировку — и код превращается в спагетти из логики сетевого взаимодействия.
Здесь и приходит Service Mesh — инфраструктурный слой, который выносит всю сетевую магию из вашего PHP-кода. Не бойтесь: это не магия, а продуманная архитектура. И да, PHP-микросервисы могут (и должны) использовать Service Mesh.
Что такое Service Mesh и зачем он PHP-разработчику?
Service Mesh — это выделенный уровень инфраструктуры для управления межсервисным взаимодействием. Вместо того чтобы писать в каждом микросервисе логику балансировки, шифрования, трейсинга и таймаутов, вы делегируете это sidecar-прокси (обычно Envoy или Linkerd).
Для PHP это особенно актуально, потому что:
- PHP-приложения часто работают через FastCGI или FPM, и добавление сетевой логики в каждый сервис — это overhead
- Service Mesh даёт mTLS (взаимную аутентификацию) без изменения кода
- Вы получаете единый дашборд для мониторинга всех микросервисов
- Можно внедрять circuit breaker и retry прямо на уровне прокси
Самый популярный стек — Istio + Envoy. Но мы рассмотрим его с точки зрения PHP-разработчика.
Как это работает: PHP-сервис + sidecar
Допустим, у вас есть микросервис user-service на PHP. Он слушает порт 9000 (FPM). Без Service Mesh вы обращаетесь к нему напрямую:
GET http://user-service:9000/api/users/1
С Service Mesh в ваш pod (контейнер) добавляется Envoy-прокси. Теперь любой входящий и исходящий трафик проходит через него:
┌─────────────────┐ ┌──────────────┐
│ PHP-приложение │ ──► │ Envoy Proxy │
│ (user-service) │ │ (sidecar) │
└─────────────────┘ └──────┬───────┘
│
▼
┌──────────────┐
│ Envoy Proxy │
│ (другой сервис)│
└──────────────┘
Ваш PHP-код не знает о прокси. Он просто шлёт запросы на localhost:80 или localhost:9000 — Envoy перехватывает их и маршрутизирует по правилам Istio.
Практический пример: PHP-микросервис с Service Mesh на Istio
Шаг 1. Настройка PHP-сервиса (без изменений в коде)
Предположим, у вас есть простой PHP-скрипт, который возвращает данные о пользователе:
<?php
// user-service/index.php
$userId = $_GET['id'] ?? 0;
$users = [
1 => ['name' => 'Alice', 'email' => 'alice@example.com'],
2 => ['name' => 'Bob', 'email' => 'bob@example.com'],
];
header('Content-Type: application/json');
echo json_encode($users[$userId] ?? ['error' => 'User not found']);
Заверните в Docker:
# Dockerfile
FROM php:8.2-fpm
COPY index.php /var/www/html/index.php
CMD ["php-fpm"]
Всё стандартно. Никаких изменений для Service Mesh.
Шаг 2. Деплой с sidecar в Kubernetes
Добавьте аннотацию sidecar.istio.io/inject: "true" — Istio сам вставит Envoy:
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 2
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
annotations:
sidecar.istio.io/inject: "true" # <-- ключевая строка
spec:
containers:
- name: php
image: user-service:latest
ports:
- containerPort: 9000
---
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
selector:
app: user-service
ports:
- port: 80
targetPort: 9000
Теперь ваш PHP-сервис работает внутри Service Mesh.
Шаг 3. Настройка маршрутизации и таймаутов через Istio
Создайте VirtualService, который добавит таймаут и ретраи — без единой строчки PHP:
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service
spec:
hosts:
- user-service
http:
- timeout: 3s
retries:
attempts: 3
perTryTimeout: 2s
route:
- destination:
host: user-service
port:
number: 80
Если ваш PHP-сервис зависнет на 10 секунд, Istio оборвёт соединение через 3 секунды и попробует ещё 3 раза. Код не трогали!
Продвинутый кейс: PHP-клиент с gRPC через Service Mesh
PHP не очень дружит с gRPC из коробки, но через Service Mesh вы можете использовать HTTP/1.1 → gRPC transcoding. Envoy может преобразовывать REST-запросы в gRPC-вызовы.
Допустим, у вас есть gRPC-сервис на Go, а PHP-клиент хочет с ним общаться. Вместо установки protobuf в PHP вы:
- Делаете обычный POST-запрос на Envoy
- Envoy транскодирует его в gRPC
- Отправляет сервису
Пример конфигурации Envoy для транскодирования:
http_filters:
- name: envoy.filters.http.grpc_json_transcoder
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder
proto_descriptor: "/etc/envoy/proto.pb"
services: ["user.UserService"]
print_options:
add_whitespace: true
always_print_primitive_fields: true
PHP-код остаётся простым:
<?php
$client = new GuzzleHttp\Client(['base_uri' => 'http://user-service-envoy']);
$response = $client->post('/api/users', [
'json' => ['id' => 1]
]);
echo $response->getBody();
Envoy сам преобразует тело запроса в protobuf и вызовет gRPC-метод