>> Apresentação e considerações

ChurrOpers, escrever o primeiro artigo de Traefik me despertou mais curiosidades a respeito do funcionamento, sobre as suas Features e possibilidades.

Pensando nessa linha estou compartilhando a evolução dos meus estudos, e esse artigo terá como base e tradução a documentação oficial dos conceitos básicos de Traefik, e como a documentação será teórico e voltado para o entendimento dessa ferramenta fantástica, espero que apreciem…

Let’s go!

>> Visualizando e entendendo o fluxo de funcionamento

Na figura abaixo temos a representação com o fluxo do funcionamento do Traefik, e a descrições dos passos.

traefik-flow

  • As requisições entram através dos Entrypoints via HTTP ou HTTPS, caso a requisição venha como HTTP é realizado o Redirect para HTTPS.
  • O tráfego é encaminhado para o FrontEnd que possui as rotas dos hosts, paths, cabeçalhos, etc.
  • Ao identificar e reconhecer uma rota o frontend encaminha a requisição para o backend que pode ter um ou mais servers.
  • E por fim o backend encaminha a requisição para o service ou microservice correspondente.

>> Entrypoints

Entrypoints são os pontos de entrada de rede no Træfik. Eles podem ser definidos usando:

  •  Portas (80, 443…)
  • SSL (Certificados, keys, autenticação com um certificado de cliente assinado por uma CA confiável)
  • redirecionamento para outro entrypoint (redirect HTTP to HTTPS)

Exemplo de definição de Entrypoint:

[entryPoints]
  [entryPoints.http]
  address = ":80"
    [entryPoints.http.redirect]
    entryPoint = "https"
  [entryPoints.https]
  address = ":443"
    [entryPoints.https.tls]
      [[entryPoints.https.tls.certificates]]
      certFile = "tests/traefik.crt"
      keyFile = "tests/traefik.key"
  • 2 entrypoints foram definidos http e https.
  • http escuta na porta 80 e https na porta 443.
  • SSL habilitado no https com arquivo de certificado e uma chave.
  • Redirecionamento do tráfego do entrypoint http para https.

>> Frontends

Com o frontend podemos definir as diversas regras que vão determinar como as solicitações vindas do Entrypoint vão ser encaminhadas para um backend.

Existem 2 classificações de regras que são os Modifiers e Matchers .

Modifiers

Os Modifiers, não tem impacto nas decisões de roteamento, apenas modificam os pedidos através de regras.

Temos as seguintes regras:

  • AddPrefix: /churrops: Adiciona o prefixo do path ao caminho de solicitação existente antes de encaminhar a solicitação para o backend.
  • ReplacePath: /serverless-path: Substitui o caminho e adiciona o caminho antigo ao cabeçalho X-Replaced-Path. Útil para mapeamento para AWS Lambda ou Google Cloud Functions..

Matchers

As regras do Matcher determinam se um pedido específico deve ser encaminhado para um backend.

Separar vários valores de regras por , (vírgula) para habilitar QUALQUER semântica (ou seja, encaminhar uma solicitação se qualquer regra corresponder). Não funciona para Headers e HeadersRegexp.

Separar valores de regras múltiplas por ; (ponto e vírgula) para permitir TODAS as semânticas (ou seja, encaminhar uma solicitação se todas as regras coincidirem).

A seguir no quadro abaixo temos a lista de regras de matcher existentes, juntamente com exemplos:

Matcher Description
Headers: Content-Type, application/json Match HTTP header. Aceita chave/valor de forma literal separados por vírgula.
HeadersRegexp: Content-Type, application/(text/json) Match HTTP header. Aceita chave/valor separados por vírgula, onde chave é literal e valor pode ser uma expressão literal ou regular
Host: churrops.io, www.churrops.io Match request host. aceita uma sequência de hosts literais.
HostRegexp: churrops.io, {subdomain:[a-z]+}.churrops.io Match request host. Aceita uma sequência de hosts com expressão literal ou regular.
Method: GET, POST, PUT Match request HTTP method. Aceita uma sequência de métodos HTTP.
Path: /products/, /articles/{category}/{id:[0-9]+} Match no caminho exato da requisição. aceita uma sequência de paths de expressão regular e literal.
PathStrip: /products/ Match no path exato e remove o path antes de encaminhar a requisição para o backend. Aceita uma sequênca de paths literais.
PathStripRegex: /articles/{category}/{id:[0-9]+} Match no path exato e remove o path antes de encaminhar a requisição para o backend. Aceita uma sequência de paths literiais e expressões regulares.
PathPrefix: /products/, /articles/{category}/{id:[0-9]+} Match request prefix path. Ele aceita uma seqüência de caminhos de prefixo de expressão regular e literal.
PathPrefixStrip: /products/ Preencha o path do prefixo e retire o prefixo do path antes de encaminhar a solicitação para o backend. Aceita uma seqüência de paths de prefixo literais. Começando com o Traefik 1.3, o caminho de prefixo despojado estará disponível no X-Forwarded-Prefixheader.
PathPrefixStripRegex: /articles/{category}/{id:[0-9]+} Match na request prefix path e remove o prefixo do caminho antes de encaminhar a requisição para o backend. Ele aceita uma seqüência de paths de prefixo de expressão regular e literal. Começando com Traefik 1.3, o caminho de prefixo despojado estará disponível no header X-Forwarded-Prefix.
Query: foo=bar, bar=baz Match Query String parameters. Aceita uma sequência de key=value pairs.

Para usar expressões regulares com correspondentesHost e Path, você deve declarar uma variável arbitrariamente chamada seguida da expressão regular separada por dois pontos, toda fechada em chaves curly. Qualquer padrão suportado pelo pacote Go’s regexp package pode ser usado (examplo: /posts/{id:[0-9]+}).

NOTA: A variável não tem significado especial; no entanto, é exigido pela dependência gorilla/mux que incorpora a expressão regular e define a sintaxe.

Você pode habilitar opcionalmente o passHostHeader para encaminhar o cabeçalho do Host cliente para o backend. Você também pode habilitar opcionalmente o passTLSCert para encaminhar certificados do TLS Client para o backend.

Guia de uso dos PATH MATCHERS

Esta sessão explica quando usar vários path matchers.

Use Path se o seu backend ouvir somente o caminho exato. Por exemplo, Path: /products casa com /products mas não com /products/shoes.

Use um *Prefix* matcher se o seu backend escutar um path de base específico, mas também atende solicitações em sub-paths. Por exemplo, PathPrefix: /products casa com /products mas também com /products/shoes e /products/shirts. Uma vez que o path é encaminhado as-is, seu backend deverá ouvir em /products.

Use um *Strip matcher se o seu backend ouvir no path raiz (/) mais deveria ser roteável para um prefixo específico. Por exemplo, PathPrefixStrip: /products casa com /products mas também com /products/shoes e /products/shirts.
Uma vez que o path é removido antes do encaminhamento, espera-se que seu backend escutasse no /.

Se o seu backend estiver servindo assets (e.g., images ou Javascript files), as chances são de que ele deve retornar URLs relativas devidamente construídas.
Continuando o exemplo, o backend deveria retornar /products/shoes/image.png (e não /images.png que o Traefik provavelmente não poderá associar com o mesmo backend).

X-Forwarded-Prefix header (disponível desde o Traefik 1.3) pode ser consultado para construir essas URLs dinamicamente.

Ao invés de distinguir seus backends apenas por caminhos, você pode adicionar um Host matcher ao mix. Dessa forma, o namespacing de seus backends acontece com base em hosts além de caminhos.

Exemplo da definição de frontends:

[frontends]
  [frontends.frontend1]
  backend = "backend2"
    [frontends.frontend1.routes.test_1]
    rule = "Host:test.localhost,test2.localhost"
  [frontends.frontend2]
  backend = "backend1"
  passHostHeader = true
  passTLSCert = true
  priority = 10
  entrypoints = ["https"] # overrides defaultEntryPoints
    [frontends.frontend2.routes.test_1]
    rule = "HostRegexp:localhost,{subdomain:[a-z]+}.localhost"
  [frontends.frontend3]
  backend = "backend2"
    [frontends.frontend3.routes.test_1]
    rule = "Host:test3.localhost;Path:/test"
  • Foram definidos 3 frontends: frontend1frontend2 e frontend3
  • frontend1 encaminhará o tráfego para o backend2 se der match na rule Host:test.localhost,test2.localhost
  • frontend2 encaminhará o tráfego para o backend1 se a rule Host:localhost,{subdomain:[a-z]+}.localhost der match (encaminhando o client Host header para o backend)
  • frontend3 encaminhará o tráfego para o backend2 se as rules Host:test3.localhost ANDPath:/test derem match

Vimos acima que podemos combinar várias regras. No arquivo TOML, podemos utilizar várias rotas

  [frontends.frontend3]
  backend = "backend2"
    [frontends.frontend3.routes.test_1]
    rule = "Host:test3.localhost"
    [frontends.frontend3.routes.test_2]
    rule = "Path:/test"

