Despliegue de aplicaciones en varios clústeres de Kubernetes con Helm

Cómo Dailymotion utiliza Kubernetes: despliegue de aplicaciones

En Dailymotion comenzamos a utilizar Kubernetes en producción hace 3 años. Sin embargo, desplegar aplicaciones en varios clústeres es un reto, por lo que en los últimos años hemos trabajado para mejorar nuestras herramientas y procesos.

El comienzo

Aquí explicaremos cómo desplegamos nuestras aplicaciones en varios clústeres de Kubernetes en todo el mundo.

Para desplegar varios objetos de Kubernetes a la vez, utilizamos Helm, y todos nuestros charts se almacenan en un único repositorio git. Para desplegar la pila completa de la aplicación que consiste en varios servicios, utilizamos lo que llamamos un chart general. Esencialmente, este chart declara dependencias y permite inicializar la API y sus servicios con un solo comando.

Además, hemos escrito un pequeño script en Python sobre Helm para realizar verificaciones, crear charts, agregar secretos y desplegar aplicaciones. Todas estas tareas se ejecutan en una plataforma central de CI utilizando una imagen de docker.

Vayamos al grano.

Nota. Cuando leas esto, ya se ha anunciado el primer candidato a lanzamiento de Helm 3. La versión principal contiene un conjunto completo de mejoras diseñadas para resolver algunos problemas que enfrentamos en el pasado.

Flujo de trabajo de desarrollo de charts

Para las aplicaciones, utilizamos ramificación, y decidimos aplicar este mismo enfoque a los charts.

  • La rama dev se utiliza para crear charts que serán probados en clústeres de desarrollo.
  • Cuando se envía un pull request a master, se verifica en staging.
  • Finalmente, creamos un pull request para enviar los cambios a la rama prod y aplicarlos en producción.

Cada entorno tiene su propio repositorio privado que almacena nuestros charts, y damos uso a Chartmuseum con APIs muy útiles. De este modo garantizamos un estricto aislamiento entre entornos y la validación de los charts en condiciones reales antes de utilizarlos en producción.

Repositorios de charts en diferentes entornos

Cabe señalar que cuando los desarrolladores envían una rama dev, la versión de su chart se envía automáticamente al dev Chartmuseum. Así, todos los desarrolladores utilizan un único repositorio dev, y es importante especificar correctamente la versión de su chart para no usar accidentalmente los cambios de otros.

Además, nuestro pequeño script de Python verifica los objetos de Kubernetes según las especificaciones de Kubernetes OpenAPI mediante Kubeval, antes de publicarlos en Chartmuseum.

Descripción general del flujo de trabajo de desarrollo del chart

  1. Configuración de tareas del pipeline según la especificación gazr.io para el control de calidad (lint, unit-test).
  2. Envío de la imagen de docker con herramientas de Python que despliegan nuestras aplicaciones.
  3. Configuración del entorno según el nombre de la rama.
  4. Verificación de archivos yaml de Kubernetes con Kubeval.
  5. Incremento automático de la versión del chart y sus charts padres (charts que dependen del chart modificado).
  6. Envío del chart a Chartmuseum, correspondiente a su entorno

Gestión de diferencias en clústeres

Federación de clústeres

Hubo un tiempo en el que utilizamos la federación de clústeres de Kubernetes, donde se podían declarar objetos de Kubernetes desde un único punto final de API. Pero surgieron problemas. Por ejemplo, algunos objetos de Kubernetes no se podían crear en el punto final de la federación, lo que dificultaba el mantenimiento de los objetos combinados y otros objetos para clústeres individuales.

Para solucionar el problema, comenzamos a gestionar los clústeres de forma independiente, lo que simplificó considerablemente el proceso (utilizamos la primera versión de la federación; en la segunda algo podría haber cambiado).

Plataforma geodistribuida

Ahora nuestra plataforma está distribuida en 6 regiones: 3 localmente y 3 en la nube.


Despliegue distribuido

Valores globales de Helm

4 valores globales de Helm permiten definir diferencias entre clústeres. Para todos nuestros charts, hay valores mínimos predeterminados.

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

Valores globales

Estos valores ayudan a definir el contexto para nuestras aplicaciones y se utilizan para diferentes tareas: monitoreo, trazado, registro, realización de llamadas externas, escalado, etc.

  • "cloud": tenemos una plataforma híbrida de Kubernetes. Por ejemplo, nuestra API se despliega en las zonas de GCP y en nuestros centros de datos.
  • "env": algunos valores pueden cambiar para entornos no operativos. Por ejemplo, definiciones de recursos y configuración de escalado automático.
  • "region": esta información ayuda a determinar la ubicación del clúster y puede utilizarse para definir los puntos finales más cercanos para servicios externos.
  • "clusterName": si y cuando queremos definir un valor para un clúster específico.

Aquí hay un ejemplo concreto:

{{
/* Devuelve réplicas de 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 -}}

Ejemplo de plantilla Helm

Esta lógica está definida en una plantilla auxiliar para no saturar el YAML de Kubernetes.

Declaración de la aplicación

Nuestras herramientas de despliegue se basan en varios archivos YAML. A continuación, se muestra un ejemplo de cómo declaramos el servicio y su topología de escalado (número de réplicas) en el clúster.

releases:
  - foo.world

foo.world:                # Nombre de la versión
  services:               # Lista de aplicaciones/proyectos de 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

Definición del servicio

Este es el esquema de todos los pasos que definen nuestro flujo de trabajo de despliegue. El último paso despliega la aplicación simultáneamente en varios clústeres de trabajo.


Pasos de despliegue en Jenkins

¿Y los secretos?

En lo que respecta a la seguridad, rastreamos todos los secretos de diferentes lugares y los almacenamos en un almacén único Vault en París.

Nuestras herramientas de despliegue extraen valores de los secretos de Vault y, cuando llega el momento del despliegue, los insertan en Helm.

Para esto, hemos definido un mapeo entre los secretos en Vault y los secretos que necesitan nuestras aplicaciones:

secretos:                                                                                                                                                                                                        
     - 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"

  • Hemos definido reglas generales que deben seguirse al registrar secretos en Vault.
  • Si el secreto está relacionado con un contexto o clúster específico, es necesario agregar una entrada concreta. (Aquí, el contexto cluster1 tiene su propio valor para el secreto stack-app1-password).
  • De lo contrario, se utiliza el valor por defecto.
  • Para cada elemento en esta lista en el secreto de Kubernetes se inserta un par clave-valor. Por lo tanto, la plantilla del secreto en nuestros charts es muy simple.

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 y limitaciones

Trabajando con múltiples repositorios

Actualmente estamos separando el desarrollo de charts y aplicaciones. Esto significa que los desarrolladores tienen que trabajar en dos repositorios git: uno para la aplicación y otro para definir su despliegue en Kubernetes. 2 repositorios git significan 2 flujos de trabajo, y es fácil que un principiante se confunda.

Gestionar charts genéricos es laborioso

Como se mencionó, los charts genéricos son muy convenientes para definir dependencias y desplegar rápidamente varias aplicaciones. Pero estamos utilizando --reuse-values, para evitar pasar todos los valores cada vez que desplegamos una aplicación que forma parte de este chart genérico.

En el proceso de entrega continua, solo tenemos dos valores que cambian regularmente: el número de réplicas y la etiqueta de la imagen (versión). Otros valores, más estables, se cambian manualmente, lo que resulta complicado. Además, un error en el despliegue de un gráfico genérico puede causar fallos serios, como hemos aprendido por experiencia propia.

Actualización de varios archivos de configuración

Cuando el desarrollador añade una nueva aplicación, tiene que modificar varios archivos: la declaración de la aplicación, la lista de secretos, añadir la aplicación como dependencia si está en el gráfico genérico.

Los permisos de Jenkins son demasiado amplios en Vault

Actualmente tenemos uno AppRole, que lee todos los secretos de Vault.

El proceso de reversión no está automatizado

Para revertir, hay que ejecutar un comando en varios clústeres, lo que conlleva riesgos de errores. Realizamos esta operación manualmente para garantizar un identificador de versión correcto.

Nos estamos moviendo hacia GitOps

Nuestro objetivo

Queremos devolver el gráfico al repositorio de la aplicación que despliega.

El flujo de trabajo será el mismo que para el desarrollo. Por ejemplo, cuando una rama se envía a master, el despliegue se iniciará automáticamente. La principal diferencia entre este enfoque y el flujo de trabajo actual es que todo será gestionado en git (la propia aplicación y la forma en que se despliega en Kubernetes).

Hay varias ventajas:

  • Mucho más claro para el desarrollador. Es más fácil aprender a aplicar cambios en el gráfico local.
  • La definición del despliegue del servicio se puede indicar junto con el código del servicio.
  • Gestión de la eliminación de gráficos genéricos. El servicio tendrá su propio lanzamiento de Helm. Esto permitirá gestionar el ciclo de vida de la aplicación (reversión, actualización) a un nivel más granular sin afectar a otros servicios.
  • Las ventajas de git para la gestión de gráficos: reversión de cambios, registro de auditoría, etc. Si es necesario revertir un cambio en el gráfico, se puede hacer utilizando git. El despliegue se inicia automáticamente.
  • Se puede considerar la mejora del flujo de trabajo de desarrollo utilizando herramientas como Skaffold, con las que los desarrolladores pueden probar cambios en un contexto similar al de producción.

Migración en dos etapas

Nuestros desarrolladores han estado utilizando este flujo de trabajo durante 2 años, por lo que necesitamos una migración lo más indolora posible. Por eso decidimos agregar una etapa intermedia en el camino hacia la meta.
La primera etapa es sencilla:

  • Mantenemos una estructura similar para la configuración de implementación de aplicaciones, pero en un solo objeto llamado 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 lanzamiento por aplicación (sin gráficos generalizados).
  • Gráficos en el repositorio git de la aplicación.

Hemos hablado con todos los desarrolladores, así que el proceso de migración ya ha comenzado. La primera etapa aún se controla utilizando la plataforma CI. Pronto escribiré otro post sobre la segunda etapa: cómo pasamos al flujo de trabajo de GitOps con Flux. Les contaré cómo configuramos todo y las dificultades que enfrentamos (varios repositorios, secretos, etc.). Estén atentos a las novedades.

Aquí intentamos describir nuestro progreso en el flujo de trabajo de implementación de aplicaciones durante los últimos años, que nos llevó a pensar en el enfoque de GitOps. Aún no hemos alcanzado nuestra meta y estaremos informando sobre los resultados, pero ahora estamos convencidos de que hicimos bien al simplificar todo y acercarlo a los hábitos de los desarrolladores.

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster