Ambassador pattern + сравнение паттернов
Ambassador — это контейнер, который проксирует исходящие соединения main приложения, инкапсулируя всю сложность работы с внешним миром. App connects на localhost:port своего ambassador, а ambassador обрабатывает: TLS, retries, circuit breaking, sharding, connection pooling. Канонический пример — Envoy в service mesh (Istio, Linkerd, Consul Connect). Сейчас, после стабилизации native sidecar (GA v1.33), ambassador почти всегда реализован как sidecar — паттерн жив концептуально, но синтаксически слился с sidecar.
Reverse proxy: NGINX, HAProxy, TLS termination
Что делает ambassador
Главная идея: main app не должен знать о сложности внешнего мира. Он просто делает HTTP GET http://localhost:8080/orders — а ambassador разруливает:
- куда реально идёт запрос (какой Service, какой instance);
- как он шифруется (mTLS с цепочкой сертификатов);
- как реагирует на ошибки (retries с exponential backoff, circuit breaker);
- как наблюдается (distributed tracing, metrics, logging);
- как балансируется (round-robin, weighted, locality-aware).
App в этой модели — простой: один localhost endpoint, никаких внешних libraries для retry/TLS/tracing. Ambassador — сложный: вся network logic вынесена сюда.
Use case 1: Service mesh (Istio, Linkerd)
Главный production пример ambassador. Istio инжектит Envoy sidecar в каждый Pod через mutating webhook. Все исходящие соединения main app перехватываются iptables redirect и идут через Envoy.
# До mutating webhook
spec:
containers:
- name: app
image: my-app
# После Istio sidecar injection (упрощённо)
spec:
initContainers:
- name: istio-init # обычный init для iptables setup
image: istio/proxyv2
securityContext:
capabilities:
add: [NET_ADMIN, NET_RAW]
command: ['istio-iptables'] # настраивает redirect outbound на :15001
- name: istio-proxy # ambassador как native sidecar
image: istio/proxyv2
restartPolicy: Always
ports:
- containerPort: 15001 # outbound listener
- containerPort: 15006 # inbound listener
containers:
- name: app
image: my-app
Envoy ambassador даёт:
- mTLS между всеми Pods mesh: автоматический cert rotation, identity SPIFFE;
- traffic management: weighted routing, canary, A/B;
- resilience: timeout, retry, circuit breaker, outlier detection;
- observability: tracing headers, metrics, access logs;
- security: AuthorizationPolicy, RequestAuthentication.
Use case 2: DB connection pooling
Postgres на каждое соединение делает новый процесс — heavy. Если у вас 100 app instances, каждый имеет pool из 20 соединений — 2000 процессов на одной БД, она ляжет. Решение — pgbouncer как ambassador: app connects на localhost:5432, pgbouncer keeps connection pool to real postgres.
apiVersion: v1
kind: Pod
metadata:
name: app-with-pgbouncer
spec:
initContainers:
- name: pgbouncer
image: bitnami/pgbouncer:1.22
restartPolicy: Always
env:
- name: POSTGRESQL_HOST
value: 'postgres.db.svc.cluster.local'
- name: POSTGRESQL_PORT
value: '5432'
- name: PGBOUNCER_PORT
value: '5432' # листенер на localhost:5432
- name: PGBOUNCER_POOL_MODE
value: 'transaction'
containers:
- name: app
image: my-app:v1
env:
- name: DATABASE_URL
value: 'postgres://app@localhost:5432/myapp' # localhost, не реальный
App думает, что подключается к postgres напрямую. Pgbouncer держит, скажем, 10 реальных соединений в pool, переиспользует их для всех app requests.
Use case 3: Sharding logic
App говорит на localhost, ambassador смотрит на ключ запроса и роутит на нужный shard:
app → localhost:8080 → ambassador → shard-1.db (для user_id < 1000)
→ shard-2.db (для 1000 ≤ user_id < 2000)
→ shard-3.db (для user_id ≥ 2000)
App не знает про shards. Ambassador инкапсулирует topology. Изменилась схема шардирования — обновили ambassador config, app не трогаем.
Killer момент: ambassador сегодня — это просто native sidecar
Концептуально ambassador — отдельный паттерн (он именно про outbound proxy, тогда как sidecar — про дополнение функциональности). Но синтаксически в Kubernetes:
# Sidecar (логирование)
spec:
initContainers:
- name: log-shipper
image: fluent-bit
restartPolicy: Always
# Ambassador (Envoy)
spec:
initContainers:
- name: envoy
image: istio/proxyv2
restartPolicy: Always
# Adapter (Prometheus exporter)
spec:
initContainers:
- name: redis-exporter
image: prom/redis_exporter
restartPolicy: Always
Все три — один и тот же механизм native Sidecar. Различие только в том, что делает контейнер внутри. Kubernetes не знает, что один из них adapter, а другой ambassador.
С приходом native sidecar (GA v1.33) разделение adapter/ambassador/sidecar практически исчезло из манифеста. На CKAD оно полезно для проектирования и обсуждений архитектуры, но в kubectl/YAML вы пишете initContainers + restartPolicy: Always для всех трёх. Знайте имена паттернов на уровне «концепция и канонический пример», без преувеличения важности.
Итоговое сравнение 4 паттернов
| Pattern | Что делает | Жизненный цикл | YAML location |
|---|---|---|---|
| Init | Setup перед main | завершается до main | initContainers (без restartPolicy) |
| Sidecar | Дополняет main | параллельно с main | initContainers + restartPolicy: Always |
| Adapter | Transform output | параллельно с main | initContainers + restartPolicy: Always |
| Ambassador | Proxy outbound | параллельно с main | initContainers + restartPolicy: Always |
Adapter и ambassador синтаксически — native sidecar. Init — это единственный концептуально и синтаксически отдельный механизм.
Что выбрать для своей задачи: дерево решений
- Setup до старта main, потом не нужен? → Init container.
- Long-running рядом с main для дополнительной функции (логи, метрики, secrets)? → native Sidecar.
- Long-running, преобразующий output main в чужой формат? → Adapter (синтаксически — native sidecar).
- Long-running, проксирующий outbound app для mTLS/retries/sharding? → Ambassador (синтаксически — native sidecar).
- Независимый процесс, общается с main по HTTP/gRPC? → Отдельный Deployment + Service. Не multi-container.