Eu tenho um projeto de tamanho médio agora que está chegando ao fim da fase "protótipos desleixados de cafeína para demonstrações de clientes" e passando para a fase "pense no futuro". O projeto consiste em dispositivos baseados em Linux com software e firmware e um servidor web administrativo central. Atualmente, existem 10 protótipos, e espera-se que a produção seja da ordem dos 1000 baixos.
Não sendo versado na arte das atualizações automáticas e com pouco tempo, rapidamente desenvolvi minha própria estratégia de implantação / atualização automática de software e, francamente, é péssimo. Atualmente, ele consiste no seguinte:
- Um repositório git hospedado (GitLab) com uma ramificação de liberação de produção (observe que a fonte do servidor da web também está nesse mesmo repositório, além de algumas outras coisas).
- Um botão "implantar atualização" na interface da web que:
- Puxa a versão mais recente da ramificação de liberação de produção para uma área de repo local e também a copia para uma área de preparação temporária de preparação de pacotes.
- Executa um script de higienização (armazenado no repositório) na área de preparação para remover arquivos de origem não relacionados (por exemplo, fonte do servidor, fonte de firmware etc.) e arquivos .git.
- Grava o hash git atual em um arquivo no pacote de atualização (o objetivo ficará claro abaixo).
- Se tudo der certo, ele compactará e compactará o arquivo com o mesmo nome, substituindo o pacote gzip anterior por um arquivo com o mesmo nome e excluindo a área de teste.
- Observe que agora existem duas cópias do software do dispositivo atual no servidor, que devem estar sincronizadas: um repositório git local completo no ramo de produção mais recente e um pacote gzip pronto para uso que agora supõe representar mesma versão.
- O software no dispositivo é independente em um diretório chamado
/opt/example/current
, que é um link simbólico para a versão atual do software. - Uma função de atualização automática no dispositivo que, na inicialização:
- Verifica a presença de um
do_not_update
arquivo e não toma nenhuma ação adicional, se existir (para dispositivos de desenvolvimento, veja abaixo). - Lê o hash de confirmação atual do arquivo de texto mencionado acima.
- Faz uma solicitação HTTP para o servidor com esse hash como parâmetro de consulta. O servidor responderá com um 304 (hash é a versão atual) ou servirá o pacote de atualização compactado com gzip.
- Instala o pacote de atualização, se um foi recebido, em
/opt/example
:- Extrair a pasta informações de um software atualizado chamado
stage
. - A execução de um script pós-instalação a partir do pacote de atualização que faz coisas como as alterações locais necessárias para essa atualização, etc.
- Copiar a pasta raiz do software atual para
previous
(exclui a existenteprevious
primeiro, se houver uma). - Copiar a
stage
pasta paralatest
(exclui a existentelatest
primeiro, se houver uma). - Garantindo que o
current
link simbólico aponte paralatest
. - Reinicializando o dispositivo (atualizações de firmware, se presentes, são aplicadas na reinicialização).
- Extrair a pasta informações de um software atualizado chamado
- Verifica a presença de um
Há também o problema da implantação inicial em dispositivos recém-construídos. Atualmente, os dispositivos são baseados em cartão SD (tem seu próprio conjunto de problemas, fora do escopo aqui), portanto esse processo consiste em:
- Existe uma imagem SD que possui uma versão anterior estável do software.
- Um cartão SD é criado a partir desta imagem.
- Na primeira inicialização, várias inicializações específicas do dispositivo (baseadas em número de série) são realizadas pela primeira vez e o atualizador automático pega e instala a versão de produção mais recente do software, como de costume.
Além disso, eu precisava de suporte para dispositivos de desenvolvimento. Para dispositivos de desenvolvimento:
- Um repositório git local completo é mantido no dispositivo.
- O
current
link simbólico aponta para o diretório de desenvolvimento. - Existe um
do_not_update
arquivo local que impede o atualizador automático de descartar o código de desenvolvimento com uma atualização de produção.
Agora, o processo de implantação foi teoricamente destinado a ser:
- Quando o código estiver pronto para implantação, envie-o para o ramo de lançamento.
- Pressione o botão "implantar atualização" no servidor.
- A atualização agora está ativa e os dispositivos serão atualizados automaticamente na próxima vez que verificarem.
No entanto, existem muitos problemas na prática:
- O código do servidor da web está no mesmo repositório que o código do dispositivo e o servidor possui um repositório git local que eu executo. O código mais recente do servidor da web não está na mesma ramificação que o código do dispositivo mais recente. A estrutura de diretórios é problemática. Quando o botão "implantar atualização" extrai a versão mais recente da ramificação de produção, ele a puxa para um subdiretório do código do servidor. Isso significa que, quando implanto em um servidor do zero, tenho que "propagar" manualmente esse subdiretório, agarrando a ramificação de produção do dispositivo, porque, provavelmente devido ao erro do usuário git da minha parte, se a implantação não tentar, puxe o código do dispositivo da filial do servidor da web do diretório pai . Eu acho que isso é solucionável, fazendo com que a área de teste não seja um subdiretório do repositório git local do servidor.
- O servidor da Web atualmente não mantém o hash git do software do dispositivo persistentemente. Na inicialização do servidor, ele realiza um
git rev-parse HEAD
repo no software do dispositivo local para recuperar o hash atual. Por razões que não consigo entender, isso também está causando uma tonelada de erros lógicos que não descreverei aqui, basta dizer que às vezes reiniciar o servidor estraga tudo, especialmente se o servidor for novinho em folha e sem produção o repositório de filial ainda foi retirado. Gostaria de compartilhar a fonte dessa lógica, se solicitado, mas este post está ficando longo. - Se o script de higienização (lado do servidor) falhar por algum motivo, o servidor
git rev-parse HEAD
ficará com um repositório atualizado, mas com um pacote de atualização ausente / fora de sincronia / ausente, retornará um hash que não corresponde ao que realmente está sendo servido aos dispositivos, e os problemas aqui devem ser corrigidos manualmente na linha de comando do servidor. Ou seja, o servidor não sabe que o pacote de atualização não está correto, mas sempre assume isso com pura fé. Isso combinado com os pontos anteriores, torna o servidor extremamente frágil na prática. - Um dos maiores problemas é : Atualmente, não há daemon atualizador separado em execução no dispositivo. Devido a complicações que aguardam o acesso à Internet Wi-Fi e alguns hackers de última hora, é o principal software de controle de dispositivo que verifica e atualiza o dispositivo. Isso significa que, se de alguma forma uma versão mal testada entra em produção e o software de controle não pode ser iniciado, todos os dispositivos existentes são essencialmente em blocos, pois não podem mais se atualizar. Isso seria um pesadelo absoluto na produção. O mesmo acordo para um único dispositivo se ele perder energia em um momento de azar.
- O outro grande problema é : Não há suporte para atualizações incrementais. Se um dispositivo, por exemplo, não estiver ligado por um tempo, na próxima vez que for atualizado, ele pulará várias versões de lançamento, ele deverá ser capaz de fazer uma atualização direta de pulo de versão. A conseqüência disso é que a implantação atualizada é um pesadelo para garantir que qualquer atualização possa ser aplicada sobre qualquer versão anterior. Além disso, como os hashes git são usados para identificar versões em vez de números de versão, a comparação lexicográfica de versões para facilitar atualizações incrementais não é atualmente possível.
- Um novo requisito que atualmente não suporte é que existam algumas opções de configuração por dispositivo (pares de chave / valor) que devem ser configuradas no lado do servidor administrativo. Eu não me importaria de alguma forma fornecer essas opções por dispositivo de volta ao dispositivo na mesma solicitação HTTP da atualização de software (talvez eu possa encapsulá-lo em cabeçalhos / cookies HTTP), embora não esteja muito preocupado com isso, pois posso sempre faça uma solicitação HTTP separada.
- Há uma ligeira complicação devido ao fato de existirem duas (e mais no futuro) versões do hardware. A versão atual do hardware é realmente armazenada como uma variável de ambiente em sua imagem SD inicial (eles não podem se identificar) e todo o software foi projetado para ser compatível com todas as versões dos dispositivos. As atualizações de firmware são escolhidas com base nessa variável de ambiente e o pacote de atualização contém firmware para todas as versões do hardware. Eu posso viver com isso, embora seja um pouco desajeitado.
- Atualmente, não existe maneira de fazer upload manualmente de uma atualização para o dispositivo (em resumo, esses dispositivos possuem dois adaptadores Wi-Fi, um para conectar-se à Internet e outro no modo AP que o usuário usa para configurar o dispositivo; no futuro Pretendo adicionar uma função "atualizar software" à interface da Web local do dispositivo). Isso não é grande coisa, mas tem algum impacto no método de instalação da atualização.
- Um monte de outras frustrações e insegurança geral.
Então ... isso foi longo. Mas minha pergunta se resume a isso:
Como faço isso de maneira adequada e segura? Existem pequenos ajustes que posso fazer no meu processo existente? Existe uma estratégia testada pelo tempo / sistema existente que eu possa aproveitar para não precisar lançar meu próprio sistema de atualização ruim ? Ou, se eu tiver que fazer o meu próprio, quais são as coisas que devem ser verdadeiras para que um processo de implantação / atualização seja seguro e bem-sucedido? Também preciso incluir dispositivos de desenvolvimento no mix.
Espero que a pergunta seja clara. Percebo que é um pouco confuso, mas tenho 100% de certeza de que esse é um problema que foi resolvido antes e resolvido com sucesso; simplesmente não sei quais são as estratégias atualmente aceitas.