
⏰ 🔥 Um proxy TCP para simular condições de rede e sistema para testes de caos e resiliência
Toxiproxy é um framework para simular condições de rede. Ele foi feito especificamente para funcionar em ambientes de teste, CI e desenvolvimento, com suporte a adulteração determinística de conexões, mas também com suporte a caos aleatório e customização. Toxiproxy é a ferramenta que você precisa para provar com testes que sua aplicação não tem pontos únicos de falha. Nós o usamos com sucesso em todos os ambientes de desenvolvimento e teste na Shopify desde outubro de 2014. Veja nosso [post no blog][blog] sobre resiliência para mais informações.
O uso do Toxiproxy consiste em duas partes. Um proxy TCP escrito em Go (o que este repositório contém) e um cliente que se comunica com o proxy via HTTP. Você configura sua aplicação para que todas as conexões de teste passem pelo Toxiproxy e então pode manipular a saúde delas via HTTP. Veja Uso abaixo sobre como configurar seu projeto.
Por exemplo, para adicionar 1000ms de latência à resposta do MySQL a partir do cliente Ruby:```ruby Toxiproxy[:mysql_master].downstream(:latency, latency: 1000).apply do Shop.first # this takes at least 1s end
Para derrubar todas as instâncias Redis:```ruby
Toxiproxy[/redis/].down do
Shop.first # this will throw an exception
end
While the examples in this README are currently in Ruby, there's nothing stopping you from creating a client in any other language (see Clientes).
As soluções existentes que encontramos não forneciam o tipo de API dinâmica de que precisávamos para
testes de integração e unitários. Ferramentas Linux como nc e outras não são
multiplataforma e exigem root, o que as torna problemáticas em ambientes de teste,
desenvolvimento e CI.
Vamos percorrer um exemplo com um aplicativo Rails. Observe que o Toxiproxy não está de forma alguma amarrado ao Ruby, ele foi apenas nosso primeiro caso de uso. Você pode ver o exemplo completo em sirupsen/toxiproxy-rails-example. Para começar imediatamente, pule para Uso.
Para nosso blog popular, por algum motivo estamos armazenando as tags das nossas postagens no
Redis e as próprias postagens no MySQL. Podemos ter uma classe Post que
inclui alguns métodos para manipular tags em um Redis set:```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
Decidimos que gerar erro ao escrever no armazenamento de tags
(adicionar/remover) é aceitável. No entanto, se o armazenamento de tags estiver indisponível, devemos ser
capazes de ver o post sem tags. Poderíamos simplesmente capturar o
`Redis::CannotConnectError` em torno da chamada Redis `SMEMBERS` no método `tags`
method. Vamos usar o Toxiproxy para testar isso.
Como já instalamos o Toxiproxy e ele está rodando em nossa máquina, podemos
pular para o passo 2. É aqui que precisamos garantir que o Toxiproxy tenha um mapeamento para
as tags do Redis. Em `config/boot.rb` (antes que qualquer conexão seja feita), adicionamos:```ruby
require 'toxiproxy'
Toxiproxy.populate([
{
name: "toxiproxy_test_redis_tags",
listen: "127.0.0.1:22222",
upstream: "127.0.0.1:6379"
}
])
Então, em config/environments/test.rb, definimos o TagRedis como um cliente Redis que se conecta ao Redis por meio do Toxiproxy adicionando esta linha:```ruby
TagRedis = Redis.new(port: 22222)
Todas as chamadas no ambiente de teste agora passam pelo Toxiproxy. Isso significa que podemos
adicionar um teste de unidade onde simulamos uma falha:```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
O teste falha com Redis::CannotConnectError. Perfeito! O Toxiproxy derrubou
o Redis com sucesso durante a duração do closure. Vamos corrigir o método tags
para que ele seja resiliente:```ruby
def tags
TagRedis.smembers(tag_key)
rescue Redis::CannotConnectError
[]
end
Os testes passam! Agora temos um teste unitário que prova que buscar as tags quando o Redis
está fora do ar retorna um array vazio, em vez de lançar uma exceção. Para uma cobertura
completa, você também deve escrever um teste de integração que envolva a busca da
página inteira do post do blog quando o Redis está fora do ar.
O aplicativo de exemplo completo está em
[sirupsen/toxiproxy-rails-example](https://github.com/sirupsen/toxiproxy-rails-example).
## Uso
Configurar um projeto para usar o Toxiproxy consiste em três etapas:
1. Instalando o Toxiproxy
2. Populando o Toxiproxy
3. Usando o Toxiproxy
### 1. Instalando o Toxiproxy
**Linux**
Consulte [`Releases`](https://github.com/Shopify/toxiproxy/releases) para os binários e pacotes de sistema mais recentes para sua arquitetura.
**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
Com Homebrew:```bash $ brew tap shopify/shopify $ brew install toxiproxy
Ou com [MacPorts](https://www.macports.org/):```bash
$ port install toxiproxy
Windows
O Toxiproxy para Windows está disponível para download em https://github.com/Shopify/toxiproxy/releases/download/v2.1.4/toxiproxy-server-windows-amd64.exe
Docker
O Toxiproxy está disponível em Github container registry.
Versões antigas <= 2.1.4 estão disponíveis em em Docker Hub.```bash
$ docker pull ghcr.io/shopify/toxiproxy
$ docker run --rm -it ghcr.io/shopify/toxiproxy
Se estiver usando o Toxiproxy a partir do host em vez de outros contêineres, habilite a rede do host com `--net=host`.```shell
$ docker run --rm --entrypoint="/toxiproxy-cli" -it ghcr.io/shopify/toxiproxy list
Se você tiver o Go instalado, pode compilar o Toxiproxy a partir do código-fonte usando o make file:```bash $ make build $ ./toxiproxy-server
#### Atualizando do Toxiproxy 1.x
No Toxiproxy 2.0, várias mudanças foram feitas na API que a tornam incompatível com a versão 1.x.
Para usar a versão 2.x do servidor Toxiproxy, você precisará garantir que a sua biblioteca
cliente suporte a mesma versão. Você pode verificar qual versão do Toxiproxy está executando
consultando o endpoint `/version`.
Consulte a documentação da sua biblioteca cliente para mudanças específicas. Alterações detalhadas
para o servidor Toxiproxy podem ser encontradas em [CHANGELOG.md](https://github.com/shopify/toxiproxy/blob/HEAD/CHANGELOG.md).
### 2. Populando o Toxiproxy
Quando a sua aplicação inicia, ela precisa garantir que o Toxiproxy saiba quais
endpoints encaminhar e para onde. Os principais parâmetros são: nome, endereço no qual o Toxiproxy
deve **escutar** e o endereço do upstream.
Algumas bibliotecas cliente possuem utilitários para essa tarefa, que essencialmente consiste em
garantir que cada proxy em uma lista seja criado. Exemplo do 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 precisa ser executado o mais cedo possível durante a inicialização, antes que qualquer código estabeleça uma conexão por meio do Toxiproxy. Consulte a documentação da sua biblioteca do cliente sobre os auxiliares de população.
Como alternativa, use a CLI para criar proxies, por exemplo:```bash toxiproxy-cli create -l localhost:26379 -u localhost:6379 shopify_test_redis_master
Recomendamos uma nomenclatura como a acima: `<app>_<env>_<data store>_<shard>`.
Isso garante que não haja conflitos entre aplicações que utilizam o mesmo
Toxiproxy.
Para aplicações grandes, recomendamos armazenar as configurações do Toxiproxy em um
arquivo de configuração separado. Usamos `config/toxiproxy.json`. Este arquivo pode ser
passado para o servidor usando a opção `-config`, ou carregado pela aplicação
para uso com a função `populate`.
Um exemplo 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
}
]
Use portas fora do intervalo de portas efêmeras para evitar conflitos aleatórios de portas.
O padrão é 32,768 a 61,000 no Linux, consulte
/proc/sys/net/ipv4/ip_local_port_range.
Para usar o Toxiproxy, agora você precisa configurar sua aplicação para se conectar por meio do Toxiproxy. Continuando com nosso exemplo da etapa dois, podemos configurar nosso Redis cliente para se conectar por meio do Toxiproxy:```ruby
redis = Redis.new(port: 6380)
redis = Redis.new(port: 22220)
Agora você pode manipulá-lo através da API do Toxiproxy. Em 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
Ou via CLI:```bash toxiproxy-cli toxic add -t latency -a latency=1000 shopify_test_redis_master
Por favor, consulte a respectiva biblioteca do cliente para obter instruções de uso.
### 4. Registro de Logs
Existem os seguintes níveis de log: panic, fatal, error, warn ou warning, info, debug e trace.
O nível pode ser atualizado por meio da variável de ambiente `LOG_LEVEL`.
### Toxics
Os toxics manipulam o pipe entre o cliente e o upstream. Eles podem ser adicionados
e removidos dos proxies usando a [API HTTP](#http-api). Cada toxic tem seus próprios parâmetros
para alterar como ele afeta os links do proxy.
Para documentação sobre como implementar toxics personalizados, consulte [CREATING_TOXICS.md](https://github.com/shopify/toxiproxy/blob/HEAD/CREATING_TOXICS.md)
#### latency
Adiciona um atraso a todos os dados que passam pelo proxy. O atraso é igual a `latency` +/- `jitter`.
Atributos:
- `latency`: tempo em milissegundos
- `jitter`: tempo em milissegundos
#### down
Derrubar um serviço não é tecnicamente um toxic na implementação do
Toxiproxy. Isso é feito por meio de `POST` para `/proxies/{proxy}` e definindo o
campo `enabled` como `false`.
#### bandwidth
Limita uma conexão a um número máximo de kilobytes por segundo.
Atributos:
- `rate`: taxa em KB/s
#### slow_close
Atrasa o fechamento do socket TCP até que `delay` tenha decorrido.
Atributos:
- `delay`: tempo em milissegundos
#### timeout
Impede que todos os dados passem e fecha a conexão após `timeout`. Se
`timeout` for 0, a conexão não será fechada e os dados serão descartados até que o
toxic seja removido.
Atributos:
- `timeout`: tempo em milissegundos
#### reset_peer
Simula TCP RESET (Connection reset by peer) nas conexões fechando o stub Input
imediatamente ou após um `timeout`.
Atributos:
- `timeout`: tempo em milissegundos
#### slicer
Fatia os dados TCP em pequenos pedaços, opcionalmente adicionando um atraso entre cada
"pacote" fatiado.
Atributos:
- `average_size`: tamanho em bytes de um pacote médio
- `size_variation`: variação em bytes de um pacote médio (deve ser menor que average_size)
- `delay`: tempo em microssegundos para atrasar cada pacote
#### limit_data
Fecha a conexão quando os dados transmitidos excederem o limite.
- `bytes`: número de bytes que devem ser transmitidos antes que a conexão seja fechada
#### packet_loss
Descarta aleatoriamente blocos de dados que fluem pelo proxy, simulando
condições de rede instáveis, como Wi-Fi, rede móvel ou satélite.
Atributos:
- `loss_rate`: probabilidade [0.0-1.0] de um bloco ser descartado (padrão 0.0)
- `correlation`: probabilidade extra de descarte quando o bloco anterior foi descartado, modelando perda em rajadas (padrão 0.0)
### HTTP API
Toda a comunicação com o daemon do Toxiproxy a partir do cliente acontece por meio da
interface HTTP, que é descrita aqui.
O Toxiproxy escuta HTTP na porta **8474**.
#### Campos do proxy:
- `name`: nome do proxy (string)
- `listen`: endereço de escuta (string)
- `upstream`: endereço upstream do proxy (string)
- `enabled`: true/false (padrão é true na criação)
Para alterar o nome de um proxy, ele deve ser excluído e recriado.
Alterar os campos `listen` ou `upstream` reiniciará o proxy e derrubará todas as conexões ativas.
Se `listen` for especificado com uma porta 0, o toxiproxy escolherá uma porta efêmera. O campo `listen`
na resposta será atualizado com a porta real.
Se você alterar `enabled` para `false`, o proxy será derrubado. Você pode alterná-lo
de volta para `true` para reativá-lo.
#### Campos do toxic:
- `name`: nome do toxic (string, padrão é `<type>_<stream>`)
- `type`: tipo do toxic (string)
- `stream`: direção do link a ser afetada (padrão é `downstream`)
- `toxicity`: probabilidade de o toxic ser aplicado a um link (padrão é 1.0, 100%)
- `attributes`: um mapa de atributos específicos do toxic
Consulte [Toxics](#toxics) para ver os atributos específicos de cada toxic.
A direção `stream` deve ser `upstream` ou `downstream`. `upstream` aplica
o toxic na conexão `client -> server`, enquanto `downstream` aplica o toxic
na conexão `server -> client`. Isso pode ser usado para modificar requisições e respostas
separadamente.
#### Endpoints
Todos os endpoints são JSON.
- **GET /proxies** - Listar proxies existentes e seus toxics
- **POST /proxies** - Criar um novo proxy
- **POST /populate** - Criar ou substituir uma lista de proxies
- **GET /proxies/{proxy}** - Mostrar o proxy com todos os seus toxics ativos
- **POST /proxies/{proxy}** - Atualizar os campos de um proxy
- **DELETE /proxies/{proxy}** - Excluir um proxy existente
- **GET /proxies/{proxy}/toxics** - Listar toxics ativos
- **POST /proxies/{proxy}/toxics** - Criar um novo toxic
- **GET /proxies/{proxy}/toxics/{toxic}** - Obter os campos de um toxic ativo
- **POST /proxies/{proxy}/toxics/{toxic}** - Atualizar um toxic ativo
- **DELETE /proxies/{proxy}/toxics/{toxic}** - Remover um toxic ativo
- **POST /reset** - Ativar todos os proxies e remover todos os toxics ativos
- **GET /version** - Retorna o número da versão do servidor
- **GET /metrics** - Retorna métricas compatíveis com Prometheus
#### Populando Proxies
Os proxies podem ser adicionados e configurados em massa usando o endpoint `/populate`. Isso é feito
passando um array JSON de proxies para o toxiproxy. Se um proxy com o mesmo nome já existir,
ele será comparado ao novo proxy e substituído se os endereços `upstream` e `listen` não corresponderem.
Uma chamada `/populate` pode ser incluída, por exemplo, na inicialização do aplicativo para garantir que todos os proxies necessários
existam. É seguro fazer essa chamada várias vezes, pois os proxies não serão alterados desde que seus
campos sejam consistentes com os novos dados.
### Exemplo 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 content was provided to translate.```bash $ redis-cli -p 26379 127.0.0.1:26379> SET omg pandas OK 127.0.0.1:26379> GET omg "pandas"
`web-token/jwt-framework` é uma biblioteca que fornece uma maneira de criar,
e facilitar a verificação de, JSON Web Tokens (JWT). Ela implementa as
especificações JWT, JWS, JWE, JWK e JWA.
Esta ferramenta tem como objetivo testar a segurança de implementações JWT, fornecendo uma
ampla gama de capacidades de ataque. Ela pode ser usada para testar a segurança de
suas próprias aplicações ou para avaliar a segurança de implementações
de terceiros.
Não sabe o que é um JWT? Acesse [jwt.io](https://jwt.io) para mais
informações.```bash
$ toxiproxy-cli toxic add -t latency -a latency=1000 redis
Added downstream latency toxic 'latency_downstream' on proxy 'redis'
Por favor, forneça o conteúdo Markdown para tradução.```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)
Nenhuma entrada fornecida para tradução.```bash
$ toxiproxy-cli toxic remove -n latency_downstream redis
Removed toxic 'latency_downstream' on proxy 'redis'
Por favor, forneça o conteúdo Markdown para tradução.```bash $ redis-cli -p 26379 127.0.0.1:26379> GET omg (nil)
Please provide the Markdown content to translate.```bash
$ toxiproxy-cli delete redis
Deleted proxy redis
(empty)```bash $ redis-cli -p 26379 Could not connect to Redis at 127.0.0.1:26379: Connection refused
### Métricas
O Toxiproxy expõe métricas compatíveis com Prometheus através da sua API HTTP em /metrics.
Consulte [METRICS.md](https://github.com/shopify/toxiproxy/blob/HEAD/METRICS.md) para descrições completas.
### Perguntas Frequentes
**Qual a velocidade do Toxiproxy?** A velocidade do Toxiproxy depende em grande parte do seu hardware,
mas pode esperar uma latência de *< 100µs* quando nenhum tóxico está ativado. Ao executar
com `GOMAXPROCS=4` num Macbook Pro alcançámos um débito de *~1000MB/s*, e até
*2400MB/s* num desktop de gama alta. Basicamente, pode esperar que o Toxiproxy mova
dados pelo menos tão rápido quanto a aplicação que está a testar.
**O Toxiproxy consegue fazer testes aleatórios?** Muitos dos tóxicos disponíveis podem ser configurados
para ter aleatoriedade, como `jitter` no tóxico `latency`. Existe também um
parâmetro global `toxicity` que especifica a percentagem de conexões que um tóxico
afetará. Isto é mais útil para coisas como o tóxico `timeout`, que permitiria
que X% das conexões atingissem o tempo limite.
**Não estou a ver as minhas ações do Toxiproxy refletidas no MySQL**. O MySQL prefere
o socket de domínio Unix local para alguns clientes, independentemente da porta que lhe passe,
se o host estiver definido como `localhost`. Configure o seu servidor MySQL para não criar um
socket e use `127.0.0.1` como host. Lembre-se de remover o socket antigo
depois de reiniciar o servidor.
**O Toxiproxy causa falhas de conexão intermitentes**. Use portas fora do
intervalo de portas efémeras para evitar conflitos de portas aleatórios. É `32.768` a `61.000` no
Linux por padrão, consulte `/proc/sys/net/ipv4/ip_local_port_range`.
**Devo executar um Toxiproxy para cada aplicação?** Não, recomendamos usar o
mesmo Toxiproxy para todas as aplicações. Para distinguir entre serviços,
recomendamos nomear os seus proxies com o esquema: `<app>_<env>_<data store>_<shard>`.
Por exemplo, `shopify_test_redis_master` ou `shopify_development_mysql_1`.
### Desenvolvimento
* `make`. Compilar um binário de desenvolvimento do Toxiproxy para a plataforma atual.
* `make all`. Compilar binários e pacotes do Toxiproxy para todas as plataformas. Requer
ter Go compilado com compilação cruzada ativada no Linux e Darwin (amd64),
bem como [`goreleaser`](https://goreleaser.com/) no seu `$PATH` para
compilar binários do pacote Linux.
* `make test`. Executar os testes do Toxiproxy.
### Lançamento
Consulte [RELEASE.md](https://github.com/shopify/toxiproxy/blob/HEAD/RELEASE.md)
[blog]: https://shopify.engineering/building-and-testing-resilient-ruby-on-rails-applications