Nascimento da plataforma



O mundo mudou. Eu sinto na água, vejo no chão, sinto no ar. Tudo o que existia se foi, e não há mais quem se lembre disso.
Do filme "O Senhor dos Anéis: A Sociedade do Anel"

Existem 100500 artigos e relatórios na Internet sobre o tema “como vimos um monólito”, e não desejo escrever outro. Tentei ir um pouco mais longe e contar como as mudanças tecnológicas levaram ao aparecimento de um produto completamente novo (spoiler: escrevemos a caixa e escrevemos a plataforma). O artigo é basicamente uma revisão, sem detalhes técnicos. Detalhes virão mais tarde.

Será sobre o site do painel de controle e o servidor Vepp. Este é um produto do ISPsystem onde lidero o desenvolvimento. Leia sobre os recursos do novo painel em outro artigo , aqui - apenas sobre tecnologia. Mas primeiro, como sempre, um pouco de história.

Parte 1. É necessário mudar alguma coisa


Nossa empresa desenvolve software para automação de serviços de hospedagem há mais de 15 anos. Durante esse período, várias gerações de nossos produtos mudaram. Quando passamos do segundo para o quarto, mudamos o Perl para C ++ e iniciamos a venda gratuita de nosso software ... Quando passamos do quarto para o quinto, em busca da velocidade, passamos de um aplicativo de thread único para multithread (de um monólito contendo todos os nossos produtos para estrutura).

Mas, não apenas estávamos mudando, nossos clientes e concorrentes estavam mudando. Se há 10 a 15 anos o proprietário do site era tecnicamente bem versado (outros estavam fracamente interessados ​​na Internet), agora pode ser uma pessoa que não está conectada à TI de forma alguma. Existem muitas soluções concorrentes. Sob tais condições, simplesmente trabalhar um produto não é suficiente, é necessário que seja fácil e agradável de usar.

Redesign heróico


Esta mudança tornou-se, em todos os sentidos, a mais notável. Todo esse tempo, a interface de nossos produtos permaneceu praticamente inalterada e unificada. Já escrevemos sobre isso separadamente - uma visão do UX. Na quinta geração de produtos, a API definiu a aparência de formulários e listas. Que, por um lado, tornou possível implementar muitas coisas sem envolver desenvolvedores de front-end, por outro, deu origem a chamadas complexas muito complexas, às vezes afetando a maior parte do sistema, e limitou bastante a capacidade de alterar a interface. Afinal, qualquer alteração é inevitavelmente uma alteração na API. E é isso, olá para as integrações!

Por exemplo: a criação de um usuário no ISPmanager também pode levar à criação de um usuário FTP, domínio de email, caixa de correio, registro DNS, site. E tudo isso é feito atomicamente e, portanto, bloqueia as alterações nos componentes listados até que a operação seja concluída.

Na nova API, mudamos para operações atômicas pequenas e simples, e os desenvolvedores do Frontend ficaram com ações complexas complexas. Isso permite implementar uma interface complexa e alterá-la sem afetar a API principal e, portanto, sem interromper a integração.

Para desenvolvedores de front-end, implementamos um mecanismo de execução de consultas em lote com a capacidade de executar ações reversas em caso de erro. Como mostra a prática, na maioria dos casos, consultas complexas são solicitações para criar algo, e a criação é bastante fácil de cancelar, executando a exclusão.

Reduza o tempo de resposta e as notificações instantâneas


Recusamos solicitações longas de modificação. As versões anteriores de nossos produtos podem travar por um longo tempo, tentando atender a uma solicitação do usuário. Decidimos que para cada ação é mais difícil alterar trivialmente os dados no banco de dados, criaremos uma tarefa no sistema e responderemos ao cliente o mais rápido possível, permitindo que ele continue trabalhando e não observe o processo de carregamento sem fim.

Mas e se você precisar do resultado, o que é chamado aqui e agora? Acho que muitas pessoas estão familiarizadas com a situação quando você recarrega a página repetidamente, na esperança de que a operação esteja prestes a terminar.

Na nova geração de produtos, usamos o websocket para entrega instantânea de eventos.

A primeira implementação usou o longo caminho, mas os desenvolvedores de front-end não se sentiram à vontade com essa abordagem.

Comunicação interna HTTP


HTTP como um transporte ganho. Agora, em qualquer idioma, um servidor HTTP é implementado em segundos (se você pesquisar no Google e em minutos). Mesmo localmente, é mais fácil formar uma solicitação HTTP do que bloquear seu protocolo.

Na geração anterior, extensões (plugins) eram aplicativos que, se necessário, foram lançados como CGI. Mas, para escrever uma extensão de longa duração, tive que me esforçar bastante: escrever um plug-in em C ++ e reconstruí-lo a cada atualização do produto.

Portanto, na sexta geração, mudamos para a interação interna via HTTP, e as extensões tornaram-se pequenos servidores WEB.

Nova API (não exatamente REST)


