Primer commit

This commit is contained in:
2025-04-27 00:26:25 +00:00
commit 6884e2e379
93 changed files with 3478 additions and 0 deletions

109
docs/arquitectura.md Normal file
View File

@ -0,0 +1,109 @@
# 🏗️ Arquitectura del clúster K3s
Este documento describe la arquitectura del clúster Kubernetes desplegado con K3s, su distribución de roles, y los componentes principales.
---
## 🧱 Nodos y roles
### 🖥️ tartaro (192.168.1.7)
- Rol: Control plane principal
- Prioridad Keepalived: MASTER
- Ejecuta:
- K3s (cluster-init)
- Ingress Controller
- cert-manager
- Gitea
- Argo CD
- App of Apps
- Guacamole
- KubeVirt Operator
- VS Code Server
- Servicios principales del clúster
---
### 🖥️ styx (192.168.1.8)
- Rol: Segundo nodo control plane
- Prioridad Keepalived: BACKUP
- Ejecuta:
- K3s (join al control plane)
- Ingress Controller (DaemonSet)
- Réplicas de servicios distribuidos
- Nodo tolerante a caída del master
---
### 🖥️ niflheim (192.168.1.10)
- Rol: Nodo dedicado a almacenamiento y tareas pesadas
- Taint: `storage=only:NoSchedule`
- Ejecuta:
- K3s (con taint para evitar pods no relacionados)
- ZFS + NFS server
- NFS client provisioner
- Volúmenes persistentes para el resto del clúster
- ISO server (para KubeVirt)
- Posibles futuras tareas de backup, observabilidad, etc.
---
## 🔁 Alta disponibilidad
Se implementa con:
- **Bonding de red**: Interfaz `bond0` en todos los nodos.
- **IP virtual (192.168.1.9)** gestionada por `keepalived`.
- Redirección de puertos 80/443 desde el router a esta IP.
- Ingress Controller desplegado como `DaemonSet` para disponibilidad inmediata en todos los nodos.
---
## 🧱 Almacenamiento
- **Backend principal**: ZFS pool `k8spool/k8s` montado en `/mnt/storage/k8s` (en niflheim).
- **NFS server** exporta este almacenamiento a todos los nodos.
- **nfs-subdir-external-provisioner** permite provisión dinámica de volúmenes desde Kubernetes.
---
## 🔐 TLS e Ingress
- **cert-manager** gestiona certificados con Let's Encrypt.
- **Ingress-NGINX** funciona como controlador de entrada.
- TLS completamente automatizado.
- Ingress de cada aplicación define el hostname correspondiente.
---
## 🌀 GitOps
- **Gitea** aloja todos los manifiestos como repositorios individuales.
- **Argo CD** sincroniza automáticamente los repositorios con el clúster.
- **App of Apps** en `k8s-master` gestiona la orquestación general.
---
## 🖥️ Virtualización
- **KubeVirt** permite ejecutar VMs dentro de Kubernetes.
- **CDI (Containerized Data Importer)** permite importar imágenes ISO.
- **ISO Server** expone imágenes desde NFS o HTTP.
- **Guacamole** permite acceso remoto vía navegador a VMs (VNC, RDP, SSH).
---
## 🔧 Herramientas de soporte
- **VS Code Server** accesible por navegador para edición remota en el clúster.
- **Scripts auxiliares** en la carpeta `docs/` para gestión de repositorios.
---
## 🧪 Recomendaciones
- Etiquetar nodos con `kubectl label node <node> <key>=<value>`
- Usar `taints` para separar roles claramente
- Separar StorageClass por aplicación si se desea control granular
- Automatizar despliegue con Argo CD y sincronización automática
---

60
docs/direccionamiento.md Normal file
View File

@ -0,0 +1,60 @@
# ⚔️ Valhalla Cluster - Homelab Personal (Infraestructura Actual)
## 📊 Tabla de Direccionamiento y VLANes
### 🌐 Segmentación por VLANes
| VLAN | Rango IP | Nombre | Uso principal |
|:-----|:-------------------|:-------------------|:--------------|
| VLAN 1 | 192.168.1.0/24 | Administración y acceso exterior | Gestión de infraestructura, acceso a Kubernetes, tráfico externo |
| VLAN 30 | 192.168.3.0/24 | Almacenamiento | Tráfico dedicado de almacenamiento NFS (sobre volúmenes ZFS replicados) |
| VLAN 40 | 192.168.4.0/24 | Tráfico Interno Kubernetes | Comunicación interna entre nodos Kubernetes (API Server, etcd, servicios internos) |
---
## 📡 Diseño de Red y Resolución de Nombres
- **Puerta de enlace principal**: 192.168.1.1 (Router Ubiquiti).
- **DNS interno**: CoreDNS desplegado en Kubernetes.
- **DNS externos**: Cloudflare (1.1.1.1) y Google (8.8.8.8).
- **Almacenamiento**: Exportado vía NFS desde Niflheim (ZFS RAID10), replicado a futuro hacia Gehena mediante `zfs send/receive`.
- **Alta Disponibilidad de Almacenamiento**: IP flotante `192.168.3.10` en VLAN 30 gestionada por Keepalived.
- **Alta Disponibilidad del Control Plane**: IP flotante `192.168.1.9` en VLAN 1 gestionada por Keepalived.
---
## 🧭 Tabla de Direccionamiento por Dispositivo
## 🧭 Tabla de Direccionamiento por Dispositivo
| Dispositivo | VLAN 1 (Admin/Acceso) | VLAN 30 (Storage) | VLAN 40 (Internode) | Observaciones |
|:----------------------------|:----------------------|:------------------|:--------------------|:--------------|
| Router Ubiquiti | 192.168.1.1 | — | — | Puerta de enlace principal |
| Switch QNAP 10GbE | 192.168.1.2 | — | — | Switch principal de 10Gbps |
| Switch MikroTik | 192.168.1.3 | — | — | Switch auxiliar / expansión |
| iLO de Niflheim | 192.168.1.4 | — | — | Gestión remota de servidor |
| iLO de Gehena | 192.168.1.5 | — | — | Gestión remota futuro nodo |
| IP Virtual Keepalived CP | 192.168.1.10 | — | — | IP flotante Kubernetes Control Plane |
| IP Virtual Keepalived Storage | — | 192.168.3.10 (planificado) | — | IP flotante almacenamiento NFS |
| Tartaro (Nodo Kubernetes) | 192.168.1.11 | 192.168.3.1 | 192.168.4.1 | Nodo K3s |
| Styx (Nodo Kubernetes) | 192.168.1.12 | 192.168.3.2 | 192.168.4.2 | Nodo K3s |
| Niflheim (Storage principal) | 192.168.1.13 | 192.168.3.3 | 192.168.4.3 | Nodo NFS + Provisioner K3s |
| Gehena (Futuro) | 192.168.1.14 (planificado) | 192.168.3.4 (planificado) | 192.168.4.4 (planificado) | Segundo nodo de almacenamiento |
| Respaldo Tartaro | 192.168.1.20 | — | — | Conexión alternativa de emergencia |
| Respaldo Styx | 192.168.1.21 | — | — | Conexión alternativa de emergencia |
| Respaldo Niflheim | 192.168.1.22 | — | — | Conexión alternativa de emergencia |
| Respaldo Gehena | 192.168.1.23 (planificado) | — | — | Conexión alternativa de emergencia |
---
## 🔒 Conclusiones
- **Separación total de tráfico**: tráfico de usuario/API Server, almacenamiento, y comunicación interna de nodos segregados en VLANs independientes.
- **Alta disponibilidad** en almacenamiento y control plane mediante Keepalived.
- **IP de emergencia configurada** en interfaces físicas sueltas (`enp87s0`) para Tartaro, Styx y Niflheim, asegurando acceso fuera del bonding en caso de fallo.
- **Almacenamiento NFS montado sobre ZFS RAID10 replicable** a futuro, asegurando integridad de datos y resiliencia de infraestructura.
- Preparado para expansión multi-site mediante VPN y replicación cruzada de almacenamiento.
---

469
docs/guia-instalacion.md Normal file
View File

