Compare commits

..

20 Commits

Author SHA1 Message Date
0bbc20ca14 add repo 2025-09-04 00:13:39 +02:00
bb76fc67dc añadido dynu-updater 2025-09-01 17:18:13 +02:00
549fd4ca37 arreglando dynu 2025-09-01 17:15:49 +02:00
4c5b451308 añadido dynu uodater 2025-09-01 14:06:16 +02:00
bf2e26a8bf añadiendo maquinas virtuales 2025-08-31 13:44:21 +02:00
01cbdf2a1d arreglado apolo kubevirt y cosillas 2025-08-31 10:35:16 +02:00
0fc9bbb2c0 pulido de argos, coredns y guacamole 2025-08-31 08:42:35 +02:00
xguefer
a3500d6250 Actualizar readme.md 2025-08-30 23:08:49 +00:00
xguefer
f2f8a5cf5a Actualizar readme.md 2025-08-30 22:56:25 +00:00
xguefer
91737b6592 Actualizar readme.md 2025-08-30 22:28:53 +00:00
xguefer
b836c7afce Actualizar readme.md 2025-08-30 22:28:25 +00:00
9291e46fe9 renombrando carpeta 2025-08-31 00:27:06 +02:00
2331da8cf8 velero 2025-08-31 00:24:21 +02:00
2eff32d251 driver dell 2025-08-30 13:35:31 +02:00
55c23dda1d añadido manual de harbor 2025-08-30 00:06:05 +02:00
xguefer
8f7eee7cc1 Actualizar readme.md 2025-08-29 19:59:48 +00:00
xguefer
b6da6350ac Actualizar cluster_init.md 2025-08-29 19:58:34 +00:00
xguefer
d173a90b2e Actualizar readme.md 2025-08-29 15:44:31 +00:00
452b04aa5d limpieza de manifiesto ceph. gitea y wireguard funcionando 2025-08-29 17:43:07 +02:00
dad001ae56 reseteo completo del cluster. CEPH con arbitro 2025-08-29 17:36:52 +02:00
119 changed files with 1833 additions and 1433 deletions

View File

@@ -1,10 +0,0 @@
kubeadm join 192.168.0.20:6443 --token rvz86n.c8rdb9ygtikrwnub --discovery-token-ca-cert-hash sha256:f925653dfb3d2b4697395a272e0b07cf4eb16b7ae5a2cc3b33aeab1f36fe7d13 --control-plane --certificate-key eeab98651b2f07f6ce53649b2cca1bf3c449d4fe6270ec0645219cd8c6795ca7 --apiserver-advertise-address=192.168.4.2
sudo rm -rf /var/lib/kubelet/
sudo rm -rf /etc/kubernetes
sudo rm -rf /etc/cni/
sudo rm -rf /var/lib/etcd
sudo systemctl stop kubelet
sudo systemctl status kubelet
sudo kubeadm join 192.168.0.20:6443 --token rvz86n.c8rdb9ygtikrwnub --discovery-token-ca-cert-hash sha256:f925653dfb3d2b4697395a272e0b07cf4eb16b7ae5a2cc3b33aeab1f36fe7d13 --control-plane --certificate-key eeab98651b2f07f6ce53649b2cca1bf3c449d4fe6270ec0645219cd8c6795ca7 --apiserver-advertise-address=192.168.4.2

View File

@@ -20,10 +20,10 @@ spec:
app.kubernetes.io/component: app6 app.kubernetes.io/component: app6
spec: spec:
imagePullSecrets: imagePullSecrets:
- name: harbor-cred - name: harbor-cred-apolo
containers: containers:
- name: app6 - name: app6
image: harbor.c2et.com/xrf-ssl/xrf-app6:6.0 image: harbor.c2et.net/apolo/xrf-app6:6.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:
- name: tcp-app6 - name: tcp-app6

View File

@@ -20,10 +20,10 @@ spec:
app.kubernetes.io/component: colossus app.kubernetes.io/component: colossus
spec: spec:
imagePullSecrets: imagePullSecrets:
- name: harbor-cred - name: harbor-cred-apolo
containers: containers:
- name: colossus - name: colossus
image: harbor.c2et.com/xrf-ssl/xrf-webcolossus:6.0 image: harbor.c2et.net/apolo/xrf-webcolossus:6.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:
- name: http - name: http

View File

@@ -20,10 +20,10 @@ spec:
app.kubernetes.io/component: consumer app.kubernetes.io/component: consumer
spec: spec:
imagePullSecrets: imagePullSecrets:
- name: harbor-cred - name: harbor-cred-apolo
containers: containers:
- name: consumer - name: consumer
image: harbor.c2et.com/xrf-ssl/xrf-consumer:6.0 image: harbor.c2et.net/apolo/xrf-consumer:6.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
envFrom: envFrom:
- secretRef: - secretRef:

View File

@@ -20,10 +20,10 @@ spec:
app.kubernetes.io/component: drone app.kubernetes.io/component: drone
spec: spec:
imagePullSecrets: imagePullSecrets:
- name: harbor-cred - name: harbor-cred-apolo
containers: containers:
- name: drone - name: drone
image: harbor.c2et.com/xrf-ssl/xrf-drone:6.0 image: harbor.c2et.net/apolo/xrf-drone:6.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
# Si Drone necesita otras vars del backend, puedes añadir: # Si Drone necesita otras vars del backend, puedes añadir:
# envFrom: # envFrom:

View File

@@ -20,9 +20,7 @@ spec:
app.kubernetes.io/component: ejabberd app.kubernetes.io/component: ejabberd
spec: spec:
imagePullSecrets: imagePullSecrets:
- name: harbor-cred - name: harbor-cred-apolo
# >>> Asegura permisos/ownership en volúmenes
securityContext: securityContext:
runAsUser: 9000 runAsUser: 9000
runAsGroup: 9000 runAsGroup: 9000
@@ -62,7 +60,7 @@ spec:
containers: containers:
- name: ejabberd - name: ejabberd
image: harbor.c2et.com/xrf-ssl/xrf-ejabberd:6.0 image: harbor.c2et.net/apolo/xrf-ejabberd:6.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
envFrom: envFrom:
- secretRef: - secretRef:

View File

@@ -20,10 +20,10 @@ spec:
app.kubernetes.io/component: kurento app.kubernetes.io/component: kurento
spec: spec:
imagePullSecrets: imagePullSecrets:
- name: harbor-cred - name: harbor-cred-apolo
containers: containers:
- name: kurento-media-server - name: kurento-media-server
image: harbor.c2et.com/xrf-ssl/xrf-kurento-media-server:6.0 image: harbor.c2et.net/apolo/xrf-kurento-media-server:6.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
envFrom: envFrom:
- configMapRef: - configMapRef:

View File

@@ -20,10 +20,10 @@ spec:
app.kubernetes.io/component: kurento-api app.kubernetes.io/component: kurento-api
spec: spec:
imagePullSecrets: imagePullSecrets:
- name: harbor-cred - name: harbor-cred-apolo
containers: containers:
- name: kurento-api - name: kurento-api
image: harbor.c2et.com/xrf-ssl/xrf-kurento-api:6.0 image: harbor.c2et.net/apolo/xrf-kurento-api:6.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
envFrom: envFrom:
- configMapRef: - configMapRef:

View File

@@ -20,10 +20,10 @@ spec:
app.kubernetes.io/component: media app.kubernetes.io/component: media
spec: spec:
imagePullSecrets: imagePullSecrets:
- name: harbor-cred - name: harbor-cred-apolo
containers: containers:
- name: mediamtx - name: mediamtx
image: harbor.c2et.com/xrf-ssl/xrf-media-server:6.0 image: harbor.c2et.net/apolo/xrf-media-server:6.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
envFrom: envFrom:
- configMapRef: - configMapRef:

View File

@@ -20,10 +20,10 @@ spec:
app.kubernetes.io/component: nakama app.kubernetes.io/component: nakama
spec: spec:
imagePullSecrets: imagePullSecrets:
- name: harbor-cred - name: harbor-cred-apolo
containers: containers:
- name: nakama - name: nakama
image: harbor.c2et.com/xrf-ssl/xrf-nakama:6.0 image: harbor.c2et.net/apolo/xrf-nakama:6.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:
- name: http - name: http

View File

@@ -20,10 +20,10 @@ spec:
app.kubernetes.io/component: php app.kubernetes.io/component: php
spec: spec:
imagePullSecrets: imagePullSecrets:
- name: harbor-cred - name: harbor-cred-apolo
containers: containers:
- name: php-fpm - name: php-fpm
image: harbor.c2et.com/xrf-ssl/xrf-php:6.0 image: harbor.c2et.net/apolo/xrf-php:6.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:
- name: php-fpm - name: php-fpm

View File

@@ -20,11 +20,11 @@ spec:
app.kubernetes.io/component: portal app.kubernetes.io/component: portal
spec: spec:
imagePullSecrets: imagePullSecrets:
- name: harbor-cred - name: harbor-cred-apolo
containers: containers:
# Contenedor de la app (como venías) # Contenedor de la app (como venías)
- name: portal - name: portal
image: harbor.c2et.com/xrf-ssl/xrf-portal-https:6.0 image: harbor.c2et.net/apolo/xrf-portal-https:6.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:
- name: app - name: app

View File

@@ -20,7 +20,7 @@ spec:
app.kubernetes.io/component: postgres app.kubernetes.io/component: postgres
spec: spec:
imagePullSecrets: imagePullSecrets:
- name: harbor-cred - name: harbor-cred-apolo
securityContext: securityContext:
fsGroup: 999 fsGroup: 999
initContainers: initContainers:
@@ -40,7 +40,7 @@ spec:
runAsUser: 0 runAsUser: 0
containers: containers:
- name: postgres - name: postgres
image: harbor.c2et.com/xrf-ssl/xrf-db:6.0 image: harbor.c2et.net/apolo/xrf-db:6.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
securityContext: securityContext:
runAsUser: 999 runAsUser: 999

View File

@@ -20,10 +20,10 @@ spec:
app.kubernetes.io/component: rabbitmq app.kubernetes.io/component: rabbitmq
spec: spec:
imagePullSecrets: imagePullSecrets:
- name: harbor-cred - name: harbor-cred-apolo
containers: containers:
- name: rabbitmq - name: rabbitmq
image: harbor.c2et.com/xrf-ssl/xrf-rabbitmq:6.0 image: harbor.c2et.net/apolo/xrf-rabbitmq:6.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
envFrom: envFrom:
- secretRef: - secretRef:

View File

@@ -20,10 +20,10 @@ spec:
app.kubernetes.io/component: streamer app.kubernetes.io/component: streamer
spec: spec:
imagePullSecrets: imagePullSecrets:
- name: harbor-cred - name: harbor-cred-apolo
containers: containers:
- name: streamer - name: streamer
image: harbor.c2et.com/xrf-ssl/xrf-streamer-server:6.0 image: harbor.c2et.net/apolo/xrf-streamer-server:6.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
command: ["npm","start"] command: ["npm","start"]
envFrom: envFrom:

View File

@@ -20,10 +20,10 @@ spec:
app.kubernetes.io/component: web app.kubernetes.io/component: web
spec: spec:
imagePullSecrets: imagePullSecrets:
- name: harbor-cred - name: harbor-cred-apolo
containers: containers:
- name: nginx - name: nginx
image: harbor.c2et.com/xrf-ssl/xrf-web:6.0 image: harbor.c2et.net/apolo/xrf-web:6.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
envFrom: envFrom:
- configMapRef: - configMapRef:

View File

@@ -20,10 +20,10 @@ spec:
app.kubernetes.io/component: websocket app.kubernetes.io/component: websocket
spec: spec:
imagePullSecrets: imagePullSecrets:
- name: harbor-cred - name: harbor-cred-apolo
containers: containers:
- name: websocket - name: websocket
image: harbor.c2et.com/xrf-ssl/xrf-websocket:6.0 image: harbor.c2et.net/apolo/xrf-websocket:6.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:
- name: ws - name: ws

View File

@@ -18,7 +18,6 @@ resources:
- certs/certificate-meeting.yaml - certs/certificate-meeting.yaml
# ConfigMaps # ConfigMaps
- configmaps/configmap-coredns.yaml
- configmaps/configmap-ejabberd-inetrc.yaml - configmaps/configmap-ejabberd-inetrc.yaml
- configmaps/configmap-ejabberd.yaml - configmaps/configmap-ejabberd.yaml
- configmaps/configmap-kms-api.yaml - configmaps/configmap-kms-api.yaml
@@ -49,7 +48,6 @@ resources:
- deployments/deploy-app6.yaml - deployments/deploy-app6.yaml
- deployments/deploy-colossus.yaml - deployments/deploy-colossus.yaml
- deployments/deploy-consumer.yaml - deployments/deploy-consumer.yaml
- deployments/deploy-coredns.yaml
- deployments/deploy-drone.yaml - deployments/deploy-drone.yaml
- deployments/deploy-ejabberd.yaml - deployments/deploy-ejabberd.yaml
- deployments/deploy-kms.yaml - deployments/deploy-kms.yaml
@@ -68,7 +66,6 @@ resources:
- services/svc-aliases-compose.yaml - services/svc-aliases-compose.yaml
- services/svc-app6.yaml - services/svc-app6.yaml
- services/svc-colossus.yaml - services/svc-colossus.yaml
- services/svc-coredns.yaml
- services/svc-ejabberd.yaml - services/svc-ejabberd.yaml
- services/svc-kms.yaml - services/svc-kms.yaml
- services/svc-kurento-api.yaml - services/svc-kurento-api.yaml