Nas gerações anteriores de produtos, passamos todos os parâmetros por meio de GET ou POST. Se não houver problemas especiais com o GET - seu tamanho é pequeno, no caso do POST, não há como verificar o acesso ou redirecionar a solicitação até que ela seja totalmente lida.

Imagine como é triste: aceitar algumas centenas de megabytes ou gigabytes e descobrir que eles foram despejados por um usuário não autorizado ou que agora eles precisam ser transferidos para esse servidor!

Agora, o nome da função é passado no URI e a autorização é exclusivamente nos cabeçalhos. E isso permite que você execute parte das verificações antes de ler o corpo da solicitação.

Além disso, as funções da API tornaram-se mais simples. Sim, agora não garantimos a atomicidade de criar um usuário, correio, site e similares. Mas damos a oportunidade de combinar essas operações conforme necessário.

Implementamos a capacidade de execução de consultas em lote. De fato, este é um serviço separado que aceita uma lista de solicitações e as executa sequencialmente. Ele também pode reverter as operações já concluídas no caso de um erro.

Viva SSH!


Outra decisão que tomamos com base em nossa experiência anterior é trabalhar com o servidor somente através do SSH (mesmo que seja um servidor local). Inicialmente, trabalhamos com o servidor local no VMmanager e no ISPmanager e, somente então, tornamos possível adicionar servidores remotos adicionais. Isso levou à necessidade de suportar duas implementações.

E quando nos recusamos a trabalhar com o servidor local, os últimos motivos para usar as bibliotecas nativas do sistema operacional do usuário desapareceram. Com eles, somos atormentados desde a fundação da empresa. Não, as bibliotecas nativas têm suas vantagens, mas há mais desvantagens.

Uma vantagem absoluta é menos consumo de disco e memória e, para trabalhar no VDS, isso pode ser bastante significativo. Das desvantagens - o uso de uma biblioteca cuja versão você não controla pode levar a resultados inesperados, o que aumenta bastante a carga no desenvolvimento e no teste. Outra desvantagem é a incapacidade de usar as versões mais recentes das bibliotecas e do C ++ moderno (por exemplo, no CentOS 6, mesmo o C ++ 11 não é totalmente suportado).

Velhos hábitos


Mudando as abordagens e mudando para novas tecnologias, continuamos a agir da maneira antiga. Isso levou a dificuldades.

O tópico de hype com microsserviços não passou por nós. Também decidimos dividir o aplicativo em componentes de serviço separados. Isso tornou possível aumentar o controle sobre a interação e realizar testes de partes individuais do aplicativo. E, a fim de controlar a interação ainda mais rigorosamente, as colocamos em contêineres, que, no entanto, sempre podiam ser reunidos em uma pilha.

Em um aplicativo monolítico, você pode acessar facilmente quase todos os dados. Mas mesmo se você dividir o produto em vários aplicativos e deixá-los por perto, eles, como os vivos, podem formar links. Por exemplo, na forma de arquivos compartilhados ou solicitações diretas entre si.

Mudar para microsserviços não foi fácil. O antigo padrão de "escrever uma biblioteca e conectá-la sempre que necessário" nos assombra há bastante tempo. Por exemplo, temos um serviço responsável por executar operações "longas". Inicialmente, foi implementado como uma biblioteca conectada ao aplicativo que precisava.

Outro hábito pode ser descrito da seguinte maneira: por que escrever outro serviço, se você pode ensinar esse já existente. A primeira coisa que cortamos do nosso monólito é o mecanismo de autorização. Mas então a tentação pareceu empurrar todos os componentes comuns para esse serviço, como no COREmanager (a estrutura básica dos produtos de quinta geração).

Parte 2. Combinando o incompatível


Inicialmente, as solicitações de leitura e gravação foram realizadas por um único processo. Normalmente, as solicitações de gravação estão bloqueando as solicitações, mas são muito rápidas: escrevi no banco de dados, criei uma tarefa e respondi. Com um pedido de leitura, a história é diferente. Criar uma tarefa é difícil. Pode gerar uma resposta bastante volumosa, e o que fazer com essa resposta se o cliente não retornar depois dela? Quanto armazenar? Mas, ao mesmo tempo, o processamento de pedidos de leitura é perfeitamente paralelo. Essas diferenças levaram a problemas ao reiniciar esses processos. Seus ciclos de vida são simplesmente incompatíveis!

Dividimos o aplicativo em duas partes: leitura e escrita. É verdade que logo ficou claro que isso não é muito conveniente do ponto de vista do desenvolvimento. Lendo a lista que você faz em um lugar, editando em outro. E aqui a principal coisa - não se esqueça de corrigir o segundo, se você mudar o primeiro. Sim, e alternar entre arquivos é pelo menos irritante. Portanto, chegamos a um aplicativo que roda em dois modos: leitura e escrita.

A geração anterior de nossos produtos fez uso extensivo de fios. Mas, como a prática demonstrou, eles não nos salvaram muito. Devido aos muitos bloqueios, a carga na CPU raramente excedia 100%. O surgimento de um grande número de serviços separados bastante rápidos permitiu abandonar o multithreading em favor do trabalho assíncrono e de thread único.

