Saltar a contenido

Instalar

Esta guía instala kubelatch en un cluster de gestión con su chart de Helm, con Postgres, TLS, la clave de cifrado y el primer administrador.

Cada versión se prueba repitiendo estos pasos en un cluster kind desechable, con la base de datos en CloudNativePG.

Public preview

kubelatch está en public preview. Funciona, pero le faltan piezas, que están en el roadmap, y todavía no hay compromiso de estabilidad entre versiones; solo se da soporte a la última.

Antes de empezar

Pieza Detalle
Cluster de gestión Kubernetes 1.30 o superior, con kubectl de administrador. Puede ser uno de los clusters que kubelatch va a gestionar.
Helm 3.8 o superior (charts OCI).
Postgres El tuyo, versión 17, con la que se prueba kubelatch (RDS, Cloud SQL, Azure Database…), con un rol que pueda crear tablas en la base de datos: kubelatch crea el esquema al arrancar. O uno que crea el chart con el operador CloudNativePG: Base de datos con CloudNativePG.
Nombre DNS y certificado Un nombre público o interno (kubelatch.example.com) y un certificado para él: cert-manager (recomendado) o uno propio.
Red Los pods de kubelatch deben llegar por https al API server de cada cluster gestionado. Las personas y los runners de CI deben llegar a kubelatch por https.

Todos los comandos de esta guía usan dos variables: el kubeconfig del cluster de gestión y la versión que se instala. Defínelas antes de empezar:

export MGMT_KUBECONFIG=<ruta-al-kubeconfig-del-cluster-de-gestión>
export KUBELATCH_VERSION=<versión>   # sin la v: 1.2.3 (https://github.com/picaportelabs/kubelatch/releases)

Usa siempre un kubeconfig explícito

En una máquina con varios clusters, un kubectl o un helm sin --kubeconfig puede aplicar cambios en el cluster equivocado. Por eso todos los comandos de esta guía llevan --kubeconfig "$MGMT_KUBECONFIG".

Qué instala el chart

El chart es oci://ghcr.io/picaportelabs/charts/kubelatch. Su versión es la de kubelatch, e instala la imagen ghcr.io/picaportelabs/kubelatch de esa misma versión. Desde la release posterior a la 0.43.0, los dos van firmados: Verificar una release. En el namespace de la release crea:

  • Una ServiceAccount sin permisos y sin token montado: kubelatch no habla con el API server del cluster de gestión con ella.
  • Un Deployment de una réplica: maxSurge: 1, maxUnavailable: 0, terminationGracePeriodSeconds: 60, usuario no root, sistema de ficheros de solo lectura y sin capabilities.
  • El ConfigMap kubelatch-config, a partir de los values config. Los pods llevan su hash: cambiarlo redespliega.
  • Un Service LoadBalancer en el puerto 443 con externalTrafficPolicy: Local.
  • Una NetworkPolicy que solo deja entrar tráfico al puerto de kubelatch.

El pod monta, de solo lectura, el ConfigMap kube-root-ca.crt que Kubernetes publica en cada namespace: la CA del cluster donde se ejecuta kubelatch, para que enrolar ese cluster con tu identidad no pida pegar nada. No da ningún permiso, y el pod sigue sin token de ServiceAccount. clusterCA.mount: false lo apaga.

Según los values, crea además un Ingress, un Certificate de cert-manager, un PodDisruptionBudget, la clave de cifrado generada, una base de datos de CloudNativePG y la política de endurecimiento. Todos los values están en Values de Helm, y helm show values oci://ghcr.io/picaportelabs/charts/kubelatch --version "$KUBELATCH_VERSION" los muestra con sus comentarios.

Por defecto no crea el namespace, y nunca crea los Secrets que traes tú: la clave de cifrado, DATABASE_URL y el certificado.

