Перейти к содержанию

Прокси gRPC

Не сохраняющий состояние обратный прокси etcd, работающий на уровне gRPC

Прокси gRPC — это не сохраняющий состояние обратный прокси etcd, работающий на уровне gRPC (L7). Он предназначен для снижения общей вычислительной нагрузки на основной кластер etcd. Для горизонтального масштабирования прокси объединяет запросы API наблюдения и аренды. Для защиты кластера от злоупотребляющих клиентов он кэширует запросы диапазонов ключей.

Прокси gRPC поддерживает несколько конечных точек сервера etcd. При запуске прокси случайным образом выбирает одну конечную точку сервера etcd. Она обслуживает все запросы, пока прокси не обнаружит её отказ. Обнаружив отказ конечной точки, прокси gRPC переключается на другую доступную точку, скрывая сбой от клиентов. В будущем могут поддерживаться и другие политики повторных попыток, например взвешенный циклический выбор.

Масштабируемый API наблюдения

Прокси gRPC объединяет несколько клиентских наблюдателей (c-watchers) за одним ключом или диапазоном в единственного наблюдателя (s-watcher), подключённого к серверу etcd. Все события от s-watcher прокси рассылает своим c-watchers.

Если N клиентов наблюдают за одним ключом, один прокси gRPC может снизить нагрузку наблюдения на сервер etcd с N до 1. Для дальнейшего распределения серверной нагрузки можно развернуть несколько прокси gRPC.

В следующем примере три клиента наблюдают за ключом A. Прокси gRPC объединяет трёх наблюдателей, создавая одного наблюдателя, подключённого к серверу etcd.

            +-------------+
            | etcd server |
            +------+------+
                   ^ watch key A (s-watcher)
                   |
           +-------+-----+
           | gRPC proxy  | <-------+
           |             |         |
           ++-----+------+         |watch key A (c-watcher)
watch key A ^     ^ watch key A    |
(c-watcher) |     | (c-watcher)    |
    +-------+-+  ++--------+  +----+----+
    |  client |  |  client |  |  client |
    |         |  |         |  |         |
    +---------+  +---------+  +---------+

Ограничения

Чтобы эффективно объединить несколько клиентских наблюдателей в одного, прокси gRPC по возможности присоединяет новые c-watchers к существующему s-watcher. Такой объединённый s-watcher может быть не синхронизирован с сервером etcd из-за сетевых задержек или буферизованных недоставленных событий. Если ревизия наблюдения не указана, прокси gRPC не гарантирует, что c-watcher начнёт наблюдение с самой последней ревизии хранилища. Например, наблюдатель клиента, подключённого к серверу etcd с ревизией 1000, начнёт с ревизии 1000. Наблюдатель клиента, подключённого через прокси gRPC, может начать с ревизии 990.

Аналогичные ограничения относятся к отмене. При отмене наблюдателя ревизия сервера etcd может превышать ревизию ответа на отмену.

В большинстве сценариев эти два ограничения не должны создавать проблем. В будущем могут появиться дополнительные параметры, принудительно направляющие наблюдателя в обход прокси gRPC для получения более точных ревизий в ответах.

Масштабируемый API аренды

Чтобы поддерживать аренды активными, клиент должен установить как минимум один поток gRPC к серверу etcd для отправки периодических сигналов активности. Если рабочая нагрузка etcd включает интенсивную работу с арендами, распределённую между множеством клиентов, эти потоки могут приводить к чрезмерной загрузке CPU. Чтобы сократить общее количество потоков в основном кластере, прокси поддерживает объединение потоков аренды.

Если N клиентов обновляют аренды, один прокси gRPC снижает потоковую нагрузку на сервер etcd с N до 1. В развёртывании можно использовать дополнительные прокси gRPC, чтобы распределить потоки между несколькими прокси.