View File

@@ -1,9 +1,9 @@
apiVersion: v1 apiVersion: v1
data: data:
.dockerconfigjson: eyJhdXRocyI6eyJoYXJib3IuYzJldC5jb20iOnsidXNlcm5hbWUiOiJ4YXZvciIsInBhc3N3b3JkIjoiTUBuYWJvMjAyNSIsImVtYWlsIjoibm8tcmVwbHlAYzJldC5jb20iLCJhdXRoIjoiZUdGMmIzSTZUVUJ1WVdKdk1qQXlOUT09In19fQ== .dockerconfigjson: eyJhdXRocyI6eyJoYXJib3IuYzJldC5uZXQiOnsidXNlcm5hbWUiOiJ4YXZvciIsInBhc3N3b3JkIjoiTUBuYWJvMjAyNSIsImVtYWlsIjoieGF2b3JAaG90bWFpbC5lcyIsImF1dGgiOiJlR0YyYjNJNlRVQnVZV0p2TWpBeU5RPT0ifX19
kind: Secret kind: Secret
metadata: metadata:
creationTimestamp: null creationTimestamp: null
name: harbor-cred name: harbor-cred
namespace: apolo namespace: guacamole
type: kubernetes.io/dockerconfigjson type: kubernetes.io/dockerconfigjson

View File

@@ -0,0 +1,9 @@
apiVersion: v1
data:
.dockerconfigjson: eyJhdXRocyI6eyJoYXJib3IuYzJldC5jb20iOnsidXNlcm5hbWUiOiJ4YXZvciIsInBhc3N3b3JkIjoiTUBuYWJvMjAyNSIsImVtYWlsIjoibm8tcmVwbHlAYzJldC5jb20iLCJhdXRoIjoiZUdGMmIzSTZUVUJ1WVdKdk1qQXlOUT09In19fQ==
kind: Secret
metadata:
creationTimestamp: null
name: harbor-cred
namespace: apolo
type: kubernetes.io/dockerconfigjson

View File

@@ -1,87 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: argos-panel-config
namespace: argos-core
data:
app.py: |
import os, sqlite3, time
from fastapi import FastAPI, HTTPException
from fastapi.responses import HTMLResponse
from minio import Minio
from urllib.parse import urlparse
DB="/data/argos.db"
mc=Minio(os.getenv("MINIO_ENDPOINT","s3.argos.interna"),
access_key=os.getenv("MINIO_ACCESS_KEY"),
secret_key=os.getenv("MINIO_SECRET_KEY"),
secure=os.getenv("MINIO_SECURE","false").lower()=="true")
app=FastAPI()
def rows(limit=100, camera=None, since=None):
q="SELECT id, ts, edge, camera, label, s3url, thumb_s3 FROM events"
cond=[]; args=[]
if camera: cond.append("camera=?"); args.append(camera)
if since: cond.append("ts>=?"); args.append(int(since))
if cond: q+=" WHERE "+ " AND ".join(cond)
q+=" ORDER BY ts DESC LIMIT ?"; args.append(limit)
con=sqlite3.connect(DB); cur=con.cursor()
cur.execute(q, tuple(args)); r=cur.fetchall(); con.close()
return r
@app.get("/api/events")
def api_events(limit:int=100, camera:str=None, since:int=None):
return [dict(id=i, ts=t, edge=e, camera=c, label=l or "", s3url=s, thumb=th or "")
for (i,t,e,c,l,s,th) in rows(limit,camera,since)]
@app.get("/api/url/{event_id}")
def presign(event_id: str, expires: int = 600):
con=sqlite3.connect(DB); cur=con.cursor()
cur.execute("SELECT s3url FROM events WHERE id=?", (event_id,))
row=cur.fetchone(); con.close()
if not row: raise HTTPException(404, "Not found")
s3url=row[0]; p=urlparse(s3url); b=p.netloc; k=p.path.lstrip("/")
return {"url": mc.presigned_get_object(b, k, expires=expires)}
@app.get("/", response_class=HTMLResponse)
def index():
return """
<!doctype html><meta charset="utf-8"><title>ARGOS Panel</title>
<style>body{font-family:system-ui;margin:1.5rem} .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}
.card{border:1px solid #ddd;border-radius:10px;padding:10px} img{width:100%;height:160px;object-fit:cover;border-radius:8px}
button{padding:.4rem .6rem;margin-right:.3rem}</style>
<h1>ARGOS Alarmas</h1>
<div class="grid" id="grid"></div>
<div style="margin-top:1rem"><video id="player" width="960" controls></video></div>
<script>
const fmt=t=>new Date(t*1000).toLocaleString();
async function load(){
const r=await fetch('/api/events?limit=100'); const data=await r.json();
const g=document.getElementById('grid'); g.innerHTML='';
for(const ev of data){
const d=document.createElement('div'); d.className='card';
const img = ev.thumb ? `<img src="${ev.thumb.replace('s3://','/api/url/THUMB?key=')}" alt="thumb">` : '';
d.innerHTML = `${img}<div><b>${ev.camera}</b> — ${fmt(ev.ts)}<br>${ev.label||''}</div>
<div style="margin-top:.4rem">
<button data-id="${ev.id}" data-action="clip">Ver clip</button>
<button data-path="${ev.camera}" data-action="live">En directo</button>
</div>`;
g.appendChild(d);
}
g.onclick=async (e)=>{
if(e.target.tagName!=='BUTTON') return;
const v=document.getElementById('player');
if(e.target.dataset.action==='clip'){
const id=e.target.dataset.id;
const j=await (await fetch('/api/url/'+id)).json();
v.src=j.url; v.play();
}else if(e.target.dataset.action==='live'){
const path=e.target.dataset.path;
// usa MediaMTX web player
window.open('http://mediamtx.argos.interna/?path='+encodeURIComponent(path),'_blank');
}
}
}
load(); setInterval(load,10000);
</script>
"""

View File

@@ -13,7 +13,7 @@ spec:
spec: spec:
containers: containers:
- name: mosquitto - name: mosquitto
image: eclipse-mosquitto:2 image: eclipse-mosquitto:latest
ports: ports:
- containerPort: 1883 - containerPort: 1883
volumeMounts: volumeMounts:

View File

@@ -13,7 +13,7 @@ spec:
spec: spec:
containers: containers:
- name: panel - name: panel
image: python:3.13.7-slim-bookworm image: harbor.c2et.net/library/python:3.13.7-slim-bookworm
command: ["/bin/sh","-c"] command: ["/bin/sh","-c"]
args: args:
- | - |

0
argos/kubectl Normal file
View File

View File

@@ -86,7 +86,7 @@ Este paso parte de una instalación limpia de openSUSE/SLES actualizada y con pe
```bash ```bash
sudo zypper refresh sudo zypper refresh
sudo zypper update sudo zypper update
sudo zypper install -y curl ca-certificates keepalived chrony sudo zypper install -y curl ca-certificates keepalived chrony yq jq open-iscsi yast2-iscsi-client multipath-tools gdisk util-linux helm tree git htop
``` ```
### b) Añade el repositorio oficial de Kubernetes ### b) Añade el repositorio oficial de Kubernetes

View File

@@ -33,6 +33,34 @@ data:
192.168.0.100 admin.powervault1.c2et.net 192.168.0.100 admin.powervault1.c2et.net
192.168.0.100 admin.powervault2.c2et.net 192.168.0.100 admin.powervault2.c2et.net
192.168.0.100 ceph.c2et.net 192.168.0.100 ceph.c2et.net
192.168.0.100 heimdall.c2et.net
# === dotcom ) ===
192.168.0.40 cockpit.c2et.com
192.168.0.40 git.c2et.com
192.168.0.40 harbor.c2et.com
192.168.0.40 wireguard.c2et.com
192.168.0.40 proxy.c2et.com
192.168.0.80 backend.apolo.c2et.com
192.168.0.80 portal.apolo.c2et.com
192.168.0.80 colossus.apolo.c2et.com
192.168.0.80 chat.apolo.c2et.com
192.168.0.80 muc.chat.apolo.c2et.com
192.168.0.81 streaming.apolo.c2et.com
192.168.0.81 meeting.apolo.c2et.com
# === dotnet ) ===
192.168.0.100 repo.c2et.net
192.168.0.100 git.c2et.net
192.168.0.100 wireguard.c2et.net
192.168.0.100 ceph.c2et.net
192.168.0.100 harbor.c2et.net
192.168.0.100 grafana.c2et.net
192.168.0.100 kubevirt.c2et.net
192.168.0.100 heimdall.c2et.net
192.168.0.100 argos.panel.c2et.net
192.168.0.100 vscode.c2et.net
fallthrough fallthrough
} }
forward . /etc/resolv.conf forward . /etc/resolv.conf

View File

@@ -8,3 +8,4 @@ resources:
- configmap-coredns.yaml - configmap-coredns.yaml
- deploy-coredns.yaml - deploy-coredns.yaml
- svc-coredns.yaml - svc-coredns.yaml
- svc-coredns-admin.yaml

View File

@@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
name: coredns-custom-admin
namespace: coredns
spec:
type: LoadBalancer
loadBalancerIP: 192.168.0.110
selector:
app: coredns-custom
ports:
- name: dns-udp
port: 53
targetPort: 53
protocol: UDP
- name: dns-tcp
port: 53
targetPort: 53
protocol: TCP

18
dynu-updater/cronjob.yaml Normal file
View File

@@ -0,0 +1,18 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: dynu-updater
namespace: dynu-updater
spec:
schedule: "*/5 * * * *"
jobTemplate:
spec:
template:
spec:
imagePullSecrets:
- name: harbor-regcred
containers:
- name: dynu-updater
image: harbor.c2et.net/c3s/dynu-updater-c3s:1.0
imagePullPolicy: Always
restartPolicy: OnFailure

View File

@@ -0,0 +1,3 @@
resources:
- namespace.yaml
- cronjob.yaml

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: dynu-updater

119
dynu-updater/readme.md Normal file
View File

@@ -0,0 +1,119 @@
# Dynu IP Updater en Kubernetes
Este proyecto despliega un cliente de Dynu personalizado como `CronJob` y `Job` en un clúster K3s/Kubernetes. Su función es mantener actualizada la dirección IP pública de un grupo de dominios gestionados en Dynu usando su API.
---
## 🚀 Características
* Imagen Docker ligera basada en Alpine.
* Actualiza la IP pública mediante peticiones HTTP a la API de Dynu.
* Guarda la IP anterior para evitar actualizaciones innecesarias.
* Ejecutado periódicamente mediante `CronJob` (cada 5 minutos).
* Puede ejecutarse manualmente mediante un `Job`.
* Los logs de ejecución se almacenan y pueden consultarse con `kubectl logs`.
---
## 🚜 Estructura de archivos
```
.
k8s-dynu-updater/
├── cronjob.yaml # CronJob de Kubernetes
├── job-manual.yaml # Job manual para pruebas
├── kustomization.yaml # Kustomize para despliegue
├── namespace.yaml # Namespace aislado para el updater
```
La imagen Docker utilizada se crea con el script `update.sh` incrustado, que:
1. Detecta la IP pública actual.
2. Comprueba si ha cambiado desde la última ejecución.
3. Llama a la API de Dynu con usuario, grupo y contraseña hash MD5.
---
## 📂 Despliegue
1. Aplicar los manifiestos:
```bash
cd k8s-dynu-updater
kubectl apply -k .
```
2. Comprobar el estado:
```bash
kubectl get pods -n dynu-updater
```
---
## ✅ Ejecución manual
Para probar el script sin esperar al cron:
```bash
kubectl apply -f job-manual.yaml
kubectl logs -n dynu-updater job/dynu-updater-manual
```
---
## ⚖️ Configuración del script
El script embebido en la imagen Docker:
```bash
USERNAME="xavor"
PASSWORD="M@nabo2025"
GROUP="Trabajo"
```
> La contraseña se convierte a hash MD5 antes de enviarla.
---
## 🚨 Seguridad
* La contraseña se envía como hash MD5.
* Se recomienda usar un "IP Update Password" diferente del de cuenta.
* Puedes montar `Secret` en Kubernetes para no incluir credenciales directamente en la imagen.
---
## 🔍 Logs en Dynu
Dynu registra las actualizaciones entrantes. Puedes ver líneas como:
```
/nic/update?username=xavor&group=manabovalencia&myip=62.15.155.254&myipv6=no&password=***** Good
```
Esto confirma que el pod ha funcionado correctamente.
---
## 🔍 Referencias
* Dynu IP Update Protocol: [https://www.dynu.com/DynamicDNS/IP-Update-Protocol](https://www.dynu.com/DynamicDNS/IP-Update-Protocol)
* API: `https://api.dynu.com/nic/update`
* Cliente basado en `curl` y `cron` en Alpine Linux
---
## 📅 Mantenimiento
* Se puede adaptar a otros grupos (por ejemplo: `ManaboTorrevieja`).
* Si se quiere logs persistentes, se puede montar un volumen.
* Si se quiere gestionar con ArgoCD, agregarlo como `Application`.
---
## 📄 Autor
Xavor (2025)
Este cliente se ha probado en K3s y Dynu, actualizando correctamente el grupo `ManaboValencia`.

View File

@@ -33,3 +33,43 @@ data:
proxy_ssl_verify off; proxy_ssl_verify off;
} }
} }
gitdotcom.conf: |
server {
listen 3001;
server_name git.c2et.com;
location / {
proxy_pass https://192.168.0.40;
proxy_ssl_verify off;
}
}
harbordotcom.conf: |
server {
listen 85;
server_name harbor.c2et.com;
location / {
proxy_pass https://192.168.0.40;
proxy_ssl_verify off;
}
}
wireguarddotcom.conf: |
server {
listen 51821;
server_name wireguard.c2et.com;
location / {
proxy_pass https://192.168.0.40;
proxy_ssl_verify off;
}
}
cockpitdotcom.conf: |
server {
listen 9090;
server_name cockpit.c2et.com;
location / {
proxy_pass https://192.168.0.40;
proxy_ssl_verify off;
}
}

