Implantação de aplicativos em vários clusters Kubernetes com Helm

Como o Dailymotion utiliza Kubernetes: implantação de aplicativos

No Dailymotion, começamos a usar Kubernetes em produção há 3 anos. Mas implantar aplicativos em vários clusters é um desafio, por isso, nos últimos anos, temos buscado melhorar nossas ferramentas e fluxos de trabalho.

Como tudo começou

Aqui, vamos explicar como implantamos nossos aplicativos em vários clusters Kubernetes ao redor do mundo.

Para implantar vários objetos Kubernetes de uma só vez, utilizamos Helm, e todos os nossos charts são armazenados em um único repositório git. Para implantar a pilha completa de um aplicativo de vários serviços, usamos o que chamamos de chart agregador. Essencialmente, este é um chart que declara as dependências e permite iniciar a API e seus serviços com um único comando.

Além disso, escrevemos um pequeno script Python sobre o Helm para realizar verificações, criar charts, adicionar segredos e implantar aplicativos. Todas essas tarefas são executadas em uma plataforma CI central com uma imagem Docker.

Vamos direto ao ponto.

Nota. Quando você lê isso, o primeiro release candidate do Helm 3 já foi anunciado. A versão principal contém uma série de melhorias destinadas a resolver alguns problemas que enfrentamos no passado.

Fluxo de trabalho de desenvolvimento de charts

Para os aplicativos, utilizamos o branching, e decidimos aplicar a mesma abordagem aos charts.

  • A branch dev é utilizada para criar charts que serão testados em clusters de desenvolvimento.
  • Quando o pull request é enviado para master, eles são verificados em staging.
  • Finalmente, criamos um pull request para mesclar as mudanças na branch prod e aplicá-las em produção.

Cada ambiente tem seu próprio repositório privado que armazena nossos charts, e usamos Chartmuseum com APIs muito úteis. Dessa forma, garantimos um isolamento rigoroso entre os ambientes e testamos os charts em condições reais, antes de usá-los em produção.

Repositórios de charts em diferentes ambientes

Vale a pena mencionar que quando os desenvolvedores submetem uma branch dev, a versão de seu chart é automaticamente enviada para o dev Chartmuseum. Assim, todos os desenvolvedores usam um único repositório dev, e é preciso ter cuidado ao especificar a versão do seu chart para não utilizar acidentalmente as mudanças de alguém.

Além disso, nosso pequeno script Python verifica objetos Kubernetes de acordo com as especificações do Kubernetes OpenAPI usando Kubeval, antes de publicá-los no Chartmuseum.

Descrição geral do fluxo de trabalho de desenvolvimento do chart

  1. Configuração de tarefas do pipeline de acordo com as especificações gazr.io para controle de qualidade (lint, unit-test).
  2. Envio da imagem docker com ferramentas Python que implantam nossas aplicações.
  3. Configuração do ambiente pelo nome da branch.
  4. Validação de arquivos yaml Kubernetes usando Kubeval.
  5. Aumento automático da versão do chart e seus charts pais (charts que dependem do chart modificado).
  6. Envio do chart para o Chartmuseum que corresponde ao seu ambiente

Gerenciamento de diferenças entre clusters

Federação de clusters

Houve um tempo em que usávamos a federação de clusters Kubernetes, onde era possível declarar objetos Kubernetes de um único ponto final da API. Mas surgiram problemas. Por exemplo, alguns objetos Kubernetes não podiam ser criados no ponto final da federação, o que dificultava a manutenção de objetos unificados e outros objetos para clusters separados.

Para resolver o problema, começamos a gerenciar clusters de forma independente, o que simplificou significativamente o processo (usamos a primeira versão da federação; na segunda, algo poderia ter mudado).

Plataforma geograficamente distribuída

Atualmente, nossa plataforma está distribuída em 6 regiões — 3 localmente e 3 na nuvem.


Implantação distribuída

Valores globais do Helm

4 valores globais do Helm permitem definir diferenças entre clusters. Para todos os nossos charts, existem valores mínimos padrão.

global:
  cloud: True
  env: staging
  region: us-central1
  clusterName: staging-us-central1

Valores globais

Esses valores ajudam a definir o contexto para nossas aplicações e são usados para várias tarefas: monitoramento, rastreamento, registro, chamadas externas, escalonamento etc.

  • "cloud": temos uma plataforma Kubernetes híbrida. Por exemplo, nossa API é implantada em zonas GCP e em nossos data centers.
  • "env": alguns valores podem mudar para ambientes não produtivos. Por exemplo, definições de recursos e configuração de autoescalonamento.
  • "region": essas informações ajudam a determinar a localização do cluster e podem ser usadas para definir os pontos finais mais próximos para serviços externos.
  • "clusterName": se e quando quisermos definir um valor para um cluster específico.

Aqui está um exemplo específico:

{{
/* Retorna o número de réplicas do Horizontal Pod Autoscaler para GraphQL*/}}
{{- define "graphql.hpaReplicas" -}}
{{- if eq .Values.global.env "prod" }}
{{- if eq .Values.global.region "europe-west1" }}
minReplicas: 40
{{- else }}
minReplicas: 150
{{- end }}
maxReplicas: 1400
{{- else }}
minReplicas: 4
maxReplicas: 20
{{- end }}
{{- end -}}

Exemplo de modelo Helm

Essa lógica é definida em um modelo auxiliar, para não poluir o YAML do Kubernetes.

Declaração do aplicativo

Nossos ferramentas de implantação são baseadas em vários arquivos YAML. Abaixo está um exemplo de como declaramos o serviço e sua topologia de escalonamento (número de réplicas) no cluster.

releases:
  - foo.world

foo.world:                # Nome do Release
  services:               # Lista de aplicativos/projetos da dailymotion
    foobar:
      chart_name: foo-foobar
      repo: git@github.com:dailymotion/foobar
      contexts:
        prod-europe-west1:
          deployments:
            - name: foo-bar-baz
              replicas: 18
            - name: another-deployment
              replicas: 3

Definição do serviço

Este é um esquema de todos os passos que definem nosso fluxo de trabalho de implementação. O último passo implanta o aplicativo simultaneamente em vários clusters de trabalho.


Passos de implantação no Jenkins

E os segredos?

Em termos de segurança, rastreamos todos os segredos de diferentes lugares e os armazenamos em um repositório único. Vault em Paris.

Nossos ferramentas de implantação extraem os valores dos segredos do Vault e, quando chega a hora da implementação, os inserem no Helm.

Para isso, definimos um mapeamento entre os segredos no Vault e os segredos necessários para nossas aplicações:

segredos:                                                                                                                                                                                                        
     - secret_id: "stack1-app1-password"                                                                                                                                                                                  
       contexts:                                                                                                                                                                                                   
         - name: "padrão"                                                                                                                                                                                         
           vaultPath: "\/kv\/dev\/stack1\/app1\/test"                                                                                                                                                               
           vaultKey: "senha"                                                                                                                                                                                    
         - name: "cluster1"                                                                                                                                                                           
           vaultPath: "\/kv\/dev\/stack1\/app1\/test"                                                                                                                                                               
           vaultKey: "senha"

  • Definimos regras gerais que devem ser seguidas ao gravar segredos no Vault.
  • Se o segredo se refere a um contexto ou cluster específico, é necessário adicionar uma entrada específica. (Aqui, o contexto cluster1 possui um valor próprio para o segredo stack-app1-password).
  • Caso contrário, é utilizado o valor padrão.
  • Para cada item nesta lista, um segredo do Kubernetes insere um par chave-valor. Portanto, o modelo do segredo em nossos gráficos é muito simples.

apiVersion: v1
data:
{{- range $key,$value := .Values.secrets }}
  {{ $key }}: {{ $value | b64enc | quote }}
{{ end }}
kind: Secret
metadata:
  name: "{{ .Chart.Name }}"
  labels:
    chartVersion: "{{ .Chart.Version }}"
    tillerVersion: "{{ .Capabilities.TillerVersion.SemVer }}"
type: Opaque

Problemas e limitações

Trabalhando com múltiplos repositórios

Atualmente, estamos separando o desenvolvimento de gráficos e aplicações. Isso significa que os desenvolvedores precisam trabalhar em dois repositórios Git: um para a aplicação e outro para definir sua implantação no Kubernetes. 2 repositórios Git são 2 fluxos de trabalho, e é fácil para um iniciante se confundir.

Gerir gráficos genéricos é trabalhoso

Como já mencionamos, gráficos genéricos são muito úteis para definir dependências e implantar rapidamente várias aplicações. Mas utilizamos --reuse-values, para evitar a passagem de todos os valores cada vez que implantamos uma aplicação que faz parte desse gráfico genérico.

