Compare commits

...

29 Commits

Author SHA1 Message Date
aa976406e4 a ver 2025-09-04 23:20:28 +02:00
11da827e52 configurado external, dynu-updater, repo y coredns con cariño 2025-09-04 23:19:05 +02:00
23c556938b añadido script clientes 2025-09-04 00:39:48 +02:00
3bfbd99958 Actualizar readme.md 2025-09-03 22:23:46 +00:00
1d06b6bb37 Actualizar readme.md 2025-09-03 22:20:29 +00:00
612b808259 Actualizar readme.md 2025-09-03 22:19:32 +00:00
9991f318fc Actualizar readme.md 2025-09-03 22:19:00 +00:00
acc0b0bb79 Actualizar readme.md 2025-09-03 22:18:06 +00:00
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
4389c53a2c script de limpieza de discos 2025-08-28 13:31:43 +02:00
122 changed files with 2124 additions and 1441 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,9 +33,37 @@ 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.100 cockpit.c2et.com
192.168.0.100 git.c2et.com
192.168.0.100 harbor.c2et.com
192.168.0.100 wireguard.c2et.com
192.168.0.100 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 . 8.8.8.8 1.1.1.1
cache 120 cache 120
# prometheus 0.0.0.0:9153 # <- activa si quieres métricas # prometheus 0.0.0.0:9153 # <- activa si quieres métricas
} }

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,9 @@
apiVersion: v1
data:
.dockerconfigjson: eyJhdXRocyI6eyJoYXJib3IuYzJldC5uZXQiOnsidXNlcm5hbWUiOiJ4YXZvciIsInBhc3N3b3JkIjoiTUBuYWJvMjAyNSIsImVtYWlsIjoiYWRtaW5AYzJldC5uZXQiLCJhdXRoIjoiZUdGMmIzSTZUVUJ1WVdKdk1qQXlOUT09In19fQ==
kind: Secret
metadata:
creationTimestamp: null
name: harbor-regcred
namespace: dynu-updater
type: kubernetes.io/dockerconfigjson

View File

@@ -0,0 +1,4 @@
resources:
- harbor-regcred.yaml
- 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

@@ -4,6 +4,20 @@ metadata:
name: nginx-router-config name: nginx-router-config
namespace: external namespace: external
data: data:
_common.conf: |
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600;
proxy_send_timeout 3600;
client_max_body_size 0;
proxy_redirect off;
proxy_ssl_server_name on;
router.conf: | router.conf: |
server { server {
listen 80 default_server; listen 80 default_server;
@@ -33,3 +47,46 @@ data:
proxy_ssl_verify off; proxy_ssl_verify off;
} }
} }
gitdotcom.conf: |
server {
listen 80;
server_name git.c2et.com;
location / {
include /etc/nginx/conf.d/_common.conf;
proxy_pass http://192.168.0.40:3001;
}
}
wireguarddotcom.conf: |
server {
listen 80;
server_name wireguard.c2et.com;
location / {
include /etc/nginx/conf.d/_common.conf;
proxy_pass http://192.168.0.40:51821;
}
}
harbordotcom.conf: |
server {
listen 80;
server_name harbor.c2et.com;
location / {
include /etc/nginx/conf.d/_common.conf;
proxy_pass http://192.168.0.40:85;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
}
}
cockpitdotcom.conf: |
server {
listen 80;
server_name cockpit.c2et.com;
location / {
proxy_pass https://192.168.0.40:9090;
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

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

@@ -0,0 +1,28 @@
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"
nginx.ingress.kubernetes.io/upstream-vhost: "wireguard.c2et.com"
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

@@ -114,6 +114,13 @@ Este repositorio contiene los **manifiestos, scripts y documentación** para des
* Montado sobre dos **almacenes S3** (Minio), uno por **SITE** * Montado sobre dos **almacenes S3** (Minio), uno por **SITE**
* Cada almacen en una cabina de almacenamiento **(DriverCSI)** * Cada almacen en una cabina de almacenamiento **(DriverCSI)**
### 5.4. Repositorio interno
* **Repo**
* De momento, los repos de OpenSUSE 15.6 (ampliable)
* Servidor HTTP/HTTPS para centralizar las descargas
* servidor Samba para replicas a traves de diodo de datos
--- ---
## 6. 📚 Índice de documentos y referencias cruzadas ## 6. 📚 Índice de documentos y referencias cruzadas
@@ -131,6 +138,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,8 +151,9 @@ 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) |
| `dynu-updater\readme.md` | Manual de Dynu-updater | [Ver](./dynu-updater/readme.md) |
| `repo\readme.md` | Manual del repo SUSE | [Ver](./repo/readme.md) |
--- ---
## 7. 📊 Estado actual de la instalación ## 7. 📊 Estado actual de la instalación
@@ -157,18 +166,20 @@ Este repositorio contiene los **manifiestos, scripts y documentación** para des
| `Volumenes persistentes` | ✅ Completado | Rook Ceph a 4 nodos, falta ampliar a 5 nodos | [https://ceph.c2et.net](https://ceph.c2et.net/) | admin / Pozuelo12345 | | `Volumenes persistentes` | ✅ Completado | Rook Ceph a 4 nodos, falta ampliar a 5 nodos | [https://ceph.c2et.net](https://ceph.c2et.net/) | admin / Pozuelo12345 |
| `Volumenes persistentes` | ✅ Completado | Driver para las cabinas de almacenamiendo DEEL Powervault | | | | `Volumenes persistentes` | ✅ Completado | Driver para las cabinas de almacenamiendo DEEL Powervault | | |
| `Maquinas Virtuales` | ✅ Completado | Desplegado kubevirt, dashboard e isoserver | [https://kubevirt.c2et.net](https://kubevirt.c2et.net/) <br>[https://isoserver.c2et.net](https://isoserver.c2et.net/) | - | | `Maquinas Virtuales` | ✅ Completado | Desplegado kubevirt, dashboard e isoserver | [https://kubevirt.c2et.net](https://kubevirt.c2et.net/) <br>[https://isoserver.c2et.net](https://isoserver.c2et.net/) | - |
| `Wireguard` | ✅ Completado | Funcionando | [https://wireguard.c2et.net](https://wireguard.c2et.net/) | Pozuelo12345 | | `Wireguard` | ✅ Completado | version con acceso a 0.0 y a 200.0 | [https://wireguard.c2et.net](https://wireguard.c2et.net/) | Pozuelo12345 |
| `CoreDNS` | ✅ Completado | Funcionando | | | | `CoreDNS` | ✅ Completado | Split DNS interno configurado en los host fisicos | | |
| `Apolo` | ✅ Completado | Funcionando | [https://portal.apolo.c2et.net](https://portal.apolo.c2et.net/) | admin / 123456 | | `Apolo` | ✅ Completado | Funcionando, falta probar streaming | [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 pero no esta Ismael (solo estoy yo)| [https://harbor.c2et.net](https://harbor.c2et.net) | |
| `Guacamole` | ✅ Completado | Funcionando | [https://heimdall.c2et.net](https://heimdall.c2et.net) | | | `Guacamole` | ✅ Completado | Funcionando, pero esta en blanco (hay que crear los hosts) | [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 con mapa de España de prueba | [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 los servicios de docker | [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 sin clientes configurados | [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 | Copia de seguridad diaria de dynu y semanal de todo | | |
| `Velero` | ✅ Completado | Funcionando | | | | `Dynu-updater` | ✅ Completado | Funcionando, actualiza el grupo "Trabajo" | | |
| `Repo` | ✅ Completado | Probando a ver si funciona | [http://repo.c2et.net/](https://repo.c2et.net) | |
--- ---

View File

@@ -0,0 +1,100 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: repo-sources
namespace: repo
data:
# Lista de orígenes a espejar
# Formato por línea: NAME|URL|SUBDIR
# SUBDIR cuelga de /mirror/repos (asegúrate de que tu Deployment monta el PVC en /usr/local/apache2/htdocs con subPath repos)
sources.txt: |
# openSUSE Leap 15.6 (básicos + updates)
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
# Codecs openh264
openh264|http://codecs.opensuse.org/openh264/openSUSE_Leap/|opensuse/openh264
# Terceros (opcional)
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
# Claves públicas a publicar en /mirror/keys
# Formato por línea: NAME|URL|FILENAME
# Ajusta las URLs si prefieres otras fuentes/ubicaciones oficiales
keys.txt: |
opensuse|https://download.opensuse.org/repositories/openSUSE:/Leap:/15.6:/Update/standard/repodata/repomd.xml.key|RPM-GPG-KEY-openSUSE
nvidia|https://download.nvidia.com/opensuse/repodata/repomd.xml.key|RPM-GPG-KEY-NVIDIA
k8s|https://pkgs.k8s.io/core:/stable:/v1.33/rpm/repodata/repomd.xml.key|RPM-GPG-KEY-k8s
# Script de sincronización diario (CronJob)
# - Sincroniza repos a /mirror/repos/...
# - Publica claves GPG en /mirror/keys
sync.sh: |
#!/usr/bin/env bash
set -euo pipefail
SRC_LIST="/config/sources.txt"
KEYS_LIST="/config/keys.txt"
DEST_ROOT="/mirror/repos"
DEST_KEYS="/mirror/keys"
mkdir -p "$DEST_ROOT" "$DEST_KEYS"
# Requisitos
command -v wget >/dev/null 2>&1 || { echo "ERROR: wget requerido"; exit 1; }
if ! command -v rsync >/dev/null 2>&1; then
echo "Aviso: rsync no disponible; usaré wget para HTTP/HTTPS"
fi
echo "===== SYNC REPOS ====="
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
# Sincronización eficiente por rsync (si el mirror lo soporta)
rsync -aH --delete --partial --info=stats1,progress2 "${URL}" "${DEST}/"
else
# Mirror vía HTTP/HTTPS con wget
TMP="${DEST}.tmp"
mkdir -p "$TMP"
# -m (mirror), -np (no subir), -nH (sin host en ruta), robots=off
wget -m -np -nH -e robots=off -P "$TMP" --no-verbose --show-progress "$URL"
# Mover contenido espeljado a DEST (limpiando y dejando estructura limpia)
shopt -s dotglob nullglob
if compgen -G "$TMP/*" >/dev/null; then
rsync -a --delete "$TMP"/ "$DEST"/
fi
rm -rf "$TMP"
fi
# Permisos legibles por httpd y Samba
chmod -R a+rX "$DEST"
done < "$SRC_LIST"
echo "===== SYNC KEYS ====="
if [[ -f "$KEYS_LIST" ]]; then
while IFS='|' read -r KNAME KURL KFILE; do
[[ -z "${KNAME:-}" || "${KNAME:0:1}" == "#" ]] && continue
echo "==> Key ${KNAME} (${KURL}) -> ${DEST_KEYS}/${KFILE}"
wget -q -O "${DEST_KEYS}/${KFILE}.tmp" "$KURL"
mv "${DEST_KEYS}/${KFILE}.tmp" "${DEST_KEYS}/${KFILE}"
chmod a+r "${DEST_KEYS}/${KFILE}"
done < "$KEYS_LIST"
else
echo "No hay KEYS_LIST ($KEYS_LIST), omitido."
fi
echo "===== DONE ====="

View File

@@ -0,0 +1,144 @@
#!/usr/bin/env bash
set -euo pipefail
# ============================
# Configuración (ajusta aquí)
# ============================
BASE_URL="${BASE_URL:-http://repo.c2et.net}" # tu dominio del mirror (sin / al final)
LEAP_VER="${LEAP_VER:-15.6}" # versión de Leap
ENABLE_NVIDIA="${ENABLE_NVIDIA:-true}" # true/false
ENABLE_K8S="${ENABLE_K8S:-true}" # true/false
DISABLE_EXTERNAL="${DISABLE_EXTERNAL:-true}" # true/false (deshabilitar repos externos)
# Rutas base del mirror interno
REPO_BASE="${BASE_URL}/opensuse/leap/${LEAP_VER}"
KEYS_BASE="${BASE_URL}/keys"
# Directorio de repos dnf/zypp
REPOS_DIR="/etc/zypp/repos.d"
# ============================
# Helpers
# ============================
need_root() {
if [[ $EUID -ne 0 ]]; then
echo "Este script debe ejecutarse como root (o con sudo)." >&2
exit 1
fi
}
have_cmd() {
command -v "$1" >/dev/null 2>&1
}
write_repo() {
local alias="$1" name="$2" baseurl="$3" gpgkey="$4"
local path="${REPOS_DIR}/${alias}.repo"
cat >"${path}.tmp" <<EOF
[${alias}]
name=${name}
enabled=1
autorefresh=1
baseurl=${baseurl}
type=rpm-md
gpgcheck=1
gpgkey=${gpgkey}
EOF
# Solo mueve si cambió (idempotente)
if [[ ! -f "${path}" ]] || ! cmp -s "${path}.tmp" "${path}"; then
mv "${path}.tmp" "${path}"
echo " - Escrito ${path}"
else
rm -f "${path}.tmp"
echo " - Sin cambios ${path}"
fi
}
import_key() {
local url="$1" file="key-$(basename "$url")"
# rpm ignora si ya está importada; esto es idempotente
echo " - Importando clave: $url"
rpm --import "$url" || {
echo " * Aviso: no se pudo importar $url. ¿Hay conectividad al mirror?" >&2
return 1
}
}
disable_external_repos() {
echo "Deshabilitando repos externos conocidos..."
# Deshabilita TODO lo que no sea *_local que creemos, de forma segura:
# Busca todos los alias actuales y deshabilita los que no terminen en '-local'
local aliases
aliases=$(zypper --non-interactive lr -u | awk 'NR>2 {print $1,$2}' | tail -n +1 | awk '{print $2}')
for a in $aliases; do
if [[ "$a" != *-local ]]; then
# Algunos sistemas traen alias con espacios; saltamos los complicados
if [[ "$a" =~ ^[A-Za-z0-9._:-]+$ ]]; then
zypper --non-interactive mr -d "$a" || true
fi
fi
done
}
# ============================
# Main
# ============================
need_root
if ! have_cmd zypper; then
echo "No se encontró zypper. ¿Es openSUSE/SLE este sistema?" >&2
exit 1
fi
echo "== Configurando repos locales desde ${BASE_URL} para Leap ${LEAP_VER} =="
mkdir -p "$REPOS_DIR"
# 1) Importa claves GPG desde tu mirror
echo "Importando claves GPG desde ${KEYS_BASE} ..."
import_key "${KEYS_BASE}/RPM-GPG-KEY-openSUSE" || true
$ENABLE_NVIDIA && import_key "${KEYS_BASE}/RPM-GPG-KEY-NVIDIA" || true
$ENABLE_K8S && import_key "${KEYS_BASE}/RPM-GPG-KEY-k8s" || true
# 2) Repos base de openSUSE
echo "Escribiendo archivos .repo para repos locales..."
write_repo "repo-oss-local" "repo-oss-local" "${REPO_BASE}/oss" "${KEYS_BASE}/RPM-GPG-KEY-openSUSE"
write_repo "repo-non-oss-local" "repo-non-oss-local" "${REPO_BASE}/non-oss" "${KEYS_BASE}/RPM-GPG-KEY-openSUSE"
write_repo "update-oss-local" "update-oss-local" "${REPO_BASE}/update/oss" "${KEYS_BASE}/RPM-GPG-KEY-openSUSE"
write_repo "update-non-oss-local" "update-non-oss-local" "${REPO_BASE}/update/non-oss" "${KEYS_BASE}/RPM-GPG-KEY-openSUSE"
write_repo "update-sle-local" "update-sle-local" "${REPO_BASE}/update/sle" "${KEYS_BASE}/RPM-GPG-KEY-openSUSE"
write_repo "update-backports-local" "update-backports-local" "${REPO_BASE}/update/backports" "${KEYS_BASE}/RPM-GPG-KEY-openSUSE"
write_repo "openh264-local" "openh264-local" "${BASE_URL}/opensuse/openh264" "${KEYS_BASE}/RPM-GPG-KEY-openSUSE"
# 3) Repos de terceros (opcional)
if $ENABLE_NVIDIA; then
write_repo "nvidia-local" "nvidia-local" \
"${BASE_URL}/thirdparty/nvidia/leap/${LEAP_VER}" \
"${KEYS_BASE}/RPM-GPG-KEY-NVIDIA"
fi
if $ENABLE_K8S; then
write_repo "k8s-stable-local" "k8s-stable-local" \
"${BASE_URL}/thirdparty/kubernetes/core/stable/v1.33/rpm" \
"${KEYS_BASE}/RPM-GPG-KEY-k8s"
fi
# 4) Deshabilitar repos externos si procede
if $DISABLE_EXTERNAL; then
disable_external_repos
fi
# 5) Refrescar repos (no interactivo)
echo "Refrescando repos..."
zypper --non-interactive --gpg-auto-import-keys ref || true
echo
echo "== Listado final de repos =="
zypper lr -d || true
echo
echo "Listo. Si quieres personalizar:"
echo " BASE_URL=... LEAP_VER=... ENABLE_NVIDIA=true/false ENABLE_K8S=true/false DISABLE_EXTERNAL=true/false \\"
echo " sudo -E ./$(basename "$0")"

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

71
repo/readme.md Normal file
View File

@@ -0,0 +1,71 @@
# Repositorio Privado openSUSE
Este despliegue en Kubernetes crea un **mirror interno de repositorios de openSUSE** (y de terceros opcionales, como NVIDIA o Kubernetes). Sirve para que los servidores de nuestra red se actualicen **desde dentro**, sin depender de internet.
El sistema funciona con:
* **Servidor HTTP/HTTPS** → los clientes SUSE acceden vía `http://repo.c2et.net/...` o `https://repo.c2et.net/...` para descargar paquetes y metadatos.
* **Servidor Samba (SMB)** → expone la misma carpeta por red. Esto nos permite que el **“diodo de datos”** copie los repos de manera unidireccional hacia la red clasificada. Así aseguramos que las máquinas en la red sensible reciben actualizaciones sin conectividad exterior.
La carpeta de repos se actualiza automáticamente cada día mediante un **CronJob**, que sincroniza contra los repos oficiales de openSUSE y de terceros.
---
## Cómo desplegarlo
1. Ajusta **dominio** en el Ingress y (si quieres) IP fija en el Service de Samba.
2. Revisa tamaño de **PVC** (mínimo 300GB recomendado).
3. (Opcional) Cambia o amplía la lista en `sources.txt` (por ejemplo, usando mirrors con `rsync://`).
4. Despliega todo de una vez con **Kustomize**:
```bash
kubectl apply -k repo/
```
*(Si prefieres, aún puedes aplicar los manifiestos uno por uno en el orden indicado en la carpeta `repo/`.)*
5. Para lanzar una sincronización inicial manual (sin esperar al cron):
```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 los clientes no hace falta configurar repos manualmente. Basta con ejecutar el **script de cliente** incluido en este repo (`configure-local-repos.sh`). Este script:
* Importa las claves GPG desde `http://repo.c2et.net/keys/`.
* Crea los `.repo` apuntando al mirror interno.
* Deshabilita los repos externos para que solo se usen los `-local`.
### Uso del script en el cliente
```bash
chmod +x configure-local-repos.sh
sudo ./configure-local-repos.sh
```
Esto deja el sistema listo para trabajar solo con los repos locales.
---
## Ventajas de esta arquitectura
* **Seguridad**: los clientes nunca salen a internet, solo acceden al repo interno.
* **Control**: el mirror se actualiza de forma programada (p. ej. de madrugada). Siempre sabemos qué versiones están disponibles.
* **Simplicidad**: los clientes usan HTTP/HTTPS estándar; el Ingress se encarga del TLS si hace falta.
* **Integración con el diodo**: gracias a Samba, la carpeta puede replicarse unidireccionalmente hacia la red clasificada.
* **Verificación**: zypper siempre valida las firmas GPG de los paquetes, aunque se distribuyan por HTTP.
---
## Sugerencias y mejoras
* Usar **mirrors oficiales con rsync** para ahorrar ancho de banda y tiempo de sincronización.
* Añadir `--bwlimit` en el `sync.sh` si queremos limitar consumo nocturno de ancho de banda.
* Sustituir `httpd` por `nginx` si se busca mayor rendimiento en descargas masivas.
* Proteger el Ingress con autenticación si se expone fuera de la red de confianza.
* Mantener el **script de cliente** actualizado para simplificar el alta de repos en todos los servidores SUSE.

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

38
rook/borrar_discos.sh Executable file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# ⚠️ AJUSTA ESTA LISTA A TUS DISCOS DE CEPH (NUNCA el del sistema)
DISKS=(sdb sdc sdd sde sdf sdg)
echo "Discos objetivo:"
printf ' - /dev/%s\n' "${DISKS[@]}"
echo
read -r -p "¿Seguro que quieres LIMPIAR estos discos? Escribe 'SI' para continuar: " ok
[[ "$ok" == "SI" ]] || { echo "Cancelado."; exit 1; }
for d in "${DISKS[@]}"; do
dev="/dev/$d"
echo ">>> Limpiando $dev"
# 0) Opcional: si vienen de un cluster Ceph viejo, intenta zappearlos con ceph-volume
if command -v ceph-volume >/dev/null 2>&1; then
sudo ceph-volume lvm zap --destroy "$dev" || true
fi
# 1) GPT/MBR
sudo sgdisk --zap-all "$dev" || true
# 2) Firmas de FS/LVM/RAID
sudo wipefs -a "$dev" || true
# 3) TRIM (si soporta). Si falla, hacemos un “zero header” de 10 MiB.
if ! sudo blkdiscard -f "$dev"; then
sudo dd if=/dev/zero of="$dev" bs=1M count=10 oflag=direct conv=fsync || true
fi
# 4) Limpieza de particiones fantasma en el kernel
sudo partprobe "$dev" || true
echo ">>> $dev limpiado."
done
echo "Hecho."

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,28 +105,22 @@ 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:
@@ -131,43 +128,80 @@ spec:
- 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

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