Instalar paso a paso

  1. Crea el namespace con el nivel restricted de Pod Security: kubelatch funciona con él, y en este namespace no debe haber nada más.

    kubectl --kubeconfig "$MGMT_KUBECONFIG" create namespace kubelatch
    kubectl --kubeconfig "$MGMT_KUBECONFIG" label namespace kubelatch pod-security.kubernetes.io/enforce=restricted
    

    Con Helm 3.17 o superior, puede crearlo el chart: namespace.create: true en tus values y --create-namespace --take-ownership en el paso 5. El namespace lleva entonces la etiqueta y se queda tras helm uninstall. Con un Helm anterior esa opción no funciona: Helm necesita el namespace para guardar la release antes de crear nada, y sin --take-ownership se niega a adoptar el que acaba de crear --create-namespace.

  2. Crea los secretos. El chart carga entero el Secret kubelatch-secrets (secrets.existingSecret):

    kubectl --kubeconfig "$MGMT_KUBECONFIG" -n kubelatch create secret generic kubelatch-secrets \
      --from-literal=DATABASE_URL='postgres://kubelatch:<contraseña>@<host>:5432/kubelatch?sslmode=require' \
      --from-literal=KUBELATCH_ENCRYPTION_KEY="$(head -c32 /dev/urandom | base64)"
    

    sslmode=require cifra la conexión pero no comprueba el certificado del servidor. Usa sslmode=verify-full, que además lo comprueba junto con el nombre del host. Si el certificado es de una CA pública, no hace falta más. Con una CA privada (RDS, Cloud SQL…), pon la CA en un Secret, móntala con extraVolumes y extraVolumeMounts en tus values (paso 4) y nómbrala con sslrootcert en DATABASE_URL:

    kubectl --kubeconfig "$MGMT_KUBECONFIG" -n kubelatch create secret generic kubelatch-pg-ca --from-file=ca.crt=<bundle-de-la-ca>.pem
    
    extraVolumes:
      - name: pg-ca
        secret:
          secretName: kubelatch-pg-ca
    extraVolumeMounts:
      - name: pg-ca
        mountPath: /etc/kubelatch/pg-ca
        readOnly: true
    
    DATABASE_URL=postgres://kubelatch:<contraseña>@<host>:5432/kubelatch?sslmode=verify-full&sslrootcert=/etc/kubelatch/pg-ca/ca.crt
    

    kubelatch vuelve a leer ese fichero cuando cambia, así que rotar la CA es solo actualizar el Secret, sin reiniciar. Funciona porque el Secret se monta como directorio: con subPath el kubelet nunca actualiza el fichero.

    Con la base de datos en CloudNativePG, deja fuera DATABASE_URL: sale del Secret del operador, y una en kubelatch-secrets se ignora. Guarda KUBELATCH_ENCRYPTION_KEY en tu gestor de secretos, aparte de las copias de Postgres: ver La clave de cifrado.

  3. Pon el certificado. Con un certificado propio:

    kubectl --kubeconfig "$MGMT_KUBECONFIG" -n kubelatch create secret tls kubelatch-tls --cert=tls.crt --key=tls.key
    

    Con cert-manager, el chart crea un Certificate que rellena kubelatch-tls (siguiente paso). Si tienes que pasar por un Ingress, lee antes Exponer kubelatch.

  4. Escribe tus values en un values.yaml. Como mínimo config.baseURL: la URL pública exacta, sin barra final. Todos los values, con su valor por defecto, están en Values de Helm, y qué hace cada variable de kubelatch, en Configuración. No actives todavía el login con GitHub ni con OIDC: eso viene después, siguiendo Login con GitHub o Login con OIDC (SSO). El repositorio público tiene dos ejemplos completos, con el LoadBalancer L4 y con un Ingress, en examples/helm.

    config:
      baseURL: https://kubelatch.example.com
    # Con cert-manager en vez de tu propio Secret kubelatch-tls:
    # tls:
    #   certManager:
    #     enabled: true
    #     issuerRef: {name: letsencrypt, kind: ClusterIssuer}
    # Con la base de datos en CloudNativePG (instala antes el operador):
    # postgres:
    #   mode: cnpg
    
  5. Instala el chart. Mantén el nombre de release kubelatch: los recursos se llaman entonces kubelatch, como en esta documentación (con otro nombre son <release>-kubelatch).

    helm --kubeconfig "$MGMT_KUBECONFIG" install kubelatch oci://ghcr.io/picaportelabs/charts/kubelatch \
      --version "$KUBELATCH_VERSION" -n kubelatch -f values.yaml
    

    Si falta un value o no encaja (sin config.baseURL, un Ingress sin host, postgres.mode: cnpg sin el operador…), helm install falla con un mensaje que lo nombra y no instala nada.

  6. Comprueba el arranque.

    kubectl --kubeconfig "$MGMT_KUBECONFIG" -n kubelatch rollout status deploy/kubelatch
    kubectl --kubeconfig "$MGMT_KUBECONFIG" -n kubelatch logs deploy/kubelatch | head
    curl https://kubelatch.example.com/readyz
    

    El log muestra una línea listening con version, tls y audit_retention. /readyz responde ok. Con CloudNativePG, el pod espera a los Secrets del operador y se reinicia hasta que la base de datos está lista: espera antes a la base de datos con kubectl --kubeconfig "$MGMT_KUBECONFIG" -n kubelatch wait --for=condition=Ready clusters.postgresql.cnpg.io/kubelatch-db --timeout=5m.

Las sondas funcionan así: el startupProbe da hasta dos minutos a las migraciones. /readyz hace ping a Postgres. /healthz solo comprueba el proceso: una caída de Postgres no reinicia el pod ni corta las sesiones abiertas, y el proxy responde 503 mientras tanto.

La clave de cifrado

KUBELATCH_ENCRYPTION_KEY cifra los tokens de las ServiceAccounts de cada cluster y firma las sesiones. No está en Postgres: si la pierdes, una base de datos restaurada no puede usar los tokens de los clusters (Si se pierde o se cambia la clave de cifrado). El chart la toma de uno de dos sitios:

Opción Cómo Cuándo
Tu Secret (por defecto) KUBELATCH_ENCRYPTION_KEY en kubelatch-secrets, como en el paso 2. Siempre vale. La generas tú y la guardas en tu gestor de secretos.
Generada por el chart secrets.generateEncryptionKey: true. La primera instalación la genera en el Secret kubelatch-encryption-key (clave encryption-key); cada actualización la reutiliza, y helm uninstall la deja en su sitio. Solo en instalaciones nuevas, cuando nadie deba manejar la clave, y solo con helm install y helm upgrade contra el cluster. Cópiala a tu gestor de secretos nada más instalar.

Para copiar la clave generada:

kubectl --kubeconfig "$MGMT_KUBECONFIG" -n kubelatch get secret kubelatch-encryption-key -o jsonpath='{.data.encryption-key}' | base64 -d

Cuando kubelatch ya funciona con una clave, conserva esa clave: activar después secrets.generateEncryptionKey daría al pod otra, y habría que rotar los tokens de cada cluster. Si kubelatch-secrets ya tiene KUBELATCH_ENCRYPTION_KEY, el chart se niega a generar una (Solución de problemas). Una clave que llega por extraEnvFrom no se detecta: la generada ocuparía su lugar en silencio, así que nunca combines las dos.

GitOps: trae tu propia clave

La clave generada depende de que Helm lea el cluster mientras renderiza. Las herramientas que renderizan el chart sin cluster (helm template, Argo CD o cualquier otra que renderice sin conexión) generarían una clave distinta en cada render. El Secret es inmutable, así que aplicar un render posterior da un error explícito en vez de rotar la clave. Tampoco se ejecutan las demás comprobaciones del chart sobre la clave (reutilizarla, rechazar un Secret kubelatch-encryption-key sin su campo, negarse a generar una cuando kubelatch-secrets ya tiene clave). Flux ejecuta Helm contra el cluster, así que allí la consulta funciona. Aun así, con cualquier herramienta GitOps, crea tú el Secret de la clave y usa secrets.existingSecret, nunca secrets.generateEncryptionKey: la clave vive entonces en tu gestor de secretos, no solo en el cluster. Los objetos que Helm conserva en helm uninstall (la base de datos, la clave generada, el namespace) llevan además el Delete=false,Prune=false de Argo CD; con otra herramienta, asegúrate de que nunca los poda.

Crear el primer administrador

La imagen no tiene shell: kubectl exec ejecuta el binario directamente, con las mismas variables que el servidor.

kubectl --kubeconfig "$MGMT_KUBECONFIG" -n kubelatch exec -it deploy/kubelatch -- /kubelatch-server user create admin --admin --display-name "Admin"

Pide la contraseña dos veces, sin eco (mínimo 12 caracteres). Sin terminal, pásala por la entrada estándar:

printf '%s' '<contraseña larga>' | kubectl --kubeconfig "$MGMT_KUBECONFIG" -n kubelatch exec -i deploy/kubelatch -- /kubelatch-server user create admin --admin --password-stdin

Si vas a activar el login con GitHub o con OIDC, marca ahora esta cuenta como cuenta break-glass (--break-glass). Lo explica Cuentas y recuperación.

Comprueba que funciona

  1. Abre https://kubelatch.example.com y entra como admin.
  2. Registra un cluster en «Clusters»: Registrar un cluster.
  3. Invita a las personas en «Usuarios» (Usuarios y bots) y concédeles niveles en «Permisos» (Conceder permisos).
  4. Cada persona entra en «Inicio» y emite su credencial: Obtener una credencial.

Exponer kubelatch: LoadBalancer L4 o Ingress

kubelatch lleva conexiones largas e inusuales: watch y logs -f de horas casi sin tráfico, exec/attach/port-forward como upgrades SPDY o WebSocket sobre HTTP/1.1, y apply de varios megabytes. Cualquier intermediario con timeouts o buffers de aplicación las rompe. Por eso la opción por defecto (exposure: loadBalancer) es L4.

LoadBalancer L4 (por defecto)

El balanceador pasa los bytes TLS tal cual y kubelatch termina el TLS en el puerto 8443 con el certificado kubelatch-tls. La IP de origen llega intacta (auditoría y límites por IP sin X-Forwarded-For), sin timeouts de aplicación ni límites de tamaño. Solo puede cortar una conexión el idle timeout TCP del balanceador. kubelatch envía keepalives TCP cada 15 s, que las tres nubes cuentan como actividad. Los ajustes de cada nube van en service.annotations:

Nube Idle timeout Qué hacer
AWS NLB 350 s, fijo Nada: los keepalives lo reinician. Para un NLB: service.beta.kubernetes.io/aws-load-balancer-type: "external", service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "instance" y service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing".
Azure Load Balancer 4 min por defecto service.beta.kubernetes.io/azure-load-balancer-tcp-idle-timeout: "30" (el máximo, en minutos).
GCP (Network LB passthrough) Sin timeout Nada.
On-prem (MetalLB, kube-vip) Sin timeout Nada. Sin balanceador, service.type: NodePort (y service.nodePort).

Con externalTrafficPolicy: Local solo pasan el health check los nodos con un pod de kubelatch. Es lo deseado. Deja config.trustedProxies vacío.

Ingress (alternativa)

Si tu organización obliga a pasar por un Ingress, usa exposure: ingress. Está pensado para ingress-nginx y no entra en las pruebas de cada versión: valídalo en tu cluster. El TLS termina en el controlador (el Secret kubelatch-tls, o cert-manager con tls.certManager.enabled) y kubelatch escucha HTTP en :8080, detrás de un Service ClusterIP.

exposure: ingress
config:
  baseURL: https://kubelatch.example.com
  trustedProxies: 10.244.0.0/16   # las IP de los pods del controlador: estréchalo para tu cluster
ingress:
  host: kubelatch.example.com
  className: nginx
networkPolicy:
  ingressControllerNamespace: ingress-nginx

El chart pone en el Ingress los tres ajustes que necesita el proxy:

  1. proxy-read-timeout y proxy-send-timeout a 3600. Los 60 s por defecto cortan watch, logs -f y un exec inactivo.
  2. proxy-body-size: "0". Con 1 MiB por defecto, un kubectl apply grande recibe 413 antes de llegar a kubelatch.
  3. Backend por HTTP/1.1 (backend-protocol: HTTP). Nunca HTTP/2 ni gRPC hacia el backend: los upgrades SPDY/WebSocket viajan por HTTP/1.1.

Además desactiva el buffering de respuestas (proxy-buffering: "off"). ingress.annotations añade las tuyas (por ejemplo cert-manager.io/cluster-issuer).

Tienes que ajustar esto:

  • ingress.host: el host de config.baseURL, exactamente. Si no, el chart se para: kubelatch solo conoce un host público.
  • El certificado: o tls.certManager.enabled (el Certificate del chart) o una anotación cert-manager.io/cluster-issuer (o cert-manager.io/issuer) en ingress.annotations, nunca las dos: con las dos, el chart se para.
  • config.trustedProxies: las IP de los pods del controlador y nada más (el rango más estrecho que las cubra, o el CIDR de los nodos si usa hostNetwork). El chart no instala sin él. El valor de ejemplo, 10.244.0.0/16, es el CIDR de pods de todo un cluster kind: no sirve para producción. Detalle en Proxies de confianza.
  • networkPolicy.ingressControllerNamespace: la NetworkPolicy del chart solo deja llegar al :8080 desde ese namespace (ingress-nginx por defecto). La salida no se restringe: kubelatch necesita Postgres, los clusters, y GitHub o tu proveedor de identidad. Deja networkPolicy.enabled activado: con exposure: ingress y sin NetworkPolicy, el chart se para (Solución de problemas).

La NetworkPolicy necesita un CNI que la aplique

Sin un CNI que aplique NetworkPolicy (Calico, Cilium…), se ignora en silencio. kubelatch queda entonces expuesto en HTTP plano a cualquier pod, y cualquiera dentro del rango de confianza puede falsificar la IP de la auditoría.

Otros controladores (Traefik, HAProxy, ALB de AWS) tienen ajustes equivalentes: ingress.className e ingress.annotations. Con cualquiera, ingress-nginx incluido, haz tres pruebas antes de darlo por bueno: kubectl get pods -w durante más de 2 minutos, un kubectl exec inactivo 5 minutos y un kubectl create -f de 2 MB.

kubelatch trata cualquier cabecera Authorization en /api como un token de la CLI, así que un proxy delante que la inyecte (basic-auth del Ingress, oauth2-proxy con pass-authorization-header) rompe la interfaz web: no la añadas en el host de kubelatch.

Proxies de confianza y la IP de origen

La auditoría, el registro del plano de control y los limitadores por IP (login, acceso desde la CLI, canje de CI y fallos de autenticación del proxy) usan la IP de la conexión TCP. Detrás de un proxy L7 esa IP es la del proxy. config.trustedProxies (KUBELATCH_TRUSTED_PROXIES) dice de qué redes se acepta X-Forwarded-For:

  • Se lee de derecha a izquierda. El primer salto que no es de confianza es el cliente; lo que un cliente falsifique queda a la izquierda y se ignora.
  • Solo admite CIDR (10.244.0.0/16, 10.0.0.1/32). Una IP suelta impide el arranque.
  • Un prefijo más amplio que /8 en IPv4 o /16 en IPv6 también impide el arranque.
  • Las direcciones IPv4 mapeadas en IPv6 (::ffff:10.0.0.1) se comparan sin mapear.

Nunca pongas un rango que alcance un cliente (por ejemplo 0.0.0.0/0): podría elegir su IP en la auditoría y esquivar los límites. Si falta detrás de un Ingress, todo aparece como venido del controlador y el límite de login se aplica a todos a la vez.

Certificados y CA privada

kubelatch lee el certificado de /etc/kubelatch/tls y recarga el par certificado-clave cuando cambia en disco: comprueba las fechas como mucho cada 10 s. Una renovación de cert-manager no necesita reiniciar. Si el par nuevo no es válido (clave de otro certificado, fichero a medias), sigue sirviendo el anterior y lo registra (tls: reload failed). Cada hora avisa si quedan menos de 30 días.

Con tls.certManager.enabled el Certificate del chart reproduce el recomendado: ECDSA P-256, 90 días, renovado 30 días antes, para el host de config.baseURL (tls.certManager.dnsNames, duration y renewBefore los cambian).

Con una CA pública (Let's Encrypt), los kubeconfigs no llevan CA: kubectl confía por el almacén del sistema. Con una CA privada, apunta config.kubeconfigCA (KUBELATCH_KUBECONFIG_CA) al bundle PEM y cada kubeconfig emitido llevará certificate-authority-data. Con un issuer privado de cert-manager, el Secret ya trae ca.crt: usa /etc/kubelatch/tls/ca.crt (solo con exposure: loadBalancer: detrás de un Ingress, kubelatch no monta el certificado).

El fichero debe ser regular, de 1 MiB como mucho, y contener solo bloques CERTIFICATE. Una clave pegada por error u otro bloque PEM impide el arranque.

kubelatch nunca incluye la CA de los clusters gestionados en los kubeconfigs. kubectl verifica a kubelatch, y kubelatch verifica cada API server con la CA con la que se enroló.

Varias réplicas

Dos réplicas mantienen kubelatch en servicio mientras se drena un nodo o se reinicia un pod. Repártelas en dos nodos con affinity, porque el chart no lo hace por su cuenta. Usa la forma preferred: con la forma required en un cluster de dos nodos, el pod adicional de un rollout (maxSurge: 1) no tiene dónde ir.

replicas: 2
affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          topologyKey: kubernetes.io/hostname
          labelSelector:
            matchLabels:
              app.kubernetes.io/name: kubelatch
              app.kubernetes.io/instance: kubelatch

Con replicas mayor que 1, el chart añade un PodDisruptionBudget con pdb.minAvailable (1). El balanceador o el Ingress no necesitan afinidad de sesión: cualquier réplica atiende cualquier petición.

Lo que comparten dos réplicas, porque está en Postgres o va firmado con la clave de cifrado que leen las dos:

  • Los inicios de sesión. Una sesión web, un kubelatch login en el navegador o con código de dispositivo, la entrada OAuth de un agente, un login con GitHub o un login con OIDC pueden empezar en una réplica y acabar en la otra. El bloqueo de cuenta tras 5 logins fallidos cuenta en las dos.
  • Las aprobaciones de agentes. La llamada de un agente puede esperar la decisión en una réplica mientras un administrador la aprueba en la otra. La llamada ve la decisión en menos de un segundo.
  • La revocación. Revocar una credencial o deshabilitar una cuenta corta sus exec, port-forward y watch abiertos en 10 s, en la réplica que los tenga.
  • Las tareas de fondo. Cada réplica ejecuta el reconciliador, la retención de la auditoría y la sincronización con GitHub, y un lock de Postgres impide que dos pasadas se solapen. «Reconciliar» responde que ya hay una pasada en marcha mientras la de la otra réplica tiene el cluster. Las migraciones se aplican en una réplica mientras la otra espera.

Lo que cada réplica guarda para sí:

Qué Con dos réplicas
Los límites de frecuencia: login, abrir enlaces de invitación y de restablecimiento, inicio de sesión de la CLI, canje de CI, OAuth de agentes, peticiones a /mcp por credencial, búsquedas de credenciales y el límite por IP de las peticiones que el proxy rechaza Cada réplica lleva su propia cuenta, así que el límite efectivo es el doble del configurado.
Una clave de licencia pegada o quitada en «Edición» La réplica que la recibe la aplica al momento, y la otra en su comprobación de cada hora. Hasta entonces, «Edición», los límites de Free y la ventana de la auditoría dependen de la réplica que responda. Pon la clave en KUBELATCH_LICENSE (el Secret), o reinicia los pods después de pegarla.
La última sincronización con GitHub que muestra «Usuarios» Cada réplica muestra su última pasada.
La vigilancia de los namespaces de cada cluster, que reconcilia un cluster cuando un namespace con permisos cambia su etiqueta de Pod Security o desaparece (La reconciliación) Cada réplica vigila todos los clusters y escribe en su log sus propios namespace watch down y namespace watch up. Un cluster que se enrola, recibe tokens nuevos o se borra en una réplica llega a la vigilancia de la otra en menos de un minuto.
El documento de descubrimiento del proveedor OIDC (se guarda un día) y sus claves (una hora), y su estado en «Usuarios» Cada réplica lee los suyos; «Usuarios» muestra el estado de la réplica que responde.
«Comprobar ahora» en «Edición» Solo comprueba la réplica que responde.

Lo que no está soportado:

  • Dos instalaciones sobre las mismas tablas. Dos releases necesitan cada una su base de datos, o al menos su esquema. Las réplicas de una misma release sí están soportadas.
  • Pasar un stream a la otra réplica. Un rollout o un pod reiniciado cortan los exec, port-forward y watch que tienen. kubectl vuelve a abrir un watch por su cuenta, en la réplica que sigue en servicio, pero un exec o un port-forward hay que volver a ejecutarlos: ver Qué pasa durante un rollout.

Siguientes pasos