No processo de entrega contínua, temos apenas dois valores que mudam regularmente: a quantidade de réplicas e a tag da imagem (versão). Outros valores, mais estáveis, são alterados manualmente e isso pode ser bastante complicado. Além disso, um erro na implantação de um gráfico genérico pode levar a falhas sérias, como experimentamos em primeira mão.

Atualizando vários arquivos de configuração

Quando um desenvolvedor adiciona um novo aplicativo, ele precisa modificar vários arquivos: a declaração do aplicativo, a lista de segredos, adicionar o aplicativo às dependências se fizer parte do gráfico genérico.

As permissões do Jenkins estão muito amplas no Vault

Atualmente temos um AppRole, que lê todos os segredos do Vault.

O processo de rollback não é automatizado

Para realizar um rollback, é necessário executar um comando em vários clusters, o que é propenso a erros. Realizamos essa operação manualmente para garantir que o identificador de versão correto seja especificado.

Estamos avançando rumo ao GitOps

Nosso objetivo

Queremos retornar o gráfico para o repositório do aplicativo que ele implanta.

O fluxo de trabalho será o mesmo que para o desenvolvimento. Por exemplo, quando uma branch é enviada para o master, a implantação será iniciada automaticamente. A principal diferença entre essa abordagem e o fluxo de trabalho atual é que tudo será gerenciado no git (o próprio aplicativo e a forma como ele é implantado no Kubernetes).

Existem várias vantagens:

  • Muito mais claro para o desenvolvedor. Mais fácil de aprender a aplicar mudanças no gráfico local.
  • A definição da implantação do serviço pode ser especificada ali mesmo onde o código do serviço está.
  • Gerenciamento da exclusão de gráficos genéricos. O serviço terá sua própria liberação do Helm. Isso permitirá gerenciar o ciclo de vida do aplicativo (rollback, upgrade) no nível mais detalhado, sem afetar outros serviços.
  • As vantagens do git para gerenciar gráficos: reverter alterações, registro de auditoria, etc. Se precisar reverter uma alteração de gráfico, isso pode ser feito através do git. A implantação é iniciada automaticamente.
  • Podemos considerar aprimorar o fluxo de trabalho de desenvolvimento com ferramentas como Skaffold, com as quais os desenvolvedores podem testar alterações em um contexto próximo ao de produção.

Migração em duas etapas

Nossos desenvolvedores usam este fluxo de trabalho há 2 anos, então precisamos de uma migração o menos dolorosa possível. Por isso, decidimos adicionar uma etapa intermediária no caminho até o objetivo.
A primeira etapa é simples:

  • Mantemos uma estrutura semelhante para a configuração de implantação de aplicações, mas em um único objeto chamado DailymotionRelease.

apiVersion: "v1"
kind: "DailymotionRelease"
metadata:
  name: "app1.ns1"
  environment: "dev"
  branch: "mybranch"
spec:
  slack_channel: "#admin"
  chart_name: "app1"
  scaling:
    - context: "dev-us-central1-0"
      replicas:
        - name: "hermes"
          count: 2
    - context: "dev-europe-west1-0"
      replicas:
        - name: "app1-deploy"
          count: 2
  secrets:
    - secret_id: "app1"
      contexts:
        - name: "default"
          vaultPath: "\/kv\/dev\/ns1\/app1\/test"
          vaultKey: "senha"
        - name: "dev-europe-west1-0"
          vaultPath: "\/kv\/dev\/ns1\/app1\/test"
          vaultKey: "senha"

  • 1 lançamento por aplicação (sem gráficos resumidos).
  • Gráficos no repositório git da aplicação.

Conversamos com todos os desenvolvedores, então o processo de migração já começou. A primeira etapa ainda está sendo controlada pela plataforma CI. Em breve escreverei outro post sobre a segunda etapa: como migramos para o fluxo de trabalho GitOps com Flux. Vou contar como configuramos tudo e quais dificuldades encontramos (vários repositórios, segredos etc.). Fiquem atentos às novidades.

Aqui tentamos descrever nosso progresso no fluxo de trabalho de implantação de aplicações nos últimos anos, que nos levou a pensar sobre a abordagem GitOps. Ainda não alcançamos nosso objetivo e teremos atualizações, mas agora estamos convencidos de que fizemos a coisa certa ao decidir simplificar tudo e aproximar-se dos hábitos dos desenvolvedores.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster