company_banner

Сравниваем производительность HashiCorp Vault с разными бэкендами

    Vault — Open Source-решение от HashiCorp для управления секретами. Его изначальная ориентированность на модульность и масштабируемость позволяет запускать как небольшой dev-сервер Vault на своем ноутбуке, так и полноценный HA-кластер для production-сред.

    Начиная работать с Vault, мы задались двумя вопросами:

    1. Какой бэкенд (т.е. место, где физически хранятся секреты; им может быть локальная файловая система или решение на основе реляционных БД и других хранилищ) использовать и что по этому поводу говорят сами авторы?

    2. Какую производительность и нагрузку может выдержать выбранная нами архитектура?

    По первому вопросу все сначала кажется предельно простым: 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. Тестирование проводится по локальной сети, чтобы минимизировать сетевые задержки.

    Участники 

    Мы будем тестировать три популярных бэкенда:

    1. Consul;

    2. PostgreSQL;

    3. GCS (Google Cloud Storage).

    Главным критерием при их выборе была поддержка HA (по этой причине есть GCS, но нет AWS S3), а также старались не брать концептуально одинаковые виды, чтобы сравнение было более интересным и показательным.

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

    План

    Как будет происходить тестирование?

    1. В несколько потоков записываем 10000 секретов. У этого количества нет строгого обоснования: значение выбрано для достаточного объёма, «сложности» при тестировании бэкенда.

    2. Аналогично считываем их.

    3. Замеряем полученные данные для каждого бэкенда.

    Технически нам в этом поможет специализированная утилита для тестирования нагрузки — 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. Естественно, дополнительная нагрузка по обслуживанию никуда не делась, однако когда заходит речь за стабильность работы проекта, на компромиссы идти не хочется: надо делать всё возможное, чтобы архитектура соответствовала требованиям.

    Флант
    DevOps-as-a-Service, Kubernetes, обслуживание 24×7

    Комментарии 10

      +1

      Спасибо за пост. PostgreSQL у вас в кластере или single?

        0
        Single инстанс постгри
          +4

          Все таки сравнение некорректное. Consul в кластере, а PostgreSQL в single режиме.

            0
            В целом замечание справедливо, но в нашем случае кластер пг — явный overhead для такой задачи, поэтому сравнивали в таком простом виде.
            Ну, и это небольшой эксперимент для нас (а не полноценное исследование), и я специально подробно описывал весь процесс, чтобы любой мог воспользоваться наработками и повторить процесс для того, что актуально конкретно вам
              +1
              Вы писали вам нужен HA. О каком HA может идти речь в случае single postgres? У консула же может упасть несколько нод и он будет работать.

              Вообще я не понял о какой сложности поддержки consul идет речь, он написан на go, там один бинарник и настраивается элементарно.
        +2
        Старнно, что не сравнивали и с integrated storage, которое появилось с версии 1.4+
          +1
          Думал об этом, но изначально тестировались проверенные варианты которые хорошо работают в кубах. А поддержка Raft появилась относительно недавно. В любом случае изучу этот вопрос и посмотрю на что он способен, спасибо
          +4
          PostgreSQL показал себя с самой лучшей стороны в плане записи. Он в 4 раза производительнее Consul, и в 10 раз производительнее GCS.

          Кто бы мог подумать что запись в Raft-enabled кластер или сетевое хранилище будет намного медленнее, чем в один инстанс постгреса в append-only режиме

            +1

            Ещё dynamodb не хватает в тесте.
            Очень популярное решение

              +2
              Не было цели «Сравнить все». Вот даны все вводные для возможности сравнить то, что требуется конкретно вам.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое