Implante aplicativos em vários clusters Kubernetes com Helm

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

Nós do Dailymotion começamos a usar o Kubernetes em produção há 3 anos. Mas implantar aplicativos em vários clusters é divertido, por isso, nos últimos anos, temos tentado melhorar nossas ferramentas e fluxos de trabalho.

Onde tudo começou

Aqui abordaremos como implantamos nossos aplicativos em vários clusters Kubernetes em todo o mundo.

Para implantar vários objetos Kubernetes de uma vez, usamos Capacete, e todos os nossos gráficos são armazenados em um repositório git. Para implantar uma pilha completa de aplicativos de vários serviços, usamos o chamado gráfico de resumo. Essencialmente, este é um gráfico que declara dependências e permite inicializar a API e seus serviços com um comando.

Também escrevemos um pequeno script Python no Helm para fazer verificações, criar gráficos, adicionar segredos e implantar aplicativos. Todas essas tarefas são executadas em uma plataforma central de CI usando uma imagem docker.

Vamos direto ao ponto.

Observação. Enquanto você lê isto, o primeiro release candidate do Helm 3 já foi anunciado. A versão principal contém uma série de melhorias para resolver alguns dos problemas que encontramos no passado.

Fluxo de trabalho de desenvolvimento de gráfico

Usamos ramificação para aplicativos e decidimos aplicar a mesma abordagem aos gráficos.

  • Filial dev usado para criar gráficos que serão testados em clusters de desenvolvimento.
  • Quando uma solicitação pull é enviada para dominar, eles são verificados na preparação.
  • Por fim, criamos uma solicitação pull para confirmar as alterações no branch estímulo e aplicá-los na produção.

Cada ambiente possui seu próprio repositório privado que armazena nossos gráficos, e usamos Museu Cartográfico com APIs muito úteis. Dessa forma, garantimos um isolamento rigoroso entre ambientes e testes reais de gráficos antes de usá-los em produção.

Repositórios de gráficos em diferentes ambientes

É importante notar que quando os desenvolvedores enviam um branch de desenvolvimento, uma versão de seu gráfico é automaticamente enviada para o Chartmuseum de desenvolvimento. Assim, todos os desenvolvedores usam o mesmo repositório de desenvolvimento, e você precisa especificar cuidadosamente sua versão do gráfico para não usar acidentalmente as alterações de outra pessoa.

Além disso, nosso pequeno script Python valida objetos Kubernetes em relação às especificações OpenAPI do Kubernetes usando Kubeval, antes de publicá-los no Chartmusem.

Descrição geral do fluxo de trabalho de desenvolvimento de gráficos

  1. Configurando tarefas de pipeline de acordo com a especificação gazr.io para controle de qualidade (lint, teste unitário).
  2. Enviar uma imagem docker com ferramentas Python que implantam nossos aplicativos.
  3. Configurando o ambiente por nome da filial.
  4. Validando arquivos yaml do Kubernetes usando Kubeval.
  5. Aumenta automaticamente a versão de um gráfico e seus gráficos pai (gráficos que dependem do gráfico que está sendo alterado).
  6. Enviando um gráfico para um Chartmuseum que corresponda ao seu ambiente

Gerenciando diferenças entre clusters

Federação de Clusters

Houve um tempo em que usávamos federação de clusters Kubernetes, onde os objetos do Kubernetes podem ser declarados a partir de um único endpoint de API. Mas surgiram problemas. Por exemplo, alguns objetos do Kubernetes não puderam ser criados no endpoint da federação, dificultando a manutenção de objetos federados e outros objetos para clusters individuais.

Para resolver o problema, passamos a gerenciar os clusters de forma independente, o que simplificou bastante o processo (usamos a primeira versão da federação; algo pode ter mudado na segunda).

Plataforma distribuída geograficamente

Nossa plataforma está atualmente distribuída em 6 regiões – 3 localmente e 3 na nuvem.


Implantação Distribuída

Valores do Helm Global

4 valores globais do Helm permitem identificar diferenças entre clusters. Todos os nossos gráficos possuem 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 de nossas aplicações e são usados ​​para diversos fins: monitoramento, rastreamento, registro, realização de chamadas externas, escalonamento, etc.

  • "cloud": Temos uma plataforma híbrida Kubernetes. Por exemplo, nossa API é implantada em zonas GCP e em nossos data centers.
  • "env": Alguns valores podem mudar para ambientes que não sejam de produção. Por exemplo, definições de recursos e configurações de escalonamento automático.
  • "região": esta informação ajuda a determinar a localização do cluster e pode ser usada para determinar pontos finais próximos para serviços externos.
  • "clusterName": se e quando quisermos definir um valor para um cluster individual.

Aqui está um exemplo específico:

{{/* Returns Horizontal Pod Autoscaler replicas for 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 de leme

Essa lógica é definida em um modelo auxiliar para evitar sobrecarregar o YAML do Kubernetes.

Anúncio de inscrição

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

releases:
  - foo.world

foo.world:                # Release name
  services:               # List of dailymotion's apps/projects
    foobar:
      chart_name: foo-foobar
      repo: [email protected]:dailymotion/foobar
      contexts:
        prod-europe-west1:
          deployments:
            - name: foo-bar-baz
              replicas: 18
            - name: another-deployment
              replicas: 3

Definição de serviço

Este é um resumo de todas as etapas que definem nosso fluxo de trabalho de implantação. A última etapa implanta o aplicativo em vários clusters de trabalhadores simultaneamente.


Etapas de implantação do Jenkins

E quanto aos segredos?

Em relação à segurança, rastreamos todos os segredos de diferentes locais e os armazenamos em um cofre exclusivo Cofre em Paris.

Nossas ferramentas de implantação extraem valores secretos do Vault e, quando chegar a hora da implantação, os inserem no Helm.

Para fazer isso, definimos um mapeamento entre os segredos do Vault e os segredos que nossas aplicações precisam:

secrets:                                                                                                                                                                                                        
     - secret_id: "stack1-app1-password"                                                                                                                                                                                  
       contexts:                                                                                                                                                                                                   
         - name: "default"                                                                                                                                                                                         
           vaultPath: "/kv/dev/stack1/app1/test"                                                                                                                                                               
           vaultKey: "password"                                                                                                                                                                                    
         - name: "cluster1"                                                                                                                                                                           
           vaultPath: "/kv/dev/stack1/app1/test"                                                                                                                                                               
           vaultKey: "password"

  • Definimos regras gerais a serem seguidas ao gravar segredos no Vault.
  • Se o segredo se aplicar para um contexto ou cluster específico, você precisa adicionar uma entrada específica. (Aqui o contexto cluster1 tem seu próprio valor para o segredo stack-app1-password).
  • Caso contrário, o valor é usado por padrão.
  • Para cada item desta lista em Segredo do Kubernetes um par de valores-chave é inserido. Portanto, o modelo secreto 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

Desafios e limitações

Trabalhando com vários repositórios

Agora separamos o desenvolvimento de gráficos e aplicações. Isso significa que os desenvolvedores precisam trabalhar em dois repositórios git: um para o aplicativo e outro para definir sua implantação no Kubernetes. 2 repositórios git significam 2 fluxos de trabalho e é fácil para um novato ficar confuso.

Gerenciar gráficos generalizados é um incômodo

Como já dissemos, os gráficos genéricos são muito úteis para identificar dependências e implantar rapidamente vários aplicativos. Mas nós usamos --reuse-valuespara evitar passar todos os valores sempre que implantarmos uma aplicação que faz parte deste gráfico generalizado.

Em um fluxo de trabalho de entrega contínua, temos apenas dois valores que mudam regularmente: o número de réplicas e a tag da imagem (versão). Outros valores mais estáveis ​​são alterados manualmente, e isso é bastante difícil. Além disso, um erro na implantação de um gráfico generalizado pode levar a falhas graves, como vimos pela nossa própria experiência.

Atualizando vários arquivos de configuração

Quando um desenvolvedor adiciona uma nova aplicação, ele tem que alterar vários arquivos: a declaração da aplicação, a lista de segredos, adicionando a aplicação como uma dependência caso esteja incluída no gráfico generalizado.

As permissões do Jenkins são muito estendidas no Vault

Temos um agora AppRole, que lê todos os segredos do Vault.

O processo de reversão não é automatizado

Para reverter, você precisa executar o comando em vários clusters, e isso está repleto de erros. Realizamos esta operação manualmente para garantir que o ID de versão correto seja especificado.

Estamos caminhando para o GitOps

Nosso objetivo

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

O fluxo de trabalho será o mesmo do desenvolvimento. Por exemplo, quando uma ramificação é enviada para master, a implantação será acionada automaticamente. A principal diferença entre esta abordagem e o fluxo de trabalho atual seria 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 aprender como aplicar alterações em um gráfico local.
  • A definição de implantação de serviço pode ser especificada mesmo lugar do código serviço.
  • Gerenciando a remoção de gráficos generalizados. O serviço terá sua própria versão do Helm. Isso permitirá que você gerencie o ciclo de vida do aplicativo (reversão, atualização) no menor nível, para não afetar outros serviços.
  • Benefícios do git para gerenciamento de gráficos: desfazer alterações, log de auditoria, etc. Se precisar desfazer uma alteração em um gráfico, você pode fazer isso usando git. A implantação é iniciada automaticamente.
  • Você pode considerar melhorar seu fluxo de trabalho de desenvolvimento com ferramentas como Andaime, com o qual os desenvolvedores podem testar alterações em um contexto próximo à produção.

Migração em duas etapas

Nossos desenvolvedores usam esse fluxo de trabalho há 2 anos, por isso queremos que a migração seja o mais simples possível. Portanto, decidimos adicionar uma etapa intermediária no caminho para a meta.
A primeira etapa é simples:

  • Mantemos uma estrutura semelhante para configurar a implantação da aplicação, 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: "password"
        - name: "dev-europe-west1-0"
          vaultPath: "/kv/dev/ns1/app1/test"
          vaultKey: "password"

  • 1 release por aplicação (sem gráficos generalizados).
  • Gráficos no repositório git do aplicativo.

Conversamos com todos os desenvolvedores, então o processo de migração já começou. A primeira etapa ainda é controlada pela plataforma CI. Escreverei outro post em breve sobre a fase dois: como mudamos para um fluxo de trabalho GitOps com Fluxo. Vou contar como configuramos tudo e quais dificuldades encontramos (múltiplos repositórios, segredos, etc.). Acompanhe as novidades.

Aqui tentamos descrever nosso progresso no fluxo de trabalho de implantação de aplicativos nos últimos anos, o que levou a reflexões sobre a abordagem GitOps. Ainda não atingimos a meta e iremos reportar os resultados, mas agora temos a convicção de que fizemos a coisa certa quando decidimos simplificar tudo e aproximar os hábitos dos desenvolvedores.

Fonte: habr.com

Adicionar um comentário