Problema

Los entornos homelab cada vez incorporan servidores basados en ARM, pero la mayoría de los manifiestos, operadores y pipelines siguen diseñados para x86. Cuando se sustituye el hardware tradicional por nodos Ampere Q80‑30 o similares, aparecen fallos de compatibilidad: imágenes sin soporte arm64, CRDs que asumen arquitectura x86, y políticas de scheduling que no distribuyen correctamente los pods. El síntoma típico es que el clúster arranca, pero varios componentes (por ejemplo, ClickHouse Operator o Harbor) no se despliegan, o los pods quedan en Pending con mensajes de “no matching node”.

Causa

  1. Imágenes Docker mono‑arch – Muchos charts usan imágenes construidas solo para amd64. En ARM los contenedores no arrancan y el scheduler los marca como incompatibles.
  2. Operadores sin binaries arm64 – Algunos operadores descargan binarios precompilados para x86 en tiempo de instalación (por ejemplo, ciertos plugins de Barman o el controlador de NATS).
  3. Node affinity rígida – Manifiestos que fijan nodeSelector.kubernetes.io/arch: amd64 o que usan nodeAffinity con valores hard‑coded impiden que los pods se programen en los nuevos nodos.
  4. CNI/BGP configurado para interfaces específicas – En clusters mixtos, Cilium o Calico pueden estar atados a nombres de interfaces que solo existen en la placa base x86, provocando pérdida de conectividad.
  5. Herramientas de CI/CD que generan imágenes solo para la arquitectura del runner – Si el pipeline de BuildKit corre en un nodo x86, docker build producirá imágenes amd64 por defecto, lo que rompe la cadena de despliegue en ARM.

Solución

1. Preparar la capa de imágenes multi‑arch

  • Buildx: habilitar un builder con soporte para linux/amd64,linux/arm64.
  • Publicar ambas variantes bajo la misma etiqueta; Kubernetes elegirá la adecuada según el nodo.
  • Para charts que no ofrecen versiones multi‑arch, crear una fork mínima que reemplace la imagen con una construida localmente.

2. Auditar operadores y componentes críticos

  • Revisar cada CRD que instala binarios externos.
  • Si el proyecto no publica arm64, compilar desde fuente o buscar forks que lo hagan.
  • Documentar los binarios en un ConfigMap y montar como emptyDir para que el pod los encuentre.

3. Normalizar los selectors de arquitectura

  • Reemplazar nodeSelector.kubernetes.io/arch: amd64 por una variable de plantilla que se resuelva en tiempo de renderizado.
  • Usar nodeSelector.kubernetes.io/arch: {{ .Values.arch }} y definir arch: arm64 en values.yaml para los nodos ARM.
  • Añadir tolerations genéricas y dejar que el scheduler decida.

4. Ajustar la CNI

  • En Cilium, habilitar enable-ipv4=true y enable-ipv6=false (o viceversa) sin referir nombres de interfaces.
  • Configurar BGP con nodePort dinámico y evitar hard‑coding de eth0.
  • Verificar que el daemonset de Cilium se despliegue en ambos tipos de nodo; si no, añadir nodeSelector.kubernetes.io/arch: arm64 al daemonset.

5. Re‑configurar la pipeline de CI/CD

  • En los runners de GitHub Actions, GitLab CI o cualquier otro, instalar docker buildx y crear un builder con --platform linux/arm64.
  • Modificar los Dockerfile para usar ARG TARGETARCH y copiar binarios específicos según la arquitectura.
  • En los jobs de Flux, habilitar --kustomize-build-options=--load-restrictor=LoadRestrictionsNone para que los manifests se apliquen sin validar la arquitectura del contenedor.

6. Validar la migración con Flux y Talos

  • Talos no depende de la arquitectura del kernel; sin embargo, los machineconfig deben apuntar a la versión de firmware adecuada para Ampere.
  • Flux Operator permite declarar Kustomization por arquitectura; crear dos Kustomization distintas (una para amd64, otra para arm64) y usar dependsOn para asegurar el orden correcto.

Cuándo aplicar esta solución

  • Síntomas: pods en Pending con mensaje “no nodes match pod’s node selector”, errores de descarga de binarios durante la instalación de operadores, o fallos de docker pull por “manifest unknown”.
  • Entorno: clústeres bare‑metal o on‑premise con nodos mixtos x86/arm64, usando Talos, Flux y Cilium.
  • No aplica: clusters totalmente gestionados (EKS, GKE) donde la capa de infraestructura ya garantiza imágenes multi‑arch y los operadores están pre‑compilados para ambas arquitecturas.

Código

# Crear builder multi‑arch
docker buildx create --use --name multiarch-builder

# Habilitar plataformas
docker buildx inspect --bootstrap

# Construir y publicar ambas variantes
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t myrepo/app:latest \
  --push .

Verificación

  1. Ejecutar kubectl get nodes -o wide y confirmar que la columna ARCH muestra arm64 en los nuevos servidores.
  2. Desplegar un pod de prueba con imagen oficial multi‑arch (por ejemplo, nginx:alpine) y observar que el contenedor arranca sin errores.
  3. Revisar los logs de Cilium (kubectl -n kube-system logs ds/cilium) para asegurarse de que no haya mensajes de “interface not found”.
  4. Ejecutar flux get kustomizations y validar que los objetos de cada arquitectura aparecen con estado ready.
  5. En la pipeline de CI, observar que el artefacto Docker tiene ambas manifest entries (docker manifest inspect myrepo/app:latest).

Notas adicionales

  • Algunas imágenes de terceros (Harbor, Gitea) todavía no ofrecen builds oficiales para arm64. En esos casos, la alternativa más estable es usar la versión scratch y compilar desde código fuente.
  • Cuando se usan GPUs (RTX 3090) en nodos x86 y se combinan con nodos ARM, los pods que requieren CUDA deben tener nodeSelector.kubernetes.io/arch: amd64 explícito; de lo contrario el scheduler los enviará a nodos sin GPU y fallarán.
  • Mantener una rama de values.yaml por arquitectura simplifica la gestión de Helm; usar helmfile para orquestar la aplicación simultánea de ambas versiones.
  • Si el clúster está conectado a un NAS con NVMe‑of, verifica que el driver CSI sea compatible con arm64; la mayoría de los plugins de Longhorn ya lo son, pero versiones muy antiguas pueden requerir recompilación.

Con estos pasos el clúster homelab pasa de depender exclusivamente de Intel/AMD a aprovechar la densidad y eficiencia energética de los procesadores Ampere, sin perder funcionalidad en los operadores críticos ni en los pipelines de CI/CD.