Сравнение производительности HashiCorp Vault с разными бэкендами
Vault — Open Source-решение от HashiCorp для управления секретами. Его изначальная ориентированность на модульность и масштабируемость позволяет запускать как небольшой dev-сервер Vault на своем ноутбуке, так и полноценный HA-кластер для production-сред.
Начиная работать с Vault, мы задались двумя вопросами:
Какой бэкенд (т.е. место, где физически хранятся секреты; им может быть локальная файловая система или решение на основе реляционных БД и других хранилищ) использовать и что по этому поводу говорят сами авторы?
Какую производительность и нагрузку может выдержать выбранная нами архитектура?
По первому вопросу все сначала кажется предельно простым: HashiCorp рекомендует единственный правильный вариант — Consul. Не сказать, что удивительно, если знать/посмотреть на авторов этого продукта. А вот со вторым вопросом все сложнее. Но почему в принципе нас это волновало?
В проекте, где активно используется Vault, мы изначально запустили его на основе Consul, доверившись выбору по умолчанию. И вскоре увидели, что сам факт наличия и обслуживания Consul«а добавляет существенные накладные расходы: десятки репозиториев, десятки окружений, множество задач по обслуживанию эксплуатации (обновления, реконфигурация и т.д.). Посовещавшись с коллегами, мы приняли решение упростить себе жизнь и мигрировать на GCS (Google Cloud Storage), т.к. это полностью снимает головную боль от поддержки бэкенд-решения как self-hosted.
Однако со временем проявилась одна неожиданность, о которой сразу не задумывались: бэкенды рассчитаны на совершенно разную нагрузку. Если GCS выдерживал «спокойный» трафик от разработки, то во время массовых тестов и активных демо-показов заказчикам начинались неприятности. Это естественным образом натолкнуло на мысль: «Что же будет на проде? Раз мы столкнулись с такой проблемой, надо бы протестировать нагрузочные способности различных бэкендов, сравнить их».
Итак, есть много параметров, от которых зависит производительность Vault: задержка сети, выбранный бэкенд, количество узлов в кластере Vault и нагрузка на них… При изменении даже одного из них цифры сильно меняются. В рамках этой статьи мы рассмотрим только часть этих факторов — «базовую» (т.е. не включающую в себя оптимизацию) производительность различных бэкендов.
Хорошая новость в том, что в Git есть огромное количество репозиториев с готовыми скриптами для тестирования. Если вы захотите провести испытания на своем Vault«е, можно воспользоваться, например, Vault Benchmarking Scripts и Load Tests for Vault. Или же дочитайте статью и попробуйте мой метод тестирования, основанный на скриптах от HashiCorp.
Вводные для тестирования
Требования
Чтобы проводить описанные ниже тесты, нам понадобятся:
Уже работающий в режиме HA Vault. Его запуск я буду производить в отдельном Kubernetes-кластере, где будут разворачиваться 3 варианта кластеров Vault (с разными бэкендами).
Сторонний сервер для запуска скриптов тестирования. В нашем случае развернут отдельный сервер рядом с Kubernetes. Тестирование проводится по локальной сети, чтобы минимизировать сетевые задержки.
Участники
Мы будем тестировать три популярных бэкенда:
Consul;
PostgreSQL;
GCS (Google Cloud Storage).
Главным критерием при их выборе была поддержка HA (по этой причине есть GCS, но нет AWS S3), а также старались не брать концептуально одинаковые виды, чтобы сравнение было более интересным и показательным.
Полный список поддерживаемых бэкендов можно найти здесь. Используя представленный в статье алгоритм, не составит труда провести аналогичное тестирование и для любых других вариантов.
План
Как будет происходить тестирование?
В несколько потоков записываем 10000 секретов. У этого количества нет строгого обоснования: значение выбрано для достаточного объёма, «сложности» при тестировании бэкенда.
Аналогично считываем их.
Замеряем полученные данные для каждого бэкенда.
Технически нам в этом поможет специализированная утилита для тестирования нагрузки — wrk. Именно она используется в benchmarking-наборе от самой HashiCorp.
Тестируем производительность
1. Consul
Итак, сначала протестируем Vault с бэкендом Consul. По плану у нас два теста: на запись и на чтение секретов.
Запускаем кластер Consul из узлов в 3 pod«ах и указываем его как backend для Vault, тоже запущенного на 3 узлах.
Мы готовы начинать!
# Подготавливаем переменные окружения:
export VAULT_ADDR=https://vault.service.consul:8200
export VAULT_TOKEN=YOUR_ROOT_TOKEN
# Включаем авторизацию в Vault для простого тестирования
vault auth enable userpass
vault write auth/userpass/users/loadtester password=benchmark policies=default
# Так как Vault запущен не в dev-моде,
# в пути `secret` сейчас ничего не должно быть
vault secrets enable -path secret -version 1 kv
git clone https://github.com/hashicorp/vault-guides.git
cd vault-guides/operations/benchmarking/wrk-core-vault-operations/
# Будем одновременно записывать рандомные секреты, используя:
# 6 потоков
# 16 соединений на поток
# 30 секунд на выполнение теста
# 10000 секретов для записи
nohup wrk -t6 -c16 -d30s -H "X-Vault-Token: $VAULT_TOKEN" -s write-random-secrets.lua $VAULT_ADDR -- 10000 > prod-test-write-1000-random-secrets-t6-c16-30sec.log &
Получаем такие данные:
Number of secrets is: 10000
thread 1 created
Number of secrets is: 10000
thread 2 created
Number of secrets is: 10000
thread 3 created
Number of secrets is: 10000
thread 4 created
Number of secrets is: 10000
thread 5 created
Number of secrets is: 10000
thread 6 created
Running 30s test @ http://vault:8200
6 threads and 16 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 134.96ms 68.89ms 558.62ms 84.21%
Req/Sec 16.31 6.62 50.00 87.82%
2685 requests in 30.04s, 317.27KB read
Requests/sec: 89.39
Transfer/sec: 10.56KB
thread 1 made 446 requests including 446 writes and got 443 responses
thread 2 made 447 requests including 447 writes and got 445 responses
thread 3 made 447 requests including 447 writes and got 445 responses
thread 4 made 459 requests including 459 writes and got 457 responses
thread 5 made 450 requests including 450 writes and got 448 responses
thread 6 made 449 requests including 449 writes and got 447 responses
Дальше проведем тест на чтение: запишем 1000 секретов, после чего будем читать их способом, аналогичным тому, что применяли выше для записи.
# Запишем 1000 секретов:
wrk -t1 -c1 -d5m -H "X-Vault-Token: $VAULT_TOKEN" -s write-secrets.lua $VAULT_ADDR -- 1000
# И проверим, что тысячный секрет создался:
vault read secret/read-test/secret-1000
Key Value
--- -----
refresh_interval 768h
extra 1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx7xxxxxxxxx8xxxxxxxxx9xxxxxxxxx0xxxxxxxxx
thread-1 write-1000
# Пробуем одновременное чтение 1000 секретов в 4 потока
nohup wrk -t4 -c16 -d30s -H "X-Vault-Token: $VAULT_TOKEN" -s read-secrets.lua $VAULT_ADDR -- 1000 false > prod-test-read-1000-random-secrets-t4-c16-30s.log &
Результат:
Number of secrets is: 1000
thread 1 created with print_secrets set to false
Number of secrets is: 1000
thread 2 created with print_secrets set to false
Number of secrets is: 1000
thread 3 created with print_secrets set to false
Number of secrets is: 1000
thread 4 created with print_secrets set to false
Running 30s test @ http://vault:8200
4 threads and 16 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 2.25ms 2.88ms 54.18ms 88.77%
Req/Sec 2.64k 622.04 4.81k 65.91%
315079 requests in 30.06s, 130.08MB read
Requests/sec: 10483.06
Transfer/sec: 4.33MB
thread 1 made 79705 requests including 79705 reads and got 79700 responses
thread 2 made 79057 requests including 79057 reads and got 79053 responses
thread 3 made 78584 requests including 78584 reads and got 78581 responses
thread 4 made 77748 requests including 77748 reads and got 77745 responses
2. PostgreSQL
Проводим аналогичный тест для PostgreSQL 11.7.0 в качестве бэкенда. SSL выключен, все настройки выставлены по умолчанию.
Результат записи:
Number of secrets is: 10000
thread 1 created
Number of secrets is: 10000
thread 2 created
Number of secrets is: 10000
thread 3 created
Number of secrets is: 10000
thread 4 created
Number of secrets is: 10000
thread 5 created
Number of secrets is: 10000
thread 6 created
Running 30s test @ http://vault:8200
6 threads and 16 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 33.02ms 14.74ms 120.97ms 67.81%
Req/Sec 60.70 15.02 121.00 69.44%
10927 requests in 30.03s, 1.26MB read
Requests/sec: 363.88
Transfer/sec: 43.00KB
thread 1 made 1826 requests including 1826 writes and got 1823 responses
thread 2 made 1797 requests including 1797 writes and got 1796 responses
thread 3 made 1833 requests including 1833 writes and got 1831 responses
thread 4 made 1832 requests including 1832 writes and got 1830 responses
thread 5 made 1801 requests including 1801 writes and got 1799 responses
thread 6 made 1850 requests including 1850 writes and got 1848 responses
Результат чтения:
Number of secrets is: 1000
thread 1 created with print_secrets set to false
Number of secrets is: 1000
thread 2 created with print_secrets set to false
Number of secrets is: 1000
thread 3 created with print_secrets set to false
Number of secrets is: 1000
thread 4 created with print_secrets set to false
Running 30s test @ http://vault:8200
4 threads and 16 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 2.48ms 2.93ms 71.93ms 89.76%
Req/Sec 2.11k 495.44 3.01k 65.67%
251861 requests in 30.02s, 103.98MB read
Requests/sec: 8390.15
Transfer/sec: 3.46MB
thread 1 made 62957 requests including 62957 reads and got 62952 responses
thread 2 made 62593 requests including 62593 reads and got 62590 responses
thread 3 made 63074 requests including 63074 reads and got 63070 responses
thread 4 made 63252 requests including 63252 reads and got 63249 responses
3. GCS
Теперь для бэкенда GCS (настройки тоже по умолчанию):
Запись:
Number of secrets is: 10000
thread 1 created
Number of secrets is: 10000
thread 2 created
Number of secrets is: 10000
thread 3 created
Number of secrets is: 10000
thread 4 created
Number of secrets is: 10000
thread 5 created
Number of secrets is: 10000
thread 6 created
Running 30s test @ http://vault:8200
6 threads and 16 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 209.08ms 42.58ms 654.51ms 91.96%
Req/Sec 9.89 2.45 20.00 87.21%
1713 requests in 30.04s, 202.42KB read
Requests/sec: 57.03
Transfer/sec: 6.74KB
thread 1 made 292 requests including 292 writes and got 289 responses
thread 2 made 286 requests including 286 writes and got 284 responses
thread 3 made 284 requests including 284 writes and got 282 responses
thread 4 made 289 requests including 289 writes and got 287 responses
thread 5 made 287 requests including 287 writes and got 285 responses
thread 6 made 288 requests including 288 writes and got 286 responses
Чтение:
Number of secrets is: 1000
thread 1 created with print_secrets set to false
Number of secrets is: 1000
thread 2 created with print_secrets set to false
Number of secrets is: 1000
thread 3 created with print_secrets set to false
Number of secrets is: 1000
thread 4 created with print_secrets set to false
Running 30s test @ http://vault:8200
4 threads and 16 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 4.40ms 1.72ms 53.59ms 92.11%
Req/Sec 0.93k 121.29 1.19k 71.83%
111196 requests in 30.02s, 45.91MB read
Requests/sec: 3704.27
Transfer/sec: 1.53MB
thread 1 made 31344 requests including 31344 reads and got 31339 responses
thread 2 made 26639 requests including 26639 reads and got 26635 responses
thread 3 made 25690 requests including 25690 reads and got 25686 responses
thread 4 made 27540 requests including 27540 reads and got 27536 responses
Сводные результаты
Consul | PostgreSQL | GCS | |
write (RPS/thread) | 16.31 | 60.70 | 9.89 |
write (total) | 2685 | 10927 | 1713 |
read (RPS/thread) | 2640 | 2110 | 930 |
read (total) | 315079 | 251861 | 111196 |
Показатели в таблице — это количество секретов:
записываемых в секунду на 1 поток;
записанных в общей сложности (во всех потоках) за 30 секунд;
читаемых в секунду на 1 поток;
прочитанных в общей сложности (во всех потоках) за 30 секунд.
Прочитать полученные результаты можно так:
PostgreSQL показал себя с самой лучшей стороны в плане записи. Он в 4 раза производительнее Consul, и в 10 раз производительнее GCS.
В плане чтения лучший показатель у Consul.
Более общие выводы:
При ожидаемом очень большом количестве записей можно рекомендовать использовать PostgreSQL. В остальных случаях — Consul, который официально советуют использовать в Hashicorp.
По нашему опыту, GCS наиболее удобен, если мы не хотим сами разворачивать и поддерживать бэкенд. Вдобавок, у него есть удобный Auto Unseal и встроенный сервис для создания бэкапов (с ними есть свои сложности, но об этом — в другой статье).
Примечание по результатам
Полученные цифры позволяют получить базовое представление о нагрузках, которые может выдержать стоковый Vault без особых настроек для оптимизации. Судя по результатам, именно производительность самих бэкендов была пределом, поэтому дополнительные тесты (например, с другим числом потоков) в статью не добавлены. Впрочем, при желании их легко произвести самостоятельно.
При тестировании мы использовали токен $VAULT_TOKEN
, не учитывая процесс авторизации. Результаты будут разниться в зависимости от разных параметров. Среди них:
количество одновременных клиентов;
какой метод аутентификации используете;
как часто клиенты будут аутентифицироваться;
какой тип секретов будет использоваться;
откуда будут приходить запросы (из локальной сети или через интернет);
…
Заключение
Надеюсь, что проведенное испытание поможет ответить на вопрос о производительности бэкендов в Vault, который возник и у нас. Представленную здесь последовательность действий рассматривайте как основу для проведения собственных экспериментов. И уже такое внутреннее тестирование поможет получить более точное представление, как будет себя вести Vault при реальных нагрузках в вашем случае. Для этого могут потребоваться изменения в конфигурации бэкендов (и их выбор вообще), а также модификация Lua-скриптов и/или параметров их вызова.
Для себя мы выбрали Consul как бэкенд, который наиболее оптимально справляется с нашими итоговыми нагрузками в проекте, и как решение, что по умолчанию предлагается разработчиками Vault. Естественно, дополнительная нагрузка по обслуживанию никуда не делась, однако когда заходит речь за стабильность работы проекта, на компромиссы идти не хочется: надо делать всё возможное, чтобы архитектура соответствовала требованиям.