
⏰ 🔥 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, admitiendo la manipulación determinista de conexiones, pero con soporte para caos aleatorizado y personalización. Toxiproxy es la herramienta que necesitas para demostrar mediante pruebas que tu aplicación no tiene puntos únicos de fallo. Lo hemos estado usando con éxito en todos los entornos de desarrollo y prueba en 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 este repositorio contiene) y un cliente que se comunica con el proxy mediante 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 saber cómo configurar tu proyecto.
Por ejemplo, para añadir 1000 ms de latencia a la respuesta de MySQL desde el cliente 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:
Toxiproxy[/redis/].down do
Shop.first # this will throw an exception
end
Aunque los ejemplos de este README están actualmente en Ruby, nada te impide crear un cliente en cualquier otro lenguaje (consulta Clients).
Los 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 similares no son multiplataforma y requieren root, lo que las hace
problemáticas en entornos de prueba, desarrollo y CI.
Veamos un ejemplo con una aplicación Rails. Ten en cuenta que Toxiproxy no está de ninguna manera ligado a Ruby, simplemente fue 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 mismas en MySQL. Podríamos tener una clase Post que
incluya algunos métodos para manipular etiquetas en un conjunto de Redis:
class Post < ActiveRecord::Base
# Return an Array of all the tags.
def tags
TagRedis.smembers(tag_key)
end
# Add a tag to the post.
def add_tag(tag)
TagRedis.sadd(tag_key, tag)
end
# Remove a tag from the post.
def remove_tag(tag)
TagRedis.srem(tag_key, tag)
end
# Return the key in Redis for the set of tags for the post.
def tag_key
"post:tags:#{self.id}"
end
end
Hemos decidido que generar un error al escribir en el almacén de datos de etiquetas (añadir/eliminar) está bien. 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 a Redis SMEMBERS en el método tags. Vamos a usar Toxiproxy para probar eso.
Dado que 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 un mapeo para las etiquetas de Redis. A config/boot.rb (antes de que se realice cualquier conexión) añadimos:
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 como un cliente Redis que se conecta a Redis a través de Toxiproxy añadiendo esta línea:
TagRedis = Redis.new(port: 22222)
Todas las llamadas en el entorno de prueba ahora pasan por Toxiproxy. Eso significa que podemos añadir una prueba unitaria donde simulamos un fallo:
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
La prueba falla con Redis::CannotConnectError. ¡Perfecto! Toxiproxy derribó Redis con éxito durante la duración del bloque. Corrijamos el método tags para que sea resiliente:
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 arreglo 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.
Configurar un proyecto para usar Toxiproxy consta de tres pasos:
Linux
Consulta Releases para obtener los binarios y paquetes del sistema más recientes para tu arquitectura.
Ubuntu
$ wget -O toxiproxy-2.13.0.deb https://github.com/Shopify/toxiproxy/releases/download/v2.13.0/toxiproxy_2.13.0_linux_amd64.deb
$ sudo dpkg -i toxiproxy-2.13.0.deb
$ sudo service toxiproxy start
OS X
Con Homebrew: