DevSecOps

1 fev, 2017

Conductor Netflix: um orquestrador de microsserviços

Publicidade

O time de engenharia da Netflix, devido à escalabilidade dos seus serviços, tem trabalhado com um grande número de processos que são orientados por uma orquestração assíncrona de tarefas, executadas por meio de microsserviços. Além disso, muitos destes processos têm tempos de execução de vários dias e possuem uma função crítica, deixar os títulos prontos para serem exibidos a todos os usuários ao redor do mundo

Alguns exemplos desses processos são:

  • Integração do estúdio parceiro para adição de conteúdo;
  • Adição de conteúdo por parceiros baseada em IMF;
  • Processamento da configuração de novos títulos no Netflix;
  • Adição de conteúdo, codificação, e distribuição para o CDN.

Até então os processos eram orquestrados de uma maneira ad-hoc, com uma combinação de pub/sub (publish/subscribe), fazendo chamadas REST diretas, e utilizando um banco de dados para gerenciar os estados. Porém, conforme a quantidade de microsserviços cresce, também cresce a sua complexidade. E isso torna ainda mais difícil a visibilidade da distribuição dos fluxos sem a utilização de um orquestrador central.

Sendo assim, o time de engenharia da Netflix construiu uma engine orquestradora capaz de tratar os seguintes requisitos, tirar a necessidade dos clichês nos aplicativos e fornecer um fluxo reativo:

  • Baseado em modelo. Um DSL JASON baseado em modelo define o fluxo de execução;
  • Acompanhamento e gerenciamento de fluxos;
  • Habilidade de parar, retomar, e reiniciar os processos;
  • Interface de usuário para visualizar os fluxos de processo;
  • Habilidade de processar sincronicamente todas as tarefas quando necessário;
  • Habilidade de escalar para milhões de fluxos de processos executados concorrentemente;
  • Sustentado por um serviço de filas abstraídas dos clientes;
  • Ser capaz de operar através do HTTP e outros protocolos, como gRPC;

O condutor em questão foi construído para alcançar os requisitos acima e tem sido utilizado no Netflix por quase um ano. Até hoje, ajudou a orquestrar mais de 2,6 milhões de fluxos de processos que vão de simples fluxos lineares à fluxos dinâmicos muito complexos que são executados por dias.

A Netflix liberou o código do Conductor para a comunidade a fim de melhorar o desempenho do seu produto e de ajudar outros profissionais com o seu caso de estudo. É possível encontrar a documentação do Conductor aqui.

Por que não fazer a coreografia ponto a ponto?

A princípio foi testada esta solução, porém, foi complexo para o time de engenharia conseguir escalonar, devido ao crescimento da complexidade e necessidades do negócio. O modelo Pub/Sub funcionou para os fluxos mais simples, mas rapidamente ressaltou alguns problemas associados com a abordagem:

  • Fluxos de processo são “embutidos” nos códigos de muitas aplicações;
  • Geralmente, existe acoplamento e suposições fortes entre as entradas e saídas, SLAs e etc., tornando mais difícil adaptar para as mudanças necessárias;
  • Quase nenhuma maneira de responder sistematicamente “O que falta para a configuração de um filme estar completa?”.

Por que microsserviços?

Muito se fala em microsserviços atualmente e, neste ambiente complexo e com muita atividades paralelas e automações de processos de negócios, é necessário um serviço de orquestração. Assim, o Conductor habilita a orquestração entre os serviços e também fornece controle e visibilidade para as interações. Essa habilidade deu ao time de engenharia de Netflix a possibilidade de alavancar os serviços existentes, construir novos fluxos e atualizar os existentes, aumentando assim a velocidade de utilização do fluxo, o que forneceu uma rota efetivamente mais fácil para a adoção da solução.

Resumo de arquitetura

No coração da engine está uma máquina de serviço de estado, também conhecido como serviço Decider. Conforme os eventos do fluxo ocorrem ( ex.: Conclusão de tarefas, falhas, etc.), o Decider combina o modelo do fluxo com o estado atual do fluxo, identifica o próximo estado e agenda apropriadamente as tarefas e/ou atualizações dos estados do fluxo.

O Decider trabalha com uma fila distribuída para gerenciar as tarefas agendadas. São utilizadas dyno-queues em cima do Dynomite para gerenciar as filas distribuídas atrasadas. A receita da fila foi aberta mais cedo nesse ano, veja aqui. 

Implementação do executor de tarefas

Tarefas implementadas por aplicações executoras se comunicam pela camada API. Os executores atingem isso de duas maneiras. Pela implementação em uma extremidade REST, que pode ser chamada pelo motor de orquestração, ou implementando um laço de pesquisa capaz de verificar periodicamente as tarefas pendentes. Os executores estão destinados a serem funções independentes sem estado. O modelo de pesquisa permitiu ao time de engenharia da Netflix tratar a pressão nos executores e fornecer auto-escalabilidade baseada no tamanho da fila. O Conductor fornece APIs para inspecionar o balanceamento do trabalho para cada executor que pode ser utilizado para auto-escalar as instâncias dos executores.

Comunicação do worker com a engine

Camada API

As APIs são expostas por HTTP – Utilizar o HTTP permite uma fácil integração com clientes diferentes. No entanto, é esperado que adicionar outro protocolo (Ex.: gRPC) deveria ser fácil e relativamente direto.

Armazenamento 

Foi utilizado o Dynomite “como engine de armazenamento” junto com o ElasticSearch para a indexação e execução dos fluxos. As APIs de armazenamento são plugáveis e podem ser adaptadas para vários sistemas de armazenamento tradicional, incluindo o RDBMS ou o Apache Cassandra do tipo NoSql.

Conceitos-chave

Definição de fluxo de processo

Os fluxos de processo são definidos utilizando o JSON baseado em DSL. Um modelo de fluxo define uma série de tarefas que precisam ser executadas. Existem dois tipos de tarefas: tarefa de controle (ex.: fork, join, decisão, sub-fluxo) e tarefa executora. As definições dos fluxos são versionadas e fornecem flexibilidade na gestão das atualizações e migração.

Um esboço da definição de um fluxo

{
  "name": "workflow_name",
  "description": "Description of workflow",
  "version": 1,
  "tasks": [
    {
      "name": "name_of_task",
      "taskReferenceName": "ref_name_unique_within_blueprint",
      "inputParameters": {
        "movieId": "${workflow.input.movieId}",
        "url": "${workflow.input.fileLocation}"
      },
      "type": "SIMPLE",
      ... (any other task specific parameters)
    },
    {}
    ...
  ],
  "outputParameters": {
    "encoded_url": "${encode.output.location}"
  }
}

Definição das tarefas

O comportamento de cada tarefa é controlado por seu template, conhecido como definição de tarefas. Uma definição de tarefa fornece parâmetros de controle para cada tarefa, como limite de tempo, políticas para novas tentativas etc.. Uma tarefa pode ser do tipo executadora, implementada pela aplicação, ou de sistema, que é executada pelo servidor de orquestração. O Conductor fornece tarefas de sistema fora da caixa, como decisões, forks, joins, sub-fluxos, e um SPI que permite conectar tarefas personalizadas do sistema. Os engenheiros da Netflix adicionaram um suporte para tarefas HTTP que facilitam realizar as chamadas dos serviços REST.

Fragmento em JSON de uma definição de tarefa

{
  "name": "encode_task",
  "retryCount": 3,
  "timeoutSeconds": 1200,
  "inputKeys": [
    "sourceRequestId",
    "qcElementType"
  ],
  "outputKeys": [
    "state",
    "skipped",
    "result"
  ],
  "timeoutPolicy": "TIME_OUT_WF",
  "retryLogic": "FIXED",
  "retryDelaySeconds": 600,
  "responseTimeoutSeconds": 3600
}

Entradas/Saídas 

A entrada para uma tarefa é um mapa com as entradas recebidas como parte da instanciação do fluxo de trabalho ou a saída de outra tarefa. Tal configuração permite o encaminhamento de entradas/saídas de um fluxo de trabalho ou de outras tarefas como entradas para as tarefas que eles chamam. Por exemplo: A saída de uma tarefa de codificação pode ser fornecida para uma tarefa de publicação como entrada para publicar para o CDN.

Fragmento em JSON de uma definição das entradas das tarefas 

 {
      "name": "name_of_task",
      "taskReferenceName": "ref_name_unique_within_blueprint",
      "inputParameters": {
        "movieId": "${workflow.input.movieId}",
        "url": "${workflow.input.fileLocation}"
      },
      "type": "SIMPLE"
    }

Um exemplo

Vamos ver um exemplo muito simples de encode e deploy de um fluxo de trabalho

Existe um total de 3 tarefas executoras e uma tarefa de controle (Errors) envolvidas:

  1. Inspetor de conteúdo (Content Inspector): verifica o arquivo no momento da entrada para saber se está correto e completo.
  2. Codificar (Enconde): gera a codificação de um vídeo
  3. Publicar (Publish): publica para o CDN.

Essas três tarefas são implementadas por executores diferentes, que estão procurando por tarefas pendentes utilizando a API de tarefas. Essas são, idealmente, tarefas independentes que operam na entrada fornecida para a tarefa, realiza o trabalho e envia a atualização de estado.

Conforme cada tarefa é completada, o Decider avalia o estado da instância do fluxo de trabalho de acordo com o modelo (com a versão correspondente à instancia do fluxo) e identifica o próximo conjunto de tarefas a ser agendado, ou completa o fluxo, se todas as tarefas estão feitas.

Interface de Usuário (UI)

A UI é o mecanismo primário para monitoramento e resolução de problemas nas execuções dos fluxos. Ela fornece a visibilidade necessária dos processos e é capaz de permitir buscas baseadas em vários parâmetros, incluindo os parâmetros de entrada/saída. Além disso, fornece uma apresentação visual do modelo e os caminhos que foram tomados para entender melhor a execução do fluxo de processo. Para cada instância de fluxo existente, a UI fornece detalhes dobre a execução de cada tarefa com os seguintes detalhes:

  • Informações de horário para quando a tarefa foi agendada, assumida pelo executor e concluída;
  • Se a tarefa falhou, qual foi a razão da falha;
  • Número de novas tentativas;
  • Host no qual a tarefa foi executada;
  • Entradas fornecidas para a tarefa e saídas da tarefa após a conclusão.

Aqui está um fragmento de um fluxo de trabalho “kitchen sink” utilizado para gerar números de performance.

Outras soluções consideradas

Amazon SWF

A primeira escolha do time de engenharia da Netflix foi um fluxo simples do AWS. No entanto, eles escolheram construir o Conductor, dadas algumas das limitações com o SWF:

  • Necessidade de um modelo baseado em orquestração, ao contrário dos decisores pragmáticos como requisitados pelo SWF;
  • UI para visualização dos fluxos;
  • Necessidade de uma natureza mais síncrona das APIs quando requisitado (ao invés de puramente orientado a mensagens);
  • Necessidade de indexar as entradas e saídas por fluxo e tarefas e habilidade de pesquisar fluxos baseado nisso;
  • Necessidade de manter um armazenamento de dados separado para suportar os eventos de fluxo e se recuperar de falhas, pesquisas, etc.

Amazon Step Function

Porém, o recentemente anunciado AWS Step Funcions adicionou algumas das funcionalidades que o time da Netflix estava procurando em uma engine de orquestração. Existe potencial para o Conductor adotar a linguagem de estados para definir os fluxos de trabalho.

Algumas estatísticas 

Abaixo são listadas algumas estatísticas da instância de produção que está sendo executada há aproximadamente um ano. A maioria dos fluxos de trabalho são utilizados pela engenharia da plataforma de conteúdo com o objetivo de suportar vários cursos para aquisição de conteúdo, adição e codificação.

  • Total de instâncias criadas esse ano – 2,6 milhões;
  • Número de definições de fluxos distintos – 100;
  • Número de executores únicos – 190;
  • Número médio de tarefas por definição de fluxo – 6;
  • Maior Fluxo – 48 tarefas.

Considerações futuras 

  • O suporte para funções em AWS Lambda (ou similar) como tarefas para tarefas simples sem o servidor;
  • Integração mais forte com os frameworks orquestradores, que vão permitir aos executores instanciar a auto-escalabilidade;
  • Guardar dados de log para cada tarefa. Para a Netflix, esta foi uma adição útil que ajudará na resolução dos problemas;
  • Habilidade de criar e gerenciar os modelos dos fluxos a partir da UI;
  • Suporte às linguagens de estado.

 

Fonte: http://techblog.netflix.com/2016/12/netflix-conductor-microservices.html