Acima o frontend3 encaminha o tráfego para o backend2 se as rules  Host:test3.localhost e Path:/test derem match.

Você também pode usar a notação usando um separador ;, com o mesmo resultado:

  [frontends.frontend3]
  backend = "backend2"
    [frontends.frontend3.routes.test_1]
    rule = "Host:test3.localhost;Path:/test"

Finalmente, você pode criar uma regra para vincular vários domínios ou paths para um frontend, usando o separador ,:

 [frontends.frontend2]
    [frontends.frontend2.routes.test_1]
    rule = "Host:test1.localhost,test2.localhost"
  [frontends.frontend3]
  backend = "backend2"
    [frontends.frontend3.routes.test_1]
    rule = "Path:/test1,/test2"

Ordem das Rules

Quando combinamos as rules de Modifier e as rules de Matcher, é importante lembrar que as regras de Modifier SEMPRE se aplicam após as regras do Matcher.

As seguintes regras são Matchers e Modifiers, então a parte Matcher da regra será aplicada primeiro e o Modificador será aplicado mais tarde.

  • PathStrip
  • PathStripRegex
  • PathPrefixStrip
  • PathPrefixStripRegex

Os modificadores serão aplicados em uma ordem pré-determinada independentemente da ordem na seção de configuração da regra.

  1. PathStrip
  2. PathPrefixStrip
  3. PathStripRegex
  4. PathPrefixStripRegex
  5. AddPrefix
  6. ReplacePath

Prioridades

Por padrão, as rotas serão ordenadas (em ordem decrescente): PathPrefix:/12345 vai dar match antes de PathPrefix:/1234 que por sua vez dará match antes do PathPrefix:/1.

Você pode personalizar a prioridade pelo frontend:

  [frontends]
    [frontends.frontend1]
    backend = "backend1"
    priority = 10
    passHostHeader = true
      [frontends.frontend1.routes.test_1]
      rule = "PathPrefix:/to"
    [frontends.frontend2]
    priority = 5
    backend = "backend2"
    passHostHeader = true
      [frontends.frontend2.routes.test_1]
      rule = "PathPrefix:/toto"

Aqui, o frontend1 dará match antes do frontend2 (10 > 5).

Custom headers

Custom headers podem ser configurados através dos frontends, para adicionar headers a pedidos ou respostas que correspondam às regras do frontend. Isso permite configurar os headers como um X-Script-Name para ser adicionado a request, ou custom headers para serem adicionados à resposta.

[frontends]
  [frontends.frontend1]
  backend = "backend1"
    [frontends.frontend1.headers.customresponseheaders]
    X-Custom-Response-Header = "True"
    [frontends.frontend1.headers.customrequestheaders]
    X-Script-Name = "test"
    [frontends.frontend1.routes.test_1]
    rule = "PathPrefixStrip:/cheese"

Nesse exemplo, todos os matches para o path /cheese terão o header X-Script-Name adicionado à solicitação proxied, e o X-Custom-Response-Header adicionado à resposta.

Security headers

Security related headers (HSTS headers, SSL redirection, Browser XSS filter, etc) podem ser adicionados e configurados por frontend de forma semelhante aos custom headers acima. Essa funcionalidade permite que algumas features simples de segurança sejam rapidamente configuradas.

Um exemplo de alguns security headers:

[frontends]
  [frontends.frontend1]
  backend = "backend1"
    [frontends.frontend1.headers]
    FrameDeny = true
    [frontends.frontend1.routes.test_1]
    rule = "PathPrefixStrip:/cheddar"
  [frontends.frontend2]
  backend = "backend2"
    [frontends.frontend2.headers]
    SSLRedirect = true
    [frontends.frontend2.routes.test_1]
    rule = "PathPrefixStrip:/stilton"

Neste exemplo, o tráfego roteado através do primeiro frontend terá o header X-Frame-Options setado para DENY, e o segundo permitirá somente a solicitação HTTPS, caso contrário retornará um redirecionamento 301 HTTPS.

A documentação detalhada dos security headers podem ser encontradas em: unrolled/secure.

>> Backend

Um backend é responsável por equilibrar a carga do tráfego que vem de um ou mais frontends para um conjunto de servidores http.

Vários métodos de load-balancing são suportados:

  • wrr: Weighted Round Robin: Por peso
  • drr: Dynamic Round Robin: Aumenta o peso em servidores que possuem uma melhor performance que outros. Também retorna aos pesos originais se os servidores tiverem mudado.