37
external/configmaps/configmap.yaml.save vendored Normal file
View File

@@ -0,0 +1,37 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-router-config
namespace: external
data:
router.conf: |
server {
listen 80 default_server;
server_name admin.firewall.c2et.net;
location / {
proxy_pass https://192.168.0.1;
proxy_ssl_verify off;
}
}
powervault1.conf: |
server {
listen 80;
server_name admin.powervault1.c2et.net;
location / {
proxy_pass https://192.168.0.71;
proxy_ssl_verify off;
}
}
powervault2.conf: |
server {
listen 80;
server_name admin.powervault2.c2et.net;
location / {
proxy_pass https://192.168.0.74;
proxy_ssl_verify off;
}
}

27
external/ingress/cockpitdotcom.yaml vendored Normal file
View File

@@ -0,0 +1,27 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: cockpitdotcom-ingress
namespace: external
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- cockpit.c2et.com
secretName: cockpitdotcom-tls
rules:
- host: cockpit.c2et.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: external-router-svc
port:
number: 80

27
external/ingress/gitdotcom.yaml vendored Normal file
View File

@@ -0,0 +1,27 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gitdotcom-ingress
namespace: external
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- git.c2et.com
secretName: gitdotcom-tls
rules:
- host: git.c2et.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: external-router-svc
port:
number: 80

27
external/ingress/harbordotcom.yaml vendored Normal file
View File

@@ -0,0 +1,27 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: harbordotcom-ingress
namespace: external
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- harbor.c2et.com
secretName: harbordotcom-tls
rules:
- host: harbor.c2et.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: external-router-svc
port:
number: 80

27
external/ingress/wireguarddotcom.yaml vendored Normal file
View File

@@ -0,0 +1,27 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wireguarddotcom-ingress
namespace: external
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- wireguard.c2et.com
secretName: wireguarddotcom-tls
rules:
- host: wireguard.c2et.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: external-router-svc
port:
number: 80

View File

@@ -15,7 +15,7 @@ spec:
spec: spec:
containers: containers:
- name: mysql - name: mysql
image: mysql:8 image: mysql:latest
env: env:
- name: MYSQL_ROOT_PASSWORD - name: MYSQL_ROOT_PASSWORD
value: gitea123 value: gitea123

View File

@@ -32,7 +32,7 @@ spec:
- name: GITEA__database__USER - name: GITEA__database__USER
value: "gitea" value: "gitea"
- name: GITEA__database__PASSWD - name: GITEA__database__PASSWD
value: "gitea123" value: "gitea"
volumeMounts: volumeMounts:
- name: gitea-data - name: gitea-data
mountPath: /data mountPath: /data

View File

@@ -1,7 +1,5 @@
resources: resources:
- namespace.yaml - namespace.yaml
- pvc/gitea-data.yaml
- pvc/gitea-db.yaml
- deployments/gitea.yaml - deployments/gitea.yaml
- deployments/gitea-db.yaml - deployments/gitea-db.yaml
- services/gitea.yaml - services/gitea.yaml

View File

@@ -4,11 +4,9 @@ metadata:
name: gitea name: gitea
namespace: gitea namespace: gitea
spec: spec:
type: NodePort
selector: selector:
app: gitea app: gitea
ports: ports:
- name: http - name: http
port: 3000 port: 3000
targetPort: 3000 targetPort: 3000
nodePort: 30300

28
grafana/kps-values.yaml Normal file
View File

@@ -0,0 +1,28 @@
grafana:
enabled: true
service:
type: ClusterIP
ingress:
enabled: true
ingressClassName: nginx # <- tu IngressClass
hosts:
- grafana.c2et.net
tls:
- secretName: grafana-tls
hosts:
- grafana.c2et.net
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod # o el que uses
adminPassword: "Pozuelo12345" # vacío = autogenera; o pon tu contraseña si quieres
# puedes forzar la password así:
# admin:
# existingSecret: grafana-admin
# userKey: admin-user
# passwordKey: admin-password
prometheus:
ingress:
enabled: false # <- mantenlo interno (recomendado)
alertmanager:
ingress:
enabled: false # <- interno

View File

@@ -29,6 +29,7 @@ spec:
volumeMounts: volumeMounts:
- name: mysql-storage - name: mysql-storage
mountPath: /var/lib/mysql mountPath: /var/lib/mysql
subPath: data
volumes: volumes:
- name: mysql-storage - name: mysql-storage
persistentVolumeClaim: persistentVolumeClaim:

27
harbor/readme.md Normal file
View File

