Files
kubernetes/cephrook.md
2025-08-11 15:33:15 +02:00

14 KiB
Raw Blame History

Despliegue de RookCeph en clúster Kubernetes (SUSE) con discos locales (Bluestore)

Guía actualizada para un clúster Kubernetes (no K3s) en SUSE, con 4 nodos iniciales y futura ampliación a stretch con un quinto nodo árbitro. Discos locales (RAID/HBA), red de almacenamiento dedicada VLAN 30 192.168.3.0/24, y exposición del dashboard vía Ingress NGINX con TLS.


1) Requisitos previos

  • 4 nodos Kubernetes operativos: srvfkvm01, srvfkvm02, srvfkvm03, srvfkvm04 (control-plane o mixtos)
  • Cada nodo con 6 discos dedicados (~894GB) para Ceph
  • Acceso a Internet desde los nodos
  • Red de almacenamiento dedicada VLAN 30 192.168.3.0/24 (Ceph public/cluster)
  • kubectl configurado y permisos de admin

Nota de versiones: ejemplos probados con Rook 1.17.x y Ceph v19.x (Squid) o v18.x (Reef). En los manifiestos se usa una imagen estable.


2) Preparar discos en SUSE (solo discos de datos)

Instala utilidades necesarias en cada nodo:

sudo zypper -n install gdisk util-linux

Limpieza segura solo de sdb…sdg (ajusta si difiere):

set -euo pipefail
DISKS=(sdb sdc sdd sde sdf sdg)

for d in "${DISKS[@]}"; do
  echo ">>> /dev/$d"
  sudo sgdisk --zap-all /dev/$d || true       # limpia GPT/MBR
  sudo wipefs -a /dev/$d || true              # borra firmas FS/LVM
  sudo blkdiscard -f /dev/$d || \             # TRIM (si soporta)
    sudo dd if=/dev/zero of=/dev/$d bs=1M count=10 oflag=direct,dsync
done

Obtén las rutas persistentes byid para cada disco (en cada nodo):

for d in sdb sdc sdd sde sdf sdg; do
  echo "=== $HOSTNAME -> $d ==="
  ls -l /dev/disk/by-id/ | awk -v d="$d" '$NF ~ ("/" d "$") {print "/dev/disk/by-id/"$9}'
done

Usa siempre /dev/disk/by-id/... en los manifiestos (campo fullpath:) para evitar cambios de letra.


3) Etiquetado de nodos por site

Vamos a distribuir por zonas lógicas desde el inicio (A/B). El árbitro llegará después.

# SITE A
kubectl label node srvfkvm01 topology.kubernetes.io/zone=site-a --overwrite
kubectl label node srvfkvm02 topology.kubernetes.io/zone=site-a --overwrite

# SITE B
kubectl label node srvfkvm03 topology.kubernetes.io/zone=site-b --overwrite
kubectl label node srvfkvm04 topology.kubernetes.io/zone=site-b --overwrite

Cuando exista el nodo árbitro, se etiquetará como topology.kubernetes.io/zone=arbiter.


4) Instalar Rook (CRDs, comunes y operador)

kubectl create namespace rook-ceph || true

# Clonar repo oficial (opcional para tener toolbox/ejemplos)
git clone https://github.com/rook/rook.git
cd rook/deploy/examples

kubectl apply -f crds.yaml -f common.yaml -f operator.yaml

Comprueba el operador:

kubectl -n rook-ceph get pods | grep operator

5) CephCluster 4 nodos, discos byid, red de storage (VLAN 30)

Archivo cluster/ceph-cluster.yaml:

apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
  name: rook-ceph
  namespace: rook-ceph
spec:
  cephVersion:
    image: quay.io/ceph/ceph:v19.2.3   # estable (puedes usar v18.2.x si prefieres)
  dataDirHostPath: /var/lib/rook

  # Red: usamos hostNetworking y restringimos a VLAN de storage
  network:
    provider: host
    addressRanges:
      public:
        - "192.168.3.0/24"
      cluster:
        - "192.168.3.0/24"

  mon:
    count: 3
    allowMultiplePerNode: false

  dashboard:
    enabled: true

  # No queremos OSDs en el futuro nodo árbitro
  placement:
    osd:
      nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
          - matchExpressions:
            - key: topology.kubernetes.io/zone
              operator: In
              values: ["site-a", "site-b"]

  storage:
    useAllNodes: false
    useAllDevices: false
    nodes:
      - name: srvfkvm01
        devices:
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d5bb177a1716
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d5dc196bd3a7
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d5f81b10f7ef
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d6151cca8afd
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d62f1e5e9699
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d64f204b2405
      - name: srvfkvm02
        devices:
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030127eef88828273
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030127f879197de32
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030128081a076ba0c
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030128114a93e33b9
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94300301281a7b1fc151a
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030128235ba79d801
      - name: srvfkvm03
        devices:
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128aef3bb4e0ae
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b0e3d8bc1dc
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b2b3f446dd7
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b4440c2d027
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b5e42510c2a
          - fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b7d442e592c
      - name: srvfkvm04
        devices:
          - fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c003012887ebfca6752
          - fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c0030128896e360075f
          - fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c00301288ac038600d4
          - fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c00301288c62acb6efc
          - fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c00301288e456c6d441
          - fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c00301288f976534b4f

Aplicar y verificar:

kubectl apply -f cluster/ceph-cluster.yaml
kubectl -n rook-ceph get pods

Instala el toolbox para diagnósticos: kubectl -n rook-ceph apply -f rook/deploy/examples/toolbox.yaml


6) Pool RBD inicial (replica 4 sobre hosts) + StorageClass

Con 2 sites (A/B) y sin árbitro, no uses failureDomain: zone con size: 4 o las PGs quedarán undersized. Empezamos con host y, cuando activemos stretch, pasaremos a zone.

pools/ceph-blockpool-rbd.yaml:

apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: rbd-2x2-sites
  namespace: rook-ceph
spec:
  failureDomain: host
  replicated:
    size: 4

storageclasses/rbd.yaml:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ceph-rbd
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
  clusterID: rook-ceph
  pool: rbd-2x2-sites
  imageFormat: "2"
  imageFeatures: layering
  csi.storage.k8s.io/fstype: ext4
  csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
  csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
  csi.storage.k8s.io/controller-expand-secret-name: rook-csi-rbd-provisioner
  csi.storage.k8s.io/controller-expand-secret-namespace: rook-ceph
  csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
  csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
reclaimPolicy: Delete
allowVolumeExpansion: true
mountOptions: ["discard"]

Aplicar:

kubectl apply -f pools/ceph-blockpool-rbd.yaml
kubectl apply -f storageclasses/rbd.yaml
kubectl get sc

Si creaste el pool inicialmente con failureDomain: zone y ves active+undersized, crea y asigna una CRUSH rule a host:

kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- bash -lc '
set -e
ceph osd crush rule create-replicated rbd-4x-host default host || true
ceph osd pool set rbd-2x2-sites crush_rule rbd-4x-host
ceph osd pool get rbd-2x2-sites crush_rule
'

7) Marcar OSDs como SSD (si Ceph los detecta como HDD por el HBA)

# Desde el toolbox
kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- bash -lc '
for id in $(ceph osd ls); do ceph osd crush rm-device-class osd.$id || true; done
for id in $(ceph osd ls); do ceph osd crush set-device-class ssd osd.$id; done
ceph osd tree | egrep "zone|host|osd."
'

Si más adelante creas un pool soloSSD, añade spec.deviceClass: ssd al CephBlockPool.


8) Dashboard por Ingress (NGINX) en ceph.c2et.net

El dashboard del MGR escucha por defecto en HTTP 7000. Hacemos TLS en el Ingress (certmanager) y HTTP hacia el backend.

ingress/dashboard.yaml:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ceph-dashboard
  namespace: rook-ceph
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
spec:
  ingressClassName: nginx
  tls:
    - hosts: ["ceph.c2et.net"]
      secretName: ceph-dashboard-tls
  rules:
    - host: ceph.c2et.net
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: rook-ceph-mgr-dashboard
                port:
                  number: 7000

Credenciales:

# Usuario por defecto
admin

# Contraseña generada
kubectl -n rook-ceph get secret rook-ceph-dashboard-password -o jsonpath="{.data.password}" | base64 -d; echo

# Cambiar contraseña (ejemplo)
kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- bash -lc \
'echo -n "MiNuevaPass" | ceph dashboard ac-user-set-password admin -i -'

Si prefieres HTTPS 8443 también hacia el backend, habilita TLS en el dashboard de Ceph y cambia el Ingress a backend-protocol: "HTTPS" y puerto 8443 (y opcionalmente proxy-ssl-verify: "off").


9) Prueba rápida de PVC

tests/pvc-test.yaml:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-rbd
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 5Gi
  storageClassName: ceph-rbd

tests/pod-test.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: rbd-tester
spec:
  containers:
    - name: app
      image: busybox
      command: ["sh","-c","sleep 36000"]
      volumeMounts:
        - mountPath: /data
          name: vol
  volumes:
    - name: vol
      persistentVolumeClaim:
        claimName: test-rbd

Aplicar y verificar:

kubectl apply -f tests/pvc-test.yaml
kubectl apply -f tests/pod-test.yaml
kubectl exec -it rbd-tester -- sh -c 'df -h /data && dd if=/dev/zero of=/data/test.bin bs=1M count=100 && ls -lh /data'

10) Ampliación futura: modo Stretch con árbitro (2 sites + arbiter)

Objetivo: supervivencia a la caída completa de un site y distribución 2+2 de réplicas entre site-a y site-b.

  1. Añade el nodo árbitro y etiqueta:
kubectl label node <NODO_ARBITRO> topology.kubernetes.io/zone=arbiter --overwrite
  1. Actualiza el CephCluster a stretch (5 MON):
# parche del CephCluster (fragmento spec)
mon:
  count: 5
  allowMultiplePerNode: false
  stretchCluster:
    failureDomainLabel: topology.kubernetes.io/zone
    subFailureDomain: host
    zones:
      - name: arbiter
        arbiter: true
      - name: site-a
      - name: site-b

Mantén placement.osd restringido a site-a/site-b para no crear OSDs en el árbitro.

  1. (Opcional recomendado) Cambia el CephBlockPool para que el failure domain vuelva a zone con size: 4 (2 por zona). Si prefieres asegurar la regla, crea una CRUSH rule específica y asígnala al pool.
# Ejemplo: regla por zona
kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- bash -lc '
set -e
# Crea regla "rbd-4x-zone" (elige leaves de tipo zone)
ceph osd crush rule create-replicated rbd-4x-zone default zone || true
# Asigna la regla al pool y ajusta size
ceph osd pool set rbd-2x2-sites crush_rule rbd-4x-zone
ceph osd pool set rbd-2x2-sites size 4
ceph osd pool get rbd-2x2-sites crush_rule
'

Tras el cambio a zone, Ceph reubica PGs para cumplir 2+2 entre site-a y site-b. Hazlo en ventana si ya hay mucho dato.


11) Troubleshooting rápido

  • PGs active+undersized con pool size=4: ocurre si la regla CRUSH elige zone y solo hay 2 zonas (sin stretch). Solución: usa failureDomain: host o asigna una regla a host (sección 6) hasta activar stretch.
  • Ingress 503 al abrir el dashboard: el Service rook-ceph-mgr-dashboard usa puerto 7000 (HTTP). Ajusta Ingress a backend-protocol: "HTTP" y puerto 7000.
  • Cert TLS no emite: revisa ClusterIssuer, DNS público hacia el Ingress y que el solver HTTP01 use class: nginx. Evita redirecciones que interfieran /.well-known/acme-challenge/.

12) Apéndice Comandos útiles

Estado general:

kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph -s
kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph osd tree
kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph df

Ver pools y reglas:

kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph osd pool ls detail
kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph osd pool get rbd-2x2-sites crush_rule
kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph osd crush rule dump rbd-4x-host

Dashboard:

kubectl -n rook-ceph get secret rook-ceph-dashboard-password -o jsonpath="{.data.password}" | base64 -d; echo
kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- bash -lc 'echo -n "NuevaPass" | ceph dashboard ac-user-set-password admin -i -'

Resumen: despliegas RookCeph con red de almacenamiento dedicada, discos por byid, pool RBD size 4 sobre host para evitar PGs undersized sin árbitro, dashboard por Ingress (TLS en NGINX, backend HTTP:7000) y, cuando añadas el árbitro, pasas el clúster a stretch y el pool a failureDomain: zone con 2+2 por site.