В следующем примере три клиента обновляют три независимые аренды (L1, L2 и L3). Прокси gRPC объединяет три клиентских потока аренды (c-streams) в один поток поддержания аренды активной (s-stream), подключённый к серверу etcd. Прокси пересылает сигналы активности аренды с клиентской стороны из c-streams в s-stream, а затем возвращает ответы соответствующим c-streams.

          +-------------+
          | etcd server |
          +------+------+
                 ^
                 | heartbeat L1, L2, L3
                 | (s-stream)
                 v
         +-------+-----+
         | gRPC proxy  +<-----------+
         +---+------+--+            | heartbeat L3
             ^      ^               | (c-stream)
heartbeat L1 |      | heartbeat L2  |
(c-stream)   v      v (c-stream)    v
      +------+-+  +-+------+  +-----+--+
      | client |  | client |  | client |
      +--------+  +--------+  +--------+

Защита от злоупотребляющих клиентов

Прокси gRPC кэширует ответы на запросы, когда это не нарушает требований согласованности. Так сервер etcd можно защитить от злоупотребляющих клиентов с плотными циклами for.

Запуск прокси gRPC etcd

Рассмотрим кластер etcd со следующими статическими конечными точками:

ИмяАдресИмя узла
infra010.0.1.10infra0.example.com
infra110.0.1.11infra1.example.com
infra210.0.1.12infra2.example.com

Запустите прокси gRPC etcd для использования этих статических конечных точек следующей командой:

$ etcd grpc-proxy start --endpoints=infra0.example.com,infra1.example.com,infra2.example.com --listen-addr=127.0.0.1:2379

Прокси gRPC etcd запускается и слушает порт 2379. Он пересылает клиентские запросы одной из трёх указанных выше конечных точек.

Отправка запросов через прокси:

$ ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379 put foo bar
OK
$ ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379 get foo
foo
bar

Синхронизация клиентских конечных точек и разрешение имён

Прокси поддерживает регистрацию своих конечных точек для обнаружения путём записи в заданную пользователем конечную точку. Это решает две задачи. Во-первых, клиенты могут синхронизировать свои конечные точки с набором конечных точек прокси для высокой доступности. Во-вторых, прокси становится поставщиком конечных точек для разрешения имён gRPC в etcd.

Зарегистрируйте прокси, указав определённый пользователем префикс:

$ etcd grpc-proxy start --endpoints=localhost:2379 \
  --listen-addr=127.0.0.1:23790 \
  --advertise-client-url=127.0.0.1:23790 \
  --resolver-prefix="___grpc_proxy_endpoint" \
  --resolver-ttl=60

$ etcd grpc-proxy start --endpoints=localhost:2379 \
  --listen-addr=127.0.0.1:23791 \
  --advertise-client-url=127.0.0.1:23791 \
  --resolver-prefix="___grpc_proxy_endpoint" \
  --resolver-ttl=60

В списке участников прокси перечислит всех своих участников:

ETCDCTL_API=3 etcdctl --endpoints=http://localhost:23790 member list --write-out table

+----+---------+--------------------------------+------------+-----------------+
| ID | STATUS  |              NAME              | PEER ADDRS |  CLIENT ADDRS   |
+----+---------+--------------------------------+------------+-----------------+
|  0 | started | Gyu-Hos-MBP.sfo.coreos.systems |            | 127.0.0.1:23791 |
|  0 | started | Gyu-Hos-MBP.sfo.coreos.systems |            | 127.0.0.1:23790 |
+----+---------+--------------------------------+------------+-----------------+

Это позволяет клиентам автоматически обнаруживать конечные точки прокси через Sync:

cli, err := clientv3.New(clientv3.Config{
    Endpoints: []string{"http://localhost:23790"},
})
if err != nil {
    log.Fatal(err)
}
defer cli.Close()

// fetch registered grpc-proxy endpoints
if err := cli.Sync(context.Background()); err != nil {
    log.Fatal(err)
}

Обратите внимание: если прокси настроен без префикса разрешения,

$ etcd grpc-proxy start --endpoints=localhost:2379 \
  --listen-addr=127.0.0.1:23792 \
  --advertise-client-url=127.0.0.1:23792

API списка участников прокси grpc-proxy возвращает его собственный advertise-client-url:

ETCDCTL_API=3 etcdctl --endpoints=http://localhost:23792 member list --write-out table