@@ -0,0 +1,27 @@
## Instalacion de Harbor
### Fase 1: Despliegue con Ingress
```bash
helm repo add harbor https://helm.goharbor.io
helm repo update
helm install harbor harbor/harbor \
--namespace harbor --create-namespace \
-f values.yaml
```
> Una vez listo, podrás acceder a:
>
> **[https://harbor.c2et.net](https://harbor.c2et.net)**
>
> Usuario: `admin`
> Contraseña: la definida en `harborAdminPassword` (p.ej. `Harbor12345`)
```bash
docker login harbor.c2et.net
```

View File

@@ -4,7 +4,7 @@ expose:
enabled: true enabled: true
certSource: auto certSource: auto
ingress: ingress:
ingressClassName: nginx className: nginx
annotations: annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod" cert-manager.io/cluster-issuer: "letsencrypt-prod"
hosts: hosts:

View File

@@ -6,4 +6,5 @@ resources:
- configmap/configmap.yaml - configmap/configmap.yaml
- deployments/deployment.yaml - deployments/deployment.yaml
- services/service.yaml - services/service.yaml
- services/service-srv.yaml
- ingressclass/ingressclass.yaml - ingressclass/ingressclass.yaml

View File

@@ -0,0 +1,14 @@
apiVersion: kubevirt.io/v1
kind: KubeVirt
metadata:
name: kubevirt
namespace: kubevirt
spec:
certificateRotateStrategy:
selfSigned:
ca:
duration: "26280h" # 3 años
renewBefore: "720h" # 30 días antes
server:
duration: "8760h" # 1 año
renewBefore: "240h" # 10 días antes

View File

@@ -13,6 +13,17 @@ spec:
labels: labels:
app: iso-server app: iso-server
spec: spec:
initContainers:
- name: init-dirs
image: alpine:latest
command: ["sh","-lc"]
args:
- |
mkdir -p /share/isos
chmod 755 /share/isos
volumeMounts:
- name: iso-storage
mountPath: /share
containers: containers:
- name: httpd - name: httpd
image: httpd:2.4 image: httpd:2.4
@@ -21,6 +32,7 @@ spec:
volumeMounts: volumeMounts:
- name: iso-storage - name: iso-storage
mountPath: /usr/local/apache2/htdocs mountPath: /usr/local/apache2/htdocs
subPath: isos
readinessProbe: readinessProbe:
httpGet: httpGet:
path: / path: /
@@ -36,7 +48,7 @@ spec:
- name: samba - name: samba
image: dperson/samba image: dperson/samba
args: ["-p", "-s", "isos;/share;yes;no"] args: ["-p", "-s", "isos;/share/isos;yes;no"]
ports: ports:
- containerPort: 445 - containerPort: 445
securityContext: securityContext:

View File

@@ -1,78 +0,0 @@
# MinIO en Kubernetes — c2et.net (Site A/B)
Este paquete contiene manifiestos sin Helm para desplegar **dos instancias independientes de MinIO**,
una por site, usando tus StorageClasses `sc-me5-site-a` y `sc-me5-site-b`, y forzando programación por zona.
## Estructura
```
minio-k8s-c2et-net/
site-a/
namespace.yaml
secret-root.yaml
pvc.yaml
statefulset.yaml
service.yaml
ingress-api.yaml
ingress-console.yaml
site-b/
(idéntico con valores del site B)
```
## Credenciales de administración
- Usuario: **admin**
- Password: **Pozuelo12345**
> Cambia estas credenciales en `secret-root.yaml` antes de ir a producción.
## Dominios
- Site A API: `s3-a.c2et.net`
- Site A Consola: `console.s3-a.c2et.net`
- Site B API: `s3-b.c2et.net`
- Site B Consola: `console.s3-b.c2et.net`
Requisitos previos:
- IngressClass `nginx` operativo.
- `cert-manager` con `ClusterIssuer` llamado `letsencrypt-prod`.
- DNS apuntando los hosts anteriores al Ingress Controller.
## Despliegue rápido
```bash
kubectl apply -f site-a/namespace.yaml
kubectl apply -f site-a/secret-root.yaml
kubectl apply -f site-a/pvc.yaml
kubectl apply -f site-a/service.yaml
kubectl apply -f site-a/statefulset.yaml
kubectl apply -f site-a/ingress-api.yaml
kubectl apply -f site-a/ingress-console.yaml
kubectl apply -f site-b/namespace.yaml
kubectl apply -f site-b/secret-root.yaml
kubectl apply -f site-b/pvc.yaml
kubectl apply -f site-b/service.yaml
kubectl apply -f site-b/statefulset.yaml
kubectl apply -f site-b/ingress-api.yaml
kubectl apply -f site-b/ingress-console.yaml
```
## Probar
```bash
export AWS_ACCESS_KEY_ID=admin
export AWS_SECRET_ACCESS_KEY='Pozuelo12345'
export AWS_S3_FORCE_PATH_STYLE=true
aws --endpoint-url https://s3-a.c2et.net s3 mb s3://mi-bucket-a
aws --endpoint-url https://s3-a.c2et.net s3 ls
aws --endpoint-url https://s3-b.c2et.net s3 mb s3://mi-bucket-b
aws --endpoint-url https://s3-b.c2et.net s3 ls
```
## Notas
- Los PVC usan `WaitForFirstConsumer` a través de tus StorageClasses; el `nodeSelector` del StatefulSet garantiza
que cada volumen se cree en el **site** correcto.
- Imagen MinIO: `quay.io/minio/minio:RELEASE.2025-02-20T00-00-00Z` (ajústala a la que certifiques).
- Tamaño del PVC por defecto: `2Ti` (modifícalo a tu necesidad).

View File

@@ -1,18 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: minio
namespace: minio-site-a
spec:
type: ClusterIP
selector:
app: minio
ports:
- name: api
port: 9000
targetPort: 9000
protocol: TCP
- name: console
port: 9001
targetPort: 9001
protocol: TCP

View File

@@ -1,18 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: minio
namespace: minio-site-b
spec:
type: ClusterIP
selector:
app: minio
ports:
- name: api
port: 9000
targetPort: 9000
protocol: TCP
- name: console
port: 9001
targetPort: 9001
protocol: TCP

View File

@@ -131,6 +131,7 @@ Este repositorio contiene los **manifiestos, scripts y documentación** para des
| `comprobaciones.md` | Checklist tras cada paso crítico | [Ver](./comprobaciones.md) | | `comprobaciones.md` | Checklist tras cada paso crítico | [Ver](./comprobaciones.md) |
| `script_limpieza.md` | Script para limpiar un nodo | [Ver](script_limpieza.md) | | `script_limpieza.md` | Script para limpiar un nodo | [Ver](script_limpieza.md) |
| `coredns-demo\readme.md` | Ejemplo de Multus con CoreDNS | [Ver](./coredns-demo/readme.md) | | `coredns-demo\readme.md` | Ejemplo de Multus con CoreDNS | [Ver](./coredns-demo/readme.md) |
| `harbor\readme.md` | Manual de instalacion de Harbor | [Ver](./harbor/readme.md) |
| `storage\readme.md` | Ejemplo de StorageClass | [Ver](./storage/readme.md) | | `storage\readme.md` | Ejemplo de StorageClass | [Ver](./storage/readme.md) |
| `dashboard\readme.md` | Ejemplo con ingress dashboard | [Ver](./dashboard/readme.md) | | `dashboard\readme.md` | Ejemplo con ingress dashboard | [Ver](./dashboard/readme.md) |
| `wireguard\readme.md` | Manual de WireGuard | [Ver](./wireguard/readme.md) | | `wireguard\readme.md` | Manual de WireGuard | [Ver](./wireguard/readme.md) |
@@ -143,7 +144,6 @@ Este repositorio contiene los **manifiestos, scripts y documentación** para des
| `mapas\readme.md` | Manual de instalación de Tileserver-GL | [Ver](./mapas/readme.md) | | `mapas\readme.md` | Manual de instalación de Tileserver-GL | [Ver](./mapas/readme.md) |
| `argos\readme.md` | Manual de instalación de Argos Core | [Ver](./argos/readme.md) | | `argos\readme.md` | Manual de instalación de Argos Core | [Ver](./argos/readme.md) |
| `multusk3s.md` | Notas para Multus en K3s | [Ver](./multusk3s.md) | | `multusk3s.md` | Notas para Multus en K3s | [Ver](./multusk3s.md) |
| `minio\readme.md` | Manual de instalación de Minio para Velero | [Ver](./minio/readme.md) |
| `velero\readme.md` | Manual de instalación de Velero | [Ver](./velero/readme.md) | | `velero\readme.md` | Manual de instalación de Velero | [Ver](./velero/readme.md) |
--- ---
@@ -162,12 +162,11 @@ Este repositorio contiene los **manifiestos, scripts y documentación** para des
| `Apolo` | ✅ Completado | Funcionando | [https://portal.apolo.c2et.net](https://portal.apolo.c2et.net/) | admin / 123456 | | `Apolo` | ✅ Completado | Funcionando | [https://portal.apolo.c2et.net](https://portal.apolo.c2et.net/) | admin / 123456 |
| `Gitea` | ✅ Completado | Funcionando | [https://git.c2et.net](https://git.c2et.net) | | | `Gitea` | ✅ Completado | Funcionando | [https://git.c2et.net](https://git.c2et.net) | |
| `Harbor` | ✅ Completado | Funcionando | [https://harbor.c2et.net](https://harbor.c2et.net) | | | `Harbor` | ✅ Completado | Funcionando | [https://harbor.c2et.net](https://harbor.c2et.net) | |
| `Guacamole` | ✅ Completado | Funcionando | [https://heimdall.c2et.net](https://heimdall.c2et.net) | | | `Guacamole` | ✅ Completado | Funcionando | [https://heimdall.c2et.net](https://heimdall.c2et.net) | guacadmin / guacadmin |
| `VSCode` | ✅ Completado | Funcionando | [https://vscode.c2et.net](https://vscode.c2et.net) | Pozuelo12345 | | `VSCode` | ✅ Completado | Funcionando | [https://vscode.c2et.net](https://vscode.c2et.net) | Pozuelo12345 |
| `Tileserver-GL` | ✅ Completado | Funcionando | [https://mapas.c2et.net](https://mapas.c2et.net) | | | `Tileserver-GL` | ✅ Completado | Funcionando | [https://mapas.c2et.net](https://mapas.c2et.net) | |
| `External` | ✅ Completado | Funcionando | [https://admin.firewall.c2et.net](https://admin.firewall.c2et.net) <br>[https://admin.powervault1.c2et.net](https://admin.powervault1.c2et.net)<br> [https://admin.powervault2.c2et.net](https://admin.powervault2.c2et.net) | | | `External` | ✅ Completado | Funcionando | [https://admin.firewall.c2et.net](https://admin.firewall.c2et.net) <br>[https://admin.powervault1.c2et.net](https://admin.powervault1.c2et.net)<br> [https://admin.powervault2.c2et.net](https://admin.powervault2.c2et.net) | |
| `Argos Core` | ✅ Completado | Funcionando | [https://argos.panel.c2et.net/](https://argos.panel.c2et.net) | | | `Argos Core` | ✅ Completado | Funcionando | [https://argos.panel.c2et.net/](https://argos.panel.c2et.net) | |
| `Minio` | ✅ Completado | Funcionando | [https://console.s3-a.c2et.net](https://console.s3-a.c2et.net) <br>[https://console.s3-b.c2et.net](https://console.s3-b.c2et.net) | admin / Pozuelo12345 |
| `Velero` | ✅ Completado | Funcionando | | | | `Velero` | ✅ Completado | Funcionando | | |
--- ---

View File

@@ -0,0 +1,56 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: repo-sources
namespace: repo
data:
sources.txt: |
repo-oss|http://download.opensuse.org/distribution/leap/15.6/repo/oss/|opensuse/leap/15.6/oss
repo-non-oss|http://download.opensuse.org/distribution/leap/15.6/repo/non-oss/|opensuse/leap/15.6/non-oss
update-oss|http://download.opensuse.org/update/leap/15.6/oss/|opensuse/leap/15.6/update/oss
update-non-oss|http://download.opensuse.org/update/leap/15.6/non-oss/|opensuse/leap/15.6/update/non-oss
update-sle|http://download.opensuse.org/update/leap/15.6/sle/|opensuse/leap/15.6/update/sle
backports|http://download.opensuse.org/update/leap/15.6/backports/|opensuse/leap/15.6/update/backports
openh264|http://codecs.opensuse.org/openh264/openSUSE_Leap/|opensuse/openh264
nvidia|https://download.nvidia.com/opensuse/leap/15.6/|thirdparty/nvidia/leap/15.6
k8s-stable|https://pkgs.k8s.io/core:/stable:/v1.33/rpm/|thirdparty/kubernetes/core/stable/v1.33/rpm
sync.sh: |
set -euo pipefail
SRC_LIST="/config/sources.txt"
DEST_ROOT="/mirror/repos"
mkdir -p "$DEST_ROOT"
command -v rsync >/dev/null 2>&1 || echo "Aviso: rsync no disponible; usaré wget para HTTP/HTTPS"
command -v wget >/dev/null 2>&1 || { echo "ERROR: wget requerido"; exit 1; }
while IFS='|' read -r NAME URL SUBDIR; do
[[ -z "${NAME:-}" || "${NAME:0:1}" == "#" ]] && continue
DEST="${DEST_ROOT}/${SUBDIR}"
mkdir -p "$DEST"
echo "==> Sync ${NAME} (${URL}) -> ${DEST}"
if [[ "$URL" == rsync://* ]]; then
rsync -aH --delete --partial --info=stats1,progress2 "${URL}" "${DEST}/"
else
TMP="${DEST}.tmp"
mkdir -p "$TMP"
wget -m -np -nH -e robots=off -P "$TMP" --no-verbose --show-progress "$URL"
shopt -s dotglob nullglob
SRC_CONTENT=("$TMP"/*)
if [[ ${#SRC_CONTENT[@]} -gt 0 ]]; then
rsync -a --delete "$TMP"/ "$DEST"/
fi
rm -rf "$TMP"
fi
chmod -R a+rX "$DEST"
done < "$SRC_LIST"
echo "Sync completado."

View File

@@ -0,0 +1,38 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: repo-sync
namespace: repo
spec:
schedule: "15 2 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 2
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: sync
image: alpine:latest
command: ["sh","-lc"]
args:
- |
set -e
apk add --no-cache rsync wget bash coreutils
chmod +x /config/sync.sh
/config/sync.sh
volumeMounts:
- name: repo-storage
mountPath: /mirror
- name: repo-config
mountPath: /config
volumes:
- name: repo-storage
persistentVolumeClaim:
claimName: repo-pvc
- name: repo-config
configMap:
name: repo-sources
defaultMode: 0755

View File

@@ -0,0 +1,59 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: repo-server
namespace: repo
spec:
replicas: 1
selector:
matchLabels:
app: repo-server
template:
metadata:
labels:
app: repo-server
spec:
initContainers:
- name: init-dirs
image: alpine:3.20
command: ["sh","-lc"]
args:
- |
mkdir -p /share/repos
chmod 755 /share /share/repos
volumeMounts:
- name: repo-storage
mountPath: /share
containers:
- name: httpd
image: httpd:2.4
ports:
- containerPort: 80
volumeMounts:
- name: repo-storage
mountPath: /usr/local/apache2/htdocs
subPath: repos
readinessProbe:
httpGet: { path: /, port: 80 }
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet: { path: /, port: 80 }
initialDelaySeconds: 15
periodSeconds: 20
- name: samba
image: dperson/samba
args: ["-p", "-s", "repos;/share/repos;yes;no"]
ports:
- containerPort: 445
securityContext:
runAsUser: 0
volumeMounts:
- name: repo-storage
mountPath: /share
volumes:
- name: repo-storage
persistentVolumeClaim:
claimName: repo-pvc

View File

@@ -0,0 +1,23 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: repo
namespace: repo
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts: [ "repo.c2et.net" ]
secretName: repo-c2et-net-tls
rules:
- host: repo.c2et.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: repo-http
port:
number: 80

26
repo/kustomization.yaml Normal file
View File

@@ -0,0 +1,26 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: repo
commonLabels:
app.kubernetes.io/name: repo-mirror
app.kubernetes.io/part-of: suse-repo
resources:
- namespace.yaml
- pvc.yaml
- configmap/repo-sources.yaml
- deployments/repo-server.yaml
- services/service-http.yaml
- services/service-samba.yaml
- ingress/ingress-repo.yaml
- cronjobs/repo-sync.yaml
images:
- name: httpd
newTag: "2.4"
- name: alpine
newTag: "latest"
- name: dperson/samba
newTag: "latest"

4
repo/namespace.yaml Normal file
View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: repo

12
repo/pvc.yaml Normal file
View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: repo-pvc
namespace: repo
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 2Ti
storageClassName: sc-me5-site-a

54
repo/readme.md Normal file
View File

@@ -0,0 +1,54 @@
## Cómo usarlo (rápido)
1. Ajusta **dominio** en el Ingress y (si quieres) IP fija en el Service de Samba.
2. Revisa tamaño de **PVC**.
3. (Opcional) Cambia o amplía la lista en `sources.txt`. Si tienes un mirror con **rsync**, usa `rsync://...` en la URL para más eficiencia.
4. Aplica en orden:
```bash
kubectl apply -f repo/namespace.yaml
kubectl apply -f repo/pvc.yaml
kubectl apply -f repo/configmap/repo-sources.yaml
kubectl apply -f repo/deployments/repo-server.yaml
kubectl apply -f repo/services/service-http.yaml
kubectl apply -f repo/services/service-samba.yaml # o NodePort
kubectl apply -f repo/ingress/ingress-repo.yaml
kubectl apply -f repo/cronjobs/repo-sync.yaml
```
5. Lanza una sync inicial **ad hoc** (sin esperar al cron) creando un Job manual:
```bash
kubectl create job --from=cronjob/repo-sync repo-sync-now -n repo
kubectl logs -f job/repo-sync-now -n repo
```
---
## Configuración en los clientes SUSE
En tus servidores, añade repos apuntando a tu repo interno, por ejemplo:
```bash
# HTTP (recomendado)
sudo zypper ar -f http://repo.c2et.net/opensuse/leap/15.6/oss repo-oss-local
sudo zypper ar -f http://repo.c2et.net/opensuse/leap/15.6/non-oss repo-non-oss-local
sudo zypper ar -f http://repo.c2et.net/opensuse/leap/15.6/update/oss update-oss-local
sudo zypper ar -f http://repo.c2et.net/opensuse/leap/15.6/update/non-oss update-non-oss-local
sudo zypper ar -f http://repo.c2et.net/opensuse/leap/15.6/update/sle update-sle-local
sudo zypper ar -f http://repo.c2et.net/opensuse/leap/15.6/update/backports update-backports-local
sudo zypper ar -f http://repo.c2et.net/opensuse/openh264 openh264-local
# Terceros (si los espejas):
sudo zypper ar -f http://repo.c2et.net/thirdparty/nvidia/leap/15.6 nvidia-local
sudo zypper ar -f http://repo.c2et.net/thirdparty/kubernetes/core/stable/v1.33/rpm k8s-stable-local
```
> Truco: deja tus repos “externos” deshabilitados (`zypper mr -d <alias>`) y activa solo los “-local”. Así fuerzan el uso del mirror interno.
---
## Sugerencias y mejoras
* **Mejor rsync**: si eliges un mirror con `rsync://`, cambia las URLs de `download.opensuse.org` a ese mirror (ej. `rsync://<mirror>/opensuse/distribution/leap/15.6/repo/oss/`) para acelerar y reducir ancho de banda (usa `--delete`).
* **Throttle nocturno**: añade `--bwlimit=MB` a `rsync` si tu ventana noc

View File

@@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
name: repo-http
namespace: repo
spec:
type: ClusterIP
selector:
app: repo-server
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: repo-samba
namespace: repo
spec:
type: LoadBalancer
loadBalancerIP: 192.168.0.106
selector:
app: repo-server
ports:
- name: samba
port: 445
targetPort: 445
protocol: TCP

View File

@@ -1,87 +0,0 @@
apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
name: rook-ceph
namespace: rook-ceph
spec:
cephVersion:
image: quay.io/ceph/ceph:v19.2.3
dataDirHostPath: /var/lib/rook
network:
provider: host
addressRanges:
public:
- "192.168.4.0/24"
cluster:
- "192.168.4.0/24"
mgr:
count: 2
mon:
count: 3
allowMultiplePerNode: false
dashboard:
enabled: true
# Evita OSDs en el futuro nodo árbitro (cuando lo añadas)
placement:
osd:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["site-a","site-b"]
mgr:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: ["srvfkvm01","srvfkvm04"]
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

View File

@@ -1,73 +0,0 @@
apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
name: rook-ceph
namespace: rook-ceph
spec:
cephVersion:
image: quay.io/ceph/ceph:v19.2.3
dataDirHostPath: /var/lib/rook
network:
provider: host
connections:
publicNetwork: "192.168.3.0/24"
clusterNetwork: "192.168.3.0/24"
mon:
count: 3
allowMultiplePerNode: false
dashboard:
enabled: true
# Evita OSDs en el futuro nodo árbitro (cuando lo añadas)
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

View File

@@ -0,0 +1,109 @@
apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
name: rook-ceph
namespace: rook-ceph
spec:
cephVersion:
image: quay.io/ceph/ceph:v18
dataDirHostPath: /var/lib/rook
dashboard:
enabled: true
mon:
count: 3
allowMultiplePerNode: false
mgr:
count: 2
# Reglas de colocación: mons (A/B/arbiter), mgrs (A/B), OSDs solo en A/B
placement:
mon:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["site-a","site-b","arbiter"]
topologySpreadConstraints:
- labelSelector:
matchLabels: { app: rook-ceph-mon }
maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
mgr:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["site-a","site-b"]
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values: ["rook-ceph-mgr"]
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- labelSelector:
matchLabels: { app: rook-ceph-mgr }
maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
osd:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["site-a","site-b"]
cleanupPolicy:
wipeDevicesFromOtherClusters: true
sanitizeDisks:
method: quick
dataSource: zero
storage:
useAllNodes: false
useAllDevices: false
nodes:
- name: srvfkvm01
devices:
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d5bb177a1716, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d5dc196bd3a7, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d5f81b10f7ef, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d6151cca8afd, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d62f1e5e9699, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d64f204b2405, config: { deviceClass: ssd } }
- name: srvfkvm02
devices:
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030127eef88828273, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030127f879197de32, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030128081a076ba0c, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030128114a93e33b9, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94300301281a7b1fc151a, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030128235ba79d801, config: { deviceClass: ssd } }
- name: srvfkvm03
devices:
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128aef3bb4e0ae, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b0e3d8bc1dc, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b2b3f446dd7, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b4440c2d027, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b5e42510c2a, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b7d442e592c, config: { deviceClass: ssd } }
- name: srvfkvm04
devices:
- { fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c003012887ebfca6752, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c0030128896e360075f, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c00301288ac038600d4, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c00301288c62acb6efc, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c00301288e456c6d441, config: { deviceClass: ssd } }
- { fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c00301288f976534b4f, config: { deviceClass: ssd } }

View File

@@ -5,8 +5,8 @@ metadata:
namespace: rook-ceph namespace: rook-ceph
annotations: annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
nginx.ingress.kubernetes.io/whitelist-source-range: "192.168.200.0/24,192.168.0.0/24,10.244.0.0/16,192.168.4.0/24" # nginx.ingress.kubernetes.io/whitelist-source-range: "192.168.200.0/24,192.168.0.0/24,10.244.0.0/16,192.168.4.0/24"
spec: spec:
ingressClassName: nginx ingressClassName: nginx
tls: tls:

View File

@@ -1,9 +0,0 @@
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
name: rbd-2x2-sites
namespace: rook-ceph
spec:
failureDomain: zone
replicated:
size: 4

View File

@@ -0,0 +1,18 @@
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
name: rbd-2x2-sites
namespace: rook-ceph
spec:
deviceClass: ssd
failureDomain: zone
replicated:
size: 4
replicasPerFailureDomain: 2
subFailureDomain: host
requireSafeReplicaSize: true
parameters:
pg_autoscale_mode: "on"
min_size: "2"
mirroring:
enabled: false

View File

@@ -1,60 +1,26 @@
# Despliegue de RookCeph en clúster **Kubernetes** (SUSE) con discos locales (Bluestore) # Despliegue de **RookCeph** en Kubernetes (SUSE) con 2 zonas + **árbitro**
> 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. > Guía basada en el estado **actual** del clúster (A/B + *arbiter*), sin fase previa “sin árbitro. Discos locales (Bluestore), distribución por **zona**, 3 MON (uno por zona) y 2 MGR (uno por site A y otro por site B). Pool RBD con **size=4** (2+2 por zona) y **min
> to**=2.
--- ---
## 1) Requisitos previos ## 1) Topología y requisitos
* 4 nodos Kubernetes operativos: `srvfkvm01`, `srvfkvm02`, `srvfkvm03`, `srvfkvm04` (control-plane o mixtos) * Nodos y zonas:
* 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. * **site-a**: `srvfkvm01`, `srvfkvm02`
* **site-b**: `srvfkvm03`, `srvfkvm04`
* **arbiter**: `srvfkvm05` *(sin OSDs)*
* Cada nodo de datos con **6 discos** dedicados a Ceph (usar rutas persistentes `/dev/disk/by-id/...`).
* Acceso a Internet desde los nodos. `kubectl` con permisos de admin.
* Versiones empleadas: **Rook v1.18.x**, **Ceph v18 (Reef)**.
> **Objetivo de resiliencia**: tolerar la caída completa de un site (A **o** B). El árbitro aloja MON (y opcionalmente MGR), **no** OSDs.
--- ---
## 2) Preparar discos en SUSE (solo discos de datos) ## 2) Etiquetar nodos por **zona**
Instala utilidades necesarias en **cada nodo**:
```bash
sudo zypper -n install gdisk util-linux
```
Limpieza segura **solo** de `sdb…sdg` (ajusta si difiere):
```bash
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):
```bash
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.
```bash ```bash
# SITE A # SITE A
@@ -64,35 +30,72 @@ kubectl label node srvfkvm02 topology.kubernetes.io/zone=site-a --overwrite
# SITE B # SITE B
kubectl label node srvfkvm03 topology.kubernetes.io/zone=site-b --overwrite kubectl label node srvfkvm03 topology.kubernetes.io/zone=site-b --overwrite
kubectl label node srvfkvm04 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`. # ÁRBITRO
kubectl label node srvfkvm05 topology.kubernetes.io/zone=arbiter --overwrite
```
--- ---
## 4) Instalar Rook (CRDs, comunes y operador) ## 3) Preparar discos (SUSE)
Instalar utilidades (en **cada nodo de datos**):
```bash
sudo zypper -n install gdisk util-linux
```
Limpiar de forma segura (ajusta IDs según cada host):
```bash
# Ejemplo genérico; usa *by-id* reales de cada nodo
for d in \
/dev/disk/by-id/wwn-...a \
/dev/disk/by-id/wwn-...b \
/dev/disk/by-id/wwn-...c \
/dev/disk/by-id/wwn-...d \
/dev/disk/by-id/wwn-...e \
/dev/disk/by-id/wwn-...f; do
echo ">>> $d"
sudo wipefs -a "$d" || true
# Cabecera 100MiB
sudo dd if=/dev/zero of="$d" bs=1M count=100 oflag=direct,dsync || true
# Cola 100MiB
real=$(readlink -f "$d"); dev=$(basename "$real")
sz=$(cat /sys/class/block/$dev/size); tail=$((100*1024*1024/512)); seek=$((sz - tail)); ((seek<0)) && seek=0
sudo dd if=/dev/zero of="$real" bs=512 seek="$seek" count="$tail" oflag=direct,dsync || true
sudo partprobe "$real" || true; sudo udevadm settle || true
done
```
> **Consejo**: guarda las rutas *byid* exactas de cada nodo; son las que se usarán en el `CephCluster`.
---
## 4) Instalar Rook (CRDs + operador)
```bash ```bash
kubectl create namespace rook-ceph || true kubectl create namespace rook-ceph || true
# Clonar repo oficial (opcional para tener toolbox/ejemplos) # CRDs + common + operator (Rook v1.18.x)
git clone https://github.com/rook/rook.git kubectl apply -f https://raw.githubusercontent.com/rook/rook/v1.18.0/deploy/examples/crds.yaml \
cd rook/deploy/examples -f https://raw.githubusercontent.com/rook/rook/v1.18.0/deploy/examples/common.yaml \
-f https://raw.githubusercontent.com/rook/rook/v1.18.0/deploy/examples/operator.yaml
kubectl apply -f crds.yaml -f common.yaml -f operator.yaml
```
Comprueba el operador:
```bash
kubectl -n rook-ceph get pods | grep operator kubectl -n rook-ceph get pods | grep operator
``` ```
> **Toolbox** (útil para diagnosticar):
>
> ```bash
> kubectl -n rook-ceph apply -f https://raw.githubusercontent.com/rook/rook/v1.18.0/deploy/examples/toolbox.yaml
> ```
--- ---
## 5) CephCluster 4 nodos, discos *byid*, red de storage (VLAN 30) ## 5) Manifiesto **CephCluster** (A/B + árbitro, OSDs solo en A/B)
Archivo `cluster/ceph-cluster.yaml`: Archivo `cluster/ceph-cluster.yaml` **adaptado a tu entorno actual**:
```yaml ```yaml
apiVersion: ceph.rook.io/v1 apiVersion: ceph.rook.io/v1
@@ -102,72 +105,103 @@ metadata:
namespace: rook-ceph namespace: rook-ceph
spec: spec:
cephVersion: cephVersion:
image: quay.io/ceph/ceph:v19.2.3 # estable (puedes usar v18.2.x si prefieres) image: quay.io/ceph/ceph:v18
dataDirHostPath: /var/lib/rook dataDirHostPath: /var/lib/rook
# Red: usamos hostNetworking y restringimos a VLAN de storage dashboard:
network: enabled: true
provider: host
addressRanges: mgr:
public: count: 2
- "192.168.3.0/24"
cluster:
- "192.168.3.0/24"
mon: mon:
count: 3 count: 3
allowMultiplePerNode: false allowMultiplePerNode: false
dashboard:
enabled: true
# No queremos OSDs en el futuro nodo árbitro
placement: placement:
osd: # MGR repartidos entre site-a y site-b
mgr:
nodeAffinity: nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms: nodeSelectorTerms:
- matchExpressions: - matchExpressions:
- key: topology.kubernetes.io/zone - key: topology.kubernetes.io/zone
operator: In operator: In
values: ["site-a", "site-b"] values: ["site-a","site-b"]
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values: ["rook-ceph-mgr"]
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- labelSelector:
matchLabels:
app: rook-ceph-mgr
maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
# MON uno por zona (site-a, site-b, arbiter)
mon:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["site-a","site-b","arbiter"]
topologySpreadConstraints:
- labelSelector:
matchLabels:
app: rook-ceph-mon
maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
security:
cephx:
csi: {}
daemon: {}
rbdMirrorPeer: {}
storage: storage:
useAllNodes: false
useAllDevices: false useAllDevices: false
nodes: nodes:
- name: srvfkvm01 - name: srvfkvm01
devices: devices:
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d5bb177a1716 - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d5bb177a1716, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d5dc196bd3a7 - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d5dc196bd3a7, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d5f81b10f7ef - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d5f81b10f7ef, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d6151cca8afd - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d6151cca8afd, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d62f1e5e9699 - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d62f1e5e9699, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d64f204b2405 - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94b003012d64f204b2405, config: {deviceClass: ssd}}
- name: srvfkvm02 - name: srvfkvm02
devices: devices:
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030127eef88828273 - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030127eef88828273, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030127f879197de32 - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030127f879197de32, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030128081a076ba0c - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030128081a076ba0c, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030128114a93e33b9 - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030128114a93e33b9, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94300301281a7b1fc151a - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d94300301281a7b1fc151a, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030128235ba79d801 - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9430030128235ba79d801, config: {deviceClass: ssd}}
- name: srvfkvm03 - name: srvfkvm03
devices: devices:
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128aef3bb4e0ae - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128aef3bb4e0ae, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b0e3d8bc1dc - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b0e3d8bc1dc, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b2b3f446dd7 - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b2b3f446dd7, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b4440c2d027 - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b4440c2d027, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b5e42510c2a - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b5e42510c2a, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b7d442e592c - { fullpath: /dev/disk/by-id/wwn-0x64cd98f036d9510030128b7d442e592c, config: {deviceClass: ssd}}
- name: srvfkvm04 - name: srvfkvm04
devices: devices:
- fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c003012887ebfca6752 - { fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c003012887ebfca6752, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c0030128896e360075f - { fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c0030128896e360075f, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c00301288ac038600d4 - { fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c00301288ac038600d4, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c00301288c62acb6efc - { fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c00301288c62acb6efc, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c00301288e456c6d441 - { fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c00301288e456c6d441, config: {deviceClass: ssd}}
- fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c00301288f976534b4f - { fullpath: /dev/disk/by-id/wwn-0x6ec2a72037894c00301288f976534b4f, config: {deviceClass: ssd}}
``` ```
Aplicar y verificar: Aplicar y verificar:
@@ -177,13 +211,20 @@ kubectl apply -f cluster/ceph-cluster.yaml
kubectl -n rook-ceph get pods kubectl -n rook-ceph get pods
``` ```
> Instala el **toolbox** para diagnósticos: `kubectl -n rook-ceph apply -f rook/deploy/examples/toolbox.yaml` > **Nota**: los MON deberían quedar uno en `site-a`, otro en `site-b` y otro en `arbiter`; los MGR en `site-a` y `site-b`. Los OSDs solo en A/B.
--- ---
## 6) Pool RBD inicial (replica **4** sobre **hosts**) + StorageClass ## 6) Activar **Orchestrator** (backend Rook)
> 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`. ```bash
kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph orch set backend rook
kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph orch status
```
---
## 7) Pool **RBD** 2×2 por **zona** + StorageClass
`pools/ceph-blockpool-rbd.yaml`: `pools/ceph-blockpool-rbd.yaml`:
@@ -194,9 +235,16 @@ metadata:
name: rbd-2x2-sites name: rbd-2x2-sites
namespace: rook-ceph namespace: rook-ceph
spec: spec:
failureDomain: host deviceClass: ssd
failureDomain: zone
replicated: replicated:
size: 4 size: 4 # 2 por site (A/B)
minSize: 2
replicasPerFailureDomain: 2
subFailureDomain: host
requireSafeReplicaSize: true
parameters:
pg_autoscale_mode: "on"
``` ```
`storageclasses/rbd.yaml`: `storageclasses/rbd.yaml`:
@@ -226,47 +274,25 @@ allowVolumeExpansion: true
mountOptions: ["discard"] mountOptions: ["discard"]
``` ```
Aplicar: Aplicar y comprobar:
```bash ```bash
kubectl apply -f pools/ceph-blockpool-rbd.yaml kubectl apply -f pools/ceph-blockpool-rbd.yaml
kubectl apply -f storageclasses/rbd.yaml kubectl apply -f storageclasses/rbd.yaml
kubectl get sc
# Verificaciones rápidas
kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph osd pool get rbd-2x2-sites size
kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph osd pool get rbd-2x2-sites min_size
kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph osd crush rule dump rbd-2x2-sites -f json-pretty
``` ```
> Si creaste el pool inicialmente con `failureDomain: zone` y ves `active+undersized`, crea y asigna una **CRUSH rule** a host: > La regla CRUSH generada elige **zona** y luego **host** (2 réplicas por zona). Con OSDs solo en A/B, el árbitro **no** aloja datos.
>
> ```bash
> 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) ## 8) Dashboard por **Ingress** (opcional)
```bash `ingress/dashboard.yaml` (backend HTTP:7000):
# 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`:
```yaml ```yaml
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
@@ -275,15 +301,11 @@ metadata:
name: ceph-dashboard name: ceph-dashboard
namespace: rook-ceph namespace: rook-ceph
annotations: annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/backend-protocol: "HTTP" nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
spec: spec:
ingressClassName: nginx ingressClassName: nginx
tls:
- hosts: ["ceph.c2et.net"]
secretName: ceph-dashboard-tls
rules: rules:
- host: ceph.c2et.net - host: ceph.example.local
http: http:
paths: paths:
- path: / - path: /
@@ -295,27 +317,24 @@ spec:
number: 7000 number: 7000
``` ```
Credenciales: Contraseña admin:
```bash ```bash
# Usuario por defecto kubectl -n rook-ceph get secret rook-ceph-dashboard-password -o jsonpath='{.data.password}' | base64 -d; echo
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"`). Crear usuario admin.c3s (el otro suele resetear la pass):
```bash
kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- bash -lc \
'echo -n "Pozuelo12345" | ceph dashboard ac-user-create admin.c3s administrator -i - && ceph dashboard ac-user-list'
```
--- ---
## 9) Prueba rápida de PVC ## 9) Prueba de StorageClass (PVC + Pod)
`tests/pvc-test.yaml`: `tests/pvc.yaml`:
```yaml ```yaml
apiVersion: v1 apiVersion: v1
@@ -330,7 +349,7 @@ spec:
storageClassName: ceph-rbd storageClassName: ceph-rbd
``` ```
`tests/pod-test.yaml`: `tests/pod.yaml`:
```yaml ```yaml
apiVersion: v1 apiVersion: v1
@@ -351,97 +370,42 @@ spec:
claimName: test-rbd claimName: test-rbd
``` ```
Aplicar y verificar:
```bash ```bash
kubectl apply -f tests/pvc-test.yaml kubectl apply -f tests/pvc.yaml
kubectl apply -f tests/pod-test.yaml kubectl apply -f tests/pod.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' 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) ## 10) Guardar manifiestos exactos desde el clúster
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:
```bash ```bash
kubectl label node <NODO_ARBITRO> topology.kubernetes.io/zone=arbiter --overwrite # CephCluster “limpio” sin campos efímeros
``` kubectl -n rook-ceph get cephcluster rook-ceph -o yaml --show-managed-fields=false \
| yq 'del(.metadata.creationTimestamp,.metadata.generation,.metadata.resourceVersion,.metadata.uid,.status)' \
> ceph-cluster-export.yaml
2. **Actualiza el CephCluster** a stretch (5 MON): # Pool y StorageClass
kubectl -n rook-ceph get cephblockpool rbd-2x2-sites -o yaml > ceph-blockpool-export.yaml
```yaml kubectl get sc ceph-rbd -o yaml > storageclass-rbd-export.yaml
# 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.
3. **(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.
```bash
# 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:
```bash
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:
```bash
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:
```bash
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. ## 11) Troubleshooting breve
* **MON no se reprograma** tras borrar uno: el operador necesita que el **quórum** quede seguro. Revisa `rook-ceph-mon-endpoints`, `deployment/rook-ceph-mon-*` y `op-mon` en logs del operador.
* **OSDs detectados como HDD** vía HBA: puedes forzar `deviceClass: ssd` por disco (como en el `CephCluster`) o, ya desplegado, ajustar con `ceph osd crush set-device-class ssd osd.N`.
* **Dashboard “Orchestrator is not available”**:
```bash
kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph orch set backend rook
kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph orch status
```
---
### Fin
Con esto dispones de un despliegue RookCeph alineado con la realidad actual: 2 zonas de datos + árbitro, 3 MON (uno por zona), 2 MGR (A/B), OSDs solo en A/B, y un pool RBD con réplicas **2+2** por zona. ¡Listo para producción y ampliaciones futuras!

View File

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- csi-exos-x-csidriver.yaml

View File

@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- csidriver/
- secrets/
- storageclass/

View File

@@ -1,6 +1,6 @@
# Seagate Exos X CSI (ME5 dualsite) — Guía de instalación y operación # Seagate Exos X CSI (ME5 dual-site) — Guía de instalación y operación
Este README documenta cómo he dejado **reproducible** la instalación del *Seagate Exos X CSI Driver* (soporta ME5) en un clúster Kubernetes con **dos cabinas / dos zonas** (sitea y siteb) usando iSCSI + multipath y *topología por zona*. Este README documenta cómo he dejado **reproducible** la instalación del *Seagate Exos X CSI Driver* (soporta ME5) en un clúster Kubernetes con **dos cabinas / dos zonas** (site-a y site-b) usando iSCSI + multipath y *topología por zona*.
> **Objetivo** > **Objetivo**
> >
@@ -11,11 +11,100 @@ Este README documenta cómo he dejado **reproducible** la instalación del *Seag
--- ---
## 1) Prerrequisitos en los nodos ## 1) Configuración iSCSI en los nodos
1. **Multipath** y **iSCSI** instalados/activos. En **todos los nodos** del clúster:
2. **/etc/multipath.conf** — opciones relevantes usadas: 1. Instalar dependencias:
```bash
sudo zypper install open-iscsi yast2-iscsi-client multipath-tools
```
2. Habilitar y arrancar el servicio iSCSI:
```bash
sudo systemctl enable --now iscsid.service
systemctl status iscsid.service
```
3. Descubrir los targets en las cabinas:
```bash
sudo iscsiadm -m discovery -t sendtargets -p 192.168.3.11
sudo iscsiadm -m discovery -t sendtargets -p 192.168.3.21
```
En este punto hay que **añadir en las cabinas el grupo de host con cada host**.
4. Iniciar sesión contra todos los portales de ambas cabinas:
```bash
# Cabina site-a
sudo iscsiadm -m node -T iqn.1988-11.com.dell:01.array.bc305b5e92b6 -p 192.168.3.11:3260 --login &
sudo iscsiadm -m node -T iqn.1988-11.com.dell:01.array.bc305b5e92b6 -p 192.168.3.12:3260 --login &
sudo iscsiadm -m node -T iqn.1988-11.com.dell:01.array.bc305b5e92b6 -p 192.168.3.13:3260 --login &
sudo iscsiadm -m node -T iqn.1988-11.com.dell:01.array.bc305b5e92b6 -p 192.168.3.14:3260 --login &
sudo iscsiadm -m node -T iqn.1988-11.com.dell:01.array.bc305b5e92b6 -p 192.168.3.15:3260 --login &
sudo iscsiadm -m node -T iqn.1988-11.com.dell:01.array.bc305b5e92b6 -p 192.168.3.16:3260 --login &
sudo iscsiadm -m node -T iqn.1988-11.com.dell:01.array.bc305b5e92b6 -p 192.168.3.17:3260 --login &
sudo iscsiadm -m node -T iqn.1988-11.com.dell:01.array.bc305b5e92b6 -p 192.168.3.18:3260 --login &
# Cabina site-b
sudo iscsiadm -m node -T iqn.1988-11.com.dell:01.array.bc305b5e8e43 -p 192.168.3.21:3260 --login &
sudo iscsiadm -m node -T iqn.1988-11.com.dell:01.array.bc305b5e8e43 -p 192.168.3.22:3260 --login &
sudo iscsiadm -m node -T iqn.1988-11.com.dell:01.array.bc305b5e8e43 -p 192.168.3.23:3260 --login &
sudo iscsiadm -m node -T iqn.1988-11.com.dell:01.array.bc305b5e8e43 -p 192.168.3.24:3260 --login &
sudo iscsiadm -m node -T iqn.1988-11.com.dell:01.array.bc305b5e8e43 -p 192.168.3.25:3260 --login &
sudo iscsiadm -m node -T iqn.1988-11.com.dell:01.array.bc305b5e8e43 -p 192.168.3.26:3260 --login &
sudo iscsiadm -m node -T iqn.1988-11.com.dell:01.array.bc305b5e8e43 -p 192.168.3.27:3260 --login &
sudo iscsiadm -m node -T iqn.1988-11.com.dell:01.array.bc305b5e8e43 -p 192.168.3.28:3260 --login
```
5. Verificar la sesión activa:
```bash
sudo iscsiadm -m session
```
6. Editar configuración de iSCSI en `/etc/iscsi/iscsid.conf`:
```conf
iscsid.startup = /bin/systemctl start iscsid.socket iscsiuio.socket
iscsid.safe_logout = Yes
node.startup = automatic
node.leading_login = No
node.session.timeo.replacement_timeout = 120
node.conn[0].timeo.login_timeout = 15
node.conn[0].timeo.logout_timeout = 15
node.conn[0].timeo.noop_out_interval = 5
node.conn[0].timeo.noop_out_timeout = 5
node.session.err_timeo.abort_timeout = 15
node.session.err_timeo.lu_reset_timeout = 30
node.session.err_timeo.tgt_reset_timeout = 30
node.session.err_timeo.host_reset_timeout = 60
node.session.initial_login_retry_max = 8
node.session.cmds_max = 128
node.session.queue_depth = 32
node.session.xmit_thread_priority = -20
node.session.iscsi.InitialR2T = No
node.session.iscsi.ImmediateData = Yes
node.session.iscsi.FirstBurstLength = 262144
node.session.iscsi.MaxBurstLength = 16776192
node.conn[0].iscsi.MaxRecvDataSegmentLength = 262144
node.conn[0].iscsi.MaxXmitDataSegmentLength = 0
discovery.sendtargets.iscsi.MaxRecvDataSegmentLength = 32768
node.session.nr_sessions = 1
node.session.reopen_max = 0
node.session.iscsi.FastAbort = Yes
node.session.scan = auto
```
---
## 2) Prerrequisitos en los nodos
### 2.1. Configuración `/etc/multipath.conf`
```conf ```conf
defaults { defaults {
@@ -37,23 +126,24 @@ devices {
> **Por qué `greedy`?** > **Por qué `greedy`?**
> >
> * `find_multipaths "greedy"` evita crear *maps* hasta que haya más de un camino **o** el dispositivo sea claramente multipath, reduciendo falsos positivos y estabilizando el *udev settle*. Mejora tiempos de descubrimiento y evita *flapping*. > * `find_multipaths "greedy"` evita crear *maps* hasta que haya más de un camino **o** el dispositivo sea claramente multipath, reduciendo falsos positivos y estabilizando el *udev settle*.
Reiniciar servicios y refrescar paths tras cambiar multipath:
### 2.2. Multipath e iSCSI activos
Asegurarse de tener `multipathd` en ejecución:
```bash ```bash
sudo systemctl restart multipathd sudo systemctl restart multipathd
sudo multipath -r sudo multipath -r
``` ```
3. **Propagación de montajes (rshared)** ### 2.3. Propagación de montajes (rshared)
Asegurar que `/` y `/var/lib/kubelet` están en **rshared** para que los montajes hechos por el plugin dentro del pod del *nodeserver* aparezcan en el host:
```bash ```bash
sudo mount --make-rshared / sudo mount --make-rshared /
# systemd dropin para kubelet # systemd drop-in para kubelet
sudo install -d /etc/systemd/system/kubelet.service.d sudo install -d /etc/systemd/system/kubelet.service.d
cat <<'EOF' | sudo tee /etc/systemd/system/kubelet.service.d/10-mount-propagation.conf cat <<'EOF' | sudo tee /etc/systemd/system/kubelet.service.d/10-mount-propagation.conf
[Service] [Service]
@@ -67,16 +157,14 @@ sudo systemctl daemon-reload
sudo systemctl restart kubelet sudo systemctl restart kubelet
``` ```
Comprobar: Verificar:
```bash ```bash
sudo findmnt -o TARGET,PROPAGATION / sudo findmnt -o TARGET,PROPAGATION /
sudo findmnt -o TARGET,PROPAGATION /var/lib/kubelet sudo findmnt -o TARGET,PROPAGATION /var/lib/kubelet
``` ```
4. **Etiquetas de topología en nodos** ### 2.4. Etiquetas de topología en nodos
Etiquetar cada nodo con su zona:
```bash ```bash
kubectl label nodes <nodo-del-site-a> topology.kubernetes.io/zone=site-a --overwrite kubectl label nodes <nodo-del-site-a> topology.kubernetes.io/zone=site-a --overwrite
@@ -85,30 +173,9 @@ kubectl label nodes <nodo-del-site-b> topology.kubernetes.io/zone=site-b --overw
--- ---
## 2) Despliegue del Driver con Helm ## 3) Despliegue del Driver con Helm
### 2.1. Namespace y valores ### 3.1. Instalación
```bash
kubectl apply -f namespace.yaml # namespace: seagate
```
**values.yaml** (resumen de lo usado):
* Imagen del driver: `ghcr.io/seagate/seagate-exos-x-csi:v1.10.0`
* Sidecars:
* `csi-provisioner v5.0.1` (timeout 60s)
* `csi-attacher v4.6.1`
* `csi-resizer v1.11.1`
* `csi-snapshotter v8.0.1`
* `csi-node-driver-registrar v2.9.0`
* `controller.extraArgs: ["-v=2"]`
* `node.extraArgs: ["-v=2"]`
> **Nota:** no es necesario tocar `CSIDriver` para topología; la topología se maneja desde los `StorageClass` + etiquetas de nodo.
### 2.2. Instalación
```bash ```bash
helm upgrade --install exos-x-csi \ helm upgrade --install exos-x-csi \
@@ -117,69 +184,41 @@ helm upgrade --install exos-x-csi \
-f ./values.yaml -f ./values.yaml
``` ```
#### Si hay residuos de una instalación anterior (RBAC) *(Si hay residuos RBAC, eliminarlos antes de reintentar)*
Si aparece un error de *invalid ownership metadata* con recursos tipo `ClusterRole/ClusterRoleBinding` de un release previo (p.ej. `exosx-csi`), eliminarlos:
### 3.2. Namespace y valores
```bash ```bash
kubectl delete clusterrole external-provisioner-runner-systems kubectl apply -k .
kubectl delete clusterrolebinding csi-provisioner-role-systems
# (si hubiera más, listarlos por label y borrarlos)
# kubectl get clusterrole,clusterrolebinding -A -l app.kubernetes.io/instance=<old-release>
``` ```
Reintentar `helm upgrade --install`. Esto asegura que se creen en el orden correcto:
1. `namespace.yaml` → crea el namespace seagate.
2. `csidriver/` → instala el recurso CSIDriver (cluster-scoped).
3. `secrets/` → instala los secrets de conexión en el namespace seagate.
4. `storageclass/` → instala los dos StorageClass (sc-me5-site-a y sc-me5-site-b).
>Notas sobre recursos cluster-scoped:
En Kubernetes hay recursos que pertenecen a un namespace (ej: Pod, Secret, ConfigMap) y otros que son globales para todo el clúster (ej: CSIDriver, StorageClass, Node, Namespace). Los resources namespaced se pueden repetir en distintos namespaces. Los cluster-scoped solo existen una vez en todo el clúster y no tienen campo namespace.
En este repositorio:
* CSIDriver y StorageClass son cluster-scoped → no tienen namespace.
* Los Secret sí son namespaced → se instalan en seagate.
Por eso el kustomization.yaml está separado en subcarpetas:
* secrets/kustomization.yaml tiene namespace: seagate porque aplica solo a objetos namespaced.
* csidriver/ y storageclass/ no tienen namespace porque son cluster-scoped.
Esto evita errores y mantiene la instalación GitOps-friendly.
--- ---
## 3) Secret por cabina (A y B) ## 4) Prueba de extremo a extremo
Un `Secret` por sitio en el *namespace* `seagate` con `apiAddress`, `username`, `password` en Base64. PVC + Pod en site-a:
```bash
kubectl apply -f secret-me5-site-a.yaml
kubectl apply -f secret-me5-site-b.yaml
```
> **Importante:** Los `StorageClass` deben usar las **claves estándar CSI** para que el provisioner pase el Secret al driver:
>
> * `csi.storage.k8s.io/provisioner-secret-name|namespace`
> * `csi.storage.k8s.io/controller-publish-secret-name|namespace`
> * `csi.storage.k8s.io/controller-expand-secret-name|namespace`
> * `csi.storage.k8s.io/node-stage-secret-name|namespace` *(si aplica)*
> * `csi.storage.k8s.io/node-publish-secret-name|namespace` *(si aplica)*
El síntoma de no usar estos nombres es: `missing API credentials` en el evento del PVC.
---
## 4) StorageClass por zona (topología)
Se definen **dos** `StorageClass` idénticos salvo:
* Secret (A o B)
* `pool` (p. ej., `dg01` para sitea, `dg02` para siteb)
* `volPrefix` (ej. `sza` / `szb` para identificar site en el nombre de LUN)
* `allowedTopologies` con la zona correspondiente
* `volumeBindingMode: WaitForFirstConsumer`
> Con WFFC, el PVC **no** se enlaza hasta que exista un Pod consumidor; el scheduler elige un nodo, y el provisioner crea el volumen en la **zona del nodo**.
Aplicar ambos `StorageClass`:
```bash
kubectl apply -f sc-me5-site-a.yaml
kubectl apply -f sc-me5-site-b.yaml
```
---
## 5) Prueba de extremo a extremo
### 5.1. PVC + Pod en sitea
* PVC: `pvc-a` con `storageClassName: sc-me5-site-a`
* Pod: `pod-a` con `nodeSelector: topology.kubernetes.io/zone=site-a`
```bash ```bash
kubectl apply -f pvc-pod-a.yaml kubectl apply -f pvc-pod-a.yaml
@@ -187,115 +226,47 @@ kubectl apply -f pod-a.yaml
kubectl get pvc,pod kubectl get pvc,pod
``` ```
Deberías ver el Pod en *Running* y el volumen creado/montado en la ME5 del sitea. Verificar `iscsiadm`, `multipath`, eventos del PVC y logs del controller.
### 5.2. Verificaciones útiles
* **iSCSI nodes vistos:**
```bash
sudo iscsiadm -m node | sort
```
* **Multipath:**
```bash
sudo multipath -ll
```
* **Eventos del PVC:**
```bash
kubectl describe pvc <nombre>
```
* **Logs del controller:** (búsqueda de credenciales / errores de provisión)
```bash
kubectl -n seagate logs deploy/seagate-exos-x-csi-controller-server \
-c seagate-exos-x-csi-controller | grep -i -E 'cred|secret|error'
```
--- ---
## 6) Medir el tiempo de *NodePublish* (montaje) ## 5) Medición de tiempos de *NodePublish*
Para medir cuánto tarda el montaje (fase *NodePublishVolume*) desde el *nodeserver*:
```bash ```bash
kubectl -n seagate logs -l name=seagate-exos-x-csi-node-server \ kubectl -n seagate logs -l name=seagate-exos-x-csi-node-server \
-c seagate-exos-x-csi-node --tail=10000 \ -c seagate-exos-x-csi-node --tail=10000 \
| grep "NodePublishVolume" \ | grep "NodePublishVolume" | grep "ROUTINE END"
| grep "ROUTINE END" \
| sed -E 's/.*NodePublishVolume.*<([^>]*)>.*/\1/'
``` ```
* Valores \~**< 2 min** indican que el montaje completa dentro de la ventana de kubelet, evitando `DeadlineExceeded`. ---
* Si ves \~**4m34s** constantes: el driver está esperando a que aparezcan *dmname* de portales inaccesibles. Revisa topologías, conectividad y que solo se prueben portales de la zona activa.
> Para validar zonaB, lanza un Pod/PVC análogo en `site-b` y repite el grep anterior en los logs. ## 6) Solución de problemas
* `missing API credentials` → revisar claves CSI en el StorageClass.
* `DeadlineExceeded` → revisar multipath, etiquetas de zona y topología.
* Helm RBAC conflict → borrar roles residuales.
--- ---
## 7) Solución de problemas ## 7) Limpieza
* **`missing API credentials` al provisionar**
* Asegúrate de usar las **claves CSI** en `parameters:` del `StorageClass` (ver §3).
* **Errores Helm de *invalid ownership metadata***
* Borra los `ClusterRole/ClusterRoleBinding` residuales del release antiguo (ver §2.2).
* **`DeadlineExceeded` durante montaje**
* Comprueba:
* `find_multipaths "greedy"` y resto de multipath según §1.2.
* Etiquetas de zona en el nodo donde programa el Pod.
* Que el `StorageClass` correcto tenga `allowedTopologies` de esa zona.
* **Ver puertos/portales iSCSI efectivos**
* `sudo iscsiadm -m node | sort` para ver a qué destinos quedó configurado el nodo. Con topología bien aplicada, deben ser los del sitio correspondiente.
---
## 8) Limpieza y reintentos
Para repetir la prueba desde cero (manteniendo el driver):
```bash ```bash
kubectl delete -f pod-a.yaml kubectl delete -f pod-a.yaml
kubectl delete -f pvc-pod-a.yaml kubectl delete -f pvc-pod-a.yaml
``` ```
Si quisieras limpiar *todo el despliegue* del driver: Para desinstalar completamente:
```bash ```bash
helm uninstall exos-x-csi -n seagate helm uninstall exos-x-csi -n seagate
# Si quedaron RBAC de releases previos:
kubectl delete clusterrole external-provisioner-runner-systems || true
kubectl delete clusterrolebinding csi-provisioner-role-systems || true
``` ```
--- ---
## 9) Resumen de lo que quedó en repo (carpeta `seagate/`) ## 8) Anexos — Comandos útiles
* `namespace.yaml` — Namespace `seagate`. * Reinicio multipath/kubelet
* `secret-me5-site-a.yaml` / `secret-me5-site-b.yaml` — Credenciales por sitio. * Limpieza iSCSI/multipath:
* `values.yaml` — Valores de Helm usados para el driver v1.10.0.
* `sc-me5-site-a.yaml` / `sc-me5-site-b.yaml` — StorageClass con `allowedTopologies`, `pool`, `volPrefix`, claves CSI de Secret y `WaitForFirstConsumer`.
* `pvc-pod-a.yaml` + `pod-a.yaml` — Manifests de prueba en `site-a`.
* *(Opcional)* `csi-exos-x-csidriver.yaml` — no es necesario modificarlo para topología en esta versión.
---
## 10) Anexos — Comandos útiles ejecutados
* Reinicio multipath/kubelet y propagación de montajes.
* Limpieza iSCSI/multipath (cuando se rehizo la prueba):
```bash ```bash
sudo iscsiadm -m node -u || true sudo iscsiadm -m node -u || true
@@ -303,14 +274,3 @@ sudo iscsiadm -m node -o delete || true
sudo multipath -F || true sudo multipath -F || true
sudo multipath -r sudo multipath -r
``` ```
* Despliegue Helm + manejo de residuos RBAC (ver §2.2).
* Aplicación secuencial de `namespace`, `secrets`, `StorageClass`, `PVC` y `Pod`.
---
### Resultado
* **Reproducible**: con esta receta, el volumen se crea en la cabina de su zona y el Pod arranca.
* **Tiempos de montaje**: bajan de \~4m34s a **≈1m30s** (observado), dentro del presupuesto de kubelet.
* **Aislamiento por zona**: cada StorageClass limita portales iSCSI a su sitio gracias a `allowedTopologies` + etiquetas de nodo.

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- secret-me5-site-a.yaml
- secret-me5-site-b.yaml

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- sc-me5-site-a.yaml
- sc-me5-site-b.yaml

View File

@@ -3,7 +3,8 @@ kind: StorageClass
metadata: metadata:
name: sc-me5-site-a name: sc-me5-site-a
provisioner: csi-exos-x.seagate.com provisioner: csi-exos-x.seagate.com
volumeBindingMode: WaitForFirstConsumer reclaimPolicy: Delete
volumeBindingMode: Immediate
allowVolumeExpansion: true allowVolumeExpansion: true
parameters: parameters:
csi.storage.k8s.io/provisioner-secret-name: seagate-me5-site-a csi.storage.k8s.io/provisioner-secret-name: seagate-me5-site-a
@@ -13,10 +14,7 @@ parameters:
csi.storage.k8s.io/controller-expand-secret-name: seagate-me5-site-a csi.storage.k8s.io/controller-expand-secret-name: seagate-me5-site-a
csi.storage.k8s.io/controller-expand-secret-namespace: seagate csi.storage.k8s.io/controller-expand-secret-namespace: seagate
csi.storage.k8s.io/fstype: ext4 csi.storage.k8s.io/fstype: ext4
pool: dg01 # pool de la ME5 del Site A pool: pool
volPrefix: sza # prefijo corto para identificar Site A volPrefix: sza
storageProtocol: iscsi storageProtocol: iscsi
allowedTopologies:
- matchLabelExpressions:
- key: topology.kubernetes.io/zone
values: ["site-a"]

View File

@@ -3,7 +3,8 @@ kind: StorageClass
metadata: metadata:
name: sc-me5-site-b name: sc-me5-site-b
provisioner: csi-exos-x.seagate.com provisioner: csi-exos-x.seagate.com
volumeBindingMode: WaitForFirstConsumer reclaimPolicy: Delete
volumeBindingMode: Immediate
allowVolumeExpansion: true allowVolumeExpansion: true
parameters: parameters:
csi.storage.k8s.io/provisioner-secret-name: seagate-me5-site-b csi.storage.k8s.io/provisioner-secret-name: seagate-me5-site-b
@@ -13,10 +14,6 @@ parameters:
csi.storage.k8s.io/controller-expand-secret-name: seagate-me5-site-b csi.storage.k8s.io/controller-expand-secret-name: seagate-me5-site-b
csi.storage.k8s.io/controller-expand-secret-namespace: seagate csi.storage.k8s.io/controller-expand-secret-namespace: seagate
csi.storage.k8s.io/fstype: ext4 csi.storage.k8s.io/fstype: ext4
pool: dg02 pool: pool
volPrefix: szb volPrefix: szb
storageProtocol: iscsi storageProtocol: iscsi
allowedTopologies:
- matchLabelExpressions:
- key: topology.kubernetes.io/zone
values: ["site-b"]

View File

@@ -1,36 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: test-http
# annotations:
# metallb.universe.tf/address-pool: default
spec:
# type: NodePort
type: LoadBalancer
loadBalancerIP: 192.168.200.10
selector:
app: test-http
ports:
- port: 80
targetPort: 80
# nodePort: 30080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-http
spec:
replicas: 1
selector:
matchLabels:
app: test-http
template:
metadata:
labels:
app: test-http
spec:
containers:
- name: test-http
image: nginx:alpine
ports:
- containerPort: 80

View File

@@ -0,0 +1,16 @@
apiVersion: velero.io/v1
kind: BackupStorageLocation
metadata:
name: bsl-incluster-a
spec:
provider: aws
objectStorage:
bucket: velero-backups
config:
s3Url: http://minio-a.minio-velero.svc.cluster.local:9000
s3ForcePathStyle: "true"
region: site-a
accessMode: ReadWrite
credential:
name: cloud-credentials
key: cloud

View File

@@ -0,0 +1,16 @@
apiVersion: velero.io/v1
kind: BackupStorageLocation
metadata:
name: bsl-incluster-b
spec:
provider: aws
objectStorage:
bucket: velero-backups
config:
s3Url: http://minio-b.minio-velero.svc.cluster.local:9000
s3ForcePathStyle: "true"
region: site-b
accessMode: ReadWrite
credential:
name: cloud-credentials
key: cloud

View File

@@ -1,16 +0,0 @@
apiVersion: velero.io/v1
kind: BackupStorageLocation
metadata:
name: default
namespace: velero
spec:
provider: aws
objectStorage:
bucket: velero
config:
region: minio
s3Url: https://s3-a.c2et.net
s3ForcePathStyle: "true"
credential:
name: cloud-credentials-site-a
key: cloud

View File

@@ -1,16 +0,0 @@
apiVersion: velero.io/v1
kind: BackupStorageLocation
metadata:
name: site-b
namespace: velero
spec:
provider: aws
objectStorage:
bucket: velero
config:
region: minio
s3Url: https://s3-b.c2et.net
s3ForcePathStyle: "true"
credential:
name: cloud-credentials-site-b
key: cloud

View File

@@ -0,0 +1,47 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: minio-a
labels:
app.kubernetes.io/name: minio
app.kubernetes.io/instance: minio-a
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: minio
app.kubernetes.io/instance: minio-a
template:
metadata:
labels:
app.kubernetes.io/name: minio
app.kubernetes.io/instance: minio-a
spec:
securityContext:
fsGroup: 1000
containers:
- name: minio
image: quay.io/minio/minio:latest
args: ["server", "/data", "--console-address", ":9001"]
envFrom:
- secretRef:
name: minio-root
ports:
- name: s3
containerPort: 9000
- name: console
containerPort: 9001
volumeMounts:
- name: data
mountPath: /data
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "2"
memory: "4Gi"
volumes:
- name: data
persistentVolumeClaim:
claimName: minio-a-data

View File

@@ -0,0 +1,47 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: minio-b
labels:
app.kubernetes.io/name: minio
app.kubernetes.io/instance: minio-b
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: minio
app.kubernetes.io/instance: minio-b
template:
metadata:
labels:
app.kubernetes.io/name: minio
app.kubernetes.io/instance: minio-b
spec:
securityContext:
fsGroup: 1000
containers:
- name: minio
image: quay.io/minio/minio:latest
args: ["server", "/data", "--console-address", ":9001"]
envFrom:
- secretRef:
name: minio-root
ports:
- name: s3
containerPort: 9000
- name: console
containerPort: 9001
volumeMounts:
- name: data
mountPath: /data
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "2"
memory: "4Gi"
volumes:
- name: data
persistentVolumeClaim:
claimName: minio-b-data

View File

@@ -1,36 +0,0 @@
credentials:
useSecret: true
existingSecret: ""
secretContents:
cloud: |
[default]
aws_access_key_id=velero-a
aws_secret_access_key=Clave-Velero-A
configuration:
features: EnableCSI
backupStorageLocation:
- name: default
provider: aws
bucket: velero
config:
region: minio
s3Url: https://s3-a.c2et.net
s3ForcePathStyle: "true"
initContainers:
- name: velero-plugin-for-aws
image: velero/velero-plugin-for-aws:v1.9.0
imagePullPolicy: IfNotPresent
volumeMounts:
- name: plugins
mountPath: /target
- name: velero-plugin-for-csi
image: velero/velero-plugin-for-csi:v0.7.0
imagePullPolicy: IfNotPresent
volumeMounts:
- name: plugins
mountPath: /target
nodeAgent:
enabled: true

View File

@@ -1,30 +0,0 @@
# values-combined.yaml
credentials:
useSecret: false # Secrets y BSLs los aplicas tú por YAML (como ya hiciste)
configuration:
features: ""
backupStorageLocation: [] # ninguno desde Helm (los gestionas por YAML)
defaultVolumesToFsBackup: true # copia datos de PV vía node-agent/Kopia al BSL
# Dejamos SOLO el plugin de AWS; el CSI externo se quita (viene integrado en Velero 1.16)
initContainers:
- name: velero-plugin-for-aws
image: velero/velero-plugin-for-aws:v1.9.0
imagePullPolicy: IfNotPresent
volumeMounts:
- name: plugins
mountPath: /target
# **activar** el node-agent (DaemonSet) y darle tolerations "catch-all"
deployNodeAgent: true
nodeAgent:
podConfig:
tolerations:
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
- operator: "Exists" # tolera cualquier otro taint

View File

@@ -0,0 +1,50 @@
apiVersion: batch/v1
kind: Job
metadata:
name: minio-a-init
namespace: minio-velero
spec:
backoffLimit: 3
template:
spec:
restartPolicy: OnFailure
containers:
- name: init
image: bitnami/minio-client:latest
command: ["/bin/sh","-lc"]
env:
- name: MINIO_ENDPOINT
value: "http://minio-a.minio-velero.svc.cluster.local:9000"
- name: MINIO_ROOT_USER
valueFrom:
secretKeyRef:
name: minio-root
key: MINIO_ROOT_USER
- name: MINIO_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: minio-root
key: MINIO_ROOT_PASSWORD
args:
- |
set -euo pipefail
echo "[init-a] waiting for $MINIO_ENDPOINT ..."
until curl -sf "$MINIO_ENDPOINT/minio/health/ready" >/dev/null; do sleep 2; done
echo "[init-a] configuring bucket/user/policy"
mc alias set minioA "$MINIO_ENDPOINT" "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"
mc mb minioA/velero-backups || true
mc version enable minioA/velero-backups || true
mc admin user add minioA velero Velero12345 || true
cat > /tmp/policy.json <<'EOF'
{ "Version":"2012-10-17",
"Statement":[
{"Effect":"Allow","Action":["s3:*"],
"Resource":["arn:aws:s3:::velero-backups","arn:aws:s3:::velero-backups/*"]}
]}
EOF
mc admin policy create minioA velero-rw /tmp/policy.json || true
mc admin policy attach minioA velero-rw --user velero || true
echo "[init-a] verifying with velero creds"
mc alias set a-vel "$MINIO_ENDPOINT" velero Velero12345
mc ls a-vel/velero-backups >/dev/null
echo "[init-a] done"

View File

@@ -0,0 +1,50 @@
apiVersion: batch/v1
kind: Job
metadata:
name: minio-b-init
namespace: minio-velero
spec:
backoffLimit: 3
template:
spec:
restartPolicy: OnFailure
containers:
- name: init
image: bitnami/minio-client:latest
command: ["/bin/sh","-lc"]
env:
- name: MINIO_ENDPOINT
value: "http://minio-b.minio-velero.svc.cluster.local:9000"
- name: MINIO_ROOT_USER
valueFrom:
secretKeyRef:
name: minio-root
key: MINIO_ROOT_USER
- name: MINIO_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: minio-root
key: MINIO_ROOT_PASSWORD
args:
- |
set -euo pipefail
echo "[init-b] waiting for $MINIO_ENDPOINT ..."
until curl -sf "$MINIO_ENDPOINT/minio/health/ready" >/dev/null; do sleep 2; done
echo "[init-b] configuring bucket/user/policy"
mc alias set minioB "$MINIO_ENDPOINT" "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"
mc mb minioB/velero-backups || true
mc version enable minioB/velero-backups || true
mc admin user add minioB velero Velero12345 || true
cat > /tmp/policy.json <<'EOF'
{ "Version":"2012-10-17",
"Statement":[
{"Effect":"Allow","Action":["s3:*"],
"Resource":["arn:aws:s3:::velero-backups","arn:aws:s3:::velero-backups/*"]}
]}
EOF
mc admin policy create minioB velero-rw /tmp/policy.json || true
mc admin policy attach minioB velero-rw --user velero || true
echo "[init-b] verifying with velero creds"
mc alias set b-vel "$MINIO_ENDPOINT" velero Velero12345
mc ls b-vel/velero-backups >/dev/null
echo "[init-b] done"

21
velero/kustomization.yaml Normal file
View File

@@ -0,0 +1,21 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: minio-velero
resources:
- namespace.yaml
- secrets/minio-root.yaml
- secrets/cloud-credentials.yaml
- networkpolicies/default-deny-ingress.yaml
- networkpolicies/allow-self-to-minio.yaml
- pvcs/minio-a-pvc.yaml
- deployments/minio-a.yaml
- services/minio-a.yaml
- jobs/minio-a-init.yaml
- pvcs/minio-b-pvc.yaml
- deployments/minio-b.yaml
- services/minio-b.yaml
- jobs/minio-b-init.yaml
- backupstoragelocations/bsl-a.yaml
- backupstoragelocations/bsl-b.yaml

View File

@@ -1,92 +0,0 @@
{
"annotations": {
"list": []
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"panels": [
{
"type": "stat",
"title": "Backups - Total",
"targets": [
{
"expr": "sum(velero_backup_total)",
"legendFormat": "total"
}
],
"id": 1,
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
]
}
}
},
{
"type": "timeSeries",
"title": "Backups por estado",
"targets": [
{
"expr": "sum by (phase) (increase(velero_backup_attempt_total[1h]))",
"legendFormat": "{{phase}}"
}
],
"id": 2,
"datasource": {
"type": "prometheus",
"uid": "prometheus"
}
},
{
"type": "timeSeries",
"title": "Duraci\u00f3n de backups (p95)",
"targets": [
{
"expr": "histogram_quantile(0.95, sum(rate(velero_backup_duration_seconds_bucket[5m])) by (le))",
"legendFormat": "p95"
}
],
"id": 3,
"datasource": {
"type": "prometheus",
"uid": "prometheus"
}
},
{
"type": "timeSeries",
"title": "Errores del node-agent",
"targets": [
{
"expr": "sum(rate(velero_node_agent_errors_total[5m]))",
"legendFormat": "errores"
}
],
"id": 4,
"datasource": {
"type": "prometheus",
"uid": "prometheus"
}
}
],
"schemaVersion": 37,
"style": "dark",
"tags": [
"velero",
"backup"
],
"templating": {
"list": []
},
"time": {
"from": "now-24h",
"to": "now"
},
"title": "Velero (MinIO S3)",
"version": 1
}

View File

@@ -1,16 +0,0 @@
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: velero
namespace: velero
labels:
release: prometheus # ajusta al selector de tu Prometheus
spec:
selector:
matchLabels:
app.kubernetes.io/name: velero
namespaceSelector:
matchNames: ["velero"]
endpoints:
- port: metrics
interval: 30s

View File

@@ -1,4 +1,4 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: velero name: minio-velero

View File

@@ -0,0 +1,19 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-self-to-minio
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: minio
policyTypes: ["Ingress"]
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: minio-velero
ports:
- protocol: TCP
port: 9000
- protocol: TCP
port: 9001

View File

@@ -0,0 +1,7 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes: ["Ingress"]

View File

@@ -0,0 +1,10 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: minio-a-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: sc-me5-site-a
resources:
requests:
storage: 5Ti

View File

@@ -0,0 +1,10 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: minio-b-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: sc-me5-site-b
resources:
requests:
storage: 5Ti

Some files were not shown because too many files have changed in this diff Show More