@ -0,0 +1,469 @@
# 📘 Guía de instalación del clúster K3s - Valhalla Cluster
## 1. Instalar el sistema operativo
En los tres servidores:
- Configurar **bond0** como interfaz principal.
- Crear un **bridge `br0`** sobre `bond0` para tráfico sin NAT de VLAN 1 (administración y acceso).
- Montar **VLAN 30 (almacenamiento)** y **VLAN 40 (internode)**.
- Configurar **una interfaz de respaldo** (`enp88s0`, `eno1`, etc.) con IP secundaria en VLAN 1.
### Asignación de IPs principales:
| Nodo | VLAN 1 (Administración) | VLAN 30 (Storage) | VLAN 40 (Internode) |
|:-----------|:------------------------|:------------------|:--------------------|
| tartaro | 192.168.1.11 | 192.168.3.1 | 192.168.4.1 |
| styx | 192.168.1.12 | 192.168.3.2 | 192.168.4.2 |
| niflheim | 192.168.1.13 | 192.168.3.3 | 192.168.4.3 |
---
## 📄 Netplan para cada servidor
### Tartaro (`/etc/netplan/00-installer-config.yaml`):
```yaml
network:
version: 2
ethernets:
enp2s0f0np0: {}
enp2s0f1np1: {}
enp88s0:
addresses:
- "192.168.1.8/24"
bonds:
bond0:
interfaces:
- enp2s0f0np0
- enp2s0f1np1
parameters:
mode: "802.3ad"
lacp-rate: "fast"
transmit-hash-policy: "layer3+4"
vlans:
bond0.30:
id: 30
link: bond0
addresses:
- "192.168.3.1/24"
bond0.40:
id: 40
link: bond0
addresses:
- "192.168.4.1/24"
bridges:
br0:
interfaces:
- bond0
addresses:
- "192.168.1.11/24"
nameservers:
addresses:
- 192.168.1.1
- 1.1.1.1
- 8.8.8.8
search: []
routes:
- to: "default"
via: "192.168.1.1"
```
---
### Styx (`/etc/netplan/00-installer-config.yaml`):
```yaml
network:
version: 2
ethernets:
enp2s0f0np0: {}
enp2s0f1np1: {}
enp88s0:
addresses:
- "192.168.1.21/24"
bonds:
bond0:
interfaces:
- enp2s0f0np0
- enp2s0f1np1
parameters:
mode: "802.3ad"
lacp-rate: "fast"
transmit-hash-policy: "layer3+4"
vlans:
bond0.30:
id: 30
link: bond0
addresses:
- "192.168.3.2/24"
bond0.40:
id: 40
link: bond0
addresses:
- "192.168.4.2/24"
bridges:
br0:
interfaces:
- bond0
addresses:
- "192.168.1.12/24"
nameservers:
addresses:
- 192.168.1.1
- 1.1.1.1
- 8.8.8.8
search: []
routes:
- to: "default"
via: "192.168.1.1"
```
---
### Niflheim (`/etc/netplan/00-installer-config.yaml`):
```yaml
network:
version: 2
ethernets:
ens1f0: {}
ens1f1: {}
eno1:
addresses:
- "192.168.1.22/24"
bonds:
bond0:
interfaces:
- ens1f0
- ens1f1
parameters:
mode: "802.3ad"
lacp-rate: "fast"
transmit-hash-policy: "layer3+4"
vlans:
bond0.30:
id: 30
link: bond0
addresses:
- "192.168.3.3/24"
bond0.40:
id: 40
link: bond0
addresses:
- "192.168.4.3/24"
bridges:
br0:
interfaces:
- bond0
addresses:
- "192.168.1.13/24"
nameservers:
addresses:
- 192.168.1.1
- 1.1.1.1
- 8.8.8.8
search: []
routes:
- to: "default"
via: "192.168.1.1"
```
---
## 2. Preparación básica
En **todos los servidores**:
```bash
sudo apt update && sudo apt upgrade -y
sudo apt install -y keepalived nfs-common
```
> Asegúrate de tener los manifiestos clonados desde Gitea o preparados localmente antes de empezar.
---
## 2. Preparación básica
En **todos los servidores**:
```bash
sudo apt update && sudo apt upgrade -y
sudo apt install -y keepalived nfs-common
```
> Asegúrate de tener los manifiestos clonados desde Gitea o preparados localmente antes de empezar.
---
### Configuración de ZFS en `niflheim`
1. Instalar ZFS:
sudo apt install -y zfsutils-linux
2. Crear el pool ZFS con los 4 discos Toshiba (RAID10 con 2 espejos):
sudo zpool create -o ashift=12 k8spool \
mirror /dev/sda /dev/sdb \
mirror /dev/sdc /dev/sde
3. Crear dataset:
sudo zfs create k8spool/k8s
sudo zfs set mountpoint=/mnt/storage/k8s k8spool/k8s
sudo zfs set compression=lz4 k8spool/k8s
sudo chown nobody:nogroup /mnt/storage/k8s
4. Verificar:
sudo zpool status
sudo zfs list
sudo zfs get compression k8spool/k8s
---
## 3. Configuración de Keepalived
En tartaro (MASTER):
`sudo nano /etc/keepalived/keepalived.conf`
vrrp_instance VI_1 {
state MASTER
interface br0
virtual_router_id 51
priority 150
advert_int 1
authentication {
auth_type PASS
auth_pass 42manabo42
}
virtual_ipaddress {
192.168.1.10/24
}
}
En styx (BACKUP):
`sudo nano /etc/keepalived/keepalived.conf`
vrrp_instance VI_1 {
state BACKUP
interface br0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 42manabo42
}
virtual_ipaddress {
192.168.1.10/24
}
}
Después en ambos:
sudo systemctl enable keepalived
sudo systemctl start keepalived
## 4. Instalar K3s
En `tartaro` (control plane principal):
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--cluster-init --disable traefik \
--node-name tartaro \
--node-ip 192.168.4.1 \
--advertise-address 192.168.4.1 \
--tls-san 192.168.1.10 \
--tls-san 192.168.1.11 \
--write-kubeconfig-mode 644" sh -
Obtener token:
sudo cat /var/lib/rancher/k3s/server/node-token
En `styx` (segundo nodo):
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --disable traefik \
--node-name styx \
--node-ip 192.168.4.2 \
--advertise-address 192.168.4.2 \
--tls-san 192.168.1.10 \
--tls-san 192.168.1.12" \
K3S_URL=https://192.168.1.10:6443 \
K3S_TOKEN="token" \
sh -
En `niflheim` (control plane adicional, dedicado exclusivamente a almacenamiento):
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --disable traefik \
--node-name niflheim \
--node-ip 192.168.4.3 \
--advertise-address 192.168.4.3 \
--tls-san 192.168.1.10 \
--tls-san 192.168.1.13 \
--node-taint storage=only:NoSchedule" \
K3S_URL=https://192.168.1.10:6443 \
K3S_TOKEN="token" \
sh -
### Verificar estado del clúster
Desde tartaro (o con el kubeconfig copiado):
kubectl get nodes
En styx, niflheim para permitir acceso al kubeconfig:
sudo chmod 644 /etc/rancher/k3s/k3s.yaml
## 5. Instalar driver de almacenamiento.
En **Tartaro** (o en el nodo donde previamente hayamos clonado los repositorios):
cd ~/k3s/k8s-storage/
kubectl apply -k .
Comprobar:
kubectl get pods -n nfs-provisioner
## 6. Desplegar sistema automatizado de Ingress
### Redirección de puertos desde el router
- Hay que hacer port forwarding de puertos externos 80 y 443 a la IP virtual de Keepalived (192.168.1.9)
- El `NodePort` está configurado en el manifiesto como:
- 30080 → 80 (HTTP)
- 30443 → 443 (HTTPS)
- Por lo tanto la redireccion sera:
- de 80 a 192.168.1.9:30080
- de 443 a 192.168.1.9:30443
>Si necesitamos ver los puertos en uso podemos listarlos por la via rapida con el comando:
kubectl get svc --all-namespaces -o jsonpath="{range .items[*]}{.metadata.namespace}:{.metadata.name} → {.spec.ports[*].nodePort}{'\n'}{end}" | grep -v "→ $"
### Desplegar cert-manager
cd ~/k3s/k8s-cert-manager/
kubectl apply -f namespace.yaml
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
kubectl apply -f clusterissuer-staging.yaml
kubectl apply -f clusterissuer-prod.yaml
### Desplegar ingress-controller
cd ~/k3s/k8s-ingress-controller/
kubectl apply -k .
## 7. Desplegar Gitea manualmente
### En Tartaro (o en el nodo donde hayas copiado los repositorios)
cd ~/k3s/k8s-gitea/
kubectl apply -k .
Comprueba que los pods estén en estado `Running`:
kubectl get pods -n gitea -w
>Con acceso ya a gitea, seria el momento de crear todos los repositorios remotos. Es una buena idea apoyarnos en [git-publish](herramienta%20git-publish.md).
>Si tambien te has hartado de teclear git-publish, tambien tenemos un script para ti: [publicar-todos](herramienta%20publicar-todos.md)
## 8. Instalar ArgoCD
### En Tartaro (o donde tengamos los manifiestos locales clonados de Gitea)
cd ~/k3s/k8s-argocd/
kubectl apply -f namespace.yaml
# Instalar ArgoCD desde manifiesto oficial (26000 líneas aprox)
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl apply -f services/argocd.yaml
kubectl apply -f ingress/ingress.yaml
### Acceder
>Crear acceso en NPM es lo mas adecuado.
Se puede obtener la contraseña de admin con:
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d && echo
### Crear la App of Apps
En la interfaz web:
1. Name: app-of-apps (todo en minúsculas)
2. Project: default
3. Repository URL: el repositorio k8s-master en Gitea: https://git.manabo.org/xavor/k8s-master.git
4. Path: apps
5. Cluster URL: https://kubernetes.default.svc
6. Namespace: argocd
7. Sync policy: automática
8. Marca las casillas: `AUTO-CREATE NAMESPACE` `PRUNE` `SELF HEAL` `DIRECTORY RECURSE`
## 9. Instalar KubeVirt
export KUBEVIRT_VERSION=$(curl -s https://api.github.com/repos/kubevirt/kubevirt/releases/latest | grep tag_name | cut -d '"' -f 4)
kubectl create namespace kubevirt
kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-operator.yaml
kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml
kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset.yml
export CDI_VERSION=$(curl -s https://api.github.com/repos/kubevirt/containerized-data-importer/releases/latest | grep tag_name | cut -d '"' -f 4)
kubectl create -f https://github.com/kubevirt/containerized-data-importer/releases/download/${CDI_VERSION}/cdi-operator.yaml
kubectl create -f https://github.com/kubevirt/containerized-data-importer/releases/download/${CDI_VERSION}/cdi-cr.yaml
### Comprobar despliegue
kubectl get pods -n kubevirt
### Instalar virtctl (herramienta de cliente)
export KUBEVIRT_VERSION=$(curl -s https://api.github.com/repos/kubevirt/kubevirt/releases/latest | grep tag_name | cut -d '"' -f 4)
curl -L -o virtctl https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/virtctl-${KUBEVIRT_VERSION}-linux-amd64
chmod +x virtctl
sudo mv virtctl /usr/local/bin/
### 9.1 Configurar Multus y las redes virtuales
En cada nodo:
sudo rm -f /etc/cni/net.d/*
sudo find /etc/cni/net.d/ -type f -exec rm {} \;
sudo cp /var/lib/rancher/k3s/agent/etc/cni/net.d/10-flannel.conflist /etc/cni/net.d/
y en tartaro (o cualquier nodo)
kubectl -n kube-system delete pod -l app=multus
## 10. Desplegar servidor HTTP para ISOs (KubeVirt ISO Server)
cd ~/k3s/k8s-kubevirt-isoserver/
kubectl apply -k .
## 11. Desplegar Apache Guacamole
cd ~/k3s/k8s-guacamole/
kubectl apply -k .
>⚠️ Es necesario inyectar manualmente el esquema SQL de la base de datos tras el despliegue.
## Inyectar full-schema.sql
cd ~/k3s/k8s-guacamole/
kubectl cp full-schema.sql -n guacamole \
$(kubectl get pod -n guacamole -l app=mysql -o jsonpath="{.items[0].metadata.name}"):/full-schema.sql
kubectl exec -n guacamole deploy/mysql -- \
bash -c "mysql -u root -pguacroot guacamole_db < /full-schema.sql"
## Comprobación
kubectl exec -n guacamole deploy/mysql -it -- \
mysql -uguacuser -pguacpass -D guacamole_db -e \
"SELECT name FROM guacamole_entity WHERE type='USER';"
>Usuario/pass por defecto: ```guacadmin/guacadmin```

View File

@ -0,0 +1,52 @@
# git-bajartodos
📥 Descarga (`git pull`) los últimos cambios de todos los repositorios `k8s-*`.
## ¿Qué hace?
Este script entra en cada carpeta que empieza por `k8s-` y ejecuta `git pull`, trayendo los cambios remotos a tu copia local.
## 1. Crea el script
Copia este contenido en ~/bin/git-publish (crea la carpeta ~/bin si no existe):
#!/bin/bash
for dir in k8s-*; do
if [ -d "$dir/.git" ]; then
echo "📦 Entrando en $dir"
cd "$dir"
git pull
cd ..
fi
done
Hazlo ejecutable:
chmod +x ~/bin/git-bajartodos
## 2. Añade ~/bin a tu PATH si no lo tienes
Edita ~/.bashrc (o ~/.zshrc si usas Zsh) y añade:
export PATH="$HOME/bin:$PATH"
Y recarga:
source ~/.bashrc
## Uso
./git-bajartodos
## Requisitos
- Tener acceso al remoto (vía SSH o HTTPS).
- No tener conflictos sin resolver en el repositorio local.
## Ejemplo de salida
📦 Entrando en k8s-npm
Already up to date.
## Notas
- Si hay conflictos al hacer `pull`, Git te lo indicará y deberás resolverlos manualmente.
- Este script no sube nada, solo sincroniza **desde** el remoto. Para subir cambios locales, usa `git-subirtodos`.

View File

@ -0,0 +1,45 @@
# 🛠Creacion de herramienta git-childremove
Git-childremove es una herramienta para eliminar los git de las carpetas hijas
## 1. Crea el script
Copia este contenido en ~/bin/git-publish (crea la carpeta ~/bin si no existe):
#!/bin/bash
set -e
ENV_FILE="${HOME}/.gitpublish.env"
if [ ! -f "$ENV_FILE" ]; then
echo "Falta archivo .env en $ENV_FILE"
exit 1# 🛠Creacion de herramienta git-publish
Git-publish es una herramienta para automatizar la creacion y publicacion de repositorios
## 1. Crea el script
Copia este contenido en ~/bin/git-publish (crea la carpeta ~/bin si no existe):
#!/bin/bash
echo "🔍 Buscando carpetas .git en subdirectorios..."
find . -mindepth 2 -type d -name ".git" | while read gitdir; do
echo "🗑️ Eliminando $gitdir"
rm -rf "$gitdir"
done
echo "✅ Todas las carpetas .git han sido eliminadas."
Hazlo ejecutable:
chmod +x ~/bin/git-childremove
## 2. Añade ~/bin a tu PATH si no lo tienes
Edita ~/.bashrc (o ~/.zshrc si usas Zsh) y añade:
export PATH="$HOME/bin:$PATH"
Y recarga:
source ~/.bashrc

View File

@ -0,0 +1,79 @@
# 🛠Creacion de herramienta git-publish
Git-publish es una herramienta para automatizar la creacion y publicacion de repositorios
## 1. Crea el script
Copia este contenido en ~/bin/git-publish (crea la carpeta ~/bin si no existe):
#!/bin/bash
set -e
ENV_FILE="${HOME}/.gitpublish.env"
if [ ! -f "$ENV_FILE" ]; then
echo "Falta archivo .env en $ENV_FILE"
exit 1
fi
source "$ENV_FILE"
REPO_NAME=$(basename "$(pwd)")
REPO_DESC="${1:-Repositorio publicado desde script}"
API_URL="$GITEA_URL/api/v1/user/repos"
echo "Inicializando git..."
git init
git checkout -b main 2>/dev/null || git checkout main
git add .
git commit -m "Primer commit" || echo "(commit ya hecho)"
echo "Creando repo remoto $REPO_NAME en Gitea..."
REPO_EXISTS=$(curl -s -H "Authorization: token $GITEA_TOKEN" "$GITEA_URL/api/v1/repos/$GITEA_USER/$REPO_NAME" | jq -r '.id // empty')
if [ -z "$REPO_EXISTS" ]; then
curl -s -X POST "$API_URL" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\": \"$REPO_NAME\", \"description\": \"$REPO_DESC\", \"private\": false}" \
> /dev/null
echo "Repositorio remoto creado."
else
echo "Repositorio remoto ya existe."
fi
echo "Subiendo código..."
git remote add origin "$GITEA_URL/$GITEA_USER/$REPO_NAME.git" 2>/dev/null || true
git push -u origin main
echo "✔️ Repo $REPO_NAME publicado en $GITEA_URL/$GITEA_USER/$REPO_NAME"
Hazlo ejecutable:
chmod +x ~/bin/git-publish
## 2. Añade ~/bin a tu PATH si no lo tienes
Edita ~/.bashrc (o ~/.zshrc si usas Zsh) y añade:
export PATH="$HOME/bin:$PATH"
Y recarga:
source ~/.bashrc
## 3. Crea tu .env
Archivo ~/.gitpublish.env: (crea y copia tu token en gitea)
GITEA_URL=https://git.manabo.org
GITEA_USER=xavor
GITEA_TOKEN=61a7abcb33ad7bf92454dd5a85c7bfe4becc41eb
## 4. Inicializa valores globales
git config --global user.email "xavor@hotmail.es"
git config --global user.name "xavor"
git config --global credential.helper store
### ✅ Y ahora sí: úsalo
cd ~/k3s/k8s-gitea
git-publish "Manifiestos de Gitea"

View File

@ -0,0 +1,67 @@
# git-subirtodos
🚀 Sube automáticamente todos los cambios locales a los repositorios `git` dentro de carpetas `k8s-*`.
### ¿Qué hace?
Este script busca todas las carpetas que empiecen por `k8s-`, detecta si hay cambios locales sin commitear, y si los hay:
1. Añade todos los cambios (`git add .`)
2. Hace un commit con un mensaje genérico
3. Ejecuta `git push` al remoto
## 1. Crea el script
Copia este contenido en ~/bin/git-publish (crea la carpeta ~/bin si no existe):
#!/bin/bash
for dir in k8s-*; do
if [ -d "$dir/.git" ]; then
echo "📦 Entrando en $dir"
cd "$dir"
if ! git diff --quiet || ! git diff --cached --quiet; then
echo "✅ Cambios detectados en $dir. Haciendo commit y push..."
git add .
git commit -m "Actualización automática desde git-subirtodos"
git push
else
echo "⚠️ No hay cambios en $dir"
fi
cd ..
fi
done
Hazlo ejecutable:
chmod +x ~/bin/git-subirtodos
## 2. Añade ~/bin a tu PATH si no lo tienes
Edita ~/.bashrc (o ~/.zshrc si usas Zsh) y añade:
export PATH="$HOME/bin:$PATH"
Y recarga:
source ~/.bashrc
## Uso
./git-subirtodos
## Requisitos
- Tener permisos de escritura en todos los repositorios `k8s-*`.
- Estar en una rama con remoto configurado (`origin`).
- Tener acceso por SSH o con credenciales ya configuradas.
## Ejemplo de salida
tree
📦 Entrando en k8s-gitea
✅ Cambios detectados en k8s-gitea. Haciendo commit y push...
## Notas
- Si no hay cambios, el script te lo indicará.
- No se realiza `pull`, solo subida. Usa `git-bajartodos` si quieres sincronizar desde remoto.

View File

@ -0,0 +1,22 @@
# Script: publicar-todos.sh
Si no hay ganas de teclear todos los git-publish de todos los repositorios, guarda este script como ```publicar-todos.sh```
#!/bin/bash
# Recorre todas las carpetas que empiecen por k8s-
for dir in k8s-*; do
if [ -d "$dir" ]; then
echo "📦 Entrando en $dir"
cd "$dir"
git-publish "Manifiestos de ${dir}"
cd ..
fi
done
Y dale permisos de ejecucion
chmod +x publicar-todos.sh
Hazlo en la carpeta donde tengas los repositorios y subira todas las carpetas que empiecen por ```k8s-*``` con el comando:
./publicar-todos.sh

View File

@ -0,0 +1,74 @@
# 🛠️ Herramientas auxiliares
Esta sección recoge los scripts y utilidades que facilitan el trabajo con los repositorios Git del clúster, automatizando tareas como publicar, subir o clonar todos los manifiestos gestionados por Argo CD y Gitea.
---
## `git-publish`
Publica un repositorio local en Gitea automáticamente.
Crea el repositorio remoto (si no existe), hace `init`, `add`, `commit`, crea la rama `main` y hace `push`.
📝 Documentación: [`herramienta git-publish.md`](herramienta%20git-publish.md)
---
## `publicar-todos.sh`
Script que recorre todas las carpetas de servicios (`k8s-*`) y ejecuta `git-publish` en cada una de ellas.
Ideal para hacer el primer despliegue completo de manifiestos en Gitea.
📝 Documentación: [`herramienta publicar-todos.md`](herramienta%20publicar-todos.md)
---
## `git-subirtodos`
Hace `git add`, `commit` y `push` en todos los subrepositorios del clúster.
Útil cuando se ha trabajado localmente en varias apps y se quiere subir todo de golpe.
📝 Documentación: [`herramienta git-subirtodos.md`](herramienta%20git-subirtodos.md)
---
## `git-bajartodos`
Clona todos los repositorios hijos (servicios del clúster) desde Gitea.
Permite replicar rápidamente toda la estructura del clúster en un nuevo entorno de desarrollo.
📝 Documentación: [`herramienta git-bajartodos.md`](herramienta%20git-bajartodos.md)
---
## `git-chilremove`
Elimina todos los subrepositorios Git de carpetas hijas (útil para limpiar y reestructurar).
Requiere confirmación para evitar errores destructivos.
📝 Documentación: [`herramienta git-chilremove.md`](herramienta%20git-chilremove.md)
---
## Requisitos
- Token de Gitea guardado en `.env`
- Nombre de usuario y URL base configurados en los scripts
- Estructura de carpetas estándar (`k8s-*` con su `readme.md` y `kustomization.yaml`)
---
## Ejemplo de uso rápido
```bash
# Publicar todos los manifiestos como repos en Gitea
./publicar-todos.sh
# Hacer commit y push masivo
./docs/git-subirtodos.sh
# Clonar todos los servicios desde Gitea
./docs/git-bajartodos.sh

View File

@ -0,0 +1,83 @@
# 📚 Procedimientos adicionales
Aquí se documentan acciones puntuales que pueden ser necesarias durante el mantenimiento o despliegue del clúster, incluyendo soluciones a errores frecuentes o tareas manuales especiales.
---
## 🛠️ Eliminar namespace atascado (en terminación)
### ✅ Diagnóstico confirmado
Si ves un error como este:
...is forbidden: unable to create new content in namespace kubevirt-manager because it is being terminated
Significa que el namespace está atascado en estado de terminación y Kubernetes no puede limpiarlo correctamente.
---
### 🧹 Solución forzada paso a paso
#### 1. Exporta el namespace a un archivo
kubectl get namespace kubevirt-manager -o json > ns.json
#### 2. Edita el archivo ns.json
Abre el archivo con tu editor (nano, vim, code, etc.) y elimina la sección finalizers dentro de spec.
Ejemplo original:
"spec": {
"finalizers": [
"kubernetes"
]
}
Debes dejarlo así:
"spec": {}
Guarda y cierra el archivo.
#### 3. Aplica la eliminación forzada
kubectl replace --raw "/api/v1/namespaces/kubevirt-manager/finalize" -f ./ns.json
✅ Resultado esperado
El namespace kubevirt-manager desaparecerá en unos segundos.
Verifícalo con:
kubectl get ns
#### 4. Vuelve a aplicar los manifiestos si es necesario
kubectl apply -k .
## 📥 Copiar manualmente una ISO al servidor HTTP de KubeVirt
Este procedimiento permite subir manualmente una imagen `.iso` al servidor HTTP que sirve las ISOs en el namespace `kubevirt-isoserver`, para que pueda ser utilizada desde una VM en KubeVirt.
---
### ✅ 1. Identificar la ruta del PVC del servidor HTTP
Ejecuta:
kubectl -n kubevirt-isoserver get pvc
Salida
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
iso-pvc Bound pvc-169349e9-5e0c-4cb1-b7a5-8cd6ddc48be4 800Gi RWX nfs-manabo 17m
La carpeta real en el servidor de almacenamiento será algo como:
/mnt/storage/k8s/nfsshare/kubevirt-isoserver-iso-pvc-pvc-169349e9-5e0c-4cb1-b7a5-8cd6ddc48be4/
### 📤 2. Copiar la imagen desde el servidor origen
Estando en el servidor donde tienes la imagen .iso (ej. 192.168.1.3) y suponiendo que tienes acceso SSH al servidor de almacenamiento (192.168.1.10), ejecuta:
scp /mnt/Iso/Windows.iso xavor@192.168.1.10:/mnt/storage/k8s/nfsshare/kubevirt-isoserver-iso-pvc-pvc-169349e9-5e0c-4cb1-b7a5-8cd6ddc48be4/
Deberías ver el archivo Windows.iso.

View File

@ -0,0 +1,28 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argocd
namespace: argocd
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- argo.manabo.org
secretName: argocd-tls
rules:
- host: argo.manabo.org
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argocd-server
port:
number: 443

View File

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

14
k8s-argocd/readme.md Normal file
View File

@ -0,0 +1,14 @@
# Manifiestos para Argo CD
Este repositorio contiene los manifiestos necesarios para desplegar Argo CD, una herramienta de Continuous Delivery para Kubernetes basada en GitOps.
Se instala en el namespace argocd, utilizando los recursos oficiales del proyecto, y se expone al exterior mediante un servicio NodePort, permitiendo su acceso a través de Nginx Proxy Manager o directamente desde IP pública.
>Argo CD monitoriza los repositorios Git declarativos del clúster y sincroniza automáticamente el estado deseado con el real, facilitando el despliegue, control y actualización de aplicaciones en Kubernetes.
## Despliegue
kubectl apply -f namespace.yaml
# Instalar ArgoCD desde manifiesto oficial (26000 líneas aprox)
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl apply -f services/argocd.yaml

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Service
metadata:
name: argocd-server-nodeport
namespace: argocd
spec:
type: NodePort
selector:
app.kubernetes.io/name: argocd-server
ports:
- name: http
port: 80
targetPort: 8080
nodePort: 32080
- name: https
port: 443
targetPort: 8080
nodePort: 32443

View File

@ -0,0 +1,14 @@
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
email: xavor@hotmail.es
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
ingressClassName: nginx

View File

@ -0,0 +1,14 @@
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
email: xavor@hotmail.es
server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-staging
solvers:
- http01:
ingress:
ingressClassName: nginx

View File

@ -0,0 +1,5 @@
resources:
- clusterissuer-prod.yaml
- clusterissuer-staging.yaml
- namespace.yaml

View File

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

View File

@ -0,0 +1,15 @@
# Cert Manager para Kubernetes
Este repositorio contiene los manifiestos necesarios para desplegar [cert-manager](https://cert-manager.io), una herramienta que automatiza la gestión y renovación de certificados TLS en Kubernetes.
Cert-manager se encarga de emitir y renovar automáticamente certificados mediante ACME (por ejemplo, Let's Encrypt), y es compatible con `Ingress` para habilitar TLS en tus servicios expuestos.
---
## Despliegue
kubectl apply -f namespace.yaml
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
kubectl apply -f clusterissuer-staging.yaml
kubectl apply -f clusterissuer-prod.yaml

View File

@ -0,0 +1,36 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitea-db
namespace: gitea
spec:
replicas: 1
selector:
matchLabels:
app: gitea-db
template:
metadata:
labels:
app: gitea-db
spec:
containers:
- name: mysql
image: mysql:8
env:
- name: MYSQL_ROOT_PASSWORD
value: gitea123
- name: MYSQL_DATABASE
value: gitea
- name: MYSQL_USER
value: gitea
- name: MYSQL_PASSWORD
value: gitea123
ports:
- containerPort: 3306
volumeMounts:
- name: gitea-db
mountPath: /var/lib/mysql
volumes:
- name: gitea-db
persistentVolumeClaim:
claimName: gitea-db

View File

@ -0,0 +1,42 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitea
namespace: gitea
spec:
replicas: 1
selector:
matchLabels:
app: gitea
template:
metadata:
labels:
app: gitea
spec:
containers:
- name: gitea
image: gitea/gitea:latest
ports:
- containerPort: 3000
env:
- name: USER_UID
value: "1000"
- name: USER_GID
value: "1000"
- name: GITEA__database__DB_TYPE
value: "mysql"
- name: GITEA__database__HOST
value: "gitea-db:3306"
- name: GITEA__database__NAME
value: "gitea"
- name: GITEA__database__USER
value: "gitea"
- name: GITEA__database__PASSWD
value: "gitea123"
volumeMounts:
- name: gitea-data
mountPath: /data
volumes:
- name: gitea-data
persistentVolumeClaim:
claimName: gitea-data

View File

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

View File

@ -0,0 +1,9 @@
resources:
- namespace.yaml
- pvc/gitea-data.yaml
- pvc/gitea-db.yaml
- deployments/gitea.yaml
- deployments/gitea-db.yaml
- services/gitea.yaml
- services/gitea-db.yaml
- ingress/ingress.yaml

4
k8s-gitea/namespace.yaml Normal file
View File

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

View File

@ -0,0 +1,12 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gitea-data
namespace: gitea
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
storageClassName: nfs-manabo

View File

@ -0,0 +1,12 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gitea-db
namespace: gitea
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
storageClassName: nfs-manabo

12
k8s-gitea/readme.md Normal file
View File

@ -0,0 +1,12 @@
# Manifiestos para Gitea
Este repositorio contiene los manifiestos necesarios para desplegar Gitea, un servidor Git ligero, en el namespace gitea.
## Despliegue
kubectl apply -f namespace.yaml
kubectl apply -f pvc/gitea-data.yaml
kubectl apply -f pvc/gitea-db.yaml
kubectl apply -f deployments/gitea-db.yaml
kubectl apply -f services/gitea-db.yaml
kubectl apply -f deployments/gitea.yaml
kubectl apply -f services/gitea.yaml

View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: gitea-db
namespace: gitea
spec:
type: ClusterIP
selector:
app: gitea-db
ports:
- port: 3306

View File

@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
name: gitea
namespace: gitea
spec:
type: NodePort
selector:
app: gitea
ports:
- name: http
port: 3000
targetPort: 3000
nodePort: 30300

View File

@ -0,0 +1,37 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: guacamole
namespace: guacamole
spec:
replicas: 1
selector:
matchLabels:
app: guacamole
template:
metadata:
labels:
app: guacamole
spec:
containers:
- name: guacamole
image: guacamole/guacamole
ports:
- containerPort: 8080
env:
- name: MYSQL_HOSTNAME
value: mysql
- name: MYSQL_DATABASE
value: guacamole_db
- name: MYSQL_USER
value: guacuser
- name: MYSQL_PASSWORD
value: guacpass
- name: GUACD_HOSTNAME
value: localhost
- name: MYSQL_PORT
value: "3306"
- name: guacd
image: guacamole/guacd
ports:
- containerPort: 4822

View File

@ -0,0 +1,35 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
namespace: guacamole
spec:
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ROOT_PASSWORD
value: guacroot
- name: MYSQL_DATABASE
value: guacamole_db
- name: MYSQL_USER
value: guacuser
- name: MYSQL_PASSWORD
value: guacpass
ports:
- containerPort: 3306
volumeMounts:
- name: mysql-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-storage
persistentVolumeClaim:
claimName: mysql-pvc

View File

@ -0,0 +1,666 @@
--
-- Licensed to the Apache Software Foundation (ASF) under one
-- or more contributor license agreements. See the NOTICE file
-- distributed with this work for additional information
-- regarding copyright ownership. The ASF licenses this file
-- to you under the Apache License, Version 2.0 (the
-- "License"); you may not use this file except in compliance
-- with the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing,
-- software distributed under the License is distributed on an
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-- KIND, either express or implied. See the License for the
-- specific language governing permissions and limitations
-- under the License.
--
--
-- Table of connection groups. Each connection group has a name.
--
CREATE TABLE `guacamole_connection_group` (
`connection_group_id` int(11) NOT NULL AUTO_INCREMENT,
`parent_id` int(11),
`connection_group_name` varchar(128) NOT NULL,
`type` enum('ORGANIZATIONAL',
'BALANCING') NOT NULL DEFAULT 'ORGANIZATIONAL',
-- Concurrency limits
`max_connections` int(11),
`max_connections_per_user` int(11),
`enable_session_affinity` boolean NOT NULL DEFAULT 0,
PRIMARY KEY (`connection_group_id`),
UNIQUE KEY `connection_group_name_parent` (`connection_group_name`, `parent_id`),
CONSTRAINT `guacamole_connection_group_ibfk_1`
FOREIGN KEY (`parent_id`)
REFERENCES `guacamole_connection_group` (`connection_group_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of connections. Each connection has a name, protocol, and
-- associated set of parameters.
-- A connection may belong to a connection group.
--
CREATE TABLE `guacamole_connection` (
`connection_id` int(11) NOT NULL AUTO_INCREMENT,
`connection_name` varchar(128) NOT NULL,
`parent_id` int(11),
`protocol` varchar(32) NOT NULL,
-- Guacamole proxy (guacd) overrides
`proxy_port` integer,
`proxy_hostname` varchar(512),
`proxy_encryption_method` enum('NONE', 'SSL'),
-- Concurrency limits
`max_connections` int(11),
`max_connections_per_user` int(11),
-- Load-balancing behavior
`connection_weight` int(11),
`failover_only` boolean NOT NULL DEFAULT 0,
PRIMARY KEY (`connection_id`),
UNIQUE KEY `connection_name_parent` (`connection_name`, `parent_id`),
CONSTRAINT `guacamole_connection_ibfk_1`
FOREIGN KEY (`parent_id`)
REFERENCES `guacamole_connection_group` (`connection_group_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of base entities which may each be either a user or user group. Other
-- tables which represent qualities shared by both users and groups will point
-- to guacamole_entity, while tables which represent qualities specific to
-- users or groups will point to guacamole_user or guacamole_user_group.
--
CREATE TABLE `guacamole_entity` (
`entity_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL,
`type` enum('USER',
'USER_GROUP') NOT NULL,
PRIMARY KEY (`entity_id`),
UNIQUE KEY `guacamole_entity_name_scope` (`type`, `name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of users. Each user has a unique username and a hashed password
-- with corresponding salt. Although the authentication system will always set
-- salted passwords, other systems may set unsalted passwords by simply not
-- providing the salt.
--
CREATE TABLE `guacamole_user` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`entity_id` int(11) NOT NULL,
-- Optionally-salted password
`password_hash` binary(32) NOT NULL,
`password_salt` binary(32),
`password_date` datetime NOT NULL,
-- Account disabled/expired status
`disabled` boolean NOT NULL DEFAULT 0,
`expired` boolean NOT NULL DEFAULT 0,
-- Time-based access restriction
`access_window_start` TIME,
`access_window_end` TIME,
-- Date-based access restriction
`valid_from` DATE,
`valid_until` DATE,
-- Timezone used for all date/time comparisons and interpretation
`timezone` VARCHAR(64),
-- Profile information
`full_name` VARCHAR(256),
`email_address` VARCHAR(256),
`organization` VARCHAR(256),
`organizational_role` VARCHAR(256),
PRIMARY KEY (`user_id`),
UNIQUE KEY `guacamole_user_single_entity` (`entity_id`),
CONSTRAINT `guacamole_user_entity`
FOREIGN KEY (`entity_id`)
REFERENCES `guacamole_entity` (`entity_id`)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of user groups. Each user group may have an arbitrary set of member
-- users and member groups, with those members inheriting the permissions
-- granted to that group.
--
CREATE TABLE `guacamole_user_group` (
`user_group_id` int(11) NOT NULL AUTO_INCREMENT,
`entity_id` int(11) NOT NULL,
-- Group disabled status
`disabled` boolean NOT NULL DEFAULT 0,
PRIMARY KEY (`user_group_id`),
UNIQUE KEY `guacamole_user_group_single_entity` (`entity_id`),
CONSTRAINT `guacamole_user_group_entity`
FOREIGN KEY (`entity_id`)
REFERENCES `guacamole_entity` (`entity_id`)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of users which are members of given user groups.
--
CREATE TABLE `guacamole_user_group_member` (
`user_group_id` int(11) NOT NULL,
`member_entity_id` int(11) NOT NULL,
PRIMARY KEY (`user_group_id`, `member_entity_id`),
-- Parent must be a user group
CONSTRAINT `guacamole_user_group_member_parent_id`
FOREIGN KEY (`user_group_id`)
REFERENCES `guacamole_user_group` (`user_group_id`) ON DELETE CASCADE,
-- Member may be either a user or a user group (any entity)
CONSTRAINT `guacamole_user_group_member_entity_id`
FOREIGN KEY (`member_entity_id`)
REFERENCES `guacamole_entity` (`entity_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of sharing profiles. Each sharing profile has a name, associated set
-- of parameters, and a primary connection. The primary connection is the
-- connection that the sharing profile shares, and the parameters dictate the
-- restrictions/features which apply to the user joining the connection via the
-- sharing profile.
--
CREATE TABLE guacamole_sharing_profile (
`sharing_profile_id` int(11) NOT NULL AUTO_INCREMENT,
`sharing_profile_name` varchar(128) NOT NULL,
`primary_connection_id` int(11) NOT NULL,
PRIMARY KEY (`sharing_profile_id`),
UNIQUE KEY `sharing_profile_name_primary` (sharing_profile_name, primary_connection_id),
CONSTRAINT `guacamole_sharing_profile_ibfk_1`
FOREIGN KEY (`primary_connection_id`)
REFERENCES `guacamole_connection` (`connection_id`)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of connection parameters. Each parameter is simply a name/value pair
-- associated with a connection.
--
CREATE TABLE `guacamole_connection_parameter` (
`connection_id` int(11) NOT NULL,
`parameter_name` varchar(128) NOT NULL,
`parameter_value` varchar(4096) NOT NULL,
PRIMARY KEY (`connection_id`,`parameter_name`),
CONSTRAINT `guacamole_connection_parameter_ibfk_1`
FOREIGN KEY (`connection_id`)
REFERENCES `guacamole_connection` (`connection_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of sharing profile parameters. Each parameter is simply
-- name/value pair associated with a sharing profile. These parameters dictate
-- the restrictions/features which apply to the user joining the associated
-- connection via the sharing profile.
--
CREATE TABLE guacamole_sharing_profile_parameter (
`sharing_profile_id` integer NOT NULL,
`parameter_name` varchar(128) NOT NULL,
`parameter_value` varchar(4096) NOT NULL,
PRIMARY KEY (`sharing_profile_id`, `parameter_name`),
CONSTRAINT `guacamole_sharing_profile_parameter_ibfk_1`
FOREIGN KEY (`sharing_profile_id`)
REFERENCES `guacamole_sharing_profile` (`sharing_profile_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of arbitrary user attributes. Each attribute is simply a name/value
-- pair associated with a user. Arbitrary attributes are defined by other
-- extensions. Attributes defined by this extension will be mapped to
-- properly-typed columns of a specific table.
--
CREATE TABLE guacamole_user_attribute (
`user_id` int(11) NOT NULL,
`attribute_name` varchar(128) NOT NULL,
`attribute_value` varchar(4096) NOT NULL,
PRIMARY KEY (user_id, attribute_name),
KEY `user_id` (`user_id`),
CONSTRAINT guacamole_user_attribute_ibfk_1
FOREIGN KEY (user_id)
REFERENCES guacamole_user (user_id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of arbitrary user group attributes. Each attribute is simply a
-- name/value pair associated with a user group. Arbitrary attributes are
-- defined by other extensions. Attributes defined by this extension will be
-- mapped to properly-typed columns of a specific table.
--
CREATE TABLE guacamole_user_group_attribute (
`user_group_id` int(11) NOT NULL,
`attribute_name` varchar(128) NOT NULL,
`attribute_value` varchar(4096) NOT NULL,
PRIMARY KEY (`user_group_id`, `attribute_name`),
KEY `user_group_id` (`user_group_id`),
CONSTRAINT `guacamole_user_group_attribute_ibfk_1`
FOREIGN KEY (`user_group_id`)
REFERENCES `guacamole_user_group` (`user_group_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of arbitrary connection attributes. Each attribute is simply a
-- name/value pair associated with a connection. Arbitrary attributes are
-- defined by other extensions. Attributes defined by this extension will be
-- mapped to properly-typed columns of a specific table.
--
CREATE TABLE guacamole_connection_attribute (
`connection_id` int(11) NOT NULL,
`attribute_name` varchar(128) NOT NULL,
`attribute_value` varchar(4096) NOT NULL,
PRIMARY KEY (connection_id, attribute_name),
KEY `connection_id` (`connection_id`),
CONSTRAINT guacamole_connection_attribute_ibfk_1
FOREIGN KEY (connection_id)
REFERENCES guacamole_connection (connection_id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of arbitrary connection group attributes. Each attribute is simply a
-- name/value pair associated with a connection group. Arbitrary attributes are
-- defined by other extensions. Attributes defined by this extension will be
-- mapped to properly-typed columns of a specific table.
--
CREATE TABLE guacamole_connection_group_attribute (
`connection_group_id` int(11) NOT NULL,
`attribute_name` varchar(128) NOT NULL,
`attribute_value` varchar(4096) NOT NULL,
PRIMARY KEY (connection_group_id, attribute_name),
KEY `connection_group_id` (`connection_group_id`),
CONSTRAINT guacamole_connection_group_attribute_ibfk_1
FOREIGN KEY (connection_group_id)
REFERENCES guacamole_connection_group (connection_group_id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of arbitrary sharing profile attributes. Each attribute is simply a
-- name/value pair associated with a sharing profile. Arbitrary attributes are
-- defined by other extensions. Attributes defined by this extension will be
-- mapped to properly-typed columns of a specific table.
--
CREATE TABLE guacamole_sharing_profile_attribute (
`sharing_profile_id` int(11) NOT NULL,
`attribute_name` varchar(128) NOT NULL,
`attribute_value` varchar(4096) NOT NULL,
PRIMARY KEY (sharing_profile_id, attribute_name),
KEY `sharing_profile_id` (`sharing_profile_id`),
CONSTRAINT guacamole_sharing_profile_attribute_ibfk_1
FOREIGN KEY (sharing_profile_id)
REFERENCES guacamole_sharing_profile (sharing_profile_id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of connection permissions. Each connection permission grants a user or
-- user group specific access to a connection.
--
CREATE TABLE `guacamole_connection_permission` (
`entity_id` int(11) NOT NULL,
`connection_id` int(11) NOT NULL,
`permission` enum('READ',
'UPDATE',
'DELETE',
'ADMINISTER') NOT NULL,
PRIMARY KEY (`entity_id`,`connection_id`,`permission`),
CONSTRAINT `guacamole_connection_permission_ibfk_1`
FOREIGN KEY (`connection_id`)
REFERENCES `guacamole_connection` (`connection_id`) ON DELETE CASCADE,
CONSTRAINT `guacamole_connection_permission_entity`
FOREIGN KEY (`entity_id`)
REFERENCES `guacamole_entity` (`entity_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of connection group permissions. Each group permission grants a user
-- or user group specific access to a connection group.
--
CREATE TABLE `guacamole_connection_group_permission` (
`entity_id` int(11) NOT NULL,
`connection_group_id` int(11) NOT NULL,
`permission` enum('READ',
'UPDATE',
'DELETE',
'ADMINISTER') NOT NULL,
PRIMARY KEY (`entity_id`,`connection_group_id`,`permission`),
CONSTRAINT `guacamole_connection_group_permission_ibfk_1`
FOREIGN KEY (`connection_group_id`)
REFERENCES `guacamole_connection_group` (`connection_group_id`) ON DELETE CASCADE,
CONSTRAINT `guacamole_connection_group_permission_entity`
FOREIGN KEY (`entity_id`)
REFERENCES `guacamole_entity` (`entity_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of sharing profile permissions. Each sharing profile permission grants
-- a user or user group specific access to a sharing profile.
--
CREATE TABLE guacamole_sharing_profile_permission (
`entity_id` integer NOT NULL,
`sharing_profile_id` integer NOT NULL,
`permission` enum('READ',
'UPDATE',
'DELETE',
'ADMINISTER') NOT NULL,
PRIMARY KEY (`entity_id`, `sharing_profile_id`, `permission`),
CONSTRAINT `guacamole_sharing_profile_permission_ibfk_1`
FOREIGN KEY (`sharing_profile_id`)
REFERENCES `guacamole_sharing_profile` (`sharing_profile_id`) ON DELETE CASCADE,
CONSTRAINT `guacamole_sharing_profile_permission_entity`
FOREIGN KEY (`entity_id`)
REFERENCES `guacamole_entity` (`entity_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of system permissions. Each system permission grants a user or user
-- group a system-level privilege of some kind.
--
CREATE TABLE `guacamole_system_permission` (
`entity_id` int(11) NOT NULL,
`permission` enum('CREATE_CONNECTION',
'CREATE_CONNECTION_GROUP',
'CREATE_SHARING_PROFILE',
'CREATE_USER',
'CREATE_USER_GROUP',
'ADMINISTER') NOT NULL,
PRIMARY KEY (`entity_id`,`permission`),
CONSTRAINT `guacamole_system_permission_entity`
FOREIGN KEY (`entity_id`)
REFERENCES `guacamole_entity` (`entity_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of user permissions. Each user permission grants a user or user group
-- access to another user (the "affected" user) for a specific type of
-- operation.
--
CREATE TABLE `guacamole_user_permission` (
`entity_id` int(11) NOT NULL,
`affected_user_id` int(11) NOT NULL,
`permission` enum('READ',
'UPDATE',
'DELETE',
'ADMINISTER') NOT NULL,
PRIMARY KEY (`entity_id`,`affected_user_id`,`permission`),
CONSTRAINT `guacamole_user_permission_ibfk_1`
FOREIGN KEY (`affected_user_id`)
REFERENCES `guacamole_user` (`user_id`) ON DELETE CASCADE,
CONSTRAINT `guacamole_user_permission_entity`
FOREIGN KEY (`entity_id`)
REFERENCES `guacamole_entity` (`entity_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of user group permissions. Each user group permission grants a user
-- or user group access to a another user group (the "affected" user group) for
-- a specific type of operation.
--
CREATE TABLE `guacamole_user_group_permission` (
`entity_id` int(11) NOT NULL,
`affected_user_group_id` int(11) NOT NULL,
`permission` enum('READ',
'UPDATE',
'DELETE',
'ADMINISTER') NOT NULL,
PRIMARY KEY (`entity_id`, `affected_user_group_id`, `permission`),
CONSTRAINT `guacamole_user_group_permission_affected_user_group`
FOREIGN KEY (`affected_user_group_id`)
REFERENCES `guacamole_user_group` (`user_group_id`) ON DELETE CASCADE,
CONSTRAINT `guacamole_user_group_permission_entity`
FOREIGN KEY (`entity_id`)
REFERENCES `guacamole_entity` (`entity_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table of connection history records. Each record defines a specific user's
-- session, including the connection used, the start time, and the end time
-- (if any).
--
CREATE TABLE `guacamole_connection_history` (
`history_id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`username` varchar(128) NOT NULL,
`remote_host` varchar(256) DEFAULT NULL,
`connection_id` int(11) DEFAULT NULL,
`connection_name` varchar(128) NOT NULL,
`sharing_profile_id` int(11) DEFAULT NULL,
`sharing_profile_name` varchar(128) DEFAULT NULL,
`start_date` datetime NOT NULL,
`end_date` datetime DEFAULT NULL,
PRIMARY KEY (`history_id`),
KEY `user_id` (`user_id`),
KEY `connection_id` (`connection_id`),
KEY `sharing_profile_id` (`sharing_profile_id`),
KEY `start_date` (`start_date`),
KEY `end_date` (`end_date`),
KEY `connection_start_date` (`connection_id`, `start_date`),
CONSTRAINT `guacamole_connection_history_ibfk_1`
FOREIGN KEY (`user_id`)
REFERENCES `guacamole_user` (`user_id`) ON DELETE SET NULL,
CONSTRAINT `guacamole_connection_history_ibfk_2`
FOREIGN KEY (`connection_id`)
REFERENCES `guacamole_connection` (`connection_id`) ON DELETE SET NULL,
CONSTRAINT `guacamole_connection_history_ibfk_3`
FOREIGN KEY (`sharing_profile_id`)
REFERENCES `guacamole_sharing_profile` (`sharing_profile_id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- User login/logout history
--
CREATE TABLE guacamole_user_history (
`history_id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`username` varchar(128) NOT NULL,
`remote_host` varchar(256) DEFAULT NULL,
`start_date` datetime NOT NULL,
`end_date` datetime DEFAULT NULL,
PRIMARY KEY (history_id),
KEY `user_id` (`user_id`),
KEY `start_date` (`start_date`),
KEY `end_date` (`end_date`),
KEY `user_start_date` (`user_id`, `start_date`),
CONSTRAINT guacamole_user_history_ibfk_1
FOREIGN KEY (user_id)
REFERENCES guacamole_user (user_id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- User password history
--
CREATE TABLE guacamole_user_password_history (
`password_history_id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
-- Salted password
`password_hash` binary(32) NOT NULL,
`password_salt` binary(32),
`password_date` datetime NOT NULL,
PRIMARY KEY (`password_history_id`),
KEY `user_id` (`user_id`),
CONSTRAINT `guacamole_user_password_history_ibfk_1`
FOREIGN KEY (`user_id`)
REFERENCES `guacamole_user` (`user_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Licensed to the Apache Software Foundation (ASF) under one
-- or more contributor license agreements. See the NOTICE file
-- distributed with this work for additional information
-- regarding copyright ownership. The ASF licenses this file
-- to you under the Apache License, Version 2.0 (the
-- "License"); you may not use this file except in compliance
-- with the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing,
-- software distributed under the License is distributed on an
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-- KIND, either express or implied. See the License for the
-- specific language governing permissions and limitations
-- under the License.
--
-- Create default user "guacadmin" with password "guacadmin"
INSERT INTO guacamole_entity (name, type) VALUES ('guacadmin', 'USER');
INSERT INTO guacamole_user (entity_id, password_hash, password_salt, password_date)
SELECT
entity_id,
x'CA458A7D494E3BE824F5E1E175A1556C0F8EEF2C2D7DF3633BEC4A29C4411960', -- 'guacadmin'
x'FE24ADC5E11E2B25288D1704ABE67A79E342ECC26064CE69C5B3177795A82264',
NOW()
FROM guacamole_entity WHERE name = 'guacadmin';
-- Grant this user all system permissions
INSERT INTO guacamole_system_permission (entity_id, permission)
SELECT entity_id, permission
FROM (
SELECT 'guacadmin' AS username, 'CREATE_CONNECTION' AS permission
UNION SELECT 'guacadmin' AS username, 'CREATE_CONNECTION_GROUP' AS permission
UNION SELECT 'guacadmin' AS username, 'CREATE_SHARING_PROFILE' AS permission
UNION SELECT 'guacadmin' AS username, 'CREATE_USER' AS permission
UNION SELECT 'guacadmin' AS username, 'CREATE_USER_GROUP' AS permission
UNION SELECT 'guacadmin' AS username, 'ADMINISTER' AS permission
) permissions
JOIN guacamole_entity ON permissions.username = guacamole_entity.name AND guacamole_entity.type = 'USER';
-- Grant admin permission to read/update/administer self
INSERT INTO guacamole_user_permission (entity_id, affected_user_id, permission)
SELECT guacamole_entity.entity_id, guacamole_user.user_id, permission
FROM (
SELECT 'guacadmin' AS username, 'guacadmin' AS affected_username, 'READ' AS permission
UNION SELECT 'guacadmin' AS username, 'guacadmin' AS affected_username, 'UPDATE' AS permission
UNION SELECT 'guacadmin' AS username, 'guacadmin' AS affected_username, 'ADMINISTER' AS permission
) permissions
JOIN guacamole_entity ON permissions.username = guacamole_entity.name AND guacamole_entity.type = 'USER'
JOIN guacamole_entity affected ON permissions.affected_username = affected.name AND guacamole_entity.type = 'USER'
JOIN guacamole_user ON guacamole_user.entity_id = affected.entity_id;

View File

@ -0,0 +1,28 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: guacamole
namespace: guacamole
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
nginx.ingress.kubernetes.io/enable-websockets: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- gua.manabo.org
secretName: guacamole-tls
rules:
- host: gua.manabo.org
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: guacamole
port:
number: 80

View File

@ -0,0 +1,12 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- deployments/guacamole-deployment.yaml
- deployments/mysql-deployment.yaml
- pvc/mysql-pvc.yaml
- services/guacamole-service.yaml
- services/mysql-service.yaml
- ingress/ingress.yaml

View File

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

View File

@ -0,0 +1,12 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
namespace: guacamole
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: nfs-manabo

31
k8s-guacamole/readme.md Normal file
View File

@ -0,0 +1,31 @@
# Manifiestos para Guacamole
Este repositorio contiene los manifiestos necesarios para desplegar [Apache Guacamole](https://guacamole.apache.org/) en un clúster Kubernetes. El despliegue incluye:
- Guacamole + Guacd (como contenedores en el mismo `Deployment`)
- MySQL 5.7 como backend de autenticación
- Ingreso expuesto mediante Ingress y cert-manager
- Inyección automatizada del esquema `full-schema.sql` usando un `initContainer` y un `ConfigMap`
> Apache Guacamole es un cliente remoto sin necesidad de plugins, accesible desde navegador, compatible con protocolos como VNC, RDP y SSH.
---
## Despliegue
Aplica todos los manifiestos usando kustomize:
kubectl apply -k .
>Usuario/pass por defecto: ```guacadmin/guacadmin```
## Reinyectar full-schema.sql (por si no sale el configmap)
kubectl cp full-schema.sql -n guacamole $(kubectl get pod -n guacamole -l app=mysql -o jsonpath="{.items[0].metadata.name}"):/full-schema.sql
kubectl exec -n guacamole deploy/mysql -- bash -c "mysql -u root -pguacroot guacamole_db < /full-schema.sql"
## Comprobación
Para verificar que el usuario guacadmin ha sido creado correctamente:
kubectl exec -n guacamole deploy/mysql -it -- \
mysql -uguacuser -pguacpass -D guacamole_db -e \
"SELECT name FROM guacamole_entity WHERE type='USER';"

View File

@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: guacamole
namespace: guacamole
spec:
selector:
app: guacamole
ports:
- protocol: TCP
port: 80
targetPort: 8080

View File

@ -0,0 +1,10 @@
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: guacamole
spec:
ports:
- port: 3306
selector:
app: mysql

View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
data:
allow-snippet-annotations: "true"
enable-ssl-passthrough: "false"
proxy-read-timeout: "3600"
proxy-send-timeout: "3600"
use-forwarded-headers: "true"

View File

@ -0,0 +1,59 @@
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
spec:
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
spec:
serviceAccountName: ingress-nginx
containers:
- name: controller
image: registry.k8s.io/ingress-nginx/controller:v1.9.4
args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
- --election-id=ingress-controller-leader
- --controller-class=k8s.io/ingress-nginx
- --ingress-class=nginx
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- containerPort: 80
name: http
protocol: TCP
- containerPort: 443
name: https
protocol: TCP
- containerPort: 10254
name: metrics
protocol: TCP
readinessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10

View File

@ -0,0 +1,6 @@
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: nginx
spec:
controller: k8s.io/ingress-nginx

View File

@ -0,0 +1,9 @@
resources:
- namespace.yaml
- rbac/clusterrole.yaml
- rbac/clusterrolebinding.yaml
- rbac/serviceaccount.yaml
- configmap/configmap.yaml
- deployments/deployment.yaml
- services/service.yaml
- ingressclass/ingressclass.yaml

View File

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

View File

@ -0,0 +1,62 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- networking.k8s.io
resources:
- ingresses
- ingressclasses
- ingresses/status
verbs:
- get
- list
- watch
- update
- apiGroups:
- coordination.k8s.io
resources:
- leases
verbs:
- get
- create
- update
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- list
- watch

View File

@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ingress-nginx
subjects:
- kind: ServiceAccount
name: ingress-nginx
namespace: ingress-nginx

View File

@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: ingress-nginx
namespace: ingress-nginx

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
type: NodePort
selector:
app.kubernetes.io/name: ingress-nginx
ports:
- name: http
port: 80
targetPort: 80
nodePort: 30080
- name: https
port: 443
targetPort: 443
nodePort: 30443

View File

@ -0,0 +1,37 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: iso-server
namespace: kubevirt
spec:
replicas: 1
selector:
matchLabels:
app: iso-server
template:
metadata:
labels:
app: iso-server
spec:
containers:
- name: httpd
image: httpd:2.4
ports:
- containerPort: 80
volumeMounts:
- name: iso-storage
mountPath: /usr/local/apache2/htdocs
- name: samba
image: dperson/samba
args: ["-p", "-s", "isos;/share;yes;no"]
ports:
- containerPort: 445
securityContext:
runAsUser: 0
volumeMounts:
- name: iso-storage
mountPath: /share
volumes:
- name: iso-storage
persistentVolumeClaim:
claimName: iso-pvc

View File

@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: iso-server
namespace: kubevirt
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
# nginx.ingress.kubernetes.io/whitelist-source-range: "192.168.1.0/24"
spec:
ingressClassName: nginx
tls:
- hosts:
- isoserver.manabo.org
secretName: isoserver-tls
rules:
- host: isoserver.manabo.org
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: iso-server
port:
number: 80

View File

@ -0,0 +1,6 @@
resources:
- namespace.yaml
- pvc/iso-pvc.yaml
- deployments/isoserver.yaml
- services/service.yaml
- ingress/ingress.yaml

View File

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

View File

@ -0,0 +1,12 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: iso-pvc
namespace: kubevirt
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 800Gi
storageClassName: nfs-manabo

View File

@ -0,0 +1,34 @@
# Manifiestos para servidor HTTP de imágenes ISO (KubeVirt ISO Server)
Este repositorio contiene los manifiestos necesarios para desplegar un servidor HTTP basado en Apache (httpd:2.4) que sirve una carpeta compartida con archivos ISO, accesible por KubeVirt para instalar sistemas operativos en máquinas virtuales.
>El almacenamiento se expone desde un servidor externo NFS (por ejemplo, niflheim) con las imágenes ISO ubicadas en /mnt/Iso.
## Requisitos:
- El servidor NFS debe exportar la ruta /mnt/Iso con permisos RO (solo lectura) para la red del clúster.
- Las ISOs deben estar copiadas dentro de /mnt/Iso.
- Debes tener configurado nfs-common en todos los nodos del clúster.
### Despliegue manual:
Entra al repositorio:
cd ~/k3s/k8s-kubevirt-isoserver
Aplica los manifiestos en orden:
kubectl apply -f namespace.yaml
kubectl apply -f pv/iso-pv.yaml
kubectl apply -f pv/iso-pvc.yaml
kubectl apply -f deployments/httpd.yaml
kubectl apply -f services/httpd.yaml
### Acceso:
El servicio httpd queda expuesto por un NodePort que puedes consultar con:
kubectl get svc -n iso-server
>Despues de pasarlo por npm, puedes montar esta URL como cdrom o disk en tus VirtualMachine (VM) manifests, usando por ejemplo:
http://iso-server.manabo.org/windows.iso

View File

@ -0,0 +1,20 @@
apiVersion: v1
kind: Service
metadata:
name: iso-server
namespace: kubevirt
spec:
type: NodePort
selector:
app: iso-server
ports:
- name: http
port: 80
targetPort: 80
nodePort: 30088 # Puerto externo HTTP
- name: samba
port: 445
targetPort: 445
nodePort: 30445 # Puerto externo Samba
externalIPs:
- 192.168.1.9

View File

@ -0,0 +1,24 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubevirt-manager
namespace: kubevirt-manager
spec:
replicas: 1
selector:
matchLabels:
app: kubevirt-manager
template:
metadata:
labels:
app: kubevirt-manager
spec:
serviceAccountName: kubevirt-manager
containers:
- name: kubevirt-manager
image: kubevirtmanager/kubevirt-manager:1.4.0
ports:
- containerPort: 8001
env:
- name: BACKEND_URL
value: "http://localhost:8080"

View File

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

View File

@ -0,0 +1,7 @@
resources:
- namespace.yaml
- deployments/deployment.yaml
- services/service.yaml
- ingress/ingress.yaml
- rbac/serviceaccount.yaml
- rbac/clusterrolebinding.yaml

View File

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

View File

@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubevirt-manager
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: kubevirt-manager
namespace: kubevirt-manager

View File

@ -0,0 +1,6 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: kubevirt-manager
namespace: kubevirt-manager

View File

@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: kubevirt-manager
namespace: kubevirt-manager
spec:
selector:
app: kubevirt-manager
ports:
- port: 80
targetPort: 8001
protocol: TCP
type: ClusterIP

View File

@ -0,0 +1,20 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: k8s-external-router
namespace: argocd
spec:
project: default
source:
repoURL: 'http://192.168.1.9:30300/xavor/k8s-external-router.git' # ← ajusta si hace falta
targetRevision: HEAD
path: .
destination:
server: 'https://kubernetes.default.svc'
namespace: external-router
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@ -0,0 +1,20 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: gitea
namespace: argocd
spec:
project: default
source:
repoURL: 'http://192.168.1.9:30300/xavor/k8s-gitea.git'
targetRevision: HEAD
path: .
destination:
server: 'https://kubernetes.default.svc'
namespace: gitea
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@ -0,0 +1,20 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: guacamole
namespace: argocd
spec:
project: default
source:
repoURL: 'http://192.168.1.9:30300/xavor/k8s-guacamole.git'
targetRevision: HEAD
path: .
destination:
server: 'https://kubernetes.default.svc'
namespace: guacamole
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@ -0,0 +1,20 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: ingress-controller
namespace: argocd
spec:
project: default
source:
repoURL: 'http://192.168.1.9:30300/xavor/k8s-ingress-controller.git'
targetRevision: HEAD
path: .
destination:
server: 'https://kubernetes.default.svc'
namespace: ingress-nginx
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@ -0,0 +1,20 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: kubevirt-isoserver
namespace: argocd
spec:
project: default
source:
repoURL: 'http://192.168.1.9:30300/xavor/k8s-kubevirt-isoserver.git'
targetRevision: HEAD
path: .
destination:
server: 'https://kubernetes.default.svc'
namespace: kubevirt
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@ -0,0 +1,20 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: storage
namespace: argocd
spec:
project: default
source:
repoURL: 'http://192.168.1.9:30300/xavor/k8s-storage.git'
targetRevision: HEAD
path: .
destination:
server: 'https://kubernetes.default.svc'
namespace: storage
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@ -0,0 +1,20 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: k8s-vm-windows-demo
namespace: argocd
spec:
project: default
source:
repoURL: 'http://192.168.1.9:30300/xavor/k8s-vm-windows-demo.git'
targetRevision: HEAD
path: .
destination:
server: 'https://kubernetes.default.svc'
namespace: k8s-vm-windows-demo
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@ -0,0 +1,20 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: vscode
namespace: argocd
spec:
project: default
source:
repoURL: 'http://192.168.1.9:30300/xavor/k8s-vscode.git'
targetRevision: HEAD
path: .
destination:
server: 'https://kubernetes.default.svc'
namespace: vscode
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

4
k8s-master/readme.md Normal file
View File

@ -0,0 +1,4 @@
# Manifiestos para K8S-Master
Este repositorio define una "App de apps" para Argo CD. Actúa como punto central de sincronización del clúster, permitiendo desplegar automáticamente cada aplicación (como Gitea o NPM) de forma desacoplada mediante repositorios independientes.
>No contiene recursos a aplicar directamente con kubectl, sino que se utiliza desde la interfaz o los manifiestos de Argo CD como raíz de sincronización.

View File

@ -0,0 +1,41 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
namespace: nfs-provisioner
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: k8s.gcr.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 192.168.3.3 # VLAN 30 IP de niflheim
- name: NFS_PATH
value: /
- name: LABELS
value: "namespace,pvcName"
volumes:
- name: nfs-client-root
nfs:
server: 192.168.3.3
path: /
tolerations:
- key: "storage"
operator: "Equal"
value: "only"
effect: "NoSchedule"

View File

@ -0,0 +1,43 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-server
namespace: nfs-provisioner
spec:
replicas: 1
selector:
matchLabels:
app: nfs-server
template:
metadata:
labels:
app: nfs-server
spec:
hostNetwork: true
containers:
- name: nfs-server
image: itsthenetwork/nfs-server-alpine:latest
ports:
- name: nfs
containerPort: 2049
protocol: TCP
securityContext:
privileged: true
env:
- name: SHARED_DIRECTORY
value: /nfsshare
volumeMounts:
- name: nfs-data
mountPath: /nfsshare
volumes:
- name: nfs-data
hostPath:
path: /mnt/storage/k8s/nfsshare
type: Directory
tolerations:
- key: "storage"
operator: "Equal"
value: "only"
effect: "NoSchedule"
nodeSelector:
kubernetes.io/hostname: niflheim

View File

@ -0,0 +1,8 @@
resources:
- namespace.yaml
- rbac/clusterrolebinding.yaml
- rbac/clusterrole.yaml
- rbac/serviceaccount.yaml
- deployments/nfs-server.yaml
- deployments/nfs-client-provisioner.yaml
- storageclass/storageclass.yaml

View File

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

View File

@ -0,0 +1,20 @@
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

View File

@ -0,0 +1,12 @@
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: nfs-provisioner
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io

View File

@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: nfs-provisioner

17
k8s-storage/readme.md Normal file
View File

@ -0,0 +1,17 @@
# k8s-storage
Este m<>dulo despliega un driver de almacenamiento din<69>mico basado en NFS, apuntando a un servidor ZFS (`niflheim`, 192.168.1.10) con la ruta `/mnt/storage/k8s`.
## Componentes incluidos
- Namespace `nfs-provisioner`
- RBAC necesario
- Deployment del provisioner din<69>mico
- StorageClass predeterminado `nfs-manabo`
## C<>mo aplicar
kubectl apply -f namespace.yaml
kubectl apply -f rbac/
kubectl apply -f deployment/
kubectl apply -f storageclass/

View File

@ -0,0 +1,9 @@
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-manabo
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
reclaimPolicy: Retain
volumeBindingMode: Immediate

View File

@ -0,0 +1,16 @@
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
name: windows-disk
namespace: k8s-vm-windows-demo
spec:
source:
http:
url: "https://isoserver.manabo.org/Windows.iso"
pvc:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 60Gi
storageClassName: nfs-manabo

View File

@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- virtual-machine.yaml
- data-volume.yaml

View File

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: k8s-vm-windows-demo

View File

@ -0,0 +1,50 @@
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: windows-demo
namespace: k8s-vm-windows-demo
spec:
running: true
template:
metadata:
labels:
kubevirt.io/domain: windows-demo
spec:
domain:
cpu:
cores: 2
devices:
disks:
- name: cdromiso
cdrom:
bus: sata
- name: harddrive
disk:
bus: virtio
- disk:
bus: virtio
name: cloudinitdisk
interfaces:
- name: default
masquerade: {}
machine:
type: q35
resources:
requests:
memory: 4Gi
volumes:
- name: cdromiso
containerDisk:
image: kubevirt/cirros-container-disk-demo # se reemplazará en DataVolume
- name: harddrive
dataVolume:
name: windows-disk
- name: cloudinitdisk
cloudInitNoCloud:
userData: |
#cloud-config
password: "Windows1234"
chpasswd: { expire: False }
networks:
- name: default
pod: {}

View File

@ -0,0 +1,30 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: vscode
namespace: vscode
spec:
replicas: 1
selector:
matchLabels:
app: vscode
template:
metadata:
labels:
app: vscode
spec:
containers:
- name: vscode
image: xavor/vscode-custom:latest
ports:
- containerPort: 8443
env:
- name: PASSWORD
value: "M@nabo2025"
volumeMounts:
- mountPath: /home/coder/project
name: vscode-data
volumes:
- name: vscode-data
persistentVolumeClaim:
claimName: vscode-data

View File

@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vscode
namespace: vscode
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
spec:
ingressClassName: nginx
tls:
- hosts:
- vscode.manabo.org
secretName: vscode-tls
rules:
- host: vscode.manabo.org
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vscode
port:
number: 8080

View File

@ -0,0 +1,6 @@
resources:
- namespace.yaml
- pvc/vscode-pvc.yaml
- deployments/vscode.yaml
- services/vscode.yaml
- ingress/ingress.yaml

View File

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

View File

@ -0,0 +1,12 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: vscode-data
namespace: vscode
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: nfs-manabo

263
k8s-vscode/readme.md Normal file
View File

@ -0,0 +1,263 @@
# 🚀 Virtualización Next-Gen: Clúster K3s con DevOps: manual de instalacion
## 1. Instalar el sistema operativo en ambos servidores
Configurar bonding (bond0) en ambos servidores como interfaz principal.
- Configurar IP fija de la red (ejemplo):
- tartaro: 192.168.1.7
- styx: 192.168.1.8
- niflheim: 192.168.1.10
---
## 2. Preparación básica del sistema
En **todos los servidores**:
sudo apt update && sudo apt upgrade -y
sudo apt install -y keepalived nfs-common
> Asegúrate de tener los manifiestos clonados desde Gitea o preparados localmente antes de empezar.
### Configuración de ZFS en `niflheim`
1. Instalar ZFS:
sudo apt install -y zfsutils-linux
2. Crear el pool ZFS con los 4 discos Toshiba (RAID10 con 2 espejos):
sudo zpool create -o ashift=12 k8spool mirror /dev/sda /dev/sdb mirror /dev/sdc /dev/sde
3. Crear dataset:
sudo zfs create k8spool/k8s
sudo zfs set mountpoint=/mnt/storage/k8s k8spool/k8s
sudo zfs set compression=lz4 k8spool/k8s
sudo chown nobody:nogroup /mnt/storage/k8s
4. Verificar:
sudo zpool status
sudo zfs list
sudo zfs get compression k8spool/k8s
---
## 3. Configuración de Keepalived
En tartaro (MASTER):
`sudo nano /etc/keepalived/keepalived.conf`
vrrp_instance VI_1 {
state MASTER
interface bond0
virtual_router_id 51
priority 150
advert_int 1
authentication {
auth_type PASS
auth_pass 42manabo42
}
virtual_ipaddress {
192.168.1.9/24
}
}
En styx (BACKUP):
`sudo nano /etc/keepalived/keepalived.conf`
vrrp_instance VI_1 {
state BACKUP
interface bond0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 42manabo42
}
virtual_ipaddress {
192.168.1.9/24
}
}
Después en ambos:
sudo systemctl enable keepalived
sudo systemctl start keepalived
## 4. Instalar K3s
En `tartaro` (control plane principal):
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--cluster-init --disable traefik --write-kubeconfig-mode 644 --node-name tartaro --tls-san 192.168.1.7 --tls-san 192.168.1.9" sh -
Obtener token:
sudo cat /var/lib/rancher/k3s/server/node-token
En `styx` (segundo nodo):
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --node-name styx --disable traefik" \
K3S_URL=https://192.168.1.7:6443 \
K3S_TOKEN="token" \
sh -
En `niflheim` (control plane adicional, dedicado exclusivamente a almacenamiento):
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --node-name niflheim --disable traefik --node-taint storage=only:NoSchedule" \
K3S_URL=https://192.168.1.7:6443 \
K3S_TOKEN="token" \
sh -
### Verificar estado del clúster
Desde tartaro (o con el kubeconfig copiado):
kubectl get nodes
En styx, niflheim para permitir acceso al kubeconfig:
sudo chmod 644 /etc/rancher/k3s/k3s.yaml
## 5. Instalar driver de almacenamiento.
En **Tartaro** (o en el nodo donde previamente hayamos clonado los repositorios):
cd ~/k3s/k8s-storage/
kubectl apply -k .
Comprobar:
kubectl get pods -n nfs-provisioner
## 6. Desplegar sistema automatizado de Ingress
### Redirección de puertos desde el router
- Hay que hacer port forwarding de puertos externos 80 y 443 a la IP virtual de Keepalived (192.168.1.9)
- El `NodePort` está configurado en el manifiesto como:
- 30080 → 80 (HTTP)
- 30443 → 443 (HTTPS)
- Por lo tanto la redireccion sera:
- de 80 a 192.168.1.9:30080
- de 443 a 192.168.1.9:30443
>Si necesitamos ver los puertos en uso podemos listarlos por la via rapida con el comando:
kubectl get svc --all-namespaces -o jsonpath="{range .items[*]}{.metadata.namespace}:{.metadata.name} → {.spec.ports[*].nodePort}{'\n'}{end}" | grep -v "→ $"
### Desplegar cert-manager
cd ~/k3s/k8s-cert-manager/
kubectl apply -f namespace.yaml
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
kubectl apply -f clusterissuer-staging.yaml
kubectl apply -f clusterissuer-prod.yaml
### Desplegar ingress-controller
cd ~/k3s/k8s-ingress-controller/
kubectl apply -k .
## 7. Desplegar Gitea manualmente
### En Tartaro (o en el nodo donde hayas copiado los repositorios)
cd ~/k3s/k8s-gitea/
kubectl apply -k .
Comprueba que los pods estén en estado `Running`:
kubectl get pods -n gitea -w
>Con acceso ya a gitea, seria el momento de crear todos los repositorios remotos. Es una buena idea apoyarnos en [git-publish](herramienta%20git-publish.md).
>Si tambien te has hartado de teclear git-publish, tambien tenemos un script para ti: [publicar-todos](herramienta%20publicar-todos.md)
## 8. Instalar ArgoCD
### En Tartaro (o donde tengamos los manifiestos locales clonados de Gitea)
cd ~/k3s/k8s-argocd/
kubectl apply -f namespace.yaml
# Instalar ArgoCD desde manifiesto oficial (26000 líneas aprox)
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl apply -f services/argocd.yaml
kubectl apply -f ingress/ingress.yaml
### Acceder
>Crear acceso en NPM es lo mas adecuado.
Se puede obtener la contraseña de admin con:
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d && echo
### Crear la App of Apps
En la interfaz web:
1. Name: app-of-apps (todo en minúsculas)
2. Project: default
3. Repository URL: el repositorio k8s-master en Gitea: https://git.manabo.org/xavor/k8s-master.git
4. Path: apps
5. Cluster URL: https://kubernetes.default.svc
6. Namespace: argocd
7. Sync policy: automática
8. Marca las casillas: `AUTO-CREATE NAMESPACE` `PRUNE` `SELF HEAL` `DIRECTORY RECURSE`
## 9. Instalar KubeVirt
KubeVirt permite crear y gestionar máquinas virtuales dentro de Kubernetes. Es compatible con el entorno actual y se integra fácilmente con el resto de recursos.
### Instalación manual del operador y CR
Desde cualquier nodo con kubectl configurado, ejecutar:
export KUBEVIRT_VERSION=$(curl -s https://api.github.com/repos/kubevirt/kubevirt/releases/latest | grep tag_name | cut -d '"' -f 4)
kubectl create namespace kubevirt
kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-operator.yaml
kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml
export CDI_VERSION=$(curl -s https://api.github.com/repos/kubevirt/containerized-data-importer/releases/latest | grep tag_name | cut -d '"' -f 4)
kubectl create -f https://github.com/kubevirt/containerized-data-importer/releases/download/${CDI_VERSION}/cdi-operator.yaml
kubectl create -f https://github.com/kubevirt/containerized-data-importer/releases/download/${CDI_VERSION}/cdi-cr.yaml
### Comprobar despliegue
kubectl get pods -n kubevirt
### Instalar virtctl (herramienta de cliente)
Virtctl es una herramienta de línea de comandos para gestionar máquinas virtuales con KubeVirt
export KUBEVIRT_VERSION=$(curl -s https://api.github.com/repos/kubevirt/kubevirt/releases/latest | grep tag_name | cut -d '"' -f 4)
curl -L -o virtctl https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/virtctl-${KUBEVIRT_VERSION}-linux-amd64
chmod +x virtctl
sudo mv virtctl /usr/local/bin/
## 10. Desplegar servidor HTTP para ISOs (KubeVirt ISO Server)
El servidor de ISOs se encarga de exponer vía HTTP las imágenes necesarias para el despliegue de sistemas operativos en máquinas virtuales con KubeVirt.
## 11. Desplegar Apache Guacamole
Guacamole permite conectarse desde el navegador a escritorios remotos a través de protocolos como VNC, RDP o SSH, sin necesidad de plugins. Se despliega con autenticación MySQL y se expone mediante Ingress con TLS automatizado por cert-manager.
>⚠️ Es necesario inyectar manualmente el esquema SQL de la base de datos tras el despliegue.
## Inyectar full-schema.sql
cd ~/k3s/k8s-guacamole/
kubectl cp full-schema.sql -n guacamole \
$(kubectl get pod -n guacamole -l app=mysql -o jsonpath="{.items[0].metadata.name}"):/full-schema.sql
kubectl exec -n guacamole deploy/mysql -- \
bash -c "mysql -u root -pguacroot guacamole_db < /full-schema.sql"
## Comprobación
Para verificar que el usuario guacadmin ha sido creado correctamente:
kubectl exec -n guacamole deploy/mysql -it -- \
mysql -uguacuser -pguacpass -D guacamole_db -e \
"SELECT name FROM guacamole_entity WHERE type='USER';"
>Usuario/pass por defecto: ```guacadmin/guacadmin```

View File

@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: vscode
namespace: vscode
spec:
type: ClusterIP
selector:
app: vscode
ports:
- port: 8080
targetPort: 8080

11
publicar-todos.sh Normal file
View File

@ -0,0 +1,11 @@
#!/bin/bash
# Recorre todas las carpetas que empiecen por k8s-
for dir in k8s-*; do
if [ -d "$dir" ]; then
echo "📦 Entrando en $dir"
cd "$dir"
git-publish "Manifiestos de ${dir}"
cd ..
fi
done

98
readme.md Normal file
View File

@ -0,0 +1,98 @@
# 🚀 Virtualización Next-Gen: Clúster K3s con DevOps
Bienvenido a **Virtualización Next-Gen**, una guía práctica y modular para desplegar un clúster Kubernetes con [K3s](https://k3s.io/), alta disponibilidad, almacenamiento centralizado, ingress con TLS automático y soporte para máquinas virtuales con KubeVirt. Todo está versionado en Git y gestionado con Argo CD.
Este repositorio está orientado tanto a **homelabs avanzados** como a entornos **profesionales** que buscan una infraestructura reproducible, automatizada y documentada.
---
## 🧭 Tabla de contenidos
| Sección | Descripción |
|--------|-------------|
| [📘 Guía de instalación](docs/guia-instalacion.md) | Paso a paso para desplegar el clúster completo. |
| [🏗️ Arquitectura del clúster](docs/arquitectura.md) | Estructura lógica, roles de nodos y servicios desplegados. |
| [🛠️ Herramientas auxiliares](docs/herramientas-auxiliares.md) | Scripts para automatizar tareas como publicar, clonar o subir repos. |
| [📚 Procedimientos adicionales](docs/procedimientos-adicionales.md) | Tareas puntuales como eliminar namespaces atascados o cargas manuales. |
---
## ⚙️ Componentes clave del clúster
- **Alta disponibilidad** con IP flotante (`keepalived`) y bonding de red (`bond0`)
- **K3s cluster** en `tartaro`, `styx` y `niflheim` (dedicado a almacenamiento)
- **ZFS + NFS** como backend de almacenamiento
- **Ingress TLS automatizado** con cert-manager + ingress-nginx
- **Gitea** como Git server para los manifiestos
- **Argo CD** para despliegue GitOps (App of Apps)
- **KubeVirt** para gestionar VMs dentro de Kubernetes
- **Apache Guacamole** para acceso remoto a escritorios vía navegador
- **VS Code Server** desplegado como pod accesible por Ingress
---
## 📂 Estructura del repositorio
Estructura del repositorio:
- docs/: Carpeta de documentación con guías y herramientas auxiliares.
- guia-instalacion.md: Manual paso a paso para desplegar todo el clúster.
- herramienta git-publish.md: Explica el script para publicar repositorios individuales en Gitea.
- herramienta publicar-todos.md: Script para publicar todos los manifiestos del clúster de forma masiva.
- herramienta git-subirtodos.md: Sube todos los cambios a los repositorios remotos.
- herramienta git-bajartodos.md: Clona todos los repositorios configurados.
- herramienta git-chilremove.md: Elimina subrepositorios integrados.
- k8s-argocd/: Manifiestos para desplegar Argo CD, incluyendo Ingress y servicios personalizados.
- k8s-cert-manager/: Manifiestos para instalar cert-manager, incluyendo ClusterIssuer de staging y producción.
- k8s-gitea/: Manifiestos para desplegar Gitea y su base de datos con almacenamiento persistente y acceso externo.
- k8s-guacamole/: Manifiestos para desplegar Apache Guacamole con autenticación MySQL y acceso vía Ingress.
- k8s-ingress-controller/: Despliegue de ingress-nginx como DaemonSet con configuración personalizada, RBAC, y service.
- k8s-kubevirt-isoserver/: Servidor HTTP que expone imágenes ISO para KubeVirt, con su PVC y despliegue.
- k8s-kubevirt-manager/: Despliegue opcional del panel web de KubeVirt con su acceso Ingress y RBAC.
- k8s-master/: Repositorio central con la definición App of Apps de Argo CD (apps/*.yaml para cada servicio).
- k8s-storage/: Despliegue del NFS server y del NFS client provisioner, con su StorageClass y permisos.
- k8s-vm-windows-demo/: Ejemplo de despliegue de una máquina virtual Windows usando KubeVirt.
- k8s-vscode/: Pod de Visual Studio Code Server accesible vía navegador y gestionado por Kubernetes.
- publicar-todos.sh: Script shell para publicar automáticamente todos los manifiestos locales a sus repos Gitea.
- readme.md: Documento principal del repositorio con introducción, tabla de contenidos y enlaces.
---
## 🛠️ Herramientas auxiliares
En la carpeta `docs/` tienes scripts útiles para trabajar con Gitea:
- [`herramienta git-publish.md`](docs/herramienta%20git-publish.md): Publica un repo local en Gitea automáticamente.
- [`herramienta publicar-todos.md`](docs/herramienta%20publicar-todos.md): Publica todos los servicios con un solo comando.
- [`herramienta git-subirtodos.md`](docs/herramienta%20git-subirtodos.md): Hace `push` de todos los repositorios hijos.
- [`herramienta git-bajartodos.md`](docs/herramienta%20git-bajartodos.md): Clona todos los repos del clúster.
- [`herramienta git-chilremove.md`](docs/herramienta%20git-chilremove.md): Limpia todos los subrepos.
---
## 📚 Procedimientos adicionales
Ejemplos:
- Inyectar el esquema SQL de Guacamole: ver sección correspondiente en la [guía de instalación](docs/guia-instalacion.md)
- Crear y gestionar volúmenes o VMs de prueba con KubeVirt
- Publicar todos los manifiestos desde local con [`publicar-todos.sh`](publicar-todos.sh)
---
## 🙌 Autor
**Xavor - Manabo Inc**
Proyecto autogestionado y mantenido para homelab y formación profesional.
Repositorio privado/documentado en [https://git.manabo.org](https://git.manabo.org)
---
## 🧪 Estado actual
✅ Clúster en funcionamiento
✅ Ingress con TLS
✅ Almacenamiento replicado
✅ App of Apps en Argo CD
✅ Acceso remoto vía Guacamole
☑️ Próximos pasos: integración con FreeIPA, backups, y monitorización