Durante o processo de desenvolvimento, tentamos usar fluxos juntamente com a assincronia (boost :: Asio permite isso). Mas essa abordagem provavelmente traz ao seu projeto todas as deficiências de ambas as abordagens que oferecem vantagens visíveis: você terá que combinar a necessidade de controle ao acessar objetos compartilhados e a dificuldade de escrever código assíncrono.

Parte 3. Como escrevemos a caixa e escrevemos a plataforma


Todos os serviços são organizados em contêineres e funcionam com o servidor cliente remotamente. E por que, então, colocar o aplicativo no servidor do cliente? Essa é a pergunta que fiz ao gerenciamento quando chegou a hora de empacotar o produto resultante para instalação no servidor.

O que é uma plataforma? Primeiro, implantamos o SaaS, um serviço que é executado em nossos servidores e permite que você configure seu servidor. Se você usou algum painel de controle do servidor e o comprou - esta é a solução para você. Mas isso não serve para provedores: eles não estão prontos para fornecer acesso aos servidores de seus clientes a uma empresa terceirizada, e eu os entendo muito bem. Isso levanta questões de segurança e tolerância a falhas. Portanto, decidimos dar a eles todo o SaaS para que eles pudessem implantá-lo em casa. É como a Amazon, que você pode executar em seu próprio data center e conectar-se ao seu sistema de cobrança. Chamamos essa solução de plataforma.

A primeira implantação não foi muito tranquila. Para cada usuário ativo, criamos um contêiner separado. Os contêineres do Docker aumentam rapidamente, mas a descoberta de serviços não funciona instantaneamente: não se destina a aumentar / parar dinamicamente os contêineres em um segundo. E desde o momento de elevar até o momento em que o serviço pode ser usado, às vezes minutos se passaram !!!

Eu já escrevi que o usuário de hospedagem mudou muito na última década. Agora imagine: ele compra uma hospedagem sua - e obtém acesso ao shell. WTF?!?! Para usá-lo, primeiro ele terá que encontrar algum cliente SSH (no Windows, isso pode se tornar um problema - por padrão, não há cliente, geralmente sou silencioso sobre clientes móveis).

Depois que ele ainda pode entrar no console, ele precisa instalar o painel. E esta operação também não é rápida. E se algo der errado? Por exemplo, o RosKomNadzor pode bloquear os servidores dos quais os pacotes do seu sistema operacional são baixados. Com esses erros, o usuário será deixado cara a cara.



Você pode argumentar que, na maioria dos casos, o usuário recebe o painel já instalado pelo hoster e o acima não se aplica a ele. Possivelmente. Mas o painel em execução no servidor consome os recursos pelos quais você pagou (ocupa espaço em disco, consome seu processador e memória). Seu desempenho depende diretamente do desempenho do servidor (o servidor travou - o painel travou).

Ainda pode haver quem não use painéis e pense: isso não me interessa. Mas talvez no seu servidor, se você tiver um, algum tipo de painel ainda permaneça e consome recursos, mas você simplesmente não o usa?

"Por isso, é maravilhoso, você nos ajuda a vender recursos", afirmam os anfitriões russos. Outros acrescentam: "Por que gastamos nossos recursos implantando uma plataforma que consome mais do que um painel independente?"

Existem várias respostas para esta pergunta.
  1. A qualidade do serviço é aprimorada: você pode controlar a versão do painel - atualize-a rapidamente quando novas funcionalidades aparecerem ou forem detectados erros; Você pode anunciar ações diretamente no painel.
  2. Em uma escala de infraestrutura, você economiza disco, memória e um processador, pois alguns processos são iniciados apenas para atender usuários ativos e outros atendem a muitos clientes ao mesmo tempo.
  3. O suporte não precisa analisar se o comportamento é um recurso de uma versão específica do painel ou um erro. Isso economiza tempo.

Muitos de nossos clientes compraram licenças em pacotes e depois as manipularam, revendendo-as a seus clientes. Não há mais necessidade de fazer isso, em princípio, um trabalho sem sentido. Afinal, agora este é um produto.

Além disso, tivemos a oportunidade de usar soluções pesadas, oferecendo funcionalidade anteriormente inacessível. Por exemplo, um serviço para obter capturas de tela de um site requer o lançamento de um cromo sem cabeça. Eu não acho que o usuário ficará muito feliz se, devido a essa operação, ele ficar sem memória e se matar, digamos, MySQL.

Concluindo, quero observar que aprendemos com a experiência de outras pessoas e estamos desenvolvendo ativamente a nossa. Registro, janela de encaixe, descoberta de serviço, todos os tipos de atrasos, tentativas e filas ... Agora você não se lembra de tudo o que tinha para dominar ou reinventar. Tudo isso não facilita o desenvolvimento, mas abre novas oportunidades e torna nosso trabalho mais emocionante.

A história ainda não acabou, mas o que aconteceu pode ser visto no site da Vepp .

Source: https://habr.com/ru/post/pt467973/


All Articles