
⏰ 🔥 TCP-прокси для имитации сетевых и системных условий при хаос-тестировании и проверке отказоустойчивости
Toxiproxy — это фреймворк для моделирования сетевых условий. Он создан специально для работы в средах тестирования, CI и разработки, поддерживая детерминированное вмешательство в соединения, но также с поддержкой случайного хаоса и настройки. Toxiproxy — это инструмент, который нужен вам, чтобы с помощью тестов доказать, что ваше приложение не имеет единых точек отказа. Мы успешно используем его во всех средах разработки и тестирования в Shopify с октября 2014 года. Смотрите наш [пост в блоге][blog] об отказоустойчивости для получения дополнительной информации.
Использование Toxiproxy состоит из двух частей. TCP-прокси, написанный на Go (именно он содержится в этом репозитории), и клиент, взаимодействующий с прокси по HTTP. Вы настраиваете своё приложение так, чтобы все тестовые соединения проходили через Toxiproxy и затем можете управлять их состоянием через HTTP. Смотрите Использование ниже о том, как настроить ваш проект.
Например, чтобы добавить 1000 мс задержки к ответу MySQL из клиента Ruby:```ruby Toxiproxy[:mysql_master].downstream(:latency, latency: 1000).apply do Shop.first # this takes at least 1s end
Чтобы остановить все экземпляры Redis:```ruby
Toxiproxy[/redis/].down do
Shop.first # this will throw an exception
end
Хотя примеры в этом README в настоящее время написаны на Ruby, ничто не мешает вам создать клиент на любом другом языке (см. Клиенты).
Существующие решения, которые мы нашли, не предоставляли такого динамического API, который был нужен нам для интеграционного и модульного тестирования. Инструменты Linux вроде nc и подобные не кроссплатформенны и требуют прав root, что делает их проблематичными в тестовых средах, средах разработки и CI.
Давайте разберём пример с приложением на Rails. Обратите внимание, что Toxiproxy никак не привязан к Ruby — это просто наш первый случай использования. Полный пример можно посмотреть на sirupsen/toxiproxy-rails-example. Чтобы сразу приступить к делу, перейдите к разделу Использование.
Для нашего популярного блога мы по какой-то причине храним теги записей в
Redis, а сами записи — в MySQL. У нас может быть класс Post, который
содержит несколько методов для работы с тегами в множестве Redis:```ruby
class Post < ActiveRecord::Base
def tags TagRedis.smembers(tag_key) end
def add_tag(tag) TagRedis.sadd(tag_key, tag) end
def remove_tag(tag) TagRedis.srem(tag_key, tag) end
def tag_key "post:tags:#{self.id}" end end
Мы решили, что возникновение ошибки при записи в хранилище тегов
(добавление/удаление) допустимо. Однако, если хранилище тегов недоступно, мы
должны иметь возможность видеть пост без тегов. Мы можем просто перехватить
`Redis::CannotConnectError` вокруг вызова Redis `SMEMBERS` в методе `tags`.
Давайте используем Toxiproxy, чтобы проверить это.
Поскольку мы уже установили Toxiproxy и он запущен на нашей машине, мы можем
перейти к шагу 2. Здесь нам нужно убедиться, что Toxiproxy имеет сопоставление
для тегов Redis. В `config/boot.rb` (до установления любого соединения) мы
добавляем:```ruby
require 'toxiproxy'
Toxiproxy.populate([
{
name: "toxiproxy_test_redis_tags",
listen: "127.0.0.1:22222",
upstream: "127.0.0.1:6379"
}
])
Затем в config/environments/test.rb мы задаём TagRedis как Redis-клиент, который подключается к Redis через Toxiproxy, добавив эту строку:```ruby
TagRedis = Redis.new(port: 22222)
Все вызовы в тестовой среде теперь проходят через Toxiproxy. Это означает, что мы можем
добавить модульный тест, в котором мы имитируем сбой:```ruby
test "should return empty array when tag redis is down when listing tags" do
@post.add_tag "mammals"
# Take down all Redises in Toxiproxy
Toxiproxy[/redis/].down do
assert_equal [], @post.tags
end
end
Тест завершается ошибкой Redis::CannotConnectError. Отлично! Toxiproxy успешно отключил Redis на время выполнения замыкания. Давайте сделаем метод tags устойчивым к сбоям:```ruby
def tags
TagRedis.smembers(tag_key)
rescue Redis::CannotConnectError
[]
end
Тесты проходят! Теперь у нас есть модульный тест, который доказывает, что при недоступном Redis
получение тегов возвращает пустой массив, а не выбрасывает исключение. Для полного
покрытия следует также написать интеграционный тест, который охватывает получение
всей страницы блога, когда Redis недоступен.
Полный пример приложения находится по адресу
[sirupsen/toxiproxy-rails-example](https://github.com/sirupsen/toxiproxy-rails-example).
## Использование
Настройка проекта для использования Toxiproxy состоит из трёх шагов:
1. Установка Toxiproxy
2. Заполнение Toxiproxy
3. Использование Toxiproxy
### 1. Установка Toxiproxy
**Linux**
Обратитесь к [`Releases`](https://github.com/Shopify/toxiproxy/releases) за последними
бинарными файлами и системными пакетами для вашей архитектуры.
**Ubuntu**```bash
$ wget -O toxiproxy-2.1.4.deb https://github.com/Shopify/toxiproxy/releases/download/v2.1.4/toxiproxy_2.1.4_amd64.deb
$ sudo dpkg -i toxiproxy-2.1.4.deb
$ sudo service toxiproxy start
OS X
С помощью Homebrew:```bash $ brew tap shopify/shopify $ brew install toxiproxy
Или с помощью [MacPorts](https://www.macports.org/):```bash
$ port install toxiproxy
Windows
Toxiproxy для Windows доступен для загрузки по адресу https://github.com/Shopify/toxiproxy/releases/download/v2.1.4/toxiproxy-server-windows-amd64.exe
Docker
Toxiproxy доступен в реестре контейнеров GitHub.
Старые версии <= 2.1.4 доступны на Docker Hub.```bash
$ docker pull ghcr.io/shopify/toxiproxy
$ docker run --rm -it ghcr.io/shopify/toxiproxy
Если вы используете Toxiproxy с хоста, а не из других контейнеров, включите сеть хоста с помощью `--net=host`.```shell
$ docker run --rm --entrypoint="/toxiproxy-cli" -it ghcr.io/shopify/toxiproxy list
Если у вас установлен Go, вы можете собрать Toxiproxy из исходного кода с помощью make-файла:```bash $ make build $ ./toxiproxy-server
#### Обновление с Toxiproxy 1.x
В Toxiproxy 2.0 в API были внесены несколько изменений, которые делают его несовместимым с версией 1.x.
Чтобы использовать версию 2.x сервера Toxiproxy, вам необходимо убедиться, что ваша клиентская
библиотека поддерживает ту же версию. Вы можете проверить, какая версия Toxiproxy у вас запущена,
обратившись к конечной точке `/version`.
Смотрите документацию вашей клиентской библиотеки для получения информации об изменениях в конкретной библиотеке. Подробные изменения
для сервера Toxiproxy можно найти в [CHANGELOG.md](https://github.com/shopify/toxiproxy/blob/HEAD/CHANGELOG.md).
### 2. Заполнение Toxiproxy
При запуске вашего приложения ему необходимо убедиться, что Toxiproxy знает, какие
конечные точки проксировать и куда. Основные параметры: имя, адрес, на котором Toxiproxy
будет **слушать**, и адрес вышестоящего сервера (upstream).
Некоторые клиентские библиотеки имеют вспомогательные средства для этой задачи, которые по сути просто
обеспечивают создание каждого прокси в списке. Пример из Ruby-клиента:```ruby
# Make sure `shopify_test_redis_master` and `shopify_test_mysql_master` are
# present in Toxiproxy
Toxiproxy.populate([
{
name: "shopify_test_redis_master",
listen: "127.0.0.1:22220",
upstream: "127.0.0.1:6379"
},
{
name: "shopify_test_mysql_master",
listen: "127.0.0.1:24220",
upstream: "127.0.0.1:3306"
}
])
Этот код должен выполняться как можно раньше при загрузке, до того как какой-либо код установит соединение через Toxiproxy. Пожалуйста, обратитесь к документации вашей клиентской библиотеки по вспомогательным функциям заполнения.
В качестве альтернативы используйте CLI для создания прокси, например:```bash toxiproxy-cli create -l localhost:26379 -u localhost:6379 shopify_test_redis_master
Мы рекомендуем использовать именование, как указано выше: `<app>_<env>_<data store>_<shard>`.
Это гарантирует отсутствие конфликтов между приложениями, использующими один и тот же
Toxiproxy.
Для крупных приложений мы рекомендуем хранить конфигурации Toxiproxy в отдельном
файле конфигурации. Мы используем `config/toxiproxy.json`. Этот файл можно
передать серверу с помощью опции `-config` или загрузить приложением
для использования с функцией `populate`.
Пример `config/toxiproxy.json`:```json
[
{
"name": "web_dev_frontend_1",
"listen": "[::]:https://raw.githubusercontent.com/shopify/toxiproxy/HEAD/18080%22,
"upstream": "webapp.domain:8080",
"enabled": true
},
{
"name": "web_dev_mysql_1",
"listen": "[::]:13306",
"upstream": "database.domain:3306",
"enabled": true
}
]
Используйте порты за пределами диапазона эфемерных портов, чтобы избежать случайных конфликтов портов.
По умолчанию в Linux это 32,768–61,000, см.
/proc/sys/net/ipv4/ip_local_port_range.
Чтобы использовать Toxiproxy, теперь нужно настроить ваше приложение для подключения через Toxiproxy. Продолжая наш пример из шага два, мы можем настроить наш Redis-клиент для подключения через Toxiproxy:```ruby
redis = Redis.new(port: 6380)
redis = Redis.new(port: 22220)
Теперь вы можете манипулировать им через Toxiproxy API. На Ruby:```ruby
redis = Redis.new(port: 22220)
Toxiproxy[:shopify_test_redis_master].downstream(:latency, latency: 1000).apply do
redis.get("test") # will take 1s
end
Или через CLI:```bash toxiproxy-cli toxic add -t latency -a latency=1000 shopify_test_redis_master
Пожалуйста, обратитесь к документации вашей клиентской библиотеки по вопросам использования.
### 4. Логирование
Доступны следующие уровни логирования: panic, fatal, error, warn или warning, info, debug и trace.
Уровень можно изменить через переменную окружения `LOG_LEVEL`.
### Токсики
Токсики управляют каналом между клиентом и upstream. Их можно добавлять
и удалять из прокси с помощью [HTTP API](#http-api). У каждого токсика есть свои параметры,
изменяющие его воздействие на соединения прокси.
Документацию по реализации собственных токсиков см. в [CREATING_TOXICS.md](https://github.com/shopify/toxiproxy/blob/HEAD/CREATING_TOXICS.md)
#### latency
Добавляет задержку ко всем данным, проходящим через прокси. Задержка равна `latency` +/- `jitter`.
Атрибуты:
- `latency`: время в миллисекундах
- `jitter`: время в миллисекундах
#### down
Остановка сервиса технически не является токсиком в реализации
Toxiproxy. Это делается отправкой `POST`-запроса на `/proxies/{proxy}` и установкой
поля `enabled` в значение `false`.
#### bandwidth
Ограничивает соединение максимальным количеством килобайт в секунду.
Атрибуты:
- `rate`: скорость в KB/s
#### slow_close
Задерживает закрытие TCP-сокета до истечения `delay`.
Атрибуты:
- `delay`: время в миллисекундах
#### timeout
Останавливает передачу всех данных и закрывает соединение по истечении `timeout`. Если
`timeout` равен 0, соединение не будет закрыто, а данные будут отбрасываться, пока
токсик не будет удалён.
Атрибуты:
- `timeout`: время в миллисекундах
#### reset_peer
Имитирует TCP RESET (сброс соединения одноранговым узлом) на соединениях, закрывая заглушку Input
немедленно или по истечении `timeout`.
Атрибуты:
- `timeout`: время в миллисекундах
#### slicer
Разрезает TCP-данные на мелкие части, при необходимости добавляя задержку между каждым
разрезанным «пакетом».
Атрибуты:
- `average_size`: размер среднего пакета в байтах
- `size_variation`: вариация размера среднего пакета в байтах (должна быть меньше average_size)
- `delay`: время в микросекундах задержки каждого пакета
#### limit_data
Закрывает соединение, когда переданные данные превышают лимит.
- `bytes`: количество байт, которое должно быть передано до закрытия соединения
#### packet_loss
Случайным образом отбрасывает фрагменты, проходящие через прокси, имитируя
нестабильные условия Wi-Fi, мобильной или спутниковой сети.
Атрибуты:
- `loss_rate`: вероятность [0.0-1.0] того, что фрагмент будет отброшен (по умолчанию 0.0)
- `correlation`: дополнительная вероятность отбрасывания, если предыдущий фрагмент был отброшен, моделирующая пакетные потери (по умолчанию 0.0)
### HTTP API
Всё взаимодействие клиента с демоном Toxiproxy происходит через
HTTP-интерфейс, описанный ниже.
Toxiproxy принимает HTTP-запросы на порту **8474**.
#### Поля прокси:
- `name`: имя прокси (string)
- `listen`: адрес прослушивания (string)
- `upstream`: адрес upstream-сервера (string)
- `enabled`: true/false (по умолчанию true при создании)
Чтобы изменить имя прокси, его необходимо удалить и создать заново.
Изменение полей `listen` или `upstream` приведёт к перезапуску прокси и разрыву всех активных соединений.
Если `listen` указан с портом 0, toxiproxy выберет эфемерный порт. Поле `listen`
в ответе будет обновлено с указанием фактического порта.
Если вы измените `enabled` на `false`, прокси будет остановлен. Вы можете переключить его
обратно на `true`, чтобы снова включить.
#### Поля токсиков:
- `name`: имя токсика (string, по умолчанию `<type>_<stream>`)
- `type`: тип токсика (string)
- `stream`: направление соединения, на которое воздействует токсик (по умолчанию `downstream`)
- `toxicity`: вероятность применения токсика к соединению (по умолчанию 1.0, 100%)
- `attributes`: карта атрибутов, специфичных для токсика
См. [Токсики](#toxics) для получения атрибутов, специфичных для токсиков.
Направление `stream` должно быть либо `upstream`, либо `downstream`. `upstream` применяет
токсик к соединению `client -> server`, а `downstream` применяет токсик
к соединению `server -> client`. Это можно использовать для отдельного изменения запросов и ответов.
#### Эндпоинты
Все эндпоинты работают с JSON.
- **GET /proxies** - список существующих прокси и их токсиков
- **POST /proxies** - создать новый прокси
- **POST /populate** - создать или заменить список прокси
- **GET /proxies/{proxy}** - показать прокси со всеми его активными токсиками
- **POST /proxies/{proxy}** - обновить поля прокси
- **DELETE /proxies/{proxy}** - удалить существующий прокси
- **GET /proxies/{proxy}/toxics** - список активных токсиков
- **POST /proxies/{proxy}/toxics** - создать новый токсик
- **GET /proxies/{proxy}/toxics/{toxic}** - получить поля активного токсика
- **POST /proxies/{proxy}/toxics/{toxic}** - обновить активный токсик
- **DELETE /proxies/{proxy}/toxics/{toxic}** - удалить активный токсик
- **POST /reset** - включить все прокси и удалить все активные токсики
- **GET /version** - возвращает номер версии сервера
- **GET /metrics** - возвращает метрики, совместимые с Prometheus
#### Заполнение прокси
Прокси можно добавлять и настраивать массово с помощью эндпоинта `/populate`. Для этого
в toxiproxy передаётся JSON-массив прокси. Если прокси с таким же именем уже существует,
он будет сравнён с новым прокси и заменён, если адреса `upstream` и `listen` не совпадают.
Вызов `/populate` может быть включён, например, при запуске приложения, чтобы убедиться, что все необходимые прокси
существуют. Этот вызов безопасно выполнять несколько раз, так как прокси останутся нетронутыми, пока их
поля согласованы с новыми данными.
### Пример CLI```bash
$ toxiproxy-cli create -l localhost:26379 -u localhost:6379 redis
Created new proxy redis
$ toxiproxy-cli list
Listen Upstream Name Enabled Toxics
======================================================================
127.0.0.1:26379 localhost:6379 redis true None
Hint: inspect toxics with `toxiproxy-client inspect <proxyName>`
При запуске без аргументов EDR Telemetry выводит таблицу доступных модулей с номерами, которые можно выбрать, введя соответствующий номер:
Примечание: Приведённая выше таблица автоматически перегенерируется при финализации сборки.
Модули можно использовать в трёх различных режимах:
net или действовать как EDR и блокировать сетевые подключения, включая чистые [SYN]-пакеты без полезной нагрузки:Однострочный режим – быстрый вариант. Вы можете запустить EDR Telemetry против целевой системы и немедленно завершить работу. Несколько примеров:
# CLI Examples
.\EDR.exe -s 192.168.1.1 -u user -p pass -m svc -a
.\EDR.exe -s 192.168.1.1 -u user -p pass -m fs -f C:\Windows\System32\drivers\etc\hosts
.\EDR.exe -s 192.168.1.1 -u user -p pass -m ps
.\EDR.exe -s 192.168.1.1 -u user -p pass -m net
Все доступные параметры командной строки можно найти ниже.
Режим EDR / блокировки – см. модуль .
$ redis-cli -p 26379 127.0.0.1:26379> SET omg pandas OK 127.0.0.1:26379> GET omg "pandas"
Пожалуйста, предоставьте Markdown-контент для перевода.```bash
$ toxiproxy-cli toxic add -t latency -a latency=1000 redis
Added downstream latency toxic 'latency_downstream' on proxy 'redis'
The input chunk is empty — there is no source text to translate. Please provide the actual chunk content.```bash $ redis-cli -p 26379 127.0.0.1:26379> GET omg "pandas" (1.00s) 127.0.0.1:26379> DEL omg (integer) 1 (1.00s)
I don't see any translatable content in the INPUT section — it's empty. Please provide the actual chunk text you'd like translated.```bash
$ toxiproxy-cli toxic remove -n latency_downstream redis
Removed toxic 'latency_downstream' on proxy 'redis'
Easily create SSH keys for your GitHub account over and over again
Hackers
curl -sL https://raw.githubusercontent.com/x4nth055/github-ssh-gen/master/install.sh | bash
github-ssh-gen
github-ssh-gen
curl -sL https://raw.githubusercontent.com/x4nth055/github-ssh-gen/master/uninstall.sh | bash
This project is licensed under the MIT License - see the LICENSE file for details```bash $ redis-cli -p 26379 127.0.0.1:26379> GET omg (nil)
The input chunk is empty — there is no content to translate.```bash
$ toxiproxy-cli delete redis
Deleted proxy redis
Input text is empty — no content was provided to translate. Please resend chunk 53.```bash $ redis-cli -p 26379 Could not connect to Redis at 127.0.0.1:26379: Connection refused
### Метрики
Toxiproxy предоставляет метрики, совместимые с Prometheus, через свой HTTP API по адресу /metrics.
Полные описания приведены в [METRICS.md](https://github.com/shopify/toxiproxy/blob/HEAD/METRICS.md).
### Часто задаваемые вопросы
**Насколько быстр Toxiproxy?** Скорость Toxiproxy во многом зависит от вашего оборудования,
но вы можете рассчитывать на задержку *< 100µs*, когда токсики не включены. При работе
с `GOMAXPROCS=4` на Macbook Pro мы достигли пропускной способности *~1000MB/s*, а на
более мощном настольном компьютере — до *2400MB/s*. В целом можно ожидать, что Toxiproxy
перемещает данные по крайней мере так же быстро, как приложение, которое вы тестируете.
**Может ли Toxiproxy выполнять рандомизированное тестирование?** Многие доступные токсики можно настроить
на случайность, например `jitter` в токсике `latency`. Также существует
глобальный параметр `toxicity`, который определяет процент соединений, на которые токсик
повлияет. Это наиболее полезно для таких токсиков, как `timeout`, который позволит
X% соединений получить тайм-аут.
**Я не вижу отражения моих действий Toxiproxy в MySQL**. MySQL предпочтёт
локальный сокет домена Unix для некоторых клиентов, независимо от того, какой порт вы передаёте,
если хост установлен в `localhost`. Настройте ваш сервер MySQL так, чтобы он не создавал
сокет, и используйте `127.0.0.1` в качестве хоста. Не забудьте удалить старый сокет
после перезапуска сервера.
**Toxiproxy вызывает перемежающиеся сбои соединений**. Используйте порты за пределами
диапазона эфемерных портов, чтобы избежать случайных конфликтов портов. Это `32,768`–`61,000` на
Linux по умолчанию, см. `/proc/sys/net/ipv4/ip_local_port_range`.
**Следует ли запускать отдельный Toxiproxy для каждого приложения?** Нет, мы рекомендуем использовать
один и тот же Toxiproxy для всех приложений. Чтобы различать сервисы, мы
рекомендуем называть прокси по схеме: `<app>_<env>_<data store>_<shard>`.
Например, `shopify_test_redis_master` или `shopify_development_mysql_1`.
### Разработка
* `make`. Соберите бинарный файл Toxiproxy для разработки под текущую платформу.
* `make all`. Соберите бинарные файлы и пакеты Toxiproxy для всех платформ. Требуется
наличие Go, скомпилированного с включённой кросс-компиляцией на Linux и Darwin (amd64),
а также [`goreleaser`](https://goreleaser.com/) в вашем `$PATH` для
сборки бинарных файлов Linux-пакета.
* `make test`. Запустите тесты Toxiproxy.
### Релиз
См. [RELEASE.md](https://github.com/shopify/toxiproxy/blob/HEAD/RELEASE.md)
[blog]: https://shopify.engineering/building-and-testing-resilient-ruby-on-rails-applications
| # | Модуль | Описание |
|---|
| 1 | svc | Запрос и взаимодействие с запущенными службами / драйверами |
| 2 | drv | Перечисление и запрос информации о драйверах |
| 3 | fs | Взаимодействие с файловой системой (перечисление файлов, чтение, запись) |
| 4 | net | Сетевые подключения, открытые порты и связанная информация |
| 5 | evt | Перечисление и запрос журналов событий |
| 6 | reg | Запрос и изменение реестра |
| 7 | ps | Перечисление процессов, дамп памяти и управление процессами |
| 8 | sys | Сбор информации о системе |
| 9 | ops | Общие операции с ОС / Etw / ядром |
| 10 | job | Взаимодействие с Job Objects |
| 11 | clip | Взаимодействие с буфером обмена |
| 12 | upd | Самообновление модуля |
ops