+----+---------+--------------------------------+------------+-----------------+
| ID | STATUS  |              NAME              | PEER ADDRS |  CLIENT ADDRS   |
+----+---------+--------------------------------+------------+-----------------+
|  0 | started | Gyu-Hos-MBP.sfo.coreos.systems |            | 127.0.0.1:23792 |
+----+---------+--------------------------------+------------+-----------------+

Пространства имён

Предположим, приложению нужен полный контроль над всем пространством ключей, однако кластер etcd используется совместно с другими приложениями. Чтобы все приложения могли работать, не мешая друг другу, прокси может разделить пространство ключей etcd так, чтобы клиентам казалось, что они имеют доступ ко всему пространству. Если прокси передан флаг --namespace, все поступающие в него клиентские запросы преобразуются: к ключам добавляется заданный пользователем префикс. Обращения к кластеру etcd выполняются под этим префиксом, а прокси удаляет префикс из ответов; для клиента всё выглядит так, будто префикса нет.

Чтобы назначить прокси пространство имён, запустите его с --namespace:

$ etcd grpc-proxy start --endpoints=localhost:2379 \
  --listen-addr=127.0.0.1:23790 \
  --namespace=my-prefix/

Теперь обращения к прокси прозрачно снабжаются префиксом в кластере etcd:

$ ETCDCTL_API=3 etcdctl --endpoints=localhost:23790 put my-key abc
# OK
$ ETCDCTL_API=3 etcdctl --endpoints=localhost:23790 get my-key
# my-key
# abc
$ ETCDCTL_API=3 etcdctl --endpoints=localhost:2379 get my-prefix/my-key
# my-prefix/my-key
# abc

Терминирование TLS

Терминируйте TLS защищённого кластера etcd с помощью прокси gRPC, обслуживающего незашифрованную локальную конечную точку.

Для проверки запустите одноузловой кластер etcd с клиентским https:

$ etcd --listen-client-urls https://localhost:2379 --advertise-client-urls https://localhost:2379 --cert-file=peer.crt --key-file=peer.key --trusted-ca-file=ca.crt --client-cert-auth

Убедитесь, что клиентский порт обслуживает https:

# fails
$ ETCDCTL_API=3 etcdctl --endpoints=http://localhost:2379 endpoint status
# works
$ ETCDCTL_API=3 etcdctl --endpoints=https://localhost:2379 --cert=client.crt --key=client.key --cacert=ca.crt endpoint status

Затем запустите прокси gRPC на localhost:12379, подключив его к конечной точке etcd https://localhost:2379 с помощью клиентских сертификатов:

$ etcd grpc-proxy start --endpoints=https://localhost:2379 --listen-addr localhost:12379 --cert client.crt --key client.key --cacert=ca.crt --insecure-skip-tls-verify &

Наконец, проверьте терминирование TLS, записав ключ в прокси по http:

$ ETCDCTL_API=3 etcdctl --endpoints=http://localhost:12379 put abc def
# OK

Метрики и состояние

Прокси gRPC предоставляет конечные точки /health и Prometheus /metrics для участников etcd, заданных через --endpoints. В качестве альтернативы флагом --metrics-addr можно определить дополнительный URL, отвечающий на запросы к конечным точкам /metrics и /health.

$ etcd grpc-proxy start \
  --endpoints https://localhost:2379 \
  --metrics-addr https://0.0.0.0:4443 \
  --listen-addr 127.0.0.1:23790 \
  --key client.key \
  --key-file proxy-server.key \
  --cert client.crt \
  --cert-file proxy-server.crt \
  --cacert ca.pem \
  --trusted-ca-file proxy-ca.pem

Известная проблема

Основной интерфейс прокси обслуживает как HTTP2, так и HTTP/1.1. Если прокси настроен с TLS, как в примере выше, при обращении к слушающему интерфейсу клиентом наподобие cURL для получения /metrics или /health потребуется явно указать в запросе протокол HTTP/1.1. При использовании флага --metrics-addr вторичный интерфейс этого не требует.

 $ curl --cacert proxy-ca.pem --key proxy-client.key --cert proxy-client.crt https://127.0.0.1:23790/metrics --http1.1