[{"authors":[],"categories":[],"content":"Proyecto de infraestructura: montar un pipeline CI/CD completo y autoalojado, desde cero, sin depender de servicios externos como GitHub Actions o GitLab CI.\n1. Introducción ¿Qué es CI/CD? Metodología que automatiza la integración y el despliegue de código.\nCI (Integración Continua): ejecuta tests automáticamente en cada cambio de código. CD (Despliegue Continuo): despliega a producción automáticamente si los tests pasan. Objetivo del proyecto:\nAlmacenar el código en un servidor Git privado (Gitea) Detectar automáticamente cambios en el repositorio Ejecutar tareas de despliegue automáticamente Actualizar el servidor web sin intervención manual 2. Instalación de Gitea Crear el contenedor LXC (Osiris) 1sudo lxc-create -n osiris -t download -- -d debian -r bookworm -a amd64 2lxc-start -n osiris 3lxc-attach -n osiris Configurar red del contenedor IP estática en la DMZ (192.168.0.5):\n1nano /etc/systemd/network/10-eth0.network 1[Match] 2Name=eth0 3 4[Network] 5Address=192.168.0.5/24 6Gateway=192.168.0.1 7DNS=192.168.0.2 1systemctl enable systemd-networkd 2systemctl start systemd-networkd Instalar dependencias 1apt update \u0026amp;\u0026amp; apt install -y git wget sqlite3 Crear usuario para Gitea Por seguridad, Gitea no se ejecuta como root:\n1useradd -r -d /var/lib/gitea -s /bin/bash -m gitea Descargar e instalar Gitea 1cd /tmp 2wget https://dl.gitea.com/gitea/1.21.7/gitea-1.21.7-linux-amd64 -O gitea 3chmod +x gitea 4mv gitea /usr/local/bin/ Crear los directorios necesarios 1mkdir -p /var/lib/gitea/{custom,data,log} 2mkdir -p /etc/gitea 3chown -R gitea:gitea /var/lib/gitea 4chown -R gitea:gitea /etc/gitea Crear servicio systemd 1nano /etc/systemd/system/gitea.service 1[Unit] 2Description=Gitea (Git with a cup of tea) 3After=network.target 4 5[Service] 6Type=simple 7User=gitea 8Group=gitea 9WorkingDirectory=/var/lib/gitea 10ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini 11Restart=always 12Environment=USER=gitea HOME=/var/lib/gitea GITEA_WORK_DIR=/var/lib/gitea 13 14[Install] 15WantedBy=multi-user.target Explicación de cada sección:\n[Unit] → metadatos: descripción y que arranque después de que la red esté lista (After=network.target) [Service] → Type=simple (proceso en primer plano), User=gitea (no root), Restart=always (se reinicia si se cae), Environment con las variables necesarias [Install] → WantedBy=multi-user.target, se inicia en modo multiusuario 1systemctl daemon-reload 2systemctl enable gitea 3systemctl start gitea 4systemctl status gitea Configurar DNAT en RA Para acceder a Gitea desde internet, redirigir el puerto 3000:\n1sudo iptables -t nat -A PREROUTING -i ens3 -p tcp --dport 3000 -j DNAT --to-destination 192.168.0.5:3000 2sudo iptables-save \u0026gt; /etc/iptables/rules.v4 Configurar DNS Para acceder con nombre de dominio en vez de IP. Conectar a ISIS:\n1lxc-attach -n isis Zona DNS externa:\n1nano /etc/bind/zones/db.externa.josemanuel.gonzalonazareno.org 1git IN A 172.22.201.179 Zona DNS interna:\n1nano /etc/bind/zones/db.josemanuel.gonzalonazareno.org 1git IN A 192.168.0.5 1systemctl restart bind9 Configuración inicial de Gitea Acceder vía navegador a http://172.22.201.179:3000 y completar el instalador:\nCuenta de administrador:\nUsuario: josemanuel Email: ***@gmail.com Contraseña: ******** Crear un repositorio de prueba En la interfaz de Gitea: botón + → Nuevo repositorio → Nombre: Web-Demo → Descripción: \u0026ldquo;Sitio web de prueba para CI/CD\u0026rdquo; → marcar \u0026ldquo;Inicializar repositorio\u0026rdquo; → Licencia: MIT License → .gitignore vacío → Crear repositorio.\n3. Instalación de Drone CI Crear el contenedor LXC (Thoth) 1lxc-create -n thoth -t download -- -d debian -r bookworm -a amd64 2lxc-start -n thoth 3lxc-attach -n thoth Configurar la red IP estática 192.168.0.6:\n1nano /etc/systemd/network/10-eth0.network 1[Match] 2Name=eth0 3 4[Network] 5Address=192.168.0.6/24 6Gateway=192.168.0.1 7DNS=192.168.0.2 1systemctl enable systemd-networkd 2systemctl start systemd-networkd Instalar Docker Drone se ejecuta dentro de contenedores Docker:\n1apt update \u0026amp;\u0026amp; apt install -y ca-certificates curl gnupg 2install -m 0755 -d /etc/apt/keyrings 3curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc 4chmod a+r /etc/apt/keyrings/docker.asc 5echo \u0026#34;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release \u0026amp;\u0026amp; echo \u0026#34;$VERSION_CODENAME\u0026#34;) stable\u0026#34; | tee /etc/apt/sources.list.d/docker.list \u0026gt; /dev/null 6apt update \u0026amp;\u0026amp; apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin Configurar LXC para Docker anidado Docker dentro de LXC requiere configuración especial; si no, Thoth no deja crear contenedores Docker. En RA, editar la configuración del contenedor:\n1nano /var/lib/lxc/thoth/config 1lxc.apparmor.profile = unconfined 2lxc.cgroup2.devices.allow = a 3lxc.cap.drop = 4lxc.mount.auto = proc:rw sys:rw cgroup:rw 1lxc-stop -n thoth 2lxc-start -n thoth 3lxc-attach -n thoth Generar una contraseña compartida Drone Server y Runner se comunican mediante un secreto compartido:\n1openssl rand -hex 16 Crear aplicación OAuth en Gitea Drone necesita autenticarse con Gitea vía OAuth:\nAbrir Gitea → login como josemanuel Avatar → Configuración → Aplicaciones Sección \u0026ldquo;Administrar aplicaciones OAuth2\u0026rdquo; → Crear una nueva aplicación OAuth2 Nombre: Drone CI; URL de redirección: http://drone.josemanuel.gonzalonazareno.org:8080/login Crear aplicación OAuth es un protocolo de autorización que permite a Drone acceder a Gitea sin necesitar la contraseña del usuario.\nCrear configuración de Drone Server 1mkdir -p /var/lib/drone 2nano /root/drone.env 1DRONE_GITEA_SERVER=http://git.josemanuel.gonzalonazareno.org:3000 2DRONE_GITEA_CLIENT_ID=******** 3DRONE_GITEA_CLIENT_SECRET=******** 4DRONE_RPC_SECRET=******** 5DRONE_SERVER_HOST=drone.josemanuel.gonzalonazareno.org:8080 6DRONE_SERVER_PROTO=http 7DRONE_DATABASE_DRIVER=sqlite3 8DRONE_DATABASE_DATASOURCE=/data/database.sqlite 9DRONE_USER_CREATE=username:josemanuel,admin:true 10DRONE_SERVER_PORT=:8080 Explicación de cada variable:\nDRONE_GITEA_SERVER → URL de Gitea DRONE_GITEA_CLIENT_ID / DRONE_GITEA_CLIENT_SECRET → credenciales OAuth de Gitea DRONE_RPC_SECRET → secreto para la comunicación Server↔Runner DRONE_SERVER_HOST / DRONE_SERVER_PROTO → dominio público y protocolo de Drone DRONE_DATABASE_DRIVER / DRONE_DATABASE_DATASOURCE → base de datos (SQLite) DRONE_USER_CREATE → crea automáticamente un usuario admin DRONE_SERVER_PORT → puerto interno Ejecutar Drone Server con Docker 1docker network create drone-network 2 3docker run \\ 4 --privileged \\ 5 --network=drone-network \\ 6 --volume=/var/lib/drone:/data \\ 7 --env-file=/root/drone.env \\ 8 --publish=8080:8080 \\ 9 --restart=always \\ 10 --detach=true \\ 11 --name=drone \\ 12 drone/drone:2 Configurar DNAT y DNS para Drone 1sudo iptables -t nat -A PREROUTING -i ens3 -p tcp --dport 8080 -j DNAT --to-destination 192.168.0.6:8080 2sudo iptables-save \u0026gt; /etc/iptables/rules.v4 1lxc-attach -n isis 2nano /etc/bind/zones/db.externa.josemanuel.gonzalonazareno.org 1drone IN A 172.22.201.179 1nano /etc/bind/zones/db.josemanuel.gonzalonazareno.org 1drone IN A 192.168.0.6 1systemctl restart bind9 Acceder a Drone 1http://drone.josemanuel.gonzalonazareno.org:8080 Instalar Drone Runner El Runner es el que ejecuta las tareas:\n1lxc-attach -n thoth 2nano /root/drone-runner.env 1DRONE_RPC_PROTO=http 2DRONE_RPC_HOST=drone:8080 3DRONE_RPC_SECRET=******** 4DRONE_RUNNER_CAPACITY=2 5DRONE_RUNNER_NAME=docker-runner Explicación:\nDRONE_RPC_PROTO / DRONE_RPC_HOST → protocolo y nombre del contenedor Drone Server (resuelto por la red Docker drone-network) DRONE_RPC_SECRET → mismo secreto que el Server DRONE_RUNNER_CAPACITY → máximo de builds simultáneos DRONE_RUNNER_NAME → nombre identificativo del runner 1docker run \\ 2 --privileged \\ 3 --network=drone-network \\ 4 --volume=/var/run/docker.sock:/var/run/docker.sock \\ 5 --env-file=/root/drone-runner.env \\ 6 --restart=always \\ 7 --detach=true \\ 8 --name=runner \\ 9 drone/drone-runner-docker:1 4. Configuración del Pipeline Configurar repositorio en Anubis Clonar el repositorio desde Gitea:\n1ssh anubis 2git config --global user.name \u0026#34;Jose Manuel\u0026#34; 3git config --global user.email \u0026#34;***@gmail.com\u0026#34; 4cd /var/www/html 5sudo git clone http://git.josemanuel.gonzalonazareno.org:3000/josemanuel/Web-Demo.git web-demo Configurar Apache para servir este directorio:\n1sudo nano /etc/httpd/conf.d/web-demo.conf 1\u0026lt;VirtualHost *:80\u0026gt; 2 ServerName www.josemanuel.gonzalonazareno.org 3 DocumentRoot /var/www/html/web-demo 4 5 \u0026lt;Directory /var/www/html/web-demo\u0026gt; 6 AllowOverride All 7 Require all granted 8 \u0026lt;/Directory\u0026gt; 9 10 ErrorLog /var/log/httpd/web-demo-error.log 11 CustomLog /var/log/httpd/web-demo-access.log combined 12\u0026lt;/VirtualHost\u0026gt; 1sudo chown -R apache:apache /var/www/html/web-demo 2sudo chmod -R 755 /var/www/html/web-demo 3sudo systemctl restart httpd Crear un index con una plantilla 1cd web-demo 2sudo nano index.html 1\u0026lt;!DOCTYPE html\u0026gt; 2\u0026lt;html lang=\u0026#34;es\u0026#34;\u0026gt; 3\u0026lt;head\u0026gt; 4\u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; 5\u0026lt;meta name=\u0026#34;viewport\u0026#34; content=\u0026#34;width=device-width, initial-scale=1.0\u0026#34;\u0026gt; 6\u0026lt;title\u0026gt;Web Demo - CI/CD Pipeline\u0026lt;/title\u0026gt; 7\u0026lt;style\u0026gt; 8body { 9 font-family: Arial, sans-serif; 10 max-width: 800px; 11 margin: 50px auto; 12 padding: 20px; 13 background: #f0f0f0; 14} 15.container { 16 background: white; 17 padding: 30px; 18 border-radius: 10px; 19 box-shadow: 0 2px 10px rgba(0,0,0,0.1); 20} 21h1 { color: #2c3e50; } 22.version { color: #7f8c8d; font-size: 14px; } 23\u0026lt;/style\u0026gt; 24\u0026lt;/head\u0026gt; 25\u0026lt;body\u0026gt; 26\u0026lt;div class=\u0026#34;container\u0026#34;\u0026gt; 27 \u0026lt;h1\u0026gt;Pipeline CI/CD Funcionando\u0026lt;/h1\u0026gt; 28 \u0026lt;p\u0026gt;Esta página se despliega automáticamente desde Gitea.\u0026lt;/p\u0026gt; 29\u0026lt;/div\u0026gt; 30\u0026lt;/body\u0026gt; 31\u0026lt;/html\u0026gt; 1sudo git add index.html 2sudo git commit -m \u0026#34;Añadido index.html - versión 1.0\u0026#34; 3sudo git push origin main Al hacer push, pide las credenciales de Gitea.\nConfigurar clave SSH para despliegue Para que Drone pueda desplegar automáticamente en Anubis, necesita autenticarse por SSH:\n1ssh-keygen -t ed25519 -f ~/.ssh/drone_deploy -N \u0026#34;\u0026#34; Configurar secreto en Drone En Drone → repositorio josemanuel/Web-Demo → Settings → Secrets → New Secret Rellenar: Name: ssh_key; Value: la clave privada completa; Pull Request Access: Disabled Create Los secretos en Drone se almacenan encriptados y solo están disponibles durante la ejecución del pipeline — es una forma segura de manejar credenciales.\nActivar repositorio en Drone En Drone → repositorio → Settings → General: Project Visibility: Public; Configuration: .drone.yml; Timeout: 60 minutes → Guardar cambios → Activate Repository.\nCrear archivo .drone.yml Define qué hacer cuando hay un push. En Anubis, dentro de web-demo:\n1cd /var/www/html/web-demo 2sudo nano .drone.yml 1kind: pipeline 2type: docker 3name: deploy-web 4 5steps: 6- name: deploy-to-anubis 7 image: appleboy/drone-ssh 8 settings: 9 host: 172.16.0.200 10 username: josemanuel 11 key: 12 from_secret: ssh_key 13 port: 22 14 script: 15 - cd /var/www/html/web-demo 16 - git pull origin main 17 - echo \u0026#34;Desplegado exitosamente en $(date)\u0026#34; Explicación del fichero:\nkind: pipeline / type: docker → pipeline que se ejecuta en contenedores Docker name: deploy-web → nombre descriptivo steps → lista de pasos; aquí un único paso deploy-to-anubis image: appleboy/drone-ssh → imagen que ejecuta comandos por SSH host / username / port → datos de conexión al servidor de despliegue key: from_secret: ssh_key → usa la clave SSH guardada como secreto script → comandos remotos: entra al directorio, hace git pull y confirma con un mensaje Subir .drone.yml a Gitea 1sudo git add .drone.yml 2sudo git commit -m \u0026#34;Añadido pipeline CI/CD\u0026#34; 3sudo git push origin main Este push dispara automáticamente el pipeline en Drone.\n5. Pruebas y Verificación Verificar build en Drone Drone → josemanuel/Web-Demo → Builds. Debería verse un build ejecutándose o completado.\nVer logs del build Al hacer clic en el build se ven los logs detallados de cada paso (clone, deploy-to-anubis).\nDespliegue automático completo Prueba de extremo a extremo: modificar index.html en Anubis y comprobar que se despliega solo.\n1cd /var/www/html/web-demo 2sudo nano index.html 3sudo git add index.html 4sudo git commit -m \u0026#34;Actualizado a versión xxx\u0026#34; 5sudo git push origin main Verificación: en Drone aparece un build nuevo automáticamente, y http://www.josemanuel.gonzalonazareno.org muestra la versión nueva sin haber copiado archivos a mano.\nConclusión Con esto queda montado un pipeline CI/CD autoalojado y funcional: Gitea como servidor Git privado, Drone como motor de CI/CD (Server + Runner sobre Docker), y un despliegue automático por SSH a un servidor Apache cada vez que se hace push a main.\n","date":"8 de marzo de 2026","img":"https://jmsaroca.es/images/gitea-drone/portadgiteaaaaa.png","lang":"es","langName":"Español","largeImg":"","permalink":"/blog/2026/03/implementaci%C3%B3n-de-pipeline-ci-cd-con-gitea--drone/","series":[],"smallImg":"","tags":[{"title":"Gitea","url":"/tags/gitea/"},{"title":"Drone","url":"/tags/drone/"},{"title":"CI/CD","url":"/tags/ci/cd/"},{"title":"Docker","url":"/tags/docker/"}],"timestamp":1772928000,"title":"Implementación De Pipeline CI/CD Con Gitea + Drone"},{"authors":[],"categories":[],"content":" 1. Exportación del esquema de SCOTT con Oracle Data Pump Vamos a entrar como administrador y vamos a crear el directorio de Data Pump Oracle Data Pump no trabaja con rutas del sistema operativo directamente. Necesita un objeto DIRECTORY dentro de Oracle que apunte a una carpeta real del sistema.\n1CREATE OR REPLACE DIRECTORY ej1_dir AS \u0026#39;/home/oracle\u0026#39;; Este comando crea un objeto lógico dentro de Oracle. dp_dir es el nombre que usaremos en los comandos de expdp/impdp para referirnos a esa ruta. La carpeta indicada es la ruta real del sistema operativo donde se guardarán los ficheros; si no existe, se crea.\nAhora damos permiso a SCOTT para usar ese directorio 1GRANT READ, WRITE ON DIRECTORY ej1_dir TO c##scott; Sin esto, SCOTT no puede leer ni escribir en ese directorio aunque exista.\nEstimación previa del tamaño Antes de exportar de verdad, hacemos una estimación del espacio necesario. Creamos un archivo de parámetros:\n1nano /tmp/export_scott.par 1SCHEMAS=SCOTT 2CONTENT=ALL 3DIRECTORY=dp_dir 4DUMPFILE=scott_export.dmp 5LOGFILE=ej1_dir:scott_export.log 6EXCLUDE=TABLE:\u0026#34;=\u0026#39;BONUS\u0026#39;\u0026#34; 7QUERY=SCOTT.EMP:\u0026#34;WHERE DEPTNO IN (SELECT DEPTNO FROM SCOTT.EMP GROUP BY DEPTNO HAVING COUNT(*) \u0026gt;= 2)\u0026#34; 8REUSE_DUMPFILES=YES Y probamos:\n1expdp c##scott/tiger@localhost:1521/orclpdb1 PARFILE=/tmp/export_scott.par Explicación de los parámetros:\nSCHEMAS=SCOTT: le decimos que queremos trabajar con el esquema (conjunto de objetos) de SCOTT ESTIMATE_ONLY=YES: con esto no se exporta nada, solo calcula cuánto espacio ocuparía — muy útil antes de lanzar exportaciones grandes DIRECTORY=dp_dir: usa el directorio Oracle que creamos antes LOGFILE=ej1_dir:scott_export.log: genera un log con el resultado en ese directorio La exportación real con todas las condiciones Ahora la exportación está completa. Hay que programarla para dentro de 2 minutos:\n1echo \u0026#34;expdp c##scott/tiger@localhost:1521/orclpdb1 PARFILE=/tmp/export_scott.par\u0026#34; | at now + 2 minutes Esperamos hasta la hora programada y luego comprobamos que el fichero se generó:\n1ls -l 2. Importar el fichero en un usuario distinto de la misma base de datos Creamos el usuario destino en Oracle 1ALTER SESSION SET CONTAINER = ORCLPDB1; 2 3CREATE USER scott2 IDENTIFIED BY tiger2; 4GRANT CONNECT, RESOURCE TO scott2; 5GRANT UNLIMITED TABLESPACE TO scott2; 6GRANT READ, WRITE ON DIRECTORY dp_dir TO scott2; Ejecutamos la exportación de los datos 1expdp c##scott/tiger@localhost:1521/orclpdb1 PARFILE=/tmp/export_scott.par Y ahora la importamos 1impdp system/oracle@localhost:1521/orclpdb1 DIRECTORY=dp_dir DUMPFILE=scott_export.dmp LOGFILE=dp_dir:importacion_scott2.log REMAP_SCHEMA=SCOTT:SCOTT2 REMAP_SCHEMA=SCOTT:SCOTT2 es la clave: le dice a Data Pump que todo lo que pertenecía a SCOTT se cree en el esquema SCOTT2 en lugar de en el original.\nVerificamos 1sqlplus system/oracle@localhost:1521/orclpdb1 2SELECT TABLE_NAME FROM DBA_TABLES WHERE OWNER = \u0026#39;SCOTT2\u0026#39;; 3SELECT COUNT(*) FROM SCOTT2.EMP; 4SELECT COUNT(*) FROM SCOTT2.DEPT; 3. Exportación de la estructura completa con expdp (al menos 5 opciones) Conceder permisos de exportación completa El usuario c##scott necesita el rol DATAPUMP_EXP_FULL_DATABASE para poder realizar un export de toda la base de datos (FULL=YES). Sin este rol solo puede exportar su propio esquema.\n1GRANT DATAPUMP_EXP_FULL_DATABASE TO c##scott; Crear el fichero de parámetros (parfile) En lugar de escribir todos los parámetros en la línea de comandos, Oracle Data Pump permite agruparlos en un fichero de parámetros (.par). Esto facilita la reutilización y documentación del proceso.\n1FULL=YES 2CONTENT=METADATA_ONLY 3DIRECTORY=dp_dir 4DUMPFILE=full_estructura.dmp 5LOGFILE=root_dir:full_estructura.log 6COMPRESSION=METADATA_ONLY 7FLASHBACK_TIME=SYSTIMESTAMP 8REUSE_DUMPFILES=YES 9METRICS=YES Al menos cinco opciones documentadas:\nFULL=YES: exporta toda la base de datos completa, no solo un esquema CONTENT=METADATA_ONLY: exporta únicamente la estructura DDL (CREATE TABLE, CREATE INDEX\u0026hellip;), sin datos COMPRESSION=METADATA_ONLY: comprime los metadatos dentro del fichero .dmp para reducir su tamaño FLASHBACK_TIME=SYSTIMESTAMP: garantiza consistencia exportando todos los objetos en el mismo punto exacto del tiempo REUSE_DUMPFILES=YES: si el fichero .dmp ya existe lo sobreescribe en lugar de dar error METRICS=YES: añade al log información detallada de rendimiento y tiempo por cada objeto exportado Ejecutar la exportación 1expdp c##scott/tiger@localhost:1521/orclpdb1 PARFILE=/tmp/export_full.par 4. Importación/exportación con MySQL desde línea de comandos MySQL y MariaDB incluyen la herramienta mysqldump para exportar bases de datos a ficheros SQL de texto plano. Estos ficheros contienen todas las sentencias necesarias para recrear la estructura y los datos. En este ejercicio se crea una base de datos de prueba, se exporta desde el servidor de bases de datos y se importa en otra máquina.\nCrear la base de datos ej4 en servidorbd Se crea una base de datos llamada ej4 con dos tablas de prueba: empleados y departamentos, con datos representativos.\nExportar con mysqldump mysqldump genera un fichero .sql con todas las sentencias CREATE TABLE e INSERT necesarias para recrear la base de datos completa. Es la herramienta estándar de exportación en MySQL y MariaDB.\n1mysqldump -u root -p ej4 \u0026gt; /tmp/ej4.sql Ahora nos la pasamos por scp:\n1scp /tmp/ej4.sql oracle@192.168.122.48:/tmp/ Ahora en nuestra otra máquina creamos una base de datos llamada ej4_importacion para importar la base de datos:\nImportamos el fichero a nuestra bd nueva 1sudo mysql -u root -p ej4_importacion \u0026lt; /tmp/ej4.sql Verificamos 5. Importación/exportación con PostgreSQL desde línea de comandos PostgreSQL incluye las herramientas pg_dump y pg_dumpall para exportar bases de datos. pg_dump exporta una base de datos concreta, mientras que pg_dumpall exporta toda la instancia. El fichero generado contiene sentencias SQL estándar compatibles con psql para la importación.\nAquí en Postgres haremos lo mismo: crearemos una base de datos llamada ej5 y repetiremos el mismo proceso que en MariaDB.\nExportar con pg_dump pg_dump genera un fichero SQL con toda la estructura y datos de la base de datos indicada. A diferencia de mysqldump, pg_dump por defecto no incluye la instrucción CREATE DATABASE, por lo que hay que crearla manualmente antes de importar.\n1sudo -u postgres pg_dump ej5 \u0026gt; /tmp/ej5.sql Lo pasamos a nuestro otro servidor:\n1scp /tmp/ej5.sql oracle@192.168.122.48:/tmp/ Ahora creamos la base de datos destino y la importamos.\nImportamos el fichero 1sudo -u postgres psql ej5_importada \u0026lt; /tmp/ej5.sql Verificamos 6. Exportar documentos de MongoDB filtrados por condición MongoDB utiliza las herramientas mongoexport y mongoimport, que permiten exportar e importar documentos en formato JSON o CSV, con la posibilidad de filtrar por condición.\nCrear la colección de prueba en servidorbd Se crea la base de datos ej6 con una colección empleados que incluye un campo activo (booleano) que usaremos como condición de filtrado en la exportación.\nExportar con condición usando mongoexport mongoexport permite filtrar los documentos a exportar mediante el parámetro --query, que acepta un filtro en formato JSON, igual que los filtros de MongoDB.\n1mongoexport --db ej6 --collection empleados --query \u0026#39;{\u0026#34;activo\u0026#34;: true}\u0026#39; --out /tmp/ej6.json Parámetros utilizados:\n--db ej6: base de datos origen --collection empleados: colección a exportar --query '{\u0026quot;activo\u0026quot;: true}': filtro — solo documentos donde activo sea true. Exporta 3 de los 5 documentos --out /tmp/ej6.json: fichero de salida en formato JSON (un documento por línea) Ahora transferimos el fichero JSON al servidor:\n1scp /tmp/ej6.json oracle@192.168.122.48:/tmp/ Importación 1mongoimport --db ej6_importada --collection empleados --file /tmp/ej6.json Verificamos 1mongosh 2use ej6 3db.empleados.find().pretty() 7. Carga masiva de MariaDB a Oracle con SQL*Loader SQL*Loader es la herramienta de carga masiva de Oracle. Permite cargar grandes volúmenes de datos desde ficheros de texto plano (CSV, delimitado por tabuladores, de ancho fijo\u0026hellip;) a tablas Oracle. Requiere dos ficheros principales: el fichero de datos y el fichero de control.\nExportar tablas de MariaDB a texto plano Se exportan las tablas de la base de datos ej4 a ficheros CSV usando el modo batch de MySQL. Este modo genera la salida separada por tabuladores, sin cabeceras ni formato.\n1mysql -u root -p ej4 -e \u0026#34;SELECT * FROM empleados\u0026#34; --batch --silent \u0026gt; /tmp/empleados.csv 2mysql -u root -p ej4 -e \u0026#34;SELECT * FROM departamentos\u0026#34; --batch --silent \u0026gt; /tmp/departamentos.csv El parámetro --batch activa el modo no interactivo (salida separada por tabuladores) y --silent elimina las cabeceras de columna y mensajes de estado.\nPasamos los CSV a la máquina Oracle con scp:\n1scp /tmp/empleados.csv oracle@192.168.122.48:/tmp/ 2scp /tmp/departamentos.csv oracle@192.168.122.48:/tmp/ Crear las tablas destino en Oracle 1CREATE TABLE empleados ( 2 id NUMBER PRIMARY KEY, 3 nombre VARCHAR2(50), 4 departamento VARCHAR2(50), 5 salario NUMBER(10,2), 6 fecha_alta DATE 7); 8 9CREATE TABLE departamentos ( 10 id NUMBER PRIMARY KEY, 11 nombre VARCHAR2(50), 12 ubicacion VARCHAR2(100) 13); Crear los ficheros de control (.ctl) El fichero de control es el núcleo de SQL*Loader. Define todo lo necesario para interpretar el fichero de datos y cargarlo en Oracle.\n1cat /tmp/empleados.ctl 1LOAD DATA 2INFILE \u0026#39;/tmp/empleados.csv\u0026#39; 3INTO TABLE empleados 4FIELDS TERMINATED BY X\u0026#39;09\u0026#39; 5TRAILING NULLCOLS 6( 7 id CHAR, 8 nombre CHAR, 9 departamento CHAR, 10 salario CHAR \u0026#34;TO_NUMBER(:salario, \u0026#39;9999.99\u0026#39;)\u0026#34;, 11 fecha_alta DATE \u0026#34;YYYY-MM-DD\u0026#34; 12) 1cat /tmp/departamentos.ctl 1LOAD DATA 2INFILE \u0026#39;/tmp/departamentos.csv\u0026#39; 3INTO TABLE departamentos 4FIELDS TERMINATED BY \u0026#39;\\t\u0026#39; 5TRAILING NULLCOLS 6( 7 id, 8 nombre, 9 ubicacion 10) Explicación de las directivas del fichero de control:\nLOAD DATA: indica el inicio de la definición de carga INFILE: ruta al fichero de datos a cargar INTO TABLE: tabla Oracle destino donde se insertarán los datos FIELDS TERMINATED BY X'09': el delimitador es el tabulador — se usa la notación hexadecimal porque '\\t' no funciona directamente en SQL*Loader TRAILING NULLCOLS: si el último campo de un registro está vacío, inserta NULL en lugar de dar error CHAR: SQL*Loader lee el campo como texto; luego la conversión al tipo NUMBER o DATE se hace con la función indicada TO_NUMBER(:salario, '9999.99'): convierte el texto (p. ej. '2500.00') al tipo NUMBER de Oracle usando la máscara de formato indicada DATE \u0026quot;YYYY-MM-DD\u0026quot;: convierte el texto (p. ej. '2022-03-15') al tipo DATE de Oracle usando el formato de fecha indicado Ejecutar SQL*Loader Cargamos los datos de departamentos:\n1sqlldr system/oracle@localhost:1521/orclpdb1 \\ 2 CONTROL=/tmp/departamentos.ctl \\ 3 LOG=/tmp/departamentos.log \\ 4 BAD=/tmp/departamentos.bad Y cargamos los datos de los empleados:\n1sqlldr system/oracle@localhost:1521/orclpdb1 \\ 2 CONTROL=/tmp/empleados.ctl \\ 3 LOG=/tmp/empleados.log \\ 4 BAD=/tmp/empleados.bad Interpretar el fichero de log El fichero de log generado por SQL*Loader contiene información muy detallada del proceso de carga. Las secciones más importantes son:\nArchivo de Control: ruta al fichero .ctl utilizado Archivo de Datos: ruta al fichero de datos procesado Archivo de Errores (.bad): ruta donde se guardaron los registros rechazados Ruta de acceso utilizada: Convencional (inserción fila a fila) o Direct (carga directa sin pasar por el buffer, más rápida para grandes volúmenes) Opción INSERT/APPEND/REPLACE: INSERT inserta solo si la tabla está vacía, APPEND agrega sin borrar, REPLACE borra todo y recarga Tabla X: N Filas cargadas: resumen del resultado por tabla Total de registros leídos: cuántos registros se leyeron del fichero de datos Total de registros rechazados: cuántos no pudieron insertarse (van al .bad) Total de registros desechados: cuántos no cumplieron la cláusula WHEN (van al .dsc) Tiempo transcurrido / Tiempo de CPU: duración total y consumo de CPU de la operación de carga 1cat /tmp/empleados.log 1cat /tmp/departamentos.log ","date":"25 de febrero de 2026","img":"https://jmsaroca.es/images/movimiento-datos/portmovdat.png","lang":"es","langName":"Español","largeImg":"","permalink":"/blog/2026/02/movimiento-de-datos-data-pump-mysqldump-pg_dump-mongoexport-y-sqlloader/","series":[],"smallImg":"","tags":[{"title":"Oracle","url":"/tags/oracle/"},{"title":"MySQL","url":"/tags/mysql/"},{"title":"PostgreSQL","url":"/tags/postgresql/"},{"title":"MongoDB","url":"/tags/mongodb/"},{"title":"Bases De Datos","url":"/tags/bases-de-datos/"}],"timestamp":1771977600,"title":"Movimiento De Datos: Data Pump, Mysqldump, Pg_dump, Mongoexport Y SQL*Loader"},{"authors":[],"categories":[],"content":"En esta práctica se monta una arquitectura profesional de observabilidad que permite monitorizar sistemas en tiempo real. Se construye un stack completo con tres componentes clave:\nOpenTelemetry Collector: recolector universal de métricas desde múltiples fuentes (aplicaciones, sistemas operativos, contenedores). Prometheus: motor de almacenamiento eficiente en base de datos de series temporales (TSDB) y consulta de datos mediante lenguaje PromQL. Grafana: interfaz de visualización con dashboards interactivos y alertas configurables. Este tipo de infraestructura es utilizada por empresas como Google (Borgmon), Netflix, Amazon (CloudWatch), Spotify, Uber y Airbnb para monitorizar sus sistemas de producción a escala global.\nEstructura del proyecto Organizar el proyecto en una estructura clara:\n1mkdir -p ~/herramientas 2cd ~/herramientas 3 4mkdir -p grafana/datasources 5mkdir -p grafana/dashboards 6mkdir -p opentelemetry 7mkdir -p prometheus Configuración de servicios Archivo docker-compose.yaml Creamos el archivo principal de orquestación:\n1nano docker-compose.yaml 1services: 2 prometheus: 3 image: prom/prometheus:latest 4 container_name: prometheus 5 ports: 6 - \u0026#34;9090:9090\u0026#34; 7 volumes: 8 - ./prometheus/prometheus.yaml:/etc/prometheus/prometheus.yml 9 - prometheus-data:/prometheus 10 command: 11 - \u0026#39;--config.file=/etc/prometheus/prometheus.yml\u0026#39; 12 - \u0026#39;--storage.tsdb.path=/prometheus\u0026#39; 13 - \u0026#39;--web.console.libraries=/usr/share/prometheus/console_libraries\u0026#39; 14 - \u0026#39;--web.console.templates=/usr/share/prometheus/consoles\u0026#39; 15 networks: 16 - monitoring 17 restart: unless-stopped 18 19 otel-collector: 20 image: otel/opentelemetry-collector-contrib:latest 21 container_name: otel-collector 22 ports: 23 - \u0026#34;4317:4317\u0026#34; 24 - \u0026#34;4318:4318\u0026#34; 25 - \u0026#34;8888:8888\u0026#34; 26 - \u0026#34;8889:8889\u0026#34; 27 volumes: 28 - ./opentelemetry/opentelemetry.yml:/etc/otel/config.yaml 29 command: [\u0026#34;--config=/etc/otel/config.yaml\u0026#34;] 30 networks: 31 - monitoring 32 restart: unless-stopped 33 34 grafana: 35 image: grafana/grafana:latest 36 container_name: grafana 37 ports: 38 - \u0026#34;3000:3000\u0026#34; 39 volumes: 40 - grafana-data:/var/lib/grafana 41 - ./grafana:/etc/grafana/provisioning 42 environment: 43 - GF_SECURITY_ADMIN_USER=admin 44 - GF_SECURITY_ADMIN_PASSWORD=******** 45 - GF_USERS_ALLOW_SIGN_UP=false 46 networks: 47 - monitoring 48 restart: unless-stopped 49 depends_on: 50 - prometheus 51 52 app-demo: 53 image: nginx:alpine 54 container_name: app-demo 55 ports: 56 - \u0026#34;8080:80\u0026#34; 57 networks: 58 - monitoring 59 restart: unless-stopped 60 61volumes: 62 prometheus-data: 63 grafana-data: 64 65networks: 66 monitoring: 67 driver: bridge Explicación del yaml:\nServicio prometheus:\nimage: Imagen oficial de Prometheus ports: Expone la interfaz web en el puerto 9090 volumes: configuración (./prometheus/prometheus.yaml → /etc/prometheus/prometheus.yml) y datos persistentes (prometheus-data → /prometheus) command: parámetros de inicio — --config.file (ubicación del archivo de configuración) y --storage.tsdb.path (directorio de almacenamiento TSDB) networks: conecta a la red interna monitoring restart: reinicia automáticamente salvo parada manual Servicio otel-collector:\nports: 4317 (protocolo OTLP sobre gRPC), 4318 (OTLP sobre HTTP), 8888 (métricas internas del collector), 8889 (endpoint para scraping de Prometheus) volumes: monta la configuración del collector Servicio grafana:\nports: interfaz web en el puerto 3000 environment: GF_SECURITY_ADMIN_USER (usuario administrador), GF_SECURITY_ADMIN_PASSWORD (contraseña inicial), GF_USERS_ALLOW_SIGN_UP (deshabilita el registro público) depends_on: indica dependencia de Prometheus Servicio app-demo: Nginx Alpine, servidor web ligero para generar tráfico de prueba.\nVolúmenes: prometheus-data (persistencia de métricas históricas) y grafana-data (persistencia de configuración y dashboards).\nRedes: monitoring, red bridge interna para comunicación entre contenedores.\nConfiguración de Prometheus Creamos el archivo de configuración:\n1nano prometheus/prometheus.yaml 1global: 2 scrape_interval: 15s 3 evaluation_interval: 15s 4 5 external_labels: 6 monitor: \u0026#39;monitoring-stack\u0026#39; 7 environment: \u0026#39;development\u0026#39; 8 9scrape_configs: 10 - job_name: \u0026#39;prometheus\u0026#39; 11 static_configs: 12 - targets: [\u0026#39;localhost:9090\u0026#39;] 13 labels: 14 service: \u0026#39;prometheus\u0026#39; 15 16 - job_name: \u0026#39;otel-collector\u0026#39; 17 static_configs: 18 - targets: [\u0026#39;otel-collector:8888\u0026#39;] 19 labels: 20 service: \u0026#39;otel-collector\u0026#39; 21 22 - job_name: \u0026#39;otel-metrics\u0026#39; 23 static_configs: 24 - targets: [\u0026#39;otel-collector:8889\u0026#39;] 25 labels: 26 service: \u0026#39;otel-exported-metrics\u0026#39; Explicación de la configuración:\nglobal.scrape_interval: cada cuánto Prometheus recoge métricas de los targets (15s) global.evaluation_interval: cada cuánto evalúa las reglas de alertas (15s) global.external_labels: etiquetas que se añaden a todas las métricas (útil para identificar el origen) scrape_configs: lista de fuentes de métricas (jobs), cada uno con job_name (identificador único), static_configs (configuración estática, sin descubrimiento dinámico), targets (endpoints donde hará scraping) y labels (etiquetas adicionales) Cómo funciona el scraping:\nPrometheus hace peticiones HTTP GET a cada target cada 15 segundos Los targets exponen métricas en formato texto plano Prometheus parsea y almacena las métricas en su base de datos de series temporales (TSDB) Configuración de OpenTelemetry Collector Creamos el archivo de configuración:\n1nano opentelemetry/opentelemetry.yml 1receivers: 2 otlp: 3 protocols: 4 grpc: 5 endpoint: 0.0.0.0:4317 6 http: 7 endpoint: 0.0.0.0:4318 8 9 prometheus: 10 config: 11 scrape_configs: 12 - job_name: \u0026#39;app-demo-nginx\u0026#39; 13 scrape_interval: 10s 14 static_configs: 15 - targets: [\u0026#39;app-demo:80\u0026#39;] 16 17 hostmetrics: 18 collection_interval: 30s 19 scrapers: 20 cpu: 21 memory: 22 disk: 23 network: 24 load: 25 filesystem: 26 27processors: 28 batch: 29 timeout: 10s 30 send_batch_size: 1024 31 32 resource: 33 attributes: 34 - key: service.instance.id 35 value: otel-collector-01 36 action: insert 37 - key: deployment.environment 38 value: development 39 action: insert 40 41exporters: 42 prometheus: 43 endpoint: \u0026#34;0.0.0.0:8889\u0026#34; 44 namespace: \u0026#34;otel\u0026#34; 45 const_labels: 46 collector: \u0026#34;otel-collector\u0026#34; 47 48 debug: 49 verbosity: detailed 50 51 prometheusremotewrite: 52 endpoint: \u0026#34;http://prometheus:9090/api/v1/write\u0026#34; 53 tls: 54 insecure: true 55 56service: 57 pipelines: 58 metrics: 59 receivers: [otlp, prometheus, hostmetrics] 60 processors: [batch, resource] 61 exporters: [prometheus, debug, prometheusremotewrite] 62 63 telemetry: 64 logs: 65 level: info Flujo de datos:\nLas métricas entran por cualquiera de los 3 receivers (otlp, prometheus, hostmetrics) Pasan por el procesador batch Pasan por el procesador resource Se exportan simultáneamente a los 3 exportadores Configuración de Grafana Creamos la configuración de la fuente de datos:\n1nano grafana/datasources/prometheus.yml 1apiVersion: 1 2 3datasources: 4 - name: Prometheus 5 type: prometheus 6 access: proxy 7 url: http://prometheus:9090 8 isDefault: true 9 editable: true 10 jsonData: 11 timeInterval: \u0026#34;15s\u0026#34; 12 httpMethod: POST Explicación:\napiVersion: 1: versión del formato de provisión name: Prometheus: nombre de la fuente de datos en Grafana type: prometheus: tipo de fuente de datos access: proxy: Grafana hace las peticiones (no el navegador) url: URL interna de Prometheus (nombre del servicio Docker) isDefault: true: fuente de datos por defecto editable: true: permite editar desde la UI timeInterval: \u0026quot;15s\u0026quot;: intervalo mínimo entre queries (coincide con el scrape_interval) httpMethod: POST: usar POST para queries largas (más eficiente) Configuración de provisión de dashboards Creamos la configuración de provisión:\n1nano grafana/dashboards/dashboard.yml 1apiVersion: 1 2 3providers: 4 - name: \u0026#39;Default\u0026#39; 5 orgId: 1 6 folder: \u0026#39;\u0026#39; 7 type: file 8 disableDeletion: false 9 updateIntervalSeconds: 10 10 allowUiUpdates: true 11 options: 12 path: /etc/grafana/provisioning/dashboards Explicación:\nproviders: lista de proveedores de dashboards name: 'Default': nombre del proveedor orgId: 1: organización de Grafana folder: '': carpeta donde aparecerán los dashboards type: file: los dashboards se cargan desde archivos disableDeletion: false: permite borrar dashboards desde la UI updateIntervalSeconds: 10: cada cuánto busca nuevos dashboards allowUiUpdates: true: permite editar dashboards desde la UI path: ruta donde busca los archivos JSON de dashboards Dashboard de sistema Creamos el dashboard básico:\n1nano grafana/system.json El dashboard define 4 paneles sobre la fuente de datos de Prometheus, cada uno con su propia query PromQL, umbrales de color y tipo de visualización:\nCPU Usage (%) — gráfico de series temporales\nQuery: 100 - (avg by (instance) (rate(otel_system_cpu_time_seconds_total{state=\u0026quot;idle\u0026quot;}[1m])) * 100) Cálculo: 100% − tiempo en idle = tiempo activo de CPU Umbrales: verde (\u0026lt;70%), amarillo (70-85%), rojo (\u0026gt;85%) Memory Usage (%) — gauge (medidor)\nQuery: (otel_system_memory_usage_bytes{state=\u0026quot;used\u0026quot;} / otel_system_memory_usage_bytes) * 100 Muestra el porcentaje de memoria usada Umbrales: verde (\u0026lt;70%), amarillo (70-90%), rojo (\u0026gt;90%) Network I/O — gráfico de series temporales\nQuery RX: rate(otel_system_network_io_bytes_total{direction=\u0026quot;receive\u0026quot;}[1m]) Query TX: rate(otel_system_network_io_bytes_total{direction=\u0026quot;transmit\u0026quot;}[1m]) Muestra bytes/segundo recibidos y transmitidos Disk Usage (%) — bar gauge (barras horizontales)\nQuery: (otel_system_filesystem_usage_bytes{state=\u0026quot;used\u0026quot;} / otel_system_filesystem_usage_bytes) * 100 Muestra el uso de disco por dispositivo/punto de montaje El JSON completo del dashboard (paneles, fieldConfig, umbrales y queries) se guarda tal cual en grafana/system.json para que Grafana lo importe en el siguiente paso.\nDespliegue del stack Antes de desplegar, verificamos que todos los archivos estén en su sitio:\n1cd ~/herramientas 2tree Levantar servicios Vamos a iniciar todos los contenedores:\n1docker compose up -d Para ver que se han iniciado correctamente:\n1docker compose ps Uso del stack de monitorización Vamos a acceder a Grafana, poniendo la IP donde tenemos alojado nuestro servidor y el puerto 3000:\nY ahora nos aparecerá un panel lateral a la izquierda; vamos a darle en ese panel a Dashboards → New:\nE importamos nuestro fichero .json que creamos anteriormente; una vez importado, nos aparecerá en el panel:\nEntonces vemos cómo monitorizamos nuestro servidor: la CPU, la RAM, el disco y la red.\nComo vemos, OpenTelemetry recoge las métricas de tu servidor, Prometheus las guarda y Grafana las muestra en gráficos.\n","date":"15 de febrero de 2026","img":"https://jmsaroca.es/images/otel-prometheus-grafana/portadastack.png","lang":"es","langName":"Español","largeImg":"","permalink":"/blog/2026/02/el-stack-de-observabilidad-opentelemetry-prometheus-y-grafana/","series":[],"smallImg":"","tags":[{"title":"OpenTelemetry","url":"/tags/opentelemetry/"},{"title":"Prometheus","url":"/tags/prometheus/"},{"title":"Grafana","url":"/tags/grafana/"},{"title":"Docker","url":"/tags/docker/"},{"title":"Observabilidad","url":"/tags/observabilidad/"}],"timestamp":1771113600,"title":"El Stack De Observabilidad: OpenTelemetry, Prometheus Y Grafana"},{"authors":[],"categories":[],"content":" Creación de la máquina Vamos a crear una máquina virtual en virt-manager, le daremos 2GB de RAM, 2 CPUs y 15GB de disco. Antes de iniciar la instalación es muy importante que en el firmware pongamos que sea de tipo UEFI y con la opción de Secure Boot.\nSecure boot Si cuando hemos iniciado la máquina lo primero que tendremos que hacer es ver si tenemos el Secure Boot activado; si no, tendremos que hacer lo siguiente:\n1sudo dmesg | grep -i \u0026#34;secure boot\u0026#34; Si no lo está, apagamos la VM:\n1sudo virsh shutdown \u0026lt;nombredlamaquina\u0026gt; Eliminamos el NVRAM actual si existe:\n1sudo rm /var/lib/libvirt/qemu/nvram/Kernel_VARS.fd Y copiamos el NVRAM con claves Microsoft:\n1sudo cp /usr/share/OVMF/OVMF_VARS_4M.ms.fd /var/lib/libvirt/qemu/nvram/Kernel_VARS.fd Y ajustamos permisos:\n1sudo chown libvirt-qemu:kvm /var/lib/libvirt/qemu/nvram/Kernel_VARS.fd 2sudo chmod 600 /var/lib/libvirt/qemu/nvram/Kernel_VARS.fd Y cuando encendamos ahora la máquina, nos aparecerá activado ya:\nInstalación de las dependencias Instalar todas las herramientas necesarias para compilar el kernel, crear paquetes .deb, firmar binarios y gestionar el arranque UEFI.\n1sudo apt update \u0026amp;\u0026amp; sudo apt install build-essential libncurses-dev bison flex libdw-dev libssl-dev libelf-dev bc xz-utils fakeroot debhelper locales curl rsync sbsigntool lvm2 efibootmgr -y Explicación de los paquetes más importantes:\nbuild-essential: Compilador GCC, make y herramientas básicas de compilación libssl-dev, libelf-dev: Necesarios para firmar módulos del kernel y trabajar con binarios ELF sbsigntool: Herramienta para firmar el kernel con claves Secure Boot fakeroot, debhelper: Permiten crear paquetes .deb sin ser root lvm2: Soporte para volúmenes lógicos en el initramfs efibootmgr: Gestiona entradas de arranque UEFI desde Linux Crear entorno de trabajo Organizar el espacio de trabajo en /usr/src, ubicación estándar para código fuente del kernel en sistemas Linux.\n1sudo mkdir /usr/src/compilacion 2cd /usr/src/compilacion Descargar código fuente del kernel Obtener el código fuente oficial del kernel Linux desde kernel.org para compilarlo.\nPrimero, buscamos la última versión estable 6.12.x:\n1curl -s https://cdn.kernel.org/pub/linux/kernel/v6.x/ | grep -o \u0026#39;linux-6\\.12\\.[0-9]*\\.tar\\.xz\u0026#39; | sort -V | tail -1 Explicación del comando:\ncurl -s: Descarga silenciosamente el listado HTML grep -o: Extrae solo las coincidencias del patrón sort -V: Ordena por versión tail -1: Muestra la última (más reciente) Ahora descargamos la versión encontrada:\n1sudo wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.12.68.tar.xz Y descomprimimos el archivo:\n1sudo tar -xf linux-6.12.68.tar.xz 2cd linux-6.12.68 Configurar el kernel Adaptar la configuración del kernel actual a la nueva versión, eliminando módulos innecesarios para acelerar la compilación.\n1sudo cp /boot/config-$(uname -r) .config Este archivo .config contiene todas las opciones de compilación del kernel actual. Es un punto de partida seguro porque ya sabemos que funciona con nuestro hardware.\n1sudo make olddefconfig olddefconfig toma la .config antigua y la adapta a la nueva versión del kernel, usando valores por defecto para opciones nuevas sin hacer preguntas interactivas.\n1sudo make localmodconfig localmodconfig analiza qué módulos están cargados actualmente (lsmod) y desactiva todos los demás en el .config. Esto reduce el tiempo de compilación.\nCompilar el kernel\nCompilar el código fuente del kernel y crear paquetes .deb instalables.\n1time sudo make bindeb-pkg -j$(nproc) time: Mide cuánto tarda la compilación make bindeb-pkg: Compila el kernel y crea paquetes .deb -j$(nproc): Usa todos los núcleos de CPU disponibles $(nproc): Devuelve el número de CPUs Instalar paquetes del kernel Vamos a instalar el kernel compilado, sus headers y bibliotecas en el sistema.\n1cd .. 2sudo dpkg -i linux-*.deb Verás varios archivos .deb:\nlinux-image-6.12.68_*.deb: El kernel en sí linux-headers-6.12.68_*.deb: Headers para compilar módulos externos linux-libc-dev_*.deb: Headers para desarrollo de aplicaciones Firma del Kernel para Secure Boot Organizar las claves de firma en un directorio dedicado para mantenerlas seguras y localizadas.\n1sudo mkdir -p /usr/src/compilacion/claves 2cd /usr/src/compilacion/claves Generar par de claves Crear un par de claves RSA (pública y privada) para firmar digitalmente nuestro kernel. Secure Boot verificará esta firma antes de permitir el arranque.\n1sudo openssl req -new -x509 -newkey rsa:2048 -keyout MOK.priv \\ 2-outform DER -out MOK.der -days 3650 \\ 3-subj \u0026#34;/CN=JoseManuel Kernel CA/\u0026#34; \\ 4-nodes Explicación de parámetros:\n-x509: Crea un certificado autofirmado -newkey rsa:2048: Genera clave RSA de 2048 bits (estándar de seguridad) -keyout MOK.priv: Archivo de clave privada (mantener en secreto) -outform DER -out MOK.der: Certificado público en formato binario DER -days 3650: Válido por 10 años -subj \u0026quot;/CN=...\u0026quot;: Nombre identificativo del certificado -nodes: No encriptar la clave privada con contraseña (simplifica el proceso) Convertir formato de clave Convertir el certificado de formato binario (DER) a formato texto (PEM) para usarlo con sbsign:\n1sudo openssl x509 -inform der -in MOK.der -out MOK.pem El formato PEM es más compatible con herramientas de firma. Contiene el mismo certificado pero codificado en Base64 entre delimitadores BEGIN/END CERTIFICATE.\nImportar clave en MOK Registrar nuestra clave en el sistema MOK (Machine Owner Key) para que Secure Boot confíe en ella.\n1sudo mokutil --import MOK.der Esta contraseña es temporal y solo se usa una vez en el siguiente reinicio para confirmar que realmente queremos añadir esta clave al firmware. MOK es un sistema que permite a los propietarios de máquinas añadir sus propias claves de confianza sin desactivar Secure Boot.\nFirmar el kernel Añadir una firma digital al kernel usando nuestra clave privada. Secure Boot verificará esta firma con el certificado público enrollado en MOK.\n1sudo sbsign --key MOK.priv --cert MOK.pem \\ 2/boot/vmlinuz-6.12.68 \\ 3--output /boot/vmlinuz-6.12.68.signed 4 5sudo mv /boot/vmlinuz-6.12.68.signed /boot/vmlinuz-6.12.68 Explicación:\nsbsign: Herramienta de firma de binarios UEFI --key MOK.priv: Usa nuestra clave privada para firmar --cert MOK.pem: Incluye el certificado público en la firma Se crea un archivo .signed que luego reemplaza al original Verificar firma Confirmar que el kernel se firmó correctamente antes de reiniciar.\n1sudo sbverify --cert MOK.pem /boot/vmlinuz-6.12.68 sbverify comprueba que la firma digital del kernel corresponde a nuestro certificado. Si falla aquí, no arrancaría con Secure Boot activo.\nActualizar GRUB Regenerar la configuración de GRUB para que incluya el nuevo kernel firmado en el menú de arranque:\n1sudo update-grub GRUB busca todos los kernels en /boot y genera /boot/grub/grub.cfg con las entradas de arranque.\nReiniciar para enrollar la clave MOK Arrancar en el MOK Manager para confirmar e inscribir nuestra clave en el firmware UEFI.\n1sudo reboot Aparecerá una pantalla azul \u0026ldquo;Perform MOK management\u0026rdquo; ANTES de que arranque el sistema operativo.\nSelecciona \u0026ldquo;Enroll MOK\u0026rdquo; Selecciona \u0026ldquo;Continue\u0026rdquo; Selecciona \u0026ldquo;Yes\u0026rdquo; Ingresa la contraseña que pusiste antes Selecciona Reboot MOK Manager es una interfaz pre-boot que permite gestionar claves de confianza. La contraseña evita que alguien con acceso físico añada claves maliciosas sin tu consentimiento.\nVerificar arranque con nuevo kernel 1ssh josemanuel@192.168.122.143 2uname -r uname -r muestra la versión del kernel actualmente en ejecución. Si muestra tu versión compilada, todo funciona correctamente.\nVerificar Secure Boot y clave MOK Confirmar que Secure Boot sigue activo y que nuestra clave está correctamente enrollada en el firmware.\nVerificar Secure Boot activo:\n1sudo dmesg | grep -i \u0026#34;secure boot\u0026#34; Ver estado de Secure Boot:\n1sudo mokutil --sb-state Listar claves MOK enrolladas:\n1mokutil --list-enrolled | grep -A 10 \u0026#34;JoseManuel\u0026#34; Estos comandos verifican tres cosas: (1) que el kernel detectó Secure Boot activo al arrancar, (2) que el firmware UEFI tiene Secure Boot habilitado, y (3) que nuestra clave está en la base de datos MOK del firmware.\nArranque Directo sin GRUB Copiar kernel e initrd a EFI Colocar el kernel firmado y el initramfs en la partición EFI para que sean accesibles directamente por el firmware UEFI.\n1sudo cp /boot/vmlinuz-6.12.68 /boot/efi/EFI/debian/ 2sudo cp /boot/initrd.img-6.12.68 /boot/efi/EFI/debian/ Cuando usas EFI Stub para arranque directo, el kernel debe tener extensión .efi para que el firmware UEFI lo reconozca correctamente como un ejecutable EFI.\n1sudo mv /boot/efi/EFI/debian/vmlinuz-6.12.68 /boot/efi/EFI/debian/vmlinuz-6.12.68.efi EFI Stub es una característica del kernel Linux que lo convierte en un ejecutable EFI válido. Esto permite que el firmware UEFI cargue el kernel directamente, sin intermediarios como GRUB.\nObtener UUID de la partición raíz Identifica el UUID único de tu partición raíz para pasárselo al kernel en los parámetros de arranque.\n1lsblk -f El UUID identifica de forma única una partición. Es más confiable que usar /dev/vd* porque los nombres de dispositivo pueden cambiar entre reinicios.\nObtenemos su UUID:\n1export UUID=$(sudo blkid -s UUID -o value /dev/vda2) Creamos la entrada UEFI para arranque directo:\n1sudo efibootmgr --create --disk /dev/vda --part 1 \\ 2 --label \u0026#34;Kernel arranque directo\u0026#34; \\ 3 --loader \u0026#39;\\EFI\\debian\\vmlinuz-6.12.68.efi\u0026#39; \\ 4 --unicode \u0026#34;root=UUID=$UUID ro initrd=\\\\EFI\\\\debian\\\\initrd.img-6.12.68\u0026#34; Explicación de parámetros:\n--create: Crea una nueva entrada de arranque --disk /dev/vda --part 1: Especifica dónde está la partición EFI --label \u0026quot;Kernel arranque directo\u0026quot;: Nombre que aparecerá en el menú de arranque UEFI --loader '\\EFI\\debian\\vmlinuz-6.12.68.efi': Ruta al ejecutable EFI en la partición --unicode \u0026quot;...\u0026quot;: Parámetros que se pasan al kernel: root=UUID=$UUID: Le dice al kernel dónde está la partición raíz ro: Monta en solo lectura inicialmente initrd=\\\\EFI\\\\debian\\\\initrd.img-6.12.68: Ruta al initramfs Verificar entradas de arranque Revisar todas las entradas de arranque UEFI y localizar nuestra entrada recién creada.\n1sudo efibootmgr -v Como vemos, nuestra entrada creada está en primer lugar del BootOrder.\nCopiar certificado a partición EFI Copiamos la clave MOK a la partición EFI:\n1sudo cp /usr/src/compilacion/claves/MOK.der /boot/efi/ Ahora reiniciamos y verificamos el arranque directo. Comprobar que el sistema arranca directamente con nuestro kernel sin pasar por GRUB:\n1sudo reboot Enrollar certificado en el firmware UEFI Al reiniciar, el sistema no encontrará un bootloader válido y entrará automáticamente en la configuración UEFI. Aquí debemos enrollar nuestro certificado en la base de datos DB del firmware:\nEl sistema arranca en el menú de configuración UEFI Navega a Device Manager → Secure Boot Configuration → Secure Boot Mode → Custom Mode Selecciona Custom Secure Boot Options → DB Options → Enroll Signature Selecciona Enroll Signature Using File Navega en el explorador de archivos hasta encontrar MOK.der en la partición EFI:\nSelecciona el archivo MOK.der y presiona Enter:\nSelecciona Commit Changes and Exit:\nVerificación del arranque directo Una vez reiniciado el sistema, ya arrancará directo sin pantalla de GRUB:\n","date":"30 de enero de 2026","img":"https://jmsaroca.es/images/kernel-secure-boot/portadakernel.png","lang":"es","langName":"Español","largeImg":"","permalink":"/blog/2026/01/compilaci%C3%B3n-del-kernel-linux-y-firma-para-secure-boot/","series":[],"smallImg":"","tags":[{"title":"Kernel","url":"/tags/kernel/"},{"title":"Secure Boot","url":"/tags/secure-boot/"},{"title":"UEFI","url":"/tags/uefi/"},{"title":"Linux","url":"/tags/linux/"}],"timestamp":1769731200,"title":"Compilación Del Kernel Linux Y Firma Para Secure Boot"},{"authors":[],"categories":[],"content":" A) VPN de acceso remoto con OpenVPN y certificados x509 Escenario:\nEl escenario ya está montado con su reenvío de paquetes, ips, snat; en esta práctica simplemente nos centraremos en el servidor y en el cliente para que se comunique con el servidor. Sabiendo esto, lo primero que vamos a hacer es configurar el ServidorVPN.\nInstalación de OpenVPN y OpenSSL 1sudo apt update \u0026amp;\u0026amp; sudo apt install openvpn openssl -y Y vamos a crear también la estructura de los directorios: en la carpeta ca tendremos los certificados de la autoridad certificadora, en servidor los certificados del servidorvpn, y en cliente los certificados del clientevpn.\n1sudo mkdir -p /etc/openvpn/ca 2sudo mkdir -p /etc/openvpn/servidor 3sudo mkdir -p /etc/openvpn/cliente Generar Certificados Generar la Autoridad Certificadora Generamos la clave privada de la CA:\n1sudo openssl genrsa -out /etc/openvpn/ca/ca.key 2048 Vamos a generar ahora el certificado autofirmado de la CA:\n1sudo openssl req -new -x509 -days 3650 \\ 2-key /etc/openvpn/ca/ca.key \\ 3-out /etc/openvpn/ca/ca.crt \\ 4-subj \u0026#34;/C=ES/ST=Sevilla/L=DosHermanas/O=iesgn/OU=informatica/CN=CA-josemanuel/emailAddress=hola@gmail.com\u0026#34; Generar certificado del Servidor Generamos la clave privada del servidor:\n1sudo openssl genrsa -out /etc/openvpn/servidor/servidor.key 2048 Y generamos el CSR del servidor:\n1sudo openssl req -new -key /etc/openvpn/servidor/servidor.key -out /etc/openvpn/servidor/servidor.csr -subj \u0026#34;/C=ES/ST=Sevilla/L=DosHermanas/O=iesgn/OU=informatica/CN=servidor.iesgn.org/emailAddress=adios@gmail.com\u0026#34; Ahora firmamos el certificado del servidor con la CA:\n1sudo openssl x509 -req -days 3650 \\ 2-in /etc/openvpn/servidor/servidor.csr \\ 3-CA /etc/openvpn/ca/ca.crt \\ 4-CAkey /etc/openvpn/ca/ca.key \\ 5-set_serial 01 \\ 6-out /etc/openvpn/servidor/servidor.crt Generar certificado del CLIENTE Generamos también la clave privada del cliente:\n1sudo openssl genrsa -out /etc/openvpn/cliente/josemanuel.key 2048 Generamos el CSR del cliente:\n1sudo openssl req -new -key /etc/openvpn/cliente/josemanuel.key -out /etc/openvpn/cliente/josemanuel.csr -subj \u0026#34;/C=ES/ST=Sevilla/L=DosHermanas/O=iesgn/OU=informatica/CN=cliente.iesgn.org/emailAddress=cliente@gmail.com\u0026#34; Y lo firmamos con la CA:\n1sudo openssl x509 -req -days 3650 \\ 2-in /etc/openvpn/cliente/josemanuel.csr \\ 3-CA /etc/openvpn/ca/ca.crt \\ 4-CAkey /etc/openvpn/ca/ca.key \\ 5-set_serial 02 \\ 6-out /etc/openvpn/cliente/josemanuel.crt Generar parámetros Diffie-Hellman Diffie-Hellman es un algoritmo matemático que permite que cliente y servidor acuerden una clave secreta compartida sin enviarla por la red.\n1sudo openssl dhparam -out /etc/openvpn/dh2048.pem 2048 Vemos que todo está bien estructurado:\nCrear archivos de extensiones X509v3 Vamos a tener que crear también unos archivos de extensiones X509v3, ya que OpenVPN verifica que el certificado sea específicamente para servidor o cliente y, sin estas extensiones, OpenVPN rechaza la conexión.\nEste es para el servidor:\n1sudo nano /etc/openvpn/servidor-ext.cnf 1keyUsage = digitalSignature, keyEncipherment 2extendedKeyUsage = serverAuth Cliente:\n1sudo nano /etc/openvpn/cliente-ext.cnf 1keyUsage = digitalSignature 2extendedKeyUsage = clientAuth digitalSignature firma datos para probar que vienen del servidor. keyEncipherment cifra las claves simétricas que luego se usan para cifrar el túnel VPN. serverAuth es que solo se puede usar para autenticarse como servidor. clientAuth es que solo se puede usar para autenticarse como cliente.\nConfigurar el servidor OpenVPN Creamos el archivo de configuración del servidor:\n1sudo nano /etc/openvpn/servidor.conf 1port 1194 2proto udp 3dev tun 4ca /etc/openvpn/ca/ca.crt 5cert /etc/openvpn/servidor/servidor.crt 6key /etc/openvpn/servidor/servidor.key 7dh /etc/openvpn/dh2048.pem 8server 10.99.99.0 255.255.255.0 9persist-key 10persist-tun 11status /var/log/openvpn-status.log 12log-append /var/log/openvpn.log 13verb 3 14duplicate-cn 15keepalive 10 120 Explicación de cada parámetro:\nport 1194: Puerto UDP donde el servidor escucha conexiones entrantes. proto udp: Protocolo de transporte. UDP es mejor que TCP para la VPN, para una menor latencia y mejor rendimiento. dev tun: Tipo de interfaz virtual. ca /etc/openvpn/ca/ca.crt: Certificado de la Autoridad Certificadora, valida la autenticidad de certificados de servidor y clientes. cert /etc/openvpn/servidor/servidor.crt: Certificado público del servidor, identifica el servidor ante los clientes. key /etc/openvpn/servidor/servidor.key: Clave privada del servidor, usada para descifrar y firma digital. dh /etc/openvpn/dh2048.pem: Parámetros Diffie-Hellman, permiten el intercambio seguro de claves simétricas mediante Perfect Forward Secrecy (PFS). server 10.99.99.0 255.255.255.0: Define la red del túnel VPN; el servidor toma 10.99.99.1 y asigna IPs del rango 10.99.99.4-10.99.99.251 a los clientes. persist-key: Mantiene las claves criptográficas y evita relectura de archivos protegidos por permisos restrictivos. persist-tun: Mantiene la interfaz TUN abierta durante reinicios suaves y previene pérdida temporal de conectividad. status /var/log/openvpn-status.log: Archivo de estado con clientes conectados, IPs asignadas, bytes transferidos y timestamp de última actividad. log-append /var/log/openvpn.log: Registro acumulativo de eventos, esencial para auditoría y troubleshooting. verb 3: Nivel de verbosidad (0-11). 3 es producción estándar: muestra conexiones, desconexiones y errores sin exceso de detalle. duplicate-cn: Permite múltiples conexiones simultáneas con el mismo Common Name; por defecto, OpenVPN rechaza conexiones duplicadas por seguridad. keepalive 10 120: Envía ping cada 10 segundos y, si no hay respuesta en 120 segundos, reinicia la conexión. Habilitamos el IP Forwarding en el ServidorVPN 1sudo nano /etc/sysctl.conf Descomentamos la siguiente línea:\nAplicamos cambios y verificamos:\n1sudo sysctl -p 2cat /proc/sys/net/ipv4/ip_forward Iniciar OpenVPN 1sudo systemctl start openvpn@servidor 2sudo systemctl enable openvpn@servidor 3sudo systemctl status openvpn@servidor Y como vemos, ya nos aparece la interfaz tun0 levantada:\nConfiguración Cliente Tenemos que copiar los certificados desde ServidorVPN a ClienteVPN; para ello nos pasaremos los certificados o bien por correo cifrado, scp\u0026hellip; etc.\nCrear la configuración del cliente 1sudo nano /etc/openvpn/cliente.conf 1client 2dev tun 3proto udp 4 5remote 51.76.222.3 1194 6 7ca /etc/openvpn/ca.crt 8cert /etc/openvpn/josemanuel.crt 9key /etc/openvpn/josemanuel.key 10 11persist-key 12persist-tun 13 14remote-cert-tls server 15 16log-append /var/log/openvpn-client.log 17verb 3 18 19keepalive 10 120 20 21resolv-retry infinite 22nobind Iniciamos el cliente:\n1sudo systemctl start openvpn@cliente 2sudo systemctl enable openvpn@cliente 3sudo systemctl status openvpn@cliente Comprobación para demostrar la VPN Como vemos, el cliente y el servidor ya se pueden comunicar entre sí; en este ejemplo el cliente se puede conectar al servidor sin problemas.\nB) VPN sitio a sitio con OpenVPN y certificados x509 El escenario que hemos hecho es una simulación de dos oficinas conectadas a través de Internet. La infraestructura consta de cuatro máquinas principales: dos servidores VPN, ServidorA y ServidorB, y dos equipos cliente, Sevilla y Betis, que representan los equipos de usuario final en cada sede.\nLa red nat 192.168.122.0/24 es la simulación de \u0026ldquo;Internet\u0026rdquo; y está proporcionada por un dispositivo NAT1 de GNS3 que asigna direcciones IP dinámicas mediante DHCP a las interfaces externas de ambos servidores VPN.\nEscenario:\nInstalación de los paquetes en ServidorA y ServidorB 1sudo apt update \u0026amp;\u0026amp; sudo apt install openvpn openssl iptables-persistent -y Generar Certificados en ServidorA Crear estructura de directorios 1sudo mkdir -p /etc/openvpn/ca 2sudo mkdir -p /etc/openvpn/servidor 3sudo mkdir -p /etc/openvpn/cliente Generar la Autoridad Certificadora El primer paso en la construcción de nuestra PKI es la creación de la Autoridad Certificadora; este proceso se realiza exclusivamente en ServidorA, que actuará como emisor de todos los certificados de la infraestructura. Comenzamos generando una clave privada RSA de 2048 bits, que representa el secreto criptográfico fundamental de toda la PKI y debe ser protegida:\n1sudo openssl genrsa -out /etc/openvpn/ca/ca.key 2048 Este comando utiliza el algoritmo RSA para generar un par de claves asimétricas. La longitud de 2048 bits proporciona un nivel de seguridad considerado robusto para aplicaciones comerciales actuales, ofreciendo protección contra ataques de factorización durante varias décadas según los estándares NIST.\nVamos a generar ahora el certificado autofirmado de la CA:\n1sudo openssl req -new -x509 -days 100 \\ 2-key /etc/openvpn/ca/ca.key \\ 3-out /etc/openvpn/ca/ca.crt \\ 4-subj \u0026#34;/C=ES/ST=Sevilla/L=DosHermanas/O=iesgn/OU=informatica/CN=CA-josemanuel/emailAddress=servera@gmail.com\u0026#34; -new -x509: Crea un certificado autofirmado -days 100: Válido por 100 días -key ca.key: Usa la clave privada que acabamos de crear -out ca.crt: Guarda el certificado público -subj: Información de la CA Crear dos archivos de extensiones X509v3 OpenVPN 2.6+ implementa validaciones estrictas de certificados que requieren la presencia de extensiones X.509v3 específicas. Estas extensiones definen el propósito autorizado del certificado, implementando el principio de seguridad de \u0026ldquo;mínimo privilegio\u0026rdquo;: cada certificado debe tener únicamente los permisos necesarios para su función específica.\nEste es para el servidor:\n1sudo nano /etc/openvpn/servidor-ext.cnf 1keyUsage = digitalSignature, keyEncipherment 2extendedKeyUsage = serverAuth Y este para el cliente:\n1sudo nano /etc/openvpn/cliente-ext.cnf 1keyUsage = digitalSignature 2extendedKeyUsage = clientAuth La extensión keyUsage define las operaciones criptográficas permitidas. digitalSignature habilita la firma digital de datos para autenticación, mientras que keyEncipherment permite el cifrado de claves simétricas durante el intercambio de claves — capacidad exclusiva del servidor, porque en TLS el cliente genera la clave de sesión y la cifra con la clave pública del servidor.\nLa extensión extendedKeyUsage especifica el contexto de uso del certificado. serverAuth marca el certificado como válido únicamente para autenticación de servidores TLS, mientras que clientAuth lo restringe a autenticación de clientes TLS. Esta separación previene ataques de suplantación donde un certificado comprometido de cliente podría intentar utilizarse para suplantar un servidor legítimo.\nGenerar certificado para Sevilla en el ServidorA El proceso de generación de certificados sigue el flujo estándar de solicitud-firma característico de las PKIs. Para cada entidad (ServidorA/Sevilla y ServidorB/Betis) realizamos tres pasos:\nGeneración de clave privada:\n1sudo openssl genrsa -out /etc/openvpn/sevilla/sevilla.key 2048 Creación de CSR:\n1sudo openssl req -new \\ 2-key /etc/openvpn/sevilla/sevilla.key \\ 3-out /etc/openvpn/sevilla/sevilla.csr \\ 4-subj \u0026#34;/C=ES/ST=Sevilla/O=MiEmpresa/CN=vpn-sevilla.empresa.com/emailAddress=sevilla@empresa.com\u0026#34; Firma con la CA:\n1sudo openssl x509 -req -days 3650 \\ 2-in /etc/openvpn/sevilla/sevilla.csr \\ 3-CA /etc/openvpn/ca/ca.crt \\ 4-CAkey /etc/openvpn/ca/ca.key \\ 5-set_serial 01 \\ 6-out /etc/openvpn/sevilla/sevilla.crt \\ 7-extfile /etc/openvpn/servidor-ext.cnf Generar certificado para Betis en el ServidorB Hacemos lo mismo que para Sevilla, pero con el nombre betis y serial 02:\n1sudo openssl genrsa -out /etc/openvpn/betis/betis.key 2048 2 3sudo openssl req -new \\ 4-key /etc/openvpn/betis/betis.key \\ 5-out /etc/openvpn/betis/betis.csr \\ 6-subj \u0026#34;/C=ES/ST=Sevilla/O=MiEmpresa/CN=vpn-betis.empresa.com/emailAddress=betis@empresa.com\u0026#34; 7 8sudo openssl x509 -req -days 3650 \\ 9-in /etc/openvpn/betis/betis.csr \\ 10-CA /etc/openvpn/ca/ca.crt \\ 11-CAkey /etc/openvpn/ca/ca.key \\ 12-set_serial 02 \\ 13-out /etc/openvpn/betis/betis.crt \\ 14-extfile /etc/openvpn/cliente-ext.cnf Es crucial asignar números de serie únicos (-set_serial) a cada certificado, ya que la CA debe mantener un registro de todos los certificados emitidos para posibilitar futuras revocaciones mediante CRL o OCSP.\nGenerar parámetros Diffie-Hellman 1sudo openssl dhparam -out /etc/openvpn/dh2048.pem 2048 Diffie-Hellman es un protocolo criptográfico que permite que dos partes establezcan un secreto compartido a través de un canal inseguro sin transmitir directamente ninguna clave. Proporciona Perfect Forward Secrecy: incluso si la clave privada del servidor se viera comprometida en el futuro, las sesiones pasadas permanecerán protegidas, porque cada sesión usa claves efímeras generadas mediante DH que no se almacenan permanentemente.\nLa generación de estos parámetros es computacionalmente intensiva porque requiere encontrar números primos grandes y calcular generadores del grupo cíclico correspondiente.\nVerificamos que lo tengamos de esta manera:\nAhora nos iremos al ServidorB y crearemos la estructura de directorios:\n1sudo mkdir -p /etc/openvpn/ca 2sudo mkdir -p /etc/openvpn/betis Y nos tendremos que pasar el ca.crt, betis.crt y betis.key; para ello lo haremos por scp, aunque se podría hacer por correo también o por donde se prefiera.\nMoveremos ahora los archivos y tiene que quedar tal que así:\nConfiguración OpenVPN en el ServidorA El servidor OpenVPN se configura en modo tls-server, escuchando conexiones entrantes en el puerto UDP 1194. La configuración establece un túnel punto a punto con direccionamiento estático:\n1sudo nano /etc/openvpn/sevilla.conf 1dev tun 2proto udp 3port 1194 4tls-server 5 6ca /etc/openvpn/ca/ca.crt 7cert /etc/openvpn/sevilla/sevilla.crt 8key /etc/openvpn/sevilla/sevilla.key 9dh /etc/openvpn/dh2048.pem 10 11ifconfig 10.99.99.1 10.99.99.2 12 13route 192.168.2.0 255.255.255.0 14 15persist-key 16persist-tun 17 18status /var/log/openvpn-sevilla-status.log 19log-append /var/log/openvpn-sevilla.log 20verb 3 21 22keepalive 10 120 Análisis de parámetros clave:\ndev tun: Crea una interfaz de red virtual de capa 3 (IP). Las interfaces TUN operan con paquetes IP completos. proto udp: UDP es preferible para VPNs porque evita la \u0026ldquo;retransmisión doble\u0026rdquo; que ocurre con TCP sobre TCP: si usáramos TCP, tanto el túnel VPN como las conexiones dentro del túnel intentarían retransmitir paquetes perdidos, degradando significativamente el rendimiento. tls-server: Declara este extremo como servidor TLS, habilitando el handshake TLS donde el servidor presenta su certificado primero. ifconfig 10.99.99.1 10.99.99.2: Configura el direccionamiento punto a punto. El primer IP (10.99.99.1) es la dirección local, el segundo (10.99.99.2) es el peer remoto. route 192.168.2.0 255.255.255.0: Añade una ruta estática hacia la red remota (Betis) a través del túnel. Sin esta ruta, el servidor no sabría cómo alcanzar la red 192.168.2.0/24. persist-key y persist-tun: Mantienen las claves y la interfaz TUN abierta durante reinicios suaves (SIGUSR1), evitando recargar archivos y destruir/recrear la interfaz innecesariamente. keepalive 10 120: Envía pings de control cada 10 segundos; si no hay respuesta en 120 segundos, reinicia la conexión. Esto detecta enlaces caídos rápidamente. Habilitar IP Forwarding en los dos servidores Por defecto, los sistemas Linux no reenvían paquetes entre interfaces de red; para que nuestros servidores VPN puedan enrutar tráfico entre sus redes locales y el túnel VPN, debemos habilitar el IP forwarding:\n1sudo nano /etc/sysctl.conf 1sudo sysctl -p Iniciar OpenVPN 1sudo systemctl start openvpn@sevilla 2sudo systemctl enable openvpn@sevilla 3sudo systemctl status openvpn@sevilla Crear archivo de configuración para el ServidorB El lado cliente se conecta activamente al servidor y opera en modo tls-client:\n1sudo nano /etc/openvpn/betis.conf 1remote 192.168.122.227 1194 2dev tun 3proto udp 4tls-client 5 6ca /etc/openvpn/ca/ca.crt 7cert /etc/openvpn/betis/betis.crt 8key /etc/openvpn/betis/betis.key 9 10ifconfig 10.99.99.2 10.99.99.1 11 12route 192.168.1.0 255.255.255.0 13 14remote-cert-tls server 15 16persist-key 17persist-tun 18 19log-append /var/log/openvpn-betis.log 20verb 3 21 22keepalive 10 120 23nobind Parámetros del cliente:\nremote 192.168.122.227 1194: Especifica la dirección IP y puerto del servidor VPN. El cliente inicia activamente la conexión. tls-client: Opera como cliente TLS, esperando que el servidor presente su certificado primero durante el handshake. remote-cert-tls server: Valida que el certificado presentado por el peer remoto contenga la extensión serverAuth. Previene ataques man-in-the-middle donde un atacante podría interceptar la conexión usando un certificado de cliente robado. nobind: No vincula a un puerto local específico. Útil para clientes detrás de NAT o que necesitan múltiples conexiones simultáneas. El direccionamiento ifconfig es inverso al del servidor: local 10.99.99.2, peer 10.99.99.1.\nIniciamos OpenVPN en el ServidorB 1sudo systemctl start openvpn@betis 2sudo systemctl enable openvpn@betis 3sudo systemctl status openvpn@betis Comprobación Como vemos, ambos se hacen ping, confirmando que el túnel encriptado funciona correctamente.\nFinalmente, probamos servicios reales como SSH, y vemos cómo desde Sevilla podemos conectarnos a Betis, y Betis se puede conectar a Sevilla simultáneamente.\nC) VPN de acceso remoto con WireGuard WireGuard es un protocolo VPN moderno que destaca por su simplicidad, rendimiento superior y base de código reducida. Utiliza criptografía de última generación (Curve25519, ChaCha20, Poly1305) y fue integrado oficialmente en el kernel de Linux desde la versión 5.6.\nEn esta práctica se implementa una infraestructura VPN completa utilizando WireGuard, permitiendo la conexión segura de múltiples clientes (Windows, Linux y Android) a través de Internet, con acceso tanto a la red privada del servidor como a Internet mediante NAT, con direccionamiento de IP \u0026ldquo;públicas\u0026rdquo; para simular Internet.\nEscenario:\nTanto en el router como en el servidor ya está configurado el reenvío de paquetes y el SNAT para que puedan salir a Internet los clientes. También en el Router tengo un servidor DHCP que asigna las IPs automáticamente a los clientes.\nInstalación y Configuración del ServidorVPN Instalación de software 1sudo apt update \u0026amp;\u0026amp; sudo apt install wireguard wireguard-tools -y El paquete wireguard-tools proporciona las utilidades de espacio de usuario:\nwg: Herramienta de configuración y monitorización de interfaces WireGuard wg-quick: Script de alto nivel que simplifica la gestión de configuraciones completas Criptografía y gestión de claves Modelo de seguridad de WireGuard WireGuard implementa un modelo de seguridad radicalmente diferente al de las VPNs tradicionales. En lugar de depender de certificados X.509 con jerarquías de confianza y autoridades certificadoras, WireGuard utiliza criptografía de clave pública asimétrica donde cada peer (tanto servidor como clientes) genera un par de claves criptográficas:\nClave privada: secreto criptográfico de 256 bits que nunca sale del dispositivo que lo generó. Es análoga a una clave privada SSH y debe protegerse. Clave pública: derivada matemáticamente de la clave privada, se comparte con los peers autorizados y actúa como identificador único del dispositivo. Generación de claves criptográficas En el ServidorVPN, nos vamos al directorio que ha creado WireGuard para las claves:\n1cd /etc/wireguard Generamos clave privada y pública del servidor:\n1wg genkey | sudo tee server_private.key | wg pubkey | sudo tee server_public.key Generamos la clave privada para el cliente Windows (la pública nos la dará Windows al meter la clave privada):\n1wg genkey | sudo tee windows_private.key Generamos claves para el cliente Linux:\n1wg genkey | sudo tee linux_private.key | wg pubkey | sudo tee linux_public.key Generamos la clave para Android (solo la privada, la pública nos la dará Android):\n1wg genkey | sudo tee android_private.key Protegemos las claves privadas:\n1sudo chmod 600 /etc/wireguard/*_private.key Normalmente se generan en el servidor tanto las claves privadas como las públicas, y se pasarían al cliente correspondiente para la configuración (scp, email\u0026hellip;). En este caso se hace distinto: creándolas desde la clave privada que da el servidor, o incluso generándolas desde cero en el propio cliente — lo importante es que luego, en el archivo de configuración, cada clave esté puesta correctamente.\nCrear configuración del servidor WireGuard utiliza archivos de configuración INI simples ubicados en /etc/wireguard/. El nombre del archivo determina el nombre de la interfaz que se creará (wg0.conf → interfaz wg0).\n1sudo nano /etc/wireguard/wg0.conf 1[Interface] 2#ip del tunel 3Address = 10.99.99.1/24 4ListenPort = 51820 5#clave privada del servidor 6PrivateKey = ***** 7 8#Windows 9[Peer] 10PublicKey = ibCzEN6XO6Gb2OWOSC4mkBROyDICHll3YPz03ddxynI= 11AllowedIPs = 10.99.99.2/32 12PersistentKeepalive = 25 13 14#Cliente Linux 15[Peer] 16PublicKey = ESgSFMN4/VMkg7WYfg4e0DHnj/r0hwl3TkZYXoxLPT8= 17AllowedIPs = 10.99.99.3/32 18PersistentKeepalive = 25 19 20#Android 21[Peer] 22PublicKey = HAnqlFKDaIXvOTcsmRAcUM9v6a3o1iynWj3z9hEXgVE= 23AllowedIPs = 10.99.99.4/32 24PersistentKeepalive = 25 Explicación de parámetros:\nAddress: IP que tendrá el servidor dentro del túnel VPN ListenPort: Puerto UDP donde WireGuard escucha conexiones (51820 por defecto) PrivateKey: Clave privada del servidor PublicKey: Clave pública del cliente AllowedIPs: IPs que el cliente puede usar en el túnel PersistentKeepalive: Envía paquetes keepalive cada 25 segundos para mantener la conexión activa a través de NATs Iniciar y habilitar WireGuard Levantamos la interfaz wg0:\n1sudo wg-quick up wg0 Habilitamos para que se inicie automáticamente en el arranque:\n1sudo systemctl enable wg-quick@wg0 Y verificamos el estado:\n1sudo wg show Configuración ClienteLinux Instalación de WireGuard En el cliente Linux instalamos los mismos paquetes que en el servidor:\n1sudo apt update \u0026amp;\u0026amp; sudo apt install wireguard wireguard-tools -y Crear configuración del cliente En el archivo de configuración meteremos la clave privada propia y la pública que nos ha dado el servidor.\n1sudo nano /etc/wireguard/wg0.conf 1[Interface] 2Address = 10.99.99.3/24 3PrivateKey = ***** 4DNS = 8.8.8.8 5 6[Peer] 7PublicKey = c6uL2aELD42hoOQoB+NbVCm8NEENt1BKpiHtwh+MjjQ= 8Endpoint = 200.100.50.150:51820 9AllowedIPs = 0.0.0.0/0 10PersistentKeepalive = 25 Ahora vamos a iniciar la VPN La iniciamos:\n1sudo wg-quick up wg0 Habilitamos el arranque:\n1sudo systemctl enable wg-quick@wg0 Verificamos el estado:\n1sudo wg show Comprobación Para probar la conexión hacemos ping al servidor y nos conectamos por SSH:\nConfiguración cliente Windows Instalación Lo primero es instalar WireGuard, desde: wireguard.com/install\nUna vez ejecutado el .exe se instalará y comenzaremos con la configuración.\nConfiguración Windows Le damos a \u0026ldquo;Add Tunnel\u0026rdquo; y creamos la configuración:\nLa configuración queda así: metemos la clave privada que generamos en el servidor y se crea automáticamente (debajo de Name) la clave pública, que hay que copiar en el archivo de configuración del servidor wg0.conf.\nGuardamos y le damos a activar:\nComprobación Para probar si está bien, hacemos ping al servidor y a \u0026ldquo;DondeVamosAConectarnos\u0026rdquo; para ver si hay comunicación fuera de nuestra red.\nY en el servidor, con sudo wg show, también nos aparecería el peer conectado:\nConfiguración cliente Android Instalación Para instalar WireGuard en Android hay que ir a Play Store y buscar WireGuard.\nUna vez instalada, creamos el túnel VPN desde cero, dándole al \u0026ldquo;+\u0026rdquo;.\nCreamos la configuración: en el nombre ponemos lo que queramos, en la clave privada la que se generó en el servidor (se creará la clave pública, que hay que copiar en el wg0.conf del servidor), luego la dirección IP del túnel VPN que tendrá Android (en este caso 10.99.99.4/24) y el servidor DNS 8.8.8.8, para que al activar la VPN meta el nameserver en /etc/resolv.conf.\nMás abajo metemos la clave pública del ServidorVPN, con 25s de keepalive, apuntando a la IP \u0026ldquo;pública\u0026rdquo; del servidor, y permitimos todas las IPs con 0.0.0.0/0.\nGuardamos la configuración y activamos Comprobación Para comprobarlo, entramos en la terminal de Android y hacemos ping al servidor y al cliente de la otra red.\nPruebas de conectividad Si vamos al servidor y ponemos sudo wg show veremos todos los clientes conectados:\nlatest handshake: tiempo desde el último intercambio criptográfico. Si dice \u0026ldquo;(never)\u0026rdquo;, el peer no está conectado. endpoint: IP y puerto desde donde el peer se conecta. transfer: datos transmitidos/recibidos. Comunicación de Windows a los clientes y servidor:\nDe cliente Linux a los clientes y servidores:\nDe Android al cliente y servidores:\nComparación Apartado C vs A WireGuard simplifica la configuración eliminando la necesidad de PKI, certificados X.509 y parámetros Diffie-Hellman que requiere OpenVPN. Utiliza criptografía moderna de curva elíptica (Curve25519) en lugar de RSA/TLS, logrando menor latencia y mejor rendimiento. La configuración es minimalista, un solo archivo con claves públicas/privadas, frente a la complejidad de OpenVPN con múltiples certificados, CA y archivos de extensiones, siendo especialmente superior en dispositivos móviles por su mejor manejo de NAT traversal y cambios de red.\nD) VPN sitio a sitio con WireGuard Escenario:\nConfiguración del ServidorA Instalación de WireGuard 1sudo apt update \u0026amp;\u0026amp; sudo apt install wireguard wireguard-tools -y Generación de las claves criptográficas 1cd /etc/wireguard 2sudo wg genkey | sudo tee serverA_private.key 3sudo cat serverA_private.key | wg pubkey | sudo tee serverA_public.key 4sudo chmod 600 serverA_private.key Creamos el archivo de configuración del servidor 1sudo nano /etc/wireguard/wg0.conf 1[Interface] 2Address = 10.99.99.1/24 3PrivateKey = ***** 4ListenPort = 51820 5 6#servidorb 7[Peer] 8PublicKey = aAabSt754vjHHVHy29PHiUcksA3uhfbOakvk8B96e3o= 9AllowedIPs = 10.99.99.2/32, 10.0.0.0/8 10PersistentKeepalive = 25 Explicación de parámetros:\nAddress: IP que tendrá ServidorA dentro del túnel VPN (10.99.99.1/24) PrivateKey: Clave privada del servidor para autenticación criptográfica ListenPort: Puerto UDP donde escucha conexiones entrantes (51820 por defecto) AllowedIPs: Define qué rangos IP puede usar el peer remoto. Incluye tanto la IP del túnel del peer (10.99.99.2/32) como la red (10.0.0.0/8) PersistentKeepalive: Envía paquetes keepalive cada 25 segundos para mantener el túnel activo, especialmente útil detrás de NATs Configuración del ServidorB Instalación y generación de claves 1sudo apt update \u0026amp;\u0026amp; sudo apt install wireguard wireguard-tools -y 2cd /etc/wireguard 3sudo wg genkey | sudo tee serverB_private.key 4sudo cat serverB_private.key | wg pubkey | sudo tee serverB_public.key 5sudo chmod 600 serverB_private.key Crear configuración del servidor 1sudo nano /etc/wireguard/wg0.conf 1[Interface] 2Address = 10.99.99.2/24 3PrivateKey = ***** 4ListenPort = 51820 5 6#servidorA 7[Peer] 8PublicKey = CI3EFCw4OFXGI4kExF07nLTmYnlFDYzLaIkF/57+Awc= 9Endpoint = 200.100.50.96:51820 10AllowedIPs = 10.99.99.1/32, 172.22.0.0/16 11PersistentKeepalive = 25 Diferencia clave con ServidorA: Endpoint — ServidorB especifica a dónde conectarse (200.100.50.96:51820). ServidorA no tiene Endpoint porque actúa como \u0026ldquo;listener\u0026rdquo; esperando conexiones entrantes; ServidorB actúa como \u0026ldquo;initiator\u0026rdquo; que inicia la conexión. Además, ServidorB añade la ruta inversa a 172.22.0.0/16 (red del Sitio A) vía 10.99.99.1 (ServidorA).\nIniciar y habilitar WireGuard Levantamos la interfaz wg0 en el ServidorA:\n1sudo wg-quick up wg0 2sudo systemctl enable wg-quick@wg0 3sudo wg show Lo mismo hacemos en el ServidorB:\n1sudo wg-quick up wg0 2sudo systemctl enable wg-quick@wg0 3sudo wg show Como vemos, el campo latest handshake confirma que el túnel VPN se estableció correctamente.\nVerificación de conectividad Ping desde Cliente1 a Cliente2, y traceroute para ver que pasa por la VPN:\nLo mismo desde Cliente2 a Cliente1:\nComparación Apartado D vs B En sitio a sitio, WireGuard elimina la distinción cliente-servidor de OpenVPN (el tls-server/tls-client del archivo de configuración), tratando ambos extremos como peers simétricos donde solo uno especifica Endpoint para iniciar la conexión. El enrutamiento es más directo mediante AllowedIPs, especificando redes remotas directamente sin necesidad de rutas estáticas. La configuración es más simple manteniendo el mismo resultado funcional, con menor overhead y reconexión automática más rápida ante caídas del túnel.\n","date":"23 de diciembre de 2025","img":"https://jmsaroca.es/images/vpn-sad/pagina04_img1.png","lang":"es","langName":"Español","largeImg":"","permalink":"/blog/2025/12/vpn-acceso-remoto-y-sitio-a-sitio-con-openvpn-y-wireguard/","series":[],"smallImg":"","tags":[{"title":"VPN","url":"/tags/vpn/"},{"title":"OpenVPN","url":"/tags/openvpn/"},{"title":"WireGuard","url":"/tags/wireguard/"},{"title":"Seguridad","url":"/tags/seguridad/"},{"title":"X509","url":"/tags/x509/"}],"timestamp":1766448000,"title":"VPN: Acceso Remoto Y Sitio a Sitio Con OpenVPN Y WireGuard"},{"authors":[],"categories":[],"content":"Administración de Sistemas Gestores de Bases de Datos — práctica sobre cómo interconectar servidores de bases de datos para que puedan consultar y modificar datos entre sí: Oracle↔Oracle, PostgreSQL↔PostgreSQL, y la interconexión heterogénea Oracle↔PostgreSQL.\nEnlace entre dos servidores Oracle Un enlace de base de datos (database link) en Oracle permite a un usuario conectarse a una base de datos remota y acceder a sus datos como si estuvieran en la base de datos local, mediante un enlace privado que almacena las credenciales de conexión.\nConfiguración básica de las máquinas Dos máquinas Oracle: Oracle1 (192.168.122.222) y Oracle2 (192.168.122.237).\nEn Oracle1:\n1sqlplus / as sysdba 2 3CREATE USER c##josemanuel1 IDENTIFIED BY josemanuel1; 4GRANT CONNECT, RESOURCE TO c##josemanuel1; 5GRANT CREATE SESSION TO c##josemanuel1; 6GRANT CREATE DATABASE LINK TO c##josemanuel1; 7GRANT UNLIMITED TABLESPACE TO c##josemanuel1; 8exit; En Oracle2:\n1sqlplus / as sysdba 2 3CREATE USER c##josemanuel2 IDENTIFIED BY josemanuel2; 4GRANT CONNECT, RESOURCE TO c##josemanuel2; 5GRANT CREATE SESSION TO c##josemanuel2; 6GRANT CREATE DATABASE LINK TO c##josemanuel2; 7GRANT UNLIMITED TABLESPACE TO c##josemanuel2; 8EXIT; Usuarios comunes vs. locales: en una Container Database (CDB), los usuarios C## son comunes y existen en toda la CDB; los usuarios sin C## son locales y solo existen en una Pluggable Database (PDB) concreta. Para crear un usuario en una PDB (sin el prefijo C##) hay que conectarse a ella primero:\n1SHOW PDBS; 2ALTER SESSION SET CONTAINER = ORCLPDB1; 3alter pluggable database orclpdb1 open; 4-- ya se puede crear el usuario sin problema Configurar listener en ambos servidores 1sudo nano /opt/oracle/product/21c/dbhome_1/network/admin/listener.ora En Oracle1:\n1LISTENER = 2 (DESCRIPTION_LIST = 3 (DESCRIPTION = 4 (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.122.222)(PORT = 1521)) 5 ) 6 ) En Oracle2 (misma estructura, con su propia IP):\n1LISTENER = 2 (DESCRIPTION_LIST = 3 (DESCRIPTION = 4 (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.122.237)(PORT = 1521)) 5 ) 6 ) Es recomendable que la IP de cada máquina sea estática (o con reserva DHCP) para que el listener no dé conflictos.\nConfigurar archivo tnsnames.ora En Oracle1:\n1sudo nano /opt/oracle/product/21c/dbhome_1/network/admin/tnsnames.ora 1#Interconexion con oracle2 2oracle2 = 3 (DESCRIPTION = 4 (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.122.237)(PORT = 1521)) 5 (CONNECT_DATA = 6 (SERVICE_NAME = ORCLCDB) 7 ) 8 ) Esto define un alias de conexión llamado oracle2: en vez de escribir toda la cadena de conexión cada vez, basta con usar ese nombre.\noracle2 → nombre del alias que se usará HOST → IP del servidor remoto PORT → puerto donde escucha el listener SERVICE_NAME → nombre del servicio de base de datos (se obtiene con SELECT * FROM global_name;) En Oracle2 se configura el mismo archivo pero apuntando a Oracle1.\nPara comprobar que ambos se comunican:\n1tnsping oracle2 # desde oracle1 2tnsping oracle1 # desde oracle2 Una respuesta de \u0026ldquo;Realizado correctamente\u0026rdquo; confirma que el listener remoto está activo y accesible, y que el database link podrá establecerse sin problemas.\nVerificación de conexión cruzada:\n1-- Desde Oracle1 2sqlplus c##josemanuel2/josemanuel2@oracle2 3 4-- Desde Oracle2 5sqlplus c##josemanuel1/josemanuel1@oracle1 Link Oracle Creación del link en Oracle1:\n1sqlplus c##josemanuel1/josemanuel1 2 3CREATE DATABASE LINK link_oracle2 4CONNECT TO C##josemanuel2 IDENTIFIED BY josemanuel2 5USING \u0026#39;oracle2\u0026#39;; CREATE DATABASE LINK crea el enlace (link_oracle2 es su nombre); CONNECT TO es el usuario remoto de Oracle2; USING 'oracle2' es el alias definido en tnsnames.ora.\nVerificar el enlace creado:\n1SELECT * FROM dba_db_links; Tabla de ejemplo en Oracle1 para probar el link:\n1CREATE TABLE liga_espanola ( 2 id NUMBER PRIMARY KEY, 3 equipo VARCHAR2(100) NOT NULL, 4 ciudad VARCHAR2(50), 5 puntos NUMBER DEFAULT 0, 6 partidos_jugados NUMBER DEFAULT 0 7); 8INSERT INTO liga_espanola VALUES (1, \u0026#39;Sevilla FC\u0026#39;, \u0026#39;Sevilla\u0026#39;, 78, 30); 9INSERT INTO liga_espanola VALUES (2, \u0026#39;Real Madrid\u0026#39;, \u0026#39;Madrid\u0026#39;, 75, 30); 10INSERT INTO liga_espanola VALUES (3, \u0026#39;FC Barcelona\u0026#39;, \u0026#39;Barcelona\u0026#39;, 73, 30); 11INSERT INTO liga_espanola VALUES (4, \u0026#39;Atlético de Madrid\u0026#39;, \u0026#39;Madrid\u0026#39;, 70, 30); 12INSERT INTO liga_espanola VALUES (5, \u0026#39;Real Betis\u0026#39;, \u0026#39;Sevilla\u0026#39;, 65, 30); 13INSERT INTO liga_espanola VALUES (6, \u0026#39;Valencia CF\u0026#39;, \u0026#39;Valencia\u0026#39;, 60, 30); Lo mismo pero al revés (desde Oracle2, para que el enlace sea bidireccional):\n1sqlplus c##josemanuel2/josemanuel2 2 3CREATE DATABASE LINK link_oracle1 4CONNECT TO c##josemanuel1 IDENTIFIED BY josemanuel1 5USING \u0026#39;oracle1\u0026#39;; 6 7CREATE TABLE motos ( 8 id NUMBER PRIMARY KEY, 9 marca VARCHAR2(100) NOT NULL, 10 modelo VARCHAR2(100), 11 cilindrada NUMBER, 12 precio NUMBER(8,2), 13 tipo VARCHAR2(50) 14); 15INSERT INTO motos VALUES (1, \u0026#39;Honda\u0026#39;, \u0026#39;CBR 1000RR\u0026#39;, 1000, 18500.00, \u0026#39;Deportiva\u0026#39;); 16INSERT INTO motos VALUES (2, \u0026#39;Yamaha\u0026#39;, \u0026#39;MT-09\u0026#39;, 890, 11500.00, \u0026#39;Naked\u0026#39;); 17INSERT INTO motos VALUES (3, \u0026#39;Kawasaki\u0026#39;, \u0026#39;Ninja ZX-10R\u0026#39;, 1000, 22500.00, \u0026#39;Deportiva\u0026#39;); 18INSERT INTO motos VALUES (4, \u0026#39;Ducati\u0026#39;, \u0026#39;Panigale V4\u0026#39;, 1103, 28000.00, \u0026#39;Deportiva\u0026#39;); 19INSERT INTO motos VALUES (5, \u0026#39;Harley-Davidson\u0026#39;, \u0026#39;Street Glide\u0026#39;, 1868, 28500.00, \u0026#39;Cruiser\u0026#39;); 20INSERT INTO motos VALUES (6, \u0026#39;BMW\u0026#39;, \u0026#39;R 1250 GS\u0026#39;, 1254, 19500.00, \u0026#39;Adventure\u0026#39;); 21COMMIT; Consultas de datos simultáneas Desde Oracle1 hacia Oracle2:\n1SELECT * FROM motos@link_oracle2; Desde Oracle2 hacia Oracle1:\n1SELECT * FROM liga_espanola@link_oracle1; JOIN combinando datos de ambos servidores en una sola consulta:\n1SELECT m.marca, m.modelo, m.precio, e.equipo, e.ciudad, e.puntos 2FROM motos m, liga_espanola@link_oracle1 e 3WHERE m.id = e.id 4ORDER BY m.id; Errores encontrados (Oracle↔Oracle) Error de listener (tnsping no funcionaba): tenía otro tnsnames.ora/listener.ora escondido en otra ruta, generando conflicto. Solución — quedarme con un único fichero por servicio:\n1# desde root, borro los que sobran 2rm -r /opt/oracle/homes/OraDBHome21cEE/network/admin/tnsnames.ora 3rm -r /opt/oracle/homes/OraDBHome21cEE/network/admin/listener.ora Y dejo solo /opt/oracle/product/21c/dbhome_1/network/admin/{listener,tnsnames}.ora. Luego reinicio el listener:\n1sudo -u oracle bash -c \u0026#39;export ORACLE_HOME=/opt/oracle/product/21c/dbhome_1 \u0026amp;\u0026amp; export PATH=$ORACLE_HOME/bin:$PATH \u0026amp;\u0026amp; lsnrctl stop\u0026#39; 2sudo -u oracle bash -c \u0026#39;export ORACLE_HOME=/opt/oracle/product/21c/dbhome_1 \u0026amp;\u0026amp; export PATH=$ORACLE_HOME/bin:$PATH \u0026amp;\u0026amp; lsnrctl start\u0026#39; Error 2 — el listener conecta pero al intentar entrar con el usuario dice que no encuentra el servicio (ORA-12514): el listener está arrancado pero la base de datos no está registrada en él.\n1sqlplus / as sysdba 2 3-- en oracle1 4ALTER SYSTEM SET LOCAL_LISTENER=\u0026#39;(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.122.221)(PORT=1521))\u0026#39; SCOPE=BOTH; 5ALTER SYSTEM REGISTER; 6EXIT; 7 8-- en oracle2 9ALTER SYSTEM SET LOCAL_LISTENER=\u0026#39;(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.122.237)(PORT=1521))\u0026#39; SCOPE=BOTH; 10ALTER SYSTEM REGISTER; 11EXIT; Se verifica con lsnrctl status: si el servicio ya aparece listado, la conexión funciona. Enlace entre dos servidores PostgreSQL A diferencia de Oracle, donde los database links son nativos, PostgreSQL usa una extensión llamada dblink que hay que habilitar explícitamente.\nDos máquinas: Postgre1 (192.168.122.159, base bd1, tabla portatiles) y Postgre2 (192.168.122.237, base bd2, tabla coches). Se asume que PostgreSQL ya acepta conexiones remotas (configurado en la instalación).\nCreación de usuarios y bases de datos En Postgre1:\n1sudo su - postgres 2psql 3 4CREATE DATABASE bd1; 5CREATE USER josemanuel1 WITH PASSWORD \u0026#39;josemanuel1\u0026#39;; 6GRANT ALL PRIVILEGES ON DATABASE bd1 TO josemanuel1; 7\\c bd1 8GRANT ALL PRIVILEGES ON SCHEMA public TO josemanuel1; 9ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO josemanuel1; En Postgre2 En Postgre2, lo mismo con bd2 / josemanuel2. Verificación de conectividad 1# Desde Postgre1 hacia Postgre2 2psql -h 192.168.122.237 -U josemanuel2 -d bd2 3 4# Desde Postgre2 hacia Postgre1 5psql -h 192.168.122.159 -U josemanuel1 -d bd1 Instalación de la extensión dblink Solo los superusuarios (postgres) pueden crear links; si se quiere que un usuario normal también pueda, se le da con ALTER USER josemanuel1 WITH SUPERUSER;.\nEn Postgre1:\n1psql -U josemanuel1 -d bd1 2CREATE EXTENSION dblink; En Postgre2:\n1psql -U josemanuel2 -d bd2 2CREATE EXTENSION dblink; Creación de las tablas de ejemplo En Postgre1 — tabla portatiles:\n1CREATE TABLE portatiles ( 2 id SERIAL PRIMARY KEY, 3 marca VARCHAR(50), 4 modelo VARCHAR(50), 5 ram INT, 6 almacenamiento INT, 7 precio NUMERIC(10,2) 8); 9INSERT INTO portatiles (marca, modelo, ram, almacenamiento, precio) VALUES 10(\u0026#39;Lenovo\u0026#39;, \u0026#39;ThinkPad X1 Carbon\u0026#39;, 16, 512, 1500.00), 11(\u0026#39;HP\u0026#39;, \u0026#39;Pavilion 15\u0026#39;, 8, 256, 700.00), 12(\u0026#39;Dell\u0026#39;, \u0026#39;XPS 13\u0026#39;, 16, 512, 1600.00), 13(\u0026#39;Asus\u0026#39;, \u0026#39;ROG Strix\u0026#39;, 32, 1024, 2200.00), 14(\u0026#39;Apple\u0026#39;, \u0026#39;MacBook Air M2\u0026#39;, 16, 512, 1800.00); En Postgre2 — tabla coches:\n1CREATE TABLE coches ( 2 id SERIAL PRIMARY KEY, 3 marca VARCHAR(50), 4 modelo VARCHAR(50), 5 año INT, 6 precio NUMERIC(10,2) 7); 8INSERT INTO coches (marca, modelo, año, precio) VALUES 9(\u0026#39;Toyota\u0026#39;, \u0026#39;Corolla\u0026#39;, 2020, 18000.00), 10(\u0026#39;Honda\u0026#39;, \u0026#39;Civic\u0026#39;, 2019, 17500.00), 11(\u0026#39;Ford\u0026#39;, \u0026#39;Focus\u0026#39;, 2018, 15000.00), 12(\u0026#39;BMW\u0026#39;, \u0026#39;Serie 3\u0026#39;, 2021, 35000.00), 13(\u0026#39;Audi\u0026#39;, \u0026#39;A4\u0026#39;, 2022, 38000.00); Funcionamiento del enlace: primera consulta remota La función dblink sigue esta estructura:\n1SELECT * 2FROM dblink( 3 \u0026#39;cadena_de_conexion\u0026#39;, 4 \u0026#39;consulta_SQL\u0026#39; 5) AS alias(definicion_de_columnas); cadena_de_conexion → host, base de datos, usuario y contraseña del servidor remoto consulta_SQL → la consulta a ejecutar en remoto alias → nombre temporal para los resultados definicion_de_columnas → nombre y tipo de cada columna que devuelve la consulta Desde Postgre1, consultando la tabla coches de Postgre2:\n1SELECT * 2FROM dblink( 3 \u0026#39;host=192.168.122.237 dbname=bd2 user=josemanuel2 password=josemanuel2\u0026#39;, 4 \u0026#39;SELECT * FROM coches\u0026#39; 5) AS equipos_remotos( 6 id VARCHAR, 7 marca VARCHAR, 8 modelo VARCHAR, 9 año INT, 10 precio NUMERIC 11); Consulta JOIN entre bases de datos distribuidas 1SELECT p.marca, p.modelo, p.precio, c.marca, c.modelo, c.precio 2FROM portatiles p 3LEFT JOIN dblink( 4 \u0026#39;host=192.168.122.237 dbname=bd2 user=josemanuel2 password=josemanuel2\u0026#39;, 5 \u0026#39;SELECT id, marca, modelo, precio FROM coches\u0026#39; 6) AS c(id INTEGER, marca VARCHAR, modelo VARCHAR, precio NUMERIC) 7ON p.id = c.id 8ORDER BY p.id; Enlace bidireccional Enlace bidireccional Los enlaces con dblink son unidireccionales por sí mismos, así que para tener consultas en ambas direcciones hay que repetir el proceso en el otro sentido. Desde Postgre2 hacia Postgre1:\n1SELECT c.marca, c.modelo, c.año, c.precio, p.marca, p.modelo, p.ram, p.almacenamiento 2FROM coches c, 3dblink( 4 \u0026#39;host=192.168.122.159 dbname=bd1 user=josemanuel1 password=josemanuel1\u0026#39;, 5 \u0026#39;SELECT id, marca, modelo, ram, almacenamiento FROM portatiles\u0026#39; 6) AS p(id INTEGER, marca VARCHAR, modelo VARCHAR, ram INTEGER, almacenamiento INTEGER) 7WHERE c.id = p.id 8AND p.almacenamiento \u0026gt;= 512 9ORDER BY c.año DESC; Operaciones de escritura remota Operaciones de escritura remota Para INSERT/UPDATE/DELETE (que no devuelven filas) se usa dblink_exec.\nInsertar un portátil en Postgre1 desde Postgre2:\n1SELECT dblink_exec( 2 \u0026#39;host=192.168.122.159 dbname=bd1 user=josemanuel1 password=josemanuel1\u0026#39;, 3 \u0026#39;INSERT INTO portatiles (marca, modelo, ram, almacenamiento, precio) 4 VALUES (\u0026#39;\u0026#39;MSI\u0026#39;\u0026#39;, \u0026#39;\u0026#39;Katana GF66\u0026#39;\u0026#39;, 16, 512, 1200.00)\u0026#39; 5); Verificar desde el mismo Postgre2:\n1SELECT * 2FROM dblink( 3 \u0026#39;host=192.168.122.159 dbname=bd1 user=josemanuel1 password=josemanuel1\u0026#39;, 4 \u0026#39;SELECT * FROM portatiles WHERE marca = \u0026#39;\u0026#39;MSI\u0026#39;\u0026#39;\u0026#39; 5) AS nuevo_portatil(id INTEGER, marca VARCHAR, modelo VARCHAR, ram INTEGER, almacenamiento INTEGER, precio NUMERIC); Con esto queda un enlace bidireccional funcionando entre dos servidores PostgreSQL, con consultas combinadas y escritura remota.\nInterconexión Oracle ↔ PostgreSQL Datos de partida:\nOracle1 → IP 192.168.122.221, usuario C##josemanuel1, tabla liga_espanola Postgre1 → IP 192.168.122.159, usuario josemanuel1, base bd1, tabla portatiles Parte 1: Oracle → PostgreSQL Oracle no tiene soporte nativo para PostgreSQL, así que se usa ODBC + Heterogeneous Services (HS): un estándar que actúa de \u0026ldquo;traductor universal\u0026rdquo; entre gestores distintos.\n1Cliente → Oracle → ODBC → PostgreSQL Instalar paquetes ODBC en Oracle1:\n1sudo apt update \u0026amp;\u0026amp; sudo apt install unixodbc odbc-postgresql unixODBC → implementación abierta de la API ODBC odbc-postgresql → driver específico para PostgreSQL Configurar el driver de PostgreSQL (comprobar antes dónde se instaló, con ls -l /usr/lib/x86_64-linux-gnu/odbc/; se usa psqlodbcw.so por ser el más moderno):\n1sudo nano /etc/odbcinst.ini 1[PostgreSQL] 2Description = PostgreSQL ODBC driver 3Driver = /usr/lib/x86_64-linux-gnu/odbc/psqlodbcw.so 4Setup = /usr/lib/x86_64-linux-gnu/odbc/libodbcpsqlS.so 5FileUsage = 1 **Crear el DSN Crear el DSN (Data Source Name) — define cómo conectarse a una base concreta:\n1sudo nano /etc/odbc.ini 1[POSTGRE1] 2Description = PostgreSQL Database 3Driver = PostgreSQL 4Servername = 192.168.122.159 5Username = josemanuel1 6Password = josemanuel1 7Port = 5432 8Database = bd1 Probar la conexión ODBC antes de tocar Oracle:\n1isql POSTGRE1 1SELECT * FROM portatiles; Configurar Heterogeneous Services (HS es lo que permite a Oracle hablar con bases no-Oracle vía ODBC):\n1sudo nano /opt/oracle/product/21c/dbhome_1/hs/admin/initPOSTGRE1.ora 1HS_FDS_CONNECT_INFO = POSTGRE1 2HS_FDS_TRACE_LEVEL = DEBUG 3HS_FDS_SHAREABLE_NAME = /usr/lib/x86_64-linux-gnu/odbc/psqlodbcw.so 4HS_LANGUAGE = AMERICAN_AMERICA.WE8ISO8859P1 5set ODBCINI=/etc/odbc.ini Configurar el listener de Oracle para que sepa invocar el gateway heterogéneo:\n1sudo nano /opt/oracle/product/21c/dbhome_1/network/admin/listener.ora 1#postgre 2SID_LIST_LISTENER= 3 (SID_LIST= 4 (SID_DESC= 5 (GLOBAL_DBNAME=POSTGRE1) 6 (SID_NAME=POSTGRE1) 7 (ORACLE_HOME=/opt/oracle/product/21c/dbhome_1) 8 (PROGRAM=dg4odbc) 9 (ENVS=\u0026#34;LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/odbc:/opt/oracle/product/21c/dbhome_1/lib,ODBCINI=/etc/odbc.ini\u0026#34;) 10 ) 11 ) **GLOBAL_DBNAME GLOBAL_DBNAME / SID_NAME → nombre del servicio (debe coincidir con tnsnames.ora) ORACLE_HOME → ruta de instalación de Oracle (crítico que sea correcta) PROGRAM → dg4odbc, el programa gateway para ODBC ENVS → variables de entorno necesarias (rutas de librerías ODBC y del odbc.ini) Configurar tnsnames.ora:\n1sudo nano /opt/oracle/product/21c/dbhome_1/network/admin/tnsnames.ora 1#postgres 2POSTGRE1 = 3 (DESCRIPTION= 4 (ADDRESS=(PROTOCOL=tcp)(HOST=192.168.122.221)(PORT=1521)) 5 (CONNECT_DATA=(SID=POSTGRE1)) 6 (HS=OK) 7 ) Aquí el HOST es la IP del listener de Oracle, no la de PostgreSQL. (HS=OK) marca que es un servicio heterogéneo.\nReiniciar el listener y comprobar el estado (el status UNKNOWN es normal para servicios heterogéneos, ya que no son instancias Oracle):\n1sudo -u oracle bash -c \u0026#39;export ORACLE_HOME=/opt/oracle/product/21c/dbhome_1 \u0026amp;\u0026amp; export PATH=$ORACLE_HOME/bin:$PATH \u0026amp;\u0026amp; lsnrctl stop\u0026#39; 2sudo -u oracle bash -c \u0026#39;export ORACLE_HOME=/opt/oracle/product/21c/dbhome_1 \u0026amp;\u0026amp; export PATH=$ORACLE_HOME/bin:$PATH \u0026amp;\u0026amp; lsnrctl start\u0026#39; 3lsnrctl status Crear el database link en Oracle: Crear el database link en Oracle:\n1sqlplus C##josemanuel1/josemanuel1 2 3CREATE DATABASE LINK link_postgres 4CONNECT TO \u0026#34;josemanuel1\u0026#34; IDENTIFIED BY \u0026#34;josemanuel1\u0026#34; 5USING \u0026#39;POSTGRE1\u0026#39;; Probar el enlace (el nombre de tabla va entre comillas porque PostgreSQL distingue mayúsculas/minúsculas):\n1SELECT * FROM \u0026#34;portatiles\u0026#34;@link_postgres; Consulta combinando ambos servidores (columnas de Postgre1 entre comillas porque Oracle las trata como case-sensitive a través del enlace):\n1SELECT o.EQUIPO, o.CIUDAD, o.PUNTOS, p.\u0026#34;marca\u0026#34;, p.\u0026#34;modelo\u0026#34;, p.\u0026#34;precio\u0026#34; 2FROM liga_espanola o, \u0026#34;portatiles\u0026#34;@link_postgres p 3WHERE o.ID = p.\u0026#34;id\u0026#34; 4ORDER BY o.PUNTOS DESC; INSERT / UPDATE / DELETE remotos desde Oracle hacia Postgre1:\n1-- INSERT 2INSERT INTO \u0026#34;portatiles\u0026#34;@link_postgres (\u0026#34;id\u0026#34;, \u0026#34;marca\u0026#34;, \u0026#34;modelo\u0026#34;, \u0026#34;ram\u0026#34;, \u0026#34;almacenamiento\u0026#34;, \u0026#34;precio\u0026#34;) 3VALUES (7, \u0026#39;Acer\u0026#39;, \u0026#39;Aspire 5\u0026#39;, 8, 512, 650); 4 5-- UPDATE 6UPDATE \u0026#34;portatiles\u0026#34;@link_postgres 7SET \u0026#34;precio\u0026#34; = 1550 8WHERE \u0026#34;marca\u0026#34; = \u0026#39;Lenovo\u0026#39;; 9 10-- DELETE 11DELETE FROM \u0026#34;portatiles\u0026#34;@link_postgres 12WHERE \u0026#34;id\u0026#34; = 7; Errores de Oracle a Postgres Al consultar la tabla remota daba ORA-28500 / ORA-02063: line preciendo a LINK_POSTGRES. Causa: conflicto de rutas ORACLE_HOME — Oracle buscaba initPOSTGRE1.ora en /opt/oracle/homes/OraDBHome21cEE/hs/admin/ cuando en realidad estaba creado en /opt/oracle/product/21c/dbhome_1/hs/admin/.\nSolución: usar una única ruta consistente para toda la configuración (/opt/oracle/product/21c/dbhome_1) Solución: usar una única ruta consistente para toda la configuración (/opt/oracle/product/21c/dbhome_1) y asegurarse de que el listener.ora tiene bien las variables LD_LIBRARY_PATH y ODBCINI. Si el listener no encuentra el ejecutable dg4odbc:\n1ls -l /opt/oracle/product/21c/dbhome_1/bin/dg4odbc 2sudo find /opt/oracle -name \u0026#34;dg4odbc\u0026#34; y se ajusta el ORACLE_HOME del listener.ora a la ruta correcta que devuelva ese comando.\nParte 2: PostgreSQL → Oracle PostgreSQL tampoco tiene soporte nativo para Oracle, así que se usa oracle_fdw (Foreign Data Wrapper), que necesita el Oracle Instant Client instalado en la máquina PostgreSQL.\n1Cliente → PostgreSQL → oracle_fdw → Oracle Paquetes necesarios en Postgre1:\n1sudo apt update \u0026amp;\u0026amp; sudo apt install libaio1t64 postgresql-server-dev-all build-essential git unzip wget libaio1t64 → E/S asíncrona que necesita Oracle postgresql-server-dev-all → herramientas de desarrollo para extensiones de PostgreSQL build-essential, git, unzip, wget → compilar y descargar Descargar Oracle Instant Client (como usuario postgres):\n1sudo su - postgres 2 3wget https://download.oracle.com/otn_software/linux/instantclient/211000/instantclient-basic-linux.x64-21.1.0.0.0.zip 4wget https://download.oracle.com/otn_software/linux/instantclient/211000/instantclient-sdk-linux.x64-21.1.0.0.0.zip 5wget https://download.oracle.com/otn_software/linux/instantclient/211000/instantclient-sqlplus-linux.x64-21.1.0.0.0.zip 6 7unzip instantclient-basic-linux.x64-21.1.0.0.0.zip 8unzip instantclient-sqlplus-linux.x64-21.1.0.0.0.zip 9unzip instantclient-sdk-linux.x64-21.1.0.0.0.zip 10rm *.zip Variables de entorno:\n1sudo nano .bashrc 1export ORACLE_HOME=/var/lib/postgresql/instantclient_21_1 2export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME 3export PATH=$PATH:$ORACLE_HOME 1source .bashrc 2which sqlplus Verificación de conectividad con Oracle. En Debian 13, sqlplus busca libaio.so.1 (nombre del paquete en Debian 12), pero el sistema tiene libaio.so.1t64 — hace falta un enlace simbólico:\n1sudo ln -s /lib/x86_64-linux-gnu/libaio.so.1t64 /lib/x86_64-linux-gnu/libaio.so.1 2sudo ldconfig 1sqlplus c##josemanuel1/josemanuel1@192.168.122.221/ORCLCDB 2select * from liga_espanola; 3exit; Compilar e instalar oracle_fdw:\n1cd /var/lib/postgresql 2git clone https://github.com/laurenz/oracle_fdw.git 3cd oracle_fdw 4sudo make ORACLE_HOME=/var/lib/postgresql/instantclient_21_1 5sudo make install Librerías compartidas:\n1echo \u0026#39;/var/lib/postgresql/instantclient_21_1\u0026#39; | sudo tee /etc/ld.so.conf.d/oracle.conf 2sudo ldconfig Creación del enlace en PostgreSQL:\n1psql -U josemanuel1 -d bd1 2CREATE EXTENSION oracle_fdw; 3\\dx Esquema para las tablas remotas y definición del servidor:\n1CREATE SCHEMA oracle_schema; 2 3CREATE SERVER servidor_oracle1 4FOREIGN DATA WRAPPER oracle_fdw 5OPTIONS (dbserver \u0026#39;//192.168.122.221/ORCLCDB\u0026#39;); Mapeo de usuarios y permisos:\n1CREATE USER MAPPING FOR josemanuel1 2SERVER servidor_oracle1 3OPTIONS (user \u0026#39;c##josemanuel1\u0026#39;, password \u0026#39;josemanuel1\u0026#39;); 4 5GRANT ALL PRIVILEGES ON SCHEMA oracle_schema TO josemanuel1; 6GRANT ALL PRIVILEGES ON FOREIGN SERVER servidor_oracle1 TO josemanuel1; Importar las tablas remotas (el esquema de Oracle va en MAYÚSCULAS y entre comillas, porque Oracle almacena los nombres así por defecto):\n1IMPORT FOREIGN SCHEMA \u0026#34;C##JOSEMANUEL1\u0026#34; 2FROM SERVER servidor_oracle1 3INTO oracle_schema; 4 5\\det oracle_schema.* Consultar datos combinados. El nombre de tabla mantiene el formato Oracle y va entre comillas (\u0026quot;liga_espanola\u0026quot;), pero los nombres de columna se convierten a minúsculas y no llevan comillas:\n1SELECT p.id, p.marca, p.modelo, p.precio, o.equipo, o.ciudad, o.puntos 2FROM portatiles p 3LEFT JOIN oracle_schema.\u0026#34;liga_espanola\u0026#34; o ON p.id = o.id 4ORDER BY p.id; INSERT / UPDATE / DELETE remotos desde PostgreSQL hacia Oracle:\n1-- INSERT 2INSERT INTO oracle_schema.\u0026#34;liga_espanola\u0026#34; (id, equipo, ciudad, puntos, partidos_jugados) 3VALUES (7, \u0026#39;Real Sociedad\u0026#39;, \u0026#39;San Sebastián\u0026#39;, 58, 30); 4 5-- UPDATE 6UPDATE oracle_schema.\u0026#34;liga_espanola\u0026#34; 7SET puntos = 80, partidos_jugados = 31 8WHERE equipo = \u0026#39;Real Madrid\u0026#39;; 9 10-- DELETE 11DELETE FROM oracle_schema.\u0026#34;liga_espanola\u0026#34; 12WHERE id = 7; Conclusión Con esto quedan cubiertos los tres escenarios de interconexión: Oracle↔Oracle (nativo, vía database links), PostgreSQL↔PostgreSQL (vía la extensión dblink) y la interconexión heterogénea Oracle↔PostgreSQL en ambos sentidos (ODBC + Heterogeneous Services para Oracle→Postgres, y oracle_fdw para Postgres→Oracle). En los tres casos se pueden hacer tanto consultas combinadas (JOIN entre servidores) como operaciones de escritura remota.\n","date":"4 de diciembre de 2025","img":"https://jmsaroca.es/images/interconexion-bd/portadaInterconexion.png","lang":"es","langName":"Español","largeImg":"","permalink":"/blog/2025/12/interconexi%C3%B3n-de-servidores-de-bases-de-datos/","series":[],"smallImg":"","tags":[{"title":"Oracle","url":"/tags/oracle/"},{"title":"PostgreSQL","url":"/tags/postgresql/"},{"title":"Bases De Datos","url":"/tags/bases-de-datos/"}],"timestamp":1764806400,"title":"Interconexión De Servidores De Bases De Datos"},{"authors":[],"categories":[],"content":" Cuándo se usa Si no recordamos la contraseña de root de una máquina Linux, podemos entrar igualmente editando el arranque en GRUB y forzando un shell como root, sin que nos pida ninguna contraseña. Es el mismo truco que usan las \u0026ldquo;single user mode\u0026rdquo; de otros sistemas: aprovechar que quien tiene acceso físico (o a la consola) al arranque, puede saltarse el login.\n1. Entrar en la edición de GRUB Al arrancar la máquina, en la pantalla de GRUB pulsamos la tecla E repetidamente hasta entrar en el modo de edición de la entrada de arranque.\n2. Cambiar ro por rw init=/bin/bash Localizamos la línea linux de la entrada de arranque, que termina en ro quiet (montaje en solo lectura). La modificamos para que monte en lectura-escritura y arranque directamente una shell en vez del proceso de init normal:\nSustituimos ro por:\n1rw init=/bin/bash 3. Arrancar con los cambios Pulsamos Ctrl + X (o F10, según el teclado) para arrancar con la línea modificada.\n4. Cambiar la contraseña Al arrancar, entramos directamente en una terminal como root, sin login. Hay que tener cuidado porque el teclado suele estar en distribución inglesa (sajona) hasta que carguemos el sistema completo, así que algunos caracteres (como la ñ o los símbolos) no salen donde se esperan.\nYa con acceso de root, cambiamos la contraseña con:\n1passwd Y confirmamos la nueva contraseña dos veces:\n5. Reiniciar Con la contraseña ya cambiada, reiniciamos la máquina de forma normal y podremos volver a hacer login con la nueva contraseña de root.\nNota: esto funciona porque quien tiene acceso a editar el arranque de GRUB tiene, de facto, control total de la máquina. Es la razón por la que en entornos de producción conviene proteger GRUB con contraseña y restringir el acceso físico o a la consola.\n","date":"18 de noviembre de 2025","img":"https://jmsaroca.es/images/resetear-password-root-grub/pagina07_img1.png","lang":"es","langName":"Español","largeImg":"","permalink":"/blog/2025/11/cambiar-la-contrase%C3%B1a-de-root-en-linux-si-no-te-acuerdas/","series":[],"smallImg":"","tags":[{"title":"Linux","url":"/tags/linux/"},{"title":"GRUB","url":"/tags/grub/"},{"title":"Seguridad","url":"/tags/seguridad/"},{"title":"Sysadmin","url":"/tags/sysadmin/"}],"timestamp":1763424000,"title":"Cambiar La Contraseña De Root en Linux Si No Te Acuerdas"},{"authors":[],"categories":[],"content":"Escenario con tres máquinas y dos redes, todo por línea de comandos:\nRouter: conecta las máquinas virtuales con Internet Servidor NAS (Alpine): almacena los archivos de la página web y los comparte por NFS Servidor Web (Ubuntu): monta el recurso NFS y sirve la página con Nginx Introducción Asociar la IP flotante Una vez creada la instancia de OpenStack, hay que asociar la IP flotante (en este caso, 172.22.201.35):\nAccedemos por ssh -A para poder saltar desde ahí a las otras máquinas. Metemos la clave pública en el authorized_keys:\n1sudo nano ~/.ssh/authorized_keys Configuración máquina openstack Actualizamos el sistema e instalamos los paquetes necesarios:\n1sudo apt update \u0026amp;\u0026amp; sudo apt upgrade -y 2 3sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virtinst virt-manager libguestfs-tools genisoimage cpu-checker Comprobamos que la CPU soporta virtualización:\n1sudo kvm-ok Tiene que decir \u0026ldquo;KVM acceleration can be used\u0026rdquo;.\nAñadimos el usuario a los grupos libvirt y kvm:\n1sudo usermod -aG libvirt $USER 2sudo usermod -aG kvm $USER Iniciamos el servicio y lo hacemos persistente:\n1sudo systemctl start libvirt 2sudo systemctl enable libvirtd 3sudo systemctl status libvirtd Creación de redes Borramos la red por defecto y creamos dos redes nuevas: una default con MTU 1442, y una red_intra muy aislada.\nParamos y borramos la red por defecto:\n1sudo virsh net-destroy default \u0026amp;\u0026amp; sudo virsh net-undefine default Nos vamos al directorio de redes de libvirt:\n1cd /usr/share/libvirt/networks 2sudo rm -r default.xml Red Default Creamos la red-default con MTU 1442:\n1sudo nano red-default.xml 1\u0026lt;network\u0026gt; 2 \u0026lt;name\u0026gt;default\u0026lt;/name\u0026gt; 3 \u0026lt;forward mode=\u0026#39;nat\u0026#39;/\u0026gt; 4 \u0026lt;bridge name=\u0026#39;virbr0\u0026#39; stp=\u0026#39;on\u0026#39; delay=\u0026#39;0\u0026#39;/\u0026gt; 5 \u0026lt;mtu size=\u0026#39;1442\u0026#39;/\u0026gt; 6 \u0026lt;ip address=\u0026#39;192.168.122.1\u0026#39; netmask=\u0026#39;255.255.255.0\u0026#39;\u0026gt; 7 \u0026lt;dhcp\u0026gt; 8 \u0026lt;range start=\u0026#39;192.168.122.2\u0026#39; end=\u0026#39;192.168.122.254\u0026#39;/\u0026gt; 9 \u0026lt;/dhcp\u0026gt; 10 \u0026lt;/ip\u0026gt; 11\u0026lt;/network\u0026gt; Definimos e iniciamos la red:\n1sudo virsh net-define red-default.xml 2sudo virsh net-start default 3sudo virsh net-autostart default Red Intra Lo mismo para la red intra, esta sin NAT ni DHCP — totalmente aislada:\n1sudo nano red-intra.xml 1\u0026lt;network\u0026gt; 2 \u0026lt;name\u0026gt;red_intra\u0026lt;/name\u0026gt; 3 \u0026lt;bridge name=\u0026#39;virbr1\u0026#39; stp=\u0026#39;on\u0026#39; delay=\u0026#39;0\u0026#39;/\u0026gt; 4 \u0026lt;mtu size=\u0026#39;1442\u0026#39;/\u0026gt; 5\u0026lt;/network\u0026gt; 1sudo virsh net-define red-intra.xml 2sudo virsh net-start red_intra 3sudo virsh net-autostart red_intra Verificamos que las dos redes se crearon bien:\n1sudo virsh net-list --all Para ver el XML de una red y verificar que está bien:\n1sudo virsh net-dumpxml red_intra Creación de máquinas y Configuración Creación de la máquina Router Instalación por red. Primero el disco:\n1sudo qemu-img create -f qcow2 /var/lib/libvirt/images/router-josemanuel.qcow2 10G 1sudo virt-install \\ 2 --name router-josemanuel \\ 3 --ram 1024 \\ 4 --vcpus 1 \\ 5 --disk path=/var/lib/libvirt/images/router-josemanuel.qcow2,size=10 \\ 6 --network network=default \\ 7 --network network=red_intra \\ 8 --location http://deb.debian.org/debian/dists/trixie/main/installer-amd64/ \\ 9 --graphics none \\ 10 --console pty,target_type=serial \\ 11 --extra-args \u0026#39;console=ttyS0,115200n8 serial\u0026#39; \\ 12 --autostart El router queda con dos interfaces: una en default (salida a Internet) y otra en red_intra (hacia el NAS y el servidor web).\nConfiguración router 1ssh -A user@192.168.122.170 Metemos la clave pública propia y la del profesor:\n1mkdir -p ~/.ssh 2chmod 700 ~/.ssh 3nano ~/.ssh/authorized_keys 4chmod 600 ~/.ssh/authorized_keys Configuramos sudo sin contraseña:\n1sudo visudo Al final del archivo:\n1user ALL=(ALL) NOPASSWD:ALL Configuramos las interfaces de red, dejando la de red_intra en estático (es la que conecta con el NAS y el servidor web):\n1sudo nano /etc/network/interfaces 1# red muy aislada 2auto enp2s0 3iface enp2s0 inet static 4 address 192.168.200.1 5 netmask 255.255.255.0 1sudo systemctl restart networking Comprobamos que se aplicó bien:\n1ip a Activación del IP forwarding 1echo \u0026#34;net.ipv4.ip_forward=1\u0026#34; | sudo tee -a /etc/sysctl.conf 2sudo sysctl -p Ya que estamos, configuramos el SNAT para que, al poner la gateway en el NAS y en el servidor web, tengan salida a Internet durante su instalación:\n1sudo apt update \u0026amp;\u0026amp; sudo apt install iptables-persistent -y SNAT 1sudo nano /etc/iptables/rules.v4 1*filter 2:INPUT ACCEPT [0:0] 3:FORWARD ACCEPT [0:0] 4:OUTPUT ACCEPT [0:0] 5COMMIT 6 7*nat 8:PREROUTING ACCEPT [0:0] 9:INPUT ACCEPT [0:0] 10:OUTPUT ACCEPT [0:0] 11:POSTROUTING ACCEPT [0:0] 12 13# SNAT 14-A POSTROUTING -s 192.168.200.0/24 -o enp1s0 -j MASQUERADE 15 16COMMIT Guardamos y aplicamos los cambios:\n1sudo systemctl restart netfilter-persistent Y verificamos las reglas con sudo iptables -t nat -L -n -v.\nUna vez hecho esto, salimos y seguimos con las dos máquinas que faltan: el NAS y el servidor web.\nCreación de la máquina Alpine Va a ser el servidor NAS. Descargamos la ISO:\n1wget https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/x86_64/alpine-standard-3.22.0-x86_64.iso 2sudo mv alpine-standard-3.22.0-x86_64.iso /var/lib/libvirt/images/ Creamos los discos (uno para el sistema, otro para los datos):\n1sudo qemu-img create -f qcow2 /var/lib/libvirt/images/nas-josemanuel.qcow2 10G 2sudo qemu-img create -f qcow2 /var/lib/libvirt/images/nas-josemanuel-data.qcow2 1G Instalación desde ISO:\n1sudo virt-install \\ 2 --name nas-josemanuel \\ 3 --ram 1024 \\ 4 --vcpus 1 \\ 5 --disk path=/var/lib/libvirt/images/nas-josemanuel.qcow2,size=10 \\ 6 --disk path=/var/lib/libvirt/images/nas-josemanuel-data.qcow2,size=1 \\ 7 --network network=red_intra \\ 8 --cdrom /var/lib/libvirt/images/alpine-standard-3.22.0-x86_64.iso \\ 9 --osinfo alpinelinux3.19 \\ 10 --graphics none \\ 11 --console pty,target_type=serial \\ 12 --autostart Configuración Alpine Login como root (la primera vez no pide contraseña):\nCon setup-alpine empezamos la configuración. Primero el hostname (nas-josemanuel):\nConfiguración de red, en estático, dentro de la red aislada:\nContraseña de root:\nZona horaria (Europe/Madrid):\nEl proxy lo dejamos vacío:\nEn el APK Mirror, si hay internet, simplemente enter; si no, se pone directamente la URL oficial en /etc/apk/repositories:\n1http://dl-cdn.alpinelinux.org/alpine/v3.22/main 2http://dl-cdn.alpinelinux.org/alpine/v3.22/community Creamos el usuario, su contraseña, y le decimos que instale OpenSSH:\nEspecificamos el disco de instalación:\nCtrl + ] para salir de la consola serie.\nConfiguración Alpine (post-instalación) 1ssh user@192.168.200.2 Desde root:\n1apk update \u0026amp;\u0026amp; apk add sudo nano lsblk Sudo sin contraseña con visudo (editor vi: i para insertar, Ctrl+Fin, Esc y :wq para guardar):\n1user ALL=(ALL) NOPASSWD:ALL Metemos también la clave del profesor:\n1mkdir -p /home/user/.ssh 2nano /home/user/.ssh/authorized_keys 3chown -R user:user /home/user/.ssh 4chmod 700 /home/user/.ssh 5chmod 600 /home/user/.ssh/authorized_keys Vemos el disco extra que añadimos:\n1lsblk Está sin montar. Lo formateamos:\n1mkfs.ext4 /dev/vdb 2mkdir -p /srv/data 3mount /dev/vdb /srv/data Para hacerlo persistente:\n1nano /etc/fstab 1/dev/vdb /srv/data ext4 defaults 0 0 Permisos y página web de prueba:\n1chmod 755 /srv/data 2nano /srv/data/index.html 1\u0026lt;!DOCTYPE html\u0026gt; 2\u0026lt;html lang=\u0026#34;es\u0026#34;\u0026gt; 3\u0026lt;head\u0026gt; 4 \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; 5 \u0026lt;meta name=\u0026#34;viewport\u0026#34; content=\u0026#34;width=device-width, initial-scale=1.0\u0026#34;\u0026gt; 6 \u0026lt;title\u0026gt;Jose manuel sierra\u0026lt;/title\u0026gt; 7 \u0026lt;style\u0026gt; 8 body { 9 font-family: Arial, sans-serif; 10 text-align: center; 11 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 12 color: white; 13 padding: 50px; 14 } 15 .container { 16 background: rgba(255, 255, 255, 0.1); 17 padding: 30px; 18 border-radius: 15px; 19 max-width: 600px; 20 margin: 0 auto; 21 } 22 h1 { font-size: 2.5em; } 23 p { font-size: 1.2em; } 24 \u0026lt;/style\u0026gt; 25\u0026lt;/head\u0026gt; 26\u0026lt;body\u0026gt; 27 \u0026lt;div class=\u0026#34;container\u0026#34;\u0026gt; 28 \u0026lt;h1\u0026gt;José Manuel Sierra Aroca\u0026lt;/h1\u0026gt; 29 \u0026lt;/div\u0026gt; 30\u0026lt;/body\u0026gt; 31\u0026lt;/html\u0026gt; Ahora instalaremos el servidor NFS 1apk add nfs-utils 2rc-update add nfs 3rc-service nfs start Verificamos:\n1rc-service nfs status Configuramos la exportación:\n1nano /etc/exports 1/srv/data 192.168.200.0/24(rw,sync,no_subtree_check,no_root_squash) Aplicamos y verificamos:\n1exportfs -ra 2exportfs -v Creación del Servidor web Ubuntu Servidor Web Ubuntu 24.04 con cloud-init. Descargamos la imagen cloud:\n1wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img 2sudo mv noble-server-cloudimg-amd64.img /var/lib/libvirt/images/ Disco con clonación enlazada (backing file, para no duplicar la imagen base):\n1sudo qemu-img create -f qcow2 -F qcow2 -b /var/lib/libvirt/images/noble-server-cloudimg-amd64.img /var/lib/libvirt/images/web-josemanuel.qcow2 Ficheros de configuración de cloud-init:\n1sudo nano meta-data 1instance-id: web-josemanuel 2local-hostname: web-josemanuel 1sudo nano user-data 1users: 2 - name: user 3 sudo: ALL=(ALL) NOPASSWD:ALL 4 shell: /bin/bash 5 lock_passwd: false 6 plain_text_passwd: ******** 7 ssh_authorized_keys: 8 - ssh-ed25519 AAAA... arooca27@gmail.com 9 - ssh-rsa AAAA... jose@debian 1sudo nano network-config 1version: 2 2ethernets: 3 enp1s0: 4 addresses: 5 - 192.168.200.3/24 6 routes: 7 - to: default 8 via: 192.168.200.1 9 nameservers: 10 addresses: 11 - 8.8.8.8 Generamos la ISO de cloud-init:\n1sudo genisoimage -output /var/lib/libvirt/images/web-josemanuel-cidata.iso -volid cidata -joliet -rock meta-data user-data network-config Y creamos la máquina:\n1sudo virt-install \\ 2 --name web-josemanuel \\ 3 --ram 1024 \\ 4 --vcpus 1 \\ 5 --disk path=/var/lib/libvirt/images/web-josemanuel.qcow2 \\ 6 --disk path=/var/lib/libvirt/images/web-josemanuel-cidata.iso,device=cdrom \\ 7 --network network=red_intra \\ 8 --osinfo ubuntu24.04 \\ 9 --graphics none \\ 10 --console pty,target_type=serial \\ 11 --import \\ 12 --autostart Ctrl + ] para salir.\nConfiguración servidor web Desde el router, por ssh:\n1ssh user@192.168.200.3 Instalamos Nginx:\n1sudo apt update \u0026amp;\u0026amp; sudo apt install -y nginx 2sudo systemctl enable nginx 3sudo systemctl start nginx 4sudo systemctl status nginx Comprobamos con curl:\n1curl http://localhost Ahora nos instalaremos el cliente NFS 1sudo apt install -y nfs-common 2sudo mkdir -p /var/www/data 3sudo mount -t nfs 192.168.200.2:/srv/data /var/www/data Verificamos el montaje y el contenido:\n1df -h | grep srv/data 1ls -la /var/www/data Montaje persistente:\n1sudo nano /etc/fstab 1192.168.200.2:/srv/data /var/www/data nfs defaults 0 0 Si mount -a no muestra nada, es que está bien:\n1sudo mount -a Virtual host 1sudo nano /etc/nginx/sites-available/data 1server { 2 listen 80; 3 server_name data.josemanuel.org; 4 5 root /var/www/data; 6 index index.html index.htm; 7 8 location / { 9 try_files $uri $uri/ =404; 10 } 11 12 access_log /var/log/nginx/data.josemanuel.org-access.log; 13 error_log /var/log/nginx/data.josemanuel.org-error.log; 14} Lo habilitamos y desactivamos el sitio por defecto (daba conflicto):\n1sudo ln -s /etc/nginx/sites-available/data /etc/nginx/sites-enabled/ 2sudo rm /etc/nginx/sites-enabled/default Verificamos y recargamos:\n1sudo nginx -t 1sudo systemctl reload nginx Probamos:\n1curl -H \u0026#34;Host: data.josemanuel.org\u0026#34; http://localhost Y ahora probamos desde el router:\n1curl http://192.168.200.3 Sabiendo que se ve desde el router, ahora hacemos un DNAT en el router para verlo desde la instancia de OpenStack, y por último un proxy inverso en la máquina de OpenStack para verlo desde el host físico. Se hace en este orden (de derecha a izquierda: host → máquina OpenStack → router → servidor web) para saber exactamente en qué capa falla algo si deja de funcionar.\nConfiguración DNAT en el router 1sudo nano /etc/iptables/rules.v4 1*nat 2:PREROUTING ACCEPT [0:0] 3:OUTPUT ACCEPT [0:0] 4:POSTROUTING ACCEPT [0:0] 5:INPUT ACCEPT [0:0] 6 7#SNAT 8-A POSTROUTING -s 192.168.200.0/24 -o enp1s0 -j MASQUERADE 9 10#DNAT 11-A PREROUTING -i enp1s0 -p tcp --dport 80 -j DNAT --to-destination 192.168.200.3:80 12 13COMMIT 1sudo systemctl restart netfilter-persistent.service Desde la máquina de OpenStack, curl a la IP del router para comprobar el DNAT:\n1curl http://192.168.122.170 La vemos, así que está bien. Ahora, en la máquina de OpenStack, configuramos el proxy inverso — instalamos Nginx:\n1sudo apt update \u0026amp;\u0026amp; sudo apt install -y nginx Archivo de configuración del proxy:\n1sudo nano /etc/nginx/sites-available/proxy-router 1server { 2 listen 80 default_server; 3 listen [::]:80 default_server; 4 5 server_name _; 6 7 location / { 8 proxy_pass http://192.168.122.170/; 9 proxy_redirect http://192.168.122.170/ /; 10 proxy_set_header Host $host; 11 proxy_set_header X-Real-IP $remote_addr; 12 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 13 proxy_set_header X-Forwarded-Proto $scheme; 14 } 15} Activamos la configuración y desactivamos el sitio por defecto:\n1sudo ln -s /etc/nginx/sites-available/proxy-router /etc/nginx/sites-enabled/ 2sudo rm /etc/nginx/sites-enabled/default Verificamos y reiniciamos:\n1sudo nginx -t 2sudo systemctl restart nginx 3sudo systemctl status nginx Comprobación Como antes se modificó el index.html tanto del servidor web como el de la propia máquina de OpenStack, sirve para distinguir cuál de las dos páginas se está viendo realmente al entrar por la IP — y así confirmar que es la del servidor web y no la de OpenStack por defecto (que es justo lo que no queremos). Para ello también se configuró el /etc/hosts del host físico para poder resolver por nombre:\nY si buscamos desde el host, por navegador, vemos que nos muestra la página del servidor web y no la de OpenStack:\nRedimensionar disco NAS Con todo funcionando, redimensionamos el disco NAS. Hay que apagar la máquina primero, porque no se puede redimensionar en caliente:\n1sudo virsh shutdown nas-josemanuel 2sudo virsh list --all Redimensionamos la imagen del disco de datos:\n1sudo qemu-img resize /var/lib/libvirt/images/nas-josemanuel-data.qcow2 2G 2sudo qemu-img info /var/lib/libvirt/images/nas-josemanuel-data.qcow2 Iniciamos el NAS de nuevo y nos conectamos:\n1sudo virsh start nas-josemanuel 2ssh -A user@192.168.122.170 3ssh user@192.168.200.2 Vemos el disco ya de 2GB:\n1fdisk -l /dev/vdb Ahora redimensionamos también el sistema de archivos. Instalamos el paquete necesario:\n1sudo apk add e2fsprogs-extra 2sudo resize2fs /dev/vdb Miramos el espacio disponible:\n1df -h | grep /srv/data Creación de SNAPSHOTS Desde la instancia de OpenStack, un snapshot por máquina:\n1sudo virsh snapshot-create-as router-josemanuel \\ 2 snapshot-configuracion \\ 3 \u0026#34;Snapshot después de configurar el router\u0026#34; 4 5sudo virsh snapshot-create-as nas-josemanuel \\ 6 snapshot-configuracion \\ 7 \u0026#34;Snapshot después de configurar NFS y redimensionar disco\u0026#34; 8 9sudo virsh snapshot-create-as web-josemanuel \\ 10 snapshot-configuracion \\ 11 \u0026#34;Snapshot después de configurar nginx y montar NFS\u0026#34; Listamos todos los snapshots para comprobar que se crearon bien:\n1sudo virsh snapshot-list router-josemanuel 2sudo virsh snapshot-list nas-josemanuel 3sudo virsh snapshot-list web-josemanuel ","date":"11 de noviembre de 2025","img":"https://jmsaroca.es/images/router-nas-web-openstack/portadaopens.png","lang":"es","langName":"Español","largeImg":"","permalink":"/blog/2025/11/escenario-en-openstack-router-nas-con-nfs-y-servidor-web-con-proxy-inverso/","series":[],"smallImg":"","tags":[{"title":"OpenStack","url":"/tags/openstack/"},{"title":"NFS","url":"/tags/nfs/"},{"title":"Nginx","url":"/tags/nginx/"},{"title":"Alpine","url":"/tags/alpine/"},{"title":"Redes","url":"/tags/redes/"}],"timestamp":1762819200,"title":"Escenario en OpenStack: Router, NAS Con NFS Y Servidor Web Con Proxy Inverso"},{"authors":[],"categories":[],"content":"Soy José Manuel Sierra, Administrador de Sistemas Informáticos en Red (ASIR).\nDurante mi formación y prácticas he trabajado sobretodo con sistemas Linux, redes, automatización de infraestructura, contenedores, seguridad y base de datos. Me interesa el mundo DevOps, SysAdmin y la Ciberseguridad.\nEn este sitio documento los trabajos mas interesantes que he realizado durante el curso(todos en entornos locales).\nAprendo rápido, me adapto bien y no tengo miedo a los entornos nuevos, busco mi primera oportunidad donde pueda aportar.\nContacto Cualquier duda o si quieres contactarme y saber mas de mí:\nGitHub: github.com/iL3RO LinkedIn: linkedin.com/in/jmsa27 Email: arooca16@gmail.com ","date":"1 de enero de 1","img":"","lang":"es","langName":"Español","largeImg":"","permalink":"/about/","series":[],"smallImg":"","tags":[],"timestamp":-62135596800,"title":"Acerca De Mí"}]