Um circuit breaker ou disjuntor pode ser aplicado ao backend, impedindo altas cargas em servidores com falha. O estado inicial é Standby. O CB (circuit breaker) observa as estatísticas e não modifica o pedido. Caso a condição corresponda, o CB entra no Tripped state, onde responde com um código pré definido ou redireciona para outro frontend. Quando o temporizador Tripped expirar, o CB entra no estado de recovery e reseta todas as estatísticas. Caso a condição não corresponda e o temporizador de recuperação expire, o CB entra no estado de espera.

Pode ser configurado usando:

  • Métodos: LatencyAtQuantileMSNetworkErrorRatioResponseCodeRatio
  • Operadores: ANDOREQNEQLTLEGTGE

Por exemplo:

  • NetworkErrorRatio() > 0.5: Assiste o error ratio em uma janela de 10 segundos para um frontend
  • LatencyAtQuantileMS(50.0) > 50: Assiste a latência em quantile em milissegundos.
  • ResponseCodeRatio(500, 600, 0, 600) > 0.5: relação dos códigos de resposta no range [500-600) to [0-600)

Para impedir proativamente que os backends sejam sobrecarregados com alta carga, um limite de conexão máximo também pode ser aplicado a cada backend.

As conexões máximas podem ser configuradas especificando um valor inteiro para maxconn.amount e maxconn.extractorfunc que é uma estratégia usada para determinar como categorizar solicitações para avaliar as conexões máximas.

Por exemplo:

[backends]
  [backends.backend1]
    [backends.backend1.maxconn]
       amount = 10
       extractorfunc = "request.host"
  • backend1 retornará HTTP code 429 Too Many Requests se já houver 10 pedidos em progresso para um mesmo Host header.
  • Outro valor possível para extractorfunc é client.ip que classificará as solicitações com base no IP do origem do cliente.
  • Finalmente o extractorfunc pode levar o valor de request.header.ANY_HEADER que classificará os pedidos com base em ANY_HEADER que você fornece.

Sticky sessions

Sticky sessions são suportados pelo 2 load balancers.

Quando sticky sessions está habilitado, um cookie é definido na solicitação inicial. O nome padrão do cookie é uma abreviação de um sha1 (ex: _1d52e). Em pedidos sub sequentes, o cliente será direcionado para o backend armazenado no cookie se estiver saudável. Se não, um novo backend será atribuído.

[backends]
  [backends.backend1]
    # Enable sticky session
    [backends.backend1.loadbalancer.stickiness]

    # Customize the cookie name
    #
    # Optional
    # Default: a sha1 (6 chars)
    #
    #  cookieName = "my_cookie"

Forma antiga (deprecated):

[backends]
  [backends.backend1]
    [backends.backend1.loadbalancer]
      sticky = true

Health Check

Um health check pode ser configurado para remover um backend da rotação do LB, desde que ele continue retornando HTTP status codes diferentes de 200 OK para pedidos HTTP GET periodicamente executados pelo Traefik.

O check é definido por um path anexado à URL do backend e um intervalo (fornecido em um formato compreendido por time.ParseDuration) especificando a frequência com que a verificação de integridade deve ser executada (o padrão é de 30 segundos). Cada backend deve responder o health check dentro de 5 segundos.

Por padrão, a porta do servidor backend é usada, mas isso pode substituído.

Um backend de recuperação que retorna respostas 200 OK novamente, é retornado para o pool de rotação LB.

Por exemplo:

[backends]
  [backends.backend1]
    [backends.backend1.healthcheck]
    path = "/health"
    interval = "10s"

Para usar uma porta diferente no healthcheck:

[backends]
  [backends.backend1]
    [backends.backend1.healthcheck]
    path = "/health"
    interval = "10s"
    port = 8080

Servers

Os servidores são simplesmente definidos usando uma url. Você também pode aplicar um peso personalizado a cada servidor (isso será usado pelo balanceamento de carga).

NOTA: Paths na url são ignorados. Use o Modifier para especificar paths ao invés disso.

Aqui está em exemplo de definição de backends e servers:

