Saltar al contenido principal

FluxCD

Introducción

La lista de servicios utilizada por Sora Project es medianamente grande, así que requiere un poco de organización.

Me he basado en la arquitectura MonoRepo de FluxCD, la cual permite configurar todos los entornos y servicios en un único repositorio y rama, separando por carpetas. Esta define una base y la sobrescribe para cada entorno, permitiendo ahorrar mucho tiempo.

Los servicios se separan en dos partes:

  • Infraestructura: Conforman la estructura base y son utilizados por el resto, también llamados servicios de plataforma. En este caso son aquellos separados por niveles.
  • Servicios: Son los servicios que realmente quieres publicar y que hacen uso (directa o indirectamente) de los de plataforma.
info

Kubernetes no permite "bloquear" un despliegue en espera de que sus dependencias estén desplegadas, pero FluxCD sí que lo permite. Esto nos permite evitar explosiones tontas o añadir reintentos que añadiría complejidad al sistema.

Encriptación

En mi caso utilizo un repositorio público, es por ello por lo que los datos sensibles DEBEN estar encriptados.

FluxCD dispone de varios métodos, en mi caso utilizo SOPS con encriptación Age.

tip

Normalmente se utilizaría el Vault original de la Cloud o uno desplegado, por ejemplo, el de HashiCorp. Alguna gente despliega el Vault dentro del propio clúster, pero tiene cierto peligro ya que la base de almacén está en el mismo clúster que estás desplegando, si esta falla pues ... tienes un problema 😄

precaución

Ojo con el tema, subir un fichero sin encriptar no tiene fácil solución, volverlo a subir hará que Git recuerde el original.

Inicialización

Requerimientos

Para gestionar FluxCD y los ficheros encriptados con SOPS y Age, se requieren los 3 comandos.

Este comando los instala mediante brew, accesible en Unix:

brew install fluxcd/tap/flux sops age

Desplegar FluxCD

Para evitar problemas, es mejor confirmar que todo funcionará y dejar los servicios desplegados.

flux check --pre
flux install
flux check

Encriptación

Para simplificar, los datos sensibles se almacenan en secrets que después se asignan como un values externo. Estos suelen estar configurados a nivel de entorno.

Se debe crear el fichero de claves y registrarlo en el clúster.

age-keygen -o age.agekey && \
cat age.agekey | kubectl create secret generic sops-age -n flux-system --from-file=age.agekey=/dev/stdin && \
rm age.agekey

Para encriptar los ficheros necesitamos el Public key que habrá impreso por pantalla, si lo perdemos, siempre podemos recuperarlo del secret.

peligro

El fichero age.agekey permite desencriptar, así que ojo con él.

Conectar al repositorio Git

El repositorio debe ser accesible por el clúster, tener las carpetas básicas y tener un token con acceso de escritura.

tip

La primera vez, es recomendable empezar con un repositorio "vacío", añadiendo poco a poco las carpetas o servicios siguientes, controlando que todo esté como se espera.

aviso

Recordar actualizar todos los ficheros encriptados con la clave generada previamente. Para ello se requiere el fichero sin encriptar (se detallan todos más adelante) y el siguiente comando indicando el Public key:

sops --age=replace-me_public-key \
--encrypt --encrypted-regex '^(data|stringData)$' \
--in-place secret-file.yaml

En caso de GitLab, el token se crea directamente en el repositorio con el rol de Maintainer y el scope de api. Es altamente aconsejable tener uno diferente por entorno.

info

Este comando es para GitLab, FluxCD dispone de un bootstrap diferente (aunque similares) para cada tipo de repositorio Git.

Ajustar según el token, owner (hace de grupo), nombre de repositorio y la carpeta padre para ese entorno. Esto conectará el clúster hacia ese repositorio y construirá sus ficheros.

export GITLAB_TOKEN="replace-me_token"
flux bootstrap gitlab --owner=reiizumi --repository=fluxcd-thor-network --path=clusters/testing --token-auth

Notas de FluxCD

Actualización manual

Los ficheros de configuración son recargados cuando se cumple el tiempo de intervalo, pero es posible lanzar una orden de actualización inmediata de todos los ficheros.

flux reconcile kustomization flux-system --with-source

Si el cambio proviene de un Helm, recargar toda la configuración no siempre funcionará (además de ser lento). En estos casos, se debe actualizar directamente el que toque.

Este ejemplo es para actualizar un OCIRepository de un namespace concreto:

flux reconcile source oci <oci_name> -n <namespace>

Notificaciones con Slack

FluxCD permite enviar notificaciones a múltiples destinos, este proceso es para Slack.

Para utilizar las notificaciones, se requiere configurar una nueva App y el Canal al que estará asignado.

  1. Crear el canal desde la App
  2. Ir a la web y clicar en New App
  3. Seleccionar From scratch
  4. Elegir App Name + Workspace
  5. Ir a Incoming Webhooks
    1. Encender Activate Incoming Webhooks
    2. Añadir New Webhooks to Workspace y escoger el canal
    3. Copiar la URL para el Provider
  6. Ir a OAuth & Permissions, en Bot Token Scopes
    1. Añadir el OAuth Scope y rellenar chat:write
  7. Ir a OAuth & Permissions
    1. Utilizar el Bot User OAuth Token en el notification-provider-secret.yaml (explicado en Nivel 1)
    2. Clicar en Reinstall to Workspace y elegir nuevamente el workspace

Notas rápidas

Un pequeño resumen sobre la configuración de FluxCD:

  • Todas las carpetas tienen el kustomization.yaml. Este es quien apunta al resto de ficheros (o carpetas) y se encarga de sobrescribir al resto. Todas las carpetas tienen su fichero.
  • El fichero kustomization.yaml puede modificar valores (con patches) directamente, o apuntar a un fichero yaml que sobrescribe al original.
  • Para simplificar, mis ficheros tienen todos los parámetros, incluido los que cambian dependiendo del entorno. Estos se ven fácilmente ya que tienen replace-me como valor.
  • Los datos sensibles están en secrets encriptados para cada entorno.
  • Todos los servicios provienen de Helm charts.
  • Cada servicio define el kustomization.yaml y namespace. el repositorio se define en repository con OCIRepository siempre que esté disponible, HelmRepository en caso contrario. Los charts se configuran en los release.
info

Los OCIRepository indican la versión, mientras que los HelmRepository dejan esto para el HelmRelease.

kustomization.yaml

Este es un ejemplo típico del fichero principal. Indica el resto de los ficheros (si no se indican, no se usan aunque existan) y añade el namespace a todos los ficheros, lo que ahorra problemas.

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: cert-manager-system
resources:
- namespace.yaml
- repository.yaml
- release.yaml
precaución

Aunque este fichero indique un namespace, se debe crear su yaml para que sea creado. Además, si se llaman diferentes, el namespace cambiará su nombre según el de este fichero.

repository.yaml

Los repositorios se pueden recuperar de múltiples lugares, pero siempre intento utilizar los OCI (que permiten indicar la versión), si no es posible, el HelmRepository (que deja la versión a ser definida en el release).

En general son similares. Indican un nombre (que es utilizado por el release), el tiempo para buscar por actualizaciones y la URL (ojo con el tipo de URL).

OCIRepository
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
name: kube-prometheus-stack
spec:
interval: 24h
url: oci://ghcr.io/prometheus-community/charts/kube-prometheus-stack
ref:
semver: "61.x.x"
HelmRepository
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: cert-manager
spec:
interval: 24h
url: https://charts.jetstack.io
Versión

Los Helm charts definen las versiones según semver. Estos ficheros actualizan automáticamente al detectar versiones superiores cuando esta versión no se indica (siempre actualiza a la última) o aparece una superior dentro de la limitación indicada. Para ello, cualquier valor de versión con un x o no definida, se considera que permite cualquier actualización.

peligro

Ojo con las actualizaciones automáticas, ya que puede recuperar una versión incompatible con la anterior. Semver funciona con x.y.z, donde x significa que el cambio es incompatible con el anterior (requiere un proceso manual), pero mucha gente no sigue esta estándar. Una actualización de y o z puede incorporar incompatibilidades que bloqueen el servicio. Nada se puede hacer contra ello ... 🔥

release.yaml

Los HelmRelease tienen una base similar al resto. Indican el chart (OCI o Helm), el intervalo para detectar cambios en ese fichero puede tener datos específicos (como la gestión de CRDs que Helm no dispone) o los reintentos y los values a definir.

HelmRelease con OCIRepository
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: kube-prom
spec:
chartRef:
kind: OCIRepository
name: kube-prometheus-stack
install:
crds: Create
upgrade:
crds: CreateReplace
driftDetection:
mode: enabled
ignore:
# Ignore "validated" annotation which is not inserted during install
- paths: [ "/metadata/annotations/prometheus-operator-validated" ]
target:
kind: PrometheusRule
interval: 24h
values:
prometheus:
prometheusSpec:
retention: replace-me
retentionSize: replace-me
storageSpec:
volumeClaimTemplate:
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: replace-me
resources:
requests:
cpu: 100m
memory: 768Mi
limits:
cpu: 500m
memory: 1Gi
alertmanager:
enabled: false
grafana:
enabled: false
HelmRelease con HelmRepository
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: cert-manager
spec:
chart:
spec:
chart: cert-manager
version: '1.*.*'
sourceRef:
kind: HelmRepository
name: cert-manager
interval: 24h
install:
remediation:
retries: 1
values:
installCRDs: true