Только дурак нуждается в порядке — гений господствует над хаосом
– Albert Einstein
Когда мы разрабатываем приложение, мы хотим, чтобы оно было стабильным и надежным. Но как мы можем быть уверены, что наше приложение справится со сбоями, которые могут возникнуть во внешних сервисах, от которых оно зависит? В этом случае нам может помочь инструмент под названием Toxiproxy.
Toxiproxy - это инструмент, который позволяет имитировать различные виды сбоев и ошибок во внешних сервисах, чтобы убедиться, что наше приложение правильно обрабатывает эти ситуации. Мы можем использовать Toxiproxy для тестирования наших приложений на всех этапах разработки и убедиться в их надежности и стабильности.
В этой статье мы рассмотрим, как Toxiproxy помогает разработчикам тестировать приложения и избежать сбоев во внешних сервисах. Расскажу о том, как работает Toxiproxy, как его использовать и какие преимущества он может принести для разработки наших приложений.
Для более удобной работы есть инструмент toxiproxy-frontend, веб морда для взаимодействия с сервером toxiproxy. Так же существуют клиенты под разные языки, например для PHP toxiproxy-php-client или для Python toxiproxy-python, список клиентов можно посмотреть в репозиторий проекта.
Все материалы для этой статьй в репозиторий на github.
Сервер Link to heading
Для начала нам нужно поднять сервер toxiproxy, я буду использовать docker.
поднимаем сервер и наружу пробрасываем прорты
- 5000-5001 - порты на которых будем создавать прокси
- 8474 - порт сервера toxiproxy
- 9000 - порт nginx
- 9001 - порт веб морды для toxiproxy
docker-compose up -d
проверим что сервер поднялся
docker-compose ps
если всё хорошо, увидим примерно следующее
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
toxiproxy_example-nginx-1 nginx "/docker-entrypoint.…" nginx 3 seconds ago Up 1 second 0.0.0.0:9000->80/tcp, :::9000->80/tcp
toxiproxy_example-toxiproxy-1 ghcr.io/shopify/toxiproxy "/toxiproxy -host=0.…" toxiproxy 3 seconds ago Up 2 seconds 0.0.0.0:5000-5001->5000-5001/tcp, :::5000-5001->5000-5001/tcp, 0.0.0.0:8474->8474/tcp, :::8474->8474/tcp
toxiproxy_example-toxiproxy-frontend-1 buckle/toxiproxy-frontend "java -jar server.jar" toxiproxy-frontend 3 seconds ago Up 1 second 0.0.0.0:9001->8080/tcp, :::9001->8080/tcp
проверим что наш nginx доступен снаружи, в ответ получим код стандартной страницы, всё хорошо
curl --silent --show-error http://localhost:9000 | grep "Welcome to nginx!"
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>
Прокси (Proxy) Link to heading
Проверим что наш сервер toxiproxy доступен, в ответ получим пустой json, хорошо
curl --silent --show-error http://localhost:8474/proxies
{}
Создадим прокси для nginx в ответ получим json нашего прокси
curl --data '{"name": "proxy_nginx", "upstream": "nginx:80", "listen": "0.0.0.0:5000"}' localhost:8474/proxies
Посмотреть список прокси
curl localhost:8474/proxies
Если нужно удалить прокси
curl -X DELETE localhost:8474/proxies/proxy_nginx
проверим что наш прокси работает, тут указываем порт нашего прокси 5000, в ответ получим код стандартной страницы, всё хорошо
curl --silent --show-error http://localhost:5000 | grep "Welcome to nginx!"
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>
Перед тем как перейти к созданию токси, для начала провирим какая задержка без токси на наш nginx сервер через наш прокси.
запустим curl с помощью команды time, а полученные данные выкиним в /dev/null
time curl --silent --show-error http://localhost:5000 > /dev/null
________________________________________________________
Executed in 4.76 millis fish external
usr time 3.82 millis 191.00 micros 3.62 millis
sys time 0.08 millis 80.00 micros 0.00 millis
Здесь нас интересует Executed in 4.76 millis, это время за которое curl запустился, сделал запрос, получил ответ и завершился.
Токсики (Toxics) Link to heading
У всех токсиков есть общие атрибуты
Атрибуты:
- type: тип токсика
- stream: может принимать значения upstream или downstream, указывает к чему будет применятся токсик, когда запрос к серверу (Upstream) или ответ от сервера (Downstream)
- toxicity: устанавливает токсичность, где 1.0 = 100%
Токсик задержки (Latency) Link to heading
Сетевые задержки, когда запрос или ответ долго висит в ожиданий, после ожидания передача или получение данных идут на нормальной скорости.
Данный токси добавляет задержку для всех данных которые проходят через прокси.
Атрибуты:
- latency: задержка в милисекундах
- jitter: дрожание в милисекундах
Задержка равна задержке +/- дрожание. Дрожание оно же мерцание, случайное на стабильное время задержки. То есть если мы создадим токси с latency = 1000 и jitter = 500, то получим случайную задержку от 0,5 до 1,5 сек. при каждом новом запросе.
Создадим такой токсик
curl --data '{"name":"latency_downstream","type":"latency","stream":"downstream","toxicity":1.0,"attributes":{"latency":1000,"jitter":500}}' localhost:8474/proxies/proxy_nginx/toxics
проверим отослав запрос несколько раз
time curl --silent --show-error http://localhost:5000 > /dev/null
________________________________________________________
Executed in 833.98 millis fish external
usr time 4.72 millis 376.00 micros 4.35 millis
sys time 0.16 millis 160.00 micros 0.00 millis
time curl --silent --show-error http://localhost:5000 > /dev/null
________________________________________________________
Executed in 1.37 secs fish external
usr time 0.39 millis 390.00 micros 0.00 millis
sys time 3.79 millis 167.00 micros 3.62 millis
time curl --silent --show-error http://localhost:5000 > /dev/null
________________________________________________________
Executed in 537.06 millis fish external
usr time 4.65 millis 375.00 micros 4.27 millis
sys time 0.17 millis 168.00 micros 0.00 millis
как видим время ответа сильно отличается всё работает как ожидалось
Можем удалим токсик
curl -X DELETE localhost:8474/proxies/proxy_nginx/toxics/latency_downstream
Токсик (down) Link to heading
Bringing a service down is not technically a toxic in the implementation of Toxiproxy. This is done by POSTing to /proxies/{proxy} and setting the enabled field to false.
Токсик пропускной способности (Bandwidth) Link to heading
Ограничивает соединение максимальным установленым количеством килобайт в секунду.
Атрибуты:
- rate: скорость в КБ/с
проверим насколько быстро скачается файл размером 10МБ без ограницений скорости
time curl --silent --show-error http://localhost:5000/large_file.bin > /dev/null
________________________________________________________
Executed in 13.93 millis fish external
usr time 0.47 millis 468.00 micros 0.00 millis
sys time 7.67 millis 0.00 micros 7.67 millis
Создадим токсик с ограничением скорости в 1000 КБ/с
curl --data '{"name":"bandwidth_downstream","type":"bandwidth","stream":"downstream","toxicity":1.0,"attributes":{"rate":1000}}' localhost:8474/proxies/proxy_nginx/toxics
Проверим сколько будет скачиваться файл
time curl --silent --show-error http://localhost:5000/large_file.bin > /dev/null
________________________________________________________
Executed in 10.49 secs fish external
usr time 16.08 millis 366.00 micros 15.71 millis
sys time 18.69 millis 169.00 micros 18.52 millis
примерно 10 секунд, работает как и ожидалось
Так же можно проверить ситуацию в которой нам важно получить данные от сервера за определённый отрезок времени, допустим не дольше 5 секунд
time curl --silent --show-error --max-time 5 http://localhost:5000/large_file.bin > /dev/null
curl: (28) Operation timed out after 5000 milliseconds with 4995216 out of 10485760 bytes received
________________________________________________________
Executed in 5.00 secs fish external
usr time 4.72 millis 419.00 micros 4.30 millis
sys time 17.27 millis 213.00 micros 17.06 millis
получили ошибку Operation timed out after 5000 milliseconds
о том что не уложились в 5 секунд
Можем удалим токсик
curl -X DELETE localhost:8474/proxies/proxy_nginx/toxics/bandwidth_downstream
Токсик (Slow close) Link to heading
Delay the TCP socket from closing until delay has elapsed.
Атрибуты:
- delay: время в милисекундах
Токсик таймаут (Timeout) Link to heading
Останавливает передачу всех данных и закрывает соединение по истечении времени ожидания. Если тайм-аут равен 0, соединение не будет закрыто, а данные будут отложены до тех пор, пока токсик не будет удален.
Атрибуты:
- timeout: время в милисекундах
создадим токсик
curl --data '{"name":"timeout","type":"timeout","stream":"upstream","toxicity":1.0,"attributes":{"timeout":5000}}' localhost:8474/proxies/proxy_nginx/toxics
проверим как это работает
time curl --show-error --silent http://localhost:5000 | grep "Welcome to nginx!"
curl: (52) Empty reply from server
________________________________________________________
Executed in 5.01 secs fish external
usr time 2.14 millis 556.00 micros 1.58 millis
sys time 4.53 millis 277.00 micros 4.25 millis
как видим соединение было закрыто через 5 секунд
Можем удалим токсик
curl -X DELETE localhost:8474/proxies/proxy_nginx/toxics/timeout
Токсик сброс (Reset peer) Link to heading
Имитация TCP RESET (Connection reset by peer) для соединений путем закрытия заглушки Input немедленно или по истечении тайм-аута.
Атрибуты:
- timeout: время в милисекундах
создадим токсик
curl --data '{"name":"reset_peer","type":"reset_peer","stream":"downstream","toxicity":1.0,"attributes":{"timeout":5000}}' localhost:8474/proxies/proxy_nginx/toxics
проверим как это работает
time curl --silent --show-error http://localhost:5000/large_file.bin > /dev/null
curl: (52) Empty reply from server
________________________________________________________
Executed in 5.01 secs fish external
usr time 0.00 millis 0.00 micros 0.00 millis
sys time 5.00 millis 685.00 micros 4.31 millis
Токсик (Slicer) Link to heading
Нарезает данные TCP на маленькие биты, дополнительно добавляя задержку между каждым нарезанным «пакетом».
Атрибуты:
- average_size: размер пакета в байтах
- size_variation: изменение байтов среднего пакета (должно быть меньше, чем average_size)
- delay: время в микросекундах для задержки каждого пакета
создадим токсик
curl --data '{"name":"slicer_downstream","type":"slicer","stream":"downstream","toxicity":1.0,"attributes":{"average_size":10240,"size_variation":5120,"delay":100000}}' localhost:8474/proxies/proxy_nginx/toxics
проверим как это работает, запустим скачиваться файл
curl -vvv http://localhost:5000/large_file.bin > /dev/null
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 127.0.0.1:5000...
* Connected to localhost (127.0.0.1) port 5000 (#0)
> GET /large_file.bin HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/8.0.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.23.3
< Date: Mon, 01 May 2023 12:41:31 GMT
< Content-Type: application/octet-stream
< Content-Length: 10485760
< Last-Modified: Wed, 08 Mar 2023 13:18:08 GMT
< Connection: keep-alive
< ETag: "64088b10-a00000"
< Accept-Ranges: bytes
<
0 10.0M 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0{ [7240 bytes data]
21 10.0M 21 2222k 0 0 9846 0 0:17:44 0:03:51 0:13:53 10380
откроем tcpdump
командой:
sudo tcpdump -i lo -nn "port 5000"
нас интересует размер пакетов
14:45:52.781551 IP 127.0.0.1.38498 > 127.0.0.1.5000: Flags [.], ack 2587558, win 461, options [nop,nop,TS val 1778143690 ecr 1778143690], length 0
14:45:53.781988 IP 127.0.0.1.5000 > 127.0.0.1.38498: Flags [P.], seq 2587558:2592532, ack 92, win 512, options [nop,nop,TS val 1778144691 ecr 1778143690], length 4974
14:45:53.782030 IP 127.0.0.1.38498 > 127.0.0.1.5000: Flags [.], ack 2592532, win 488, options [nop,nop,TS val 1778144691 ecr 1778144691], length 0
14:45:54.782176 IP 127.0.0.1.5000 > 127.0.0.1.38498: Flags [P.], seq 2592532:2603414, ack 92, win 512, options [nop,nop,TS val 1778145691 ecr 1778144691], length 10882
14:45:54.782199 IP 127.0.0.1.38498 > 127.0.0.1.5000: Flags [.], ack 2603414, win 465, options [nop,nop,TS val 1778145691 ecr 1778145691], length 0
14:45:55.782285 IP 127.0.0.1.5000 > 127.0.0.1.38498: Flags [P.], seq 2603414:2612511, ack 92, win 512, options [nop,nop,TS val 1778146691 ecr 1778145691], length 9097
14:45:55.782329 IP 127.0.0.1.38498 > 127.0.0.1.5000: Flags [.], ack 2612511, win 472, options [nop,nop,TS val 1778146691 ecr 1778146691], length 0
14:45:56.782825 IP 127.0.0.1.5000 > 127.0.0.1.38498: Flags [P.], seq 2612511:2622115, ack 92, win 512, options [nop,nop,TS val 1778147692 ecr 1778146691], length 9604
14:45:56.782831 IP 127.0.0.1.38498 > 127.0.0.1.5000: Flags [.], ack 2622115, win 470, options [nop,nop,TS val 1778147692 ecr 1778147692], length 0
14:45:57.782934 IP 127.0.0.1.5000 > 127.0.0.1.38498: Flags [P.], seq 2622115:2636182, ack 92, win 512, options [nop,nop,TS val 1778148692 ecr 1778147692], length 14067
14:45:57.782942 IP 127.0.0.1.38498 > 127.0.0.1.5000: Flags [.], ack 2636182, win 453, options [nop,nop,TS val 1778148692 ecr 1778148692], length 0
14:45:58.783258 IP 127.0.0.1.5000 > 127.0.0.1.38498: Flags [P.], seq 2636182:2642631, ack 92, win 512, options [nop,nop,TS val 1778149692 ecr 1778148692], length 6449
14:45:58.783308 IP 127.0.0.1.38498 > 127.0.0.1.5000: Flags [.], ack 2642631, win 482, options [nop,nop,TS val 1778149692 ecr 1778149692], length 0
14:45:59.783622 IP 127.0.0.1.5000 > 127.0.0.1.38498: Flags [P.], seq 2642631:2654986, ack 92, win 512, options [nop,nop,TS val 1778150692 ecr 1778149692], length 12355
14:45:59.783665 IP 127.0.0.1.38498 > 127.0.0.1.5000: Flags [.], ack 2654986, win 459, options [nop,nop,TS val 1778150693 ecr 1778150692], length 0
14:46:00.784261 IP 127.0.0.1.5000 > 127.0.0.1.38498: Flags [P.], seq 2654986:2668950, ack 92, win 512, options [nop,nop,TS val 1778151693 ecr 1778150693], length 13964
Токсик лимит данных (Limit data) Link to heading
Закрывает соединение при превышении лимита передаваемых данных.
Атрибуты:
- bytes: количество байтов, которое он должен передать, прежде чем соединение будет закрыто
создадим токсик (создавать его на upstream не имеет смысла, только если мы не передаем данные на сервер)
curl --data '{"name":"limit_data","type":"limit_data","stream":"downstream","toxicity":1.0,"attributes":{"bytes":1000000}}' localhost:8474/proxies/proxy_nginx/toxics
проверим как это работает
time curl --silent --show-error http://localhost:5000/large_file.bin > /dev/null
curl: (18) transfer closed with 9486021 bytes remaining to read
________________________________________________________
Executed in 4.97 millis fish external
usr time 1.93 millis 268.00 micros 1.66 millis
sys time 1.68 millis 0.00 micros 1.68 millis
получили ошибку что было получено 9486021
и соединение было закрыто
Можем удалим токсик
curl -X DELETE localhost:8474/proxies/proxy_nginx/toxics/limit_data
ссылки Link to heading
Youtube - Chaos Engineering with ToxiProxy