[backends]
  [backends.backend1]
    [backends.backend1.circuitbreaker]
    expression = "NetworkErrorRatio() > 0.5"
    [backends.backend1.servers.server1]
    url = "http://172.17.0.2:80"
    weight = 10
    [backends.backend1.servers.server2]
    url = "http://172.17.0.3:80"
    weight = 1
  [backends.backend2]
    [backends.backend2.LoadBalancer]
    method = "drr"
    [backends.backend2.servers.server1]
    url = "http://172.17.0.4:80"
    weight = 1
    [backends.backend2.servers.server2]
    url = "http://172.17.0.5:80"
    weight = 2
  • Dois backends são definidos: backend1 e backend2
  • backend1 encaminhará o tráfego para 2 servers: http://172.17.0.2:80" com peso 10http://172.17.0.3:80 com peso 1 usando a estratégia  default de wrr load-balancing.
  • backend2 encaminhará o tráfego para 2 servers: http://172.17.0.4:80" com peso 1http://172.17.0.5:80 com peso 2 usando a estratégia de drr load-balancing.
  • Um circuit breaker é adicionado no backend1 usando a expressão NetworkErrorRatio() > 0.5: watch error ratio over 10 second sliding window

Configuração

A configuração da Træfik tem duas partes:

  • A configuração estática que é carregada apenas no inicio.
  • A configuração dinâmica que pode ser carregado a quente (não é necessário restartar o processo).

Static Træfik configuration

A configuração estática é a configuração global que está configurando conexões para  backends e entrypoints.

Træfik pode ser configurado usando muitas fontes de configuração com a seguinte ordem de procedência. Cada item tem procedência sobre o item abaixo:

  • Key-value store
  • Arguments
  • Configuration file
  • Default

Isso significa que os argumentos substituem o arquivo de configuração e o armazenamento de chave-valor substitui os argumentos..

NOTA:  os parâmetros de argumento que habilitam o provedor (ex, --docker) definem todos os valores padrão para o provider específico.
Não deve ser usado se uma fonte de configuração com menos precedência desejar definir um valor de provedor não padrão.

Arquivo de configuração

Por padrão, o Træfik irá tentar encontrar um traefik.toml nos seguintes lugares:

  • /etc/traefik/
  • $HOME/.traefik/
  • . O diretório de trabalho

Você pode substituir isso com um argumento de configFile:

traefik --configFile=foo/bar/myconfigfile.toml

Please refer to the global configuration section to get documentation on it.

Arguments

Cada argumento (e comando) é descrito na sessão “help”:

traefik --help

Observe que todos os valores padrão também serão exibidos.

Key-value stores

Træfik tem suporte a vários armazenadores de Key-value (chave-valor):

Consulte a documentação: User Guide Key-value store configuration.

Dynamic Træfik configuration

A configuração dinâmica se concentra em:

Træfik pode fazer o hot-reload das regras que podem ser fornecidades por multiple configuration backends.

Nós precisamos ativar a opção watch para fazer alterações do backend de configuração do Træfik e gerar as configuraões automaticamente. As rotas para serviços serão criadas e atualizadas instantaneamente em qualquer alteração.

Consulte a documentação de configuration backends.

Commands

traefik

Uso:

traefik [command] [--flag=flag_argument]

Lista dos comandos disponíveis do Træfik com descrição:

  • version : Exibe a versão
  • storeconfig : Armazena a configuração estática do Traefik com Key-value stores. Consulte a documentação Store Træfik configuration.
  • bug: A maneira mais fácil de submeter uma issue preenchida ao Traefik GitHub.
  • healthcheck: Chama o /ping para o healthcheck.

Cada comando pode ter sinalizadores relacionados.

Todas essas flags relacionadas serão exibidas com:

traefik command --help

Cada comando é descrito no início da seção de ajuda:

traefik --help

Comando: bug

A maneira mais fácil de enviar um issue preenchida ao Træfik GitHub.

traefik bug

Assista esta demo.

Comando: healthcheck

Isso pode ser usado com a instrução Docker HEALTHCHECK ou qualquer outro mecanismo de orquestração de verificação de saúde.

Este comando permite checar a saúde do Traefik. Seu exit status é 0 se o Traefik está saudável e 1 se não está saudável.

Isso pode ser usado com a instrução Docker HEALTHCHECK ou qualquer outro mecanismo de orquestração de verificação de saúde.

Nota: O web provider precisa estar habilitado para permitir as chamadas /ping do comando healthcheck.

traefik healthcheck

OK: http://:8082/ping

>> Conclusão

É isso, procurei de fato seguir o que estava na documentação para torná-la mais acessível fazendo com que mais pessoas conheçam um pouco mais dessa ferramenta que tem se destacado bastante no cenário atual da tecnologia.

Gostou? Comente e compartilhe, e nos ajude a divulgar nosso trabalho, isso é importante para a comunidade!

Abraços!