
⏰ 🔥 Un proxy TCP para simular condiciones de red y del sistema para pruebas de caos y resiliencia
Toxiproxy es un framework para simular condiciones de red. Está hecho específicamente para funcionar en entornos de pruebas, CI y desarrollo, con manipulación determinista de las conexiones, pero también con soporte para caos aleatorio y personalización. Toxiproxy es la herramienta que necesitas para demostrar con pruebas que tu aplicación no tiene puntos únicos de fallo. Lo hemos estado utilizando con éxito en todos los entornos de desarrollo y pruebas de Shopify desde octubre de 2014. Consulta nuestra [entrada de blog][blog] sobre resiliencia para más información.
El uso de Toxiproxy consta de dos partes. Un proxy TCP escrito en Go (lo que contiene este repositorio) y un cliente que se comunica con el proxy a través de HTTP. Configuras tu aplicación para que todas las conexiones de prueba pasen por Toxiproxy y luego puedes manipular su estado mediante HTTP. Consulta Uso a continuación para configurar tu proyecto.
Por ejemplo, para añadir 1000ms de latencia a la respuesta de MySQL desde el cliente Ruby:```ruby Toxiproxy[:mysql_master].downstream(:latency, latency: 1000).apply do Shop.first # this takes at least 1s end
Para derribar todas las instancias de Redis:```ruby
Toxiproxy[/redis/].down do
Shop.first # this will throw an exception
end
Mientras que los ejemplos en este README están actualmente en Ruby, nada te impide crear un cliente en cualquier otro lenguaje (consulta Clientes).
Los proxies existentes que encontramos no proporcionaban el tipo de API dinámica que necesitábamos para
pruebas de integración y unitarias. Las herramientas de Linux como nc y demás no son
multiplataforma y requieren privilegios de root, lo que las hace problemáticas en entornos de prueba,
desarrollo e integración continua (CI).
Veamos un ejemplo con una aplicación Rails. Ten en cuenta que Toxiproxy no está ligado de ninguna manera a Ruby, solo ha sido nuestro primer caso de uso. Puedes ver el ejemplo completo en sirupsen/toxiproxy-rails-example. Para empezar de inmediato, salta a Uso.
Para nuestro popular blog, por alguna razón estamos almacenando las etiquetas de nuestras publicaciones en
Redis y las publicaciones en sí en MySQL. Podríamos tener una clase Post que
incluya algunos métodos para manipular etiquetas en un conjunto de 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
Hemos decidido que provocar un error al escribir en el almacén de datos de
etiquetas (añadir/eliminar) es aceptable. Sin embargo, si el almacén de datos
de etiquetas está caído, deberíamos poder ver la publicación sin etiquetas.
Podríamos simplemente rescatar el `Redis::CannotConnectError` alrededor de la
llamada `SMEMBERS` de Redis en el método `tags`. Usemos Toxiproxy para probar eso.
Como ya hemos instalado Toxiproxy y se está ejecutando en nuestra máquina, podemos
saltar al paso 2. Aquí es donde necesitamos asegurarnos de que Toxiproxy tenga una asignación para
las etiquetas de Redis. En `config/boot.rb` (antes de que se realice cualquier conexión) añadimos:```ruby
require 'toxiproxy'
Toxiproxy.populate([
{
name: "toxiproxy_test_redis_tags",
listen: "127.0.0.1:22222",
upstream: "127.0.0.1:6379"
}
])
Luego, en config/environments/test.rb, configuramos TagRedis para que sea un cliente de Redis
que se conecta a Redis a través de Toxiproxy añadiendo esta línea:```ruby
TagRedis = Redis.new(port: 22222)
Todas las llamadas en el entorno de pruebas ahora pasan a través de Toxiproxy. Eso significa que podemos añadir una prueba unitaria donde simulamos un fallo:```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
The test fails with Redis::CannotConnectError. ¡Perfecto! Toxiproxy apagó
Redis correctamente durante la duración del bloque. Arreglemos el método tags
para que sea resiliente:```ruby
def tags
TagRedis.smembers(tag_key)
rescue Redis::CannotConnectError
[]
end
¡Las pruebas pasan! Ahora tenemos una prueba unitaria que demuestra que obtener las etiquetas cuando Redis está caído devuelve un array vacío, en lugar de lanzar una excepción. Para una cobertura completa, también deberías escribir una prueba de integración que abarque la obtención de la página completa del blog cuando Redis está caído.
La aplicación de ejemplo completa está en
[sirupsen/toxiproxy-rails-example](https://github.com/sirupsen/toxiproxy-rails-example).
## Uso
Configurar un proyecto para usar Toxiproxy consta de tres pasos:
1. Instalar Toxiproxy
2. Poblar Toxiproxy
3. Usar Toxiproxy
### 1. Instalación de Toxiproxy
**Linux**
Consulte los [`Releases`](https://github.com/Shopify/toxiproxy/releases) para obtener los binarios y paquetes de sistema más recientes para su arquitectura.
**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
Con Homebrew:```bash $ brew tap shopify/shopify $ brew install toxiproxy
O con [MacPorts](https://www.macports.org/):```bash
$ port install toxiproxy
Windows
Toxiproxy para Windows se puede descargar en https://github.com/Shopify/toxiproxy/releases/download/v2.1.4/toxiproxy-server-windows-amd64.exe
Docker
Toxiproxy está disponible en el registro de contenedores de GitHub.
Las versiones antiguas <= 2.1.4 están disponibles en Docker Hub.```bash
$ docker pull ghcr.io/shopify/toxiproxy
$ docker run --rm -it ghcr.io/shopify/toxiproxy
Si se usa Toxiproxy desde el host en lugar de otros contenedores, habilite la red del host con `--net=host`.```shell
$ docker run --rm --entrypoint="/toxiproxy-cli" -it ghcr.io/shopify/toxiproxy list
Si tienes Go instalado, puedes compilar Toxiproxy desde el código fuente usando el archivo make:```bash $ make build $ ./toxiproxy-server
#### Actualización desde Toxiproxy 1.x
En Toxiproxy 2.0 se realizaron varios cambios en la API que la hacen incompatible con la versión 1.x.
Para usar la versión 2.x del servidor Toxiproxy, deberá asegurarse de que su biblioteca
cliente sea compatible con la misma versión. Puede comprobar qué versión de Toxiproxy está ejecutando
mediante el endpoint `/version`.
Consulte la documentación de su biblioteca cliente para conocer los cambios específicos de la biblioteca. Los cambios detallados
del servidor Toxiproxy se pueden encontrar en [CHANGELOG.md](https://github.com/shopify/toxiproxy/blob/HEAD/CHANGELOG.md).
### 2. Rellenando Toxiproxy
Cuando su aplicación arranca, necesita asegurarse de que Toxiproxy sepa qué
endpoints debe redirigir como proxy y hacia dónde. Los parámetros principales son: nombre, dirección en la que Toxiproxy
debe **escuchar** y la dirección del upstream.
Algunas bibliotecas cliente tienen ayudantes para esta tarea, que esencialmente consisten en
asegurarse de que cada proxy de una lista se cree. Ejemplo del cliente 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"
}
])
Este código debe ejecutarse lo antes posible durante el arranque, antes de que cualquier código establezca una conexión a través de Toxiproxy. Consulte la documentación de su biblioteca de cliente sobre los asistentes de población.
Alternativamente, use la CLI para crear proxies, p. ej.:```bash toxiproxy-cli create -l localhost:26379 -u localhost:6379 shopify_test_redis_master
Recomendamos un nombre como el anterior: `<app>_<env>_<data store>_<shard>`.
Esto asegura que no haya conflictos entre aplicaciones que usan el mismo
Toxiproxy.
Para aplicaciones grandes recomendamos almacenar las configuraciones de Toxiproxy en un
archivo de configuración separado. Usamos `config/toxiproxy.json`. Este archivo puede ser
pasado al servidor usando la opción `-config`, o cargado por la aplicación
para usarlo con la función `populate`.
Un ejemplo de `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
}
]
Usa puertos fuera del rango de puertos efímeros para evitar conflictos de puertos aleatorios.
Por defecto es de 32,768 a 61,000 en Linux; consulta
/proc/sys/net/ipv4/ip_local_port_range.
Para usar Toxiproxy, ahora debes configurar tu aplicación para que se conecte a través de Toxiproxy. Continuando con nuestro ejemplo del paso dos, podemos configurar nuestro cliente de Redis para que se conecte a través de Toxiproxy:```ruby
redis = Redis.new(port: 6380)
redis = Redis.new(port: 22220)
Ahora puedes manipularlo a través de la API de Toxiproxy. En 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
O a través de la CLI:```bash toxiproxy-cli toxic add -t latency -a latency=1000 shopify_test_redis_master
Consulte su respectiva biblioteca cliente para el uso.
### 4. Registro
Existen los siguientes niveles de registro: panic, fatal, error, warn o warning, info, debug y trace.
El nivel puede actualizarse mediante la variable de entorno `LOG_LEVEL`.
### Toxics
Los tóxicos manipulan el pipe entre el cliente y el upstream. Se pueden añadir
y eliminar de los proxies mediante la [API HTTP](#http-api). Cada tóxico tiene sus propios parámetros
para cambiar cómo afecta a los enlaces del proxy.
Para documentación sobre cómo implementar tóxicos personalizados, consulte [CREATING_TOXICS.md](https://github.com/shopify/toxiproxy/blob/HEAD/CREATING_TOXICS.md)
#### latency
Añade un retraso a todos los datos que pasan por el proxy. El retraso es igual a `latency` +/- `jitter`.
Atributos:
- `latency`: tiempo en milisegundos
- `jitter`: tiempo en milisegundos
#### down
Dejar un servicio fuera de línea no es técnicamente un tóxico en la implementación de
Toxiproxy. Esto se hace mediante `POST` a `/proxies/{proxy}` y estableciendo el
campo `enabled` en `false`.
#### bandwidth
Limita una conexión a un número máximo de kilobytes por segundo.
Atributos:
- `rate`: velocidad en KB/s
#### slow_close
Retrasa el cierre del socket TCP hasta que haya transcurrido `delay`.
Atributos:
- `delay`: tiempo en milisegundos
#### timeout
Detiene todos los datos que intentan pasar y cierra la conexión después de `timeout`. Si
`timeout` es 0, la conexión no se cerrará y los datos se descartarán hasta que el
tóxico sea eliminado.
Atributos:
- `timeout`: tiempo en milisegundos
#### reset_peer
Simula un TCP RESET (conexión reiniciada por el par) en las conexiones cerrando la entrada stub
inmediatamente o después de un `timeout`.
Atributos:
- `timeout`: tiempo en milisegundos
#### slicer
Divide los datos TCP en pequeños fragmentos, añadiendo opcionalmente un retraso entre cada
"paquete" fragmentado.
Atributos:
- `average_size`: tamaño en bytes de un paquete promedio
- `size_variation`: variación en bytes de un paquete promedio (debe ser menor que average_size)
- `delay`: tiempo en microsegundos para retrasar cada paquete
#### limit_data
Cierra la conexión cuando los datos transmitidos superan el límite.
- `bytes`: número de bytes que debe transmitir antes de que se cierre la conexión
#### packet_loss
Descarta aleatoriamente fragmentos que fluyen a través del proxy simulando
condiciones de red inestables de Wi-Fi, móvil o satélite.
Atributos:
- `loss_rate`: probabilidad [0.0-1.0] de que un fragmento se descarte (por defecto 0.0)
- `correlation`: probabilidad adicional de descarte cuando el fragmento anterior fue descartado, modelando pérdida de ráfagas (por defecto 0.0)
### HTTP API
Toda la comunicación con el daemon de Toxiproxy desde el cliente se realiza a través de la
interfaz HTTP, que se describe aquí.
Toxiproxy escucha HTTP en el puerto **8474**.
#### Campos del proxy:
- `name`: nombre del proxy (string)
- `listen`: dirección de escucha (string)
- `upstream`: dirección upstream del proxy (string)
- `enabled`: true/false (por defecto true en la creación)
Para cambiar el nombre de un proxy, debe eliminarse y volver a crearse.
Cambiar los campos `listen` o `upstream` reiniciará el proxy y eliminará cualquier conexión activa.
Si `listen` se especifica con un puerto de 0, toxiproxy elegirá un puerto efímero. El campo `listen`
de la respuesta se actualizará con el puerto real.
Si cambias `enabled` a `false`, el proxy se detendrá. Puedes volver a cambiarlo
a `true` para reactivarlo.
#### Campos del tóxico:
- `name`: nombre del tóxico (string, por defecto `<type>_<stream>`)
- `type`: tipo de tóxico (string)
- `stream`: dirección del enlace a afectar (por defecto `downstream`)
- `toxicity`: probabilidad de que el tóxico se aplique a un enlace (por defecto 1.0, 100%)
- `attributes`: un mapa de atributos específicos del tóxico
Consulte [Toxics](#toxics) para los atributos específicos de cada tóxico.
La dirección `stream` debe ser `upstream` o `downstream`. `upstream` aplica
el tóxico en la conexión `client -> server`, mientras que `downstream` aplica el tóxico
en la conexión `server -> client`. Esto se puede utilizar para modificar solicitudes y respuestas
por separado.
#### Endpoints
Todos los endpoints son JSON.
- **GET /proxies** - Lista los proxies existentes y sus tóxicos
- **POST /proxies** - Crea un nuevo proxy
- **POST /populate** - Crea o reemplaza una lista de proxies
- **GET /proxies/{proxy}** - Muestra el proxy con todos sus tóxicos activos
- **POST /proxies/{proxy}** - Actualiza los campos de un proxy
- **DELETE /proxies/{proxy}** - Elimina un proxy existente
- **GET /proxies/{proxy}/toxics** - Lista los tóxicos activos
- **POST /proxies/{proxy}/toxics** - Crea un nuevo tóxico
- **GET /proxies/{proxy}/toxics/{toxic}** - Obtiene los campos de un tóxico activo
- **POST /proxies/{proxy}/toxics/{toxic}** - Actualiza un tóxico activo
- **DELETE /proxies/{proxy}/toxics/{toxic}** - Elimina un tóxico activo
- **POST /reset** - Habilita todos los proxies y elimina todos los tóxicos activos
- **GET /version** - Devuelve el número de versión del servidor
- **GET /metrics** - Devuelve métricas compatibles con Prometheus
#### Poblar Proxies
Los proxies se pueden añadir y configurar en bloque mediante el endpoint `/populate`. Esto se hace
pasando un array json de proxies a toxiproxy. Si ya existe un proxy con el mismo nombre,
se comparará con el nuevo proxy y se reemplazará si las direcciones `upstream` y `listen` no coinciden.
Una llamada a `/populate` se puede incluir, por ejemplo, al inicio de la aplicación para asegurar que todos los proxies requeridos
existan. Es seguro realizar esta llamada varias veces, ya que los proxies no se modificarán mientras sus
campos sean consistentes con los nuevos datos.
### Ejemplo de 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>`
(no input provided)```bash $ redis-cli -p 26379 127.0.0.1:26379> SET omg pandas OK 127.0.0.1:26379> GET omg "pandas"
## Kitploit
Kitploit es una plataforma que permite a los usuarios mantenerse actualizados con las últimas herramientas de hacking ético y pruebas de penetración, proporcionando una amplia gama de herramientas y recursos.```bash
$ toxiproxy-cli toxic add -t latency -a latency=1000 redis
Added downstream latency toxic 'latency_downstream' on proxy 'redis'
No se proporcionó contenido para traducir.```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 content to translate in the chunk you provided. The message ends with "INPUT:" and no text follows it. Please provide the actual chunk content.```bash
$ toxiproxy-cli toxic remove -n latency_downstream redis
Removed toxic 'latency_downstream' on proxy 'redis'
Por favor, proporcione el contenido en Markdown para traducir.```bash $ redis-cli -p 26379 127.0.0.1:26379> GET omg (nil)
El chunk de entrada está vacío: no se proporcionó contenido para traducir.```bash
$ toxiproxy-cli delete redis
Deleted proxy redis
No input text provided. Please paste the Markdown content to translate.```bash $ redis-cli -p 26379 Could not connect to Redis at 127.0.0.1:26379: Connection refused
### Métricas
Toxiproxy expone métricas compatibles con Prometheus a través de su API HTTP en /metrics.
Consulta [METRICS.md](https://github.com/shopify/toxiproxy/blob/HEAD/METRICS.md) para obtener las descripciones completas.
### Preguntas frecuentes
**¿Qué tan rápido es Toxiproxy?** La velocidad de Toxiproxy depende en gran medida de tu hardware,
pero puedes esperar una latencia de *< 100µs* cuando no hay tóxicos habilitados. Al ejecutarlo
con `GOMAXPROCS=4` en un Macbook Pro logramos un rendimiento de *~1000MB/s*, y hasta
*2400MB/s* en un escritorio de gama alta. Básicamente, puedes esperar que Toxiproxy mueva
datos al menos tan rápido como la aplicación que estás probando.
**¿Puede Toxiproxy realizar pruebas aleatorias?** Muchos de los tóxicos disponibles pueden configurarse
para tener aleatoriedad, como `jitter` en el tóxico `latency`. También existe un
parámetro global `toxicity` que especifica el porcentaje de conexiones que un tóxico
afectará. Esto es más útil para cosas como el tóxico `timeout`, que permitiría que
el X% de las conexiones expiren.
**No veo que mis acciones de Toxiproxy se reflejen en MySQL**. MySQL preferirá
el socket de dominio local Unix para algunos clientes, sin importar qué puerto le pases
si el host está configurado como `localhost`. Configura tu servidor MySQL para que no cree un
socket y usa `127.0.0.1` como host. Recuerda eliminar el socket antiguo
después de reiniciar el servidor.
**Toxiproxy causa fallos de conexión intermitentes**. Usa puertos fuera del
rango de puertos efímeros para evitar conflictos aleatorios de puertos. En Linux es de
`32,768` a `61,000` de forma predeterminada; consulta `/proc/sys/net/ipv4/ip_local_port_range`.
**¿Debo ejecutar un Toxiproxy para cada aplicación?** No, recomendamos usar el
mismo Toxiproxy para todas las aplicaciones. Para distinguir entre servicios,
recomendamos nombrar tus proxies con el esquema: `<app>_<env>_<data store>_<shard>`.
Por ejemplo, `shopify_test_redis_master` o `shopify_development_mysql_1`.
### Desarrollo
* `make`. Construye un binario de desarrollo de toxiproxy para la plataforma actual.
* `make all`. Construye binarios y paquetes de Toxiproxy para todas las plataformas. Requiere
tener Go compilado con compilación cruzada habilitada en Linux y Darwin (amd64)
así como [`goreleaser`](https://goreleaser.com/) en tu `$PATH` para
compilar binarios y el paquete Linux.
* `make test`. Ejecuta las pruebas de Toxiproxy.
### Lanzamiento
Consulta [RELEASE.md](https://github.com/shopify/toxiproxy/blob/HEAD/RELEASE.md)
[blog]: https://shopify.engineering/building-and-testing-resilient-ruby-on-rails-applications